From 4ad6c6cf666f2fcb1baa5a32865aac7f94a8e67c Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 29 Mar 2024 16:27:34 -0400 Subject: [PATCH] feat: winit dnd --- Cargo.toml | 31 ++- core/Cargo.toml | 2 + core/src/clipboard.rs | 85 ++++++- core/src/event.rs | 6 + core/src/widget/tree.rs | 15 ++ runtime/Cargo.toml | 3 +- runtime/src/command/action.rs | 8 + runtime/src/dnd.rs | 162 +++++++++++++ runtime/src/lib.rs | 1 + runtime/src/user_interface.rs | 5 + sctk/src/event_loop/mod.rs | 24 +- sctk/src/event_loop/state.rs | 3 +- sctk/src/handlers/data_device/data_device.rs | 53 ++-- sctk/src/handlers/data_device/data_offer.rs | 4 +- sctk/src/handlers/seat/keyboard.rs | 1 + src/application.rs | 2 +- src/lib.rs | 3 +- src/sandbox.rs | 3 +- widget/Cargo.toml | 2 + winit/Cargo.toml | 1 + winit/src/application.rs | 202 +++++++++++++++- winit/src/clipboard.rs | 138 +++++++++-- winit/src/lib.rs | 1 - winit/src/multi_window.rs | 241 ++++++++++++++++++- winit/src/proxy.rs | 31 ++- 25 files changed, 950 insertions(+), 77 deletions(-) create mode 100644 runtime/src/dnd.rs diff --git a/Cargo.toml b/Cargo.toml index d6e3ede884..42e9826f2a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,11 +51,22 @@ multi-window = ["iced_winit?/multi-window"] # Enables the advanced module advanced = [] # Enables the `accesskit` accessibility library -a11y = ["iced_accessibility", "iced_core/a11y", "iced_widget/a11y", "iced_winit?/a11y", "iced_sctk?/a11y"] +a11y = [ + "iced_accessibility", + "iced_core/a11y", + "iced_widget/a11y", + "iced_winit?/a11y", + "iced_sctk?/a11y", +] # Enables the winit shell. Conflicts with `wayland` and `glutin`. winit = ["iced_winit", "iced_accessibility?/accesskit_winit"] # Enables the sctk shell. Conflicts with `winit` and `glutin`. -wayland = ["iced_sctk", "iced_widget/wayland", "iced_accessibility?/accesskit_unix", "iced_core/wayland"] +wayland = [ + "iced_sctk", + "iced_widget/wayland", + "iced_accessibility?/accesskit_unix", + "iced_core/wayland", +] # Enables clipboard for iced_sctk wayland-clipboard = ["iced_sctk?/clipboard"] @@ -75,6 +86,8 @@ iced_accessibility.workspace = true iced_accessibility.optional = true thiserror.workspace = true window_clipboard.workspace = true +mime.workspace = true +dnd.workspace = true image.workspace = true image.optional = true @@ -94,7 +107,7 @@ members = [ "winit", "examples/*", "accessibility", - "sctk" + "sctk", ] exclude = ["examples/integration"] @@ -158,7 +171,7 @@ qrcode = { version = "0.12", default-features = false } raw-window-handle = "0.6" resvg = "0.37" rustc-hash = "1.0" -sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "2e9bf9f" } +sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "3bed072" } smol = "1.0" smol_str = "0.2" softbuffer = { git = "https://github.com/pop-os/softbuffer", tag = "cosmic-4.0" } @@ -172,13 +185,17 @@ xxhash-rust = { version = "0.8", features = ["xxh3"] } unicode-segmentation = "1.0" wasm-bindgen-futures = "0.4" wasm-timer = "0.2" -wayland-protocols = { version = "0.31.0", features = [ "staging"]} +wayland-protocols = { version = "0.31.0", features = ["staging"] } web-sys = "0.3" web-time = "0.2" # wgpu = "0.19" # Newer wgpu commit that fixes Vulkan backend on Nvidia wgpu = { git = "https://github.com/gfx-rs/wgpu", rev = "20fda69" } winapi = "0.3" -window_clipboard = { git = "https://github.com/pop-os/window_clipboard.git", tag = "pop-mime-types" } -# window_clipboard = { path = "../window_clipboard" } +window_clipboard = { git = "https://github.com/pop-os/window_clipboard.git", tag = "pop-dnd" } +dnd = { git = "https://github.com/pop-os/window_clipboard.git", tag = "pop-dnd" } +mime = { git = "https://github.com/pop-os/window_clipboard.git", tag = "pop-dnd" } +# window_clipboard = { path = "../window_clipboard" } +# dnd = { path = "../window_clipboard/dnd" } +# mime = { path = "../window_clipboard/mime" } winit = { git = "https://github.com/pop-os/winit.git", branch = "winit-0.29" } diff --git a/core/Cargo.toml b/core/Cargo.toml index 5eb0780980..40966bb89f 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -23,6 +23,8 @@ thiserror.workspace = true web-time.workspace = true xxhash-rust.workspace = true window_clipboard.workspace = true +dnd.workspace = true +mime.workspace = true sctk.workspace = true sctk.optional = true diff --git a/core/src/clipboard.rs b/core/src/clipboard.rs index f465a430c3..c97fa6e09f 100644 --- a/core/src/clipboard.rs +++ b/core/src/clipboard.rs @@ -1,6 +1,11 @@ //! Access the clipboard. -use window_clipboard::mime::{self, AllowedMimeTypes, ClipboardStoreData}; +use std::{any::Any, borrow::Cow, sync::Arc}; + +use dnd::{DndAction, DndDestinationRectangle, DndSurface}; +use mime::{self, AllowedMimeTypes, AsMimeTypes, ClipboardStoreData}; + +use crate::{widget::tree::State, window, Element}; /// A buffer for short-term storage and transfer within and between /// applications. @@ -51,6 +56,61 @@ pub trait Clipboard { >, ) { } + + /// Starts a DnD operation. + fn register_dnd_destination( + &self, + _surface: DndSurface, + _rectangles: Vec, + ) { + } + + /// Set the final action for the DnD operation. + /// Only should be done if it is requested. + fn set_action(&self, _action: DndAction) {} + + /// Registers Dnd destinations + fn start_dnd( + &self, + _internal: bool, + _source_surface: Option, + _icon_surface: Option>, + _content: Box, + _actions: DndAction, + ) { + } + + /// Ends a DnD operation. + fn end_dnd(&self) {} + + /// Consider using [`peek_dnd`] instead + /// Peeks the data on the DnD with a specific mime type. + /// Will return an error if there is no ongoing DnD operation. + fn peek_dnd(&self, _mime: String) -> Option<(Vec, String)> { + None + } +} + +/// Starts a DnD operation. +/// icon surface is a tuple of the icon element and optionally the icon element state. +pub fn start_dnd( + clipboard: &mut dyn Clipboard, + internal: bool, + source_surface: Option, + icon_surface: Option<(Element<'static, M, T, R>, State)>, + content: Box, + actions: DndAction, +) { + clipboard.start_dnd( + internal, + source_surface, + icon_surface.map(|i| { + let i: Box = Box::new(Arc::new(i)); + i + }), + content, + actions, + ); } /// A null implementation of the [`Clipboard`] trait. @@ -82,3 +142,26 @@ pub fn read_primary_data( .read_data(T::allowed().into()) .and_then(|data| T::try_from(data).ok()) } + +/// Reads the current content of the primary [`Clipboard`]. +pub fn peek_dnd( + clipboard: &mut dyn Clipboard, + mime: Option, +) -> Option { + let Some(mime) = mime.or_else(|| T::allowed().first().cloned().into()) + else { + return None; + }; + clipboard + .peek_dnd(mime) + .and_then(|data| T::try_from(data).ok()) +} + +/// Source of a DnD operation. +#[derive(Debug, Clone)] +pub enum DndSource { + /// A widget is the source of the DnD operation. + Widget(crate::id::Id), + /// A surface is the source of the DnD operation. + Surface(window::Id), +} diff --git a/core/src/event.rs b/core/src/event.rs index f029b40d14..5aec00ba9e 100644 --- a/core/src/event.rs +++ b/core/src/event.rs @@ -1,4 +1,7 @@ //! Handle events of a user interface. +use dnd::DndEvent; +use dnd::DndSurface; + use crate::keyboard; use crate::mouse; use crate::touch; @@ -33,6 +36,9 @@ pub enum Event { iced_accessibility::accesskit::ActionRequest, ), + /// A DnD event. + Dnd(DndEvent), + /// A platform specific event PlatformSpecific(PlatformSpecific), } diff --git a/core/src/widget/tree.rs b/core/src/widget/tree.rs index bfd44492a1..d9e53acbc2 100644 --- a/core/src/widget/tree.rs +++ b/core/src/widget/tree.rs @@ -53,6 +53,21 @@ impl Tree { } } + /// Finds a widget state in the tree by its id. + pub fn find<'a>(&'a self, id: &Id) -> Option<&'a Tree> { + if self.id == Some(id.clone()) { + return Some(self); + } + + for child in self.children.iter() { + if let Some(tree) = child.find(id) { + return Some(tree); + } + } + + None + } + /// Reconciliates the current tree with the provided [`Widget`]. /// /// If the tag of the [`Widget`] matches the tag of the [`Tree`], then the diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 3186662182..d867ceb9b0 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -25,4 +25,5 @@ sctk.optional = true thiserror.workspace = true iced_accessibility.workspace = true iced_accessibility.optional = true -window_clipboard.workspace = true \ No newline at end of file +window_clipboard.workspace = true +dnd.workspace = true diff --git a/runtime/src/command/action.rs b/runtime/src/command/action.rs index b8ed8d4b2b..0f412eddab 100644 --- a/runtime/src/command/action.rs +++ b/runtime/src/command/action.rs @@ -4,6 +4,7 @@ use crate::font; use crate::system; use crate::window; +use dnd::DndAction; use iced_futures::MaybeSend; use std::borrow::Cow; @@ -35,6 +36,9 @@ pub enum Action { /// Run a widget action. Widget(Box>), + /// Run a Dnd action. + Dnd(crate::dnd::DndAction), + /// Load a font from its bytes. LoadFont { /// The bytes of the font to load. @@ -78,6 +82,9 @@ impl Action { Self::PlatformSpecific(action) => { Action::PlatformSpecific(action.map(f)) } + Self::Dnd(a) => Action::Dnd(a.map(f)), + Action::LoadFont { bytes, tagger } => todo!(), + Action::PlatformSpecific(_) => todo!(), } } } @@ -99,6 +106,7 @@ impl fmt::Debug for Action { Self::PlatformSpecific(action) => { write!(f, "Action::PlatformSpecific({:?})", action) } + Self::Dnd(action) => write!(f, "Action::Dnd"), } } } diff --git a/runtime/src/dnd.rs b/runtime/src/dnd.rs new file mode 100644 index 0000000000..87d7a98411 --- /dev/null +++ b/runtime/src/dnd.rs @@ -0,0 +1,162 @@ +//! Access the clipboard. + +use std::any::Any; + +use dnd::{DndDestinationRectangle, DndSurface}; +use iced_core::clipboard::DndSource; +use iced_futures::MaybeSend; +use window_clipboard::mime::{AllowedMimeTypes, AsMimeTypes}; + +use crate::{command, Command}; + +/// An action to be performed on the system. +pub enum DndAction { + /// Register a Dnd destination. + RegisterDndDestination { + /// The surface to register. + surface: DndSurface, + /// The rectangles to register. + rectangles: Vec, + }, + /// Start a Dnd operation. + StartDnd { + /// Whether the Dnd operation is internal. + internal: bool, + /// The source surface of the Dnd operation. + source_surface: Option, + /// The icon surface of the Dnd operation. + icon_surface: Option>, + /// The content of the Dnd operation. + content: Box, + /// The actions of the Dnd operation. + actions: dnd::DndAction, + }, + /// End a Dnd operation. + EndDnd, + /// Peek the current Dnd operation. + PeekDnd(String, Box, String)>) -> T>), + /// Set the action of the Dnd operation. + SetAction(dnd::DndAction), +} + +impl std::fmt::Debug for DndAction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::RegisterDndDestination { + surface, + rectangles, + } => f + .debug_struct("RegisterDndDestination") + .field("surface", surface) + .field("rectangles", rectangles) + .finish(), + Self::StartDnd { + internal, + source_surface, + icon_surface, + content: _, + actions, + } => f + .debug_struct("StartDnd") + .field("internal", internal) + .field("source_surface", source_surface) + .field("icon_surface", icon_surface) + .field("actions", actions) + .finish(), + Self::EndDnd => f.write_str("EndDnd"), + Self::PeekDnd(mime, _) => { + f.debug_struct("PeekDnd").field("mime", mime).finish() + } + Self::SetAction(a) => f.debug_tuple("SetAction").field(a).finish(), + } + } +} + +impl DndAction { + /// Maps the output of a system [`Action`] using the provided closure. + pub fn map( + self, + f: impl Fn(T) -> A + 'static + MaybeSend + Sync, + ) -> DndAction + where + T: 'static, + { + match self { + Self::PeekDnd(m, o) => { + DndAction::PeekDnd(m, Box::new(move |d| f(o(d)))) + } + Self::EndDnd => DndAction::EndDnd, + Self::SetAction(a) => DndAction::SetAction(a), + Self::StartDnd { + internal, + source_surface, + icon_surface, + content, + actions, + } => DndAction::StartDnd { + internal, + source_surface, + icon_surface, + content, + actions, + }, + Self::RegisterDndDestination { + surface, + rectangles, + } => DndAction::RegisterDndDestination { + surface, + rectangles, + }, + } + } +} + +/// Read the current contents of the Dnd operation. +pub fn peek_dnd( + f: impl Fn(Option) -> Message + 'static, +) -> Command { + Command::single(command::Action::Dnd(DndAction::PeekDnd( + T::allowed() + .get(0) + .map_or_else(|| String::new(), |s| s.to_string()), + Box::new(move |d| f(d.and_then(|d| T::try_from(d).ok()))), + ))) +} + +/// Register a Dnd destination. +pub fn register_dnd_destination( + surface: DndSurface, + rectangles: Vec, +) -> Command { + Command::single(command::Action::Dnd(DndAction::RegisterDndDestination { + surface, + rectangles, + })) +} + +/// Start a Dnd operation. +pub fn start_dnd( + internal: bool, + source_surface: Option, + icon_surface: Option>, + content: Box, + actions: dnd::DndAction, +) -> Command { + Command::single(command::Action::Dnd(DndAction::StartDnd { + internal, + source_surface, + icon_surface, + content, + actions, + })) +} + +/// End a Dnd operation. +pub fn end_dnd() -> Command { + Command::single(command::Action::Dnd(DndAction::EndDnd)) +} + +/// Set the action of the Dnd operation. +pub fn set_action(a: dnd::DndAction) -> Command { + Command::single(command::Action::Dnd(DndAction::SetAction(a))) +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 03906f459a..eb1561d2d5 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -18,6 +18,7 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] pub mod clipboard; pub mod command; +pub mod dnd; pub mod font; pub mod keyboard; pub mod overlay; diff --git a/runtime/src/user_interface.rs b/runtime/src/user_interface.rs index e831a7f65c..5999c94db0 100644 --- a/runtime/src/user_interface.rs +++ b/runtime/src/user_interface.rs @@ -620,6 +620,11 @@ where cursor, ) } + + /// Find widget with given id + pub fn find(&self, id: &widget::Id) -> Option<&widget::Tree> { + self.state.find(id) + } } /// Reusable data of a specific [`UserInterface`]. diff --git a/sctk/src/event_loop/mod.rs b/sctk/src/event_loop/mod.rs index 06f023fe3a..9ae5aff86e 100644 --- a/sctk/src/event_loop/mod.rs +++ b/sctk/src/event_loop/mod.rs @@ -1079,11 +1079,11 @@ where Event::DataDevice(action) => { match action.inner { platform_specific::wayland::data_device::ActionInner::Accept(mime_type) => { - let drag_offer = match self.state.dnd_offer.as_mut() { + let drag_offer = match self.state.dnd_offer.as_mut().and_then(|o| o.offer.as_ref()) { Some(d) => d, None => continue, }; - drag_offer.offer.accept_mime_type(drag_offer.offer.serial, mime_type); + drag_offer.accept_mime_type(drag_offer.serial, mime_type); } platform_specific::wayland::data_device::ActionInner::StartInternalDnd { origin_id, icon_id } => { let qh = &self.state.queue_handle.clone(); @@ -1188,9 +1188,9 @@ where self.state.dnd_source = Some(Dnd { origin_id, origin, source: Some((source, data)), icon_surface, pending_requests: Vec::new(), pipe: None, cur_write: None }); }, platform_specific::wayland::data_device::ActionInner::DndFinished => { - if let Some(offer) = self.state.dnd_offer.take() { + if let Some(offer) = self.state.dnd_offer.take().filter(|o| o.offer.is_some()) { if offer.dropped { - offer.offer.finish(); + offer.offer.unwrap().finish(); } else { self.state.dnd_offer = Some(offer); @@ -1212,7 +1212,10 @@ where }, platform_specific::wayland::data_device::ActionInner::RequestDndData (mime_type) => { if let Some(dnd_offer) = self.state.dnd_offer.as_mut() { - let read_pipe = match dnd_offer.offer.receive(mime_type.clone()) { + let Some(offer) = dnd_offer.offer.as_ref() else { + continue; + }; + let read_pipe = match offer.receive(mime_type.clone()) { Ok(p) => p, Err(_) => continue, // TODO error handling }; @@ -1222,6 +1225,9 @@ where Some(s) => s, None => return PostAction::Continue, }; + let Some(offer) = dnd_offer.offer.as_ref() else { + return PostAction::Remove; + }; let (mime_type, data, token) = match dnd_offer.cur_read.take() { Some(s) => s, None => return PostAction::Continue, @@ -1231,9 +1237,9 @@ where Ok(buf) => { if buf.is_empty() { loop_handle.remove(token); - state.sctk_events.push(SctkEvent::DndOffer { event: DndOfferEvent::Data { data, mime_type }, surface: dnd_offer.offer.surface.clone() }); + state.sctk_events.push(SctkEvent::DndOffer { event: DndOfferEvent::Data { data, mime_type }, surface: dnd_offer.surface.clone() }); if dnd_offer.dropped { - dnd_offer.offer.finish(); + offer.finish(); } else { state.dnd_offer = Some(dnd_offer); } @@ -1269,8 +1275,8 @@ where } } platform_specific::wayland::data_device::ActionInner::SetActions { preferred, accepted } => { - if let Some(offer) = self.state.dnd_offer.as_ref() { - offer.offer.set_actions(accepted, preferred); + if let Some(offer) = self.state.dnd_offer.as_ref().and_then(|o| o.offer.as_ref()) { + offer.set_actions(accepted, preferred); } } } diff --git a/sctk/src/event_loop/state.rs b/sctk/src/event_loop/state.rs index fa151f1f2c..8ee524e5f3 100644 --- a/sctk/src/event_loop/state.rs +++ b/sctk/src/event_loop/state.rs @@ -248,8 +248,9 @@ impl Debug for Dnd { #[derive(Debug)] pub struct SctkDragOffer { pub(crate) dropped: bool, - pub(crate) offer: DragOffer, + pub(crate) offer: Option, pub(crate) cur_read: Option<(String, Vec, RegistrationToken)>, + pub(crate) surface: WlSurface, } #[derive(Debug)] diff --git a/sctk/src/handlers/data_device/data_device.rs b/sctk/src/handlers/data_device/data_device.rs index 5eb4b36747..e7d950363e 100644 --- a/sctk/src/handlers/data_device/data_device.rs +++ b/sctk/src/handlers/data_device/data_device.rs @@ -2,7 +2,10 @@ use sctk::{ data_device_manager::{ data_device::DataDeviceHandler, data_offer::DragOffer, }, - reexports::client::{protocol::wl_data_device, Connection, QueueHandle}, + reexports::client::{ + protocol::{wl_data_device, wl_surface::WlSurface}, + Connection, QueueHandle, + }, }; use crate::{ @@ -16,6 +19,9 @@ impl DataDeviceHandler for SctkState { _conn: &Connection, _qh: &QueueHandle, wl_data_device: &wl_data_device::WlDataDevice, + x: f64, + y: f64, + s: &WlSurface, ) { let data_device = if let Some(seat) = self .seats @@ -27,20 +33,20 @@ impl DataDeviceHandler for SctkState { return; }; - let drag_offer = data_device.data().drag_offer().unwrap(); - let mime_types = drag_offer.with_mime_types(|types| types.to_vec()); + let drag_offer = data_device.data().drag_offer(); + let mime_types = drag_offer + .as_ref() + .map(|offer| offer.with_mime_types(|types| types.to_vec())) + .unwrap_or_default(); self.dnd_offer = Some(SctkDragOffer { dropped: false, offer: drag_offer.clone(), cur_read: None, + surface: s.clone(), }); self.sctk_events.push(SctkEvent::DndOffer { - event: DndOfferEvent::Enter { - mime_types, - x: drag_offer.x, - y: drag_offer.y, - }, - surface: drag_offer.surface.clone(), + event: DndOfferEvent::Enter { mime_types, x, y }, + surface: s.clone(), }); } @@ -62,7 +68,7 @@ impl DataDeviceHandler for SctkState { self.sctk_events.push(SctkEvent::DndOffer { event: DndOfferEvent::Leave, - surface: dnd_offer.offer.surface.clone(), + surface: dnd_offer.surface.clone(), }); } } @@ -72,6 +78,8 @@ impl DataDeviceHandler for SctkState { _conn: &Connection, _qh: &QueueHandle, wl_data_device: &wl_data_device::WlDataDevice, + x: f64, + y: f64, ) { let data_device = if let Some(seat) = self .seats @@ -85,14 +93,19 @@ impl DataDeviceHandler for SctkState { let offer = data_device.data().drag_offer(); // if the offer is not the same as the current one, ignore the leave event - if offer.as_ref() != self.dnd_offer.as_ref().map(|o| &o.offer) { + if offer.as_ref() + != self.dnd_offer.as_ref().and_then(|o| o.offer.as_ref()) + { return; } - let DragOffer { x, y, surface, .. } = - data_device.data().drag_offer().unwrap(); + + let Some(surface) = offer.as_ref().map(|o| o.surface.clone()) else { + return; + }; + self.sctk_events.push(SctkEvent::DndOffer { event: DndOfferEvent::Motion { x, y }, - surface: surface.clone(), + surface, }); } @@ -121,17 +134,15 @@ impl DataDeviceHandler for SctkState { return; }; - if let Some(offer) = data_device.data().drag_offer() { - if let Some(dnd_offer) = self.dnd_offer.as_mut() { - if offer != dnd_offer.offer { - return; - } - dnd_offer.dropped = true; + if let Some(dnd_offer) = self.dnd_offer.as_mut() { + if data_device.data().drag_offer() != dnd_offer.offer { + return; } self.sctk_events.push(SctkEvent::DndOffer { event: DndOfferEvent::DropPerformed, - surface: offer.surface.clone(), + surface: dnd_offer.surface.clone(), }); + dnd_offer.dropped = true; } } } diff --git a/sctk/src/handlers/data_device/data_offer.rs b/sctk/src/handlers/data_device/data_offer.rs index 2901c07038..5ef6381ceb 100644 --- a/sctk/src/handlers/data_device/data_offer.rs +++ b/sctk/src/handlers/data_device/data_offer.rs @@ -18,7 +18,7 @@ impl DataOfferHandler for SctkState { if self .dnd_offer .as_ref() - .map(|o| o.offer.inner() == offer.inner()) + .map(|o| o.offer.as_ref().map(|o| o.inner()) == Some(offer.inner())) .unwrap_or(false) { self.sctk_events @@ -41,7 +41,7 @@ impl DataOfferHandler for SctkState { if self .dnd_offer .as_ref() - .map(|o| o.offer.inner() == offer.inner()) + .map(|o| o.offer.as_ref().map(|o| o.inner()) == Some(offer.inner())) .unwrap_or(false) { self.sctk_events diff --git a/sctk/src/handlers/seat/keyboard.rs b/sctk/src/handlers/seat/keyboard.rs index c150c077bf..a7a865a4a3 100644 --- a/sctk/src/handlers/seat/keyboard.rs +++ b/sctk/src/handlers/seat/keyboard.rs @@ -172,6 +172,7 @@ impl KeyboardHandler for SctkState { keyboard: &sctk::reexports::client::protocol::wl_keyboard::WlKeyboard, _serial: u32, modifiers: sctk::seat::keyboard::Modifiers, + layout: u32, ) { let (is_active, my_seat) = match self.seats.iter_mut().enumerate().find_map(|(i, s)| { diff --git a/src/application.rs b/src/application.rs index e3988e793a..db04ef772d 100644 --- a/src/application.rs +++ b/src/application.rs @@ -97,7 +97,7 @@ pub trait Application: Sized { type Executor: Executor; /// The type of __messages__ your [`Application`] will produce. - type Message: std::fmt::Debug + Send; + type Message: std::fmt::Debug + Send + Sync + 'static; /// The theme of your [`Application`]. type Theme: Default + StyleSheet; diff --git a/src/lib.rs b/src/lib.rs index 3b8241b7a4..90fdfdf4e4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -225,8 +225,9 @@ pub use crate::core::{ pub mod clipboard { //! Access the clipboard. pub use crate::runtime::clipboard::{read, write}; + pub use dnd; pub use iced_core::clipboard::{read_data, read_primary_data}; - pub use window_clipboard::mime; + pub use mime; } pub mod executor { diff --git a/src/sandbox.rs b/src/sandbox.rs index 65a48519e2..9ccbebb4dc 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -84,7 +84,7 @@ use crate::{Application, Command, Element, Error, Settings, Subscription}; /// ``` pub trait Sandbox { /// The type of __messages__ your [`Sandbox`] will produce. - type Message: std::fmt::Debug + Send; + type Message: std::fmt::Debug + Send + Sync + 'static; /// Initializes the [`Sandbox`]. /// @@ -155,6 +155,7 @@ pub trait Sandbox { impl Application for T where T: Sandbox, + T::Message: Send + Sync + 'static, { type Executor = iced_futures::backend::null::Executor; type Flags = (); diff --git a/widget/Cargo.toml b/widget/Cargo.toml index 64dde4be8a..fa26061721 100644 --- a/widget/Cargo.toml +++ b/widget/Cargo.toml @@ -35,6 +35,8 @@ sctk.optional = true num-traits.workspace = true thiserror.workspace = true unicode-segmentation.workspace = true +window_clipboard.workspace = true +dnd.workspace = true ouroboros.workspace = true ouroboros.optional = true diff --git a/winit/Cargo.toml b/winit/Cargo.toml index c9da88e7d7..a32bdac83d 100644 --- a/winit/Cargo.toml +++ b/winit/Cargo.toml @@ -33,6 +33,7 @@ log.workspace = true thiserror.workspace = true tracing.workspace = true window_clipboard.workspace = true +dnd.workspace = true winit.workspace = true sysinfo.workspace = true diff --git a/winit/src/application.rs b/winit/src/application.rs index afc0b2fe44..76fb85d47a 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -2,19 +2,28 @@ mod drag_resize; mod state; +use dnd::DndAction; +use dnd::DndEvent; +use dnd::DndSurface; +use dnd::Icon; #[cfg(feature = "a11y")] use iced_graphics::core::widget::operation::focusable::focus; use iced_graphics::core::widget::operation::OperationWrapper; use iced_graphics::core::widget::Operation; +use iced_graphics::Viewport; use iced_runtime::futures::futures::FutureExt; +use iced_style::core::clipboard::DndSource; use iced_style::core::Clipboard as CoreClipboard; +use iced_style::core::Length; pub use state::State; +use window_clipboard::mime; use window_clipboard::mime::ClipboardStoreData; use crate::conversion; use crate::core; use crate::core::mouse; use crate::core::renderer; +use crate::core::renderer::Renderer; use crate::core::time::Instant; use crate::core::widget::operation; use crate::core::window; @@ -31,6 +40,7 @@ use crate::{Clipboard, Error, Proxy, Settings}; use futures::channel::mpsc; use futures::stream::StreamExt; +use std::any::Any; use std::mem::ManuallyDrop; use std::sync::Arc; @@ -39,7 +49,6 @@ pub use profiler::Profiler; #[cfg(feature = "trace")] use tracing::{info_span, instrument::Instrument}; -#[derive(Debug)] /// Wrapper aroun application Messages to allow for more UserEvent variants pub enum UserEventWrapper { /// Application Message @@ -50,6 +59,49 @@ pub enum UserEventWrapper { #[cfg(feature = "a11y")] /// A11y was enabled A11yEnabled, + /// CLipboard Message + StartDnd { + /// internal dnd + internal: bool, + /// the surface the dnd is started from + source_surface: Option, + /// the icon if any + /// This is actually an Element + icon_surface: Option>, + /// the content of the dnd + content: Box, + /// the actions of the dnd + actions: DndAction, + }, + /// Dnd Event + Dnd(DndEvent), +} + +unsafe impl Send for UserEventWrapper {} +unsafe impl Sync for UserEventWrapper {} + +impl std::fmt::Debug for UserEventWrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + UserEventWrapper::Message(m) => write!(f, "Message({:?})", m), + #[cfg(feature = "a11y")] + UserEventWrapper::A11y(a) => write!(f, "A11y({:?})", a), + #[cfg(feature = "a11y")] + UserEventWrapper::A11yEnabled => write!(f, "A11yEnabled"), + UserEventWrapper::StartDnd { + internal, + source_surface: _, + icon_surface, + content: _, + actions, + } => write!( + f, + "StartDnd {{ internal: {:?}, icon_surface: {}, actions: {:?} }}", + internal, icon_surface.is_some(), actions + ), + UserEventWrapper::Dnd(_) => write!(f, "Dnd"), + } + } } #[cfg(feature = "a11y")] @@ -143,6 +195,7 @@ where E: Executor + 'static, C: Compositor + 'static, A::Theme: StyleSheet, + ::Message: Sync, { use futures::task; use futures::Future; @@ -305,6 +358,7 @@ async fn run_instance( E: Executor + 'static, C: Compositor + 'static, A::Theme: StyleSheet, + A::Message: Send + Sync + 'static, { use winit::event; use winit::event_loop::ControlFlow; @@ -313,7 +367,8 @@ async fn run_instance( let mut viewport_version = state.viewport_version(); let physical_size = state.physical_size(); - let mut clipboard = Clipboard::connect(&window); + let mut clipboard = + Clipboard::connect(&window, crate::proxy::Proxy::new(proxy.clone())); let mut cache = user_interface::Cache::default(); let mut surface = compositor.create_surface( window.clone(), @@ -444,6 +499,111 @@ async fn run_instance( } #[cfg(feature = "a11y")] UserEventWrapper::A11yEnabled => a11y_enabled = true, + UserEventWrapper::StartDnd { + internal, + source_surface: _, // not needed if there is only one window + icon_surface, + content, + actions, + } => { + let mut renderer = compositor.create_renderer(); + let icon_surface = icon_surface + .map(|i| { + let i: Box = i; + i + }) + .and_then(|i| { + i.downcast::, + core::widget::tree::State, + )>>() + .ok() + }) + .map(|e| { + let e = Arc::into_inner(*e).unwrap(); + let (mut e, widget_state) = e; + let lim = core::layout::Limits::new( + Size::new(1., 1.), + Size::new( + state.viewport().physical_width() + as f32, + state.viewport().physical_height() + as f32, + ), + ); + + let mut tree = core::widget::Tree { + id: e.as_widget().id(), + tag: e.as_widget().tag(), + state: widget_state, + children: e.as_widget().children(), + }; + + let size = e + .as_widget() + .layout(&mut tree, &renderer, &lim); + e.as_widget_mut().diff(&mut tree); + + let size = lim.resolve( + Length::Shrink, + Length::Shrink, + size.size(), + ); + let mut surface = compositor.create_surface( + window.clone(), + size.width.ceil() as u32, + size.height.ceil() as u32, + ); + let viewport = Viewport::with_logical_size( + size, + state.viewport().scale_factor(), + ); + + let mut ui = UserInterface::build( + e, + size, + user_interface::Cache::default(), + &mut renderer, + ); + _ = ui.draw( + &mut renderer, + state.theme(), + &renderer::Style { + icon_color: state.icon_color(), + text_color: state.text_color(), + scale_factor: state.scale_factor(), + }, + Default::default(), + ); + let bytes = compositor.screenshot( + &mut renderer, + &mut surface, + &viewport, + state.background_color(), + &debug.overlay(), + ); + Icon::Buffer { + data: Arc::new(bytes), + width: viewport.physical_width(), + height: viewport.physical_height(), + transparent: false, + } + }); + + clipboard.start_dnd_winit( + internal, + DndSurface(Arc::new(Box::new(window.clone()))), + icon_surface, + content, + actions, + ); + } + UserEventWrapper::Dnd(e) => events.push(Event::Dnd(e)), }; } event::Event::WindowEvent { @@ -818,7 +978,7 @@ pub fn update( Proxy>, UserEventWrapper, >, - clipboard: &mut Clipboard, + clipboard: &mut Clipboard, should_exit: &mut bool, proxy: &mut winit::event_loop::EventLoopProxy>, debug: &mut Debug, @@ -876,7 +1036,7 @@ pub fn run_command( Proxy>, UserEventWrapper, >, - clipboard: &mut Clipboard, + clipboard: &mut Clipboard, should_exit: &mut bool, proxy: &mut winit::event_loop::EventLoopProxy>, debug: &mut Debug, @@ -1137,6 +1297,40 @@ pub fn run_command( .expect("Send message to event loop"); } command::Action::PlatformSpecific(_) => todo!(), + command::Action::Dnd(a) => match a { + iced_runtime::dnd::DndAction::RegisterDndDestination { + surface, + rectangles, + } => { + clipboard.register_dnd_destination(surface, rectangles); + } + iced_runtime::dnd::DndAction::StartDnd { + internal, + source_surface, + icon_surface, + content, + actions, + } => clipboard.start_dnd( + internal, + source_surface, + icon_surface, + content, + actions, + ), + iced_runtime::dnd::DndAction::EndDnd => { + clipboard.end_dnd(); + } + iced_runtime::dnd::DndAction::PeekDnd(m, to_msg) => { + let data = clipboard.peek_dnd(m); + let message = to_msg(data); + proxy + .send_event(UserEventWrapper::Message(message)) + .expect("Send message to event loop"); + } + iced_runtime::dnd::DndAction::SetAction(a) => { + clipboard.set_action(a); + } + }, } } } diff --git a/winit/src/clipboard.rs b/winit/src/clipboard.rs index 019384f583..3183f0ea5d 100644 --- a/winit/src/clipboard.rs +++ b/winit/src/clipboard.rs @@ -1,34 +1,52 @@ //! Access the clipboard. -use window_clipboard::mime::{self, ClipboardStoreData}; +use std::{any::Any, borrow::Cow}; + +use crate::futures::futures::Sink; +use dnd::{DndAction, DndDestinationRectangle, DndSurface, Icon}; +use iced_style::core::clipboard::DndSource; +use window_clipboard::{ + dnd::DndProvider, + mime::{self, ClipboardData, ClipboardStoreData}, +}; + +use crate::{application::UserEventWrapper, Proxy}; /// A buffer for short-term storage and transfer within and between /// applications. #[allow(missing_debug_implementations)] -pub struct Clipboard { - state: State, +pub struct Clipboard { + state: State, } -enum State { - Connected(window_clipboard::Clipboard), +enum State { + Connected(window_clipboard::Clipboard, Proxy>), Unavailable, } -impl Clipboard { +impl Clipboard { /// Creates a new [`Clipboard`] for the given window. - pub fn connect(window: &winit::window::Window) -> Clipboard { + pub fn connect( + window: &winit::window::Window, + proxy: Proxy>, + ) -> Clipboard { #[allow(unsafe_code)] let state = unsafe { window_clipboard::Clipboard::connect(window) } .ok() - .map(State::Connected) + .map(|c| (c, proxy.clone())) + .map(|c| State::Connected(c.0, c.1)) .unwrap_or(State::Unavailable); + if let State::Connected(clipboard, _) = &state { + clipboard.init_dnd(Box::new(proxy)); + } + Clipboard { state } } /// Creates a new [`Clipboard`] that isn't associated with a window. /// This clipboard will never contain a copied value. - pub fn unconnected() -> Clipboard { + pub fn unconnected() -> Clipboard { Clipboard { state: State::Unavailable, } @@ -37,7 +55,7 @@ impl Clipboard { /// Reads the current content of the [`Clipboard`] as text. pub fn read(&self) -> Option { match &self.state { - State::Connected(clipboard) => clipboard.read().ok(), + State::Connected(clipboard, _) => clipboard.read().ok(), State::Unavailable => None, } } @@ -45,7 +63,7 @@ impl Clipboard { /// Writes the given text contents to the [`Clipboard`]. pub fn write(&mut self, contents: String) { match &mut self.state { - State::Connected(clipboard) => match clipboard.write(contents) { + State::Connected(clipboard, _) => match clipboard.write(contents) { Ok(()) => {} Err(error) => { log::warn!("error writing to clipboard: {error}"); @@ -54,9 +72,32 @@ impl Clipboard { State::Unavailable => {} } } + + // + pub(crate) fn start_dnd_winit( + &self, + internal: bool, + source_surface: DndSurface, + icon_surface: Option, + content: Box, + actions: DndAction, + ) { + match &self.state { + State::Connected(clipboard, _) => { + _ = clipboard.start_dnd( + internal, + source_surface, + icon_surface, + content, + actions, + ) + } + State::Unavailable => {} + } + } } -impl crate::core::Clipboard for Clipboard { +impl crate::core::Clipboard for Clipboard { fn read(&self) -> Option { self.read() } @@ -67,7 +108,7 @@ impl crate::core::Clipboard for Clipboard { fn read_primary(&self) -> Option { match &self.state { - State::Connected(clipboard) => { + State::Connected(clipboard, _) => { clipboard.read_primary().and_then(|res| res.ok()) } State::Unavailable => None, @@ -76,7 +117,7 @@ impl crate::core::Clipboard for Clipboard { fn write_primary(&mut self, contents: String) { match &mut self.state { - State::Connected(clipboard) => { + State::Connected(clipboard, _) => { _ = clipboard.write_primary(contents) } State::Unavailable => {} @@ -85,7 +126,7 @@ impl crate::core::Clipboard for Clipboard { fn read_data(&self, mimes: Vec) -> Option<(Vec, String)> { match &self.state { - State::Connected(clipboard) => { + State::Connected(clipboard, _) => { clipboard.read_raw(mimes).and_then(|res| res.ok()) } State::Unavailable => None, @@ -99,7 +140,9 @@ impl crate::core::Clipboard for Clipboard { >, ) { match &mut self.state { - State::Connected(clipboard) => _ = clipboard.write_data(contents), + State::Connected(clipboard, _) => { + _ = clipboard.write_data(contents) + } State::Unavailable => {} } } @@ -109,7 +152,7 @@ impl crate::core::Clipboard for Clipboard { mimes: Vec, ) -> Option<(Vec, String)> { match &self.state { - State::Connected(clipboard) => { + State::Connected(clipboard, _) => { clipboard.read_primary_raw(mimes).and_then(|res| res.ok()) } State::Unavailable => None, @@ -123,10 +166,69 @@ impl crate::core::Clipboard for Clipboard { >, ) { match &mut self.state { - State::Connected(clipboard) => { + State::Connected(clipboard, _) => { _ = clipboard.write_primary_data(contents) } State::Unavailable => {} } } + + fn start_dnd( + &self, + internal: bool, + source_surface: Option, + icon_surface: Option>, + content: Box, + actions: DndAction, + ) { + match &self.state { + State::Connected(_, tx) => { + tx.raw.send_event(UserEventWrapper::StartDnd { + internal, + source_surface, + icon_surface, + content, + actions, + }); + } + State::Unavailable => {} + } + } + + fn register_dnd_destination( + &self, + surface: DndSurface, + rectangles: Vec, + ) { + match &self.state { + State::Connected(clipboard, _) => { + _ = clipboard.register_dnd_destination(surface, rectangles) + } + State::Unavailable => {} + } + } + + fn end_dnd(&self) { + match &self.state { + State::Connected(clipboard, _) => _ = clipboard.end_dnd(), + State::Unavailable => {} + } + } + + fn peek_dnd(&self, mime: String) -> Option<(Vec, String)> { + match &self.state { + State::Connected(clipboard, _) => clipboard + .peek_offer::(Some(Cow::Owned(mime))) + .ok() + .map(|res| (res.0, res.1)), + State::Unavailable => None, + } + } + + fn set_action(&self, action: DndAction) { + match &self.state { + State::Connected(clipboard, _) => _ = clipboard.set_action(action), + State::Unavailable => {} + } + } } diff --git a/winit/src/lib.rs b/winit/src/lib.rs index 948576a28a..622e9396a2 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -22,7 +22,6 @@ missing_debug_implementations, missing_docs, unused_results, - unsafe_code, rustdoc::broken_intra_doc_links )] #![cfg_attr(docsrs, feature(doc_auto_cfg))] diff --git a/winit/src/multi_window.rs b/winit/src/multi_window.rs index a07ae9934b..fb6da4074c 100644 --- a/winit/src/multi_window.rs +++ b/winit/src/multi_window.rs @@ -12,6 +12,7 @@ use crate::core::renderer; use crate::core::widget::operation; use crate::core::widget::Operation; use crate::core::window; +use crate::core::Clipboard as CoreClipboard; use crate::core::Size; use crate::futures::futures::channel::mpsc; use crate::futures::futures::{task, Future, StreamExt}; @@ -26,12 +27,17 @@ use crate::runtime::user_interface::{self, UserInterface}; use crate::runtime::Debug; use crate::style::application::StyleSheet; use crate::{Clipboard, Error, Proxy, Settings}; -use crate::core::Clipboard as CoreClipboard; +use dnd::DndAction; +use dnd::DndSurface; +use dnd::Icon; +use iced_graphics::Viewport; use iced_runtime::futures::futures::FutureExt; use iced_style::Theme; pub use state::State; use window_clipboard::mime::ClipboardStoreData; +use winit::raw_window_handle::HasWindowHandle; +use std::any::Any; use std::collections::HashMap; use std::mem::ManuallyDrop; use std::sync::Arc; @@ -127,6 +133,7 @@ where A: Application + 'static, E: Executor + 'static, C: Compositor + 'static, + A::Message: Send + Sync + 'static, A::Theme: StyleSheet, { use winit::event_loop::EventLoopBuilder; @@ -350,6 +357,7 @@ async fn run_instance( A: Application + 'static, E: Executor + 'static, C: Compositor + 'static, + A::Message: Send + Sync + 'static, A::Theme: StyleSheet, { use winit::event; @@ -363,7 +371,8 @@ async fn run_instance( main_window.raw.set_visible(true); } - let mut clipboard = Clipboard::connect(&main_window.raw); + let mut clipboard = + Clipboard::connect(&main_window.raw, Proxy::new(proxy.clone())); #[cfg(feature = "a11y")] let (window_a11y_id, adapter, mut a11y_enabled) = { @@ -446,6 +455,8 @@ async fn run_instance( debug.startup_finished(); + let mut cur_dnd_surface: Option = None; + 'main: while let Some(event) = event_receiver.next().await { match event { Event::WindowCreated { @@ -971,6 +982,192 @@ async fn run_instance( UserEventWrapper::A11yEnabled => { a11y_enabled = true } + UserEventWrapper::StartDnd { + internal, + source_surface, + icon_surface, + content, + actions, + } => { + let Some(window_id) = + source_surface.and_then(|source| { + match source { + core::clipboard::DndSource::Surface( + s, + ) => Some(s), + core::clipboard::DndSource::Widget( + w, + ) => { + // search windows for widget + user_interfaces.iter().find_map( + |(id, ui)| { + if ui. + find(&w).is_some() + { + Some(*id) + } else { + None + } + }, + ) + }, + } + }) + else { + continue; + }; + + let Some(window) = + window_manager.get_mut(window_id) + else { + continue; + }; + let state = &window.state; + + let icon_surface = icon_surface + .map(|i| { + let i: Box = i; + i + }) + .and_then(|i| { + i.downcast::, + >>( + ) + .ok() + }) + .map(|e| { + let size = e.as_widget().size(); + let lim = core::layout::Limits::new( + Size::new(1., 1.), + Size::new( + state + .viewport() + .physical_width() + as f32, + state + .viewport() + .physical_height() + as f32, + ), + ); + let size = lim.resolve( + size.width, + size.height, + Size::ZERO, + ); + let mut surface = compositor + .create_surface( + window.raw.clone(), + size.width.ceil() as u32, + size.height.ceil() as u32, + ); + let viewport = + Viewport::with_logical_size( + size, + state.viewport().scale_factor(), + ); + let mut renderer = + compositor.create_renderer(); + + let e = Arc::into_inner(*e).unwrap(); + let mut ui = UserInterface::build( + e, + size, + user_interface::Cache::default(), + &mut renderer, + ); + _ = ui.draw( + &mut renderer, + state.theme(), + &renderer::Style { + icon_color: state.icon_color(), + text_color: state.text_color(), + scale_factor: state + .scale_factor(), + }, + Default::default(), + ); + let bytes = compositor.screenshot( + &mut renderer, + &mut surface, + &viewport, + state.background_color(), + &debug.overlay(), + ); + Icon::Buffer { + data: Arc::new(bytes), + width: viewport.physical_width(), + height: viewport.physical_height(), + transparent: false, + } + }); + + clipboard.start_dnd_winit( + internal, + DndSurface(Arc::new(Box::new( + window.raw.clone(), + ))), + icon_surface, + content, + actions, + ); + } + UserEventWrapper::Dnd(e) => match &e { + dnd::DndEvent::Offer( + _, + dnd::OfferEvent::Leave, + ) => { + events.push(( + cur_dnd_surface, + core::Event::Dnd(e), + )); + cur_dnd_surface = None; + } + dnd::DndEvent::Offer( + _, + dnd::OfferEvent::Enter { surface, .. }, + ) => { + let window_handle = + surface.0.window_handle().ok(); + let window_id = window_manager + .iter_mut() + .find_map(|(id, window)| { + if window + .raw + .window_handle() + .ok() + .zip(window_handle) + .map(|(a, b)| a == b) + .unwrap_or_default() + { + Some(id) + } else { + None + } + }); + + cur_dnd_surface = window_id; + events.push(( + cur_dnd_surface, + core::Event::Dnd(e), + )); + } + dnd::DndEvent::Offer(..) => { + events.push(( + cur_dnd_surface, + core::Event::Dnd(e), + )); + } + dnd::DndEvent::Source(_) => { + events.push((None, core::Event::Dnd(e))) + } + }, }; } event::Event::WindowEvent { @@ -1059,7 +1256,7 @@ fn update( Proxy>, UserEventWrapper, >, - clipboard: &mut Clipboard, + clipboard: &mut Clipboard, control_sender: &mut mpsc::UnboundedSender, proxy: &mut winit::event_loop::EventLoopProxy>, debug: &mut Debug, @@ -1068,6 +1265,7 @@ fn update( ui_caches: &mut HashMap, ) where C: Compositor + 'static, + A::Message: Send + Sync + 'static, A::Theme: StyleSheet, { for message in messages.drain(..) { @@ -1108,7 +1306,7 @@ fn run_command( Proxy>, UserEventWrapper, >, - clipboard: &mut Clipboard, + clipboard: &mut Clipboard, control_sender: &mut mpsc::UnboundedSender, proxy: &mut winit::event_loop::EventLoopProxy>, @@ -1119,6 +1317,7 @@ fn run_command( A: Application, E: Executor, C: Compositor + 'static, + A::Message: Send + Sync + 'static, A::Theme: StyleSheet, { use crate::runtime::clipboard; @@ -1442,6 +1641,40 @@ fn run_command( command::Action::PlatformSpecific(_) => { tracing::warn!("Platform specific commands are not supported yet in multi-window winit mode."); } + command::Action::Dnd(a) => match a { + iced_runtime::dnd::DndAction::RegisterDndDestination { + surface, + rectangles, + } => { + clipboard.register_dnd_destination(surface, rectangles); + } + iced_runtime::dnd::DndAction::StartDnd { + internal, + source_surface, + icon_surface, + content, + actions, + } => clipboard.start_dnd( + internal, + source_surface, + icon_surface, + content, + actions, + ), + iced_runtime::dnd::DndAction::EndDnd => { + clipboard.end_dnd(); + } + iced_runtime::dnd::DndAction::PeekDnd(m, to_msg) => { + let data = clipboard.peek_dnd(m); + let message = to_msg(data); + proxy + .send_event(UserEventWrapper::Message(message)) + .expect("Send message to event loop"); + } + iced_runtime::dnd::DndAction::SetAction(a) => { + clipboard.set_action(a); + } + }, } } } diff --git a/winit/src/proxy.rs b/winit/src/proxy.rs index 1d6c48bb25..19f0502a26 100644 --- a/winit/src/proxy.rs +++ b/winit/src/proxy.rs @@ -1,14 +1,19 @@ -use crate::futures::futures::{ - channel::mpsc, - task::{Context, Poll}, - Sink, +use dnd::{DndEvent, DndSurface}; + +use crate::{ + application::UserEventWrapper, + futures::futures::{ + channel::mpsc, + task::{Context, Poll}, + Sink, + }, }; use std::pin::Pin; /// An event loop proxy that implements `Sink`. #[derive(Debug)] pub struct Proxy { - raw: winit::event_loop::EventLoopProxy, + pub(crate) raw: winit::event_loop::EventLoopProxy, } impl Clone for Proxy { @@ -59,3 +64,19 @@ impl Sink for Proxy { Poll::Ready(Ok(())) } } + +impl dnd::Sender for Proxy> { + fn send( + &self, + event: DndEvent, + ) -> Result<(), std::sync::mpsc::SendError>> { + self.raw + .send_event(UserEventWrapper::Dnd(event)) + .map_err(|_err| { + std::sync::mpsc::SendError(DndEvent::Offer( + None, + dnd::OfferEvent::Leave, + )) + }) + } +}