Skip to content

Commit 1391388

Browse files
committed
feat: polish app list dnd
1 parent 8a040dd commit 1391388

File tree

1 file changed

+145
-80
lines changed

1 file changed

+145
-80
lines changed

cosmic-app-list/src/app.rs

Lines changed: 145 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ use cosmic::iced::wayland::actions::data_device::DndIcon;
2121
use cosmic::iced::wayland::actions::window::SctkWindowSettings;
2222
use cosmic::iced::wayland::popup::destroy_popup;
2323
use cosmic::iced::wayland::popup::get_popup;
24-
use cosmic::iced::widget::dnd_source;
25-
use cosmic::iced::widget::mouse_listener;
26-
use cosmic::iced::widget::{column, row};
24+
use cosmic::iced::widget::{column, dnd_source, mouse_listener, row, text, Column, Row};
2725
use cosmic::iced::Settings;
2826
use cosmic::iced::{window, Application, Command, Subscription};
2927
use cosmic::iced_native::alignment::Horizontal;
@@ -126,7 +124,7 @@ impl DockItem {
126124
&self,
127125
applet_helper: &CosmicAppletHelper,
128126
rectangle_tracker: Option<&RectangleTracker<u32>>,
129-
has_popup: bool,
127+
interaction_enabled: bool,
130128
) -> Element<'_, Message> {
131129
let DockItem {
132130
toplevels,
@@ -182,23 +180,28 @@ impl DockItem {
182180
let mut icon_button = cosmic::widget::button(Button::Text)
183181
.custom(vec![icon_wrapper])
184182
.padding(8);
185-
if !has_popup {
186-
icon_button = icon_button.on_press(
187-
toplevels
188-
.first()
189-
.map(|t| Message::Activate(t.0.clone()))
190-
.unwrap_or_else(|| Message::Exec(desktop_info.exec.clone())),
191-
);
192-
}
193-
194-
// TODO tooltip on hover
195-
let icon_button = dnd_source(
196-
mouse_listener(icon_button.width(Length::Shrink).height(Length::Shrink))
183+
let icon_button = if interaction_enabled {
184+
dnd_source(
185+
mouse_listener(
186+
icon_button
187+
.on_press(
188+
toplevels
189+
.first()
190+
.map(|t| Message::Activate(t.0.clone()))
191+
.unwrap_or_else(|| Message::Exec(desktop_info.exec.clone())),
192+
)
193+
.width(Length::Shrink)
194+
.height(Length::Shrink),
195+
)
197196
.on_right_release(Message::Popup(desktop_info.id.clone())),
198-
)
199-
.on_drag(Message::StartDrag(*id))
200-
.on_cancelled(Message::DragFinished)
201-
.on_finished(Message::DragFinished);
197+
)
198+
.on_drag(Message::StartDrag(desktop_info.id.clone()))
199+
.on_cancelled(Message::DragFinished)
200+
.on_finished(Message::DragFinished)
201+
} else {
202+
dnd_source(icon_button)
203+
};
204+
202205
if let Some(tracker) = rectangle_tracker {
203206
tracker.container(*id, icon_button).into()
204207
} else {
@@ -230,6 +233,7 @@ struct CosmicAppList {
230233
rectangle_tracker: Option<RectangleTracker<u32>>,
231234
rectangles: HashMap<u32, iced::Rectangle>,
232235
dnd_offer: Option<DndOffer>,
236+
is_listening_for_dnd: bool,
233237
}
234238

235239
// TODO DnD after sctk merges DnD
@@ -247,13 +251,15 @@ enum Message {
247251
NewSeat(WlSeat),
248252
RemovedSeat(WlSeat),
249253
Rectangle(RectangleUpdate<u32>),
250-
StartDrag(u32), // id of the DockItem
254+
StartDrag(String), // id of the DockItem
251255
DragFinished,
252256
DndEnter(f32, f32),
253257
DndExit,
254258
DndMotion(f32, f32),
255259
DndDrop,
256260
DndData(PathBuf),
261+
StartListeningForDnd,
262+
StopListeningForDnd,
257263
}
258264

259265
#[derive(Debug, Clone, Default)]
@@ -304,24 +310,6 @@ fn desktop_info_for_app_ids(mut app_ids: Vec<String>) -> Vec<DesktopInfo> {
304310
ret
305311
}
306312

307-
fn split_toplevel_favorites(
308-
toplevel_list: Vec<DockItem>,
309-
existing_favorites: &mut Vec<DockItem>,
310-
) -> Vec<DockItem> {
311-
let mut active_list = Vec::new();
312-
for toplevel in toplevel_list {
313-
if let Some(favorite) = existing_favorites.iter_mut().find(|f| {
314-
f.desktop_info.name == toplevel.desktop_info.id
315-
|| f.desktop_info.id == toplevel.desktop_info.id
316-
}) {
317-
favorite.toplevels = toplevel.toplevels;
318-
} else {
319-
active_list.push(toplevel);
320-
}
321-
}
322-
active_list
323-
}
324-
325313
fn index_in_list(
326314
mut list_len: usize,
327315
item_size: f32,
@@ -462,7 +450,6 @@ impl Application for CosmicAppList {
462450
.iter()
463451
.position(|t| t.desktop_info.id == id)
464452
{
465-
println!("Removing favorite 2 {}", id);
466453
let entry = self.favorite_list.remove(i);
467454
self.rectangles.remove(&entry.id);
468455
if !entry.toplevels.is_empty() {
@@ -474,6 +461,9 @@ impl Application for CosmicAppList {
474461
}
475462
}
476463
Message::Activate(handle) => {
464+
if let Some(p) = self.popup.take() {
465+
return destroy_popup(p.0);
466+
}
477467
if let (Some(tx), Some(seat)) = (self.toplevel_sender.as_ref(), self.seat.as_ref())
478468
{
479469
let _ = tx.send(ToplevelRequest::Activate(handle, seat.clone()));
@@ -501,14 +491,18 @@ impl Application for CosmicAppList {
501491
.active_list
502492
.iter()
503493
.find_map(|t| {
504-
if t.id == id {
494+
if t.desktop_info.id == id {
505495
Some((false, t.clone()))
506496
} else {
507497
None
508498
}
509499
})
510500
.or_else(|| {
511-
if let Some(pos) = self.favorite_list.iter().position(|t| t.id == id) {
501+
if let Some(pos) = self
502+
.favorite_list
503+
.iter()
504+
.position(|t| t.desktop_info.id == id)
505+
{
512506
let t = self.favorite_list.remove(pos);
513507
let _ = self.config.remove_favorite(t.desktop_info.id.clone());
514508
Some((true, t))
@@ -763,11 +757,21 @@ impl Application for CosmicAppList {
763757
return destroy_popup(p.0);
764758
}
765759
}
760+
Message::StartListeningForDnd => {
761+
self.is_listening_for_dnd = true;
762+
}
763+
Message::StopListeningForDnd => {
764+
self.is_listening_for_dnd = false;
765+
}
766766
}
767767
Command::none()
768768
}
769769

770770
fn view(&self, id: window::Id) -> Element<Message> {
771+
let is_horizontal = match self.applet_helper.anchor {
772+
PanelAnchor::Top | PanelAnchor::Bottom => true,
773+
PanelAnchor::Left | PanelAnchor::Right => false,
774+
};
771775
if let Some((_, item, _)) = self.dnd_source.as_ref().filter(|s| s.0 == id) {
772776
return cosmic::widget::icon(
773777
Path::new(&item.desktop_info.icon),
@@ -847,7 +851,7 @@ impl Application for CosmicAppList {
847851
dock_item.as_icon(
848852
&self.applet_helper,
849853
self.rectangle_tracker.as_ref(),
850-
self.popup.is_some(),
854+
self.popup.is_none(),
851855
)
852856
})
853857
.collect();
@@ -858,68 +862,105 @@ impl Application for CosmicAppList {
858862
.and_then(|o| o.dock_item.as_ref().map(|item| (item, o.preview_index)))
859863
{
860864
favorites.insert(index, item.as_icon(&self.applet_helper, None, false));
865+
} else if self.is_listening_for_dnd && self.favorite_list.is_empty() {
866+
// show star indicating favorite_list is drag target
867+
favorites.push(
868+
container(cosmic::widget::icon(
869+
"starred-symbolic.symbolic",
870+
self.applet_helper.suggested_size().0,
871+
))
872+
.padding(8)
873+
.into(),
874+
);
861875
}
862-
let active = self
876+
877+
let active: Vec<_> = self
863878
.active_list
864879
.iter()
865880
.map(|dock_item| {
866881
dock_item.as_icon(
867882
&self.applet_helper,
868883
self.rectangle_tracker.as_ref(),
869-
self.popup.is_some(),
884+
self.popup.is_none(),
870885
)
871886
})
872887
.collect();
873888

874-
let (w, h) = match self.applet_helper.anchor {
875-
PanelAnchor::Top | PanelAnchor::Bottom => (Length::Shrink, Length::Fill),
876-
PanelAnchor::Left | PanelAnchor::Right => (Length::Fill, Length::Shrink),
889+
let (w, h, favorites, active, divider) = if is_horizontal {
890+
(
891+
Length::Fill,
892+
Length::Shrink,
893+
dnd_listener(row(favorites)),
894+
row(active).into(),
895+
vertical_rule(1).into(),
896+
)
897+
} else {
898+
(
899+
Length::Shrink,
900+
Length::Fill,
901+
dnd_listener(column(favorites)),
902+
column(active).into(),
903+
divider::horizontal::light().into(),
904+
)
877905
};
878906

879-
let favorites = match self.applet_helper.anchor {
880-
PanelAnchor::Left | PanelAnchor::Right => dnd_listener(column(favorites)),
881-
PanelAnchor::Top | PanelAnchor::Bottom => dnd_listener(row(favorites)),
882-
}
883-
.on_enter(|_actions, mime_types, location| {
884-
if mime_types.iter().any(|m| m == MIME_TYPE) {
885-
Message::DndEnter(location.0, location.1)
886-
} else {
887-
Message::Ignore
888-
}
889-
})
890-
.on_motion(if self.dnd_offer.is_some() {
891-
|x, y| Message::DndMotion(x, y)
892-
} else {
893-
|_, _| Message::Ignore
894-
})
895-
.on_exit(Message::DndExit)
896-
.on_drop(Message::DndDrop)
897-
.on_data(|mime_type, data| {
898-
if mime_type == MIME_TYPE {
899-
if let Some(p) = String::from_utf8(data)
900-
.ok()
901-
.and_then(|s| Url::from_str(&s).ok())
902-
.and_then(|u| u.to_file_path().ok())
903-
{
904-
Message::DndData(p)
907+
let favorites = favorites
908+
.on_enter(|_actions, mime_types, location| {
909+
if self.is_listening_for_dnd || mime_types.iter().any(|m| m == MIME_TYPE) {
910+
Message::DndEnter(location.0, location.1)
905911
} else {
906912
Message::Ignore
907913
}
914+
})
915+
.on_motion(if self.dnd_offer.is_some() {
916+
|x, y| Message::DndMotion(x, y)
908917
} else {
909-
Message::Ignore
910-
}
911-
});
918+
|_, _| Message::Ignore
919+
})
920+
.on_exit(Message::DndExit)
921+
.on_drop(Message::DndDrop)
922+
.on_data(|mime_type, data| {
923+
if mime_type == MIME_TYPE {
924+
if let Some(p) = String::from_utf8(data)
925+
.ok()
926+
.and_then(|s| Url::from_str(&s).ok())
927+
.and_then(|u| u.to_file_path().ok())
928+
{
929+
Message::DndData(p)
930+
} else {
931+
Message::Ignore
932+
}
933+
} else {
934+
Message::Ignore
935+
}
936+
});
937+
938+
let show_favorites =
939+
!self.favorite_list.is_empty() || self.dnd_offer.is_some() || self.is_listening_for_dnd;
940+
let content_list: Vec<Element<_>> = if show_favorites && !self.active_list.is_empty() {
941+
vec![favorites.into(), divider, active]
942+
} else if show_favorites {
943+
vec![favorites.into()]
944+
} else if !self.active_list.is_empty() {
945+
vec![active]
946+
} else {
947+
vec![cosmic::widget::icon(
948+
"com.system76.CosmicAppList",
949+
self.applet_helper.suggested_size().0,
950+
)
951+
.into()]
952+
};
912953

913954
let content = match &self.applet_helper.anchor {
914955
PanelAnchor::Left | PanelAnchor::Right => container(
915-
column![favorites, divider::horizontal::light(), column(active)]
956+
Column::with_children(content_list)
916957
.spacing(4)
917958
.align_items(Alignment::Center)
918959
.height(h)
919960
.width(w),
920961
),
921962
PanelAnchor::Top | PanelAnchor::Bottom => container(
922-
row![favorites, vertical_rule(1), row(active)]
963+
Row::with_children(content_list)
923964
.spacing(4)
924965
.align_items(Alignment::Center)
925966
.height(h)
@@ -928,7 +969,7 @@ impl Application for CosmicAppList {
928969
};
929970
if self.popup.is_some() {
930971
mouse_listener(content)
931-
.on_right_press(Message::ClosePopup)
972+
.on_right_release(Message::ClosePopup)
932973
.on_press(Message::ClosePopup)
933974
.into()
934975
} else {
@@ -962,6 +1003,30 @@ impl Application for CosmicAppList {
9621003
),
9631004
),
9641005
) => Some(Message::DragFinished),
1006+
cosmic::iced_native::Event::PlatformSpecific(
1007+
cosmic::iced_native::event::PlatformSpecific::Wayland(
1008+
cosmic::iced_native::event::wayland::Event::DndOffer(
1009+
cosmic::iced_native::event::wayland::DndOfferEvent::Enter {
1010+
mime_types,
1011+
..
1012+
},
1013+
),
1014+
),
1015+
) => {
1016+
if mime_types.iter().any(|m| m == MIME_TYPE) {
1017+
Some(Message::StartListeningForDnd)
1018+
} else {
1019+
None
1020+
}
1021+
}
1022+
cosmic::iced_native::Event::PlatformSpecific(
1023+
cosmic::iced_native::event::PlatformSpecific::Wayland(
1024+
cosmic::iced_native::event::wayland::Event::DndOffer(
1025+
cosmic::iced_native::event::wayland::DndOfferEvent::Leave
1026+
| cosmic::iced_native::event::wayland::DndOfferEvent::DropPerformed,
1027+
),
1028+
),
1029+
) => Some(Message::StopListeningForDnd),
9651030
_ => None,
9661031
}),
9671032
rectangle_tracker_subscription(0).map(|(_, update)| Message::Rectangle(update)),

0 commit comments

Comments
 (0)