Skip to content

Commit

Permalink
Merge pull request #16 from jason-crabnebula/poc-ondrop
Browse files Browse the repository at this point in the history
feat: add ondrop callback
  • Loading branch information
lucasfernog-crabnebula authored Dec 11, 2023
2 parents c1113d7 + 5f99d87 commit dc4d73e
Show file tree
Hide file tree
Showing 16 changed files with 349 additions and 46 deletions.
5 changes: 5 additions & 0 deletions .changes/api-result-callback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@crabnebula/tauri-plugin-drag": minor
---

The `startDrag` function now takes an argument for a callback function for the operation result (either `Dragged` or `Cancelled`).
6 changes: 6 additions & 0 deletions .changes/result-callback.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"drag": minor
"tauri-plugin-drag": minor
---

The `start_drag` function now takes a closure for the operation result (either `DragResult::Dropped` or `DragResult::Cancelled`).
33 changes: 33 additions & 0 deletions .github/workflows/audit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Audit Rust

on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *"
push:
branches:
- main
paths:
- ".github/workflows/audit.yml"
- "**/Cargo.lock"
- "**/Cargo.toml"
pull_request:
branches:
- main
paths:
- ".github/workflows/audit.yml"
- "**/Cargo.lock"
- "**/Cargo.toml"

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: rustsec/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
81 changes: 81 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Check

on:
push:
branches:
- main
paths:
- ".github/workflows/check.yml"
- "**/*.rs"
- "**/Cargo.toml"
pull_request:
branches:
- main
paths:
- ".github/workflows/check.yml"
- "**/*.rs"
- "**/Cargo.toml"

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
rustfmt:
if: ${{ !startsWith(github.head_ref, 'renovate/') }}
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --all -- --check

clippy:
if: ${{ !startsWith(github.head_ref, 'renovate/') }}

strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]

runs-on: ${{ matrix.platform }}

steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: install webkit2gtk
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y webkit2gtk-4.0
- uses: Swatinem/rust-cache@v2
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings

rust-test:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]

runs-on: ${{ matrix.platform }}

steps:
- uses: actions/checkout@v4
- name: install webkit2gtk
if: matrix.platform == 'ubuntu-latest'
run: |
sudo apt-get update
sudo apt-get install -y webkit2gtk-4.0
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo test --workspace --lib --bins --tests --benches --all-features --no-fail-fast

deny:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v1
21 changes: 20 additions & 1 deletion crates/drag/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
//! &window,
//! item,
//! preview_icon,
//! |result| {
//! println!("drag result: {result:?}");
//! }
//! );
//! ```
//!
Expand All @@ -57,6 +60,9 @@
//! &webview.window(),
//! item,
//! preview_icon,
//! |result| {
//! println!("drag result: {result:?}");
//! }
//! );
//! ```
//!
Expand All @@ -69,7 +75,9 @@
//! let preview_icon = drag::Image::File("./examples/icon.png".into());
//!
//! # #[cfg(not(target_os = "linux"))]
//! let _ = drag::start_drag(&window, item, preview_icon);
//! let _ = drag::start_drag(&window, item, preview_icon, |result| {
//! println!("drag result: {result:?}");
//! });
//! ```
#[cfg(target_os = "macos")]
Expand Down Expand Up @@ -100,9 +108,19 @@ pub enum Error {
#[cfg(target_os = "linux")]
#[error("empty drag target list")]
EmptyTargetList,
#[error("failed to drop items")]
FailedToDrop,
}

#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum DragResult {
Dropped,
Cancel,
}

