From 35717797b7ae9b00b3f3671b50003752cc58310a Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 9 Oct 2024 16:01:47 -0400 Subject: [PATCH] refactor dnd --- cosmic-settings/src/app.rs | 6 - .../src/pages/desktop/dock/applets.rs | 2 +- .../src/pages/desktop/panel/applets_inner.rs | 790 +++++++----------- 3 files changed, 296 insertions(+), 502 deletions(-) diff --git a/cosmic-settings/src/app.rs b/cosmic-settings/src/app.rs index 960e31d..b12618e 100644 --- a/cosmic-settings/src/app.rs +++ b/cosmic-settings/src/app.rs @@ -669,12 +669,6 @@ impl cosmic::Application for SettingsApp { #[allow(clippy::too_many_lines)] fn view_window(&self, id: window::Id) -> Element { - if let Some(Some(page)) = - (id == *APPLET_DND_ICON_ID).then(|| self.pages.page::()) - { - return page.dnd_icon(); - } - panic!("unknown window ID: {id:?}"); } diff --git a/cosmic-settings/src/pages/desktop/dock/applets.rs b/cosmic-settings/src/pages/desktop/dock/applets.rs index abd31f8..57e4c1f 100644 --- a/cosmic-settings/src/pages/desktop/dock/applets.rs +++ b/cosmic-settings/src/pages/desktop/dock/applets.rs @@ -40,7 +40,7 @@ impl Default for Page { .collect(), config_helper, current_config, - reorder_widget_state: ReorderWidgetState::default(), + reorder_widget_state: None, search: String::new(), context: None, }, diff --git a/cosmic-settings/src/pages/desktop/panel/applets_inner.rs b/cosmic-settings/src/pages/desktop/panel/applets_inner.rs index caeff6b..e4fd40d 100644 --- a/cosmic-settings/src/pages/desktop/panel/applets_inner.rs +++ b/cosmic-settings/src/pages/desktop/panel/applets_inner.rs @@ -1,43 +1,33 @@ use button::Catalog as ButtonStyleSheet; +use cosmic::iced::clipboard::dnd::{ + DndAction, DndDestinationRectangle, DndEvent, OfferEvent, SourceEvent, +}; +use cosmic::iced::clipboard::mime::AsMimeTypes; +use cosmic::iced::id::Internal; use cosmic::iced::{alignment, Vector}; -use cosmic::widget::{ - button, column, container, horizontal_space, icon, list_column, row, text, text_input, Column, -}; +use cosmic::iced_core; +use cosmic::widget::{button, column, container, icon, list_column, row, text, text_input, Column}; use cosmic::{ - cctk::sctk::reexports::client::protocol::wl_data_device_manager::DndAction, cosmic_config::{Config, CosmicConfigEntry}, iced::{ alignment::{Horizontal, Vertical}, core::window, - event::{ - self, - wayland::{self}, - PlatformSpecific, - }, - mouse, overlay, touch, Alignment, Color, Length, Point, Rectangle, Size, + event, mouse, overlay, touch, Alignment, Color, Length, Point, Rectangle, Size, }, iced_runtime::{core::id::Id, Task}, - iced_widget::{ - core::{ - layout, renderer, - widget::{tree, Operation, Tree}, - Clipboard, Shell, Widget, - }, - graphics::image::image_rs::EncodableLayout, + iced_widget::core::{ + layout, renderer, + widget::{tree, Operation, Tree}, + Clipboard, Shell, Widget, }, theme, Apply, Element, }; use once_cell::sync::Lazy; -use std::{ - borrow::{Borrow, Cow}, - fmt::Debug, - mem, - path::Path, - str::FromStr, -}; +use std::path::PathBuf; +use std::{borrow::Cow, fmt::Debug, mem, path::Path}; use crate::{app, pages}; use cosmic_panel_config::CosmicPanelConfig; @@ -64,7 +54,7 @@ pub struct Page { pub(crate) available_entries: Vec>, pub(crate) config_helper: Option, pub(crate) current_config: Option, - pub(crate) reorder_widget_state: ReorderWidgetState, + pub(crate) reorder_widget_state: Option<(Applet<'static>, CosmicPanelConfig)>, pub(crate) search: String, pub(crate) context: Option, } @@ -85,7 +75,7 @@ impl Default for Page { .collect(), config_helper, current_config, - reorder_widget_state: ReorderWidgetState::default(), + reorder_widget_state: None, search: String::new(), context: None, } @@ -164,7 +154,7 @@ pub enum Message { ReorderEnd(Vec>), Applets(Vec>), PanelConfig(CosmicPanelConfig), - StartDnd(ReorderWidgetState), + StartDnd(Applet<'static>), // DnDTask(Arc ActionInner>>), Search(String), AddApplet(Applet<'static>), @@ -217,11 +207,6 @@ impl Page { } } - #[must_use] - pub fn dnd_icon(&self) -> Element { - Element::from(AppletReorderList::dnd_icon(&self.reorder_widget_state)) - } - #[must_use] #[allow(clippy::too_many_lines)] pub fn add_applet_view crate::pages::Message + Copy + 'static>( @@ -373,15 +358,13 @@ impl Page { Message::Applets(applets) => { self.available_entries = applets; } - Message::StartDnd(state) => { - self.reorder_widget_state = state; + Message::StartDnd(applet) => { + self.reorder_widget_state = + Some((applet, self.current_config.clone().unwrap_or_default())); return Task::none(); } - // Message::DnDTask(action) => { - // return data_device_action(action()); - // } Message::Save => { - self.reorder_widget_state = ReorderWidgetState::default(); + self.reorder_widget_state = None; self.save(); } Message::RemoveStart(to_remove) => { @@ -418,14 +401,9 @@ impl Page { // TODO ask design team } Message::Cancel => { - self.reorder_widget_state = ReorderWidgetState::default(); - let current_config = self.config_helper.as_ref().and_then(|config_helper| { - // TODO error handling... - let panel_config = CosmicPanelConfig::get_entry(config_helper).ok()?; - // If the config is not present, it will be created with the default values and the name will not match - (panel_config.name == "Panel").then_some(panel_config) - }); - self.current_config = current_config; + if let Some((_, config)) = self.reorder_widget_state.take() { + self.current_config = Some(config); + } } Message::Search(text) => { self.search = text; @@ -489,7 +467,6 @@ pub fn lists< .collect() }) .unwrap_or_default(), - Some((window::Id::NONE, *APPLET_DND_ICON_ID)), Message::StartDnd, // |a| Message::DnDTask(Arc::new(a)), Message::RemoveStart, @@ -497,7 +474,7 @@ pub fn lists< Message::ReorderStart, Message::Save, Message::Cancel, - page.reorder_widget_state.dragged_applet().as_ref(), + page.reorder_widget_state.as_ref().map(|(a, _)| a.clone()), ) .into(), ]) @@ -520,7 +497,6 @@ pub fn lists< .collect() }) .unwrap_or_default(), - Some((window::Id::NONE, *APPLET_DND_ICON_ID)), Message::StartDnd, // |a| Message::DnDTask(Arc::new(a)), Message::RemoveCenter, @@ -528,7 +504,7 @@ pub fn lists< Message::ReorderCenter, Message::Save, Message::Cancel, - page.reorder_widget_state.dragged_applet().as_ref(), + page.reorder_widget_state.as_ref().map(|(a, _)| a.clone()), ) .into(), ]) @@ -552,7 +528,6 @@ pub fn lists< .collect() }) .unwrap_or_default(), - Some((window::Id::NONE, *APPLET_DND_ICON_ID)), Message::StartDnd, // |a| Message::DnDTask(Arc::new(a)), Message::RemoveEnd, @@ -560,7 +535,7 @@ pub fn lists< Message::ReorderEnd, Message::Save, Message::Cancel, - page.reorder_widget_state.dragged_applet().as_ref(), + page.reorder_widget_state.as_ref().map(|(a, _)| a.clone()), ) .into(), ]) @@ -639,13 +614,13 @@ impl<'a> Applet<'a> { pub struct AppletReorderList<'a, Message> { id: Id, info: Vec>, - on_create_dnd_source: Box Message + 'a>, + on_create_dnd_source: Box) -> Message + 'a>, // on_dnd_task_produced: OnDndTask<'a, Message>, on_reorder: Box>) -> Message + 'a>, on_finish: Option, on_cancel: Option, - surface_ids: Option<(window::Id, window::Id)>, inner: Element<'a, Message>, + active_applet_offer: Option>, } impl<'a, Message: 'static + Clone> AppletReorderList<'a, Message> { @@ -654,18 +629,13 @@ impl<'a, Message: 'static + Clone> AppletReorderList<'a, Message> { /// new applet list which can be reordered and dragged pub fn new( info: Vec>, - surface_ids: Option<(window::Id, window::Id)>, - on_create_dnd_source: impl Fn(ReorderWidgetState) -> Message + 'a, - // on_dnd_task_produced: impl Fn( - // Box platform_specific::wayland::data_device::ActionInner>, - // ) -> Message - // + 'a, + on_create_dnd_source: impl Fn(Applet<'static>) -> Message + 'a, on_remove: impl Fn(String) -> Message + 'a, on_details: impl Fn(String) -> Message + 'a, on_reorder: impl Fn(Vec>) -> Message + 'a, on_apply_reorder: Message, on_cancel: Message, - active_dnd: Option<&Applet<'a>>, + active_dnd: Option>, ) -> Self { let spacing = cosmic::theme::active().cosmic().spacing; let applet_buttons = info @@ -722,7 +692,6 @@ impl<'a, Message: 'static + Clone> AppletReorderList<'a, Message> { on_reorder: Box::new(on_reorder), on_finish: Some(on_apply_reorder), on_cancel: Some(on_cancel), - surface_ids, inner: if active_dnd.is_some() && applet_buttons.is_empty() { container( text::body(fl!("drop-here")) @@ -748,53 +717,7 @@ impl<'a, Message: 'static + Clone> AppletReorderList<'a, Message> { .spacing(spacing.space_xxs) .into() }, - } - } - - #[must_use] - /// mark this as a dnd icon - pub fn dnd_icon(state: &'a ReorderWidgetState) -> Self { - Self { - id: Id::unique(), - info: Vec::new(), - on_create_dnd_source: Box::new(|_| unimplemented!()), - // on_dnd_task_produced: Box::new(|_| unimplemented!()), - on_reorder: Box::new(|_| unimplemented!()), - on_finish: None, - surface_ids: None, - inner: if let Some(info) = state.dragged_applet() { - container( - row::with_children(vec![ - icon::from_name("grip-lines-symbolic") - .size(16) - .symbolic(true) - .into(), - icon::from_name(info.icon.into_owned()).size(32).into(), - column::with_capacity(2) - .spacing(4.0) - .width(Length::Fill) - .push(text::body(info.name)) - .push(text::caption(info.description)) - .into(), - button::icon(icon::from_name("edit-delete-symbolic")) - .extra_small() - .into(), - ]) - .spacing(12) - .align_y(Alignment::Center), - ) - .width(Length::Fixed(state.layout.map_or(400.0, |l| l.width))) - .padding(8) - .class(theme::Container::Custom(Box::new(move |theme| { - let mut style = container::Catalog::style(theme, &theme::Container::Primary); - style.border.radius = 8.0.into(); - style - }))) - .into() - } else { - horizontal_space().width(1).into() - }, - on_cancel: None, + active_applet_offer: active_dnd, } } @@ -863,6 +786,61 @@ impl<'a, Message: 'static + Clone> AppletReorderList<'a, Message> { reordered } + + /// Returns the drag id of the destination. + /// + /// # Panics + /// Panics if the destination has been assigned a Set id, which is invalid. + #[must_use] + pub fn get_drag_id(&self) -> u128 { + match &self.id.0 { + Internal::Unique(id) | Internal::Custom(id, _) => *id as u128, + Internal::Set(_) => panic!("Invalid Id assigned to dnd destination."), + } + } +} + +#[must_use] +/// mark this as a dnd icon +pub fn dnd_icon(info: Applet<'static>, layout: &layout::Layout) -> AppletReorderList<'static, ()> { + AppletReorderList::<'static, ()> { + id: Id::unique(), + info: Vec::new(), + on_create_dnd_source: Box::new(|_| unimplemented!()), + // on_dnd_task_produced: Box::new(|_| unimplemented!()), + on_reorder: Box::new(|_| unimplemented!()), + on_finish: None, + inner: container( + row::with_children(vec![ + icon::from_name("grip-lines-symbolic") + .size(16) + .symbolic(true) + .into(), + icon::from_name(info.icon.into_owned()).size(32).into(), + column::with_capacity(2) + .spacing(4.0) + .width(Length::Fill) + .push(text::body(info.name)) + .push(text::caption(info.description)) + .into(), + button::icon(icon::from_name("edit-delete-symbolic")) + .extra_small() + .into(), + ]) + .spacing(12) + .align_y(Alignment::Center), + ) + .width(Length::Fixed(layout.bounds().width)) + .padding(8) + .class(theme::Container::Custom(Box::new(move |theme| { + let mut style = container::Catalog::style(theme, &theme::Container::Primary); + style.border.radius = 8.0.into(); + style + }))) + .into(), + on_cancel: None, + active_applet_offer: None, + } } impl<'a, Message: 'static> Widget @@ -910,6 +888,10 @@ where renderer: &cosmic::Renderer, operation: &mut dyn Operation<()>, ) { + let state = tree.state.downcast_mut::(); + + operation.custom(state, Some(&self.id)); + self.inner.as_widget().operate( &mut tree.children[0], layout.children().next().unwrap(), @@ -950,286 +932,124 @@ where / self.info.len() as f32; let state = tree.state.downcast_mut::(); - state.dragging_state = match mem::take(&mut state.dragging_state) { - DraggingState::None => { - // if no dragging state, listen for press events - match &event { - event::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) - | event::Event::Touch(touch::Event::FingerPressed { .. }) - if cursor_position.is_over(layout.bounds()) => - { - ret = event::Status::Captured; + state.dragging_state = { + match mem::take(&mut state.dragging_state) { + DraggingState::Dragging(applet) => match &event { + event::Event::Dnd(DndEvent::Source(source_event)) => match source_event { + SourceEvent::Cancelled => { + ret = event::Status::Captured; + if let Some(on_cancel) = self.on_cancel.clone() { + shell.publish(on_cancel); + } + DraggingState::None + } + SourceEvent::Finished => { + ret = event::Status::Captured; - DraggingState::Pressed(cursor_position.position().unwrap_or_default()) + DraggingState::None + } + _ => DraggingState::Dragging(applet), + }, + _ => DraggingState::Dragging(applet), + }, + DraggingState::None => { + // if no dragging state, listen for press events + match &event { + event::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) + | event::Event::Touch(touch::Event::FingerPressed { .. }) + if cursor_position.is_over(layout.bounds()) => + { + ret = event::Status::Captured; + + DraggingState::Pressed(cursor_position.position().unwrap_or_default()) + } + _ => DraggingState::None, } - _ => DraggingState::None, } - } - DraggingState::Dragging(applet) => match &event { - event::Event::PlatformSpecific(PlatformSpecific::Wayland( - wayland::Event::DataSource(wayland::DataSourceEvent::DndFinished), - )) => { - ret = event::Status::Captured; - DraggingState::None - } - event::Event::PlatformSpecific(PlatformSpecific::Wayland( - wayland::Event::DataSource(wayland::DataSourceEvent::Cancelled), - )) => { - ret = event::Status::Captured; - if let Some(on_cancel) = self.on_cancel.clone() { - shell.publish(on_cancel); - } - DraggingState::None - } - event::Event::PlatformSpecific(PlatformSpecific::Wayland( - wayland::Event::DataSource(wayland::DataSourceEvent::DndDropPerformed), - )) => { - ret = event::Status::Captured; + DraggingState::Pressed(start) => { + // if dragging state is pressed, listen for motion events or release events + match &event { + event::Event::Mouse(mouse::Event::CursorMoved { .. }) + | event::Event::Touch(touch::Event::FingerMoved { .. }) => { + let pos = cursor_position.position().unwrap_or_default(); + let d_y = pos.y - start.y; + let d_x = pos.x - start.x; + let distance_squared = d_y * d_y + d_x * d_x; - DraggingState::None - } - _ => DraggingState::Dragging(applet), - }, - DraggingState::Pressed(start) => { - // if dragging state is pressed, listen for motion events or release events - match &event { - event::Event::Mouse(mouse::Event::CursorMoved { .. }) - | event::Event::Touch(touch::Event::FingerMoved { .. }) => { - let pos = cursor_position.position().unwrap_or_default(); - let d_y = pos.y - start.y; - let d_x = pos.x - start.x; - let distance_squared = d_y * d_y + d_x * d_x; + if distance_squared > DRAG_START_DISTANCE_SQUARED { + if let Some((_, applet)) = + self.info.iter().enumerate().find(|(i, _)| { + start.y + < layout.bounds().y + + (*i + 1) as f32 + * (height + spacing.space_xxs as f32) + }) + { + let applet = applet.clone().into_owned(); + state.dragging_state = DraggingState::Dragging(applet.clone()); - if distance_squared > DRAG_START_DISTANCE_SQUARED { - if let Some((_, applet)) = - self.info.iter().enumerate().find(|(i, _)| { - start.y - < layout.bounds().y - + (*i + 1) as f32 * (height + spacing.space_xxs as f32) - }) - { - let (window_id, icon_id) = self.surface_ids.unwrap(); - state.dragging_state = - DraggingState::Dragging(applet.clone().into_owned()); + state.layout = Some(layout.bounds().size()); + shell.publish((self.on_create_dnd_source.as_ref())( + applet.clone(), + )); - // TODO emit a dnd Task - state.layout = Some(layout.bounds().size()); - let state_clone = state.clone(); - shell.publish((self.on_create_dnd_source.as_ref())( - state_clone.clone(), - )); - - let p = applet.path.to_path_buf(); - // shell.publish((self.on_dnd_task_produced.as_ref())(Box::new(move || { - // platform_specific::wayland::data_device::ActionInner::StartDnd { - // mime_types: vec![MIME_TYPE.to_string()], - // actions: DndAction::Move, - // origin_id: window_id, - // icon_id: Some(( - // DndIcon::Widget( - // icon_id, - // Box::new(state_clone.clone()), - // ), - // cosmic::iced::Vector::ZERO - // )), - // data: Box::new(AppletString(p.clone())), - // } - // }))); - ret = event::Status::Captured; - DraggingState::Dragging(applet.clone().into_owned()) + let p = applet.path.to_path_buf(); + iced_core::clipboard::start_dnd::< + cosmic::Theme, + cosmic::Renderer, + (), + >( + clipboard, + false, + Some(iced_core::clipboard::DndSource::Widget( + self.id.clone(), + )), + Some(( + dnd_icon(applet.clone(), &layout).into(), + iced_core::widget::tree::State::new(state.clone()), + )), + Box::new(AppletString(p.clone())), + DndAction::Move, + ); + ret = event::Status::Captured; + let reordered = self + .info + .iter() + .filter(|a| { + applet != **a + }) + .cloned() + .map(pages::desktop::panel::applets_inner::Applet::into_owned) + .collect(); + shell.publish((self.on_reorder.as_ref())(reordered)); + DraggingState::Dragging(applet.clone().into_owned()) + } else { + DraggingState::Pressed(start) + } } else { DraggingState::Pressed(start) } - } else { - DraggingState::Pressed(start) } + event::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | event::Event::Touch( + touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. }, + ) => { + ret = event::Status::Captured; + DraggingState::None + } + _ => DraggingState::Pressed(start), } - event::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | event::Event::Touch( - touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. }, - ) => { - ret = event::Status::Captured; - DraggingState::None - } - _ => DraggingState::Pressed(start), } } }; + state.dnd_offer = match mem::take(&mut state.dnd_offer) { DndOfferState::None => match &event { - event::Event::PlatformSpecific(PlatformSpecific::Wayland( - wayland::Event::DndOffer(wayland::DndOfferEvent::SourceActions(actions)), - )) => DndOfferState::OutsideWidget(Vec::new(), *actions, None), - event::Event::PlatformSpecific(PlatformSpecific::Wayland( - wayland::Event::DndOffer(wayland::DndOfferEvent::Enter { x, y, mime_types }), - )) => { - if mime_types.iter().any(|m| m.as_str() == MIME_TYPE) { - let point = Point::new(*x as f32, *y as f32); - - if layout.bounds().contains(point) { - // shell.publish((self.on_dnd_task_produced.as_ref())(Box::new( - // move || { - // platform_specific::wayland::data_device::ActionInner::SetActions { - // preferred: DndAction::Move, - // accepted: DndAction::Move, - // } - // }, - // ))); - // shell.publish((self.on_dnd_task_produced.as_ref())(Box::new( - // move || { - // platform_specific::wayland::data_device::ActionInner::Accept( - // Some(MIME_TYPE.to_string()), - // ) - // }, - // ))); - let data = if let DraggingState::Dragging(a) = &state.dragging_state { - Some(a.clone()) - } else { - // shell.publish((self.on_dnd_task_produced.as_ref())(Box::new( - // move || { - // platform_specific::wayland::data_device::ActionInner::RequestDndData( - // MIME_TYPE.to_string(), - // ) - // }, - // ))); - None - }; - DndOfferState::HandlingOffer( - mime_types.clone(), - DndAction::empty(), - data, - ) - } else { - let data = match &state.dragging_state { - DraggingState::Dragging(data) => { - let filtered: Vec<_> = self - .info - .clone() - .into_iter() - .filter(|a| a != data) - .collect(); - if filtered != self.info { - shell.publish((self.on_reorder.as_ref())( - filtered - .into_iter() - .map(pages::desktop::panel::applets_inner::Applet::into_owned) - .collect(), - )); - } - Some(data.clone()) - } - _ => None, - }; - DndOfferState::OutsideWidget( - mime_types.clone(), - DndAction::empty(), - data, - ) - } - } else { - DndOfferState::None - } - } - _ => DndOfferState::None, - }, - DndOfferState::OutsideWidget(mime_types, action, data) => match &event { - event::Event::PlatformSpecific(PlatformSpecific::Wayland( - wayland::Event::DndOffer(wayland::DndOfferEvent::SourceActions(actions)), - )) => DndOfferState::OutsideWidget(mime_types, *actions, data), - event::Event::PlatformSpecific(PlatformSpecific::Wayland( - wayland::Event::DndOffer(wayland::DndOfferEvent::Motion { x, y }), - )) => { - let point = Point::new(*x as f32, *y as f32); - - if layout.bounds().contains(point) { - // shell.publish((self.on_dnd_task_produced.as_ref())(Box::new(move || { - // platform_specific::wayland::data_device::ActionInner::SetActions { - // preferred: DndAction::Move, - // accepted: DndAction::Move, - // } - // }))); - // shell.publish((self.on_dnd_task_produced.as_ref())(Box::new(move || { - // platform_specific::wayland::data_device::ActionInner::Accept(Some( - // MIME_TYPE.to_string(), - // )) - // }))); - // shell.publish((self.on_dnd_task_produced.as_ref())(Box::new(move || { - // platform_specific::wayland::data_device::ActionInner::SetActions { - // preferred: action.intersection(DndAction::Move), - // accepted: action - // .intersection(DndAction::Move.union(DndAction::Copy)), - // } - // }))); - // TODO maybe keep track of data and request here if we don't have it - // also maybe just refactor DND Targets to allow easier handling... - - if data.is_none() { - // shell.publish((self.on_dnd_task_produced.as_ref())(Box::new( - // move || { - // platform_specific::wayland::data_device::ActionInner::RequestDndData( - // MIME_TYPE.to_string(), - // ) - // }, - // ))); - } - if let Some(applet) = data.clone() { - let reordered_list: Vec<_> = self.get_reordered( - &layout, - Point { - x: *x as f32, - y: *y as f32, - }, - applet, - ); - if reordered_list != self.info { - // shell.publish((self.on_reorder.as_ref())( - // reordered_list.into_iter().map(Applet::into_owned).collect(), - // )); - } - } - - DndOfferState::HandlingOffer(mime_types, DndAction::empty(), data) - } else { - DndOfferState::OutsideWidget(mime_types, DndAction::empty(), data) - } - } - event::Event::PlatformSpecific(PlatformSpecific::Wayland( - wayland::Event::DndOffer(wayland::DndOfferEvent::DndData { - data: new_data, - mime_type, - }), - )) => { - if mime_type.as_str() == MIME_TYPE { - let data = std::str::from_utf8(new_data.as_bytes()) - .ok() - .and_then(|s| url::Url::from_str(s).ok()) - .and_then(|url| url.to_file_path().ok()) - .and_then(|p| Applet::try_from(Cow::from(p)).ok()); - DndOfferState::OutsideWidget(mime_types, action, data) - } else { - DndOfferState::OutsideWidget(mime_types, action, data) - } - } - event::Event::PlatformSpecific(PlatformSpecific::Wayland( - wayland::Event::DndOffer( - wayland::DndOfferEvent::DropPerformed | wayland::DndOfferEvent::Leave, - ), - )) => DndOfferState::None, - _ => DndOfferState::OutsideWidget(mime_types, action, data), - }, - DndOfferState::HandlingOffer(mime_types, action, data) => match &event { - event::Event::PlatformSpecific(PlatformSpecific::Wayland( - wayland::Event::DndOffer(wayland::DndOfferEvent::Motion { x, y }), - )) => { - let point = Point::new(*x as f32, *y as f32); - if layout.bounds().contains(point) { - // shell.publish((self.on_dnd_task_produced.as_ref())(Box::new(move || { - // platform_specific::wayland::data_device::ActionInner::SetActions { - // preferred: DndAction::Move, - // accepted: DndAction::Move, - // } - // }))); - if let Some(data) = data.clone() { + event::Event::Dnd(dnd_event) => match dnd_event { + DndEvent::Offer(rectangle, OfferEvent::Enter { x, y, .. }) + if *rectangle == Some(self.get_drag_id()) => + { + if let Some(data) = self.active_applet_offer.clone() { let reordered_list = self.get_reordered( &layout, Point { @@ -1247,124 +1067,69 @@ where )); } } - DndOfferState::HandlingOffer(mime_types, DndAction::empty(), data) - } else { - if let Some(applet) = data.clone() { - let reordered_list: Vec<_> = self.get_reordered( + DndOfferState::HandlingOffer + } + _ => DndOfferState::None, + }, + _ => DndOfferState::None, + }, + DndOfferState::HandlingOffer => match &event { + event::Event::Dnd(dnd_event) => match dnd_event { + DndEvent::Offer(rectangle, OfferEvent::Motion { x, y }) + if *rectangle == Some(self.get_drag_id()) => + { + if let Some(data) = self.active_applet_offer.clone() { + let reordered_list = self.get_reordered( &layout, Point { x: *x as f32, y: *y as f32, }, - applet, + data, ); if reordered_list != self.info { shell.publish((self.on_reorder.as_ref())( - reordered_list.into_iter().map(Applet::into_owned).collect(), - )); - } - } - // shell.publish((self.on_dnd_task_produced.as_ref())(Box::new(move || { - // platform_specific::wayland::data_device::ActionInner::Accept(None) - // }))); - DndOfferState::OutsideWidget(mime_types, DndAction::empty(), data) - } - } - event::Event::PlatformSpecific(PlatformSpecific::Wayland( - wayland::Event::DndOffer(wayland::DndOfferEvent::Leave), - )) => DndOfferState::None, - event::Event::PlatformSpecific(PlatformSpecific::Wayland( - wayland::Event::DndOffer(wayland::DndOfferEvent::SourceActions(actions)), - )) => { - // shell.publish((self.on_dnd_task_produced.as_ref())(Box::new(move || { - // platform_specific::wayland::data_device::ActionInner::SetActions { - // preferred: DndAction::Move, - // accepted: DndAction::Move, - // } - // }))); - DndOfferState::HandlingOffer(mime_types, *actions, data) - } - event::Event::PlatformSpecific(PlatformSpecific::Wayland( - wayland::Event::DndOffer(wayland::DndOfferEvent::DndData { - data: new_data, - mime_type, - }), - )) => { - if mime_type.as_str() == MIME_TYPE { - let data = std::str::from_utf8(new_data.as_bytes()) - .ok() - .and_then(|s| url::Url::from_str(s).ok()) - .and_then(|url| url.to_file_path().ok()) - .and_then(|p| Applet::try_from(Cow::from(p)).ok()); - if let Some(data) = data.borrow() { - let filtered: Vec<_> = self - .info - .clone() - .into_iter() - .filter(|a| a != data) - .collect(); - if filtered != self.info { - shell.publish((self.on_reorder.as_ref())( - filtered + reordered_list .into_iter() .map(pages::desktop::panel::applets_inner::Applet::into_owned) .collect(), )); } } - - DndOfferState::HandlingOffer(mime_types, action, data) - } else { - DndOfferState::HandlingOffer(mime_types, action, data) + DndOfferState::HandlingOffer } - } - event::Event::PlatformSpecific(PlatformSpecific::Wayland( - wayland::Event::DndOffer(wayland::DndOfferEvent::DropPerformed), - )) => { - // shell.publish((self.on_dnd_task_produced.as_ref())(Box::new(move || { - // platform_specific::wayland::data_device::ActionInner::SetActions { - // preferred: DndAction::Move, - // accepted: DndAction::Move, - // } - // }))); - // shell.publish((self.on_dnd_task_produced.as_ref())(Box::new(move || { - // platform_specific::wayland::data_device::ActionInner::Accept(Some( - // MIME_TYPE.to_string(), - // )) - // }))); - // shell.publish((self.on_dnd_task_produced.as_ref())(Box::new(move || { - // platform_specific::wayland::data_device::ActionInner::RequestDndData( - // MIME_TYPE.to_string(), - // ) - // }))); - DndOfferState::Dropped - } - _ => DndOfferState::HandlingOffer(mime_types, action, data), - }, - DndOfferState::Dropped => match &event { - event::Event::PlatformSpecific(PlatformSpecific::Wayland( - wayland::Event::DndOffer(wayland::DndOfferEvent::DndData { .. }), - )) => { - if let Some(on_finish) = self.on_finish.clone() { - shell.publish(on_finish); - } - // shell.publish((self.on_dnd_task_produced.as_ref())(Box::new(move || { - // platform_specific::wayland::data_device::ActionInner::DndFinished - // }))); + DndEvent::Offer( + rectangle, + OfferEvent::LeaveDestination | OfferEvent::Leave, + ) if *rectangle == Some(self.get_drag_id()) => { + let reordered = self + .info + .iter() + .filter(|a| { + !self + .active_applet_offer + .as_ref() + .is_some_and(|offer| offer == *a) + }) + .cloned() + .map(pages::desktop::panel::applets_inner::Applet::into_owned) + .collect(); + shell.publish((self.on_reorder.as_ref())(reordered)); - DndOfferState::None - } - event::Event::PlatformSpecific(PlatformSpecific::Wayland( - wayland::Event::DndOffer(wayland::DndOfferEvent::Leave), - )) => { - // already applied the offer, so we can just finish - if let Some(on_cancel) = self.on_cancel.clone() { - shell.publish(on_cancel); + DndOfferState::None } + DndEvent::Offer(rectangle, OfferEvent::Data { .. }) + if *rectangle == Some(self.get_drag_id()) => + { + if let Some(on_finish) = self.on_finish.clone() { + shell.publish(on_finish); + } - DndOfferState::None - } - _ => DndOfferState::Dropped, + DndOfferState::None + } + _ => DndOfferState::HandlingOffer, + }, + _ => DndOfferState::HandlingOffer, }, }; @@ -1435,28 +1200,65 @@ where interaction => interaction, } } + + fn id(&self) -> Option { + Some(self.id.clone()) + } + + fn set_id(&mut self, id: Id) { + self.id = id; + } + + fn drag_destinations( + &self, + _state: &Tree, + layout: layout::Layout<'_>, + _renderer: &cosmic::Renderer, + dnd_rectangles: &mut cosmic::iced_core::clipboard::DndDestinationRectangles, + ) { + let Rectangle { + x, + y, + width, + height, + } = layout.bounds(); + dnd_rectangles.push(DndDestinationRectangle { + id: self.get_drag_id(), + rectangle: cosmic::iced::clipboard::dnd::Rectangle { + x: x as f64, + y: y as f64, + width: width as f64, + height: height as f64, + }, + mime_types: vec![Cow::Owned(MIME_TYPE.to_string())], + actions: DndAction::Move, + preferred: DndAction::Move, + }); + } } -/// A string which can be sent to the clipboard or drag-and-dropped. -// #[derive(Debug, Clone)] -// pub struct AppletString(PathBuf); +impl AsMimeTypes for AppletString { + fn available(&self) -> Cow<'static, [String]> { + Cow::Owned(vec![MIME_TYPE.to_string()]) + } -// impl DataFromMimeType for AppletString { -// fn from_mime_type(&self, mime_type: &str) -> Option> { -// if mime_type == MIME_TYPE { -// let data = Some( -// url::Url::from_file_path(self.0.clone()) -// .ok()? -// .to_string() -// .as_bytes() -// .to_vec(), -// ); -// data -// } else { -// None -// } -// } -// } + fn as_bytes(&self, mime_type: &str) -> Option> { + if mime_type == MIME_TYPE { + Some(Cow::Owned( + url::Url::from_file_path(self.0.clone()) + .ok()? + .to_string() + .as_bytes() + .to_vec(), + )) + } else { + None + } + } +} +/// A string which can be sent to the clipboard or drag-and-dropped. +#[derive(Debug, Clone)] +pub struct AppletString(PathBuf); #[derive(Debug, Default, Clone)] pub enum DraggingState { @@ -1473,9 +1275,7 @@ pub enum DraggingState { pub(crate) enum DndOfferState { #[default] None, - OutsideWidget(Vec, DndAction, Option>), - HandlingOffer(Vec, DndAction, Option>), - Dropped, + HandlingOffer, } #[derive(Debug, Default, Clone)]