From 1f3c70279a628b79549b6d271d72518be13ff7ad Mon Sep 17 00:00:00 2001 From: Chris Cochrun Date: Tue, 23 Sep 2025 10:41:08 -0500 Subject: [PATCH] drag and drop of service_items works --- Cargo.lock | 1185 +++++++++++++++++++++++++--- Cargo.toml | 1 + src/main.rs | 18 +- src/ui/widgets/draggable/column.rs | 865 ++++++++++++++++++++ src/ui/widgets/draggable/mod.rs | 42 + src/ui/widgets/draggable/row.rs | 1071 +++++++++++++++++++++++++ src/ui/widgets/mod.rs | 1 + 7 files changed, 3076 insertions(+), 107 deletions(-) create mode 100644 src/ui/widgets/draggable/column.rs create mode 100644 src/ui/widgets/draggable/mod.rs create mode 100644 src/ui/widgets/draggable/row.rs diff --git a/Cargo.lock b/Cargo.lock index d19e19c..dd7d2bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -95,7 +95,7 @@ dependencies = [ "accesskit_unix", "accesskit_windows", "raw-window-handle", - "winit", + "winit 0.30.5", ] [[package]] @@ -113,6 +113,17 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.12" @@ -332,13 +343,22 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" +[[package]] +name = "ash" +version = "0.37.3+1.3.251" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" +dependencies = [ + "libloading 0.7.4", +] + [[package]] name = "ash" version = "0.38.0+1.3.281" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" dependencies = [ - "libloading", + "libloading 0.8.8", ] [[package]] @@ -427,6 +447,17 @@ dependencies = [ "slab", ] +[[package]] +name = "async-fs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" +dependencies = [ + "async-lock 3.4.1", + "blocking", + "futures-lite 2.6.1", +] + [[package]] name = "async-io" version = "1.13.0" @@ -755,15 +786,30 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec 0.6.3", +] + [[package]] name = "bit-set" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0481a0e032742109b1133a095184ee93d88f3dc9e0d28a5d033dc77a073f44f" dependencies = [ - "bit-vec", + "bit-vec 0.7.0", ] +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bit-vec" version = "0.7.0" @@ -1037,7 +1083,7 @@ checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", - "libloading", + "libloading 0.8.8", ] [[package]] @@ -1100,6 +1146,26 @@ dependencies = [ "objc_id", ] +[[package]] +name = "clipboard_macos" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7f4aaa047ba3c3630b080bb9860894732ff23e2aee290a418909aa6d5df38f" +dependencies = [ + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "clipboard_wayland" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003f886bc4e2987729d10c1db3424e7f80809f3fc22dbc16c685738887cb37b8" +dependencies = [ + "smithay-clipboard 0.7.2", +] + [[package]] name = "clipboard_wayland" version = "0.2.2" @@ -1107,7 +1173,17 @@ source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2#6b9f dependencies = [ "dnd", "mime 0.1.0", - "smithay-clipboard", + "smithay-clipboard 0.8.0", +] + +[[package]] +name = "clipboard_x11" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4274ea815e013e0f9f04a2633423e14194e408a0576c943ce3d14ca56c50031c" +dependencies = [ + "thiserror 1.0.69", + "x11rb", ] [[package]] @@ -1128,8 +1204,8 @@ dependencies = [ "bitflags 1.3.2", "block", "cocoa-foundation", - "core-foundation", - "core-graphics", + "core-foundation 0.9.4", + "core-graphics 0.23.2", "foreign-types", "libc", "objc", @@ -1143,8 +1219,8 @@ checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" dependencies = [ "bitflags 1.3.2", "block", - "core-foundation", - "core-graphics-types", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", "libc", "objc", ] @@ -1243,6 +1319,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1256,8 +1342,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.9.4", + "core-foundation 0.10.1", + "core-graphics-types 0.2.0", "foreign-types", "libc", ] @@ -1269,7 +1368,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.9.4", + "core-foundation 0.10.1", "libc", ] @@ -1279,8 +1389,8 @@ version = "20.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" dependencies = [ - "core-foundation", - "core-graphics", + "core-foundation 0.9.4", + "core-graphics 0.23.2", "foreign-types", "libc", ] @@ -1331,7 +1441,7 @@ dependencies = [ "cosmic-settings-daemon", "dirs 6.0.0", "futures-util", - "iced_futures", + "iced_futures 0.14.0-dev", "known-folders", "notify", "ron 0.11.0", @@ -1399,6 +1509,29 @@ dependencies = [ "zbus 5.11.0", ] +[[package]] +name = "cosmic-text" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59fd57d82eb4bfe7ffa9b1cec0c05e2fd378155b47f255a67983cb4afe0e80c2" +dependencies = [ + "bitflags 2.9.4", + "fontdb 0.16.2", + "log", + "rangemap", + "rayon", + "rustc-hash 1.1.0", + "rustybuzz 0.14.1", + "self_cell", + "swash 0.1.19", + "sys-locale", + "ttf-parser 0.21.1", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + [[package]] name = "cosmic-text" version = "0.14.2" @@ -1413,7 +1546,7 @@ dependencies = [ "self_cell", "skrifa 0.36.0", "smol_str", - "swash", + "swash 0.2.5", "sys-locale", "unicode-bidi", "unicode-linebreak", @@ -1586,6 +1719,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" +[[package]] +name = "d3d12" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e3d747f100290a1ca24b752186f61f6637e1deffe3bf6320de6fcb29510a307" +dependencies = [ + "bitflags 2.9.4", + "libloading 0.8.8", + "winapi", +] + [[package]] name = "d3d12" version = "22.0.0" @@ -1593,10 +1737,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdbd1f579714e3c809ebd822c81ef148b1ceaeb3d535352afc73fd0c4c6a0017" dependencies = [ "bitflags 2.9.4", - "libloading", + "libloading 0.8.8", "winapi", ] +[[package]] +name = "dark-light" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a76fa97167fa740dcdbfe18e8895601e1bc36525f09b044e00916e717c03a3c" +dependencies = [ + "dconf_rs", + "detect-desktop-environment", + "dirs 4.0.0", + "objc", + "rust-ini", + "web-sys", + "winreg", + "zbus 4.4.0", +] + [[package]] name = "darling" version = "0.20.11" @@ -1644,6 +1804,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376" +[[package]] +name = "dconf_rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7046468a81e6a002061c01e6a7c83139daf91b11c30e66795b13217c2d885c8b" + [[package]] name = "der" version = "0.7.10" @@ -1699,6 +1865,12 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "detect-desktop-environment" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21d8ad60dd5b13a4ee6bd8fa2d5d88965c597c67bce32b5fc49c94f55cb50810" + [[package]] name = "diff" version = "0.1.13" @@ -1717,6 +1889,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys 0.3.7", +] + [[package]] name = "dirs" version = "5.0.1" @@ -1735,6 +1916,17 @@ dependencies = [ "dirs-sys 0.5.0", ] +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users 0.4.6", + "winapi", +] + [[package]] name = "dirs-sys" version = "0.4.1" @@ -1794,9 +1986,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading", + "libloading 0.8.8", ] +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + [[package]] name = "dnd" version = "0.1.0" @@ -1806,7 +2004,7 @@ dependencies = [ "mime 0.1.0", "raw-window-handle", "smithay-client-toolkit 0.19.2", - "smithay-clipboard", + "smithay-clipboard 0.8.0", ] [[package]] @@ -1835,6 +2033,20 @@ name = "dpi" version = "0.1.1" source = "git+https://github.com/pop-os/winit.git?tag=iced-xdg-surface-0.13#dbe91fcc363c101f1d6ed5301d49911b01a26f61" +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "dragking" +version = "0.1.0" +source = "git+https://github.com/airstrike/dragking#ed2713cbabc2861682274d96dcfeab865893c035" +dependencies = [ + "iced 0.13.1", +] + [[package]] name = "drm" version = "0.11.1" @@ -1843,7 +2055,20 @@ checksum = "a0f8a69e60d75ae7dab4ef26a59ca99f2a89d4c142089b537775ae0c198bdcde" dependencies = [ "bitflags 2.9.4", "bytemuck", - "drm-ffi", + "drm-ffi 0.7.1", + "drm-fourcc", + "rustix 0.38.44", +] + +[[package]] +name = "drm" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98888c4bbd601524c11a7ed63f814b8825f420514f78e96f752c437ae9cbb5d1" +dependencies = [ + "bitflags 2.9.4", + "bytemuck", + "drm-ffi 0.8.0", "drm-fourcc", "rustix 0.38.44", ] @@ -1854,7 +2079,17 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41334f8405792483e32ad05fbb9c5680ff4e84491883d2947a4757dc54cb2ac6" dependencies = [ - "drm-sys", + "drm-sys 0.6.1", + "rustix 0.38.44", +] + +[[package]] +name = "drm-ffi" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97c98727e48b7ccb4f4aea8cfe881e5b07f702d17b7875991881b41af7278d53" +dependencies = [ + "drm-sys 0.7.0", "rustix 0.38.44", ] @@ -1874,6 +2109,16 @@ dependencies = [ "linux-raw-sys 0.6.5", ] +[[package]] +name = "drm-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd39dde40b6e196c2e8763f23d119ddb1a8714534bf7d77fa97a65b0feda3986" +dependencies = [ + "libc", + "linux-raw-sys 0.6.5", +] + [[package]] name = "dwrote" version = "0.11.4" @@ -2254,8 +2499,8 @@ checksum = "2c7e611d49285d4c4b2e1727b72cf05353558885cc5252f93707b845dfcaf3d3" dependencies = [ "bitflags 2.9.4", "byteorder", - "core-foundation", - "core-graphics", + "core-foundation 0.9.4", + "core-graphics 0.23.2", "core-text", "dirs 6.0.0", "dwrote", @@ -2271,6 +2516,15 @@ dependencies = [ "yeslogic-fontconfig-sys", ] +[[package]] +name = "font-types" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3971f9a5ca983419cdc386941ba3b9e1feba01a0ab888adf78739feb2798492" +dependencies = [ + "bytemuck", +] + [[package]] name = "font-types" version = "0.9.0" @@ -2289,6 +2543,20 @@ dependencies = [ "roxmltree", ] +[[package]] +name = "fontdb" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser 0.20.0", +] + [[package]] name = "fontdb" version = "0.18.0" @@ -2687,6 +2955,15 @@ dependencies = [ "web-sys", ] +[[package]] +name = "glutin_wgl_sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8098adac955faa2d31079b65dc48841251f69efd3ac25477903fc424362ead" +dependencies = [ + "gl_generator", +] + [[package]] name = "glutin_wgl_sys" version = "0.6.1" @@ -2726,6 +3003,19 @@ dependencies = [ "bitflags 2.9.4", ] +[[package]] +name = "gpu-allocator" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f56f6318968d03c18e1bcf4857ff88c61157e9da8e47c5f29055d60e1228884" +dependencies = [ + "log", + "presser", + "thiserror 1.0.69", + "winapi", + "windows 0.52.0", +] + [[package]] name = "gpu-allocator" version = "0.26.0" @@ -2739,6 +3029,17 @@ dependencies = [ "windows 0.52.0", ] +[[package]] +name = "gpu-descriptor" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc11df1ace8e7e564511f53af41f3e42ddc95b56fd07b3f4445d2a6048bc682c" +dependencies = [ + "bitflags 2.9.4", + "gpu-descriptor-types 0.1.2", + "hashbrown 0.14.5", +] + [[package]] name = "gpu-descriptor" version = "0.3.2" @@ -2746,10 +3047,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ "bitflags 2.9.4", - "gpu-descriptor-types", + "gpu-descriptor-types 0.2.0", "hashbrown 0.15.5", ] +[[package]] +name = "gpu-descriptor-types" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bf0b36e6f090b7e1d8a4b49c0cb81c1f8376f72198c65dd3ad9ff3556b8b78c" +dependencies = [ + "bitflags 2.9.4", +] + [[package]] name = "gpu-descriptor-types" version = "0.2.0" @@ -2986,6 +3296,19 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash 0.8.12", + "allocator-api2", +] [[package]] name = "hashbrown" @@ -3016,7 +3339,7 @@ dependencies = [ "bitflags 2.9.4", "com", "libc", - "libloading", + "libloading 0.8.8", "thiserror 1.0.69", "widestring", "winapi", @@ -3185,6 +3508,20 @@ dependencies = [ "cc", ] +[[package]] +name = "iced" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88acfabc84ec077eaf9ede3457ffa3a104626d79022a9bf7f296093b1d60c73f" +dependencies = [ + "iced_core 0.13.2", + "iced_futures 0.13.2", + "iced_renderer 0.13.0", + "iced_widget 0.13.4", + "iced_winit 0.13.0", + "thiserror 1.0.69", +] + [[package]] name = "iced" version = "0.14.0-dev" @@ -3192,15 +3529,15 @@ source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d5479502 dependencies = [ "dnd", "iced_accessibility", - "iced_core", - "iced_futures", - "iced_renderer", - "iced_widget", - "iced_winit", + "iced_core 0.14.0-dev", + "iced_futures 0.14.0-dev", + "iced_renderer 0.14.0-dev", + "iced_widget 0.14.0-dev", + "iced_winit 0.14.0-dev", "image", "mime 0.1.0", "thiserror 1.0.69", - "window_clipboard", + "window_clipboard 0.4.1 (git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2)", ] [[package]] @@ -3212,6 +3549,26 @@ dependencies = [ "accesskit_winit", ] +[[package]] +name = "iced_core" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0013a238275494641bf8f1732a23a808196540dc67b22ff97099c044ae4c8a1c" +dependencies = [ + "bitflags 2.9.4", + "bytes", + "dark-light", + "glam", + "log", + "num-traits", + "once_cell", + "palette", + "rustc-hash 2.1.1", + "smol_str", + "thiserror 1.0.69", + "web-time", +] + [[package]] name = "iced_core" version = "0.14.0-dev" @@ -3233,7 +3590,21 @@ dependencies = [ "smol_str", "thiserror 1.0.69", "web-time", - "window_clipboard", + "window_clipboard 0.4.1 (git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2)", +] + +[[package]] +name = "iced_futures" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c04a6745ba2e80f32cf01e034fd00d853aa4f4cd8b91888099cb7aaee0d5d7c" +dependencies = [ + "futures", + "iced_core 0.13.2", + "log", + "rustc-hash 2.1.1", + "wasm-bindgen-futures", + "wasm-timer", ] [[package]] @@ -3242,7 +3613,7 @@ version = "0.14.0-dev" source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d547950204258dea83" dependencies = [ "futures", - "iced_core", + "iced_core 0.14.0-dev", "log", "rustc-hash 2.1.1", "tokio", @@ -3253,13 +3624,46 @@ dependencies = [ [[package]] name = "iced_glyphon" version = "0.6.0" -source = "git+https://github.com/pop-os/glyphon.git?tag=iced-0.14-dev#6ef9d12a20cfd0f7bdf38136a26ded9f7459ec8b" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41c3bb56f1820ca252bc1d0994ece33d233a55657c0c263ea7cb16895adbde82" dependencies = [ - "cosmic-text", + "cosmic-text 0.12.1", "etagere", "lru", "rustc-hash 2.1.1", - "wgpu", + "wgpu 0.19.4", +] + +[[package]] +name = "iced_glyphon" +version = "0.6.0" +source = "git+https://github.com/pop-os/glyphon.git?tag=iced-0.14-dev#6ef9d12a20cfd0f7bdf38136a26ded9f7459ec8b" +dependencies = [ + "cosmic-text 0.14.2", + "etagere", + "lru", + "rustc-hash 2.1.1", + "wgpu 22.1.0", +] + +[[package]] +name = "iced_graphics" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba25a18cfa6d5cc160aca7e1b34f73ccdff21680fa8702168c09739767b6c66f" +dependencies = [ + "bitflags 2.9.4", + "bytemuck", + "cosmic-text 0.12.1", + "half", + "iced_core 0.13.2", + "iced_futures 0.13.2", + "log", + "once_cell", + "raw-window-handle", + "rustc-hash 2.1.1", + "thiserror 1.0.69", + "unicode-segmentation", ] [[package]] @@ -3269,10 +3673,10 @@ source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d5479502 dependencies = [ "bitflags 2.9.4", "bytemuck", - "cosmic-text", + "cosmic-text 0.14.2", "half", - "iced_core", - "iced_futures", + "iced_core 0.14.0-dev", + "iced_futures 0.14.0-dev", "image", "kamadak-exif", "log", @@ -3284,18 +3688,44 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "iced_renderer" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73558208059f9e622df2bf434e044ee2f838ce75201a023cf0ca3e1244f46c2a" +dependencies = [ + "iced_graphics 0.13.0", + "iced_tiny_skia 0.13.0", + "iced_wgpu 0.13.5", + "log", + "thiserror 1.0.69", +] + [[package]] name = "iced_renderer" version = "0.14.0-dev" source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d547950204258dea83" dependencies = [ - "iced_graphics", - "iced_tiny_skia", - "iced_wgpu", + "iced_graphics 0.14.0-dev", + "iced_tiny_skia 0.14.0-dev", + "iced_wgpu 0.14.0-dev", "log", "thiserror 1.0.69", ] +[[package]] +name = "iced_runtime" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348b5b2c61c934d88ca3b0ed1ed913291e923d086a66fa288ce9669da9ef62b5" +dependencies = [ + "bytes", + "iced_core 0.13.2", + "iced_futures 0.13.2", + "raw-window-handle", + "thiserror 1.0.69", +] + [[package]] name = "iced_runtime" version = "0.14.0-dev" @@ -3304,11 +3734,27 @@ dependencies = [ "bytes", "dnd", "iced_accessibility", - "iced_core", - "iced_futures", + "iced_core 0.14.0-dev", + "iced_futures 0.14.0-dev", "raw-window-handle", "thiserror 1.0.69", - "window_clipboard", + "window_clipboard 0.4.1 (git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2)", +] + +[[package]] +name = "iced_tiny_skia" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c625d368284fcc43b0b36b176f76eff1abebe7959dd58bd8ce6897d641962a50" +dependencies = [ + "bytemuck", + "cosmic-text 0.12.1", + "iced_graphics 0.13.0", + "kurbo 0.10.4", + "log", + "rustc-hash 2.1.1", + "softbuffer 0.4.6", + "tiny-skia", ] [[package]] @@ -3317,13 +3763,13 @@ version = "0.14.0-dev" source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d547950204258dea83" dependencies = [ "bytemuck", - "cosmic-text", - "iced_graphics", + "cosmic-text 0.14.2", + "iced_graphics 0.14.0-dev", "kurbo 0.10.4", "log", "resvg 0.42.0", "rustc-hash 2.1.1", - "softbuffer", + "softbuffer 0.4.1", "tiny-skia", ] @@ -3344,6 +3790,26 @@ dependencies = [ "url", ] +[[package]] +name = "iced_wgpu" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15708887133671d2bcc6c1d01d1f176f43a64d6cdc3b2bf893396c3ee498295f" +dependencies = [ + "bitflags 2.9.4", + "bytemuck", + "futures", + "glam", + "guillotiere", + "iced_glyphon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "iced_graphics 0.13.0", + "log", + "once_cell", + "rustc-hash 2.1.1", + "thiserror 1.0.69", + "wgpu 0.19.4", +] + [[package]] name = "iced_wgpu" version = "0.14.0-dev" @@ -3356,8 +3822,8 @@ dependencies = [ "futures", "glam", "guillotiere", - "iced_glyphon", - "iced_graphics", + "iced_glyphon 0.6.0 (git+https://github.com/pop-os/glyphon.git?tag=iced-0.14-dev)", + "iced_graphics 0.14.0-dev", "log", "lyon", "once_cell", @@ -3371,10 +3837,25 @@ dependencies = [ "wayland-client", "wayland-protocols", "wayland-sys", - "wgpu", + "wgpu 22.1.0", "x11rb", ] +[[package]] +name = "iced_widget" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81429e1b950b0e4bca65be4c4278fea6678ea782030a411778f26fa9f8983e1d" +dependencies = [ + "iced_renderer 0.13.0", + "iced_runtime 0.13.2", + "num-traits", + "once_cell", + "rustc-hash 2.1.1", + "thiserror 1.0.69", + "unicode-segmentation", +] + [[package]] name = "iced_widget" version = "0.14.0-dev" @@ -3382,8 +3863,8 @@ source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d5479502 dependencies = [ "dnd", "iced_accessibility", - "iced_renderer", - "iced_runtime", + "iced_renderer 0.14.0-dev", + "iced_runtime 0.14.0-dev", "log", "num-traits", "once_cell", @@ -3391,7 +3872,27 @@ dependencies = [ "rustc-hash 2.1.1", "thiserror 1.0.69", "unicode-segmentation", - "window_clipboard", + "window_clipboard 0.4.1 (git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2)", +] + +[[package]] +name = "iced_winit" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f44cd4e1c594b6334f409282937bf972ba14d31fedf03c23aa595d982a2fda28" +dependencies = [ + "iced_futures 0.13.2", + "iced_graphics 0.13.0", + "iced_runtime 0.13.2", + "log", + "rustc-hash 2.1.1", + "thiserror 1.0.69", + "tracing", + "wasm-bindgen-futures", + "web-sys", + "winapi", + "window_clipboard 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winit 0.30.12", ] [[package]] @@ -3401,9 +3902,9 @@ source = "git+https://github.com/pop-os/libcosmic#0e797b244043ee86610113d5479502 dependencies = [ "dnd", "iced_accessibility", - "iced_futures", - "iced_graphics", - "iced_runtime", + "iced_futures 0.14.0-dev", + "iced_graphics 0.14.0-dev", + "iced_runtime 0.14.0-dev", "log", "rustc-hash 2.1.1", "rustix 0.38.44", @@ -3413,8 +3914,8 @@ dependencies = [ "wayland-client", "web-sys", "winapi", - "window_clipboard", - "winit", + "window_clipboard 0.4.1 (git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2)", + "winit 0.30.5", ] [[package]] @@ -3813,7 +4314,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading", + "libloading 0.8.8", "pkg-config", ] @@ -3914,16 +4415,16 @@ dependencies = [ "futures", "i18n-embed", "i18n-embed-fl", - "iced", + "iced 0.14.0-dev", "iced_accessibility", - "iced_core", - "iced_futures", - "iced_renderer", - "iced_runtime", - "iced_tiny_skia", - "iced_wgpu", - "iced_widget", - "iced_winit", + "iced_core 0.14.0-dev", + "iced_futures 0.14.0-dev", + "iced_renderer 0.14.0-dev", + "iced_runtime 0.14.0-dev", + "iced_tiny_skia 0.14.0-dev", + "iced_wgpu 0.14.0-dev", + "iced_widget 0.14.0-dev", + "iced_winit 0.14.0-dev", "image", "libc", "mime 0.3.17", @@ -3954,6 +4455,16 @@ dependencies = [ "cc", ] +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "libloading" version = "0.8.8" @@ -4080,6 +4591,7 @@ dependencies = [ "colors-transform", "crisp", "dirs 6.0.0", + "dragking", "gstreamer", "gstreamer-app", "iced_video_player", @@ -4237,6 +4749,21 @@ dependencies = [ "autocfg", ] +[[package]] +name = "metal" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43f73953f8cbe511f021b58f18c3ce1c3d1ae13fe953293e13345bf83217f25" +dependencies = [ + "bitflags 2.9.4", + "block", + "core-graphics-types 0.1.3", + "foreign-types", + "log", + "objc", + "paste", +] + [[package]] name = "metal" version = "0.29.0" @@ -4245,7 +4772,7 @@ checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" dependencies = [ "bitflags 2.9.4", "block", - "core-graphics-types", + "core-graphics-types 0.1.3", "foreign-types", "log", "objc", @@ -4287,7 +4814,7 @@ name = "mime" version = "0.1.0" source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2#6b9faab87bea9cebec6ae036906fd67fed254f5f" dependencies = [ - "smithay-clipboard", + "smithay-clipboard 0.8.0", ] [[package]] @@ -4371,6 +4898,26 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af" +[[package]] +name = "naga" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e3524642f53d9af419ab5e8dd29d3ba155708267667c2f3f06c88c9e130843" +dependencies = [ + "bit-set 0.5.3", + "bitflags 2.9.4", + "codespan-reporting", + "hexf-parse", + "indexmap 2.11.3", + "log", + "num-traits", + "rustc-hash 1.1.0", + "spirv", + "termcolor", + "thiserror 1.0.69", + "unicode-xid", +] + [[package]] name = "naga" version = "22.1.0" @@ -4378,7 +4925,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8bd5a652b6faf21496f2cfd88fc49989c8db0825d1f6746b1a71a6ede24a63ad" dependencies = [ "arrayvec", - "bit-set", + "bit-set 0.6.0", "bitflags 2.9.4", "cfg_aliases 0.1.1", "codespan-reporting", @@ -4470,6 +5017,19 @@ dependencies = [ "libc", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.9.4", + "cfg-if", + "cfg_aliases 0.2.1", + "libc", + "memoffset 0.9.1", +] + [[package]] name = "nix" version = "0.30.1" @@ -4665,6 +5225,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" dependencies = [ "malloc_buf", + "objc_exception", ] [[package]] @@ -4961,6 +5522,15 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + [[package]] name = "objc_id" version = "0.1.1" @@ -5015,6 +5585,16 @@ dependencies = [ "libredox", ] +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown 0.12.3", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -5705,6 +6285,16 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "read-fonts" +version = "0.22.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69aacb76b5c29acfb7f90155d39759a29496aebb49395830e928a9703d2eec2f" +dependencies = [ + "bytemuck", + "font-types 0.7.3", +] + [[package]] name = "read-fonts" version = "0.29.3" @@ -5712,7 +6302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04ca636dac446b5664bd16c069c00a9621806895b8bb02c2dc68542b23b8f25d" dependencies = [ "bytemuck", - "font-types", + "font-types 0.9.0", ] [[package]] @@ -5723,7 +6313,7 @@ checksum = "8941f8e9d5f8ad3aebea330d01ac68c0167600eb31a86ecd86e97be4d13b51f5" dependencies = [ "bytemuck", "core_maths", - "font-types", + "font-types 0.9.0", ] [[package]] @@ -5735,6 +6325,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.17" @@ -5985,6 +6584,16 @@ dependencies = [ "walkdir", ] +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rustc-demangle" version = "0.1.26" @@ -6066,6 +6675,7 @@ checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" dependencies = [ "bitflags 2.9.4", "bytemuck", + "libm", "smallvec", "ttf-parser 0.21.1", "unicode-bidi-mirroring 0.2.0", @@ -6384,6 +6994,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +[[package]] +name = "skrifa" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1c44ad1f6c5bdd4eefed8326711b7dbda9ea45dfd36068c427d332aa382cbe" +dependencies = [ + "bytemuck", + "read-fonts 0.22.7", +] + [[package]] name = "skrifa" version = "0.31.3" @@ -6483,6 +7103,17 @@ dependencies = [ "xkeysym", ] +[[package]] +name = "smithay-clipboard" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc8216eec463674a0e90f29e0ae41a4db573ec5b56b1c6c1c71615d249b6d846" +dependencies = [ + "libc", + "smithay-client-toolkit 0.19.2", + "wayland-backend", +] + [[package]] name = "smithay-clipboard" version = "0.8.0" @@ -6532,8 +7163,8 @@ dependencies = [ "bytemuck", "cfg_aliases 0.2.1", "cocoa", - "core-graphics", - "drm", + "core-graphics 0.23.2", + "drm 0.11.1", "fastrand 2.3.0", "foreign-types", "js-sys", @@ -6553,6 +7184,38 @@ dependencies = [ "x11rb", ] +[[package]] +name = "softbuffer" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" +dependencies = [ + "as-raw-xcb-connection", + "bytemuck", + "cfg_aliases 0.2.1", + "core-graphics 0.24.0", + "drm 0.12.0", + "fastrand 2.3.0", + "foreign-types", + "js-sys", + "log", + "memmap2", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall 0.5.17", + "rustix 0.38.44", + "tiny-xlib", + "wasm-bindgen", + "wayland-backend", + "wayland-client", + "wayland-sys", + "web-sys", + "windows-sys 0.59.0", + "x11rb", +] + [[package]] name = "spin" version = "0.9.8" @@ -6869,6 +7532,17 @@ dependencies = [ "siphasher", ] +[[package]] +name = "swash" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd59f3f359ddd2c95af4758c18270eddd9c730dde98598023cdabff472c2ca2" +dependencies = [ + "skrifa 0.22.3", + "yazi 0.1.6", + "zeno 0.2.3", +] + [[package]] name = "swash" version = "0.2.5" @@ -6876,8 +7550,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f745de914febc7c9ab4388dfaf94bbc87e69f57bb41133a9b0c84d4be49856f3" dependencies = [ "skrifa 0.31.3", - "yazi", - "zeno", + "yazi 0.2.1", + "zeno 0.3.3", ] [[package]] @@ -7343,7 +8017,7 @@ checksum = "0324504befd01cab6e0c994f34b2ffa257849ee019d3fb3b64fb2c858887d89e" dependencies = [ "as-raw-xcb-connection", "ctor-lite", - "libloading", + "libloading 0.8.8", "pkg-config", "tracing", ] @@ -7563,6 +8237,12 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "ttf-parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + [[package]] name = "ttf-parser" version = "0.21.1" @@ -8156,6 +8836,31 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" +[[package]] +name = "wgpu" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbd7311dbd2abcfebaabf1841a2824ed7c8be443a0f29166e5d3c6a53a762c01" +dependencies = [ + "arrayvec", + "cfg-if", + "cfg_aliases 0.1.1", + "js-sys", + "log", + "naga 0.19.2", + "parking_lot 0.11.2", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core 0.19.4", + "wgpu-hal 0.19.5", + "wgpu-types 0.19.2", +] + [[package]] name = "wgpu" version = "22.1.0" @@ -8167,7 +8872,7 @@ dependencies = [ "document-features", "js-sys", "log", - "naga", + "naga 22.1.0", "parking_lot 0.12.4", "profiling", "raw-window-handle", @@ -8176,9 +8881,35 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "wgpu-core", - "wgpu-hal", - "wgpu-types", + "wgpu-core 22.1.0", + "wgpu-hal 22.0.0", + "wgpu-types 22.0.0", +] + +[[package]] +name = "wgpu-core" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b94525fc99ba9e5c9a9e24764f2bc29bad0911a7446c12f446a8277369bf3a" +dependencies = [ + "arrayvec", + "bit-vec 0.6.3", + "bitflags 2.9.4", + "cfg_aliases 0.1.1", + "codespan-reporting", + "indexmap 2.11.3", + "log", + "naga 0.19.2", + "once_cell", + "parking_lot 0.11.2", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "web-sys", + "wgpu-hal 0.19.5", + "wgpu-types 0.19.2", ] [[package]] @@ -8188,13 +8919,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0348c840d1051b8e86c3bcd31206080c5e71e5933dabd79be1ce732b0b2f089a" dependencies = [ "arrayvec", - "bit-vec", + "bit-vec 0.7.0", "bitflags 2.9.4", "cfg_aliases 0.1.1", "document-features", "indexmap 2.11.3", "log", - "naga", + "naga 22.1.0", "once_cell", "parking_lot 0.12.4", "profiling", @@ -8202,8 +8933,53 @@ dependencies = [ "rustc-hash 1.1.0", "smallvec", "thiserror 1.0.69", - "wgpu-hal", - "wgpu-types", + "wgpu-hal 22.0.0", + "wgpu-types 22.0.0", +] + +[[package]] +name = "wgpu-hal" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfabcfc55fd86611a855816326b2d54c3b2fd7972c27ce414291562650552703" +dependencies = [ + "android_system_properties", + "arrayvec", + "ash 0.37.3+1.3.251", + "bit-set 0.5.3", + "bitflags 2.9.4", + "block", + "cfg_aliases 0.1.1", + "core-graphics-types 0.1.3", + "d3d12 0.19.0", + "glow", + "glutin_wgl_sys 0.5.0", + "gpu-alloc", + "gpu-allocator 0.25.0", + "gpu-descriptor 0.2.4", + "hassle-rs", + "js-sys", + "khronos-egl", + "libc", + "libloading 0.8.8", + "log", + "metal 0.27.0", + "naga 0.19.2", + "ndk-sys 0.5.0+25.2.9519653", + "objc", + "once_cell", + "parking_lot 0.11.2", + "profiling", + "range-alloc", + "raw-window-handle", + "renderdoc-sys", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 1.0.69", + "wasm-bindgen", + "web-sys", + "wgpu-types 0.19.2", + "winapi", ] [[package]] @@ -8214,26 +8990,26 @@ checksum = "f6bbf4b4de8b2a83c0401d9e5ae0080a2792055f25859a02bf9be97952bbed4f" dependencies = [ "android_system_properties", "arrayvec", - "ash", - "bit-set", + "ash 0.38.0+1.3.281", + "bit-set 0.6.0", "bitflags 2.9.4", "block", "cfg_aliases 0.1.1", - "core-graphics-types", - "d3d12", + "core-graphics-types 0.1.3", + "d3d12 22.0.0", "glow", - "glutin_wgl_sys", + "glutin_wgl_sys 0.6.1", "gpu-alloc", - "gpu-allocator", - "gpu-descriptor", + "gpu-allocator 0.26.0", + "gpu-descriptor 0.3.2", "hassle-rs", "js-sys", "khronos-egl", "libc", - "libloading", + "libloading 0.8.8", "log", - "metal", - "naga", + "metal 0.29.0", + "naga 22.1.0", "ndk-sys 0.5.0+25.2.9519653", "objc", "once_cell", @@ -8247,10 +9023,21 @@ dependencies = [ "thiserror 1.0.69", "wasm-bindgen", "web-sys", - "wgpu-types", + "wgpu-types 22.0.0", "winapi", ] +[[package]] +name = "wgpu-types" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b671ff9fb03f78b46ff176494ee1ebe7d603393f42664be55b64dc8d53969805" +dependencies = [ + "bitflags 2.9.4", + "js-sys", + "web-sys", +] + [[package]] name = "wgpu-types" version = "22.0.0" @@ -8309,15 +9096,29 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "window_clipboard" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d692d46038c433f9daee7ad8757e002a4248c20b0a3fbc991d99521d3bcb6d" +dependencies = [ + "clipboard-win", + "clipboard_macos 0.1.1", + "clipboard_wayland 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "clipboard_x11 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "raw-window-handle", + "thiserror 1.0.69", +] + [[package]] name = "window_clipboard" version = "0.4.1" source = "git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2#6b9faab87bea9cebec6ae036906fd67fed254f5f" dependencies = [ "clipboard-win", - "clipboard_macos", - "clipboard_wayland", - "clipboard_x11", + "clipboard_macos 0.1.0", + "clipboard_wayland 0.2.2 (git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2)", + "clipboard_x11 0.4.2 (git+https://github.com/pop-os/window_clipboard.git?tag=pop-0.13-2)", "dnd", "mime 0.1.0", "raw-window-handle", @@ -8763,7 +9564,7 @@ name = "winit" version = "0.30.5" source = "git+https://github.com/pop-os/winit.git?tag=iced-xdg-surface-0.13#dbe91fcc363c101f1d6ed5301d49911b01a26f61" dependencies = [ - "ahash", + "ahash 0.8.12", "android-activity", "atomic-waker", "bitflags 2.9.4", @@ -8772,10 +9573,10 @@ dependencies = [ "calloop 0.13.0", "cfg_aliases 0.2.1", "concurrent-queue", - "core-foundation", - "core-graphics", + "core-foundation 0.9.4", + "core-graphics 0.23.2", "cursor-icon", - "dpi", + "dpi 0.1.1", "js-sys", "libc", "memmap2", @@ -8809,6 +9610,58 @@ dependencies = [ "xkbcommon-dl", ] +[[package]] +name = "winit" +version = "0.30.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" +dependencies = [ + "ahash 0.8.12", + "android-activity", + "atomic-waker", + "bitflags 2.9.4", + "block2 0.5.1", + "bytemuck", + "calloop 0.13.0", + "cfg_aliases 0.2.1", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics 0.23.2", + "cursor-icon", + "dpi 0.1.2", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2 0.5.2", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "sctk-adwaita", + "smithay-client-toolkit 0.19.2", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + [[package]] name = "winnow" version = "0.5.40" @@ -8827,6 +9680,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + [[package]] name = "wio" version = "0.2.2" @@ -8868,7 +9730,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "libloading", + "libloading 0.8.8", "once_cell", "rustix 1.1.2", "x11rb-protocol", @@ -8971,6 +9833,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yazi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1" + [[package]] name = "yazi" version = "0.2.1" @@ -9048,6 +9916,44 @@ dependencies = [ "zvariant 3.15.2", ] +[[package]] +name = "zbus" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +dependencies = [ + "async-broadcast 0.7.2", + "async-executor", + "async-fs", + "async-io 2.6.0", + "async-lock 3.4.1", + "async-process 2.5.0", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener 5.4.1", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix 0.29.0", + "ordered-stream", + "rand 0.8.5", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros 4.4.0", + "zbus_names 3.0.0", + "zvariant 4.2.0", +] + [[package]] name = "zbus" version = "5.11.0" @@ -9096,6 +10002,19 @@ dependencies = [ "zvariant_utils 1.0.1", ] +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.106", + "zvariant_utils 2.1.0", +] + [[package]] name = "zbus_macros" version = "5.11.0" @@ -9122,6 +10041,17 @@ dependencies = [ "zvariant 3.15.2", ] +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant 4.2.0", +] + [[package]] name = "zbus_names" version = "4.2.0" @@ -9134,6 +10064,12 @@ dependencies = [ "zvariant 5.7.0", ] +[[package]] +name = "zeno" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" + [[package]] name = "zeno" version = "0.3.3" @@ -9258,6 +10194,19 @@ dependencies = [ "zvariant_derive 3.15.2", ] +[[package]] +name = "zvariant" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive 4.2.0", +] + [[package]] name = "zvariant" version = "5.7.0" @@ -9286,6 +10235,19 @@ dependencies = [ "zvariant_utils 1.0.1", ] +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.106", + "zvariant_utils 2.1.0", +] + [[package]] name = "zvariant_derive" version = "5.7.0" @@ -9310,6 +10272,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "zvariant_utils" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "zvariant_utils" version = "3.2.1" diff --git a/Cargo.toml b/Cargo.toml index 1ae39a2..916c8b5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ resvg = "0.45.1" image = "0.25.8" rapidhash = "4.0.0" rapidfuzz = "0.5.0" +dragking = { git = "https://github.com/airstrike/dragking" } # femtovg = { version = "0.16.0", features = ["wgpu"] } # wgpu = "26.0.1" # mupdf = "0.5.0" diff --git a/src/main.rs b/src/main.rs index 91122d7..7a7a63e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -150,6 +150,7 @@ enum Message { AddServiceItem(usize, ServiceItem), AddServiceItemDrop(usize), AppendServiceItem(ServiceItem), + ReorderService(usize, usize), SearchFocus, Search(String), CloseSearch, @@ -1061,6 +1062,12 @@ impl cosmic::Application for App { self.presenter.update_items(self.service.clone()); Task::none() } + Message::ReorderService(index, target_index) => { + let item = self.service.remove(index); + self.service.insert(target_index, item); + self.presenter.update_items(self.service.clone()); + Task::none() + } Message::Search(query) => { self.search_query = query.clone(); self.search(query) @@ -1457,6 +1464,8 @@ where index, ))) .on_exit(Message::HoveredServiceItem(None)) + .on_double_press(Message::None) + .on_drag(Message::None) .on_press(Message::ChangeServiceItem(index)); // let button = button::standard(item.title.clone()) // .leading_icon({ @@ -1522,7 +1531,14 @@ where .center() .width(Length::Fill), iced::widget::horizontal_rule(1), - column(list).spacing(10), + ui::widgets::draggable::column::column(list).spacing(10).on_drag(|event| { + match event { + ui::widgets::draggable::DragEvent::Picked { index } => Message::None, + ui::widgets::draggable::DragEvent::Dropped { index, target_index, drop_position } => Message::ReorderService(index, target_index), + ui::widgets::draggable::DragEvent::Canceled { index } => Message::None, + } + }), + // column(list).spacing(10), // service::service(&self.service), dnd_destination( vertical_space().width(Length::Fill), diff --git a/src/ui/widgets/draggable/column.rs b/src/ui/widgets/draggable/column.rs new file mode 100644 index 0000000..be181d6 --- /dev/null +++ b/src/ui/widgets/draggable/column.rs @@ -0,0 +1,865 @@ +//! Distribute draggable content vertically. +// This widget is a modification of the original `Column` widget from [`iced`] +// +// [`iced`]: https://github.com/iced-rs/iced +// +// Copyright 2019 Héctor Ramón, Iced contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +use cosmic::iced::advanced::layout::{self, Layout}; +use cosmic::iced::advanced::widget::{tree, Operation, Tree, Widget}; +use cosmic::iced::advanced::{overlay, renderer, Clipboard, Shell}; +use cosmic::iced::alignment::{self, Alignment}; +use cosmic::iced::event::{self, Event}; +use cosmic::iced::{self, mouse, Transformation}; +use cosmic::iced::{ + Background, Border, Color, Element, Length, Padding, Pixels, + Point, Rectangle, Size, Vector, +}; +use cosmic::Theme; + +use super::{Action, DragEvent, DropPosition}; + +pub fn column<'a, Message, Theme, Renderer>( + children: impl IntoIterator< + Item = Element<'a, Message, Theme, Renderer>, + >, +) -> Column<'a, Message, Theme, Renderer> +where + Renderer: renderer::Renderer, + Theme: Catalog, +{ + Column::with_children(children) +} + +const DRAG_DEADBAND_DISTANCE: f32 = 5.0; + +/// A container that distributes its contents vertically. +/// +/// # Example +/// ```no_run +/// # mod iced { pub mod widget { pub use iced_widget::*; } } +/// # pub type State = (); +/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>; +/// use iced::widget::{button, column}; +/// +/// #[derive(Debug, Clone)] +/// enum Message { +/// // ... +/// } +/// +/// fn view(state: &State) -> Element<'_, Message> { +/// column![ +/// "I am on top!", +/// button("I am in the center!"), +/// "I am below.", +/// ].into() +/// } +/// ``` +#[allow(missing_debug_implementations)] +pub struct Column< + 'a, + Message, + Theme = cosmic::Theme, + Renderer = iced::Renderer, +> where + Theme: Catalog, +{ + spacing: f32, + padding: Padding, + width: Length, + height: Length, + max_width: f32, + align: Alignment, + clip: bool, + deadband_zone: f32, + children: Vec>, + on_drag: Option Message + 'a>>, + class: Theme::Class<'a>, +} + +impl<'a, Message, Theme, Renderer> + Column<'a, Message, Theme, Renderer> +where + Renderer: renderer::Renderer, + Theme: Catalog, +{ + /// Creates an empty [`Column`]. + pub fn new() -> Self { + Self::from_vec(Vec::new()) + } + + /// Creates a [`Column`] with the given capacity. + pub fn with_capacity(capacity: usize) -> Self { + Self::from_vec(Vec::with_capacity(capacity)) + } + + /// Creates a [`Column`] with the given elements. + pub fn with_children( + children: impl IntoIterator< + Item = Element<'a, Message, Theme, Renderer>, + >, + ) -> Self { + let iterator = children.into_iter(); + + Self::with_capacity(iterator.size_hint().0).extend(iterator) + } + + /// Creates a [`Column`] from an already allocated [`Vec`]. + /// + /// Keep in mind that the [`Column`] will not inspect the [`Vec`], which means + /// it won't automatically adapt to the sizing strategy of its contents. + /// + /// If any of the children have a [`Length::Fill`] strategy, you will need to + /// call [`Column::width`] or [`Column::height`] accordingly. + pub fn from_vec( + children: Vec>, + ) -> Self { + Self { + spacing: 0.0, + padding: Padding::ZERO, + width: Length::Shrink, + height: Length::Shrink, + max_width: f32::INFINITY, + align: Alignment::Start, + clip: false, + deadband_zone: DRAG_DEADBAND_DISTANCE, + children, + class: Theme::default(), + on_drag: None, + } + } + + /// Sets the vertical spacing _between_ elements. + /// + /// Custom margins per element do not exist in iced. You should use this + /// method instead! While less flexible, it helps you keep spacing between + /// elements consistent. + pub fn spacing(mut self, amount: impl Into) -> Self { + self.spacing = amount.into().0; + self + } + + /// Sets the [`Padding`] of the [`Column`]. + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + /// Sets the width of the [`Column`]. + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + /// Sets the height of the [`Column`]. + pub fn height(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } + + /// Sets the maximum width of the [`Column`]. + pub fn max_width(mut self, max_width: impl Into) -> Self { + self.max_width = max_width.into().0; + self + } + + /// Sets the horizontal alignment of the contents of the [`Column`] . + pub fn align_x( + mut self, + align: impl Into, + ) -> Self { + self.align = Alignment::from(align.into()); + self + } + + /// Sets whether the contents of the [`Column`] should be clipped on + /// overflow. + pub fn clip(mut self, clip: bool) -> Self { + self.clip = clip; + self + } + + /// Sets the drag deadband zone of the [`Column`]. + pub fn deadband_zone(mut self, deadband_zone: f32) -> Self { + self.deadband_zone = deadband_zone; + self + } + + /// Adds an element to the [`Column`]. + pub fn push( + mut self, + child: impl Into>, + ) -> Self { + let child = child.into(); + let child_size = child.as_widget().size_hint(); + + self.width = self.width.enclose(child_size.width); + self.height = self.height.enclose(child_size.height); + + self.children.push(child); + self + } + + /// Adds an element to the [`Column`], if `Some`. + pub fn push_maybe( + self, + child: Option< + impl Into>, + >, + ) -> Self { + if let Some(child) = child { + self.push(child) + } else { + self + } + } + + /// Sets the style of the [`Column`]. + #[must_use] + pub fn style( + mut self, + style: impl Fn(&Theme) -> Style + 'a, + ) -> Self + where + Theme::Class<'a>: From>, + { + self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); + self + } + + /// Sets the style class of the [`Column`]. + #[must_use] + pub fn class( + mut self, + class: impl Into>, + ) -> Self { + self.class = class.into(); + self + } + + /// Extends the [`Column`] with the given children. + pub fn extend( + self, + children: impl IntoIterator< + Item = Element<'a, Message, Theme, Renderer>, + >, + ) -> Self { + children.into_iter().fold(self, Self::push) + } + + /// The message produced by the [`Column`] when a child is dragged. + pub fn on_drag( + mut self, + on_reorder: impl Fn(DragEvent) -> Message + 'a, + ) -> Self { + self.on_drag = Some(Box::new(on_reorder)); + self + } + + // Computes the index and position where a dragged item should be dropped. + fn compute_target_index( + &self, + cursor_position: Point, + layout: Layout<'_>, + dragged_index: usize, + ) -> (usize, DropPosition) { + let cursor_y = cursor_position.y; + + for (i, child_layout) in layout.children().enumerate() { + let bounds = child_layout.bounds(); + let y = bounds.y; + let height = bounds.height; + + if cursor_y >= y && cursor_y <= y + height { + if i == dragged_index { + // Cursor is over the dragged item itself + return (i, DropPosition::Swap); + } + + let thickness = height / 4.0; + let top_threshold = y + thickness; + let bottom_threshold = y + height - thickness; + + if cursor_y < top_threshold { + // Near the top edge - insert above + return (i, DropPosition::Before); + } else if cursor_y > bottom_threshold { + // Near the bottom edge - insert below + return (i + 1, DropPosition::After); + } else { + // Middle area - swap + return (i, DropPosition::Swap); + } + } else if cursor_y < y { + // Cursor is above this child + return (i, DropPosition::Before); + } + } + + // Cursor is below all children + (self.children.len(), DropPosition::After) + } +} + +impl<'a, Message, Renderer> Default + for Column<'a, Message, Theme, Renderer> +where + Renderer: renderer::Renderer, + Theme: Catalog, +{ + fn default() -> Self { + Self::new() + } +} + +impl<'a, Message, Theme, Renderer: renderer::Renderer> + FromIterator> + for Column<'a, Message, Theme, Renderer> +where + Theme: Catalog, +{ + fn from_iter< + T: IntoIterator>, + >( + iter: T, + ) -> Self { + Self::with_children(iter) + } +} + +impl<'a, Message, Theme, Renderer> Widget + for Column<'a, Message, Theme, Renderer> +where + Renderer: renderer::Renderer, + Theme: Catalog, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(Action::Idle) + } + + fn children(&self) -> Vec { + self.children.iter().map(Tree::new).collect() + } + + fn diff(&mut self, tree: &mut Tree) { + tree.diff_children(&mut self.children); + } + + fn size(&self) -> Size { + Size { + width: self.width, + height: self.height, + } + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.max_width(self.max_width); + + layout::flex::resolve( + layout::flex::Axis::Vertical, + renderer, + &limits, + self.width, + self.height, + self.padding, + self.spacing, + self.align, + &self.children, + &mut tree.children, + ) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn Operation, + ) { + operation.container( + None, + layout.bounds(), + &mut |operation| { + self.children + .iter() + .zip(&mut tree.children) + .zip(layout.children()) + .for_each(|((child, state), layout)| { + child.as_widget().operate( + state, layout, renderer, operation, + ); + }); + }, + ); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) -> event::Status { + let mut event_status = event::Status::Ignored; + + let action = tree.state.downcast_mut::(); + + match event { + Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Left, + )) => { + if let Some(cursor_position) = + cursor.position_over(layout.bounds()) + { + for (index, child_layout) in + layout.children().enumerate() + { + if child_layout + .bounds() + .contains(cursor_position) + { + *action = Action::Picking { + index, + origin: cursor_position, + }; + event_status = event::Status::Captured; + break; + } + } + } + } + Event::Mouse(mouse::Event::CursorMoved { .. }) => { + match *action { + Action::Picking { index, origin } => { + if let Some(cursor_position) = + cursor.position() + { + if cursor_position.distance(origin) + > self.deadband_zone + { + // Start dragging + *action = Action::Dragging { + index, + origin, + last_cursor: cursor_position, + }; + if let Some(on_reorder) = + &self.on_drag + { + shell.publish(on_reorder( + DragEvent::Picked { index }, + )); + } + event_status = + event::Status::Captured; + } + } + } + Action::Dragging { origin, index, .. } => { + if let Some(cursor_position) = + cursor.position() + { + *action = Action::Dragging { + last_cursor: cursor_position, + origin, + index, + }; + event_status = event::Status::Captured; + } + } + _ => {} + } + } + Event::Mouse(mouse::Event::ButtonReleased( + mouse::Button::Left, + )) => { + match *action { + Action::Dragging { index, .. } => { + if let Some(cursor_position) = + cursor.position() + { + let bounds = layout.bounds(); + if bounds.contains(cursor_position) { + let (target_index, drop_position) = + self.compute_target_index( + cursor_position, + layout, + index, + ); + + if let Some(on_reorder) = + &self.on_drag + { + shell.publish(on_reorder( + DragEvent::Dropped { + index, + target_index, + drop_position, + }, + )); + event_status = + event::Status::Captured; + } + } else if let Some(on_reorder) = + &self.on_drag + { + shell.publish(on_reorder( + DragEvent::Canceled { index }, + )); + event_status = + event::Status::Captured; + } + } + *action = Action::Idle; + } + Action::Picking { .. } => { + // Did not move enough to start dragging + *action = Action::Idle; + } + _ => {} + } + } + _ => {} + } + + let child_status = self + .children + .iter_mut() + .zip(&mut tree.children) + .zip(layout.children()) + .map(|((child, state), layout)| { + child.as_widget_mut().on_event( + state, + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + viewport, + ) + }) + .fold(event::Status::Ignored, event::Status::merge); + + event::Status::merge(event_status, child_status) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + let action = tree.state.downcast_ref::(); + + if let Action::Dragging { .. } = *action { + return mouse::Interaction::Grabbing; + } + + self.children + .iter() + .zip(&tree.children) + .zip(layout.children()) + .map(|((child, state), layout)| { + child.as_widget().mouse_interaction( + state, layout, cursor, viewport, renderer, + ) + }) + .max() + .unwrap_or_default() + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + defaults: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + let action = tree.state.downcast_ref::(); + let style = theme.style(&self.class); + + match action { + Action::Dragging { + index, + last_cursor, + origin, + .. + } => { + let child_count = self.children.len(); + + // Determine the target index based on cursor position + let target_index = if cursor.position().is_some() { + let (target_index, _) = self + .compute_target_index( + *last_cursor, + layout, + *index, + ); + target_index.min(child_count - 1) + } else { + *index + }; + + // Store the width of the dragged item + let drag_bounds = + layout.children().nth(*index).unwrap().bounds(); + let drag_height = drag_bounds.height + self.spacing; + + // Draw all children except the one being dragged + let mut translations = 0.0; + for i in 0..child_count { + let child = &self.children[i]; + let state = &tree.children[i]; + let child_layout = + layout.children().nth(i).unwrap(); + + // Draw the dragged item separately + // TODO: Draw a shadow below the picked item to enhance the + // floating effect + if i == *index { + let scaling = + Transformation::scale(style.scale); + let translation = + *last_cursor - *origin * scaling; + renderer.with_translation( + translation, + |renderer| { + renderer.with_transformation( + scaling, + |renderer| { + renderer.with_layer( + child_layout.bounds(), + |renderer| { + child + .as_widget() + .draw( + state, + renderer, + theme, + defaults, + child_layout, + cursor, + viewport, + ); + }, + ); + }, + ); + }, + ); + } else { + let offset: i32 = + match target_index.cmp(index) { + std::cmp::Ordering::Less + if i >= target_index + && i < *index => + { + 1 + } + std::cmp::Ordering::Greater + if i > *index + && i <= target_index => + { + -1 + } + _ => 0, + }; + + let translation = Vector::new( + 0.0, + offset as f32 * drag_height, + ); + renderer.with_translation( + translation, + |renderer| { + child.as_widget().draw( + state, + renderer, + theme, + defaults, + child_layout, + cursor, + viewport, + ); + // Draw an overlay if this item is being moved + // TODO: instead of drawing an overlay, it would be nicer to + // draw the item with a reduced opacity, but that's not possible today + if offset != 0 { + renderer.fill_quad( + renderer::Quad { + bounds: child_layout + .bounds(), + ..renderer::Quad::default( + ) + }, + style.moved_item_overlay, + ); + + // Keep track of the total translation so we can + // draw the "ghost" of the dragged item later + translations -= (child_layout + .bounds() + .height + + self.spacing) + * offset.signum() as f32; + } + }, + ); + } + } + // Draw a ghost of the dragged item in its would-be position + let ghost_translation = + Vector::new(0.0, translations); + renderer.with_translation( + ghost_translation, + |renderer| { + renderer.fill_quad( + renderer::Quad { + bounds: drag_bounds, + border: style.ghost_border, + ..renderer::Quad::default() + }, + style.ghost_background, + ); + }, + ); + } + _ => { + // Draw all children normally when not dragging + for ((child, state), layout) in self + .children + .iter() + .zip(&tree.children) + .zip(layout.children()) + { + child.as_widget().draw( + state, renderer, theme, defaults, layout, + cursor, viewport, + ); + } + } + } + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + translation: Vector, + ) -> Option> { + overlay::from_children( + &mut self.children, + tree, + layout, + renderer, + translation, + ) + } +} + +impl<'a, Message, Theme, Renderer> + From> + for Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Theme: Catalog + 'a, + Renderer: renderer::Renderer + 'a, +{ + fn from(column: Column<'a, Message, Theme, Renderer>) -> Self { + Self::new(column) + } +} + +/// The theme catalog of a [`Column`]. +pub trait Catalog { + /// The item class of the [`Catalog`]. + type Class<'a>; + + /// The default class produced by the [`Catalog`]. + fn default<'a>() -> Self::Class<'a>; + + /// The [`Style`] of a class with the given status. + fn style(&self, class: &Self::Class<'_>) -> Style; +} + +/// The appearance of a [`Column`]. +#[derive(Debug, Clone, Copy)] +pub struct Style { + /// The scaling to apply to a picked element while it's being dragged. + pub scale: f32, + /// The color of the overlay on items that are moved around + pub moved_item_overlay: Color, + /// The outline border of the dragged item's ghost + pub ghost_border: Border, + /// The background of the dragged item's ghost + pub ghost_background: Background, +} + +/// A styling function for a [`Column`]. +pub type StyleFn<'a, Theme> = Box Style + 'a>; + +impl Catalog for cosmic::Theme { + type Class<'a> = StyleFn<'a, Self>; + + fn default<'a>() -> Self::Class<'a> { + Box::new(default) + } + + fn style(&self, class: &Self::Class<'_>) -> Style { + class(self) + } +} + +pub fn default(theme: &Theme) -> Style { + Style { + scale: 1.05, + moved_item_overlay: Color::from(theme.cosmic().primary.base) + .scale_alpha(0.2) + .into(), + ghost_border: Border { + width: 1.0, + color: theme.cosmic().secondary.base.into(), + radius: 0.0.into(), + }, + ghost_background: Color::from(theme.cosmic().secondary.base) + .scale_alpha(0.2) + .into(), + } +} diff --git a/src/ui/widgets/draggable/mod.rs b/src/ui/widgets/draggable/mod.rs new file mode 100644 index 0000000..11b8e08 --- /dev/null +++ b/src/ui/widgets/draggable/mod.rs @@ -0,0 +1,42 @@ +use cosmic::iced::Point; + +pub use self::column::column; +pub use self::row::row; +pub mod column; +pub mod row; + +#[derive(Debug, Clone)] +pub enum Action { + Idle, + Picking { + index: usize, + origin: Point, + }, + Dragging { + index: usize, + origin: Point, + last_cursor: Point, + }, +} + +#[derive(Debug, Clone, Copy)] +pub enum DropPosition { + Before, + Swap, + After, +} + +#[derive(Debug, Clone)] +pub enum DragEvent { + Picked { + index: usize, + }, + Dropped { + index: usize, + target_index: usize, + drop_position: DropPosition, + }, + Canceled { + index: usize, + }, +} diff --git a/src/ui/widgets/draggable/row.rs b/src/ui/widgets/draggable/row.rs new file mode 100644 index 0000000..c90a5cf --- /dev/null +++ b/src/ui/widgets/draggable/row.rs @@ -0,0 +1,1071 @@ +//! Distribute draggable content horizontally. +// This widget is a modification of the original `Row` widget from [`iced`] +// +// [`iced`]: https://github.com/iced-rs/iced +// +// Copyright 2019 Héctor Ramón, Iced contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of +// this software and associated documentation files (the "Software"), to deal in +// the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software is furnished to do so, +// subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +use cosmic::iced::advanced::layout::{self, Layout}; +use cosmic::iced::advanced::widget::{tree, Operation, Tree, Widget}; +use cosmic::iced::advanced::{overlay, renderer, Clipboard, Shell}; +use cosmic::iced::alignment::{self, Alignment}; +use cosmic::iced::event::{self, Event}; +use cosmic::iced::{self, mouse, Transformation}; +use cosmic::iced::{ + Background, Border, Color, Element, Length, Padding, Pixels, + Point, Rectangle, Size, Theme, Vector, +}; + +use super::{Action, DragEvent, DropPosition}; + +pub fn row<'a, Message, Theme, Renderer>( + children: impl IntoIterator< + Item = Element<'a, Message, Theme, Renderer>, + >, +) -> Row<'a, Message, Theme, Renderer> +where + Renderer: renderer::Renderer, + Theme: Catalog, +{ + Row::with_children(children) +} + +const DRAG_DEADBAND_DISTANCE: f32 = 5.0; + +/// A container that distributes its contents horizontally. +/// +/// # Example +/// ```no_run +/// # mod iced { pub mod widget { pub use iced_widget::*; } } +/// # pub type State = (); +/// # pub type Element<'a, Message> = iced_widget::core::Element<'a, Message, iced_widget::Theme, iced_widget::Renderer>; +/// use iced::widget::{button, row}; +/// +/// #[derive(Debug, Clone)] +/// enum Message { +/// // ... +/// } +/// +/// fn view(state: &State) -> Element<'_, Message> { +/// row![ +/// "I am to the left!", +/// button("I am in the middle!"), +/// "I am to the right!", +/// ].into() +/// } +/// ``` +#[allow(missing_debug_implementations)] +pub struct Row<'a, Message, Theme, Renderer = iced::Renderer> +where + Theme: Catalog, +{ + spacing: f32, + padding: Padding, + width: Length, + height: Length, + align: Alignment, + clip: bool, + deadband_zone: f32, + children: Vec>, + on_drag: Option Message + 'a>>, + class: Theme::Class<'a>, +} + +impl<'a, Message, Theme, Renderer> Row<'a, Message, Theme, Renderer> +where + Renderer: renderer::Renderer, + Theme: Catalog, +{ + /// Creates an empty [`Row`]. + pub fn new() -> Self { + Self::from_vec(Vec::new()) + } + + /// Creates a [`Row`] with the given capacity. + pub fn with_capacity(capacity: usize) -> Self { + Self::from_vec(Vec::with_capacity(capacity)) + } + + /// Creates a [`Row`] with the given elements. + pub fn with_children( + children: impl IntoIterator< + Item = Element<'a, Message, Theme, Renderer>, + >, + ) -> Self { + let iterator = children.into_iter(); + + Self::with_capacity(iterator.size_hint().0).extend(iterator) + } + + /// Creates a [`Row`] from an already allocated [`Vec`]. + /// + /// Keep in mind that the [`Row`] will not inspect the [`Vec`], which means + /// it won't automatically adapt to the sizing strategy of its contents. + /// + /// If any of the children have a [`Length::Fill`] strategy, you will need to + /// call [`Row::width`] or [`Row::height`] accordingly. + pub fn from_vec( + children: Vec>, + ) -> Self { + Self { + spacing: 0.0, + padding: Padding::ZERO, + width: Length::Shrink, + height: Length::Shrink, + align: Alignment::Start, + clip: false, + deadband_zone: DRAG_DEADBAND_DISTANCE, + children, + class: Theme::default(), + on_drag: None, + } + } + + /// Sets the horizontal spacing _between_ elements. + /// + /// Custom margins per element do not exist in iced. You should use this + /// method instead! While less flexible, it helps you keep spacing between + /// elements consistent. + pub fn spacing(mut self, amount: impl Into) -> Self { + self.spacing = amount.into().0; + self + } + + /// Sets the [`Padding`] of the [`Row`]. + pub fn padding>(mut self, padding: P) -> Self { + self.padding = padding.into(); + self + } + + /// Sets the width of the [`Row`]. + pub fn width(mut self, width: impl Into) -> Self { + self.width = width.into(); + self + } + + /// Sets the height of the [`Row`]. + pub fn height(mut self, height: impl Into) -> Self { + self.height = height.into(); + self + } + + /// Sets the vertical alignment of the contents of the [`Row`] . + pub fn align_y( + mut self, + align: impl Into, + ) -> Self { + self.align = Alignment::from(align.into()); + self + } + + /// Sets whether the contents of the [`Row`] should be clipped on + /// overflow. + pub fn clip(mut self, clip: bool) -> Self { + self.clip = clip; + self + } + + /// Sets the drag deadband zone of the [`Row`]. + pub fn deadband_zone(mut self, deadband_zone: f32) -> Self { + self.deadband_zone = deadband_zone; + self + } + + /// Adds an [`Element`] to the [`Row`]. + pub fn push( + mut self, + child: impl Into>, + ) -> Self { + let child = child.into(); + let child_size = child.as_widget().size_hint(); + + self.width = self.width.enclose(child_size.width); + self.height = self.height.enclose(child_size.height); + + self.children.push(child); + self + } + + /// Adds an element to the [`Row`], if `Some`. + pub fn push_maybe( + self, + child: Option< + impl Into>, + >, + ) -> Self { + if let Some(child) = child { + self.push(child) + } else { + self + } + } + + /// Sets the style of the [`Row`]. + #[must_use] + pub fn style( + mut self, + style: impl Fn(&Theme) -> Style + 'a, + ) -> Self + where + Theme::Class<'a>: From>, + { + self.class = (Box::new(style) as StyleFn<'a, Theme>).into(); + self + } + + /// Sets the style class of the [`Row`]. + #[must_use] + pub fn class( + mut self, + class: impl Into>, + ) -> Self { + self.class = class.into(); + self + } + + /// Extends the [`Row`] with the given children. + pub fn extend( + self, + children: impl IntoIterator< + Item = Element<'a, Message, Theme, Renderer>, + >, + ) -> Self { + children.into_iter().fold(self, Self::push) + } + + /// Turns the [`Row`] into a [`Wrapping`] row. + /// + /// The original alignment of the [`Row`] is preserved per row wrapped. + pub fn wrap(self) -> Wrapping<'a, Message, Theme, Renderer> { + Wrapping { row: self } + } + + /// The message produced by the [`Row`] when a child is dragged. + pub fn on_drag( + mut self, + on_reorder: impl Fn(DragEvent) -> Message + 'a, + ) -> Self { + self.on_drag = Some(Box::new(on_reorder)); + self + } + + // Computes the index and position where a dragged item should be dropped. + fn compute_target_index( + &self, + cursor_position: Point, + layout: Layout<'_>, + dragged_index: usize, + ) -> (usize, DropPosition) { + let cursor_x = cursor_position.x; + + for (i, child_layout) in layout.children().enumerate() { + let bounds = child_layout.bounds(); + let x = bounds.x; + let width = bounds.width; + + if cursor_x >= x && cursor_x <= x + width { + if i == dragged_index { + // Cursor is over the dragged item itself + return (i, DropPosition::Swap); + } + + let thickness = width / 4.0; + let left_threshold = x + thickness; + let right_threshold = x + width - thickness; + + if cursor_x < left_threshold { + // Near the left edge - insert before + return (i, DropPosition::Before); + } else if cursor_x > right_threshold { + // Near the right edge - insert after + return (i + 1, DropPosition::After); + } else { + // Middle area - swap + return (i, DropPosition::Swap); + } + } else if cursor_x < x { + // Cursor is before this child + return (i, DropPosition::Before); + } + } + + // Cursor is after all children + (self.children.len(), DropPosition::After) + } +} + +impl<'a, Message, Renderer> Default + for Row<'a, Message, Theme, Renderer> +where + Renderer: renderer::Renderer, + Theme: Catalog, +{ + fn default() -> Self { + Self::new() + } +} + +impl<'a, Message, Theme, Renderer: renderer::Renderer> + FromIterator> + for Row<'a, Message, Theme, Renderer> +where + Theme: Catalog, +{ + fn from_iter< + T: IntoIterator>, + >( + iter: T, + ) -> Self { + Self::with_children(iter) + } +} + +impl<'a, Message, Theme, Renderer> Widget + for Row<'a, Message, Theme, Renderer> +where + Renderer: renderer::Renderer, + Theme: Catalog, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(Action::Idle) + } + + fn children(&self) -> Vec { + self.children.iter().map(Tree::new).collect() + } + + fn diff(&mut self, tree: &mut Tree) { + tree.diff_children(&mut self.children); + } + + fn size(&self) -> Size { + Size { + width: self.width, + height: self.height, + } + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + layout::flex::resolve( + layout::flex::Axis::Horizontal, + renderer, + limits, + self.width, + self.height, + self.padding, + self.spacing, + self.align, + &self.children, + &mut tree.children, + ) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn Operation, + ) { + operation.container( + None, + layout.bounds(), + &mut |operation| { + self.children + .iter() + .zip(&mut tree.children) + .zip(layout.children()) + .for_each(|((child, state), layout)| { + child.as_widget().operate( + state, layout, renderer, operation, + ); + }); + }, + ); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) -> event::Status { + let mut event_status = event::Status::Ignored; + + let action = tree.state.downcast_mut::(); + + match event { + Event::Mouse(mouse::Event::ButtonPressed( + mouse::Button::Left, + )) => { + if let Some(cursor_position) = + cursor.position_over(layout.bounds()) + { + for (index, child_layout) in + layout.children().enumerate() + { + if child_layout + .bounds() + .contains(cursor_position) + { + *action = Action::Picking { + index, + origin: cursor_position, + }; + event_status = event::Status::Captured; + break; + } + } + } + } + Event::Mouse(mouse::Event::CursorMoved { .. }) => { + match *action { + Action::Picking { index, origin } => { + if let Some(cursor_position) = + cursor.position() + { + if cursor_position.distance(origin) + > self.deadband_zone + { + // Start dragging + *action = Action::Dragging { + index, + origin, + last_cursor: cursor_position, + }; + if let Some(on_reorder) = + &self.on_drag + { + shell.publish(on_reorder( + DragEvent::Picked { index }, + )); + } + event_status = + event::Status::Captured; + } + } + } + Action::Dragging { origin, index, .. } => { + if let Some(cursor_position) = + cursor.position() + { + *action = Action::Dragging { + last_cursor: cursor_position, + origin, + index, + }; + event_status = event::Status::Captured; + } + } + _ => {} + } + } + Event::Mouse(mouse::Event::ButtonReleased( + mouse::Button::Left, + )) => { + match *action { + Action::Dragging { index, .. } => { + if let Some(cursor_position) = + cursor.position() + { + let bounds = layout.bounds(); + if bounds.contains(cursor_position) { + let (target_index, drop_position) = + self.compute_target_index( + cursor_position, + layout, + index, + ); + + if let Some(on_reorder) = + &self.on_drag + { + shell.publish(on_reorder( + DragEvent::Dropped { + index, + target_index, + drop_position, + }, + )); + event_status = + event::Status::Captured; + } + } else if let Some(on_reorder) = + &self.on_drag + { + shell.publish(on_reorder( + DragEvent::Canceled { index }, + )); + event_status = + event::Status::Captured; + } + } + *action = Action::Idle; + } + Action::Picking { .. } => { + // Did not move enough to start dragging + *action = Action::Idle; + } + _ => {} + } + } + _ => {} + } + + let child_status = self + .children + .iter_mut() + .zip(&mut tree.children) + .zip(layout.children()) + .map(|((child, state), layout)| { + child.as_widget_mut().on_event( + state, + event.clone(), + layout, + cursor, + renderer, + clipboard, + shell, + viewport, + ) + }) + .fold(event::Status::Ignored, event::Status::merge); + + event::Status::merge(event_status, child_status) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + let action = tree.state.downcast_ref::(); + + if let Action::Dragging { .. } = *action { + return mouse::Interaction::Grabbing; + } + + self.children + .iter() + .zip(&tree.children) + .zip(layout.children()) + .map(|((child, state), layout)| { + child.as_widget().mouse_interaction( + state, layout, cursor, viewport, renderer, + ) + }) + .max() + .unwrap_or_default() + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + defaults: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + let action = tree.state.downcast_ref::(); + let style = theme.style(&self.class); + + match action { + Action::Dragging { + index, + last_cursor, + origin, + .. + } => { + let child_count = self.children.len(); + + // Determine the target index based on cursor position + let target_index = if cursor.position().is_some() { + let (target_index, _) = self + .compute_target_index( + *last_cursor, + layout, + *index, + ); + target_index.min(child_count - 1) + } else { + *index + }; + + // Store the width of the dragged item + let drag_bounds = + layout.children().nth(*index).unwrap().bounds(); + let drag_width = drag_bounds.width + self.spacing; + + // Draw all children except the one being dragged + let mut translations = 0.0; + for i in 0..child_count { + let child = &self.children[i]; + let state = &tree.children[i]; + let child_layout = + layout.children().nth(i).unwrap(); + + // Draw the dragged item separately + // TODO: Draw a shadow below the picked item to enhance the + // floating effect + if i == *index { + let scaling = + Transformation::scale(style.scale); + let translation = + *last_cursor - *origin * scaling; + renderer.with_translation( + translation, + |renderer| { + renderer.with_transformation( + scaling, + |renderer| { + renderer.with_layer( + child_layout.bounds(), + |renderer| { + child + .as_widget() + .draw( + state, + renderer, + theme, + defaults, + child_layout, + cursor, + viewport, + ); + }, + ); + }, + ); + }, + ); + } else { + let offset: i32 = + match target_index.cmp(index) { + std::cmp::Ordering::Less + if i >= target_index + && i < *index => + { + 1 + } + std::cmp::Ordering::Greater + if i > *index + && i <= target_index => + { + -1 + } + _ => 0, + }; + + let translation = Vector::new( + offset as f32 * drag_width, + 0.0, + ); + renderer.with_translation( + translation, + |renderer| { + child.as_widget().draw( + state, + renderer, + theme, + defaults, + child_layout, + cursor, + viewport, + ); + // Draw an overlay if this item is being moved + // TODO: instead of drawing an overlay, it would be nicer to + // draw the item with a reduced opacity, but that's not possible today + if offset != 0 { + renderer.fill_quad( + renderer::Quad { + bounds: child_layout + .bounds(), + ..renderer::Quad::default( + ) + }, + style.moved_item_overlay, + ); + + // Keep track of the total translation so we can + // draw the "ghost" of the dragged item later + translations -= + (child_layout.bounds().width + + self.spacing) + * offset.signum() as f32; + } + }, + ); + } + } + // Draw a ghost of the dragged item in its would-be position + let ghost_translation = + Vector::new(translations, 0.0); + renderer.with_translation( + ghost_translation, + |renderer| { + renderer.fill_quad( + renderer::Quad { + bounds: drag_bounds, + border: style.ghost_border, + ..renderer::Quad::default() + }, + style.ghost_background, + ); + }, + ); + } + _ => { + // Draw all children normally when not dragging + for ((child, state), layout) in self + .children + .iter() + .zip(&tree.children) + .zip(layout.children()) + { + child.as_widget().draw( + state, renderer, theme, defaults, layout, + cursor, viewport, + ); + } + } + } + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + translation: Vector, + ) -> Option> { + overlay::from_children( + &mut self.children, + tree, + layout, + renderer, + translation, + ) + } +} + +impl<'a, Message, Theme, Renderer> + From> + for Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Theme: Catalog + 'a, + Renderer: renderer::Renderer + 'a, +{ + fn from(row: Row<'a, Message, Theme, Renderer>) -> Self { + Self::new(row) + } +} + +/// A [`Row`] that wraps its contents. +/// +/// Create a [`Row`] first, and then call [`Row::wrap`] to +/// obtain a [`Row`] that wraps its contents. +/// +/// The original alignment of the [`Row`] is preserved per row wrapped. +#[allow(missing_debug_implementations)] +pub struct Wrapping< + 'a, + Message, + Theme = iced::Theme, + Renderer = iced::Renderer, +> where + Theme: Catalog, +{ + row: Row<'a, Message, Theme, Renderer>, +} + +impl<'a, Message, Theme, Renderer> Widget + for Wrapping<'a, Message, Theme, Renderer> +where + Renderer: renderer::Renderer, + Theme: Catalog, +{ + fn children(&self) -> Vec { + self.row.children() + } + + fn diff(&mut self, tree: &mut Tree) { + self.row.diff(tree); + } + + fn size(&self) -> Size { + self.row.size() + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits + .width(self.row.width) + .height(self.row.height) + .shrink(self.row.padding); + + let spacing = self.row.spacing; + let max_width = limits.max().width; + + let mut children: Vec = Vec::new(); + let mut intrinsic_size = Size::ZERO; + let mut row_start = 0; + let mut row_height = 0.0; + let mut x = 0.0; + let mut y = 0.0; + + let align_factor = match self.row.align { + Alignment::Start => 0.0, + Alignment::Center => 2.0, + Alignment::End => 1.0, + }; + + let align = + |row_start: std::ops::Range, + row_height: f32, + children: &mut Vec| { + if align_factor != 0.0 { + for node in &mut children[row_start] { + let height = node.size().height; + + node.translate_mut(Vector::new( + 0.0, + (row_height - height) / align_factor, + )); + } + } + }; + + for (i, child) in self.row.children.iter().enumerate() { + let node = child.as_widget().layout( + &mut tree.children[i], + renderer, + &limits, + ); + + let child_size = node.size(); + + if x != 0.0 && x + child_size.width > max_width { + intrinsic_size.width = + intrinsic_size.width.max(x - spacing); + + align(row_start..i, row_height, &mut children); + + y += row_height + spacing; + x = 0.0; + row_start = i; + row_height = 0.0; + } + + row_height = row_height.max(child_size.height); + + children.push(node.move_to(( + x + self.row.padding.left, + y + self.row.padding.top, + ))); + + x += child_size.width + spacing; + } + + if x != 0.0 { + intrinsic_size.width = + intrinsic_size.width.max(x - spacing); + } + + intrinsic_size.height = y + row_height; + align(row_start..children.len(), row_height, &mut children); + + let size = limits.resolve( + self.row.width, + self.row.height, + intrinsic_size, + ); + + layout::Node::with_children( + size.expand(self.row.padding), + children, + ) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn Operation, + ) { + self.row.operate(tree, layout, renderer, operation); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) -> event::Status { + self.row.on_event( + tree, event, layout, cursor, renderer, clipboard, shell, + viewport, + ) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.row.mouse_interaction( + tree, layout, cursor, viewport, renderer, + ) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + self.row.draw( + tree, renderer, theme, style, layout, cursor, viewport, + ); + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + translation: Vector, + ) -> Option> { + self.row.overlay(tree, layout, renderer, translation) + } +} + +impl<'a, Message, Theme, Renderer> + From> + for Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Theme: Catalog + 'a, + Renderer: renderer::Renderer + 'a, +{ + fn from(row: Wrapping<'a, Message, Theme, Renderer>) -> Self { + Self::new(row) + } +} + +/// The theme catalog of a [`Row`]. +pub trait Catalog { + /// The item class of the [`Catalog`]. + type Class<'a>; + + /// The default class produced by the [`Catalog`]. + fn default<'a>() -> Self::Class<'a>; + + /// The [`Style`] of a class with the given status. + fn style(&self, class: &Self::Class<'_>) -> Style; +} + +/// The appearance of a [`Row`]. +#[derive(Debug, Clone, Copy)] +pub struct Style { + /// The scaling to apply to a picked element while it's being dragged. + pub scale: f32, + /// The color of the overlay on items that are moved around + pub moved_item_overlay: Color, + /// The outline border of the dragged item's ghost + pub ghost_border: Border, + /// The background of the dragged item's ghost + pub ghost_background: Background, +} + +/// A styling function for a [`Row`]. +pub type StyleFn<'a, Theme> = Box Style + 'a>; + +impl Catalog for Theme { + type Class<'a> = StyleFn<'a, Self>; + + fn default<'a>() -> Self::Class<'a> { + Box::new(default) + } + + fn style(&self, class: &Self::Class<'_>) -> Style { + class(self) + } +} + +pub fn default(theme: &Theme) -> Style { + Style { + scale: 1.05, + moved_item_overlay: theme + .extended_palette() + .primary + .base + .color + .scale_alpha(0.2), + ghost_border: Border { + width: 1.0, + color: theme.extended_palette().secondary.base.color, + radius: 0.0.into(), + }, + ghost_background: theme + .extended_palette() + .secondary + .base + .color + .scale_alpha(0.2) + .into(), + } +} diff --git a/src/ui/widgets/mod.rs b/src/ui/widgets/mod.rs index d786591..f13c9e9 100644 --- a/src/ui/widgets/mod.rs +++ b/src/ui/widgets/mod.rs @@ -1 +1,2 @@ // pub mod slide_text; +pub mod draggable;