From 926a16ce2e8d9910cffef5dc4521b7796ddc0524 Mon Sep 17 00:00:00 2001 From: Ashley Wulber <48420062+wash2@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:41:25 -0400 Subject: [PATCH] Dnd (#103) * wip: drag offers * wip: dnd * wip: dnd * feat: hover indicators * feat: change directory on hover * fix: dnd drop filtering and drop kind * fix: mouse area selection rectangle * fix: better drag rectangle and dnd drag interaction * feat: nav and tab dnd * cleanup: remove extra patch * cleanup: delete leftover dnd widgets * chore: update libcosmic * fix: list view spacer height overflow --- Cargo.lock | 421 +++++++++++++++----------- Cargo.toml | 15 + src/app.rs | 167 ++++++++++- src/clipboard.rs | 1 + src/dialog.rs | 6 + src/mouse_area.rs | 134 ++++++++- src/tab.rs | 736 +++++++++++++++++++++++++++++++++++++++++----- 7 files changed, 1229 insertions(+), 251 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7f16f2..6c59a44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "ab_glyph" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80179d7dd5d7e8c285d67c4a1e652972a92de7475beddfb92028c76463b13225" +checksum = "8e08104bebc65a46f8bc7aa733d39ea6874bfa7156f41a46b805785e3af1587d" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -117,9 +117,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -261,9 +261,9 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.7.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b3d0060af21e8d11a926981cc00c6c1541aa91dd64b9f881985c3da1094425f" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" [[package]] name = "arrayref" @@ -350,22 +350,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" dependencies = [ "concurrent-queue", - "event-listener 5.2.0", - "event-listener-strategy 0.5.0", + "event-listener 5.3.0", + "event-listener-strategy 0.5.1", "futures-core", "pin-project-lite", ] [[package]] name = "async-executor" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" +checksum = "10b3e585719c2358d2660232671ca8ca4ddb4be4ce8a1842d6c2dc8685303316" dependencies = [ "async-lock 3.3.0", "async-task", "concurrent-queue", - "fastrand 2.0.1", + "fastrand 2.0.2", "futures-lite 2.3.0", "slab", ] @@ -414,8 +414,8 @@ dependencies = [ "futures-io", "futures-lite 2.3.0", "parking", - "polling 3.5.0", - "rustix 0.38.31", + "polling 3.6.0", + "rustix 0.38.32", "slab", "tracing", "windows-sys 0.52.0", @@ -454,7 +454,7 @@ dependencies = [ "cfg-if", "event-listener 3.1.0", "futures-lite 1.13.0", - "rustix 0.38.31", + "rustix 0.38.32", "windows-sys 0.48.0", ] @@ -466,7 +466,7 @@ checksum = "30c5ef0ede93efbf733c1a727f3b6b5a1060bbedd5600183e66f6e4be4af0ec5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -481,7 +481,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 0.38.31", + "rustix 0.38.32", "signal-hook-registry", "slab", "windows-sys 0.48.0", @@ -495,13 +495,13 @@ checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" [[package]] name = "async-trait" -version = "0.1.78" +version = "0.1.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "461abc97219de0eaaf81fe3ef974a540158f3d079c2ab200f891f1a2ef201e85" +checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -527,7 +527,7 @@ name = "atomicwrites" version = "0.4.2" source = "git+https://github.com/jackpot51/rust-atomicwrites#043ab4859d53ffd3d55334685303d8df39c9f768" dependencies = [ - "rustix 0.38.31", + "rustix 0.38.32", "tempfile", "windows-sys 0.48.0", ] @@ -582,15 +582,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -696,7 +696,7 @@ dependencies = [ "async-channel", "async-lock 3.3.0", "async-task", - "fastrand 2.0.1", + "fastrand 2.0.2", "futures-io", "futures-lite 2.3.0", "piper", @@ -726,7 +726,7 @@ checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -737,9 +737,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cairo-sys-rs" @@ -759,8 +759,22 @@ checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" dependencies = [ "bitflags 2.5.0", "log", - "polling 3.5.0", - "rustix 0.38.31", + "polling 3.6.0", + "rustix 0.38.32", + "slab", + "thiserror", +] + +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.5.0", + "log", + "polling 3.6.0", + "rustix 0.38.32", "slab", "thiserror", ] @@ -771,8 +785,20 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" dependencies = [ - "calloop", - "rustix 0.38.31", + "calloop 0.12.4", + "rustix 0.38.32", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop 0.13.0", + "rustix 0.38.32", "wayland-backend", "wayland-client", ] @@ -823,9 +849,9 @@ checksum = "77e53693616d3075149f4ead59bdeecd204ac6b8192d8969757601b74bddf00f" [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", @@ -848,7 +874,7 @@ dependencies = [ [[package]] name = "clipboard_macos" version = "0.1.0" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-mime-types#f65a6c303bbbd6c7bf88f9bc34421ec06d893bea" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-3#e717bd58a0ccc7ef8bedda234f9159b5938e1894" dependencies = [ "objc", "objc-foundation", @@ -858,8 +884,9 @@ dependencies = [ [[package]] name = "clipboard_wayland" version = "0.2.2" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-mime-types#f65a6c303bbbd6c7bf88f9bc34421ec06d893bea" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-3#e717bd58a0ccc7ef8bedda234f9159b5938e1894" dependencies = [ + "dnd", "mime 0.1.0", "smithay-clipboard", ] @@ -867,7 +894,7 @@ dependencies = [ [[package]] name = "clipboard_x11" version = "0.4.2" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-mime-types#f65a6c303bbbd6c7bf88f9bc34421ec06d893bea" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-3#e717bd58a0ccc7ef8bedda234f9159b5938e1894" dependencies = [ "thiserror", "x11rb", @@ -1013,9 +1040,9 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "core-graphics" -version = "0.23.1" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -1038,7 +1065,7 @@ dependencies = [ [[package]] name = "cosmic-config" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#01d7e46feadccf70825c3a822a566fb266d3add6" +source = "git+https://github.com/pop-os/libcosmic.git#7d1b55711239864d2f7ff3561920bfbd68e02d98" dependencies = [ "atomicwrites", "cosmic-config-derive", @@ -1055,7 +1082,7 @@ dependencies = [ [[package]] name = "cosmic-config-derive" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#01d7e46feadccf70825c3a822a566fb266d3add6" +source = "git+https://github.com/pop-os/libcosmic.git#7d1b55711239864d2f7ff3561920bfbd68e02d98" dependencies = [ "quote", "syn 1.0.109", @@ -1068,7 +1095,7 @@ dependencies = [ "chrono", "dirs 5.0.1", "env_logger", - "fastrand 2.0.1", + "fastrand 2.0.2", "fork", "freedesktop_entry_parser", "fs_extra", @@ -1101,7 +1128,7 @@ dependencies = [ [[package]] name = "cosmic-text" version = "0.11.2" -source = "git+https://github.com/pop-os/cosmic-text.git#b08676909f882f553ab574601b35b58276a52458" +source = "git+https://github.com/pop-os/cosmic-text.git#ff5501d9a36e51c50d908413caf7632d8f7533b7" dependencies = [ "bitflags 2.5.0", "fontdb", @@ -1123,7 +1150,7 @@ dependencies = [ [[package]] name = "cosmic-theme" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#01d7e46feadccf70825c3a822a566fb266d3add6" +source = "git+https://github.com/pop-os/libcosmic.git#7d1b55711239864d2f7ff3561920bfbd68e02d98" dependencies = [ "almost", "cosmic-config", @@ -1227,7 +1254,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad291aa74992b9b7a7e88c38acbbf6ad7e107f1d90ee8775b7bc1fc3394f485c" dependencies = [ "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -1267,7 +1294,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -1278,7 +1305,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" dependencies = [ "darling_core", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -1329,7 +1356,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -1418,7 +1445,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -1439,6 +1466,18 @@ dependencies = [ "const-random", ] +[[package]] +name = "dnd" +version = "0.1.0" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-3#e717bd58a0ccc7ef8bedda234f9159b5938e1894" +dependencies = [ + "bitflags 2.5.0", + "mime 0.1.0", + "raw-window-handle 0.6.0", + "smithay-client-toolkit 0.18.0", + "smithay-clipboard", +] + [[package]] name = "downcast-rs" version = "1.2.0" @@ -1455,7 +1494,7 @@ dependencies = [ "bytemuck", "drm-ffi", "drm-fourcc", - "rustix 0.38.31", + "rustix 0.38.32", ] [[package]] @@ -1465,7 +1504,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41334f8405792483e32ad05fbb9c5680ff4e84491883d2947a4757dc54cb2ac6" dependencies = [ "drm-sys", - "rustix 0.38.31", + "rustix 0.38.32", ] [[package]] @@ -1508,7 +1547,7 @@ checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -1605,9 +1644,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b5fb89194fa3cad959b833185b3063ba881dbfc7030680b314250779fb4cc91" +checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" dependencies = [ "concurrent-queue", "parking", @@ -1626,11 +1665,11 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" +checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3" dependencies = [ - "event-listener 5.2.0", + "event-listener 5.3.0", "pin-project-lite", ] @@ -1667,9 +1706,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "fdeflate" @@ -1793,9 +1832,12 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "font-types" -version = "0.4.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b7f6040d337bd44434ab21fc6509154edf2cece88b23758d9d64654c4e7730b" +checksum = "bd6784a76a9c2b136ea3b8462391e9328252e938eb706eb44d752723b4c3a533" +dependencies = [ + "bytemuck", +] [[package]] name = "fontconfig-parser" @@ -1838,7 +1880,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -1877,9 +1919,9 @@ dependencies = [ [[package]] name = "freedesktop-desktop-entry" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287f89b1a3d88dd04d2b65dfec39f3c381efbcded7b736456039c4ee49d54b17" +checksum = "c201444ddafb5506fe85265b48421664ff4617e3b7090ef99e42a0070c1aead0" dependencies = [ "dirs 3.0.2", "gettext-rs", @@ -1996,7 +2038,7 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ - "fastrand 2.0.1", + "fastrand 2.0.2", "futures-core", "futures-io", "parking", @@ -2011,7 +2053,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -2367,6 +2409,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -2444,7 +2492,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.53", + "syn 2.0.58", "unic-langid", ] @@ -2458,7 +2506,7 @@ dependencies = [ "i18n-config", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -2487,8 +2535,9 @@ dependencies = [ [[package]] name = "iced" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#01d7e46feadccf70825c3a822a566fb266d3add6" +source = "git+https://github.com/pop-os/libcosmic.git#7d1b55711239864d2f7ff3561920bfbd68e02d98" dependencies = [ + "dnd", "iced_accessibility", "iced_core", "iced_futures", @@ -2496,6 +2545,7 @@ dependencies = [ "iced_widget", "iced_winit", "image", + "mime 0.1.0", "thiserror", "window_clipboard", ] @@ -2503,7 +2553,7 @@ dependencies = [ [[package]] name = "iced_accessibility" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#01d7e46feadccf70825c3a822a566fb266d3add6" +source = "git+https://github.com/pop-os/libcosmic.git#7d1b55711239864d2f7ff3561920bfbd68e02d98" dependencies = [ "accesskit", "accesskit_winit", @@ -2512,10 +2562,12 @@ dependencies = [ [[package]] name = "iced_core" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#01d7e46feadccf70825c3a822a566fb266d3add6" +source = "git+https://github.com/pop-os/libcosmic.git#7d1b55711239864d2f7ff3561920bfbd68e02d98" dependencies = [ "bitflags 1.3.2", + "dnd", "log", + "mime 0.1.0", "num-traits", "palette", "raw-window-handle 0.6.0", @@ -2530,7 +2582,7 @@ dependencies = [ [[package]] name = "iced_futures" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#01d7e46feadccf70825c3a822a566fb266d3add6" +source = "git+https://github.com/pop-os/libcosmic.git#7d1b55711239864d2f7ff3561920bfbd68e02d98" dependencies = [ "futures", "iced_core", @@ -2543,7 +2595,7 @@ dependencies = [ [[package]] name = "iced_graphics" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#01d7e46feadccf70825c3a822a566fb266d3add6" +source = "git+https://github.com/pop-os/libcosmic.git#7d1b55711239864d2f7ff3561920bfbd68e02d98" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -2567,7 +2619,7 @@ dependencies = [ [[package]] name = "iced_renderer" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#01d7e46feadccf70825c3a822a566fb266d3add6" +source = "git+https://github.com/pop-os/libcosmic.git#7d1b55711239864d2f7ff3561920bfbd68e02d98" dependencies = [ "iced_graphics", "iced_tiny_skia", @@ -2579,8 +2631,9 @@ dependencies = [ [[package]] name = "iced_runtime" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#01d7e46feadccf70825c3a822a566fb266d3add6" +source = "git+https://github.com/pop-os/libcosmic.git#7d1b55711239864d2f7ff3561920bfbd68e02d98" dependencies = [ + "dnd", "iced_core", "iced_futures", "thiserror", @@ -2590,7 +2643,7 @@ dependencies = [ [[package]] name = "iced_style" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#01d7e46feadccf70825c3a822a566fb266d3add6" +source = "git+https://github.com/pop-os/libcosmic.git#7d1b55711239864d2f7ff3561920bfbd68e02d98" dependencies = [ "iced_core", "once_cell", @@ -2600,7 +2653,7 @@ dependencies = [ [[package]] name = "iced_tiny_skia" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#01d7e46feadccf70825c3a822a566fb266d3add6" +source = "git+https://github.com/pop-os/libcosmic.git#7d1b55711239864d2f7ff3561920bfbd68e02d98" dependencies = [ "bytemuck", "cosmic-text", @@ -2617,7 +2670,7 @@ dependencies = [ [[package]] name = "iced_wgpu" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#01d7e46feadccf70825c3a822a566fb266d3add6" +source = "git+https://github.com/pop-os/libcosmic.git#7d1b55711239864d2f7ff3561920bfbd68e02d98" dependencies = [ "bitflags 1.3.2", "bytemuck", @@ -2636,8 +2689,9 @@ dependencies = [ [[package]] name = "iced_widget" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#01d7e46feadccf70825c3a822a566fb266d3add6" +source = "git+https://github.com/pop-os/libcosmic.git#7d1b55711239864d2f7ff3561920bfbd68e02d98" dependencies = [ + "dnd", "iced_renderer", "iced_runtime", "iced_style", @@ -2645,13 +2699,15 @@ dependencies = [ "ouroboros", "thiserror", "unicode-segmentation", + "window_clipboard", ] [[package]] name = "iced_winit" version = "0.12.0" -source = "git+https://github.com/pop-os/libcosmic.git#01d7e46feadccf70825c3a822a566fb266d3add6" +source = "git+https://github.com/pop-os/libcosmic.git#7d1b55711239864d2f7ff3561920bfbd68e02d98" dependencies = [ + "dnd", "iced_graphics", "iced_runtime", "iced_style", @@ -2727,9 +2783,9 @@ checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284" [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", @@ -2815,9 +2871,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jni" @@ -2975,7 +3031,7 @@ checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libcosmic" version = "0.1.0" -source = "git+https://github.com/pop-os/libcosmic.git#01d7e46feadccf70825c3a822a566fb266d3add6" +source = "git+https://github.com/pop-os/libcosmic.git#7d1b55711239864d2f7ff3561920bfbd68e02d98" dependencies = [ "apply", "ashpd 0.7.0", @@ -3041,9 +3097,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libredox" -version = "0.0.1" +version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" dependencies = [ "bitflags 2.5.0", "libc", @@ -3052,13 +3108,12 @@ dependencies = [ [[package]] name = "libredox" -version = "0.0.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.5.0", "libc", - "redox_syscall 0.4.1", ] [[package]] @@ -3180,9 +3235,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "memmap2" @@ -3204,9 +3259,9 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] @@ -3229,7 +3284,7 @@ dependencies = [ [[package]] name = "mime" version = "0.1.0" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-mime-types#f65a6c303bbbd6c7bf88f9bc34421ec06d893bea" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-3#e717bd58a0ccc7ef8bedda234f9159b5938e1894" dependencies = [ "smithay-clipboard", ] @@ -3521,7 +3576,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -3653,9 +3708,9 @@ dependencies = [ [[package]] name = "ordered-multimap" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4d6a8c22fc714f0c2373e6091bf6f5e9b37b1bc0b1184874b7e0a4e303d318f" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" dependencies = [ "dlv-list", "hashbrown", @@ -3688,11 +3743,11 @@ version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -3725,7 +3780,7 @@ checksum = "e8890702dbec0bad9116041ae586f84805b13eecd1d8b1df27c29998a9969d6d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -3842,7 +3897,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -3862,9 +3917,9 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -3879,7 +3934,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" dependencies = [ "atomic-waker", - "fastrand 2.0.1", + "fastrand 2.0.2", "futures-io", ] @@ -3920,14 +3975,15 @@ dependencies = [ [[package]] name = "polling" -version = "3.5.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24f040dee2588b4963afb4e420540439d126f73fdacf4a9c486a96d840bac3c9" +checksum = "e0c976a60b2d7e99d6f229e414670a9b85d13ac305cc6d1e9c134de58c5aaaf6" dependencies = [ "cfg-if", "concurrent-queue", + "hermit-abi", "pin-project-lite", - "rustix 0.38.31", + "rustix 0.38.32", "tracing", "windows-sys 0.52.0", ] @@ -4097,9 +4153,9 @@ checksum = "42a9830a0e1b9fb145ebb365b8bc4ccd75f290f98c0247deafbbe2c75cefb544" [[package]] name = "rayon" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4963ed1bc86e4f3ee217022bd855b297cef07fb9eac5dfa1f788b220b49b3bd" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -4123,10 +4179,11 @@ checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" [[package]] name = "read-fonts" -version = "0.16.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81c524658d3b77930a391f559756d91dbe829ab6cf4687083f615d395df99722" +checksum = "ea75b5ec052843434d263ef7a4c31cf86db5908c729694afb1ad3c884252a1b6" dependencies = [ + "bytemuck", "font-types", ] @@ -4159,20 +4216,20 @@ dependencies = [ [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ "getrandom", - "libredox 0.0.1", + "libredox 0.1.3", "thiserror", ] [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -4193,9 +4250,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "renderdoc-sys" @@ -4291,7 +4348,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.53", + "syn 2.0.58", "walkdir", ] @@ -4343,9 +4400,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ "bitflags 2.5.0", "errno", @@ -4413,7 +4470,7 @@ dependencies = [ "ab_glyph", "log", "memmap2", - "smithay-client-toolkit", + "smithay-client-toolkit 0.18.1", "tiny-skia", ] @@ -4449,7 +4506,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -4460,7 +4517,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -4550,9 +4607,33 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "smithay-client-toolkit" +version = "0.18.0" +source = "git+https://github.com/smithay/client-toolkit?rev=3bed072#3bed072b966022f5f929d12f3aff089b1ace980b" +dependencies = [ + "bitflags 2.5.0", + "calloop 0.13.0", + "calloop-wayland-source 0.3.0", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.32", + "thiserror", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] [[package]] name = "smithay-client-toolkit" @@ -4561,13 +4642,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" dependencies = [ "bitflags 2.5.0", - "calloop", - "calloop-wayland-source", + "calloop 0.12.4", + "calloop-wayland-source 0.2.0", "cursor-icon", "libc", "log", "memmap2", - "rustix 0.38.31", + "rustix 0.38.32", "thiserror", "wayland-backend", "wayland-client", @@ -4582,10 +4663,11 @@ dependencies = [ [[package]] name = "smithay-clipboard" version = "0.8.0" -source = "git+https://github.com/pop-os/smithay-clipboard?tag=pop-mime-types#cc0101c1f9ccc937a413bd3af3c0f6217f27e935" +source = "git+https://github.com/pop-os/smithay-clipboard?tag=pop-dnd-3#2f2430bec35f0adb9cb93e85e648ff8449d44dad" dependencies = [ "libc", - "smithay-client-toolkit", + "raw-window-handle 0.6.0", + "smithay-client-toolkit 0.18.0", "wayland-backend", ] @@ -4629,7 +4711,7 @@ dependencies = [ "cocoa", "core-graphics", "drm", - "fastrand 2.0.1", + "fastrand 2.0.2", "foreign-types", "js-sys", "log", @@ -4637,7 +4719,7 @@ dependencies = [ "objc", "raw-window-handle 0.6.0", "redox_syscall 0.4.1", - "rustix 0.38.31", + "rustix 0.38.32", "tiny-xlib", "wasm-bindgen", "wayland-backend", @@ -4705,9 +4787,9 @@ dependencies = [ [[package]] name = "swash" -version = "0.1.13" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9af636fb90d39858650cae1088a37e2862dab4e874a0bb49d6dfb5b2dacf0e24" +checksum = "06ec889a8e0a6fcb91041996c8f1f6be0fe1a09e94478785e07c32ce2bca2d2b" dependencies = [ "read-fonts", "yazi", @@ -4727,9 +4809,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.53" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", @@ -4747,12 +4829,12 @@ dependencies = [ [[package]] name = "system-deps" -version = "6.2.1" +version = "6.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8e9199467bcbc77c6a13cc6e32a6af21721ab8c96aa0261856c4fda5a4433f0" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" dependencies = [ "cfg-expr", - "heck", + "heck 0.5.0", "pkg-config", "toml 0.8.12", "version-compare", @@ -4777,9 +4859,9 @@ checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" [[package]] name = "temp-dir" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd16aa9ffe15fe021c6ee3766772132c6e98dfa395a167e16864f61a9cfb71d6" +checksum = "1f227968ec00f0e5322f9b8173c7a0cbcff6181a0a5b28e9892491c286277231" [[package]] name = "tempfile" @@ -4788,8 +4870,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", - "fastrand 2.0.1", - "rustix 0.38.31", + "fastrand 2.0.2", + "rustix 0.38.32", "windows-sys 0.52.0", ] @@ -4820,7 +4902,7 @@ checksum = "c8f546451eaa38373f549093fe9fd05e7d2bade739e2ddf834b9968621d60107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -4840,7 +4922,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -4960,9 +5042,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -4994,7 +5076,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.8", + "toml_edit 0.22.9", ] [[package]] @@ -5030,9 +5112,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.8" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c12219811e0c1ba077867254e5ad62ee2c9c190b0d957110750ac0cda1ae96cd" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" dependencies = [ "indexmap", "serde", @@ -5060,7 +5142,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] @@ -5115,7 +5197,7 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" dependencies = [ - "memoffset 0.9.0", + "memoffset 0.9.1", "tempfile", "winapi", ] @@ -5316,9 +5398,9 @@ dependencies = [ [[package]] name = "version-compare" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" [[package]] name = "version_check" @@ -5369,7 +5451,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", "wasm-bindgen-shared", ] @@ -5403,7 +5485,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -5437,7 +5519,7 @@ checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40" dependencies = [ "cc", "downcast-rs", - "rustix 0.38.31", + "rustix 0.38.32", "scoped-tls", "smallvec", "wayland-sys", @@ -5450,7 +5532,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f" dependencies = [ "bitflags 2.5.0", - "rustix 0.38.31", + "rustix 0.38.32", "wayland-backend", "wayland-scanner", ] @@ -5472,7 +5554,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71ce5fa868dd13d11a0d04c5e2e65726d0897be8de247c0c5a65886e283231ba" dependencies = [ - "rustix 0.38.31", + "rustix 0.38.32", "wayland-client", "xcursor", ] @@ -5706,12 +5788,13 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "window_clipboard" version = "0.4.1" -source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-mime-types#f65a6c303bbbd6c7bf88f9bc34421ec06d893bea" +source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-dnd-3#e717bd58a0ccc7ef8bedda234f9159b5938e1894" dependencies = [ "clipboard-win", "clipboard_macos", "clipboard_wayland", "clipboard_x11", + "dnd", "mime 0.1.0", "raw-window-handle 0.6.0", "thiserror", @@ -5986,7 +6069,7 @@ dependencies = [ "atomic-waker", "bitflags 2.5.0", "bytemuck", - "calloop", + "calloop 0.12.4", "cfg_aliases 0.1.1", "core-foundation", "core-graphics", @@ -6004,9 +6087,9 @@ dependencies = [ "percent-encoding", "raw-window-handle 0.6.0", "redox_syscall 0.3.5", - "rustix 0.38.31", + "rustix 0.38.32", "sctk-adwaita", - "smithay-client-toolkit", + "smithay-client-toolkit 0.18.1", "smol_str", "unicode-segmentation", "wasm-bindgen", @@ -6063,7 +6146,7 @@ dependencies = [ "libc", "libloading 0.8.3", "once_cell", - "rustix 0.38.31", + "rustix 0.38.32", "x11rb-protocol", ] @@ -6129,9 +6212,9 @@ checksum = "054a8e68b76250b253f671d1268cb7f1ae089ec35e195b2efb2a4e9a836d0621" [[package]] name = "xml-rs" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" +checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" [[package]] name = "xmlwriter" @@ -6241,7 +6324,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.53", + "syn 2.0.58", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index ca07272..9203426 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,9 @@ default = ["desktop", "wgpu"] desktop = ["libcosmic/desktop", "dep:freedesktop_entry_parser", "dep:xdg"] wgpu = ["libcosmic/wgpu"] +[profile.dev] +opt-level = 1 + [profile.release-with-debug] inherits = "release" debug = true @@ -66,3 +69,15 @@ fork = "0.1" fastrand = "2" tempfile = "3" test-log = "0.2" + +# [patch.'https://github.com/pop-os/libcosmic'] +# libcosmic = { path = "../libcosmic" } +# cosmic-config = { path = "../libcosmic/cosmic-config" } +# cosmic-theme = { path = "../libcosmic/cosmic-theme" } +# libcosmic = { git = "https://github.com/pop-os/libcosmic//", branch = "dnd" } +# cosmic-config = { git = "https://github.com/pop-os/libcosmic//", branch = "dnd" } +# cosmic-theme = { git = "https://github.com/pop-os/libcosmic//", branch = "dnd" } + +# [patch.'https://github.com/pop-os/smithay-clipboard'] +# smithay-clipboard = { git = "https://github.com/pop-os/smithay-clipboard//", rev = "2f2430b" } +# smithay-clipboard = { path = "../smithay-clipboard" } diff --git a/src/app.rs b/src/app.rs index d8aa6a9..45e858e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,6 +1,8 @@ // Copyright 2023 System76 // SPDX-License-Identifier: GPL-3.0-only +use cosmic::iced::clipboard::dnd::DndAction; +use cosmic::widget::dnd_destination::DragId; use cosmic::widget::menu::action::MenuAction; use cosmic::widget::menu::key_bind::KeyBind; use cosmic::{ @@ -27,6 +29,7 @@ use notify_debouncer_full::{ notify::{self, RecommendedWatcher, Watcher}, DebouncedEvent, Debouncer, FileIdMap, }; +use std::time::Instant; use std::{ any::TypeId, collections::{BTreeMap, HashMap, HashSet, VecDeque}, @@ -38,6 +41,7 @@ use std::{ time, }; +use crate::tab::HOVER_DURATION; use crate::{ clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste}, config::{AppTheme, Config, IconSizes, TabConfig, CONFIG_VERSION}, @@ -180,6 +184,14 @@ pub enum Message { ToggleContextPage(ContextPage), WindowClose, WindowNew, + DndHoverLocTimeout(Location), + DndHoverTabTimeout(Entity), + DndEnterNav(Entity), + DndExitNav, + DndEnterTab(Entity), + DndExitTab, + DndDropTab(Entity, Option, DndAction), + DndDropNav(Entity, Option, DndAction), } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -261,6 +273,10 @@ pub struct App { complete_operations: BTreeMap, failed_operations: BTreeMap, watcher_opt: Option<(Debouncer, HashSet)>, + nav_dnd_hover: Option<(Location, Instant)>, + tab_dnd_hover: Option<(Entity, Instant)>, + nav_drag_id: DragId, + tab_drag_id: DragId, } impl App { @@ -644,6 +660,34 @@ impl Application for App { &mut self.core } + fn nav_bar(&self) -> Option>> { + if !self.core().nav_bar_active() { + return None; + } + + let nav_model = self.nav_model()?; + + let mut nav = cosmic::widget::nav_bar_dnd( + nav_model, + |entity| cosmic::app::Message::Cosmic(cosmic::app::cosmic::Message::NavBar(entity)), + |entity, _| cosmic::app::Message::App(Message::DndEnterNav(entity)), + |_| cosmic::app::Message::App(Message::DndExitNav), + |entity, data, action| { + cosmic::app::Message::App(Message::DndDropNav(entity, data, action)) + }, + self.nav_drag_id, + ); + + if !self.core().is_condensed() { + nav = nav.max_width(280); + } + + Some(Element::from( + // XXX both must be shrink to avoid flex layout from ignoring it + nav.width(Length::Shrink).height(Length::Shrink), + )) + } + /// Creates the application, and optionally emits command on initialize. fn init(mut core: Core, flags: Self::Flags) -> (Self, Command) { //TODO: make set_nav_bar_toggle_condensed pub @@ -702,6 +746,10 @@ impl Application for App { complete_operations: BTreeMap::new(), failed_operations: BTreeMap::new(), watcher_opt: None, + nav_dnd_hover: None, + tab_dnd_hover: None, + nav_drag_id: DragId::new(), + tab_drag_id: DragId::new(), }; let mut commands = Vec::new(); @@ -1300,6 +1348,23 @@ impl Application for App { tab::Command::Scroll(id, offset) => { commands.push(scrollable::scroll_to(id, offset)); } + tab::Command::DropFiles(to, from) => { + commands.push(self.update(Message::PasteContents(to, from))); + } + tab::Command::Timeout(d, tab_msg) => { + commands.push(Command::perform( + async move { + tokio::time::sleep(d).await; + tab_msg + }, + move |msg| { + cosmic::app::Message::App(Message::TabMessage( + Some(entity), + msg, + )) + }, + )); + } } } return Command::batch(commands); @@ -1343,6 +1408,100 @@ impl Application for App { log::error!("failed to get current executable path: {}", err); } }, + Message::DndEnterNav(entity) => { + if let Some(location) = self.nav_model.data::(entity) { + self.nav_dnd_hover = Some((location.clone(), Instant::now())); + let location = location.clone(); + return Command::perform(tokio::time::sleep(HOVER_DURATION), move |_| { + cosmic::app::Message::App(Message::DndHoverLocTimeout(location)) + }); + } + } + Message::DndExitNav => { + self.nav_dnd_hover = None; + } + Message::DndDropNav(entity, data, action) => { + self.nav_dnd_hover = None; + if let Some((location, data)) = self.nav_model.data::(entity).zip(data) { + let kind = match action { + DndAction::Move => ClipboardKind::Cut, + _ => ClipboardKind::Copy, + }; + let ret = match location { + Location::Path(p) => self.update(Message::PasteContents( + p.clone(), + ClipboardPaste { + kind, + paths: data.paths, + }, + )), + Location::Trash => { + // TODO move to trash if action is cut + return Command::none(); + } + }; + return ret; + } + } + Message::DndHoverLocTimeout(location) => { + if self + .nav_dnd_hover + .as_ref() + .is_some_and(|(loc, i)| *loc == location && i.elapsed() >= HOVER_DURATION) + { + self.nav_dnd_hover = None; + let entity = self.tab_model.active(); + let title = location.to_string(); + self.tab_model.text_set(entity, title); + return Command::batch([ + self.update_title(), + self.update_watcher(), + self.rescan_tab(entity, location), + ]); + } + } + Message::DndEnterTab(entity) => { + self.tab_dnd_hover = Some((entity, Instant::now())); + return Command::perform(tokio::time::sleep(HOVER_DURATION), move |_| { + cosmic::app::Message::App(Message::DndHoverTabTimeout(entity)) + }); + } + Message::DndExitTab => { + self.nav_dnd_hover = None; + } + Message::DndDropTab(entity, data, action) => { + self.nav_dnd_hover = None; + if let Some((tab, data)) = self.tab_model.data::(entity).zip(data) { + let kind = match action { + DndAction::Move => ClipboardKind::Cut, + _ => ClipboardKind::Copy, + }; + let ret = match &tab.location { + Location::Path(p) => self.update(Message::PasteContents( + p.clone(), + ClipboardPaste { + kind, + paths: data.paths, + }, + )), + Location::Trash => { + // TODO move to trash if action is cut + return Command::none(); + } + }; + return ret; + } + } + Message::DndHoverTabTimeout(entity) => { + if self + .tab_dnd_hover + .as_ref() + .is_some_and(|(e, i)| *e == entity && i.elapsed() >= HOVER_DURATION) + { + self.tab_dnd_hover = None; + return self.update(Message::TabActivate(entity)); + } + } } Command::none() @@ -1553,7 +1712,13 @@ impl Application for App { .button_height(32) .button_spacing(space_xxs) .on_activate(Message::TabActivate) - .on_close(|entity| Message::TabClose(Some(entity))), + .on_close(|entity| Message::TabClose(Some(entity))) + .on_dnd_enter(|entity, _| Message::DndEnterTab(entity)) + .on_dnd_leave(|_| Message::DndExitTab) + .on_dnd_drop(|entity, data, action| { + Message::DndDropTab(entity, data, action) + }) + .drag_id(self.tab_drag_id), ) .style(style::Container::Background) .width(Length::Fill), diff --git a/src/clipboard.rs b/src/clipboard.rs index 7bd315e..3290948 100644 --- a/src/clipboard.rs +++ b/src/clipboard.rs @@ -16,6 +16,7 @@ pub enum ClipboardKind { Cut, } +#[derive(Clone, Debug)] pub struct ClipboardCopy { pub available: Cow<'static, [String]>, pub text_plain: Cow<'static, [u8]>, diff --git a/src/dialog.rs b/src/dialog.rs index af9ff6f..e415052 100644 --- a/src/dialog.rs +++ b/src/dialog.rs @@ -615,6 +615,9 @@ impl Application for App { commands .push(Command::batch([self.update_watcher(), self.rescan_tab()])); } + tab::Command::DropFiles(_, _) => { + log::warn!("DropFiles not supported in dialog"); + } tab::Command::FocusButton(id) => { commands.push(widget::button::focus(id)); } @@ -631,6 +634,9 @@ impl Application for App { tab::Command::Scroll(id, offset) => { commands.push(scrollable::scroll_to(id, offset)); } + tab::Command::Timeout(_, _) => { + log::warn!("Timeout not supported in dialog"); + } } } return Command::batch(commands); diff --git a/src/mouse_area.rs b/src/mouse_area.rs index fe2156f..cc1a03d 100644 --- a/src/mouse_area.rs +++ b/src/mouse_area.rs @@ -1,24 +1,34 @@ //! A container for capturing mouse events. +use std::time::Instant; + use cosmic::{ iced_core::{ border::Border, event::{self, Event}, - layout, mouse, overlay, + layout, + mouse::{self, click}, + overlay, renderer::{self, Quad, Renderer as _}, touch, widget::{tree, Operation, OperationOutputWrapper, Tree}, Clipboard, Color, Layout, Length, Point, Rectangle, Shell, Size, Widget, }, + widget::Id, Element, Renderer, Theme, }; +use crate::tab::DOUBLE_CLICK_DURATION; + /// Emit messages on mouse events. #[allow(missing_debug_implementations)] pub struct MouseArea<'a, Message> { + id: Id, content: Element<'a, Message>, on_drag: Option) -> Message + 'a>>, + on_double_click: Option) -> Message + 'a>>, on_press: Option) -> Message + 'a>>, + on_drag_end: Option) -> Message + 'a>>, on_release: Option) -> Message + 'a>>, on_resize: Option Message + 'a>>, on_right_press: Option) -> Message + 'a>>, @@ -41,6 +51,20 @@ impl<'a, Message> MouseArea<'a, Message> { self } + /// The message to emit when a drag ends. + #[must_use] + pub fn on_drag_end(mut self, message: impl Fn(Option) -> Message + 'a) -> Self { + self.on_drag_end = Some(Box::new(message)); + self + } + + /// The message to emit on a double click. + #[must_use] + pub fn on_double_click(mut self, message: impl Fn(Option) -> Message + 'a) -> Self { + self.on_double_click = Some(Box::new(message)); + self + } + /// The message to emit on a left button press. #[must_use] pub fn on_press(mut self, message: impl Fn(Option) -> Message + 'a) -> Self { @@ -132,6 +156,13 @@ impl<'a, Message> MouseArea<'a, Message> { self.show_drag_rect = show_drag_rect; self } + + /// Sets the widget's unique identifier. + #[must_use] + pub fn with_id(mut self, id: Id) -> Self { + self.id = id; + self + } } /// Local state of the [`MouseArea`]. @@ -140,6 +171,8 @@ struct State { last_size: Option, // TODO: Support on_mouse_enter and on_mouse_exit drag_initiated: Option, + + prev_click: Option<(mouse::Click, Instant)>, } impl State { @@ -160,14 +193,37 @@ impl State { } None } + + fn click(&mut self, pos: Point) -> mouse::Click { + let now = Instant::now(); + + let new = if let Some((prev_click, prev_time)) = self.prev_click.take() { + if now.duration_since(prev_time) < DOUBLE_CLICK_DURATION { + match prev_click.kind() { + mouse::click::Kind::Single => mouse::Click::new(pos, Some(prev_click)), + mouse::click::Kind::Double => mouse::Click::new(pos, Some(prev_click)), + mouse::click::Kind::Triple => mouse::Click::new(pos, Some(prev_click)), + } + } else { + mouse::Click::new(pos, None) + } + } else { + mouse::Click::new(pos, None) + }; + self.prev_click = Some((new.clone(), now)); + new + } } impl<'a, Message> MouseArea<'a, Message> { /// Creates a [`MouseArea`] with the given content. pub fn new(content: impl Into>) -> Self { MouseArea { + id: Id::unique(), content: content.into(), on_drag: None, + on_drag_end: None, + on_double_click: None, on_press: None, on_release: None, on_resize: None, @@ -336,6 +392,25 @@ where .as_widget_mut() .overlay(&mut tree.children[0], layout, renderer) } + + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + dnd_rectangles: &mut cosmic::iced_core::clipboard::DndDestinationRectangles, + ) { + self.content + .as_widget() + .drag_destinations(&state.children[0], layout, dnd_rectangles); + } + + fn id(&self) -> Option { + Some(self.id.clone()) + } + + fn set_id(&mut self, id: Id) { + self.id = id; + } } impl<'a, Message> From> for Element<'a, Message> @@ -376,7 +451,29 @@ fn update( if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) | Event::Touch(touch::Event::FingerPressed { .. }) = event { - state.drag_initiated = cursor.position(); + let click = state.click(cursor.position_in(layout_bounds).unwrap_or_default()); + match click.kind() { + click::Kind::Single => { + if let Some(message) = widget.on_press.as_ref() { + shell.publish(message(cursor.position_in(layout_bounds))); + } + } + click::Kind::Double => { + if let Some(message) = widget.on_double_click.as_ref() { + shell.publish(message(cursor.position_in(layout_bounds))); + } + } + click::Kind::Triple => { + // TODO what to do here + if let Some(message) = widget.on_press.as_ref() { + shell.publish(message(cursor.position_in(layout_bounds))); + } + } + } + if widget.on_drag.is_some() { + state.drag_initiated = cursor.position(); + } + if let Some(message) = widget.on_press.as_ref() { shell.publish(message(cursor.position_in(layout_bounds))); @@ -384,9 +481,38 @@ fn update( } } - if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) - | Event::Touch(touch::Event::FingerLifted { .. }) = event + let distance_dragged = state + .drag_initiated + .map(|initiated| initiated.distance(cursor.position().unwrap_or_default())) + .unwrap_or_default(); + if matches!( + event, + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) + ) && distance_dragged > 1.0 { + state.drag_initiated = None; + state.prev_click = None; + if let Some(message) = widget.on_drag_end.as_ref() { + shell.publish(message(cursor.position_in(layout_bounds))); + } + } + + let recent_click = state + .prev_click + .as_ref() + .map(|(_, i)| Instant::now().duration_since(*i) <= DOUBLE_CLICK_DURATION) + .unwrap_or_default(); + if matches!( + event, + Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) + | Event::Touch(touch::Event::FingerLifted { .. }) + ) && state.prev_click.is_some() + { + if !recent_click { + state.prev_click = None; + return event::Status::Ignored; + } state.drag_initiated = None; if let Some(message) = widget.on_release.as_ref() { shell.publish(message(cursor.position_in(layout_bounds))); diff --git a/src/tab.rs b/src/tab.rs index ff000e4..e99bc51 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -1,4 +1,8 @@ +use cosmic::iced::clipboard::dnd::DndAction; +use cosmic::iced::Border; +use cosmic::iced_core::widget::tree; use cosmic::widget::menu::key_bind::KeyBind; +use cosmic::widget::{vertical_space, Id, Widget}; use cosmic::{ cosmic_theme, iced::{ @@ -24,6 +28,8 @@ use cosmic::{ use mime_guess::{mime, Mime}; use once_cell::sync::Lazy; use serde::{Deserialize, Serialize}; +use std::cell::RefCell; +use std::sync::{Arc, Mutex}; use std::{ cell::Cell, cmp::Ordering, @@ -34,6 +40,7 @@ use std::{ time::{Duration, Instant}, }; +use crate::clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste}; use crate::{ app::{self, Action}, config::{IconSizes, TabConfig, ICON_SCALE_MAX, ICON_SIZE_GRID}, @@ -43,8 +50,11 @@ use crate::{ mime_icon::{mime_for_path, mime_icon}, mouse_area, }; +use cosmic::widget::{DndDestination, DndSource}; + +pub const DOUBLE_CLICK_DURATION: Duration = Duration::from_millis(500); +pub const HOVER_DURATION: Duration = Duration::from_millis(1600); -const DOUBLE_CLICK_DURATION: Duration = Duration::from_millis(500); //TODO: adjust for locales? const TIME_FORMAT: &'static str = "%a %-d %b %-Y %r"; static SPECIAL_DIRS: Lazy> = Lazy::new(|| { @@ -397,6 +407,15 @@ pub enum Location { Trash, } +impl std::fmt::Display for Location { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Path(path) => write!(f, "{}", path.display()), + Self::Trash => write!(f, "trash"), + } + } +} + impl Location { pub fn scan(&self, sizes: IconSizes) -> Vec { match self { @@ -414,11 +433,16 @@ pub enum Command { FocusTextInput(widget::Id), OpenFile(PathBuf), Scroll(widget::Id, AbsoluteOffset), + DropFiles(PathBuf, ClipboardPaste), + Timeout(Duration, Message), } #[derive(Clone, Debug)] pub enum Message { Click(Option), + DoubleClick(Option), + ClickRelease(Option), + DragEnd(Option), Config(TabConfig), ContextAction(Action), ContextMenu(Option), @@ -441,6 +465,10 @@ pub enum Message { ToggleShowHidden, View(View), ToggleSort(HeadingOptions), + Drop(Option<(Location, ClipboardPaste)>), + DndHover(Location), + DndEnter(Location), + DndLeave(Location), } #[derive(Clone, Debug)] @@ -659,7 +687,9 @@ impl HeadingOptions { } } -#[derive(Clone, Debug)] +// TODO when creating items, pass > to each item +// as a drag data, so that when dnd is initiated, they are all included +#[derive(Clone)] pub struct Tab { //TODO: make more items private pub location: Location, @@ -674,9 +704,12 @@ pub struct Tab { pub history: Vec, pub config: TabConfig, pub(crate) items_opt: Option>, + pub dnd_hovered: Option<(Location, Instant)>, scrollable_id: widget::Id, select_focus: Option, select_shift: Option, + cached_selected: RefCell>, + clicked: Option, } impl Tab { @@ -698,6 +731,9 @@ impl Tab { scrollable_id: widget::Id::unique(), select_focus: None, select_shift: None, + cached_selected: RefCell::new(None), + clicked: None, + dnd_hovered: None, } } @@ -722,6 +758,7 @@ impl Tab { } pub fn select_all(&mut self) { + *self.cached_selected.borrow_mut() = None; if let Some(ref mut items) = self.items_opt { for item in items.iter_mut() { if !self.config.show_hidden && item.hidden { @@ -735,6 +772,7 @@ impl Tab { } pub fn select_none(&mut self) -> bool { + *self.cached_selected.borrow_mut() = None; self.select_focus = None; let mut had_selection = false; if let Some(ref mut items) = self.items_opt { @@ -749,6 +787,7 @@ impl Tab { } pub fn select_name(&mut self, name: &str) { + *self.cached_selected.borrow_mut() = None; if let Some(ref mut items) = self.items_opt { for item in items.iter_mut() { if item.name == name { @@ -761,6 +800,7 @@ impl Tab { } fn select_position(&mut self, row: usize, col: usize, mod_shift: bool) -> bool { + *self.cached_selected.borrow_mut() = None; let mut start = (row, col); let mut end = (row, col); if mod_shift { @@ -808,6 +848,7 @@ impl Tab { } pub fn select_rect(&mut self, rect: Rectangle) { + *self.cached_selected.borrow_mut() = None; if let Some(ref mut items) = self.items_opt { for (_i, item) in items.iter_mut().enumerate() { //TODO: modifiers @@ -870,6 +911,7 @@ impl Tab { } fn select_first_pos_opt(&self) -> Option<(usize, usize)> { + *self.cached_selected.borrow_mut() = None; let items = self.items_opt.as_ref()?; let mut first = None; for item in items.iter() { @@ -899,6 +941,7 @@ impl Tab { } fn select_last_pos_opt(&self) -> Option<(usize, usize)> { + *self.cached_selected.borrow_mut() = None; let items = self.items_opt.as_ref()?; let mut last = None; for item in items.iter() { @@ -936,7 +979,52 @@ impl Tab { let mod_shift = modifiers.contains(Modifiers::SHIFT) && self.dialog.as_ref().map_or(true, |x| x.multiple()); match message { + Message::ClickRelease(click_i_opt) => { + if click_i_opt != self.clicked.take() { + return commands; + } + self.context_menu = None; + if let Some(l) = self.items_opt.as_mut() { + for item in l.iter_mut().enumerate() { + if Some(item.0) != click_i_opt { + item.1.selected = false; + } + } + } + } + Message::DragEnd(_) => { + self.clicked = None; + } + Message::DoubleClick(click_i_opt) => { + if let Some(clicked_item) = self + .items_opt + .as_ref() + .and_then(|items| click_i_opt.and_then(|click_i| items.get(click_i))) + { + if let Some(path) = &clicked_item.path_opt { + if clicked_item.metadata.is_dir() { + cd = Some(Location::Path(path.clone())); + } else { + commands.push(Command::OpenFile(path.clone())); + } + } else { + log::warn!("no path for item {:?}", clicked_item); + } + } else { + log::warn!("no item for click index {:?}", click_i_opt); + } + } Message::Click(click_i_opt) => { + self.context_menu = None; + if !mod_ctrl { + self.clicked = click_i_opt; + } + let dont_unset = mod_ctrl + || self.column_sort().is_some_and(|l| { + l.iter() + .any(|(e_i, e)| Some(e_i) == click_i_opt.as_ref() && e.selected) + }); + *self.cached_selected.borrow_mut() = None; if let Some(ref mut items) = self.items_opt { for (i, item) in items.iter_mut().enumerate() { if Some(i) == click_i_opt { @@ -953,32 +1041,11 @@ impl Tab { } } item.selected = true; - if let Some(click_time) = item.click_time { - if click_time.elapsed() < DOUBLE_CLICK_DURATION { - if let Some(path) = &item.path_opt { - if path.is_dir() { - //TODO: allow opening multiple tabs? - cd = Some(Location::Path(path.clone())); - } else { - commands.push(Command::OpenFile(path.clone())); - } - } else { - //TODO: open properties? - } - } - } - //TODO: prevent triple-click and beyond from opening file? - item.click_time = Some(Instant::now()); - } else if mod_ctrl { - // Holding control allows multiple selection - item.click_time = None; - } else { + } else if !dont_unset { item.selected = false; - item.click_time = None; } } } - self.context_menu = None; if self.select_focus.take().is_some() { // Unfocus currently focused button commands.push(Command::FocusButton(widget::Id::unique())); @@ -998,6 +1065,7 @@ impl Tab { } Message::Drag(rect_opt) => match rect_opt { Some(rect) => { + self.context_menu = None; self.select_rect(rect); if self.select_focus.take().is_some() { // Unfocus currently focused button @@ -1177,6 +1245,7 @@ impl Tab { self.size_opt = Some(size); } Message::RightClick(click_i) => { + *self.cached_selected.borrow_mut() = None; if let Some(ref mut items) = self.items_opt { if !items.get(click_i).map_or(false, |x| x.selected) { // If item not selected, clear selection on other items @@ -1237,6 +1306,48 @@ impl Tab { self.config.sort_direction = heading_sort; self.config.sort_name = heading_option; } + Message::Drop(Some((to, mut from))) => { + self.dnd_hovered = None; + match to { + Location::Path(to) => { + if let Ok(entries) = fs::read_dir(&to) { + for i in entries.into_iter().filter_map(|e| e.ok()) { + let i = i.path(); + from.paths.retain(|p| &i != p); + if from.paths.is_empty() { + log::info!("All dropped files already in target directory."); + return commands; + } + } + } + commands.push(Command::DropFiles(to, from)) + } + Location::Trash => { + // TODO + } + }; + } + Message::Drop(None) => { + self.dnd_hovered = None; + } + Message::DndHover(loc) => { + if self.dnd_hovered.as_ref().is_some_and(|(l, i)| { + *l == loc && Instant::now().duration_since(*i) > HOVER_DURATION + }) { + cd = Some(loc); + } + } + Message::DndEnter(loc) => { + self.dnd_hovered = Some((loc.clone(), Instant::now())); + if loc != self.location { + commands.push(Command::Timeout(HOVER_DURATION, Message::DndHover(loc))); + } + } + Message::DndLeave(loc) => { + if Some(&loc) == self.dnd_hovered.as_ref().map(|(l, _)| l) { + self.dnd_hovered = None; + } + } } if let Some(location) = cd { if location != self.location { @@ -1501,7 +1612,12 @@ impl Tab { .into() } - pub fn grid_view(&self) -> Element { + pub fn grid_view( + &self, + ) -> ( + Option>>, + Element, + ) { let cosmic_theme::Spacing { space_m, space_xxs, @@ -1547,6 +1663,12 @@ impl Tab { .column_spacing(column_spacing) .row_spacing(space_xxs) .padding([0, space_m].into()); + let mut dnd_items: Vec<(usize, (usize, usize), &Item)> = Vec::new(); + let mut drag_w_i = usize::MAX; + let mut drag_n_i = usize::MAX; + let mut drag_e_i = 0; + let mut drag_s_i = 0; + if let Some(items) = self.column_sort() { let mut count = 0; let mut col = 0; @@ -1575,14 +1697,12 @@ impl Tab { .content_fit(ContentFit::Contain) .size(icon_sizes.grid()), ) - .on_press(Message::Click(Some(i))) - .padding(space_xxxs) - .style(button_style(item.selected, false)), + .style(button_style(item.selected, false)) + .padding(space_xxxs), widget::button(widget::text(item.name.clone())) .id(item.button_id.clone()) - .on_press(Message::Click(Some(i))) - .padding([0, space_xxs]) - .style(button_style(item.selected, true)), + .style(button_style(item.selected, true)) + .padding([0, space_xxs]), ]; let mut column = widget::column::with_capacity(buttons.len()) @@ -1601,7 +1721,70 @@ impl Tab { } } - grid = grid.push(column); + let column: Element = if item.metadata.is_dir() && item.path_opt.is_some() + { + let tab_location = Location::Path(item.path_opt.clone().unwrap()); + let tab_location_enter = tab_location.clone(); + let tab_location_leave = tab_location.clone(); + let is_dnd_hovered = + self.dnd_hovered.as_ref().map(|(l, _)| l) == Some(&tab_location); + cosmic::widget::container( + DndDestination::for_data::(column, move |data, action| { + if let Some(mut data) = data { + if action == DndAction::Copy { + Message::Drop(Some((tab_location.clone(), data))) + } else if action == DndAction::Move { + data.kind = ClipboardKind::Cut; + Message::Drop(Some((tab_location.clone(), data))) + } else { + log::warn!("unsupported action: {:?}", action); + Message::Drop(None) + } + } else { + Message::Drop(None) + } + }) + .on_enter(move |_, _, _| Message::DndEnter(tab_location_enter.clone())) + .on_leave(move || Message::DndLeave(tab_location_leave.clone())), + ) + .style(if is_dnd_hovered { + theme::Container::custom(|t| { + let mut a = cosmic::iced_style::container::StyleSheet::appearance( + t, + &theme::Container::default(), + ); + let t = t.cosmic(); + // todo use theme drop target color + let mut bg = t.accent_color(); + bg.alpha = 0.2; + a.background = Some(Color::from(bg).into()); + a.border = Border { + color: t.accent_color().into(), + width: 1.0, + radius: t.radius_s().into(), + }; + a + }) + } else { + theme::Container::default() + }) + .into() + } else { + column.into() + }; + + if item.selected { + dnd_items.push((i, (row, col), item)); + drag_w_i = drag_w_i.min(col); + drag_n_i = drag_n_i.min(row); + drag_e_i = drag_e_i.max(col); + drag_s_i = drag_s_i.max(row); + } + let mouse_area = crate::mouse_area::MouseArea::new(column) + .on_press(move |_| Message::Click(Some(i))) + .on_double_click(move |_| Message::DoubleClick(Some(i))) + .on_release(move |_| Message::ClickRelease(Some(i))); + grid = grid.push(mouse_area); count += 1; col += 1; @@ -1613,7 +1796,7 @@ impl Tab { } if count == 0 { - return self.empty_view(hidden > 0); + return (None, self.empty_view(hidden > 0)); } //TODO: HACK If we don't reach the bottom of the view, go ahead and add a spacer to do that @@ -1639,18 +1822,73 @@ impl Tab { } } - widget::scrollable( + ( + (!dnd_items.is_empty()).then(|| { + let mut dnd_grid = widget::grid() + .column_spacing(column_spacing) + .row_spacing(space_xxs) + .padding([0, space_m].into()); + + let mut dnd_item_i = 0; + for r in drag_n_i..=drag_s_i { + dnd_grid = dnd_grid.insert_row(); + for c in drag_w_i..=drag_e_i { + let Some((i, (row, col), item)) = dnd_items.get(dnd_item_i) else { + break; + }; + if *row == r && *col == c { + let buttons = vec![ + widget::button( + widget::icon::icon(item.icon_handle_grid.clone()) + .content_fit(ContentFit::Contain) + .size(icon_sizes.grid()), + ) + .on_press(Message::Click(Some(*i))) + .padding(space_xxxs) + .style(button_style(item.selected, false)), + widget::button(widget::text(item.name.clone())) + .id(item.button_id.clone()) + .on_press(Message::Click(Some(*i))) + .padding([0, space_xxs]) + .style(button_style(item.selected, true)), + ]; + + let mut column = widget::column::with_capacity(buttons.len()) + .align_items(Alignment::Center) + .height(Length::Fixed(item_height as f32)) + .width(Length::Fixed(item_width as f32)); + for button in buttons { + column = column.push(button) + } + + dnd_grid = dnd_grid.push(column); + dnd_item_i += 1; + } else { + dnd_grid = dnd_grid.push( + widget::container(vertical_space(item_width as f32)) + .height(Length::Fixed(item_height as f32)), + ); + } + } + } + Element::from(dnd_grid) + .map(|m| cosmic::app::Message::App(crate::app::Message::TabMessage(None, m))) + }), mouse_area::MouseArea::new(grid) .on_drag(Message::Drag) - .show_drag_rect(true), + .on_drag_end(|_| Message::DragEnd(None)) + .show_drag_rect(true) + .on_release(|_| Message::ClickRelease(None)) + .into(), ) - .id(self.scrollable_id.clone()) - .on_scroll(Message::Scroll) - .width(Length::Fill) - .into() } - pub fn list_view(&self) -> Element { + pub fn list_view( + &self, + ) -> ( + Option>>, + Element, + ) { let cosmic_theme::Spacing { space_m, space_xxs, .. } = theme::active().cosmic().spacing; @@ -1721,7 +1959,9 @@ impl Tab { y += 1; } - if let Some(items) = self.column_sort() { + let items = self.column_sort(); + let mut drag_items = Vec::new(); + if let Some(items) = items { let mut count = 0; let mut hidden = 0; for (i, item) in items { @@ -1795,10 +2035,10 @@ impl Tab { .size(icon_size) .into(), widget::text(item.name.clone()).width(Length::Fill).into(), - widget::text(modified_text) + widget::text(modified_text.clone()) .width(Length::Fixed(modified_width)) .into(), - widget::text(size_text) + widget::text(size_text.clone()) .width(Length::Fixed(size_width)) .into(), ]) @@ -1806,69 +2046,270 @@ impl Tab { .spacing(space_xxs) }; - let button = widget::button(row) - .width(Length::Fill) - .height(Length::Fixed(row_height as f32)) - .id(item.button_id.clone()) - .padding(space_xxs) - .style(button_style(item.selected, true)) - .on_press(Message::Click(Some(i))); + let button = |row| { + crate::mouse_area::MouseArea::new( + widget::button(row) + .width(Length::Fill) + .height(Length::Fixed(row_height as f32)) + .id(item.button_id.clone()) + .padding(space_xxs) + .style(button_style(item.selected, true)), + ) + .on_press(move |_| Message::Click(Some(i))) + .on_double_click(move |_| Message::DoubleClick(Some(i))) + .on_release(move |_| Message::ClickRelease(Some(i))) + }; + + let mut button_row = button(row.into()); if self.context_menu.is_some() { - children.push(button.into()); - } else { - children.push( - mouse_area::MouseArea::new(button) - .on_right_press_no_capture(move |_point_opt| Message::RightClick(i)) - .into(), - ); + button_row = + button_row.on_right_press(move |_point_opt| Message::RightClick(i)); } + let button_row: Element<_> = if item.metadata.is_dir() && item.path_opt.is_some() { + let tab_location = Location::Path(item.path_opt.clone().unwrap()); + let tab_location_enter = tab_location.clone(); + let tab_location_leave = tab_location.clone(); + let is_dnd_hovered = + self.dnd_hovered.as_ref().map(|(l, _)| l) == Some(&tab_location); + cosmic::widget::container( + DndDestination::for_data(button_row, move |data, action| { + if let Some(mut data) = data { + if action == DndAction::Copy { + Message::Drop(Some((tab_location.clone(), data))) + } else if action == DndAction::Move { + data.kind = ClipboardKind::Cut; + Message::Drop(Some((tab_location.clone(), data))) + } else { + log::warn!("unsupported action: {:?}", action); + Message::Drop(None) + } + } else { + log::warn!("No data for drop."); + Message::Drop(None) + } + }) + .on_enter(move |_, _, _| Message::DndEnter(tab_location_enter.clone())) + .on_leave(move || Message::DndLeave(tab_location_leave.clone())), + ) + // todo refactor into the dnd destination wrapper + .style(if is_dnd_hovered { + theme::Container::custom(|t| { + let mut a = cosmic::iced_style::container::StyleSheet::appearance( + t, + &theme::Container::default(), + ); + let t = t.cosmic(); + // todo use theme drop target color + let mut bg = t.accent_color(); + bg.alpha = 0.2; + a.background = Some(Color::from(bg).into()); + a.border = Border { + color: t.accent_color().into(), + width: 1.0, + radius: t.radius_s().into(), + }; + a + }) + } else { + theme::Container::default() + }) + .into() + } else { + button_row.into() + }; + + if item.selected || !drag_items.is_empty() { + let dnd_row = if !item.selected { + Element::from(vertical_space(Length::Fixed(row_height as f32))) + } else if condensed { + widget::row::with_children(vec![ + widget::icon::icon(item.icon_handle_list_condensed.clone()) + .content_fit(ContentFit::Contain) + .size(icon_size) + .into(), + widget::column::with_children(vec![ + widget::text(item.name.clone()).into(), + //TODO: translate? + widget::text(format!("{} - {}", modified_text, size_text)).into(), + ]) + .into(), + ]) + .align_items(Alignment::Center) + .spacing(space_xxs) + .into() + } else { + widget::row::with_children(vec![ + widget::icon::icon(item.icon_handle_list.clone()) + .content_fit(ContentFit::Contain) + .size(icon_size) + .into(), + widget::text(item.name.clone()).width(Length::Fill).into(), + widget::text(modified_text) + .width(Length::Fixed(modified_width)) + .into(), + widget::text(size_text) + .width(Length::Fixed(size_width)) + .into(), + ]) + .align_items(Alignment::Center) + .spacing(space_xxs) + .into() + }; + if item.selected { + drag_items.push( + widget::container(button(dnd_row)) + .width(Length::Shrink) + .into(), + ); + } else { + drag_items.push(dnd_row); + } + } + count += 1; y += row_height; + children.push(button_row); } if count == 0 { - return self.empty_view(hidden > 0); + return (None, self.empty_view(hidden > 0)); } } + //TODO: HACK If we don't reach the bottom of the view, go ahead and add a spacer to do that + { + let spacer_height = size.height as i32 - y as i32; + if spacer_height > 0 { + children.push( + widget::container(vertical_space(Length::Fixed(spacer_height as f32))).into(), + ); + } + } + let drag_col = (!drag_items.is_empty()).then(|| { + Element::from(widget::column::with_children(drag_items)) + .map(|m| cosmic::app::Message::App(crate::app::Message::TabMessage(None, m))) + }); - widget::scrollable(widget::column::with_children(children).padding([0, space_m])) - .id(self.scrollable_id.clone()) - .on_scroll(Message::Scroll) - .width(Length::Fill) - .into() + ( + drag_col, + mouse_area::MouseArea::new( + widget::column::with_children(children).padding([0, space_m]), + ) + .with_id(Id::new("list-view")) + .on_drag(Message::Drag) + .on_drag_end(|_| Message::DragEnd(None)) + .show_drag_rect(true) + .on_release(|_| Message::ClickRelease(None)) + .into(), + ) } pub fn view(&self, key_binds: &HashMap) -> Element { let location_view = self.location_view(); - let item_view = match self.view { + let (drag_list, mut item_view) = match self.view { View::Grid => self.grid_view(), View::List => self.list_view(), }; - let mut mouse_area = - mouse_area::MouseArea::new(widget::container(item_view).height(Length::Fill)) - .on_press(move |_point_opt| Message::Click(None)) - .on_back_press(move |_point_opt| Message::GoPrevious) - .on_forward_press(move |_point_opt| Message::GoNext) - .on_resize(Message::Resize); + item_view = widget::container(item_view).width(Length::Fill).into(); + let files = self + .items_opt + .as_ref() + .map(|items| { + items + .iter() + .filter(|item| item.selected) + .filter_map(|item| item.path_opt.clone()) + .collect::>() + }) + .unwrap_or_default(); + let item_view = DndSource::<_, cosmic::app::Message, ClipboardCopy>::with_id( + item_view, + Id::new("tab-view"), + ); + let item_view = if let Some(drag_list) = drag_list { + let drag_list = ArcElementWrapper(Arc::new(Mutex::new(drag_list))); + item_view + .drag_content(move || { + ClipboardCopy::new(crate::clipboard::ClipboardKind::Copy, &files) + }) + .drag_icon(move || { + let state: tree::State = + Widget::, _, _>::state(&drag_list); + (drag_list.clone().into(), state) + }) + } else { + item_view + }; + let tab_location = self.location.clone(); + let mut mouse_area = mouse_area::MouseArea::new(item_view) + .on_press(move |_point_opt| Message::Click(None)) + .on_release(|_| Message::ClickRelease(None)) + .on_back_press(move |_point_opt| Message::GoPrevious) + .on_forward_press(move |_point_opt| Message::GoNext) + .on_resize(Message::Resize); + if self.context_menu.is_some() { mouse_area = mouse_area.on_right_press(move |_point_opt| Message::ContextMenu(None)); } else { mouse_area = mouse_area.on_right_press(move |point_opt| Message::ContextMenu(point_opt)); } + let mut popover = widget::popover(mouse_area); + if let Some(point) = self.context_menu { popover = popover .popup(menu::context_menu(&self, &key_binds)) .position(widget::popover::Position::Point(point)); } - widget::container(widget::column::with_children(vec![ + let scrollable = widget::scrollable(popover) + .id(self.scrollable_id.clone()) + .on_scroll(Message::Scroll) + .width(Length::Fill) + .height(Length::Fill); + let mut tab_view = widget::container(widget::column::with_children(vec![ location_view, - popover.into(), + scrollable.into(), ])) .height(Length::Fill) - .width(Length::Fill) - .into() + .width(Length::Fill); + + if self.dnd_hovered.as_ref().map(|(l, _)| l) == Some(&tab_location) { + tab_view = tab_view.style(cosmic::theme::Container::custom(|t| { + let mut a = cosmic::iced_style::container::StyleSheet::appearance( + t, + &cosmic::theme::Container::default(), + ); + let c = t.cosmic(); + a.border = cosmic::iced_core::Border { + color: (c.accent_color()).into(), + width: 1., + radius: c.radius_0().into(), + }; + a + })); + } + + let tab_location_2 = self.location.clone(); + let tab_location_3 = self.location.clone(); + let dnd_dest = DndDestination::for_data(tab_view, move |data, action| { + if let Some(mut data) = data { + if action == DndAction::Copy { + Message::Drop(Some((tab_location.clone(), data))) + } else if action == DndAction::Move { + data.kind = ClipboardKind::Cut; + Message::Drop(Some((tab_location.clone(), data))) + } else { + log::warn!("unsupported action: {:?}", action); + Message::Drop(None) + } + } else { + Message::Drop(None) + } + }) + .on_enter(move |_, _, _| Message::DndEnter(tab_location_2.clone())) + .on_leave(move || Message::DndLeave(tab_location_3.clone())); + + dnd_dest.into() } pub fn subscription(&self) -> Subscription { @@ -1888,9 +2329,9 @@ impl Tab { }; //TODO: HACK to ensure positions are up to date since subscription runs before view - let _ = match self.view { - View::Grid => self.grid_view(), - View::List => self.list_view(), + match self.view { + View::Grid => _ = self.grid_view(), + View::List => _ = self.list_view(), }; for item in items.iter() { @@ -2259,3 +2700,144 @@ mod tests { Ok(()) } } + +#[derive(Clone)] +pub struct ArcElementWrapper(pub Arc>>); + +impl Widget for ArcElementWrapper { + fn size(&self) -> Size { + self.0.lock().unwrap().as_widget().size() + } + + fn size_hint(&self) -> Size { + self.0.lock().unwrap().as_widget().size_hint() + } + + fn layout( + &self, + tree: &mut tree::Tree, + renderer: &cosmic::Renderer, + limits: &cosmic::iced_core::layout::Limits, + ) -> cosmic::iced_core::layout::Node { + self.0 + .lock() + .unwrap() + .as_widget_mut() + .layout(tree, renderer, limits) + } + + fn draw( + &self, + tree: &tree::Tree, + renderer: &mut cosmic::Renderer, + theme: &cosmic::Theme, + style: &cosmic::iced_core::renderer::Style, + layout: cosmic::iced_core::Layout<'_>, + cursor: cosmic::iced_core::mouse::Cursor, + viewport: &Rectangle, + ) { + self.0 + .lock() + .unwrap() + .as_widget() + .draw(tree, renderer, theme, style, layout, cursor, viewport) + } + + fn tag(&self) -> tree::Tag { + self.0.lock().unwrap().as_widget().tag() + } + + fn state(&self) -> tree::State { + self.0.lock().unwrap().as_widget().state() + } + + fn children(&self) -> Vec { + self.0.lock().unwrap().as_widget().children() + } + + fn diff(&mut self, tree: &mut tree::Tree) { + self.0.lock().unwrap().as_widget_mut().diff(tree) + } + + fn operate( + &self, + state: &mut tree::Tree, + layout: cosmic::iced_core::Layout<'_>, + renderer: &cosmic::Renderer, + operation: &mut dyn widget::Operation>, + ) { + self.0 + .lock() + .unwrap() + .as_widget() + .operate(state, layout, renderer, operation) + } + + fn on_event( + &mut self, + _state: &mut tree::Tree, + _event: cosmic::iced::Event, + _layout: cosmic::iced_core::Layout<'_>, + _cursor: cosmic::iced_core::mouse::Cursor, + _renderer: &cosmic::Renderer, + _clipboard: &mut dyn cosmic::iced_core::Clipboard, + _shell: &mut cosmic::iced_core::Shell<'_, M>, + _viewport: &Rectangle, + ) -> cosmic::iced_core::event::Status { + self.0.lock().unwrap().as_widget_mut().on_event( + _state, _event, _layout, _cursor, _renderer, _clipboard, _shell, _viewport, + ) + } + + fn mouse_interaction( + &self, + _state: &tree::Tree, + _layout: cosmic::iced_core::Layout<'_>, + _cursor: cosmic::iced_core::mouse::Cursor, + _viewport: &Rectangle, + _renderer: &cosmic::Renderer, + ) -> cosmic::iced_core::mouse::Interaction { + self.0 + .lock() + .unwrap() + .as_widget() + .mouse_interaction(_state, _layout, _cursor, _viewport, _renderer) + } + + fn overlay<'a>( + &'a mut self, + _state: &'a mut tree::Tree, + _layout: cosmic::iced_core::Layout<'_>, + _renderer: &cosmic::Renderer, + ) -> Option> { + // TODO + None + } + + fn id(&self) -> Option { + self.0.lock().unwrap().as_widget().id() + } + + fn set_id(&mut self, _id: Id) { + self.0.lock().unwrap().as_widget_mut().set_id(_id) + } + + fn drag_destinations( + &self, + _state: &tree::Tree, + _layout: cosmic::iced_core::Layout<'_>, + _dnd_rectangles: &mut cosmic::iced_core::clipboard::DndDestinationRectangles, + ) { + self.0 + .lock() + .unwrap() + .as_widget() + .drag_destinations(_state, _layout, _dnd_rectangles) + } +} + +impl From> for Element<'static, Message> { + fn from(wrapper: ArcElementWrapper) -> Self { + Element::new(wrapper) + } +}