diff --git a/Cargo.lock b/Cargo.lock index 22da49cd..1fd9c035 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -142,9 +142,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" dependencies = [ "async-lock", "async-task", @@ -180,7 +180,7 @@ dependencies = [ "log", "parking", "polling", - "rustix 0.37.7", + "rustix 0.37.9", "slab", "socket2", "waker-fn", @@ -225,9 +225,9 @@ dependencies = [ [[package]] name = "atomic-waker" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" [[package]] name = "atomicwrites" @@ -307,9 +307,9 @@ dependencies = [ [[package]] name = "blocking" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" dependencies = [ "async-channel", "async-lock", @@ -317,6 +317,7 @@ dependencies = [ "atomic-waker", "fastrand", "futures-lite", + "log", ] [[package]] @@ -490,9 +491,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "concurrent-queue" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" dependencies = [ "crossbeam-utils", ] @@ -569,6 +570,7 @@ dependencies = [ "serde", "shlex", "tokio", + "url", "xdg", ] @@ -723,7 +725,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic#e5d263b23f1965e842b098891603decafc18a1e3" +source = "git+https://github.com/pop-os/libcosmic#93ec06a34dde0122f474e7adaec885b5cb8dab5e" dependencies = [ "atomicwrites", "dirs 4.0.0", @@ -1519,6 +1521,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fraction" version = "0.13.1" @@ -1615,9 +1626,9 @@ checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand", "futures-core", @@ -1700,9 +1711,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "js-sys", @@ -2082,7 +2093,7 @@ dependencies = [ [[package]] name = "iced" version = "0.6.0" -source = "git+https://github.com/pop-os/libcosmic/?branch=master#e5d263b23f1965e842b098891603decafc18a1e3" +source = "git+https://github.com/pop-os/libcosmic/?branch=master#93ec06a34dde0122f474e7adaec885b5cb8dab5e" dependencies = [ "iced_core", "iced_dyrend", @@ -2100,7 +2111,7 @@ dependencies = [ [[package]] name = "iced_core" version = "0.6.2" -source = "git+https://github.com/pop-os/libcosmic/?branch=master#e5d263b23f1965e842b098891603decafc18a1e3" +source = "git+https://github.com/pop-os/libcosmic/?branch=master#93ec06a34dde0122f474e7adaec885b5cb8dab5e" dependencies = [ "bitflags", "palette", @@ -2110,7 +2121,7 @@ dependencies = [ [[package]] name = "iced_dyrend" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/?branch=master#e5d263b23f1965e842b098891603decafc18a1e3" +source = "git+https://github.com/pop-os/libcosmic/?branch=master#93ec06a34dde0122f474e7adaec885b5cb8dab5e" dependencies = [ "iced_glow", "iced_graphics", @@ -2124,7 +2135,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.5.1" -source = "git+https://github.com/pop-os/libcosmic/?branch=master#e5d263b23f1965e842b098891603decafc18a1e3" +source = "git+https://github.com/pop-os/libcosmic/?branch=master#93ec06a34dde0122f474e7adaec885b5cb8dab5e" dependencies = [ "futures", "log", @@ -2136,7 +2147,7 @@ dependencies = [ [[package]] name = "iced_glow" version = "0.5.1" -source = "git+https://github.com/pop-os/libcosmic/?branch=master#e5d263b23f1965e842b098891603decafc18a1e3" +source = "git+https://github.com/pop-os/libcosmic/?branch=master#93ec06a34dde0122f474e7adaec885b5cb8dab5e" dependencies = [ "bytemuck", "euclid", @@ -2151,7 +2162,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.5.0" -source = "git+https://github.com/pop-os/libcosmic/?branch=master#e5d263b23f1965e842b098891603decafc18a1e3" +source = "git+https://github.com/pop-os/libcosmic/?branch=master#93ec06a34dde0122f474e7adaec885b5cb8dab5e" dependencies = [ "bitflags", "bytemuck", @@ -2171,7 +2182,7 @@ dependencies = [ [[package]] name = "iced_lazy" version = "0.3.0" -source = "git+https://github.com/pop-os/libcosmic/?branch=master#e5d263b23f1965e842b098891603decafc18a1e3" +source = "git+https://github.com/pop-os/libcosmic/?branch=master#93ec06a34dde0122f474e7adaec885b5cb8dab5e" dependencies = [ "iced_native", "ouroboros 0.13.0", @@ -2180,7 +2191,7 @@ dependencies = [ [[package]] name = "iced_native" version = "0.7.0" -source = "git+https://github.com/pop-os/libcosmic/?branch=master#e5d263b23f1965e842b098891603decafc18a1e3" +source = "git+https://github.com/pop-os/libcosmic/?branch=master#93ec06a34dde0122f474e7adaec885b5cb8dab5e" dependencies = [ "iced_core", "iced_futures", @@ -2194,7 +2205,7 @@ dependencies = [ [[package]] name = "iced_sctk" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/?branch=master#e5d263b23f1965e842b098891603decafc18a1e3" +source = "git+https://github.com/pop-os/libcosmic/?branch=master#93ec06a34dde0122f474e7adaec885b5cb8dab5e" dependencies = [ "enum-repr", "float-cmp", @@ -2213,7 +2224,7 @@ dependencies = [ [[package]] name = "iced_softbuffer" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/?branch=master#e5d263b23f1965e842b098891603decafc18a1e3" +source = "git+https://github.com/pop-os/libcosmic/?branch=master#93ec06a34dde0122f474e7adaec885b5cb8dab5e" dependencies = [ "cosmic-text", "iced_graphics", @@ -2228,7 +2239,7 @@ dependencies = [ [[package]] name = "iced_style" version = "0.5.1" -source = "git+https://github.com/pop-os/libcosmic/?branch=master#e5d263b23f1965e842b098891603decafc18a1e3" +source = "git+https://github.com/pop-os/libcosmic/?branch=master#93ec06a34dde0122f474e7adaec885b5cb8dab5e" dependencies = [ "iced_core", "once_cell", @@ -2238,7 +2249,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.7.0" -source = "git+https://github.com/pop-os/libcosmic/?branch=master#e5d263b23f1965e842b098891603decafc18a1e3" +source = "git+https://github.com/pop-os/libcosmic/?branch=master#93ec06a34dde0122f474e7adaec885b5cb8dab5e" dependencies = [ "bitflags", "bytemuck", @@ -2272,6 +2283,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "image" version = "0.24.6" @@ -2469,7 +2490,7 @@ checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic/?branch=master#e5d263b23f1965e842b098891603decafc18a1e3" +source = "git+https://github.com/pop-os/libcosmic/?branch=master#93ec06a34dde0122f474e7adaec885b5cb8dab5e" dependencies = [ "apply", "cosmic-panel-config", @@ -3114,9 +3135,9 @@ dependencies = [ [[package]] name = "parking" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" [[package]] name = "parking_lot" @@ -3166,6 +3187,12 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + [[package]] name = "phf" version = "0.11.1" @@ -3266,9 +3293,9 @@ dependencies = [ [[package]] name = "polling" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e1f879b2998099c2d69ab9605d145d5b661195627eccc680002c4918a7fb6fa" +checksum = "4be1c66a6add46bff50935c313dae30a5030cf8385c5206e8a95e9e9def974aa" dependencies = [ "autocfg", "bitflags", @@ -3277,7 +3304,7 @@ dependencies = [ "libc", "log", "pin-project-lite", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -3642,16 +3669,16 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.7" +version = "0.37.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aae838e49b3d63e9274e1c01833cc8139d3fec468c3b84688c628f44b1ae11d" +checksum = "d3eb76a3b09109e78c52d45979fea3cd8ddaadb223531d0846bedb60e72c3e99" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys 0.3.1", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -4143,7 +4170,7 @@ dependencies = [ "cfg-if", "fastrand", "redox_syscall 0.3.5", - "rustix 0.37.7", + "rustix 0.37.9", "windows-sys 0.45.0", ] @@ -4237,6 +4264,21 @@ dependencies = [ "displaydoc", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.27.0" @@ -4473,6 +4515,15 @@ dependencies = [ "regex", ] +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-script" version = "0.5.5" @@ -4503,6 +4554,17 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "usvg" version = "0.18.0" diff --git a/cosmic-app-list/Cargo.toml b/cosmic-app-list/Cargo.toml index 1ba05555..e37c0090 100644 --- a/cosmic-app-list/Cargo.toml +++ b/cosmic-app-list/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" [dependencies] cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit" } cosmic-protocols = { git = "https://github.com/pop-os/cosmic-protocols", default-features = false, features = ["client"] } -# libcosmic = { git = "https://github.com/pop-os/libcosmic/", branch = "master", default-features = false, features = ["wayland", "applet", "tokio"] } -libcosmic = { path = "../../libcosmic", default-features = false, features = ["wayland", "applet", "tokio"] } +libcosmic = { git = "https://github.com/pop-os/libcosmic/", branch = "master", default-features = false, features = ["wayland", "applet", "tokio"] } +# libcosmic = { path = "../../libcosmic", default-features = false, features = ["wayland", "applet", "tokio"] } ron = "0.8" futures = "0.3" futures-util = "0.3" diff --git a/cosmic-app-list/src/app.rs b/cosmic-app-list/src/app.rs old mode 100644 new mode 100755 index 055eab0a..1db93d31 --- a/cosmic-app-list/src/app.rs +++ b/cosmic-app-list/src/app.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; +use std::str::FromStr; use crate::config; use crate::config::AppListConfig; @@ -28,9 +29,14 @@ use cosmic::iced::{window, Application, Command, Subscription}; use cosmic::iced_native::alignment::Horizontal; use cosmic::iced_native::subscription::events_with; use cosmic::iced_native::widget::vertical_space; +use cosmic::iced_sctk::commands::data_device::accept_mime_type; +use cosmic::iced_sctk::commands::data_device::finish_dnd; +use cosmic::iced_sctk::commands::data_device::request_dnd_data; +use cosmic::iced_sctk::commands::data_device::set_actions; use cosmic::iced_sctk::commands::data_device::start_drag; use cosmic::iced_sctk::layout::Limits; use cosmic::iced_sctk::settings::InitialSurface; +use cosmic::iced_sctk::widget::dnd_listener; use cosmic::iced_sctk::widget::vertical_rule; use cosmic::iced_style::application::{self, Appearance}; use cosmic::iced_style::Color; @@ -82,7 +88,7 @@ pub fn run() -> cosmic::iced::Result { #[derive(Debug, Clone, Default)] struct DockItem { - id: usize, + id: u32, toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo)>, desktop_info: DesktopInfo, } @@ -105,13 +111,13 @@ impl DataFromMimeType for DockItem { impl DockItem { fn new( - id: usize, - toplevel: (ZcosmicToplevelHandleV1, ToplevelInfo), + id: u32, + toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo)>, desktop_info: DesktopInfo, ) -> Self { Self { id, - toplevels: vec![toplevel], + toplevels, desktop_info, } } @@ -119,7 +125,7 @@ impl DockItem { fn as_icon( &self, applet_helper: &CosmicAppletHelper, - rectangle_tracker: Option<&RectangleTracker>, + rectangle_tracker: Option<&RectangleTracker>, has_popup: bool, ) -> Element<'_, Message> { let DockItem { @@ -201,22 +207,29 @@ impl DockItem { } } +#[derive(Debug, Clone, Default)] +struct DndOffer { + dock_item: Option, + preview_index: usize, +} + #[derive(Clone, Default)] struct CosmicAppList { theme: Theme, popup: Option<(window::Id, DockItem)>, surface_id_ctr: u32, subscription_ctr: u32, + item_ctr: u32, active_list: Vec, favorite_list: Vec, dnd_source: Option<(window::Id, DockItem, DndAction)>, - dnd_preview: Option<(bool, DockItem)>, // TODO allow non-toplevels to be dragged config: AppListConfig, toplevel_sender: Option>, applet_helper: CosmicAppletHelper, seat: Option, - rectangle_tracker: Option>, - rectangles: HashMap, + rectangle_tracker: Option>, + rectangles: HashMap, + dnd_offer: Option, } // TODO DnD after sctk merges DnD @@ -233,9 +246,14 @@ enum Message { Ignore, NewSeat(WlSeat), RemovedSeat(WlSeat), - Rectangle(RectangleUpdate), - StartDrag(usize), // id of the DockItem - DragFinished + Rectangle(RectangleUpdate), + StartDrag(u32), // id of the DockItem + DragFinished, + DndEnter(f32, f32), + DndExit, + DndMotion(f32, f32), + DndDrop, + DndData(PathBuf), } #[derive(Debug, Clone, Default)] @@ -304,6 +322,49 @@ fn split_toplevel_favorites( active_list } +fn index_in_list( + mut list_len: usize, + item_size: f32, + divider_size: f32, + existing_preview: Option, + pos_in_list: f32, +) -> usize { + if existing_preview.is_some() { + list_len += 1; + } + let total_len = list_len as f32 * (item_size + divider_size) - divider_size; + let pos_in_list = pos_in_list * total_len as f32; + let index = if list_len == 0 { + 0 + } else { + if pos_in_list < item_size / 2.0 { + 0 + } else { + let mut i = 1; + let mut pos = item_size / 2.0; + while i < list_len { + let next_pos = pos + item_size + divider_size; + if pos > pos_in_list && pos_in_list < next_pos { + break; + } + pos = next_pos; + i += 1; + } + i + } + }; + + if let Some(existing_preview) = existing_preview { + if index >= existing_preview { + index.checked_sub(1).unwrap_or_default() + } else { + index + } + } else { + index + } +} + impl Application for CosmicAppList { type Message = Message; type Theme = Theme; @@ -312,14 +373,17 @@ impl Application for CosmicAppList { fn new(_flags: ()) -> (Self, Command) { let config = config::AppListConfig::load().unwrap_or_default(); + let mut favorite_ctr = 0; let self_ = CosmicAppList { favorite_list: desktop_info_for_app_ids(config.favorites.clone()) .into_iter() - .enumerate() - .map(|(i, e)| DockItem { - id: i, - toplevels: Default::default(), - desktop_info: e, + .map(|e| { + favorite_ctr += 1; + DockItem { + id: favorite_ctr, + toplevels: Default::default(), + desktop_info: e, + } }) .collect(), config, @@ -377,7 +441,19 @@ impl Application for CosmicAppList { } } Message::Favorite(id) => { + if let Some(i) = self + .active_list + .iter() + .position(|t| t.desktop_info.id == id || t.desktop_info.name == id) + { + let entry = self.active_list.remove(i); + self.favorite_list.push(entry); + } + let _ = self.config.add_favorite(id); + if let Some((popup_id, _toplevel)) = self.popup.take() { + return destroy_popup(popup_id); + } } Message::UnFavorite(id) => { let _ = self.config.remove_favorite(id.clone()); @@ -386,12 +462,16 @@ impl Application for CosmicAppList { .iter() .position(|t| t.desktop_info.id == id) { + println!("Removing favorite 2 {}", id); let entry = self.favorite_list.remove(i); self.rectangles.remove(&entry.id); if !entry.toplevels.is_empty() { self.active_list.push(entry); } } + if let Some((popup_id, _toplevel)) = self.popup.take() { + return destroy_popup(popup_id); + } } Message::Activate(handle) => { if let (Some(tx), Some(seat)) = (self.toplevel_sender.as_ref(), self.seat.as_ref()) @@ -412,20 +492,41 @@ impl Application for CosmicAppList { } } } + if let Some((popup_id, _toplevel)) = self.popup.take() { + return destroy_popup(popup_id); + } } Message::StartDrag(id) => { - if let Some(toplevel_group) = self + if let Some((is_favorite, toplevel_group)) = self .active_list .iter() - .chain(self.favorite_list.iter()) - .find(|t| t.id == id) + .find_map(|t| { + if t.id == id { + Some((false, t.clone())) + } else { + None + } + }) + .or_else(|| { + if let Some(pos) = self.favorite_list.iter().position(|t| t.id == id) { + let t = self.favorite_list.remove(pos); + let _ = self.config.remove_favorite(t.desktop_info.id.clone()); + Some((true, t)) + } else { + None + } + }) { self.surface_id_ctr += 1; let icon_id = window::Id::new(self.surface_id_ctr); self.dnd_source = Some((icon_id, toplevel_group.clone(), DndAction::empty())); return start_drag( vec![MIME_TYPE.to_string()], - DndAction::all(), + if is_favorite { + DndAction::all() + } else { + DndAction::Copy + }, window::Id::new(0), Some(DndIcon::Custom(icon_id)), Box::new(toplevel_group.clone()), @@ -433,7 +534,134 @@ impl Application for CosmicAppList { } } Message::DragFinished => { - self.dnd_source = None; + if let Some((_, mut toplevel_group, _)) = self.dnd_source.take() { + if !self + .favorite_list + .iter() + .chain(self.active_list.iter()) + .any(|t| t.desktop_info.id == toplevel_group.desktop_info.id) + && !toplevel_group.toplevels.is_empty() + { + self.item_ctr += 1; + toplevel_group.id = self.item_ctr; + self.active_list.push(toplevel_group); + } + } + } + Message::DndEnter(x, y) => { + let item_size = self.applet_helper.suggested_size().0; + let pos_in_list = match self.applet_helper.anchor { + PanelAnchor::Top | PanelAnchor::Bottom => x, + PanelAnchor::Left | PanelAnchor::Right => y, + }; + let num_favs = self.favorite_list.len(); + let index = index_in_list(num_favs, item_size as f32, 4.0, None, pos_in_list); + self.dnd_offer = Some(DndOffer { + preview_index: index, + ..DndOffer::default() + }); + let mut cmds = vec![ + accept_mime_type(Some(MIME_TYPE.to_string())), + set_actions( + if self.dnd_source.is_some() { + DndAction::Move + } else { + DndAction::Copy + }, + DndAction::all(), + ), + ]; + if let Some(dnd_source) = self.dnd_source.as_ref() { + self.dnd_offer.as_mut().unwrap().dock_item = Some(dnd_source.1.clone()); + } else { + cmds.push(request_dnd_data(MIME_TYPE.to_string())); + } + return Command::batch(cmds); + } + Message::DndMotion(x, y) => { + if let Some(DndOffer { preview_index, .. }) = self.dnd_offer.as_mut() { + let item_size = self.applet_helper.suggested_size().0; + let pos_in_list = match self.applet_helper.anchor { + PanelAnchor::Top | PanelAnchor::Bottom => x, + PanelAnchor::Left | PanelAnchor::Right => y, + }; + let num_favs = self.favorite_list.len(); + let index = index_in_list( + num_favs, + item_size as f32, + 4.0, + Some(*preview_index), + pos_in_list, + ); + *preview_index = index; + } + } + Message::DndExit => { + self.dnd_offer = None; + return accept_mime_type(None); + } + Message::DndData(file_path) => { + if let Some(DndOffer { dock_item, .. }) = self.dnd_offer.as_mut() { + if let Some(di) = std::fs::read_to_string(&file_path).ok().and_then(|input| { + DesktopEntry::decode(&file_path, &input) + .ok() + .and_then(|de| { + freedesktop_icons::lookup(de.icon().unwrap_or(de.appid)) + .with_size(128) + .with_cache() + .find() + .map(|buf| DesktopInfo { + id: de.id().to_string(), + icon: buf, + exec: de.exec().unwrap_or_default().to_string(), + name: de.name(None).unwrap_or_default().to_string(), + path: file_path.clone(), + }) + }) + }) { + self.item_ctr += 1; + *dock_item = Some(DockItem::new(self.item_ctr, Vec::new(), di)); + } + } + } + Message::DndDrop => { + // we actually should have the data already, if not, we probably shouldn't do + // anything anyway + if let Some((mut dock_item, index)) = self + .dnd_offer + .take() + .and_then(|o| o.dock_item.map(|i| (i, o.preview_index))) + { + self.item_ctr += 1; + let _ = self.config.add_favorite(dock_item.desktop_info.id.clone()); + if let Some((pos, is_favorite)) = self + .active_list + .iter() + .position(|DockItem { desktop_info, .. }| { + desktop_info.id == dock_item.desktop_info.id + }) + .map(|pos| (pos, false)) + .or_else(|| { + self.favorite_list + .iter() + .position(|DockItem { desktop_info, .. }| { + desktop_info.id == dock_item.desktop_info.id + }) + .map(|pos| (pos, true)) + }) + { + let t = if is_favorite { + self.favorite_list.remove(pos) + } else { + self.active_list.remove(pos) + }; + dock_item.toplevels = t.toplevels; + }; + dock_item.id = self.item_ctr; + self.favorite_list + .insert(index.min(self.favorite_list.len()), dock_item); + } + return finish_dnd(); } Message::Toplevel(event) => { match event { @@ -453,8 +681,9 @@ impl Application for CosmicAppList { } else { let desktop_info = desktop_info_for_app_ids(vec![info.app_id.clone()]).remove(0); + self.item_ctr += 1; self.active_list.push(DockItem { - id: self.active_list.len(), + id: self.item_ctr, toplevels: vec![(handle, info)], desktop_info, }); @@ -611,7 +840,7 @@ impl Application for CosmicAppList { return self.applet_helper.popup_container(content).into(); } - let favorites = self + let mut favorites: Vec<_> = self .favorite_list .iter() .map(|dock_item| { @@ -622,6 +851,14 @@ impl Application for CosmicAppList { ) }) .collect(); + + if let Some((item, index)) = self + .dnd_offer + .as_ref() + .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)); + } let active = self .active_list .iter() @@ -639,20 +876,50 @@ impl Application for CosmicAppList { PanelAnchor::Left | PanelAnchor::Right => (Length::Fill, Length::Shrink), }; + let favorites = match self.applet_helper.anchor { + PanelAnchor::Left | PanelAnchor::Right => dnd_listener(column(favorites)), + PanelAnchor::Top | PanelAnchor::Bottom => dnd_listener(row(favorites)), + } + .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 { + Message::Ignore + } + } else { + Message::Ignore + } + }); + let content = match &self.applet_helper.anchor { PanelAnchor::Left | PanelAnchor::Right => container( - column![ - column(favorites), - divider::horizontal::light(), - column(active) - ] - .spacing(4) - .align_items(Alignment::Center) - .height(h) - .width(w), + column![favorites, divider::horizontal::light(), column(active)] + .spacing(4) + .align_items(Alignment::Center) + .height(h) + .width(w), ), PanelAnchor::Top | PanelAnchor::Bottom => container( - row![row(favorites), vertical_rule(1), row(active)] + row![favorites, vertical_rule(1), row(active)] .spacing(4) .align_items(Alignment::Center) .height(h) @@ -685,6 +952,16 @@ impl Application for CosmicAppList { Some(Message::RemovedSeat(seat)) } }, + // XXX Must be done to catch a finished drag after the source is removed + // (for now, the source is removed when the drag starts) + cosmic::iced_native::Event::PlatformSpecific( + cosmic::iced_native::event::PlatformSpecific::Wayland( + cosmic::iced_sctk::event::wayland::Event::DataSource( + cosmic::iced_sctk::event::wayland::DataSourceEvent::DndFinished + | cosmic::iced_sctk::event::wayland::DataSourceEvent::Cancelled, + ), + ), + ) => Some(Message::DragFinished), _ => None, }), rectangle_tracker_subscription(0).map(|(_, update)| Message::Rectangle(update)),