/// Item to be dragged.
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
pub enum DragItem {
Expand All @@ -113,6 +131,7 @@ pub enum DragItem {
}

/// An image definition.
#[derive(Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(untagged))]
pub enum Image {
Expand Down
87 changes: 71 additions & 16 deletions crates/drag/src/platform_impl/gtk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,48 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use crate::{DragItem, Image};
use gdkx11::gdk;
use std::{
rc::Rc,
sync::{Arc, Mutex},
};

use crate::{DragItem, DragResult, Image};
use gdkx11::{
gdk,
glib::{ObjectExt, SignalHandlerId},
};
use gtk::{
gdk_pixbuf,
prelude::{DragContextExtManual, PixbufLoaderExt, WidgetExt, WidgetExtManual},
};

pub fn start_drag(
pub fn start_drag<F: Fn(DragResult) + Send + 'static>(
window: &gtk::ApplicationWindow,
item: DragItem,
image: Image,
on_drop_callback: F,
) -> crate::Result<()> {
let handler_ids: Arc<Mutex<Vec<SignalHandlerId>>> = Arc::new(Mutex::new(vec![]));

window.drag_source_set(gdk::ModifierType::BUTTON1_MASK, &[], gdk::DragAction::COPY);

match item {
DragItem::Files(paths) => {
window.drag_source_add_uri_targets();

window.connect_drag_data_get(move |_, _, data, _, _| {
let uris: Vec<String> = paths
.iter()
.map(|path| format!("file://{}", path.display()))
.collect();
let uris: Vec<&str> = uris.iter().map(|s| s.as_str()).collect();
data.set_uris(&uris);
});
handler_ids
.lock()
.unwrap()
.push(window.connect_drag_data_get(move |_, _, data, _, _| {
let uris: Vec<String> = paths
.iter()
.map(|path| format!("file://{}", path.display()))
.collect();
let uris: Vec<&str> = uris.iter().map(|s| s.as_str()).collect();
data.set_uris(&uris);
}));
}
}

window.connect_drag_end(|this, _| {
this.drag_source_unset();
});

if let Some(target_list) = &window.drag_source_get_target_list() {
if let Some(drag_context) = window.drag_begin_with_coordinates(
target_list,
Expand All @@ -44,6 +53,10 @@ pub fn start_drag(
-1,
-1,
) {
let callback = Rc::new(on_drop_callback);
on_drop_cancel(callback.clone(), window, &handler_ids, &drag_context);
on_drop_performed(callback, window, &handler_ids, &drag_context);

let icon_pixbuf: Option<gdk_pixbuf::Pixbuf> = match &image {
Image::Raw(data) => image_binary_to_pixbuf(data),
Image::File(path) => match std::fs::read(path) {
Expand Down Expand Up @@ -73,3 +86,45 @@ fn image_binary_to_pixbuf(data: &[u8]) -> Option<gdk_pixbuf::Pixbuf> {
.and_then(|_| loader.pixbuf().ok_or(()))
.ok()
}

fn clear_signal_handlers(window: &gtk::ApplicationWindow, handler_ids: &mut Vec<SignalHandlerId>) {
for handler_id in handler_ids.drain(..) {
window.disconnect(handler_id);
}
}

fn on_drop_cancel<F: Fn(DragResult) + Send + 'static>(
callback: Rc<F>,
window: &gtk::ApplicationWindow,
handler_ids: &Arc<Mutex<Vec<SignalHandlerId>>>,
drag_context: &gdk::DragContext,
) {
let window = window.clone();
let handler_ids = handler_ids.clone();

drag_context.connect_cancel(move |_, _| {
let handler_ids = &mut handler_ids.lock().unwrap();
clear_signal_handlers(&window, handler_ids);
window.drag_source_unset();

callback(DragResult::Cancel);
});
}

fn on_drop_performed<F: Fn(DragResult) + Send + 'static>(
callback: Rc<F>,
window: &gtk::ApplicationWindow,
handler_ids: &Arc<Mutex<Vec<SignalHandlerId>>>,
drag_context: &gdk::DragContext,
) {
let window = window.clone();
let handler_ids = handler_ids.clone();

drag_context.connect_drop_performed(move |_, _| {
let handler_ids = &mut handler_ids.lock().unwrap();
clear_signal_handlers(&window, handler_ids);
window.drag_source_unset();

callback(DragResult::Dropped);
});
}
36 changes: 34 additions & 2 deletions crates/drag/src/platform_impl/macos/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT

use std::ffi::c_void;

use cocoa::{
appkit::{NSAlignmentOptions, NSApp, NSEvent, NSEventModifierFlags, NSEventType, NSImage},
base::{id, nil},
Expand All @@ -13,7 +15,7 @@ use objc::{
};
use raw_window_handle::{HasRawWindowHandle, RawWindowHandle};

use crate::{DragItem, Image};
use crate::{DragItem, DragResult, Image};

const UTF8_ENCODING: usize = 4;

Expand All @@ -29,10 +31,11 @@ unsafe fn new_nsstring(s: &str) -> id {
ns_string
}

pub fn start_drag<W: HasRawWindowHandle>(
pub fn start_drag<W: HasRawWindowHandle, F: Fn(DragResult) + Send + 'static>(
handle: &W,
item: DragItem,
image: Image,
on_drop_callback: F,
) -> crate::Result<()> {
if let RawWindowHandle::AppKit(w) = handle.raw_window_handle() {
unsafe {
Expand Down Expand Up @@ -103,11 +106,17 @@ pub fn start_drag<W: HasRawWindowHandle>(
let cls = ClassDecl::new("DragRsSource", class!(NSObject));
let cls = match cls {
Some(mut cls) => {
cls.add_ivar::<*mut c_void>("on_drop_ptr");
cls.add_method(
sel!(draggingSession:sourceOperationMaskForDraggingContext:),
dragging_session
as extern "C" fn(&Object, Sel, id, NSUInteger) -> NSUInteger,
);
cls.add_method(
sel!(draggingSession:endedAtPoint:operation:),
dragging_session_end
as extern "C" fn(&Object, Sel, id, NSPoint, NSUInteger),
);

extern "C" fn dragging_session(
_this: &Object,
Expand All @@ -124,6 +133,26 @@ pub fn start_drag<W: HasRawWindowHandle>(
}
}

extern "C" fn dragging_session_end(
this: &Object,
_: Sel,
_dragging_session: id,
_ended_at_point: NSPoint,
operation: NSUInteger,
) {
unsafe {
let callback = this.get_ivar::<*mut c_void>("on_drop_ptr");

let callback = &*(*callback as *mut Box<dyn Fn(DragResult)>);
if operation == 0 {
// NSDragOperationNone
callback(DragResult::Cancel);
} else {
callback(DragResult::Dropped);
}
}
}

cls.register()
}
None => Class::get("DragRsSource").expect("Failed to get the class definition"),
Expand All @@ -132,6 +161,9 @@ pub fn start_drag<W: HasRawWindowHandle>(
let source: id = msg_send![cls, alloc];
let source: id = msg_send![source, init];

let callback_ptr = Box::into_raw(Box::new(on_drop_callback));
(*source).set_ivar("on_drop_ptr", callback_ptr as *mut _ as *mut c_void);

let _: () = msg_send![ns_view, beginDraggingSessionWithItems: file_items event: drag_event source: source];
}

Expand Down
Loading

0 comments on commit dc4d73e

Please sign in to comment.