refactor dnd
This commit is contained in:
parent
de179d06f3
commit
35717797b7
3 changed files with 296 additions and 502 deletions
|
|
@ -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:?}");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue