Skip to content

Commit

Permalink
refactor!: refactor drag and drop events (#1187)
Browse files Browse the repository at this point in the history
  • Loading branch information
amrbashir authored Mar 13, 2024
1 parent ae64a09 commit 5789bf7
Show file tree
Hide file tree
Showing 11 changed files with 317 additions and 220 deletions.
10 changes: 10 additions & 0 deletions .changes/file-drop-enhancements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"wry": "patch"
---

**Breaking change**: Refactored the file-drop handling on the webview for better representation of the actual drag and drop operation:

- Renamed `file-drop` cargo feature flag to `drag-drop`.
- Removed `FileDropEvent` enum and replaced with a new `DragDropEvent` enum.
- Renamed `WebViewAttributes::file_drop_handler` field to `WebViewAttributes::drag_drop_handler`.
- Renamed `WebViewAttributes::with_file_drop_handler` method to `WebViewAttributes::with_drag_drop_handler`.
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ exclude = [ "/.changes", "/.github", "/audits", "/wry-logo.svg" ]

[package.metadata.docs.rs]
no-default-features = true
features = [ "file-drop", "protocol", "os-webview" ]
features = [ "drag-drop", "protocol", "os-webview" ]
targets = [
"x86_64-unknown-linux-gnu",
"x86_64-pc-windows-msvc",
Expand All @@ -25,9 +25,9 @@ rustc-args = [ "--cfg", "docsrs" ]
rustdoc-args = [ "--cfg", "docsrs" ]

[features]
default = [ "file-drop", "objc-exception", "protocol", "os-webview" ]
default = [ "drag-drop", "objc-exception", "protocol", "os-webview" ]
objc-exception = [ "objc/exception" ]
file-drop = [ ]
drag-drop = [ ]
protocol = [ ]
devtools = [ ]
transparent = [ ]
Expand Down
19 changes: 18 additions & 1 deletion examples/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,24 @@ fn main() -> wry::Result<()> {
WebViewBuilder::new_gtk(vbox)
};

let _webview = builder.with_url("http://tauri.app").build()?;
let _webview = builder
.with_url("http://tauri.app")
.with_drag_drop_handler(|e| {
match e {
wry::DragDropEvent::Enter { paths, position } => {
println!("DragEnter: {position:?} {paths:?} ")
}
wry::DragDropEvent::Over { position } => println!("DragOver: {position:?} "),
wry::DragDropEvent::Drop { paths, position } => {
println!("DragDrop: {position:?} {paths:?} ")
}
wry::DragDropEvent::Leave => println!("DragLeave"),
_ => {}
}

true
})
.build()?;

event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
Expand Down
68 changes: 39 additions & 29 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@
//! for the crate to work. This feature was added in preparation of other ports like cef and servo.
//! - `protocol` (default): Enables [`WebViewBuilder::with_custom_protocol`] to define custom URL scheme for handling tasks like
//! loading assets.
//! - `file-drop` (default): Enables [`WebViewBuilder::with_file_drop_handler`] to control the behaviour when there are files
//! - `drag-drop` (default): Enables [`WebViewBuilder::with_drag_drop_handler`] to control the behaviour when there are files
//! interacting with the window.
//! - `devtools`: Enables devtools on release builds. Devtools are always enabled in debug builds.
//! On **macOS**, enabling devtools, requires calling private apis so you should not enable this flag in release
Expand All @@ -191,10 +191,11 @@
//! libraries and prevent from building documentation on doc.rs fails.
//! - `linux-body`: Enables body support of custom protocol request on Linux. Requires
//! webkit2gtk v2.40 or above.
//! - `tracing`: enables [tracing] for `evaluate_script`, `ipc_handler` and `custom_protocols.
//! - `tracing`: enables [`tracing`] for `evaluate_script`, `ipc_handler` and `custom_protocols.
//!
//! [`tao`]: https://docs.rs/tao
//! [`winit`]: https://docs.rs/winit
//! [`tracing`]: https://docs.rs/tracing

#![allow(clippy::new_without_default)]
#![allow(clippy::default_constructed_unit_structs)]
Expand Down Expand Up @@ -376,17 +377,18 @@ pub struct WebViewAttributes {
/// using `window.ipc.postMessage("insert_message_here")` to host Rust code.
pub ipc_handler: Option<Box<dyn Fn(Request<String>)>>,

/// A handler closure to process incoming [`FileDropEvent`] of the webview.
/// A handler closure to process incoming [`DragDropEvent`] of the webview.
///
/// # Blocking OS Default Behavior
/// Return `true` in the callback to block the OS' default behavior of handling a file drop.
/// Return `true` in the callback to block the OS' default behavior.
///
/// Note, that if you do block this behavior, it won't be possible to drop files on `<input type="file">` forms.
/// Also note, that it's not possible to manually set the value of a `<input type="file">` via JavaScript for security reasons.
#[cfg(feature = "file-drop")]
pub file_drop_handler: Option<Box<dyn Fn(FileDropEvent) -> bool>>,
#[cfg(not(feature = "file-drop"))]
file_drop_handler: Option<Box<dyn Fn(FileDropEvent) -> bool>>,
#[cfg(feature = "drag-drop")]
#[cfg_attr(docsrs, doc(cfg(feature = "drag-drop")))]
pub drag_drop_handler: Option<Box<dyn Fn(DragDropEvent) -> bool>>,
#[cfg(not(feature = "drag-drop"))]
drag_drop_handler: Option<Box<dyn Fn(DragDropEvent) -> bool>>,

/// A navigation handler to decide if incoming url is allowed to navigate.
///
Expand Down Expand Up @@ -505,7 +507,7 @@ impl Default for WebViewAttributes {
initialization_scripts: vec![],
custom_protocols: vec![],
ipc_handler: None,
file_drop_handler: None,
drag_drop_handler: None,
navigation_handler: None,
download_started_handler: None,
download_completed_handler: None,
Expand Down Expand Up @@ -560,7 +562,7 @@ impl<'a> WebViewBuilder<'a> {
/// by callling [`gtk::init`] and advance its loop alongside your event loop using [`gtk::main_iteration_do`].
/// Checkout the [Platform Considerations](https://docs.rs/wry/latest/wry/#platform-considerations) section in the crate root documentation.
/// - **Windows**: The webview will auto-resize when the passed handle is resized.
/// - **Linux (X11)**: Unlike macOS and Windows, the webview will not auto-resize and you'll need to call [`WebView::set_size`] manually.
/// - **Linux (X11)**: Unlike macOS and Windows, the webview will not auto-resize and you'll need to call [`WebView::set_bounds`] manually.
///
/// # Panics:
///
Expand Down Expand Up @@ -768,19 +770,20 @@ impl<'a> WebViewBuilder<'a> {
self
}

/// Set a handler closure to process incoming [`FileDropEvent`] of the webview.
/// Set a handler closure to process incoming [`DragDropEvent`] of the webview.
///
/// # Blocking OS Default Behavior
/// Return `true` in the callback to block the OS' default behavior of handling a file drop.
/// Return `true` in the callback to block the OS' default behavior.
///
/// Note, that if you do block this behavior, it won't be possible to drop files on `<input type="file">` forms.
/// Also note, that it's not possible to manually set the value of a `<input type="file">` via JavaScript for security reasons.
#[cfg(feature = "file-drop")]
pub fn with_file_drop_handler<F>(mut self, handler: F) -> Self
#[cfg(feature = "drag-drop")]
#[cfg_attr(docsrs, doc(cfg(feature = "drag-drop")))]
pub fn with_drag_drop_handler<F>(mut self, handler: F) -> Self
where
F: Fn(FileDropEvent) -> bool + 'static,
F: Fn(DragDropEvent) -> bool + 'static,
{
self.attrs.file_drop_handler = Some(Box::new(handler));
self.attrs.drag_drop_handler = Some(Box::new(handler));
self
}

Expand All @@ -789,7 +792,7 @@ impl<'a> WebViewBuilder<'a> {
///
/// ## Note
///
/// Data URLs are not supported, use [`html`](Self::html) option instead.
/// Data URLs are not supported, use [`html`](Self::with_html) option instead.
pub fn with_url_and_headers(mut self, url: impl Into<String>, headers: http::HeaderMap) -> Self {
self.attrs.url = Some(url.into());
self.attrs.headers = Some(headers);
Expand All @@ -801,7 +804,7 @@ impl<'a> WebViewBuilder<'a> {
///
/// ## Note
///
/// Data URLs are not supported, use [`html`](Self::html) option instead.
/// Data URLs are not supported, use [`html`](Self::with_html) option instead.
pub fn with_url(mut self, url: impl Into<String>) -> Self {
self.attrs.url = Some(url.into());
self.attrs.headers = None;
Expand Down Expand Up @@ -1067,7 +1070,7 @@ pub trait WebViewBuilderExtWindows {
/// `false`, it disables all accelerator keys that access features specific to a web browser.
/// The default value is `true`. See the following link to know more details.
///
/// https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings#arebrowseracceleratorkeysenabled
/// <https://learn.microsoft.com/en-us/microsoft-edge/webview2/reference/winrt/microsoft_web_webview2_core/corewebview2settings#arebrowseracceleratorkeysenabled>
fn with_browser_accelerator_keys(self, enabled: bool) -> Self;

/// Specifies the theme of webview2. This affects things like `prefers-color-scheme`.
Expand Down Expand Up @@ -1250,7 +1253,7 @@ impl WebView {
/// by callling [`gtk::init`] and advance its loop alongside your event loop using [`gtk::main_iteration_do`].
/// Checkout the [Platform Considerations](https://docs.rs/wry/latest/wry/#platform-considerations) section in the crate root documentation.
/// - **macOS / Windows**: The webview will auto-resize when the passed handle is resized.
/// - **Linux (X11)**: Unlike macOS and Windows, the webview will not auto-resize and you'll need to call [`WebView::set_size`] manually.
/// - **Linux (X11)**: Unlike macOS and Windows, the webview will not auto-resize and you'll need to call [`WebView::set_bounds`] manually.
///
/// # Panics:
///
Expand Down Expand Up @@ -1410,24 +1413,31 @@ impl WebView {
}
}

/// An event describing the files drop on the webview.
/// An event describing drag and drop operations on the webview.
#[non_exhaustive]
#[derive(Debug, serde::Serialize, Clone)]
pub enum FileDropEvent {
/// The file(s) have been dragged onto the window, but have not been dropped yet.
Hovered {
pub enum DragDropEvent {
/// A drag operation has entered the webview.
Enter {
/// List of paths that are being dragged onto the webview.
paths: Vec<PathBuf>,
/// The position of the mouse cursor.
/// Position of the drag operation, relative to the webview top-left corner.
position: (i32, i32),
},
/// A drag operation is moving over the window.
Over {
/// Position of the drag operation, relative to the webview top-left corner.
position: (i32, i32),
},
/// The file(s) have been dropped onto the window.
Dropped {
Drop {
/// List of paths that are being dropped onto the window.
paths: Vec<PathBuf>,
/// The position of the mouse cursor.
/// Position of the drag operation, relative to the webview top-left corner.
position: (i32, i32),
},
/// The file drop was aborted.
Cancelled,
/// The drag operation has been cancelled or left the window.
Leave,
}

/// Get WebView/Webkit version on current platform.
Expand Down
125 changes: 125 additions & 0 deletions src/webkitgtk/drag_drop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use std::{
cell::{Cell, UnsafeCell},
path::PathBuf,
rc::Rc,
};

use gtk::{glib::GString, prelude::*};
use webkit2gtk::WebView;

use crate::DragDropEvent;

struct DragDropController {
paths: UnsafeCell<Option<Vec<PathBuf>>>,
has_entered: Cell<bool>,
position: Cell<(i32, i32)>,
handler: Box<dyn Fn(DragDropEvent) -> bool>,
}

impl DragDropController {
fn new(handler: Box<dyn Fn(DragDropEvent) -> bool>) -> Self {
Self {
handler,
paths: UnsafeCell::new(None),
has_entered: Cell::new(false),
position: Cell::new((0, 0)),
}
}

fn store_paths(&self, paths: Vec<PathBuf>) {
unsafe { *self.paths.get() = Some(paths) };
}

fn take_paths(&self) -> Option<Vec<PathBuf>> {
unsafe { &mut *self.paths.get() }.take()
}

fn store_position(&self, position: (i32, i32)) {
self.position.replace(position);
}

fn enter(&self) {
self.has_entered.set(true);
}

fn has_entered(&self) -> bool {
self.has_entered.get()
}

fn leave(&self) {
self.has_entered.set(false);
}

fn call(&self, event: DragDropEvent) -> bool {
(self.handler)(event)
}
}

pub(crate) fn connect_drag_event(webview: &WebView, handler: Box<dyn Fn(DragDropEvent) -> bool>) {
let controller = Rc::new(DragDropController::new(handler));

{
let controller = controller.clone();
webview.connect_drag_data_received(move |_, _, _, _, data, info, _| {
if info == 2 {
let uris = data.uris();
let paths = uris.iter().map(path_buf_from_uri).collect::<Vec<_>>();
controller.enter();
controller.call(DragDropEvent::Enter {
paths: paths.clone(),
position: controller.position.get(),
});
controller.store_paths(paths);
}
});
}

{
let controller = controller.clone();
webview.connect_drag_motion(move |_, _, x, y, _| {
if controller.has_entered() {
controller.call(DragDropEvent::Over { position: (x, y) });
} else {
controller.store_position((x, y));
}
false
});
}

{
let controller = controller.clone();
webview.connect_drag_drop(move |_, _, x, y, _| {
if controller.has_entered() {
if let Some(paths) = controller.take_paths() {
controller.leave();
return controller.call(DragDropEvent::Drop {
paths,
position: (x, y),
});
}
}

false
});
}

webview.connect_drag_leave(move |_, _, time| {
if time == 0 {
controller.leave();
controller.call(DragDropEvent::Leave);
}
});
}

fn path_buf_from_uri(gstr: &GString) -> PathBuf {
let path = gstr.as_str();
let path = path.strip_prefix("file://").unwrap_or(path);
let path = percent_encoding::percent_decode(path.as_bytes())
.decode_utf8_lossy()
.to_string();
PathBuf::from(path)
}
Loading

0 comments on commit 5789bf7

Please sign in to comment.