feat: polish app list dnd

This commit is contained in:
Ashley Wulber 2023-04-10 14:19:14 -04:00 committed by Ashley Wulber
parent 8a040dd338
commit 1391388ec0

View file

@ -21,9 +21,7 @@ use cosmic::iced::wayland::actions::data_device::DndIcon;
use cosmic::iced::wayland::actions::window::SctkWindowSettings; use cosmic::iced::wayland::actions::window::SctkWindowSettings;
use cosmic::iced::wayland::popup::destroy_popup; use cosmic::iced::wayland::popup::destroy_popup;
use cosmic::iced::wayland::popup::get_popup; use cosmic::iced::wayland::popup::get_popup;
use cosmic::iced::widget::dnd_source; use cosmic::iced::widget::{column, dnd_source, mouse_listener, row, text, Column, Row};
use cosmic::iced::widget::mouse_listener;
use cosmic::iced::widget::{column, row};
use cosmic::iced::Settings; use cosmic::iced::Settings;
use cosmic::iced::{window, Application, Command, Subscription}; use cosmic::iced::{window, Application, Command, Subscription};
use cosmic::iced_native::alignment::Horizontal; use cosmic::iced_native::alignment::Horizontal;
@ -126,7 +124,7 @@ impl DockItem {
&self, &self,
applet_helper: &CosmicAppletHelper, applet_helper: &CosmicAppletHelper,
rectangle_tracker: Option<&RectangleTracker<u32>>, rectangle_tracker: Option<&RectangleTracker<u32>>,
has_popup: bool, interaction_enabled: bool,
) -> Element<'_, Message> { ) -> Element<'_, Message> {
let DockItem { let DockItem {
toplevels, toplevels,
@ -182,23 +180,28 @@ impl DockItem {
let mut icon_button = cosmic::widget::button(Button::Text) let mut icon_button = cosmic::widget::button(Button::Text)
.custom(vec![icon_wrapper]) .custom(vec![icon_wrapper])
.padding(8); .padding(8);
if !has_popup { let icon_button = if interaction_enabled {
icon_button = icon_button.on_press( dnd_source(
toplevels mouse_listener(
.first() icon_button
.map(|t| Message::Activate(t.0.clone())) .on_press(
.unwrap_or_else(|| Message::Exec(desktop_info.exec.clone())), toplevels
); .first()
} .map(|t| Message::Activate(t.0.clone()))
.unwrap_or_else(|| Message::Exec(desktop_info.exec.clone())),
// TODO tooltip on hover )
let icon_button = dnd_source( .width(Length::Shrink)
mouse_listener(icon_button.width(Length::Shrink).height(Length::Shrink)) .height(Length::Shrink),
)
.on_right_release(Message::Popup(desktop_info.id.clone())), .on_right_release(Message::Popup(desktop_info.id.clone())),
) )
.on_drag(Message::StartDrag(*id)) .on_drag(Message::StartDrag(desktop_info.id.clone()))
.on_cancelled(Message::DragFinished) .on_cancelled(Message::DragFinished)
.on_finished(Message::DragFinished); .on_finished(Message::DragFinished)
} else {
dnd_source(icon_button)
};
if let Some(tracker) = rectangle_tracker { if let Some(tracker) = rectangle_tracker {
tracker.container(*id, icon_button).into() tracker.container(*id, icon_button).into()
} else { } else {
@ -230,6 +233,7 @@ struct CosmicAppList {
rectangle_tracker: Option<RectangleTracker<u32>>, rectangle_tracker: Option<RectangleTracker<u32>>,
rectangles: HashMap<u32, iced::Rectangle>, rectangles: HashMap<u32, iced::Rectangle>,
dnd_offer: Option<DndOffer>, dnd_offer: Option<DndOffer>,
is_listening_for_dnd: bool,
} }
// TODO DnD after sctk merges DnD // TODO DnD after sctk merges DnD
@ -247,13 +251,15 @@ enum Message {
NewSeat(WlSeat), NewSeat(WlSeat),
RemovedSeat(WlSeat), RemovedSeat(WlSeat),
Rectangle(RectangleUpdate<u32>), Rectangle(RectangleUpdate<u32>),
StartDrag(u32), // id of the DockItem StartDrag(String), // id of the DockItem
DragFinished, DragFinished,
DndEnter(f32, f32), DndEnter(f32, f32),
DndExit, DndExit,
DndMotion(f32, f32), DndMotion(f32, f32),
DndDrop, DndDrop,
DndData(PathBuf), DndData(PathBuf),
StartListeningForDnd,
StopListeningForDnd,
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
@ -304,24 +310,6 @@ fn desktop_info_for_app_ids(mut app_ids: Vec<String>) -> Vec<DesktopInfo> {
ret ret
} }
fn split_toplevel_favorites(
toplevel_list: Vec<DockItem>,
existing_favorites: &mut Vec<DockItem>,
) -> Vec<DockItem> {
let mut active_list = Vec::new();
for toplevel in toplevel_list {
if let Some(favorite) = existing_favorites.iter_mut().find(|f| {
f.desktop_info.name == toplevel.desktop_info.id
|| f.desktop_info.id == toplevel.desktop_info.id
}) {
favorite.toplevels = toplevel.toplevels;
} else {
active_list.push(toplevel);
}
}
active_list
}
fn index_in_list( fn index_in_list(
mut list_len: usize, mut list_len: usize,
item_size: f32, item_size: f32,
@ -462,7 +450,6 @@ impl Application for CosmicAppList {
.iter() .iter()
.position(|t| t.desktop_info.id == id) .position(|t| t.desktop_info.id == id)
{ {
println!("Removing favorite 2 {}", id);
let entry = self.favorite_list.remove(i); let entry = self.favorite_list.remove(i);
self.rectangles.remove(&entry.id); self.rectangles.remove(&entry.id);
if !entry.toplevels.is_empty() { if !entry.toplevels.is_empty() {
@ -474,6 +461,9 @@ impl Application for CosmicAppList {
} }
} }
Message::Activate(handle) => { Message::Activate(handle) => {
if let Some(p) = self.popup.take() {
return destroy_popup(p.0);
}
if let (Some(tx), Some(seat)) = (self.toplevel_sender.as_ref(), self.seat.as_ref()) if let (Some(tx), Some(seat)) = (self.toplevel_sender.as_ref(), self.seat.as_ref())
{ {
let _ = tx.send(ToplevelRequest::Activate(handle, seat.clone())); let _ = tx.send(ToplevelRequest::Activate(handle, seat.clone()));
@ -501,14 +491,18 @@ impl Application for CosmicAppList {
.active_list .active_list
.iter() .iter()
.find_map(|t| { .find_map(|t| {
if t.id == id { if t.desktop_info.id == id {
Some((false, t.clone())) Some((false, t.clone()))
} else { } else {
None None
} }
}) })
.or_else(|| { .or_else(|| {
if let Some(pos) = self.favorite_list.iter().position(|t| t.id == id) { if let Some(pos) = self
.favorite_list
.iter()
.position(|t| t.desktop_info.id == id)
{
let t = self.favorite_list.remove(pos); let t = self.favorite_list.remove(pos);
let _ = self.config.remove_favorite(t.desktop_info.id.clone()); let _ = self.config.remove_favorite(t.desktop_info.id.clone());
Some((true, t)) Some((true, t))
@ -763,11 +757,21 @@ impl Application for CosmicAppList {
return destroy_popup(p.0); return destroy_popup(p.0);
} }
} }
Message::StartListeningForDnd => {
self.is_listening_for_dnd = true;
}
Message::StopListeningForDnd => {
self.is_listening_for_dnd = false;
}
} }
Command::none() Command::none()
} }
fn view(&self, id: window::Id) -> Element<Message> { fn view(&self, id: window::Id) -> Element<Message> {
let is_horizontal = match self.applet_helper.anchor {
PanelAnchor::Top | PanelAnchor::Bottom => true,
PanelAnchor::Left | PanelAnchor::Right => false,
};
if let Some((_, item, _)) = self.dnd_source.as_ref().filter(|s| s.0 == id) { if let Some((_, item, _)) = self.dnd_source.as_ref().filter(|s| s.0 == id) {
return cosmic::widget::icon( return cosmic::widget::icon(
Path::new(&item.desktop_info.icon), Path::new(&item.desktop_info.icon),
@ -847,7 +851,7 @@ impl Application for CosmicAppList {
dock_item.as_icon( dock_item.as_icon(
&self.applet_helper, &self.applet_helper,
self.rectangle_tracker.as_ref(), self.rectangle_tracker.as_ref(),
self.popup.is_some(), self.popup.is_none(),
) )
}) })
.collect(); .collect();
@ -858,68 +862,105 @@ impl Application for CosmicAppList {
.and_then(|o| o.dock_item.as_ref().map(|item| (item, o.preview_index))) .and_then(|o| o.dock_item.as_ref().map(|item| (item, o.preview_index)))
{ {
favorites.insert(index, item.as_icon(&self.applet_helper, None, false)); favorites.insert(index, item.as_icon(&self.applet_helper, None, false));
} else if self.is_listening_for_dnd && self.favorite_list.is_empty() {
// show star indicating favorite_list is drag target
favorites.push(
container(cosmic::widget::icon(
"starred-symbolic.symbolic",
self.applet_helper.suggested_size().0,
))
.padding(8)
.into(),
);
} }
let active = self
let active: Vec<_> = self
.active_list .active_list
.iter() .iter()
.map(|dock_item| { .map(|dock_item| {
dock_item.as_icon( dock_item.as_icon(
&self.applet_helper, &self.applet_helper,
self.rectangle_tracker.as_ref(), self.rectangle_tracker.as_ref(),
self.popup.is_some(), self.popup.is_none(),
) )
}) })
.collect(); .collect();
let (w, h) = match self.applet_helper.anchor { let (w, h, favorites, active, divider) = if is_horizontal {
PanelAnchor::Top | PanelAnchor::Bottom => (Length::Shrink, Length::Fill), (
PanelAnchor::Left | PanelAnchor::Right => (Length::Fill, Length::Shrink), Length::Fill,
Length::Shrink,
dnd_listener(row(favorites)),
row(active).into(),
vertical_rule(1).into(),
)
} else {
(
Length::Shrink,
Length::Fill,
dnd_listener(column(favorites)),
column(active).into(),
divider::horizontal::light().into(),
)
}; };
let favorites = match self.applet_helper.anchor { let favorites = favorites
PanelAnchor::Left | PanelAnchor::Right => dnd_listener(column(favorites)), .on_enter(|_actions, mime_types, location| {
PanelAnchor::Top | PanelAnchor::Bottom => dnd_listener(row(favorites)), if self.is_listening_for_dnd || mime_types.iter().any(|m| m == MIME_TYPE) {
} Message::DndEnter(location.0, location.1)
.on_enter(|_actions, mime_types, location| {
if mime_types.iter().any(|m| m == MIME_TYPE) {
Message::DndEnter(location.0, location.1)
} else {
Message::Ignore
}
})
.on_motion(if self.dnd_offer.is_some() {
|x, y| Message::DndMotion(x, y)
} else {
|_, _| Message::Ignore
})
.on_exit(Message::DndExit)
.on_drop(Message::DndDrop)
.on_data(|mime_type, data| {
if mime_type == MIME_TYPE {
if let Some(p) = String::from_utf8(data)
.ok()
.and_then(|s| Url::from_str(&s).ok())
.and_then(|u| u.to_file_path().ok())
{
Message::DndData(p)
} else { } else {
Message::Ignore Message::Ignore
} }
})
.on_motion(if self.dnd_offer.is_some() {
|x, y| Message::DndMotion(x, y)
} else { } else {
Message::Ignore |_, _| Message::Ignore
} })
}); .on_exit(Message::DndExit)
.on_drop(Message::DndDrop)
.on_data(|mime_type, data| {
if mime_type == MIME_TYPE {
if let Some(p) = String::from_utf8(data)
.ok()
.and_then(|s| Url::from_str(&s).ok())
.and_then(|u| u.to_file_path().ok())
{
Message::DndData(p)
} else {
Message::Ignore
}
} else {
Message::Ignore
}
});
let show_favorites =
!self.favorite_list.is_empty() || self.dnd_offer.is_some() || self.is_listening_for_dnd;
let content_list: Vec<Element<_>> = if show_favorites && !self.active_list.is_empty() {
vec![favorites.into(), divider, active]
} else if show_favorites {
vec![favorites.into()]
} else if !self.active_list.is_empty() {
vec![active]
} else {
vec![cosmic::widget::icon(
"com.system76.CosmicAppList",
self.applet_helper.suggested_size().0,
)
.into()]
};
let content = match &self.applet_helper.anchor { let content = match &self.applet_helper.anchor {
PanelAnchor::Left | PanelAnchor::Right => container( PanelAnchor::Left | PanelAnchor::Right => container(
column![favorites, divider::horizontal::light(), column(active)] Column::with_children(content_list)
.spacing(4) .spacing(4)
.align_items(Alignment::Center) .align_items(Alignment::Center)
.height(h) .height(h)
.width(w), .width(w),
), ),
PanelAnchor::Top | PanelAnchor::Bottom => container( PanelAnchor::Top | PanelAnchor::Bottom => container(
row![favorites, vertical_rule(1), row(active)] Row::with_children(content_list)
.spacing(4) .spacing(4)
.align_items(Alignment::Center) .align_items(Alignment::Center)
.height(h) .height(h)
@ -928,7 +969,7 @@ impl Application for CosmicAppList {
}; };
if self.popup.is_some() { if self.popup.is_some() {
mouse_listener(content) mouse_listener(content)
.on_right_press(Message::ClosePopup) .on_right_release(Message::ClosePopup)
.on_press(Message::ClosePopup) .on_press(Message::ClosePopup)
.into() .into()
} else { } else {
@ -962,6 +1003,30 @@ impl Application for CosmicAppList {
), ),
), ),
) => Some(Message::DragFinished), ) => Some(Message::DragFinished),
cosmic::iced_native::Event::PlatformSpecific(
cosmic::iced_native::event::PlatformSpecific::Wayland(
cosmic::iced_native::event::wayland::Event::DndOffer(
cosmic::iced_native::event::wayland::DndOfferEvent::Enter {
mime_types,
..
},
),
),
) => {
if mime_types.iter().any(|m| m == MIME_TYPE) {
Some(Message::StartListeningForDnd)
} else {
None
}
}
cosmic::iced_native::Event::PlatformSpecific(
cosmic::iced_native::event::PlatformSpecific::Wayland(
cosmic::iced_native::event::wayland::Event::DndOffer(
cosmic::iced_native::event::wayland::DndOfferEvent::Leave
| cosmic::iced_native::event::wayland::DndOfferEvent::DropPerformed,
),
),
) => Some(Message::StopListeningForDnd),
_ => None, _ => None,
}), }),
rectangle_tracker_subscription(0).map(|(_, update)| Message::Rectangle(update)), rectangle_tracker_subscription(0).map(|(_, update)| Message::Rectangle(update)),