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)]
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:?}");
}

View file

@ -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,
},

View file

@ -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<Applet<'static>>,
pub(crate) config_helper: Option<Config>,
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) context: Option<ContextDrawer>,
}
@ -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<Applet<'static>>),
Applets(Vec<Applet<'static>>),
PanelConfig(CosmicPanelConfig),
StartDnd(ReorderWidgetState),
StartDnd(Applet<'static>),
// DnDTask(Arc<Box<dyn Send + Sync + Fn() -> ActionInner>>),
Search(String),
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]
#[allow(clippy::too_many_lines)]
pub fn add_applet_view<T: Fn(Message) -> 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<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_reorder: Box<dyn Fn(Vec<Applet<'static>>) -> Message + 'a>,
on_finish: Option<Message>,
on_cancel: Option<Message>,
surface_ids: Option<(window::Id, window::Id)>,
inner: Element<'a, Message>,
active_applet_offer: Option<Applet<'a>>,
}
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<Applet<'a>>,
surface_ids: Option<(window::Id, window::Id)>,
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_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<Applet<'static>>) -> Message + 'a,
on_apply_reorder: Message,
on_cancel: Message,
active_dnd: Option<&Applet<'a>>,
active_dnd: Option<Applet<'a>>,
) -> 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<Message, cosmic::Theme, cosmic::Renderer>
@ -910,6 +888,10 @@ where
renderer: &cosmic::Renderer,
operation: &mut dyn Operation<()>,
) {
let state = tree.state.downcast_mut::<ReorderWidgetState>();
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::<ReorderWidgetState>();
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<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.
// #[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<Vec<u8>> {
// 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<Cow<'static, [u8]>> {
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<String>, DndAction, Option<Applet<'static>>),
HandlingOffer(Vec<String>, DndAction, Option<Applet<'static>>),
Dropped,
HandlingOffer,
}
#[derive(Debug, Default, Clone)]