Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!: refactor drag and drop events #1187

Merged
merged 7 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading