refactor dnd

This commit is contained in:
Ashley Wulber 2024-10-09 16:01:47 -04:00 • committed by Ashley Wulber
parent de179d06f3
commit 35717797b7
3 changed files with 296 additions and 502 deletions

View file

@ -669,12 +669,6 @@ impl cosmic::Application for SettingsApp {
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
fn view_window(&self, id: window::Id) -> Element<Message> { fn view_window(&self, id: window::Id) -> Element<Message> {
if let Some(Some(page)) =
(id == *APPLET_DND_ICON_ID).then(|| self.pages.page::<applets_inner::Page>())
{
return page.dnd_icon();
}
panic!("unknown window ID: {id:?}"); panic!("unknown window ID: {id:?}");
} }

View file

@ -40,7 +40,7 @@ impl Default for Page {
.collect(), .collect(),
config_helper, config_helper,
current_config, current_config,
reorder_widget_state: ReorderWidgetState::default(), reorder_widget_state: None,
search: String::new(), search: String::new(),
context: None, context: None,
}, },

View file

@ -1,43 +1,33 @@
use button::Catalog as ButtonStyleSheet; 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::iced::{alignment, Vector};
use cosmic::widget::{ use cosmic::iced_core;
button, column, container, horizontal_space, icon, list_column, row, text, text_input, Column, use cosmic::widget::{button, column, container, icon, list_column, row, text, text_input, Column};
};
use cosmic::{ use cosmic::{
cctk::sctk::reexports::client::protocol::wl_data_device_manager::DndAction,
cosmic_config::{Config, CosmicConfigEntry}, cosmic_config::{Config, CosmicConfigEntry},
iced::{ iced::{
alignment::{Horizontal, Vertical}, alignment::{Horizontal, Vertical},
core::window, core::window,
event::{ event, mouse, overlay, touch, Alignment, Color, Length, Point, Rectangle, Size,
self,
wayland::{self},
PlatformSpecific,
},
mouse, overlay, touch, Alignment, Color, Length, Point, Rectangle, Size,
}, },
iced_runtime::{core::id::Id, Task}, iced_runtime::{core::id::Id, Task},
iced_widget::{ iced_widget::core::{
core::{ layout, renderer,
layout, renderer, widget::{tree, Operation, Tree},
widget::{tree, Operation, Tree}, Clipboard, Shell, Widget,
Clipboard, Shell, Widget,
},
graphics::image::image_rs::EncodableLayout,
}, },
theme, Apply, Element, theme, Apply, Element,
}; };
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use std::{ use std::path::PathBuf;
borrow::{Borrow, Cow}, use std::{borrow::Cow, fmt::Debug, mem, path::Path};
fmt::Debug,
mem,
path::Path,
str::FromStr,
};
use crate::{app, pages}; use crate::{app, pages};
use cosmic_panel_config::CosmicPanelConfig; use cosmic_panel_config::CosmicPanelConfig;
@ -64,7 +54,7 @@ pub struct Page {
pub(crate) available_entries: Vec<Applet<'static>>, pub(crate) available_entries: Vec<Applet<'static>>,
pub(crate) config_helper: Option<Config>, pub(crate) config_helper: Option<Config>,
pub(crate) current_config: Option<CosmicPanelConfig>, pub(crate) current_config: Option<CosmicPanelConfig>,
pub(crate) reorder_widget_state: ReorderWidgetState, pub(crate) reorder_widget_state: Option<(Applet<'static>, CosmicPanelConfig)>,
pub(crate) search: String, pub(crate) search: String,
pub(crate) context: Option<ContextDrawer>, pub(crate) context: Option<ContextDrawer>,
} }
@ -85,7 +75,7 @@ impl Default for Page {
.collect(), .collect(),
config_helper, config_helper,
current_config, current_config,
reorder_widget_state: ReorderWidgetState::default(), reorder_widget_state: None,
search: String::new(), search: String::new(),
context: None, context: None,
} }
@ -164,7 +154,7 @@ pub enum Message {
ReorderEnd(Vec<Applet<'static>>), ReorderEnd(Vec<Applet<'static>>),
Applets(Vec<Applet<'static>>), Applets(Vec<Applet<'static>>),
PanelConfig(CosmicPanelConfig), PanelConfig(CosmicPanelConfig),
StartDnd(ReorderWidgetState), StartDnd(Applet<'static>),
// DnDTask(Arc<Box<dyn Send + Sync + Fn() -> ActionInner>>), // DnDTask(Arc<Box<dyn Send + Sync + Fn() -> ActionInner>>),
Search(String), Search(String),
AddApplet(Applet<'static>), AddApplet(Applet<'static>),
@ -217,11 +207,6 @@ impl Page {
} }
} }
#[must_use]
pub fn dnd_icon(&self) -> Element<app::Message> {
Element::from(AppletReorderList::dnd_icon(&self.reorder_widget_state))
}
#[must_use] #[must_use]
#[allow(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
pub fn add_applet_view<T: Fn(Message) -> crate::pages::Message + Copy + 'static>( pub fn add_applet_view<T: Fn(Message) -> crate::pages::Message + Copy + 'static>(
@ -373,15 +358,13 @@ impl Page {
Message::Applets(applets) => { Message::Applets(applets) => {
self.available_entries = applets; self.available_entries = applets;
} }
Message::StartDnd(state) => { Message::StartDnd(applet) => {
self.reorder_widget_state = state; self.reorder_widget_state =
Some((applet, self.current_config.clone().unwrap_or_default()));
return Task::none(); return Task::none();
} }
// Message::DnDTask(action) => {
// return data_device_action(action());
// }
Message::Save => { Message::Save => {
self.reorder_widget_state = ReorderWidgetState::default(); self.reorder_widget_state = None;
self.save(); self.save();
} }
Message::RemoveStart(to_remove) => { Message::RemoveStart(to_remove) => {
@ -418,14 +401,9 @@ impl Page {
// TODO ask design team // TODO ask design team
} }
Message::Cancel => { Message::Cancel => {
self.reorder_widget_state = ReorderWidgetState::default(); if let Some((_, config)) = self.reorder_widget_state.take() {
let current_config = self.config_helper.as_ref().and_then(|config_helper| { self.current_config = Some(config);
// 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;
} }
Message::Search(text) => { Message::Search(text) => {
self.search = text; self.search = text;
@ -489,7 +467,6 @@ pub fn lists<
.collect() .collect()
}) })
.unwrap_or_default(), .unwrap_or_default(),
Some((window::Id::NONE, *APPLET_DND_ICON_ID)),
Message::StartDnd, Message::StartDnd,
// |a| Message::DnDTask(Arc::new(a)), // |a| Message::DnDTask(Arc::new(a)),
Message::RemoveStart, Message::RemoveStart,
@ -497,7 +474,7 @@ pub fn lists<
Message::ReorderStart, Message::ReorderStart,
Message::Save, Message::Save,
Message::Cancel, Message::Cancel,
page.reorder_widget_state.dragged_applet().as_ref(), page.reorder_widget_state.as_ref().map(|(a, _)| a.clone()),
) )
.into(), .into(),
]) ])
@ -520,7 +497,6 @@ pub fn lists<
.collect() .collect()
}) })
.unwrap_or_default(), .unwrap_or_default(),
Some((window::Id::NONE, *APPLET_DND_ICON_ID)),
Message::StartDnd, Message::StartDnd,
// |a| Message::DnDTask(Arc::new(a)), // |a| Message::DnDTask(Arc::new(a)),
Message::RemoveCenter, Message::RemoveCenter,
@ -528,7 +504,7 @@ pub fn lists<
Message::ReorderCenter, Message::ReorderCenter,
Message::Save, Message::Save,
Message::Cancel, Message::Cancel,
page.reorder_widget_state.dragged_applet().as_ref(), page.reorder_widget_state.as_ref().map(|(a, _)| a.clone()),
) )
.into(), .into(),
]) ])
@ -552,7 +528,6 @@ pub fn lists<
.collect() .collect()
}) })
.unwrap_or_default(), .unwrap_or_default(),
Some((window::Id::NONE, *APPLET_DND_ICON_ID)),
Message::StartDnd, Message::StartDnd,
// |a| Message::DnDTask(Arc::new(a)), // |a| Message::DnDTask(Arc::new(a)),
Message::RemoveEnd, Message::RemoveEnd,
@ -560,7 +535,7 @@ pub fn lists<
Message::ReorderEnd, Message::ReorderEnd,
Message::Save, Message::Save,
Message::Cancel, Message::Cancel,
page.reorder_widget_state.dragged_applet().as_ref(), page.reorder_widget_state.as_ref().map(|(a, _)| a.clone()),
) )
.into(), .into(),
]) ])
@ -639,13 +614,13 @@ impl<'a> Applet<'a> {
pub struct AppletReorderList<'a, Message> { pub struct AppletReorderList<'a, Message> {
id: Id, id: Id,
info: Vec<Applet<'a>>, info: Vec<Applet<'a>>,
on_create_dnd_source: Box<dyn Fn(ReorderWidgetState) -> Message + 'a>, on_create_dnd_source: Box<dyn Fn(Applet<'static>) -> Message + 'a>,
// on_dnd_task_produced: OnDndTask<'a, Message>, // on_dnd_task_produced: OnDndTask<'a, Message>,
on_reorder: Box<dyn Fn(Vec<Applet<'static>>) -> Message + 'a>, on_reorder: Box<dyn Fn(Vec<Applet<'static>>) -> Message + 'a>,
on_finish: Option<Message>, on_finish: Option<Message>,
on_cancel: Option<Message>, on_cancel: Option<Message>,
surface_ids: Option<(window::Id, window::Id)>,
inner: Element<'a, Message>, inner: Element<'a, Message>,
active_applet_offer: Option<Applet<'a>>,
} }
impl<'a, Message: 'static + Clone> AppletReorderList<'a, Message> { 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 /// new applet list which can be reordered and dragged
pub fn new( pub fn new(
info: Vec<Applet<'a>>, info: Vec<Applet<'a>>,
surface_ids: Option<(window::Id, window::Id)>, on_create_dnd_source: impl Fn(Applet<'static>) -> Message + 'a,
on_create_dnd_source: impl Fn(ReorderWidgetState) -> Message + 'a,
// on_dnd_task_produced: impl Fn(
// Box<dyn Send + Sync + Fn() -> platform_specific::wayland::data_device::ActionInner>,
// ) -> Message
// + 'a,
on_remove: impl Fn(String) -> Message + 'a, on_remove: impl Fn(String) -> Message + 'a,
on_details: impl Fn(String) -> Message + 'a, on_details: impl Fn(String) -> Message + 'a,
on_reorder: impl Fn(Vec<Applet<'static>>) -> Message + 'a, on_reorder: impl Fn(Vec<Applet<'static>>) -> Message + 'a,
on_apply_reorder: Message, on_apply_reorder: Message,
on_cancel: Message, on_cancel: Message,
active_dnd: Option<&Applet<'a>>, active_dnd: Option<Applet<'a>>,
) -> Self { ) -> Self {
let spacing = cosmic::theme::active().cosmic().spacing; let spacing = cosmic::theme::active().cosmic().spacing;
let applet_buttons = info let applet_buttons = info
@ -722,7 +692,6 @@ impl<'a, Message: 'static + Clone> AppletReorderList<'a, Message> {
on_reorder: Box::new(on_reorder), on_reorder: Box::new(on_reorder),
on_finish: Some(on_apply_reorder), on_finish: Some(on_apply_reorder),
on_cancel: Some(on_cancel), on_cancel: Some(on_cancel),
surface_ids,
inner: if active_dnd.is_some() && applet_buttons.is_empty() { inner: if active_dnd.is_some() && applet_buttons.is_empty() {
container( container(
text::body(fl!("drop-here")) text::body(fl!("drop-here"))
@ -748,53 +717,7 @@ impl<'a, Message: 'static + Clone> AppletReorderList<'a, Message> {
.spacing(spacing.space_xxs) .spacing(spacing.space_xxs)
.into() .into()
}, },
} active_applet_offer: active_dnd,
}
#[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,
} }
} }
@ -863,6 +786,61 @@ impl<'a, Message: 'static + Clone> AppletReorderList<'a, Message> {
reordered 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<Message, cosmic::Theme, cosmic::Renderer> impl<'a, Message: 'static> Widget<Message, cosmic::Theme, cosmic::Renderer>
@ -910,6 +888,10 @@ where
renderer: &cosmic::Renderer, renderer: &cosmic::Renderer,
operation: &mut dyn Operation<()>, operation: &mut dyn Operation<()>,
) { ) {
let state = tree.state.downcast_mut::<ReorderWidgetState>();
operation.custom(state, Some(&self.id));
self.inner.as_widget().operate( self.inner.as_widget().operate(
&mut tree.children[0], &mut tree.children[0],
layout.children().next().unwrap(), layout.children().next().unwrap(),
@ -950,286 +932,124 @@ where
/ self.info.len() as f32; / self.info.len() as f32;
let state = tree.state.downcast_mut::<ReorderWidgetState>(); let state = tree.state.downcast_mut::<ReorderWidgetState>();
state.dragging_state = match mem::take(&mut state.dragging_state) { state.dragging_state = {
DraggingState::None => { match mem::take(&mut state.dragging_state) {
// if no dragging state, listen for press events DraggingState::Dragging(applet) => match &event {
match &event { event::Event::Dnd(DndEvent::Source(source_event)) => match source_event {
event::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) SourceEvent::Cancelled => {
| event::Event::Touch(touch::Event::FingerPressed { .. }) ret = event::Status::Captured;
if cursor_position.is_over(layout.bounds()) => if let Some(on_cancel) = self.on_cancel.clone() {
{ shell.publish(on_cancel);
ret = event::Status::Captured; }
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::Pressed(start) => {
DraggingState::Dragging(applet) => match &event { // if dragging state is pressed, listen for motion events or release events
event::Event::PlatformSpecific(PlatformSpecific::Wayland( match &event {
wayland::Event::DataSource(wayland::DataSourceEvent::DndFinished), event::Event::Mouse(mouse::Event::CursorMoved { .. })
)) => { | event::Event::Touch(touch::Event::FingerMoved { .. }) => {
ret = event::Status::Captured; let pos = cursor_position.position().unwrap_or_default();
DraggingState::None let d_y = pos.y - start.y;
} let d_x = pos.x - start.x;
event::Event::PlatformSpecific(PlatformSpecific::Wayland( let distance_squared = d_y * d_y + d_x * d_x;
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::None if distance_squared > DRAG_START_DISTANCE_SQUARED {
} if let Some((_, applet)) =
_ => DraggingState::Dragging(applet), self.info.iter().enumerate().find(|(i, _)| {
}, start.y
DraggingState::Pressed(start) => { < layout.bounds().y
// if dragging state is pressed, listen for motion events or release events + (*i + 1) as f32
match &event { * (height + spacing.space_xxs as f32)
event::Event::Mouse(mouse::Event::CursorMoved { .. }) })
| event::Event::Touch(touch::Event::FingerMoved { .. }) => { {
let pos = cursor_position.position().unwrap_or_default(); let applet = applet.clone().into_owned();
let d_y = pos.y - start.y; state.dragging_state = DraggingState::Dragging(applet.clone());
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 { state.layout = Some(layout.bounds().size());
if let Some((_, applet)) = shell.publish((self.on_create_dnd_source.as_ref())(
self.info.iter().enumerate().find(|(i, _)| { applet.clone(),
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());
// TODO emit a dnd Task let p = applet.path.to_path_buf();
state.layout = Some(layout.bounds().size()); iced_core::clipboard::start_dnd::<
let state_clone = state.clone(); cosmic::Theme,
shell.publish((self.on_create_dnd_source.as_ref())( cosmic::Renderer,
state_clone.clone(), (),
)); >(
clipboard,
let p = applet.path.to_path_buf(); false,
// shell.publish((self.on_dnd_task_produced.as_ref())(Box::new(move || { Some(iced_core::clipboard::DndSource::Widget(
// platform_specific::wayland::data_device::ActionInner::StartDnd { self.id.clone(),
// mime_types: vec![MIME_TYPE.to_string()], )),
// actions: DndAction::Move, Some((
// origin_id: window_id, dnd_icon(applet.clone(), &layout).into(),
// icon_id: Some(( iced_core::widget::tree::State::new(state.clone()),
// DndIcon::Widget( )),
// icon_id, Box::new(AppletString(p.clone())),
// Box::new(state_clone.clone()), DndAction::Move,
// ), );
// cosmic::iced::Vector::ZERO ret = event::Status::Captured;
// )), let reordered = self
// data: Box::new(AppletString(p.clone())), .info
// } .iter()
// }))); .filter(|a| {
ret = event::Status::Captured; applet != **a
DraggingState::Dragging(applet.clone().into_owned()) })
.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 { } else {
DraggingState::Pressed(start) 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) { state.dnd_offer = match mem::take(&mut state.dnd_offer) {
DndOfferState::None => match &event { DndOfferState::None => match &event {
event::Event::PlatformSpecific(PlatformSpecific::Wayland( event::Event::Dnd(dnd_event) => match dnd_event {
wayland::Event::DndOffer(wayland::DndOfferEvent::SourceActions(actions)), DndEvent::Offer(rectangle, OfferEvent::Enter { x, y, .. })
)) => DndOfferState::OutsideWidget(Vec::new(), *actions, None), if *rectangle == Some(self.get_drag_id()) =>
event::Event::PlatformSpecific(PlatformSpecific::Wayland( {
wayland::Event::DndOffer(wayland::DndOfferEvent::Enter { x, y, mime_types }), if let Some(data) = self.active_applet_offer.clone() {
)) => {
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() {
let reordered_list = self.get_reordered( let reordered_list = self.get_reordered(
&layout, &layout,
Point { Point {
@ -1247,124 +1067,69 @@ where
)); ));
} }
} }
DndOfferState::HandlingOffer(mime_types, DndAction::empty(), data) DndOfferState::HandlingOffer
} else { }
if let Some(applet) = data.clone() { _ => DndOfferState::None,
let reordered_list: Vec<_> = self.get_reordered( },
_ => 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, &layout,
Point { Point {
x: *x as f32, x: *x as f32,
y: *y as f32, y: *y as f32,
}, },
applet, data,
); );
if reordered_list != self.info { if reordered_list != self.info {
shell.publish((self.on_reorder.as_ref())( shell.publish((self.on_reorder.as_ref())(
reordered_list.into_iter().map(Applet::into_owned).collect(), reordered_list
));
}
}
// 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
.into_iter() .into_iter()
.map(pages::desktop::panel::applets_inner::Applet::into_owned) .map(pages::desktop::panel::applets_inner::Applet::into_owned)
.collect(), .collect(),
)); ));
} }
} }
DndOfferState::HandlingOffer
DndOfferState::HandlingOffer(mime_types, action, data)
} else {
DndOfferState::HandlingOffer(mime_types, action, data)
} }
} DndEvent::Offer(
event::Event::PlatformSpecific(PlatformSpecific::Wayland( rectangle,
wayland::Event::DndOffer(wayland::DndOfferEvent::DropPerformed), OfferEvent::LeaveDestination | OfferEvent::Leave,
)) => { ) if *rectangle == Some(self.get_drag_id()) => {
// shell.publish((self.on_dnd_task_produced.as_ref())(Box::new(move || { let reordered = self
// platform_specific::wayland::data_device::ActionInner::SetActions { .info
// preferred: DndAction::Move, .iter()
// accepted: DndAction::Move, .filter(|a| {
// } !self
// }))); .active_applet_offer
// shell.publish((self.on_dnd_task_produced.as_ref())(Box::new(move || { .as_ref()
// platform_specific::wayland::data_device::ActionInner::Accept(Some( .is_some_and(|offer| offer == *a)
// MIME_TYPE.to_string(), })
// )) .cloned()
// }))); .map(pages::desktop::panel::applets_inner::Applet::into_owned)
// shell.publish((self.on_dnd_task_produced.as_ref())(Box::new(move || { .collect();
// platform_specific::wayland::data_device::ActionInner::RequestDndData( shell.publish((self.on_reorder.as_ref())(reordered));
// 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
// })));
DndOfferState::None 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);
} }
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::None
} }
_ => DndOfferState::Dropped, _ => DndOfferState::HandlingOffer,
},
_ => DndOfferState::HandlingOffer,
}, },
}; };
@ -1435,28 +1200,65 @@ where
interaction => interaction, interaction => interaction,
} }
} }
fn id(&self) -> Option<Id> {
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. impl AsMimeTypes for AppletString {
// #[derive(Debug, Clone)] fn available(&self) -> Cow<'static, [String]> {
// pub struct AppletString(PathBuf); Cow::Owned(vec![MIME_TYPE.to_string()])
}
// impl DataFromMimeType for AppletString { fn as_bytes(&self, mime_type: &str) -> Option<Cow<'static, [u8]>> {
// fn from_mime_type(&self, mime_type: &str) -> Option<Vec<u8>> { if mime_type == MIME_TYPE {
// if mime_type == MIME_TYPE { Some(Cow::Owned(
// let data = Some( url::Url::from_file_path(self.0.clone())
// url::Url::from_file_path(self.0.clone()) .ok()?
// .ok()? .to_string()
// .to_string() .as_bytes()
// .as_bytes() .to_vec(),
// .to_vec(), ))
// ); } else {
// data None
// } 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)] #[derive(Debug, Default, Clone)]
pub enum DraggingState { pub enum DraggingState {
@ -1473,9 +1275,7 @@ pub enum DraggingState {
pub(crate) enum DndOfferState { pub(crate) enum DndOfferState {
#[default] #[default]
None, None,
OutsideWidget(Vec<String>, DndAction, Option<Applet<'static>>), HandlingOffer,
HandlingOffer(Vec<String>, DndAction, Option<Applet<'static>>),
Dropped,
} }
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]