diff --git a/Cargo.lock b/Cargo.lock
index 92435024..2ee82412 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -118,7 +118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b"
dependencies = [
"atk-sys",
- "glib",
+ "glib 0.18.5",
"libc",
]
@@ -128,10 +128,10 @@ version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086"
dependencies = [
- "glib-sys",
- "gobject-sys",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
"libc",
- "system-deps",
+ "system-deps 6.2.2",
]
[[package]]
@@ -269,22 +269,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2"
dependencies = [
"bitflags 2.8.0",
- "cairo-sys-rs",
- "glib",
+ "cairo-sys-rs 0.18.2",
+ "glib 0.18.5",
"libc",
"once_cell",
"thiserror 1.0.69",
]
+[[package]]
+name = "cairo-rs"
+version = "0.20.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae50b5510d86cf96ac2370e66d8dc960882f3df179d6a5a1e52bd94a1416c0f7"
+dependencies = [
+ "bitflags 2.8.0",
+ "cairo-sys-rs 0.20.7",
+ "glib 0.20.7",
+ "libc",
+]
+
[[package]]
name = "cairo-sys-rs"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51"
dependencies = [
- "glib-sys",
+ "glib-sys 0.18.1",
+ "libc",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "cairo-sys-rs"
+version = "0.20.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f18b6bb8e43c7eb0f2aac7976afe0c61b6f5fc2ab7bc4c139537ea56c92290df"
+dependencies = [
+ "glib-sys 0.20.7",
"libc",
- "system-deps",
+ "system-deps 7.0.3",
]
[[package]]
@@ -340,6 +363,16 @@ dependencies = [
"target-lexicon",
]
+[[package]]
+name = "cfg-expr"
+version = "0.17.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d4ba6e40bd1184518716a6e1a781bf9160e286d219ccdb8ab2612e74cfe4789"
+dependencies = [
+ "smallvec",
+ "target-lexicon",
+]
+
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -892,13 +925,13 @@ version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691"
dependencies = [
- "cairo-rs",
- "gdk-pixbuf",
+ "cairo-rs 0.18.5",
+ "gdk-pixbuf 0.18.5",
"gdk-sys",
- "gio",
- "glib",
+ "gio 0.18.4",
+ "glib 0.18.5",
"libc",
- "pango",
+ "pango 0.18.3",
]
[[package]]
@@ -907,24 +940,49 @@ version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec"
dependencies = [
- "gdk-pixbuf-sys",
- "gio",
- "glib",
+ "gdk-pixbuf-sys 0.18.0",
+ "gio 0.18.4",
+ "glib 0.18.5",
"libc",
"once_cell",
]
+[[package]]
+name = "gdk-pixbuf"
+version = "0.20.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6efc7705f7863d37b12ad6974cbb310d35d054f5108cdc1e69037742f573c4c"
+dependencies = [
+ "gdk-pixbuf-sys 0.20.7",
+ "gio 0.20.7",
+ "glib 0.20.7",
+ "libc",
+]
+
[[package]]
name = "gdk-pixbuf-sys"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7"
dependencies = [
- "gio-sys",
- "glib-sys",
- "gobject-sys",
+ "gio-sys 0.18.1",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
+ "libc",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "gdk-pixbuf-sys"
+version = "0.20.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67f2587c9202bf997476bbba6aaed4f78a11538a2567df002a5f57f5331d0b5c"
+dependencies = [
+ "gio-sys 0.20.8",
+ "glib-sys 0.20.7",
+ "gobject-sys 0.20.7",
"libc",
- "system-deps",
+ "system-deps 7.0.3",
]
[[package]]
@@ -933,15 +991,47 @@ version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7"
dependencies = [
- "cairo-sys-rs",
- "gdk-pixbuf-sys",
- "gio-sys",
- "glib-sys",
- "gobject-sys",
+ "cairo-sys-rs 0.18.2",
+ "gdk-pixbuf-sys 0.18.0",
+ "gio-sys 0.18.1",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
"libc",
- "pango-sys",
+ "pango-sys 0.18.0",
"pkg-config",
- "system-deps",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "gdk4"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0196720118f880f71fe7da971eff58cc43a89c9cf73f46076b7cb1e60889b15"
+dependencies = [
+ "cairo-rs 0.20.7",
+ "gdk-pixbuf 0.20.7",
+ "gdk4-sys",
+ "gio 0.20.7",
+ "glib 0.20.7",
+ "libc",
+ "pango 0.20.7",
+]
+
+[[package]]
+name = "gdk4-sys"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60b0e1340bd15e7a78810cf39fed9e5d85f0a8f80b1d999d384ca17dcc452b60"
+dependencies = [
+ "cairo-sys-rs 0.20.7",
+ "gdk-pixbuf-sys 0.20.7",
+ "gio-sys 0.20.8",
+ "glib-sys 0.20.7",
+ "gobject-sys 0.20.7",
+ "libc",
+ "pango-sys 0.20.7",
+ "pkg-config",
+ "system-deps 7.0.3",
]
[[package]]
@@ -951,11 +1041,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69"
dependencies = [
"gdk-sys",
- "glib-sys",
- "gobject-sys",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
"libc",
"pkg-config",
- "system-deps",
+ "system-deps 6.2.2",
]
[[package]]
@@ -966,8 +1056,8 @@ checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe"
dependencies = [
"gdk",
"gdkx11-sys",
- "gio",
- "glib",
+ "gio 0.18.4",
+ "glib 0.18.5",
"libc",
"x11",
]
@@ -979,9 +1069,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d"
dependencies = [
"gdk-sys",
- "glib-sys",
+ "glib-sys 0.18.1",
"libc",
- "system-deps",
+ "system-deps 6.2.2",
"x11",
]
@@ -1047,8 +1137,8 @@ dependencies = [
"futures-core",
"futures-io",
"futures-util",
- "gio-sys",
- "glib",
+ "gio-sys 0.18.1",
+ "glib 0.18.5",
"libc",
"once_cell",
"pin-project-lite",
@@ -1056,19 +1146,49 @@ dependencies = [
"thiserror 1.0.69",
]
+[[package]]
+name = "gio"
+version = "0.20.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a517657589a174be9f60c667f1fec8b7ac82ed5db4ebf56cf073a3b5955d8e2e"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-util",
+ "gio-sys 0.20.8",
+ "glib 0.20.7",
+ "libc",
+ "pin-project-lite",
+ "smallvec",
+]
+
[[package]]
name = "gio-sys"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2"
dependencies = [
- "glib-sys",
- "gobject-sys",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
"libc",
- "system-deps",
+ "system-deps 6.2.2",
"winapi",
]
+[[package]]
+name = "gio-sys"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8446d9b475730ebef81802c1738d972db42fde1c5a36a627ebc4d665fc87db04"
+dependencies = [
+ "glib-sys 0.20.7",
+ "gobject-sys 0.20.7",
+ "libc",
+ "system-deps 7.0.3",
+ "windows-sys 0.59.0",
+]
+
[[package]]
name = "glib"
version = "0.18.5"
@@ -1081,10 +1201,10 @@ dependencies = [
"futures-executor",
"futures-task",
"futures-util",
- "gio-sys",
- "glib-macros",
- "glib-sys",
- "gobject-sys",
+ "gio-sys 0.18.1",
+ "glib-macros 0.18.5",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
"libc",
"memchr",
"once_cell",
@@ -1092,6 +1212,27 @@ dependencies = [
"thiserror 1.0.69",
]
+[[package]]
+name = "glib"
+version = "0.20.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f969edf089188d821a30cde713b6f9eb08b20c63fc2e584aba2892a7984a8cc0"
+dependencies = [
+ "bitflags 2.8.0",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-task",
+ "futures-util",
+ "gio-sys 0.20.8",
+ "glib-macros 0.20.7",
+ "glib-sys 0.20.7",
+ "gobject-sys 0.20.7",
+ "libc",
+ "memchr",
+ "smallvec",
+]
+
[[package]]
name = "glib-macros"
version = "0.18.5"
@@ -1099,13 +1240,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc"
dependencies = [
"heck 0.4.1",
- "proc-macro-crate 2.0.2",
+ "proc-macro-crate 2.0.0",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.96",
]
+[[package]]
+name = "glib-macros"
+version = "0.20.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "715601f8f02e71baef9c1f94a657a9a77c192aea6097cf9ae7e5e177cd8cde68"
+dependencies = [
+ "heck 0.5.0",
+ "proc-macro-crate 3.2.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
[[package]]
name = "glib-sys"
version = "0.18.1"
@@ -1113,7 +1267,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898"
dependencies = [
"libc",
- "system-deps",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "glib-sys"
+version = "0.20.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b360ff0f90d71de99095f79c526a5888c9c92fc9ee1b19da06c6f5e75f0c2a53"
+dependencies = [
+ "libc",
+ "system-deps 7.0.3",
]
[[package]]
@@ -1122,9 +1286,74 @@ version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44"
dependencies = [
- "glib-sys",
+ "glib-sys 0.18.1",
+ "libc",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "gobject-sys"
+version = "0.20.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67a56235e971a63bfd75abb13ef70064e1346388723422a68580d8a6fbac6423"
+dependencies = [
+ "glib-sys 0.20.7",
"libc",
- "system-deps",
+ "system-deps 7.0.3",
+]
+
+[[package]]
+name = "graphene-rs"
+version = "0.20.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f39d3bcd2e24fd9c2874a56f277b72c03e728de9bdc95a8d4ef4c962f10ced98"
+dependencies = [
+ "glib 0.20.7",
+ "graphene-sys",
+ "libc",
+]
+
+[[package]]
+name = "graphene-sys"
+version = "0.20.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11a68d39515bf340e879b72cecd4a25c1332557757ada6e8aba8654b4b81d23a"
+dependencies = [
+ "glib-sys 0.20.7",
+ "libc",
+ "pkg-config",
+ "system-deps 7.0.3",
+]
+
+[[package]]
+name = "gsk4"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32b9188db0a6219e708b6b6e7225718e459def664023dbddb8395ca1486d8102"
+dependencies = [
+ "cairo-rs 0.20.7",
+ "gdk4",
+ "glib 0.20.7",
+ "graphene-rs",
+ "gsk4-sys",
+ "libc",
+ "pango 0.20.7",
+]
+
+[[package]]
+name = "gsk4-sys"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bca10fc65d68528a548efa3d8747934adcbe7058b73695c9a7f43a25352fce14"
+dependencies = [
+ "cairo-sys-rs 0.20.7",
+ "gdk4-sys",
+ "glib-sys 0.20.7",
+ "gobject-sys 0.20.7",
+ "graphene-sys",
+ "libc",
+ "pango-sys 0.20.7",
+ "system-deps 7.0.3",
]
[[package]]
@@ -1134,17 +1363,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a"
dependencies = [
"atk",
- "cairo-rs",
+ "cairo-rs 0.18.5",
"field-offset",
"futures-channel",
"gdk",
- "gdk-pixbuf",
- "gio",
- "glib",
+ "gdk-pixbuf 0.18.5",
+ "gio 0.18.4",
+ "glib 0.18.5",
"gtk-sys",
"gtk3-macros",
"libc",
- "pango",
+ "pango 0.18.3",
"pkg-config",
]
@@ -1155,15 +1384,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414"
dependencies = [
"atk-sys",
- "cairo-sys-rs",
- "gdk-pixbuf-sys",
+ "cairo-sys-rs 0.18.2",
+ "gdk-pixbuf-sys 0.18.0",
"gdk-sys",
- "gio-sys",
- "glib-sys",
- "gobject-sys",
+ "gio-sys 0.18.1",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
"libc",
- "pango-sys",
- "system-deps",
+ "pango-sys 0.18.0",
+ "system-deps 6.2.2",
]
[[package]]
@@ -1179,6 +1408,58 @@ dependencies = [
"syn 2.0.96",
]
+[[package]]
+name = "gtk4"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b697ff938136625f6acf75f01951220f47a45adcf0060ee55b4671cf734dac44"
+dependencies = [
+ "cairo-rs 0.20.7",
+ "field-offset",
+ "futures-channel",
+ "gdk-pixbuf 0.20.7",
+ "gdk4",
+ "gio 0.20.7",
+ "glib 0.20.7",
+ "graphene-rs",
+ "gsk4",
+ "gtk4-macros",
+ "gtk4-sys",
+ "libc",
+ "pango 0.20.7",
+]
+
+[[package]]
+name = "gtk4-macros"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ed1786c4703dd196baf7e103525ce0cf579b3a63a0570fe653b7ee6bac33999"
+dependencies = [
+ "proc-macro-crate 3.2.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "gtk4-sys"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3af4b680cee5d2f786a2f91f1c77e95ecf2254522f0ca4edf3a2dce6cb35cecf"
+dependencies = [
+ "cairo-sys-rs 0.20.7",
+ "gdk-pixbuf-sys 0.20.7",
+ "gdk4-sys",
+ "gio-sys 0.20.8",
+ "glib-sys 0.20.7",
+ "gobject-sys 0.20.7",
+ "graphene-sys",
+ "gsk4-sys",
+ "libc",
+ "pango-sys 0.20.7",
+ "system-deps 7.0.3",
+]
+
[[package]]
name = "half"
version = "2.4.1"
@@ -1490,7 +1771,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc"
dependencies = [
"bitflags 1.3.2",
- "glib",
+ "glib 0.18.5",
"javascriptcore-rs-sys",
]
@@ -1500,10 +1781,10 @@ version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124"
dependencies = [
- "glib-sys",
- "gobject-sys",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
"libc",
- "system-deps",
+ "system-deps 6.2.2",
]
[[package]]
@@ -1626,25 +1907,6 @@ dependencies = [
"redox_syscall 0.5.8",
]
-[[package]]
-name = "libxdo"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00333b8756a3d28e78def82067a377de7fa61b24909000aeaa2b446a948d14db"
-dependencies = [
- "libxdo-sys",
-]
-
-[[package]]
-name = "libxdo-sys"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db23b9e7e2b7831bbd8aac0bbeeeb7b68cbebc162b227e7052e8e55829a09212"
-dependencies = [
- "libc",
- "x11",
-]
-
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
@@ -1773,10 +2035,9 @@ version = "0.16.1"
dependencies = [
"crossbeam-channel",
"dpi",
- "gtk",
+ "gtk4",
"image",
"keyboard-types",
- "libxdo",
"objc2 0.6.0",
"objc2-app-kit 0.3.0",
"objc2-core-foundation",
@@ -1914,7 +2175,7 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
dependencies = [
- "proc-macro-crate 2.0.2",
+ "proc-macro-crate 1.3.1",
"proc-macro2",
"quote",
"syn 2.0.96",
@@ -2223,11 +2484,23 @@ version = "0.18.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4"
dependencies = [
- "gio",
- "glib",
+ "gio 0.18.4",
+ "glib 0.18.5",
"libc",
"once_cell",
- "pango-sys",
+ "pango-sys 0.18.0",
+]
+
+[[package]]
+name = "pango"
+version = "0.20.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e89bd74250a03a05cec047b43465469102af803be2bf5e5a1088f8b8455e087"
+dependencies = [
+ "gio 0.20.7",
+ "glib 0.20.7",
+ "libc",
+ "pango-sys 0.20.7",
]
[[package]]
@@ -2236,10 +2509,22 @@ version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5"
dependencies = [
- "glib-sys",
- "gobject-sys",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
"libc",
- "system-deps",
+ "system-deps 6.2.2",
+]
+
+[[package]]
+name = "pango-sys"
+version = "0.20.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71787e0019b499a5eda889279e4adb455a4f3fdd6870cd5ab7f4a5aa25df6699"
+dependencies = [
+ "glib-sys 0.20.7",
+ "gobject-sys 0.20.7",
+ "libc",
+ "system-deps 7.0.3",
]
[[package]]
@@ -2462,14 +2747,22 @@ dependencies = [
[[package]]
name = "proc-macro-crate"
-version = "2.0.2"
+version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24"
+checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8"
dependencies = [
- "toml_datetime",
"toml_edit 0.20.2",
]
+[[package]]
+name = "proc-macro-crate"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b"
+dependencies = [
+ "toml_edit 0.22.23",
+]
+
[[package]]
name = "proc-macro-error"
version = "1.0.4"
@@ -2671,7 +2964,7 @@ dependencies = [
"rand 0.8.5",
"rand_chacha 0.3.1",
"simd_helpers",
- "system-deps",
+ "system-deps 6.2.2",
"thiserror 1.0.69",
"v_frame",
"wasm-bindgen",
@@ -2963,8 +3256,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f"
dependencies = [
"futures-channel",
- "gio",
- "glib",
+ "gio 0.18.4",
+ "glib 0.18.5",
"libc",
"soup3-sys",
]
@@ -2975,11 +3268,11 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27"
dependencies = [
- "gio-sys",
- "glib-sys",
- "gobject-sys",
+ "gio-sys 0.18.1",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
"libc",
- "system-deps",
+ "system-deps 6.2.2",
]
[[package]]
@@ -3059,7 +3352,20 @@ version = "6.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349"
dependencies = [
- "cfg-expr",
+ "cfg-expr 0.15.8",
+ "heck 0.5.0",
+ "pkg-config",
+ "toml",
+ "version-compare",
+]
+
+[[package]]
+name = "system-deps"
+version = "7.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005"
+dependencies = [
+ "cfg-expr 0.17.2",
"heck 0.5.0",
"pkg-config",
"toml",
@@ -3239,9 +3545,9 @@ dependencies = [
[[package]]
name = "toml_datetime"
-version = "0.6.3"
+version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
+checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
@@ -3254,7 +3560,7 @@ checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
"indexmap 2.7.1",
"toml_datetime",
- "winnow",
+ "winnow 0.5.40",
]
[[package]]
@@ -3267,7 +3573,18 @@ dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
- "winnow",
+ "winnow 0.5.40",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.22.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee"
+dependencies = [
+ "indexmap 2.7.1",
+ "toml_datetime",
+ "winnow 0.7.0",
]
[[package]]
@@ -3591,14 +3908,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a"
dependencies = [
"bitflags 1.3.2",
- "cairo-rs",
+ "cairo-rs 0.18.5",
"gdk",
"gdk-sys",
- "gio",
- "gio-sys",
- "glib",
- "glib-sys",
- "gobject-sys",
+ "gio 0.18.4",
+ "gio-sys 0.18.1",
+ "glib 0.18.5",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
"gtk",
"gtk-sys",
"javascriptcore-rs",
@@ -3615,17 +3932,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c"
dependencies = [
"bitflags 1.3.2",
- "cairo-sys-rs",
+ "cairo-sys-rs 0.18.2",
"gdk-sys",
- "gio-sys",
- "glib-sys",
- "gobject-sys",
+ "gio-sys 0.18.1",
+ "glib-sys 0.18.1",
+ "gobject-sys 0.18.0",
"gtk-sys",
"javascriptcore-rs-sys",
"libc",
"pkg-config",
"soup3-sys",
- "system-deps",
+ "system-deps 6.2.2",
]
[[package]]
@@ -4040,6 +4357,15 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "winnow"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e49d2d35d3fad69b39b94139037ecfb4f359f08958b9c11e7315ce770462419"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "write16"
version = "1.0.0"
diff --git a/Cargo.toml b/Cargo.toml
index 0fc3140c..fd98cec5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,8 +12,7 @@ categories = ["gui"]
rust-version = "1.71"
[features]
-default = ["libxdo"]
-libxdo = ["dep:libxdo"]
+default = []
common-controls-v6 = []
serde = ["dep:serde", "dpi/serde"]
@@ -42,8 +41,8 @@ features = [
]
[target.'cfg(target_os = "linux")'.dependencies]
-gtk = "0.18"
-libxdo = { version = "0.6.0", optional = true }
+gtk4 = "0.9"
+png = "0.17"
[target.'cfg(target_os = "macos")'.dependencies]
objc2 = "0.6.0"
diff --git a/examples/gtk.rs b/examples/gtk.rs
new file mode 100644
index 00000000..248bd990
--- /dev/null
+++ b/examples/gtk.rs
@@ -0,0 +1,96 @@
+#[cfg(target_os = "linux")]
+use gtk4::prelude::*;
+#[cfg(target_os = "linux")]
+use keyboard_types::{Code, Modifiers};
+#[cfg(target_os = "linux")]
+use muda::{accelerator::Accelerator, MenuEvent};
+
+#[cfg(target_os = "linux")]
+fn main() {
+ // Create a new application
+ let application = gtk4::Application::builder()
+ .application_id("com.github.gtk4-rs.examples.menubar")
+ .build();
+ application.connect_startup(on_startup);
+ application.connect_activate(on_activate);
+ application.run();
+}
+
+#[cfg(target_os = "linux")]
+fn on_startup(_: >k4::Application) {
+ MenuEvent::set_event_handler(Some(|event| {
+ println!("{event:?}");
+ }));
+}
+
+#[cfg(target_os = "linux")]
+fn on_activate(application: >k4::Application) {
+ use muda::ContextMenu;
+
+ let window = gtk4::ApplicationWindow::builder()
+ .application(application)
+ .title("Menubar Example")
+ .default_width(350)
+ .default_height(350)
+ .show_menubar(true)
+ .build();
+
+ window.present();
+
+ let about_menu_item = muda::MenuItem::new("About", true, None);
+
+ let check = muda::CheckMenuItem::new(
+ "Check",
+ true,
+ true,
+ Some(Accelerator::new(Modifiers::empty(), Code::KeyQ)),
+ );
+
+ let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png");
+ let icon = load_icon(std::path::Path::new(path));
+ let icon_menu_item = muda::IconMenuItem::new("Icon", true, Some(icon), None);
+
+ let quit_menu_item = muda::MenuItem::with_id(
+ "quit",
+ "&Quit",
+ true,
+ Some(Accelerator::new(Modifiers::CONTROL, Code::KeyQ)),
+ );
+
+ let file_menu = muda::Submenu::new("&File", true);
+ file_menu.append(&about_menu_item).unwrap();
+ file_menu.append(&check).unwrap();
+ file_menu.append(&icon_menu_item).unwrap();
+ file_menu.append(&quit_menu_item).unwrap();
+
+ let menubar = muda::Menu::new();
+ menubar.append(&file_menu).unwrap();
+
+ let vbox = gtk4::Box::new(gtk4::Orientation::Vertical, 0);
+ menubar.init_for_gtk_window(&window, Some(&vbox)).unwrap();
+
+ let btn = gtk4::Button::with_label("ASdasd");
+ let w = window.clone();
+
+ btn.connect_clicked(move |_| {
+ file_menu.show_context_menu_for_gtk_window(w.dynamic_cast_ref().unwrap(), None);
+ });
+ vbox.append(&btn);
+
+ window.set_child(Some(&vbox));
+}
+
+#[cfg(not(target_os = "linux"))]
+fn main() {}
+
+fn load_icon(path: &std::path::Path) -> muda::Icon {
+ let (icon_rgba, icon_width, icon_height) = {
+ let image = image::open(path)
+ .expect("Failed to open icon path")
+ .into_rgba8();
+ let (width, height) = image.dimensions();
+ let rgba = image.into_raw();
+ (rgba, width, height)
+ };
+ muda::Icon::from_rgba(icon_rgba, icon_width, icon_height).expect("Failed to open icon")
+}
diff --git a/examples/tao.rs b/examples/tao.rs
index afa95f68..38cb6e5b 100644
--- a/examples/tao.rs
+++ b/examples/tao.rs
@@ -88,7 +88,7 @@ fn main() {
"custom-i-1",
"C&ustom 1",
true,
- Some(Accelerator::new(Some(Modifiers::ALT), Code::KeyC)),
+ Some(Accelerator::new(Modifiers::ALT, Code::KeyC)),
);
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png");
@@ -98,7 +98,7 @@ fn main() {
"Image custom 1",
true,
Some(icon),
- Some(Accelerator::new(Some(Modifiers::CONTROL), Code::KeyC)),
+ Some(Accelerator::new(Modifiers::CONTROL, Code::KeyC)),
);
let check_custom_i_1 =
@@ -110,7 +110,7 @@ fn main() {
"Check Custom 3",
true,
true,
- Some(Accelerator::new(Some(Modifiers::SHIFT), Code::KeyD)),
+ Some(Accelerator::new(Modifiers::SHIFT, Code::KeyD)),
);
let copy_i = PredefinedMenuItem::copy(None);
@@ -153,11 +153,11 @@ fn main() {
menu_bar.init_for_hwnd(window.hwnd() as _);
menu_bar.init_for_hwnd(window2.hwnd() as _);
}
- #[cfg(target_os = "linux")]
- {
- menu_bar.init_for_gtk_window(window.gtk_window(), window.default_vbox());
- menu_bar.init_for_gtk_window(window2.gtk_window(), window2.default_vbox());
- }
+ // #[cfg(target_os = "linux")]
+ // {
+ // menu_bar.init_for_gtk_window(window.gtk_window(), window.default_vbox());
+ // menu_bar.init_for_gtk_window(window2.gtk_window(), window2.default_vbox());
+ // }
#[cfg(target_os = "macos")]
{
menu_bar.init_for_nsapp();
@@ -230,8 +230,8 @@ fn show_context_menu(window: &Window, menu: &dyn ContextMenu, position: Option
wry::Result<()> {
let custom_i_1 = MenuItem::new(
"C&ustom 1",
true,
- Some(Accelerator::new(Some(Modifiers::ALT), Code::KeyC)),
+ Some(Accelerator::new(Modifiers::ALT, Code::KeyC)),
);
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/examples/icon.png");
@@ -106,7 +106,7 @@ fn main() -> wry::Result<()> {
"Image custom 1",
true,
Some(icon),
- Some(Accelerator::new(Some(Modifiers::CONTROL), Code::KeyC)),
+ Some(Accelerator::new(Modifiers::CONTROL, Code::KeyC)),
);
let check_custom_i_1 = CheckMenuItem::new("Check Custom 1", true, true, None);
@@ -115,7 +115,7 @@ fn main() -> wry::Result<()> {
"Check Custom 3",
true,
true,
- Some(Accelerator::new(Some(Modifiers::SHIFT), Code::KeyD)),
+ Some(Accelerator::new(Modifiers::SHIFT, Code::KeyD)),
);
let copy_i = PredefinedMenuItem::copy(None);
@@ -172,12 +172,12 @@ fn main() -> wry::Result<()> {
}
#[cfg(target_os = "linux")]
{
- menu_bar
- .init_for_gtk_window(window.gtk_window(), window.default_vbox())
- .unwrap();
- menu_bar
- .init_for_gtk_window(window2.gtk_window(), window2.default_vbox())
- .unwrap();
+ // menu_bar
+ // .init_for_gtk_window(window.gtk_window(), window.default_vbox())
+ // .unwrap();
+ // menu_bar
+ // .init_for_gtk_window(window2.gtk_window(), window2.default_vbox())
+ // .unwrap();
}
#[cfg(target_os = "macos")]
{
@@ -257,14 +257,14 @@ fn main() -> wry::Result<()> {
.map(|(x, y)| (x.parse::().unwrap(), y.parse::().unwrap()))
.unwrap();
- #[cfg(target_os = "linux")]
- if let Some(menu_bar) = menu_bar
- .clone()
- .gtk_menubar_for_gtk_window(window.gtk_window())
- {
- use gtk::prelude::*;
- y += menu_bar.allocated_height();
- }
+ // #[cfg(target_os = "linux")]
+ // if let Some(menu_bar) = menu_bar
+ // .clone()
+ // .gtk_menubar_for_gtk_window(window.gtk_window())
+ // {
+ // use gtk::prelude::*;
+ // y += menu_bar.allocated_height();
+ // }
show_context_menu(&window, &file_m_c, Some(Position::Logical((x, y).into())))
}
@@ -315,8 +315,8 @@ fn show_context_menu(window: &Window, menu: &dyn ContextMenu, position: Option, key: Code) -> Self {
- let mut mods = mods.unwrap_or_else(Modifiers::empty);
+ /// Only [`Modifiers::ALT`], [`Modifiers::SHIFT`], [`Modifiers::CONTROL`], and [`Modifiers::SUPER`] are supported.
+ pub fn new(mut mods: Modifiers, key: Code) -> Self {
if mods.contains(Modifiers::META) {
mods.remove(Modifiers::META);
mods.insert(Modifiers::SUPER);
@@ -71,6 +70,11 @@ impl Accelerator {
Self { mods, key, id }
}
+ /// Same as [`Accelerator::new`] but consists of key without a modifier.
+ pub fn key_only(key: Code) -> Self {
+ Self::new(Modifiers::empty(), key)
+ }
+
fn generate_hash(mods: Modifiers, key: Code) -> u32 {
let mut accelerator_str = String::new();
if mods.contains(Modifiers::SHIFT) {
@@ -204,7 +208,7 @@ fn parse_accelerator(accelerator: &str) -> Result Result {
@@ -423,7 +427,7 @@ fn test_parse_accelerator() {
fn test_equality() {
let h1 = parse_accelerator("Shift+KeyR").unwrap();
let h2 = parse_accelerator("Shift+KeyR").unwrap();
- let h3 = Accelerator::new(Some(Modifiers::SHIFT), Code::KeyR);
+ let h3 = Accelerator::new(Modifiers::SHIFT, Code::KeyR);
let h4 = parse_accelerator("Alt+KeyR").unwrap();
let h5 = parse_accelerator("Alt+KeyR").unwrap();
let h6 = parse_accelerator("KeyR").unwrap();
diff --git a/src/error.rs b/src/error.rs
index 7ad6527c..1f2b2e7b 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -26,6 +26,8 @@ pub enum Error {
AlreadyInitialized,
#[error(transparent)]
AcceleratorParseError(#[from] AcceleratorParseError),
+ #[error("Gtk Window doesn't have an application")]
+ GtkWindowWithoutApplication,
}
/// Convenient type alias of Result type for muda.
diff --git a/src/icon.rs b/src/icon.rs
index 14294538..20d59d62 100644
--- a/src/icon.rs
+++ b/src/icon.rs
@@ -34,6 +34,9 @@ pub enum BadIcon {
},
/// Produced when underlying OS functionality failed to create the icon
OsError(io::Error),
+ /// Produced when encoding provided RGBA into png
+ #[cfg(target_os = "linux")]
+ PngEncodingError(png::EncodingError),
}
impl fmt::Display for BadIcon {
@@ -53,6 +56,8 @@ impl fmt::Display for BadIcon {
width, height, pixel_count, width_x_height,
),
BadIcon::OsError(e) => write!(f, "OS error when instantiating the icon: {:?}", e),
+ #[cfg(target_os = "linux")]
+ BadIcon::PngEncodingError(e) => write!(f, "PNG encoding error when instantiating the icon: {:?}", e),
}
}
}
diff --git a/src/items/normal.rs b/src/items/normal.rs
index 7935e7f6..8e1499d5 100644
--- a/src/items/normal.rs
+++ b/src/items/normal.rs
@@ -33,7 +33,12 @@ impl MenuItem {
/// - `text` could optionally contain an `&` before a character to assign this character as the mnemonic
/// for this menu item. To display a `&` without assigning a mnemenonic, use `&&`.
pub fn new>(text: S, enabled: bool, accelerator: Option) -> Self {
- let item = crate::platform_impl::MenuChild::new(text.as_ref(), enabled, accelerator, None);
+ let item = crate::platform_impl::MenuChild::new_menu_item(
+ text.as_ref(),
+ enabled,
+ accelerator,
+ None,
+ );
Self {
id: Rc::new(item.id().clone()),
inner: Rc::new(RefCell::new(item)),
@@ -53,12 +58,14 @@ impl MenuItem {
let id = id.into();
Self {
id: Rc::new(id.clone()),
- inner: Rc::new(RefCell::new(crate::platform_impl::MenuChild::new(
- text.as_ref(),
- enabled,
- accelerator,
- Some(id),
- ))),
+ inner: Rc::new(RefCell::new(
+ crate::platform_impl::MenuChild::new_menu_item(
+ text.as_ref(),
+ enabled,
+ accelerator,
+ Some(id),
+ ),
+ )),
}
}
diff --git a/src/items/predefined.rs b/src/items/predefined.rs
index 8ed4a342..d229b0f2 100644
--- a/src/items/predefined.rs
+++ b/src/items/predefined.rs
@@ -305,43 +305,34 @@ impl PredefinedMenuItemType {
pub(crate) fn accelerator(&self) -> Option {
match self {
- PredefinedMenuItemType::Copy => Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyC)),
- PredefinedMenuItemType::Cut => Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyX)),
- PredefinedMenuItemType::Paste => Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyV)),
- PredefinedMenuItemType::Undo => Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyZ)),
+ PredefinedMenuItemType::Copy => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyC)),
+ PredefinedMenuItemType::Cut => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyX)),
+ PredefinedMenuItemType::Paste => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyV)),
+ PredefinedMenuItemType::Undo => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyZ)),
#[cfg(target_os = "macos")]
PredefinedMenuItemType::Redo => Some(Accelerator::new(
Some(CMD_OR_CTRL | Modifiers::SHIFT),
Code::KeyZ,
)),
#[cfg(not(target_os = "macos"))]
- PredefinedMenuItemType::Redo => Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyY)),
- PredefinedMenuItemType::SelectAll => {
- Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyA))
- }
- PredefinedMenuItemType::Minimize => {
- Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyM))
- }
+ PredefinedMenuItemType::Redo => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyY)),
+ PredefinedMenuItemType::SelectAll => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyA)),
+ PredefinedMenuItemType::Minimize => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyM)),
#[cfg(target_os = "macos")]
PredefinedMenuItemType::Fullscreen => Some(Accelerator::new(
Some(Modifiers::META | Modifiers::CONTROL),
Code::KeyF,
)),
- PredefinedMenuItemType::Hide => Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyH)),
- PredefinedMenuItemType::HideOthers => Some(Accelerator::new(
- Some(CMD_OR_CTRL | Modifiers::ALT),
- Code::KeyH,
- )),
- #[cfg(target_os = "macos")]
- PredefinedMenuItemType::CloseWindow => {
- Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyW))
+ PredefinedMenuItemType::Hide => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyH)),
+ PredefinedMenuItemType::HideOthers => {
+ Some(Accelerator::new(CMD_OR_CTRL | Modifiers::ALT, Code::KeyH))
}
+ #[cfg(target_os = "macos")]
+ PredefinedMenuItemType::CloseWindow => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyW)),
#[cfg(not(target_os = "macos"))]
- PredefinedMenuItemType::CloseWindow => {
- Some(Accelerator::new(Some(Modifiers::ALT), Code::F4))
- }
+ PredefinedMenuItemType::CloseWindow => Some(Accelerator::new(Modifiers::ALT, Code::F4)),
#[cfg(target_os = "macos")]
- PredefinedMenuItemType::Quit => Some(Accelerator::new(Some(CMD_OR_CTRL), Code::KeyQ)),
+ PredefinedMenuItemType::Quit => Some(Accelerator::new(CMD_OR_CTRL, Code::KeyQ)),
_ => None,
}
}
diff --git a/src/items/submenu.rs b/src/items/submenu.rs
index 3c21c382..89cb8834 100644
--- a/src/items/submenu.rs
+++ b/src/items/submenu.rs
@@ -236,7 +236,7 @@ impl ContextMenu for Submenu {
#[cfg(target_os = "linux")]
fn show_context_menu_for_gtk_window(
&self,
- w: >k::Window,
+ w: >k4::Window,
position: Option,
) -> bool {
self.inner
@@ -244,11 +244,6 @@ impl ContextMenu for Submenu {
.show_context_menu_for_gtk_window(w, position)
}
- #[cfg(target_os = "linux")]
- fn gtk_context_menu(&self) -> gtk::Menu {
- self.inner.borrow_mut().gtk_context_menu()
- }
-
#[cfg(target_os = "macos")]
unsafe fn show_context_menu_for_nsview(
&self,
diff --git a/src/lib.rs b/src/lib.rs
index 25773831..753e9966 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -378,16 +378,10 @@ pub trait ContextMenu {
#[cfg(target_os = "linux")]
fn show_context_menu_for_gtk_window(
&self,
- w: >k::Window,
+ w: >k4::Window,
position: Option,
) -> bool;
- /// Get the underlying gtk menu reserved for context menus.
- ///
- /// The returned [`gtk::Menu`] is valid as long as the `ContextMenu` is.
- #[cfg(target_os = "linux")]
- fn gtk_context_menu(&self) -> gtk::Menu;
-
/// Shows this menu as a context menu for the specified `NSView`.
///
/// - `position` is relative to the window top-left corner, if `None`, the cursor position is used.
diff --git a/src/menu.rs b/src/menu.rs
index 8d5c79fe..023d47c1 100644
--- a/src/menu.rs
+++ b/src/menu.rs
@@ -161,18 +161,20 @@ impl Menu {
self.inner.borrow().items()
}
- /// Adds this menu to a [`gtk::Window`]
+ /// Adds this menu to a [`gtk4::Window`]
///
- /// - `container`: this is an optional paramter to specify a container for the [`gtk::MenuBar`],
+ /// - `container`: this is an optional paramter to specify a container for the [`gtk4::PopoverMenuBar`],
/// it is highly recommended to pass a container, otherwise the menubar will be added directly to the window,
/// which is usually not the desired behavior.
- /// If using a [`gtk::Box`] as a container, it is added using [`Box::pack_start(menubar, false, false, 0)`](gtk::prelude::BoxExt::pack_start) then
- /// reordered to be the first child of [`gtk::Box`] using [`Box::reorder_child(menubar, 0)`](gtk::prelude::BoxExt::reorder_child).
+ /// Supported types of containers are [`gtk4::Box`], [`gtk4::Stack`] and [`gtk4::Fixed`]:
+ /// - [`gtk4::Box`], menu bar is add at the beginning using [`gtk4::prelude::BoxExt::prepend`]
+ /// - [`gtk4::Stack`], menu bar is added using [`gtk4::Stack::add_child`].
+ /// - [`gtk4::Fixed`], menu bar is put at 0,0 using [`gtk4::prelude::FixedExt::put`].
///
/// ## Example:
/// ```no_run
- /// let window = gtk::Window::builder().build();
- /// let vbox = gtk::Box::new(gtk::Orientation::Vertical, 0);
+ /// let window = gtk4::Window::builder().build();
+ /// let vbox = gtk4::Box::new(gtk4::Orientation::Vertical, 0);
/// let menu = muda::Menu::new();
/// // -- snip, add your menu items --
/// menu.init_for_gtk_window(&window, Some(&vbox));
@@ -185,9 +187,9 @@ impl Menu {
#[cfg(target_os = "linux")]
pub fn init_for_gtk_window(&self, window: &W, container: Option<&C>) -> crate::Result<()>
where
- W: gtk::prelude::IsA,
- W: gtk::prelude::IsA,
- C: gtk::prelude::IsA,
+ W: gtk4::prelude::IsA,
+ W: gtk4::prelude::IsA,
+ C: gtk4::prelude::IsA,
{
self.inner
.borrow_mut()
@@ -269,11 +271,12 @@ impl Menu {
self.inner.borrow_mut().haccel()
}
- /// Removes this menu from a [`gtk::Window`]
+ /// Removes this menu from a [`gtk4::Window`]
#[cfg(target_os = "linux")]
pub fn remove_for_gtk_window(&self, window: &W) -> crate::Result<()>
where
- W: gtk::prelude::IsA,
+ W: gtk4::prelude::IsA,
+ W: gtk4::prelude::IsA,
{
self.inner.borrow_mut().remove_for_gtk_window(window)
}
@@ -288,11 +291,11 @@ impl Menu {
self.inner.borrow_mut().remove_for_hwnd(hwnd)
}
- /// Hides this menu from a [`gtk::Window`]
+ /// Hides this menu from a [`gtk4::Window`]
#[cfg(target_os = "linux")]
pub fn hide_for_gtk_window(&self, window: &W) -> crate::Result<()>
where
- W: gtk::prelude::IsA,
+ W: gtk4::prelude::IsA,
{
self.inner.borrow_mut().hide_for_gtk_window(window)
}
@@ -307,11 +310,11 @@ impl Menu {
self.inner.borrow().hide_for_hwnd(hwnd)
}
- /// Shows this menu on a [`gtk::Window`]
+ /// Shows this menu on a [`gtk4::Window`]
#[cfg(target_os = "linux")]
pub fn show_for_gtk_window(&self, window: &W) -> crate::Result<()>
where
- W: gtk::prelude::IsA,
+ W: gtk4::prelude::IsA,
{
self.inner.borrow_mut().show_for_gtk_window(window)
}
@@ -326,21 +329,21 @@ impl Menu {
self.inner.borrow().show_for_hwnd(hwnd)
}
- /// Returns whether this menu visible on a [`gtk::Window`]
+ /// Returns whether this menu visible on a [`gtk4::Window`]
#[cfg(target_os = "linux")]
pub fn is_visible_on_gtk_window(&self, window: &W) -> bool
where
- W: gtk::prelude::IsA,
+ W: gtk4::prelude::IsA,
{
self.inner.borrow().is_visible_on_gtk_window(window)
}
#[cfg(target_os = "linux")]
- /// Returns the [`gtk::MenuBar`] that is associated with this window if it exists.
+ /// Returns the [`gtk4::MenuBar`] that is associated with this window if it exists.
/// This is useful to get information about the menubar for example its height.
- pub fn gtk_menubar_for_gtk_window(self, window: &W) -> Option
+ pub fn gtk_menubar_for_gtk_window(&self, window: &W) -> Option
where
- W: gtk::prelude::IsA,
+ W: gtk4::prelude::IsA,
{
self.inner.borrow().gtk_menubar_for_gtk_window(window)
}
@@ -394,7 +397,7 @@ impl ContextMenu for Menu {
#[cfg(target_os = "linux")]
fn show_context_menu_for_gtk_window(
&self,
- window: >k::Window,
+ window: >k4::Window,
position: Option,
) -> bool {
self.inner
@@ -402,11 +405,6 @@ impl ContextMenu for Menu {
.show_context_menu_for_gtk_window(window, position)
}
- #[cfg(target_os = "linux")]
- fn gtk_context_menu(&self) -> gtk::Menu {
- self.inner.borrow_mut().gtk_context_menu()
- }
-
#[cfg(target_os = "macos")]
unsafe fn show_context_menu_for_nsview(
&self,
diff --git a/src/menu_id.rs b/src/menu_id.rs
index 24036848..6c98f5c0 100644
--- a/src/menu_id.rs
+++ b/src/menu_id.rs
@@ -20,7 +20,7 @@ impl AsRef for MenuId {
impl From for MenuId {
fn from(value: T) -> Self {
- Self::new(value.to_string())
+ Self(value.to_string())
}
}
diff --git a/src/platform_impl/gtk/accelerator.rs b/src/platform_impl/gtk/accelerator.rs
index 7b31e522..d1b32795 100644
--- a/src/platform_impl/gtk/accelerator.rs
+++ b/src/platform_impl/gtk/accelerator.rs
@@ -1,182 +1,207 @@
-// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
-// SPDX-License-Identifier: Apache-2.0
-// SPDX-License-Identifier: MIT
-
-use gtk::gdk;
use keyboard_types::{Code, Modifiers};
-use crate::accelerator::{Accelerator, AcceleratorParseError};
+use crate::accelerator::Accelerator;
-pub fn to_gtk_mnemonic>(string: S) -> String {
- string
- .as_ref()
- .replace("&&", "[~~]")
- .replace('&', "_")
- .replace("[~~]", "&&")
- .replace("[~~]", "&")
+impl Accelerator {
+ pub fn to_gtk(&self) -> String {
+ let mut gtk = modifiers_to_gtk(self.mods);
+ gtk.push_str(code_to_gtk(self.key));
+ gtk
+ }
}
-pub fn from_gtk_mnemonic>(string: S) -> String {
- string
- .as_ref()
- .replace("__", "[~~]")
- .replace('_', "&")
- .replace("[~~]", "__")
+fn modifiers_to_gtk(mods: Modifiers) -> String {
+ let mut gtk = String::new();
+
+ if mods.shift() {
+ gtk.push_str("");
+ }
+ if mods.ctrl() {
+ gtk.push_str("");
+ }
+ if mods.alt() {
+ gtk.push_str("");
+ }
+ if mods.meta() {
+ gtk.push_str("");
+ }
+ gtk
}
-pub fn parse_accelerator(
- accelerator: &Accelerator,
-) -> Result<(gdk::ModifierType, u32), AcceleratorParseError> {
- let key = match &accelerator.key {
- Code::KeyA => 'A' as u32,
- Code::KeyB => 'B' as u32,
- Code::KeyC => 'C' as u32,
- Code::KeyD => 'D' as u32,
- Code::KeyE => 'E' as u32,
- Code::KeyF => 'F' as u32,
- Code::KeyG => 'G' as u32,
- Code::KeyH => 'H' as u32,
- Code::KeyI => 'I' as u32,
- Code::KeyJ => 'J' as u32,
- Code::KeyK => 'K' as u32,
- Code::KeyL => 'L' as u32,
- Code::KeyM => 'M' as u32,
- Code::KeyN => 'N' as u32,
- Code::KeyO => 'O' as u32,
- Code::KeyP => 'P' as u32,
- Code::KeyQ => 'Q' as u32,
- Code::KeyR => 'R' as u32,
- Code::KeyS => 'S' as u32,
- Code::KeyT => 'T' as u32,
- Code::KeyU => 'U' as u32,
- Code::KeyV => 'V' as u32,
- Code::KeyW => 'W' as u32,
- Code::KeyX => 'X' as u32,
- Code::KeyY => 'Y' as u32,
- Code::KeyZ => 'Z' as u32,
- Code::Digit0 => '0' as u32,
- Code::Digit1 => '1' as u32,
- Code::Digit2 => '2' as u32,
- Code::Digit3 => '3' as u32,
- Code::Digit4 => '4' as u32,
- Code::Digit5 => '5' as u32,
- Code::Digit6 => '6' as u32,
- Code::Digit7 => '7' as u32,
- Code::Digit8 => '8' as u32,
- Code::Digit9 => '9' as u32,
- Code::Comma => ',' as u32,
- Code::Minus => '-' as u32,
- Code::Period => '.' as u32,
- Code::Space => ' ' as u32,
- Code::Equal => '=' as u32,
- Code::Semicolon => ';' as u32,
- Code::Slash => '/' as u32,
- Code::Backslash => '\\' as u32,
- Code::Quote => '\'' as u32,
- Code::Backquote => '`' as u32,
- Code::BracketLeft => '[' as u32,
- Code::BracketRight => ']' as u32,
- key => {
- if let Some(gdk_key) = key_to_raw_key(key) {
- *gdk_key
- } else {
- return Err(AcceleratorParseError::UnsupportedKey(key.to_string()));
- }
- }
- };
-
- Ok((modifiers_to_gdk_modifier_type(accelerator.mods), key))
-}
-
-fn modifiers_to_gdk_modifier_type(modifiers: Modifiers) -> gdk::ModifierType {
- let mut result = gdk::ModifierType::empty();
-
- result.set(
- gdk::ModifierType::MOD1_MASK,
- modifiers.contains(Modifiers::ALT),
- );
- result.set(
- gdk::ModifierType::CONTROL_MASK,
- modifiers.contains(Modifiers::CONTROL),
- );
- result.set(
- gdk::ModifierType::SHIFT_MASK,
- modifiers.contains(Modifiers::SHIFT),
- );
- result.set(
- gdk::ModifierType::META_MASK,
- modifiers.contains(Modifiers::SUPER),
- );
-
- result
-}
-
-fn key_to_raw_key(src: &Code) -> Option {
- use gdk::keys::constants::*;
- Some(match src {
- Code::Escape => Escape,
- Code::Backspace => BackSpace,
-
- Code::Tab => Tab,
- Code::Enter => Return,
-
- Code::ControlLeft => Control_L,
- Code::AltLeft => Alt_L,
- Code::ShiftLeft => Shift_L,
- Code::MetaLeft => Super_L,
-
- Code::ControlRight => Control_R,
- Code::AltRight => Alt_R,
- Code::ShiftRight => Shift_R,
- Code::MetaRight => Super_R,
-
- Code::CapsLock => Caps_Lock,
- Code::F1 => F1,
- Code::F2 => F2,
- Code::F3 => F3,
- Code::F4 => F4,
- Code::F5 => F5,
- Code::F6 => F6,
- Code::F7 => F7,
- Code::F8 => F8,
- Code::F9 => F9,
- Code::F10 => F10,
- Code::F11 => F11,
- Code::F12 => F12,
- Code::F13 => F13,
- Code::F14 => F14,
- Code::F15 => F15,
- Code::F16 => F16,
- Code::F17 => F17,
- Code::F18 => F18,
- Code::F19 => F19,
- Code::F20 => F20,
- Code::F21 => F21,
- Code::F22 => F22,
- Code::F23 => F23,
- Code::F24 => F24,
-
- Code::PrintScreen => Print,
- Code::ScrollLock => Scroll_Lock,
- // Pause/Break not audio.
- Code::Pause => Pause,
-
- Code::Insert => Insert,
- Code::Delete => Delete,
- Code::Home => Home,
- Code::End => End,
- Code::PageUp => Page_Up,
- Code::PageDown => Page_Down,
-
- Code::NumLock => Num_Lock,
-
- Code::ArrowUp => Up,
- Code::ArrowDown => Down,
- Code::ArrowLeft => Left,
- Code::ArrowRight => Right,
-
- Code::ContextMenu => Menu,
- Code::WakeUp => WakeUp,
- _ => return None,
- })
+fn code_to_gtk(code: Code) -> &'static str {
+ match code {
+ Code::Backquote => "grave",
+ Code::Backslash => "backslash",
+ Code::BracketLeft => "bracketleft",
+ Code::BracketRight => "bracketright",
+ Code::Comma => "comma",
+ Code::Digit0 => "1",
+ Code::Digit1 => "1",
+ Code::Digit2 => "2",
+ Code::Digit3 => "3",
+ Code::Digit4 => "4",
+ Code::Digit5 => "5",
+ Code::Digit6 => "6",
+ Code::Digit7 => "7",
+ Code::Digit8 => "8",
+ Code::Digit9 => "9",
+ Code::Equal => "equal",
+ Code::KeyA => "A",
+ Code::KeyB => "B",
+ Code::KeyC => "C",
+ Code::KeyD => "D",
+ Code::KeyE => "E",
+ Code::KeyF => "F",
+ Code::KeyG => "G",
+ Code::KeyH => "H",
+ Code::KeyI => "I",
+ Code::KeyJ => "J",
+ Code::KeyK => "K",
+ Code::KeyL => "L",
+ Code::KeyM => "M",
+ Code::KeyN => "N",
+ Code::KeyO => "O",
+ Code::KeyP => "P",
+ Code::KeyQ => "Q",
+ Code::KeyR => "R",
+ Code::KeyS => "S",
+ Code::KeyT => "T",
+ Code::KeyU => "U",
+ Code::KeyV => "V",
+ Code::KeyW => "W",
+ Code::KeyX => "X",
+ Code::KeyY => "Y",
+ Code::KeyZ => "Z",
+ Code::Minus => "minus",
+ Code::Period => "period",
+ Code::Quote => "quotedbl",
+ Code::Semicolon => "semicolon",
+ Code::Slash => "slash",
+ Code::AltLeft => "Alt_L",
+ Code::AltRight => "Alt_R",
+ Code::Backspace => "BackSpace",
+ Code::CapsLock => "Caps_Lock",
+ Code::ContextMenu => "Menu",
+ Code::ControlLeft => "Control_L",
+ Code::ControlRight => "Control_R",
+ Code::Enter => "Return",
+ Code::MetaLeft => "Meta_L",
+ Code::MetaRight => "Meta_R",
+ Code::ShiftLeft => "Shift_L",
+ Code::ShiftRight => "Shift_R",
+ Code::Space => "space",
+ Code::Tab => "Tab",
+ Code::Delete => "Delete",
+ Code::End => "End",
+ Code::Help => "Help",
+ Code::Home => "Home",
+ Code::Insert => "Insert",
+ Code::PageDown => "Page_Down",
+ Code::PageUp => "Page_Up",
+ Code::ArrowDown => "downarrow",
+ Code::ArrowLeft => "leftarrow",
+ Code::ArrowRight => "rightarrow",
+ Code::ArrowUp => "uparrow",
+ Code::NumLock => "Num_Lock",
+ Code::Numpad0 => "KP_0",
+ Code::Numpad1 => "KP_1",
+ Code::Numpad2 => "KP_2",
+ Code::Numpad3 => "KP_3",
+ Code::Numpad4 => "KP_4",
+ Code::Numpad5 => "KP_5",
+ Code::Numpad6 => "KP_6",
+ Code::Numpad7 => "KP_7",
+ Code::Numpad8 => "KP_8",
+ Code::Numpad9 => "KP_9",
+ Code::NumpadAdd => "KP_Add",
+ Code::NumpadClear => "Clear",
+ Code::NumpadComma => "KP_Separator",
+ Code::NumpadDecimal => "KP_Decimal",
+ Code::NumpadDivide => "KP_Divide",
+ Code::NumpadEnter => "KP_Enter",
+ Code::NumpadEqual => "KP_Equal",
+ Code::NumpadHash => "numbersign",
+ Code::NumpadMemoryAdd => "KP_Add",
+ Code::NumpadMemoryClear => "Clear",
+ Code::NumpadMemorySubtract => "KP_Subtract",
+ Code::NumpadMultiply => "KP_Multiply",
+ Code::NumpadParenLeft => "parenleft",
+ Code::NumpadParenRight => "parenright",
+ Code::NumpadStar => "asterisk",
+ Code::NumpadSubtract => "KP_Subtract",
+ Code::Escape => "Escape",
+ Code::Fn => "F",
+ Code::FnLock => "F",
+ Code::PrintScreen => "Print",
+ Code::ScrollLock => "Scroll_Lock",
+ Code::Pause => "Pause",
+ Code::BrowserBack => "Back",
+ Code::BrowserFavorites => "Favorites",
+ Code::BrowserForward => "Forward",
+ Code::BrowserHome => "HomePage",
+ Code::BrowserRefresh => "Reload",
+ Code::BrowserSearch => "Search",
+ Code::BrowserStop => "Stop",
+ Code::Eject => "Eject",
+ Code::LaunchApp1 => "Launch0",
+ Code::LaunchApp2 => "Launch1",
+ Code::LaunchMail => "Mail",
+ Code::MediaPlayPause => "AudioPlay",
+ Code::MediaSelect => "AudioMedia",
+ Code::MediaStop => "AudioStop",
+ Code::MediaTrackNext => "AudioNext",
+ Code::MediaTrackPrevious => "AudioPrev",
+ Code::Power => "PowerOff",
+ Code::Sleep => "Sleep",
+ Code::AudioVolumeDown => "AudioLowerVolume",
+ Code::AudioVolumeMute => "AudioMute",
+ Code::AudioVolumeUp => "AudioRaiseVolume",
+ Code::WakeUp => "WakeUp",
+ Code::Hyper => "Hyper_L",
+ Code::Super => "Super_L",
+ Code::Suspend => "Suspend",
+ Code::Copy => "Copy",
+ Code::Cut => "Cut",
+ Code::Find => "Find",
+ Code::Open => "Open",
+ Code::Paste => "Paste",
+ Code::Select => "Select",
+ Code::Undo => "Undo",
+ Code::F1 => "F1",
+ Code::F2 => "F2",
+ Code::F3 => "F3",
+ Code::F4 => "F4",
+ Code::F5 => "F5",
+ Code::F6 => "F6",
+ Code::F7 => "F7",
+ Code::F8 => "F8",
+ Code::F9 => "F9",
+ Code::F10 => "F10",
+ Code::F11 => "F11",
+ Code::F12 => "F12",
+ Code::F13 => "F13",
+ Code::F14 => "F14",
+ Code::F15 => "F15",
+ Code::F16 => "F16",
+ Code::F17 => "F17",
+ Code::F18 => "F18",
+ Code::F19 => "F19",
+ Code::F20 => "F20",
+ Code::F21 => "F21",
+ Code::F22 => "F22",
+ Code::F23 => "F23",
+ Code::F24 => "F24",
+ Code::F25 => "F25",
+ Code::F26 => "F26",
+ Code::F27 => "F27",
+ Code::F28 => "F28",
+ Code::F29 => "F29",
+ Code::F30 => "F30",
+ Code::F31 => "F31",
+ Code::F32 => "F32",
+ Code::F33 => "F33",
+ Code::F34 => "F34",
+ Code::F35 => "F35",
+ _ => return "",
+ }
}
diff --git a/src/platform_impl/gtk/icon.rs b/src/platform_impl/gtk/icon.rs
index 31ddd003..825b8a45 100644
--- a/src/platform_impl/gtk/icon.rs
+++ b/src/platform_impl/gtk/icon.rs
@@ -2,32 +2,11 @@
// Copyright 2021-2022 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
-use gtk::gdk_pixbuf::{Colorspace, Pixbuf};
-
use crate::icon::BadIcon;
/// An icon used for the window titlebar, taskbar, etc.
#[derive(Debug, Clone)]
-pub struct PlatformIcon {
- raw: Vec,
- width: i32,
- height: i32,
- row_stride: i32,
-}
-
-impl From for Pixbuf {
- fn from(icon: PlatformIcon) -> Self {
- Pixbuf::from_mut_slice(
- icon.raw,
- gtk::gdk_pixbuf::Colorspace::Rgb,
- true,
- 8,
- icon.width,
- icon.height,
- icon.row_stride,
- )
- }
-}
+pub struct PlatformIcon(gtk4::gio::BytesIcon);
impl PlatformIcon {
/// Creates an `Icon` from 32bpp RGBA data.
@@ -35,31 +14,23 @@ impl PlatformIcon {
/// The length of `rgba` must be divisible by 4, and `width * height` must equal
/// `rgba.len() / 4`. Otherwise, this will return a `BadIcon` error.
pub fn from_rgba(rgba: Vec, width: u32, height: u32) -> Result {
- let row_stride =
- Pixbuf::calculate_rowstride(Colorspace::Rgb, true, 8, width as i32, height as i32);
- Ok(Self {
- raw: rgba,
- width: width as i32,
- height: height as i32,
- row_stride,
- })
- }
+ let mut w = Vec::with_capacity(rgba.len());
+
+ let mut encoder = png::Encoder::new(&mut w, width, height);
+ encoder.set_color(png::ColorType::Rgba);
+ encoder.set_depth(png::BitDepth::Eight);
+ let mut writer = encoder.write_header().map_err(BadIcon::PngEncodingError)?;
+ writer
+ .write_image_data(&rgba)
+ .map_err(BadIcon::PngEncodingError)?;
+ writer.finish().map_err(BadIcon::PngEncodingError)?;
+
+ let bytes = gtk4::glib::Bytes::from_owned(w);
- pub fn to_pixbuf(&self) -> Pixbuf {
- Pixbuf::from_mut_slice(
- self.raw.clone(),
- gtk::gdk_pixbuf::Colorspace::Rgb,
- true,
- 8,
- self.width,
- self.height,
- self.row_stride,
- )
+ Ok(Self(gtk4::gio::BytesIcon::new(&bytes)))
}
- pub fn to_pixbuf_scale(&self, w: i32, h: i32) -> Pixbuf {
- self.to_pixbuf()
- .scale_simple(w, h, gtk::gdk_pixbuf::InterpType::Bilinear)
- .unwrap()
+ pub fn bytes_icon(&self) -> >k4::gio::BytesIcon {
+ &self.0
}
}
diff --git a/src/platform_impl/gtk/mnemonic.rs b/src/platform_impl/gtk/mnemonic.rs
new file mode 100644
index 00000000..dba54ca1
--- /dev/null
+++ b/src/platform_impl/gtk/mnemonic.rs
@@ -0,0 +1,71 @@
+// Copyright 2022-2022 Tauri Programme within The Commons Conservancy
+// SPDX-License-Identifier: Apache-2.0
+// SPDX-License-Identifier: MIT
+
+/// Converts from muda mnemonic to gtk mnemonic
+///
+/// gtk uses underline (_) for mnemonic
+/// and two underlines (__) to escape it into a single underline
+/// while we use (&) and (&&), so we have to do a few conversions
+pub fn to_gtk_mnemonic>(string: S) -> String {
+ string
+ .as_ref()
+ // escape underlines
+ .replace("_", "__")
+ // perserve &&
+ .replace("&&", "[~~]")
+ // transfrom & -> _
+ .replace('&', "_")
+ // revert back && to unsecaped &
+ .replace("[~~]", "&")
+}
+
+/// Converts from gtk mnemonic to muda mnemonic
+///
+/// gtk uses underline (_) for mnemonic
+/// and two underlines (__) to escape it into a single underline
+/// while we use (&) and (&&), so we have to do a few conversions
+pub fn from_gtk_mnemonic>(string: S) -> String {
+ string
+ .as_ref()
+ // transform escaped & to unescaped &&
+ .replace("&", "&&")
+ // perserve __
+ .replace("__", "[~~]")
+ // transfrom _ -> &
+ .replace('_', "&")
+ // revert back __ to unescaped _
+ .replace("[~~]", "_")
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::platform_impl::platform::mnemonic::{from_gtk_mnemonic, to_gtk_mnemonic};
+
+ #[test]
+ fn it_converts() {
+ assert_eq!(to_gtk_mnemonic("H&ello"), "H_ello");
+ assert_eq!(to_gtk_mnemonic("H&&ello"), "H&ello");
+ assert_eq!(to_gtk_mnemonic("H&&&ello"), "H&_ello");
+ assert_eq!(to_gtk_mnemonic("H_ello"), "H__ello");
+ assert_eq!(to_gtk_mnemonic("H__ello"), "H____ello");
+ }
+
+ #[test]
+ fn it_converts_back() {
+ let str = "H&ello";
+ assert_eq!(from_gtk_mnemonic(to_gtk_mnemonic(str)), str);
+
+ let str = "H&&ello";
+ assert_eq!(from_gtk_mnemonic(to_gtk_mnemonic(str)), str);
+
+ let str = "H&&&ello";
+ assert_eq!(from_gtk_mnemonic(to_gtk_mnemonic(str)), str);
+
+ let str = "H_ello";
+ assert_eq!(from_gtk_mnemonic(to_gtk_mnemonic(str)), str);
+
+ let str = "H__ello";
+ assert_eq!(from_gtk_mnemonic(to_gtk_mnemonic(str)), str);
+ }
+}
diff --git a/src/platform_impl/gtk/mod.rs b/src/platform_impl/gtk/mod.rs
index 7a9875ab..06e4e93d 100644
--- a/src/platform_impl/gtk/mod.rs
+++ b/src/platform_impl/gtk/mod.rs
@@ -4,90 +4,100 @@
mod accelerator;
mod icon;
+mod mnemonic;
+use std::{
+ cell::RefCell,
+ collections::{hash_map::Entry, HashMap},
+ rc::Rc,
+};
+
+use dpi::Position;
+use gtk4::{gdk::Rectangle, gio, prelude::*};
pub(crate) use icon::PlatformIcon;
+use mnemonic::to_gtk_mnemonic;
use crate::{
accelerator::Accelerator,
- dpi::Position,
- icon::{Icon, NativeIcon},
- items::*,
util::{AddOp, Counter},
- IsMenuItem, MenuEvent, MenuId, MenuItemKind, MenuItemType,
-};
-use accelerator::{from_gtk_mnemonic, parse_accelerator, to_gtk_mnemonic};
-use glib::translate::ToGlibPtr;
-use gtk::{gdk, glib, prelude::*, AboutDialog, Container, Orientation};
-use std::{
- cell::RefCell,
- collections::{hash_map::Entry, HashMap},
- rc::Rc,
- sync::atomic::{AtomicBool, Ordering},
+ Icon, IsMenuItem, MenuEvent, MenuId, MenuItemKind, MenuItemType, NativeIcon,
+ PredefinedMenuItemType,
};
static COUNTER: Counter = Counter::new();
-macro_rules! is_item_supported {
- ($item:tt) => {{
- let child = $item.child();
- let child_ = child.borrow();
- let supported = if let Some(predefined_item_type) = &child_.predefined_item_type {
- matches!(
- predefined_item_type,
- PredefinedMenuItemType::Separator
- | PredefinedMenuItemType::Copy
- | PredefinedMenuItemType::Cut
- | PredefinedMenuItemType::Paste
- | PredefinedMenuItemType::SelectAll
- | PredefinedMenuItemType::About(_)
- )
- } else {
- true
- };
- drop(child_);
- supported
- }};
+const DEFAULT_ACTION_GROUP: &str = "muda";
+const ACTION_GROUP_DATA_KEY: &str = "mudaActionGroup";
+
+enum GtkMenuBar {
+ MenuBar {
+ widget: gtk4::PopoverMenuBar,
+ menu: gio::Menu,
+ app: gtk4::Application,
+ },
+ ContextMenu {
+ widget: gtk4::PopoverMenu,
+ menu: gio::Menu,
+ app: gtk4::Application,
+ },
}
-macro_rules! return_if_item_not_supported {
- ($item:tt) => {
- if !is_item_supported!($item) {
- return Ok(());
+impl GtkMenuBar {
+ fn new(app: gtk4::Application) -> Self {
+ let menu = gio::Menu::new();
+ let widget = gtk4::PopoverMenuBar::from_model(Some(&menu));
+ Self::MenuBar { widget, menu, app }
+ }
+
+ fn new_context(app: gtk4::Application) -> Self {
+ let menu = gio::Menu::new();
+ let widget = gtk4::PopoverMenu::from_model(Some(&menu));
+ Self::ContextMenu { widget, menu, app }
+ }
+
+ fn applicaiton(&self) -> >k4::Application {
+ match self {
+ GtkMenuBar::MenuBar { app, .. } => app,
+ GtkMenuBar::ContextMenu { app, .. } => app,
}
- };
-}
+ }
-pub struct Menu {
- id: MenuId,
- children: Vec>>,
- // TODO: maybe save a reference to the window?
- gtk_menubars: HashMap,
- accel_group: Option,
- gtk_menu: (u32, Option), // dedicated menu for tray or context menus
-}
+ fn menu_bar(&self) -> >k4::PopoverMenuBar {
+ match self {
+ GtkMenuBar::MenuBar { widget, .. } => widget,
+ _ => unreachable!("This is a bug report to https://github.com/tauri-apps/muda"),
+ }
+ }
-impl Drop for Menu {
- fn drop(&mut self) {
- for (id, menu) in &self.gtk_menubars {
- drop_children_from_menu_and_destroy(*id, menu, &self.children);
- unsafe { menu.destroy() }
+ fn context_menu(&self) -> >k4::PopoverMenu {
+ match self {
+ GtkMenuBar::ContextMenu { widget, .. } => widget,
+ _ => unreachable!("This is a bug report to https://github.com/tauri-apps/muda"),
}
+ }
- if let (id, Some(menu)) = &self.gtk_menu {
- drop_children_from_menu_and_destroy(*id, menu, &self.children);
- unsafe { menu.destroy() }
+ fn menu(&self) -> &gio::Menu {
+ match self {
+ GtkMenuBar::MenuBar { menu, .. } => menu,
+ GtkMenuBar::ContextMenu { menu, .. } => menu,
}
}
}
+pub struct Menu {
+ id: MenuId,
+ instances: HashMap,
+ ctx_menu_id: u32,
+ children: Vec>>,
+}
+
impl Menu {
pub fn new(id: Option) -> Self {
Self {
id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
+ instances: HashMap::new(),
+ ctx_menu_id: COUNTER.next(),
children: Vec::new(),
- gtk_menubars: HashMap::new(),
- accel_group: None,
- gtk_menu: (COUNTER.next(), None),
}
}
@@ -95,151 +105,34 @@ impl Menu {
&self.id
}
- pub fn add_menu_item(&mut self, item: &dyn crate::IsMenuItem, op: AddOp) -> crate::Result<()> {
- if is_item_supported!(item) {
- for (menu_id, menu_bar) in &self.gtk_menubars {
- let gtk_item =
- item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true, true)?;
- match op {
- AddOp::Append => menu_bar.append(>k_item),
- AddOp::Insert(position) => menu_bar.insert(>k_item, position as i32),
- }
- gtk_item.show();
- }
-
- {
- if let (menu_id, Some(menu)) = &self.gtk_menu {
- let gtk_item =
- item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true, false)?;
- match op {
- AddOp::Append => menu.append(>k_item),
- AddOp::Insert(position) => menu.insert(>k_item, position as i32),
- }
- gtk_item.show();
- }
- }
- }
-
+ pub fn add_menu_item(&mut self, item: &dyn IsMenuItem, op: AddOp) -> crate::Result<()> {
match op {
AddOp::Append => self.children.push(item.child()),
- AddOp::Insert(position) => self.children.insert(position, item.child()),
+ AddOp::Insert(i) => self.children.insert(i, item.child()),
}
- Ok(())
- }
-
- fn add_menu_item_with_id(&self, item: &dyn crate::IsMenuItem, id: u32) -> crate::Result<()> {
- return_if_item_not_supported!(item);
-
- for (menu_id, menu_bar) in self.gtk_menubars.iter().filter(|m| *m.0 == id) {
- let gtk_item =
- item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true, true)?;
- menu_bar.append(>k_item);
- gtk_item.show();
+ for (menu_id, menu_bar) in &self.instances {
+ let gtk_item = item.make_gtk_menu_item(menu_bar.applicaiton(), *menu_id)?;
+ match op {
+ AddOp::Append => menu_bar.menu().append_item(>k_item),
+ AddOp::Insert(position) => menu_bar.menu().insert_item(position as i32, >k_item),
+ }
}
Ok(())
}
- fn add_menu_item_to_context_menu(&self, item: &dyn crate::IsMenuItem) -> crate::Result<()> {
- return_if_item_not_supported!(item);
-
- let (menu_id, menu) = &self.gtk_menu;
- if let Some(menu) = menu {
- let gtk_item =
- item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true, false)?;
- menu.append(>k_item);
- gtk_item.show();
+ pub fn add_menu_item_with_id(&mut self, item: &dyn IsMenuItem, id: u32) -> crate::Result<()> {
+ for (menu_id, menu_bar) in self.instances.iter().filter(|m| *m.0 == id) {
+ let gtk_item = item.make_gtk_menu_item(menu_bar.applicaiton(), *menu_id)?;
+ menu_bar.menu().append_item(>k_item);
}
Ok(())
}
- pub fn remove(&mut self, item: &dyn crate::IsMenuItem) -> crate::Result<()> {
- self.remove_inner(item, true, None)
- }
-
- fn remove_inner(
- &mut self,
- item: &dyn crate::IsMenuItem,
- remove_from_cache: bool,
- id: Option,
- ) -> crate::Result<()> {
- // get child
- let child = {
- let index = self
- .children
- .iter()
- .position(|e| e.borrow().id == item.id())
- .ok_or(crate::Error::NotAChildOfThisMenu)?;
- if remove_from_cache {
- self.children.remove(index)
- } else {
- self.children.get(index).cloned().unwrap()
- }
- };
-
- for (menu_id, menu_bar) in &self.gtk_menubars {
- // check if we are removing this item from all gtk_menubars
- // which is usually when this is the item the user is actaully removing
- // or if we are removing from a specific menu (id)
- // which is when the actual item being removed is a submenu
- // and we are iterating through its children and removing
- // each child gtk items that are related to this submenu.
- if id.map(|i| i == *menu_id).unwrap_or(true) {
- // bail this is not a supported item like a close_window predefined menu item
- if is_item_supported!(item) {
- let mut child_ = child.borrow_mut();
-
- if child_.item_type == MenuItemType::Submenu {
- let menus = child_.gtk_menus.as_ref().unwrap().get(menu_id).cloned();
- if let Some(menus) = menus {
- for (id, menu) in menus {
- // iterate through children and only remove the gtk items
- // related to this submenu
- for item in child_.items() {
- child_.remove_inner(item.as_ref(), false, Some(id))?;
- }
- unsafe { menu.destroy() };
- }
- }
- child_.gtk_menus.as_mut().unwrap().remove(menu_id);
- }
-
- // remove all the gtk items that are related to this gtk menubar and destroy it
- if let Some(items) = child_.gtk_menu_items.borrow_mut().remove(menu_id) {
- for item in items {
- menu_bar.remove(&item);
- if let Some(accel_group) = &child_.accel_group {
- if let Some((mods, key)) = child_.gtk_accelerator {
- item.remove_accelerator(accel_group, key, mods);
- }
- }
- unsafe { item.destroy() };
- }
- };
- }
- }
- }
-
- // remove from the gtk menu assigned to the context menu
- if remove_from_cache {
- if let (id, Some(menu)) = &self.gtk_menu {
- let child_ = child.borrow_mut();
- if let Some(items) = child_.gtk_menu_items.borrow_mut().remove(id) {
- for item in items {
- menu.remove(&item);
- if let Some(accel_group) = &child_.accel_group {
- if let Some((mods, key)) = child_.gtk_accelerator {
- item.remove_accelerator(accel_group, key, mods);
- }
- }
- unsafe { item.destroy() };
- }
- };
- }
- }
- Ok(())
+ pub fn remove(&self, item: &dyn IsMenuItem) -> crate::Result<()> {
+ todo!()
}
pub fn items(&self) -> Vec {
@@ -255,1207 +148,713 @@ impl Menu {
container: Option<&C>,
) -> crate::Result<()>
where
- W: IsA,
- W: IsA,
- C: IsA,
+ W: gtk4::prelude::IsA,
+ W: gtk4::prelude::IsA,
+ C: gtk4::prelude::IsA,
{
let id = window.as_ptr() as u32;
- if self.accel_group.is_none() {
- self.accel_group = Some(gtk::AccelGroup::new());
- }
+ let Some(app) = window.application() else {
+ return Err(crate::Error::GtkWindowWithoutApplication);
+ };
// This is the first time this method has been called on this window
- // so we need to create the menubar and its parent box
- if let Entry::Vacant(e) = self.gtk_menubars.entry(id) {
- let menu_bar = gtk::MenuBar::new();
- e.insert(menu_bar);
+ // so we need to create the menubar
+ if let Entry::Vacant(e) = self.instances.entry(id) {
+ e.insert(GtkMenuBar::new(app.clone()));
} else {
return Err(crate::Error::AlreadyInitialized);
}
- // Construct the entries of the menubar
- let menu_bar = &self.gtk_menubars[&id];
-
- window.add_accel_group(self.accel_group.as_ref().unwrap());
+ let action_group = action_group_from_app(&app);
+ window.insert_action_group(DEFAULT_ACTION_GROUP, Some(&action_group));
for item in self.items() {
self.add_menu_item_with_id(item.as_ref(), id)?;
}
+ let menu_bar = self.instances[&id].menu_bar();
+
// add the menubar to the specified widget, otherwise to the window
if let Some(container) = container {
if container.type_().name() == "GtkBox" {
- let gtk_box = container.dynamic_cast_ref::().unwrap();
- gtk_box.pack_start(menu_bar, false, false, 0);
- gtk_box.reorder_child(menu_bar, 0);
- } else {
- container.add(menu_bar);
+ let gtk_box = container.dynamic_cast_ref::().unwrap();
+ gtk_box.prepend(menu_bar);
+ } else if container.type_().name() == "GtkFixed" {
+ let gtk_box = container.dynamic_cast_ref::().unwrap();
+ gtk_box.put(menu_bar, 0., 0.);
+ } else if container.type_().name() == "GtkStack" {
+ let gtk_box = container.dynamic_cast_ref::().unwrap();
+ gtk_box.add_child(menu_bar);
}
} else {
- window.add(menu_bar);
+ window.set_child(Some(menu_bar));
}
- // Show the menubar
- menu_bar.show();
+ // show the menu bar
+ menu_bar.set_visible(true);
Ok(())
}
pub fn remove_for_gtk_window(&mut self, window: &W) -> crate::Result<()>
where
- W: IsA,
+ W: gtk4::prelude::IsA,
+ W: gtk4::prelude::IsA,
{
let id = window.as_ptr() as u32;
- // Remove from our cache
- let menu_bar = self
- .gtk_menubars
- .remove(&id)
- .ok_or(crate::Error::NotInitialized)?;
+ let Some(_menu_bar) = self.instances.remove(&id) else {
+ return Err(crate::Error::NotInitialized);
+ };
- for item in self.items() {
- let _ = self.remove_inner(item.as_ref(), false, Some(id));
- }
+ window.insert_action_group(DEFAULT_ACTION_GROUP, None::<&gio::SimpleActionGroup>);
+
+ // TODO: destroy the menu bar
- // Remove the [`gtk::Menubar`] from the widget tree
- unsafe { menu_bar.destroy() };
- // Detach the accelerators from the window
- window.remove_accel_group(self.accel_group.as_ref().unwrap());
Ok(())
}
- pub fn hide_for_gtk_window(&mut self, window: &W) -> crate::Result<()>
+ pub fn hide_for_gtk_window(&self, window: &W) -> crate::Result<()>
where
- W: IsA,
+ W: gtk4::prelude::IsA,
{
- self.gtk_menubars
- .get(&(window.as_ptr() as u32))
- .ok_or(crate::Error::NotInitialized)?
- .hide();
+ let id = window.as_ptr() as u32;
+ let Some(menu_bar) = self.instances.get(&id) else {
+ return Err(crate::Error::NotInitialized);
+ };
+ menu_bar.menu_bar().set_visible(false);
Ok(())
}
pub fn show_for_gtk_window(&self, window: &W) -> crate::Result<()>
where
- W: IsA,
+ W: gtk4::prelude::IsA,
{
- self.gtk_menubars
- .get(&(window.as_ptr() as u32))
- .ok_or(crate::Error::NotInitialized)?
- .show_all();
+ let id = window.as_ptr() as u32;
+ let Some(menu_bar) = self.instances.get(&id) else {
+ return Err(crate::Error::NotInitialized);
+ };
+ menu_bar.menu_bar().set_visible(true);
Ok(())
}
+ #[cfg(target_os = "linux")]
pub fn is_visible_on_gtk_window(&self, window: &W) -> bool
where
- W: IsA,
+ W: gtk4::prelude::IsA,
{
- self.gtk_menubars
- .get(&(window.as_ptr() as u32))
- .map(|m| m.get_visible())
+ let id = window.as_ptr() as u32;
+ self.instances
+ .get(&id)
+ .map(|m| m.menu_bar().is_visible())
.unwrap_or(false)
}
- pub fn gtk_menubar_for_gtk_window(&self, window: &W) -> Option
+ pub fn gtk_menubar_for_gtk_window(&self, window: &W) -> Option
where
- W: gtk::prelude::IsA,
+ W: gtk4::prelude::IsA,
{
- self.gtk_menubars.get(&(window.as_ptr() as u32)).cloned()
+ let id = window.as_ptr() as u32;
+ self.instances.get(&id).map(|m| m.menu_bar().clone())
}
pub fn show_context_menu_for_gtk_window(
&mut self,
- widget: &impl IsA,
+ window: >k4::Window,
position: Option,
) -> bool {
- show_context_menu(self.gtk_context_menu(), widget, position)
- }
+ let Some(app) = window.application() else {
+ return false; // TODO: better error
+ };
- pub fn gtk_context_menu(&mut self) -> gtk::Menu {
- let mut add_items = false;
+ if self.instances.get(&self.ctx_menu_id).is_none() {
+ let action_group = action_group_from_app(&app);
+ window.insert_action_group(DEFAULT_ACTION_GROUP, Some(&action_group));
- {
- if self.gtk_menu.1.is_none() {
- self.gtk_menu.1 = Some(gtk::Menu::new());
- add_items = true;
- }
- }
+ let menu = GtkMenuBar::new_context(app);
+
+ self.instances.insert(self.ctx_menu_id, menu);
- if add_items {
for item in self.items() {
- self.add_menu_item_to_context_menu(item.as_ref()).unwrap();
+ let _ = self.add_menu_item_with_id(item.as_ref(), self.ctx_menu_id);
}
}
- self.gtk_menu.1.as_ref().unwrap().clone()
- }
-}
-
-/// A generic child in a menu
-#[derive(Debug, Default)]
-pub struct MenuChild {
- // shared fields between submenus and menu items
- item_type: MenuItemType,
- text: String,
- enabled: bool,
- id: MenuId,
-
- gtk_menu_items: Rc>>>,
+ let (x, y) = match position {
+ Some(p) => p.to_logical::(window.scale_factor() as _).into(),
+ None => get_cursor_pos(window),
+ };
- // menu item fields
- accelerator: Option,
- gtk_accelerator: Option<(gdk::ModifierType, u32)>,
+ // SAFETY: it is guaranteed to exist due to the check above
+ let menu = self.instances.get(&self.ctx_menu_id).unwrap();
+ let context_menu = menu.context_menu();
- // predefined menu item fields
- predefined_item_type: Option,
+ if context_menu.parent().is_some() {
+ context_menu.unparent();
+ }
+ context_menu.set_parent(window);
- // check menu item fields
- checked: Option>,
- is_syncing_checked_state: Option>,
+ context_menu.popup();
+ context_menu.set_pointing_to(Some(&Rectangle::new(x, y, 0, 0)));
- // icon menu item fields
- icon: Option,
+ true
+ }
+}
- // submenu fields
- pub children: Option>>>,
- gtk_menus: Option>>,
- gtk_menu: Option<(u32, Option)>, // dedicated menu for tray or context menus
- accel_group: Option,
+#[derive(Clone)]
+enum GtkMenuChild {
+ Item {
+ item: gio::MenuItem,
+ app: gtk4::Application,
+ },
+ Submenu {
+ id: u32,
+ item: gio::MenuItem,
+ menu: gio::Menu,
+ app: gtk4::Application,
+ },
+ ContextMenu {
+ id: u32,
+ widget: gtk4::PopoverMenu,
+ menu: gio::Menu,
+ app: gtk4::Application,
+ },
}
-impl Drop for MenuChild {
- fn drop(&mut self) {
- if self.item_type == MenuItemType::Submenu {
- for menus in self.gtk_menus.as_ref().unwrap().values() {
- for (id, menu) in menus {
- drop_children_from_menu_and_destroy(*id, menu, self.children.as_ref().unwrap());
- unsafe { menu.destroy() };
- }
- }
+impl GtkMenuChild {
+ fn id(&self) -> u32 {
+ match self {
+ GtkMenuChild::Submenu { id, .. } => *id,
+ GtkMenuChild::ContextMenu { id, .. } => *id,
+ _ => unreachable!("This is a bug report to https://github.com/tauri-apps/muda"),
+ }
+ }
- if let Some((id, Some(menu))) = &self.gtk_menu {
- drop_children_from_menu_and_destroy(*id, menu, self.children.as_ref().unwrap());
- unsafe { menu.destroy() };
- }
+ fn application(&self) -> >k4::Application {
+ match self {
+ GtkMenuChild::Submenu { app, .. } => app,
+ GtkMenuChild::ContextMenu { app, .. } => app,
+ GtkMenuChild::Item { app, .. } => app,
}
+ }
- for items in self.gtk_menu_items.borrow().values() {
- for item in items {
- if let Some(accel_group) = &self.accel_group {
- if let Some((mods, key)) = self.gtk_accelerator {
- item.remove_accelerator(accel_group, key, mods);
- }
- }
- unsafe { item.destroy() };
- }
+ fn item(&self) -> &gio::MenuItem {
+ match self {
+ GtkMenuChild::Submenu { item, .. } => item,
+ GtkMenuChild::Item { item, .. } => item,
+ _ => unreachable!("This is a bug report to https://github.com/tauri-apps/muda"),
}
}
-}
-fn drop_children_from_menu_and_destroy(
- id: u32,
- menu: &impl IsA,
- children: &Vec>>,
-) {
- for child in children {
- let mut child_ = child.borrow_mut();
- {
- let mut menu_items = child_.gtk_menu_items.borrow_mut();
- if let Some(items) = menu_items.remove(&id) {
- for item in items {
- menu.remove(&item);
- if let Some(accel_group) = &child_.accel_group {
- if let Some((mods, key)) = child_.gtk_accelerator {
- item.remove_accelerator(accel_group, key, mods);
- }
- }
- unsafe { item.destroy() }
- }
- }
+ fn menu(&self) -> &gio::Menu {
+ match self {
+ GtkMenuChild::Submenu { menu, .. } => menu,
+ GtkMenuChild::ContextMenu { menu, .. } => menu,
+ _ => unreachable!("This is a bug report to https://github.com/tauri-apps/muda"),
}
+ }
- if child_.item_type == MenuItemType::Submenu {
- if let Some(menus) = child_.gtk_menus.as_mut().unwrap().remove(&id) {
- for (id, menu) in menus {
- let children = child_.children.as_ref().unwrap();
- drop_children_from_menu_and_destroy(id, &menu, children);
- unsafe { menu.destroy() }
- }
- }
+ fn context_menu(&self) -> >k4::PopoverMenu {
+ match self {
+ GtkMenuChild::ContextMenu { widget, .. } => widget,
+ _ => unreachable!("This is a bug report to https://github.com/tauri-apps/muda"),
}
}
}
-/// Constructors
+pub struct MenuChild {
+ id: MenuId,
+ text: String,
+ enabled: bool,
+ accelerator: Option,
+
+ checked: bool,
+
+ icon: Option,
+
+ type_: MenuItemType,
+
+ instances: HashMap>,
+ ctx_menu_id: u32,
+ children: Vec>>,
+
+ action: Option,
+}
+
impl MenuChild {
- pub fn new(
- text: &str,
- enabled: bool,
- accelerator: Option,
- id: Option,
- ) -> Self {
+ pub fn new_submenu(text: &str, enabled: bool, id: Option) -> Self {
Self {
+ id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
text: text.to_string(),
enabled,
- accelerator,
- id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
- item_type: MenuItemType::MenuItem,
- gtk_menu_items: Rc::new(RefCell::new(HashMap::new())),
- accel_group: None,
- checked: None,
- children: None,
- gtk_accelerator: None,
- gtk_menu: None,
- gtk_menus: None,
+ checked: false,
icon: None,
- is_syncing_checked_state: None,
- predefined_item_type: None,
+ accelerator: None,
+ type_: MenuItemType::Submenu,
+ ctx_menu_id: COUNTER.next(),
+ instances: HashMap::new(),
+ children: Vec::new(),
+ action: None,
}
}
- pub fn new_submenu(text: &str, enabled: bool, id: Option) -> Self {
- Self {
- text: text.to_string(),
- enabled,
- id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
- children: Some(Vec::new()),
- item_type: MenuItemType::Submenu,
- gtk_menu: Some((COUNTER.next(), None)),
- gtk_menu_items: Rc::new(RefCell::new(HashMap::new())),
- gtk_menus: Some(HashMap::new()),
- accel_group: None,
- gtk_accelerator: None,
- icon: None,
- is_syncing_checked_state: None,
- predefined_item_type: None,
- accelerator: None,
- checked: None,
+ fn create_gtk_item_for_submenu(
+ &mut self,
+ app: >k4::Application,
+ menu_id: u32,
+ ) -> crate::Result {
+ let menu = gio::Menu::new();
+ let item = gio::MenuItem::new_submenu(Some(&to_gtk_mnemonic(&self.text)), &menu);
+ item.set_detailed_action(&self.detailed_action());
+
+ if self.action.is_none() {
+ let action_group = action_group_from_app(&app);
+
+ let action = gio::SimpleAction::new(self.id.as_ref(), None);
+ action.connect_activate(|_, _| ());
+ action.set_enabled(self.enabled);
+ action_group.add_action(&action);
+
+ self.action = Some(action);
}
+
+ let id = COUNTER.next();
+ let child = GtkMenuChild::Submenu {
+ item: item.clone(),
+ menu,
+ id,
+ app: app.clone(),
+ };
+
+ self.instances.entry(menu_id).or_default().push(child);
+
+ for item in self.items() {
+ self.add_menu_item_with_id(item.as_ref(), id)?;
+ }
+
+ Ok(item)
}
- pub(crate) fn new_predefined(item_type: PredefinedMenuItemType, text: Option) -> Self {
- Self {
- text: text.unwrap_or_else(|| item_type.text().to_string()),
- enabled: true,
- accelerator: item_type.accelerator(),
- id: MenuId(COUNTER.next().to_string()),
- item_type: MenuItemType::Predefined,
- predefined_item_type: Some(item_type),
- gtk_menu_items: Rc::new(RefCell::new(HashMap::new())),
- accel_group: None,
- checked: None,
- children: None,
- gtk_accelerator: None,
- gtk_menu: None,
- gtk_menus: None,
- icon: None,
- is_syncing_checked_state: None,
+ pub fn add_menu_item(&mut self, item: &dyn IsMenuItem, op: AddOp) -> crate::Result<()> {
+ match op {
+ AddOp::Append => self.children.push(item.child()),
+ AddOp::Insert(i) => self.children.insert(i, item.child()),
+ }
+
+ for menus in self.instances.values() {
+ for gtk_child in menus {
+ let gtk_item = item.make_gtk_menu_item(gtk_child.application(), gtk_child.id())?;
+
+ match op {
+ AddOp::Append => gtk_child.menu().append_item(>k_item),
+ AddOp::Insert(position) => {
+ gtk_child.menu().insert_item(position as i32, >k_item)
+ }
+ }
+ }
}
+
+ Ok(())
}
- pub fn new_check(
- text: &str,
- enabled: bool,
- checked: bool,
- accelerator: Option,
- id: Option,
- ) -> Self {
- Self {
- text: text.to_string(),
- enabled,
- checked: Some(Rc::new(AtomicBool::new(checked))),
- is_syncing_checked_state: Some(Rc::new(AtomicBool::new(false))),
- accelerator,
- id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
- item_type: MenuItemType::Check,
- gtk_menu_items: Rc::new(RefCell::new(HashMap::new())),
- accel_group: None,
- children: None,
- gtk_accelerator: None,
- gtk_menu: None,
- gtk_menus: None,
- icon: None,
- predefined_item_type: None,
+ pub fn add_menu_item_with_id(&self, item: &dyn IsMenuItem, id: u32) -> crate::Result<()> {
+ for menus in self.instances.values() {
+ for gtk_child in menus.iter().filter(|m| m.id() == id) {
+ let gtk_item = item.make_gtk_menu_item(gtk_child.application(), gtk_child.id())?;
+ gtk_child.menu().append_item(>k_item);
+ }
}
+
+ Ok(())
}
- pub fn new_icon(
- text: &str,
- enabled: bool,
- icon: Option,
- accelerator: Option,
- id: Option,
- ) -> Self {
- Self {
- text: text.to_string(),
- enabled,
- icon,
- accelerator,
- id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
- item_type: MenuItemType::Icon,
- gtk_menu_items: Rc::new(RefCell::new(HashMap::new())),
- accel_group: None,
- checked: None,
- children: None,
- gtk_accelerator: None,
- gtk_menu: None,
- gtk_menus: None,
- is_syncing_checked_state: None,
- predefined_item_type: None,
+ pub fn remove(&self, item: &dyn IsMenuItem) -> crate::Result<()> {
+ todo!()
+ }
+
+ pub fn items(&self) -> Vec {
+ self.children
+ .iter()
+ .map(|c| c.borrow().kind(c.clone()))
+ .collect()
+ }
+
+ pub fn show_context_menu_for_gtk_window(
+ &mut self,
+ window: >k4::Window,
+ position: Option,
+ ) -> bool {
+ let Some(app) = window.application() else {
+ return false; // TODO: better error
+ };
+
+ if self.instances.get(&self.ctx_menu_id).is_none() {
+ let menu = gio::Menu::new();
+ let widget = gtk4::PopoverMenu::from_model(Some(&menu));
+
+ let action_group = action_group_from_app(&app);
+ window.insert_action_group(DEFAULT_ACTION_GROUP, Some(&action_group));
+
+ let menu = GtkMenuChild::ContextMenu {
+ id: self.ctx_menu_id,
+ widget,
+ menu,
+ app,
+ };
+
+ self.instances.insert(self.ctx_menu_id, vec![menu]);
+
+ for item in self.items() {
+ let _ = self.add_menu_item_with_id(item.as_ref(), self.ctx_menu_id);
+ }
+ }
+
+ // SAFETY: it is guaranteed to exist due to the check above
+ let menus = self.instances.get(&self.ctx_menu_id).unwrap();
+ let menu = menus.first().unwrap();
+
+ let (x, y) = match position {
+ Some(p) => p.to_logical::(window.scale_factor() as _).into(),
+ None => get_cursor_pos(window),
+ };
+
+ let context_menu = menu.context_menu();
+
+ if context_menu.parent().is_some() {
+ context_menu.unparent();
}
+ context_menu.set_parent(window);
+
+ context_menu.popup();
+ context_menu.set_pointing_to(Some(&Rectangle::new(x, y, 0, 0)));
+
+ true
}
+}
- pub fn new_native_icon(
+impl MenuChild {
+ pub fn new_menu_item(
text: &str,
enabled: bool,
- _native_icon: Option,
accelerator: Option,
id: Option,
) -> Self {
Self {
+ id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
text: text.to_string(),
enabled,
accelerator,
- id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
- item_type: MenuItemType::Icon,
- gtk_menu_items: Rc::new(RefCell::new(HashMap::new())),
- accel_group: None,
- checked: None,
- children: None,
- gtk_accelerator: None,
- gtk_menu: None,
- gtk_menus: None,
icon: None,
- is_syncing_checked_state: None,
- predefined_item_type: None,
+ checked: false,
+ type_: MenuItemType::MenuItem,
+ ctx_menu_id: 0,
+ instances: HashMap::new(),
+ children: Vec::new(),
+ action: None,
}
}
-}
-/// Shared methods
-impl MenuChild {
- pub(crate) fn item_type(&self) -> MenuItemType {
- self.item_type
+ fn create_gtk_item_for_menu_item(
+ &mut self,
+ app: >k4::Application,
+ menu_id: u32,
+ ) -> crate::Result {
+ let detailed_action = self.detailed_action();
+ let item = gio::MenuItem::new(Some(&to_gtk_mnemonic(&self.text)), Some(&detailed_action));
+
+ if let Some(accelerator) = &self.accelerator {
+ app.set_accels_for_action(&detailed_action, &[&accelerator.to_gtk()]);
+ }
+
+ if self.action.is_none() {
+ let action_group = action_group_from_app(&app);
+
+ let action = gio::SimpleAction::new(self.id.as_ref(), None);
+ let id = self.id.clone();
+ action.connect_activate(move |_, _| MenuEvent::send(MenuEvent { id: id.clone() }));
+ action.set_enabled(self.enabled);
+ action_group.add_action(&action);
+
+ self.action = Some(action);
+ }
+
+ let child = GtkMenuChild::Item {
+ item: item.clone(),
+ app: app.clone(),
+ };
+ self.instances.entry(menu_id).or_default().push(child);
+
+ Ok(item)
}
pub fn id(&self) -> &MenuId {
&self.id
}
+ fn detailed_action(&self) -> String {
+ format!("{DEFAULT_ACTION_GROUP}.{}", self.id.as_ref())
+ }
+
+ pub fn item_type(&self) -> &MenuItemType {
+ &self.type_
+ }
+
pub fn text(&self) -> String {
- match self
- .gtk_menu_items
- .borrow()
- .values()
- .collect::>()
- .first()
- .map(|v| v.first())
- .map(|e| e.map(|i| i.label().map(from_gtk_mnemonic)))
- {
- Some(Some(Some(text))) => text,
- _ => self.text.clone(),
- }
+ self.text.clone()
}
- pub fn set_text(&mut self, text: &str) {
- self.text = text.to_string();
- let text = to_gtk_mnemonic(text);
- for items in self.gtk_menu_items.borrow().values() {
- for i in items {
- i.set_label(&text);
- }
- }
+ pub fn set_text(&self, text: &str) {
+ todo!()
}
pub fn is_enabled(&self) -> bool {
- match self
- .gtk_menu_items
- .borrow()
- .values()
- .collect::>()
- .first()
- .map(|v| v.first())
- .map(|e| e.map(|i| i.is_sensitive()))
- {
- Some(Some(enabled)) => enabled,
- _ => self.enabled,
- }
+ self.enabled
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
- for items in self.gtk_menu_items.borrow().values() {
- for i in items {
- i.set_sensitive(enabled);
- }
+
+ if let Some(action) = self.action.as_ref() {
+ action.set_enabled(enabled);
}
}
pub fn set_accelerator(&mut self, accelerator: Option) -> crate::Result<()> {
- let prev_accel = self.gtk_accelerator.as_ref();
- let new_accel = accelerator.as_ref().map(parse_accelerator).transpose()?;
-
- for items in self.gtk_menu_items.borrow().values() {
- for i in items {
- if let Some((mods, key)) = prev_accel {
- if let Some(accel_group) = &self.accel_group {
- i.remove_accelerator(accel_group, *key, *mods);
- }
- }
- if let Some((mods, key)) = new_accel {
- if let Some(accel_group) = &self.accel_group {
- i.add_accelerator(
- "activate",
- accel_group,
- key,
- mods,
- gtk::AccelFlags::VISIBLE,
- )
- }
- }
- }
- }
-
- self.gtk_accelerator = new_accel;
self.accelerator = accelerator;
- Ok(())
- }
-}
-
-/// CheckMenuItem methods
-impl MenuChild {
- pub fn is_checked(&self) -> bool {
- match self
- .gtk_menu_items
- .borrow()
- .values()
- .collect::>()
- .first()
- .map(|v| v.first())
- .map(|e| e.map(|i| i.downcast_ref::().unwrap().is_active()))
- {
- Some(Some(checked)) => checked,
- _ => self.checked.as_ref().unwrap().load(Ordering::Relaxed),
+ let detailed_action = self.detailed_action();
+ let accelerator = accelerator.map(|a| a.to_gtk());
+ let accelerator = accelerator.as_deref().map(|a| [a]).unwrap_or_default();
+ for item in self.instances.values().flat_map(|v| v.iter()) {
+ let app = item.application();
+ app.set_accels_for_action(&detailed_action, accelerator.as_slice());
}
- }
- pub fn set_checked(&mut self, checked: bool) {
- self.checked
- .as_ref()
- .unwrap()
- .store(checked, Ordering::Release);
- let is_syncing = self.is_syncing_checked_state.as_ref().unwrap();
- is_syncing.store(true, Ordering::Release);
- for items in self.gtk_menu_items.borrow().values() {
- for i in items {
- i.downcast_ref::()
- .unwrap()
- .set_active(checked);
- }
- }
- is_syncing.store(false, Ordering::Release);
+ Ok(())
}
}
-/// IconMenuItem methods
impl MenuChild {
- pub fn set_icon(&mut self, icon: Option) {
- self.icon.clone_from(&icon);
-
- let pixbuf = icon.map(|i| i.inner.to_pixbuf_scale(16, 16));
- for items in self.gtk_menu_items.borrow().values() {
- for i in items {
- let box_container = i.child().unwrap().downcast::().unwrap();
- box_container.children()[0]
- .downcast_ref::()
- .unwrap()
- .set_pixbuf(pixbuf.as_ref())
- }
+ pub fn new_predefined(item_type: PredefinedMenuItemType, text: Option) -> Self {
+ Self {
+ id: MenuId(COUNTER.next().to_string()),
+ text: text.unwrap_or_else(|| item_type.text().to_string()),
+ enabled: true,
+ accelerator: None,
+ icon: None,
+ checked: false,
+ type_: MenuItemType::Predefined,
+ ctx_menu_id: 0,
+ instances: HashMap::new(),
+ children: Vec::new(),
+ action: None,
}
}
}
-/// Submenu methods
impl MenuChild {
- pub fn add_menu_item(&mut self, item: &dyn crate::IsMenuItem, op: AddOp) -> crate::Result<()> {
- if is_item_supported!(item) {
- for menus in self.gtk_menus.as_ref().unwrap().values() {
- for (menu_id, menu) in menus {
- let gtk_item =
- item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true, false)?;
- match op {
- AddOp::Append => menu.append(>k_item),
- AddOp::Insert(position) => menu.insert(>k_item, position as i32),
- }
- gtk_item.show();
- }
- }
-
- {
- if let (menu_id, Some(menu)) = self.gtk_menu.as_ref().unwrap() {
- let gtk_item =
- item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true, false)?;
- match op {
- AddOp::Append => menu.append(>k_item),
- AddOp::Insert(position) => menu.insert(>k_item, position as i32),
- }
- gtk_item.show();
- }
- }
- }
-
- match op {
- AddOp::Append => self.children.as_mut().unwrap().push(item.child()),
- AddOp::Insert(position) => self
- .children
- .as_mut()
- .unwrap()
- .insert(position, item.child()),
+ pub fn new_check(
+ text: &str,
+ enabled: bool,
+ checked: bool,
+ accelerator: Option,
+ id: Option,
+ ) -> Self {
+ Self {
+ id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
+ text: text.to_string(),
+ enabled,
+ accelerator,
+ icon: None,
+ checked,
+ type_: MenuItemType::Check,
+ ctx_menu_id: 0,
+ instances: HashMap::new(),
+ children: Vec::new(),
+ action: None,
}
-
- Ok(())
}
- fn add_menu_item_with_id(&self, item: &dyn crate::IsMenuItem, id: u32) -> crate::Result<()> {
- return_if_item_not_supported!(item);
+ fn create_gtk_item_for_check_menu_item(
+ &mut self,
+ app: >k4::Application,
+ menu_id: u32,
+ ) -> crate::Result {
+ let detailed_action = self.detailed_action();
+ let item = gio::MenuItem::new(Some(&to_gtk_mnemonic(&self.text)), Some(&detailed_action));
- for menus in self.gtk_menus.as_ref().unwrap().values() {
- for (menu_id, menu) in menus.iter().filter(|m| m.0 == id) {
- let gtk_item =
- item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true, false)?;
- menu.append(>k_item);
- gtk_item.show();
- }
+ if let Some(accelerator) = &self.accelerator {
+ app.set_accels_for_action(&detailed_action, &[&accelerator.to_gtk()]);
}
- Ok(())
- }
+ if self.action.is_none() {
+ let action_group = action_group_from_app(&app);
- fn add_menu_item_to_context_menu(&self, item: &dyn crate::IsMenuItem) -> crate::Result<()> {
- return_if_item_not_supported!(item);
+ let state = &self.checked.to_variant();
+ let action = gio::SimpleAction::new_stateful(self.id.as_ref(), None, state);
+ let id = self.id.clone();
+ action.connect_state_notify(move |_| MenuEvent::send(MenuEvent { id: id.clone() }));
+ action.set_enabled(self.enabled);
+ action_group.add_action(&action);
- let (menu_id, menu) = self.gtk_menu.as_ref().unwrap();
- if let Some(menu) = menu {
- let gtk_item =
- item.make_gtk_menu_item(*menu_id, self.accel_group.as_ref(), true, false)?;
- menu.append(>k_item);
- gtk_item.show();
+ self.action = Some(action);
}
- Ok(())
- }
-
- pub fn remove(&mut self, item: &dyn crate::IsMenuItem) -> crate::Result<()> {
- self.remove_inner(item, true, None)
- }
-
- fn remove_inner(
- &mut self,
- item: &dyn crate::IsMenuItem,
- remove_from_cache: bool,
- id: Option,
- ) -> crate::Result<()> {
- // get child
- let child = {
- let index = self
- .children
- .as_ref()
- .unwrap()
- .iter()
- .position(|e| e.borrow().id == item.id())
- .ok_or(crate::Error::NotAChildOfThisMenu)?;
- if remove_from_cache {
- self.children.as_mut().unwrap().remove(index)
- } else {
- self.children.as_ref().unwrap().get(index).cloned().unwrap()
- }
+ let child = GtkMenuChild::Item {
+ item: item.clone(),
+ app: app.clone(),
};
+ self.instances.entry(menu_id).or_default().push(child);
- for menus in self.gtk_menus.as_ref().unwrap().values() {
- for (menu_id, menu) in menus {
- // check if we are removing this item from all gtk_menus
- // which is usually when this is the item the user is actaully removing
- // or if we are removing from a specific menu (id)
- // which is when the actual item being removed is a submenu
- // and we are iterating through its children and removing
- // each child gtk items that are related to this submenu.
- if id.map(|i| i == *menu_id).unwrap_or(true) {
- // bail this is not a supported item like a close_window predefined menu item
- if is_item_supported!(item) {
- let mut child_ = child.borrow_mut();
-
- if child_.item_type == MenuItemType::Submenu {
- let menus = child_.gtk_menus.as_ref().unwrap().get(menu_id).cloned();
- if let Some(menus) = menus {
- for (id, menu) in menus {
- // iterate through children and only remove the gtk items
- // related to this submenu
- for item in child_.items() {
- child_.remove_inner(item.as_ref(), false, Some(id))?;
- }
- unsafe { menu.destroy() };
- }
- }
- child_.gtk_menus.as_mut().unwrap().remove(menu_id);
- }
-
- // remove all the gtk items that are related to this gtk menu and destroy it
- if let Some(items) = child_.gtk_menu_items.borrow_mut().remove(menu_id) {
- for item in items {
- menu.remove(&item);
- if let Some(accel_group) = &child_.accel_group {
- if let Some((mods, key)) = child_.gtk_accelerator {
- item.remove_accelerator(accel_group, key, mods);
- }
- }
- unsafe { item.destroy() };
- }
- };
- }
- }
- }
- }
-
- // remove from the gtk menu assigned to the context menu
- if remove_from_cache {
- if let (id, Some(menu)) = self.gtk_menu.as_ref().unwrap() {
- let child_ = child.borrow_mut();
- if let Some(items) = child_.gtk_menu_items.borrow_mut().remove(id) {
- for item in items {
- menu.remove(&item);
- if let Some(accel_group) = &child_.accel_group {
- if let Some((mods, key)) = child_.gtk_accelerator {
- item.remove_accelerator(accel_group, key, mods);
- }
- }
- unsafe { item.destroy() };
- }
- };
- }
- }
-
- Ok(())
+ Ok(item)
}
- pub fn items(&self) -> Vec {
- self.children
+ pub fn is_checked(&self) -> bool {
+ self.action
.as_ref()
- .unwrap()
- .iter()
- .map(|c| c.borrow().kind(c.clone()))
- .collect()
+ .and_then(|action| action.state())
+ .and_then(|s| s.get())
+ .unwrap_or(self.checked)
}
- pub fn show_context_menu_for_gtk_window(
- &mut self,
- widget: &impl IsA,
- position: Option,
- ) -> bool {
- show_context_menu(self.gtk_context_menu(), widget, position)
- }
-
- pub fn gtk_context_menu(&mut self) -> gtk::Menu {
- let mut add_items = false;
- {
- let gtk_menu = self.gtk_menu.as_mut().unwrap();
- if gtk_menu.1.is_none() {
- gtk_menu.1 = Some(gtk::Menu::new());
- add_items = true;
- }
- }
+ pub fn set_checked(&mut self, checked: bool) {
+ self.checked = checked;
- if add_items {
- for item in self.items() {
- self.add_menu_item_to_context_menu(item.as_ref()).unwrap();
- }
+ if let Some(action) = self.action.as_ref() {
+ action.set_state(&checked.to_variant());
}
-
- self.gtk_menu.as_ref().unwrap().1.as_ref().unwrap().clone()
}
}
-macro_rules! register_accel {
- ($self:ident, $item:ident, $accel_group:ident) => {
- $self.gtk_accelerator = $self
- .accelerator
- .as_ref()
- .map(parse_accelerator)
- .transpose()?;
-
- if let Some((mods, key)) = &$self.gtk_accelerator {
- if let Some(accel_group) = $accel_group {
- $item.add_accelerator(
- "activate",
- accel_group,
- *key,
- *mods,
- gtk::AccelFlags::VISIBLE,
- )
- }
- }
- };
-}
-
-/// Gtk menu item creation methods
impl MenuChild {
- fn create_gtk_item_for_submenu(
- &mut self,
- menu_id: u32,
- accel_group: Option<>k::AccelGroup>,
- add_to_cache: bool,
- ) -> crate::Result {
- let submenu = gtk::Menu::new();
- let item = gtk::MenuItem::builder()
- .label(to_gtk_mnemonic(&self.text))
- .use_underline(true)
- .submenu(&submenu)
- .sensitive(self.enabled)
- .build();
-
- item.show();
- item.set_submenu(Some(&submenu));
-
- self.accel_group = accel_group.cloned();
-
- let mut id = 0;
- if add_to_cache {
- id = COUNTER.next();
-
- self.gtk_menu_items
- .borrow_mut()
- .entry(menu_id)
- .or_default()
- .push(item.clone());
- self.gtk_menus
- .as_mut()
- .unwrap()
- .entry(menu_id)
- .or_default()
- .push((id, submenu.clone()));
- }
-
- for item in self.items() {
- if add_to_cache {
- self.add_menu_item_with_id(item.as_ref(), id)?;
- } else {
- let gtk_item = item.make_gtk_menu_item(0, None, false, false)?;
- submenu.append(>k_item);
- }
+ pub fn new_icon(
+ text: &str,
+ enabled: bool,
+ icon: Option,
+ accelerator: Option,
+ id: Option,
+ ) -> Self {
+ Self {
+ id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
+ text: text.to_string(),
+ enabled,
+ accelerator,
+ icon,
+ checked: false,
+ type_: MenuItemType::Icon,
+ ctx_menu_id: 0,
+ instances: HashMap::new(),
+ children: Vec::new(),
+ action: None,
}
-
- Ok(item)
}
- fn create_gtk_item_for_menu_item(
- &mut self,
- menu_id: u32,
- accel_group: Option<>k::AccelGroup>,
- add_to_cache: bool,
- ) -> crate::Result {
- let item = gtk::MenuItem::builder()
- .label(to_gtk_mnemonic(&self.text))
- .use_underline(true)
- .sensitive(self.enabled)
- .build();
-
- self.accel_group = accel_group.cloned();
-
- register_accel!(self, item, accel_group);
-
- let id = self.id.clone();
- item.connect_activate(move |_| {
- MenuEvent::send(crate::MenuEvent { id: id.clone() });
- });
-
- if add_to_cache {
- self.gtk_menu_items
- .borrow_mut()
- .entry(menu_id)
- .or_default()
- .push(item.clone());
+ pub fn new_native_icon(
+ text: &str,
+ enabled: bool,
+ icon: Option,
+ accelerator: Option,
+ id: Option,
+ ) -> Self {
+ Self {
+ id: id.unwrap_or_else(|| MenuId(COUNTER.next().to_string())),
+ text: text.to_string(),
+ enabled,
+ accelerator,
+ icon: None,
+ checked: false,
+ type_: MenuItemType::Submenu,
+ ctx_menu_id: 0,
+ instances: HashMap::new(),
+ children: Vec::new(),
+ action: None,
}
-
- Ok(item)
}
- fn create_gtk_item_for_predefined_menu_item(
+ fn create_gtk_item_for_icon_menu_item(
&mut self,
+ app: >k4::Application,
menu_id: u32,
- accel_group: Option<>k::AccelGroup>,
- add_to_cache: bool,
- ) -> crate::Result {
- let text = self.text.clone();
- self.gtk_accelerator = self
- .accelerator
- .as_ref()
- .map(parse_accelerator)
- .transpose()?;
- let predefined_item_type = self.predefined_item_type.clone().unwrap();
-
- let make_item = || {
- gtk::MenuItem::builder()
- .label(to_gtk_mnemonic(&text))
- .use_underline(true)
- .sensitive(true)
- .build()
- };
- let register_accel = |item: >k::MenuItem| {
- if let Some((mods, key)) = &self.gtk_accelerator {
- if let Some(accel_group) = accel_group {
- item.add_accelerator(
- "activate",
- accel_group,
- *key,
- *mods,
- gtk::AccelFlags::VISIBLE,
- )
- }
- }
- };
+ ) -> crate::Result {
+ let detailed_action = self.detailed_action();
+ let item = gio::MenuItem::new(Some(&to_gtk_mnemonic(&self.text)), Some(&detailed_action));
- let item = match predefined_item_type {
- PredefinedMenuItemType::Separator => {
- gtk::SeparatorMenuItem::new().upcast::()
- }
- PredefinedMenuItemType::Copy
- | PredefinedMenuItemType::Cut
- | PredefinedMenuItemType::Paste
- | PredefinedMenuItemType::SelectAll => {
- let item = make_item();
- let (mods, key) =
- parse_accelerator(&predefined_item_type.accelerator().unwrap()).unwrap();
- item.child()
- .unwrap()
- .downcast::()
- .unwrap()
- .set_accel(key, mods);
- item.connect_activate(move |_| {
- // TODO: wayland
- #[cfg(feature = "libxdo")]
- if let Ok(xdo) = libxdo::XDo::new(None) {
- let _ = xdo.send_keysequence(predefined_item_type.xdo_keys(), 0);
- }
- });
- item
- }
- PredefinedMenuItemType::About(metadata) => {
- let item = make_item();
- register_accel(&item);
- item.connect_activate(move |_| {
- if let Some(metadata) = &metadata {
- let mut builder = AboutDialog::builder().modal(true).resizable(false);
-
- if let Some(name) = &metadata.name {
- builder = builder.program_name(name);
- }
- if let Some(version) = &metadata.full_version() {
- builder = builder.version(version);
- }
- if let Some(authors) = &metadata.authors {
- builder = builder.authors(authors.clone());
- }
- if let Some(comments) = &metadata.comments {
- builder = builder.comments(comments);
- }
- if let Some(copyright) = &metadata.copyright {
- builder = builder.copyright(copyright);
- }
- if let Some(license) = &metadata.license {
- builder = builder.license(license);
- }
- if let Some(website) = &metadata.website {
- builder = builder.website(website);
- }
- if let Some(website_label) = &metadata.website_label {
- builder = builder.website_label(website_label);
- }
- if let Some(icon) = &metadata.icon {
- builder = builder.logo(&icon.inner.to_pixbuf());
- }
-
- let about = builder.build();
- about.run();
- unsafe {
- about.destroy();
- }
- }
- });
- item
- }
- _ => unreachable!(),
- };
-
- if add_to_cache {
- self.gtk_menu_items
- .borrow_mut()
- .entry(menu_id)
- .or_default()
- .push(item.clone());
+ if let Some(accelerator) = &self.accelerator {
+ app.set_accels_for_action(&detailed_action, &[&accelerator.to_gtk()]);
}
- Ok(item)
- }
- fn create_gtk_item_for_check_menu_item(
- &mut self,
- menu_id: u32,
- accel_group: Option<>k::AccelGroup>,
- add_to_cache: bool,
- ) -> crate::Result {
- let item = gtk::CheckMenuItem::builder()
- .label(to_gtk_mnemonic(&self.text))
- .use_underline(true)
- .sensitive(self.enabled)
- .active(self.checked.as_ref().unwrap().load(Ordering::Relaxed))
- .build();
-
- self.accel_group = accel_group.cloned();
-
- register_accel!(self, item, accel_group);
-
- let id = self.id.clone();
- let is_syncing_checked_state = self.is_syncing_checked_state.clone().unwrap();
- let checked = self.checked.clone().unwrap();
- let store = self.gtk_menu_items.clone();
- item.connect_toggled(move |i| {
- let should_dispatch = is_syncing_checked_state
- .compare_exchange(false, true, Ordering::Release, Ordering::Relaxed)
- .is_ok();
-
- if should_dispatch {
- let c = i.is_active();
- checked.store(c, Ordering::Release);
-
- for items in store.borrow().values() {
- for i in items {
- i.downcast_ref::()
- .unwrap()
- .set_active(c);
- }
- }
-
- is_syncing_checked_state.store(false, Ordering::Release);
+ if let Some(icon) = &self.icon {
+ item.set_icon(icon.inner.bytes_icon());
+ }
- MenuEvent::send(crate::MenuEvent { id: id.clone() });
- }
- });
+ if self.action.is_none() {
+ let action_group = action_group_from_app(&app);
- let item = item.upcast::();
+ let action = gio::SimpleAction::new(self.id.as_ref(), None);
+ let id = self.id.clone();
+ action.connect_activate(move |_, _| MenuEvent::send(MenuEvent { id: id.clone() }));
+ action.set_enabled(self.enabled);
+ action_group.add_action(&action);
- if add_to_cache {
- self.gtk_menu_items
- .borrow_mut()
- .entry(menu_id)
- .or_default()
- .push(item.clone());
+ self.action = Some(action);
}
- Ok(item)
- }
-
- fn create_gtk_item_for_icon_menu_item(
- &mut self,
- menu_id: u32,
- accel_group: Option<>k::AccelGroup>,
- add_to_cache: bool,
- for_menu_bar: bool,
- ) -> crate::Result {
- let image = self
- .icon
- .as_ref()
- .map(|i| gtk::Image::from_pixbuf(Some(&i.inner.to_pixbuf_scale(16, 16))))
- .unwrap_or_default();
-
- self.accel_group = accel_group.cloned();
-
- let label = gtk::AccelLabel::builder()
- .label(to_gtk_mnemonic(&self.text))
- .use_underline(true)
- .xalign(0.0)
- .build();
-
- let box_container = gtk::Box::new(Orientation::Horizontal, 6);
- if !for_menu_bar {
- let style_context = box_container.style_context();
- let css_provider = gtk::CssProvider::new();
- let theme = r#"
- box {
- margin-left: -22px;
- }
- "#;
- let _ = css_provider.load_from_data(theme.as_bytes());
- style_context.add_provider(&css_provider, gtk::STYLE_PROVIDER_PRIORITY_APPLICATION);
- }
- box_container.pack_start(&image, false, false, 0);
- box_container.pack_start(&label, true, true, 0);
- box_container.show_all();
-
- let item = gtk::MenuItem::builder()
- .child(&box_container)
- .sensitive(self.enabled)
- .build();
-
- register_accel!(self, item, accel_group);
-
- let id = self.id.clone();
- item.connect_activate(move |_| {
- MenuEvent::send(crate::MenuEvent { id: id.clone() });
- });
-
- if add_to_cache {
- self.gtk_menu_items
- .borrow_mut()
- .entry(menu_id)
- .or_default()
- .push(item.clone());
- }
+ let child = GtkMenuChild::Item {
+ item: item.clone(),
+ app: app.clone(),
+ };
+ self.instances.entry(menu_id).or_default().push(child);
Ok(item)
}
+
+ pub fn set_icon(&self, icon: Option) {}
}
-impl MenuItemKind {
+impl dyn IsMenuItem + '_ {
fn make_gtk_menu_item(
&self,
+ app: >k4::Application,
menu_id: u32,
- accel_group: Option<>k::AccelGroup>,
- add_to_cache: bool,
- for_menu_bar: bool,
- ) -> crate::Result {
- let mut child = self.child_mut();
+ ) -> crate::Result {
+ let kind = self.kind();
+ let mut child = kind.child_mut();
match child.item_type() {
- MenuItemType::Submenu => {
- child.create_gtk_item_for_submenu(menu_id, accel_group, add_to_cache)
- }
- MenuItemType::MenuItem => {
- child.create_gtk_item_for_menu_item(menu_id, accel_group, add_to_cache)
- }
- MenuItemType::Predefined => {
- child.create_gtk_item_for_predefined_menu_item(menu_id, accel_group, add_to_cache)
- }
- MenuItemType::Check => {
- child.create_gtk_item_for_check_menu_item(menu_id, accel_group, add_to_cache)
- }
- MenuItemType::Icon => child.create_gtk_item_for_icon_menu_item(
- menu_id,
- accel_group,
- add_to_cache,
- for_menu_bar,
- ),
+ MenuItemType::Submenu => child.create_gtk_item_for_submenu(app, menu_id),
+ MenuItemType::MenuItem => child.create_gtk_item_for_menu_item(app, menu_id),
+ MenuItemType::Check => child.create_gtk_item_for_check_menu_item(app, menu_id),
+ MenuItemType::Icon => child.create_gtk_item_for_icon_menu_item(app, menu_id),
+ _ => todo!(),
+ // MenuItemType::Predefined => {
+ // child.create_gtk_item_for_predefined_menu_item(menu_id, action_group)
+ // }
}
}
}
-impl dyn IsMenuItem + '_ {
- fn make_gtk_menu_item(
- &self,
- menu_id: u32,
- accel_group: Option<>k::AccelGroup>,
- add_to_cache: bool,
- for_menu_bar: bool,
- ) -> crate::Result {
- self.kind()
- .make_gtk_menu_item(menu_id, accel_group, add_to_cache, for_menu_bar)
- }
-}
+/// Returns and creates the action group on this applicaiton if necessary.
+fn action_group_from_app(app: >k4::Application) -> gio::SimpleActionGroup {
+ let action_group = unsafe { app.data::(ACTION_GROUP_DATA_KEY) };
-fn show_context_menu(
- gtk_menu: gtk::Menu,
- widget: &impl IsA,
- position: Option,
-) -> bool {
- let (pos, window) = if let Some(pos) = position {
- let window = widget.window();
- (
- pos.to_logical::(window.as_ref().map(|w| w.scale_factor()).unwrap_or(1) as _)
- .into(),
- window,
- )
+ let action_group = if let Some(action_group) = action_group {
+ unsafe { action_group.as_ref() }.clone()
} else {
- let window = widget.screen().and_then(|s| s.root_window());
- (
- window
- .as_ref()
- .and_then(|w| {
- w.display()
- .default_seat()
- .and_then(|s| s.pointer())
- .map(|s| {
- let p = s.position();
- (p.1, p.2)
- })
- })
- .unwrap_or_default(),
- window,
- )
+ let action_group = gio::SimpleActionGroup::new();
+ unsafe { app.set_data(ACTION_GROUP_DATA_KEY, action_group.clone()) };
+ action_group
};
- if let Some(window) = window {
- let mut event = gdk::Event::new(gdk::EventType::ButtonPress);
- event.set_device(
- window
- .display()
- .default_seat()
- .and_then(|d| d.pointer())
- .as_ref(),
- );
-
- // Set the time of the event otherwise GTK will close the menu
- // when right click is released
- let event_ffi: *mut gdk::ffi::GdkEvent = event.to_glib_none().0;
- if !event_ffi.is_null() {
- let time = glib::monotonic_time() / 1000;
- unsafe {
- (*event_ffi).button.time = time as _;
- }
- }
-
- let (tx, rx) = crossbeam_channel::unbounded();
- let tx_clone = tx.clone();
- let id = gtk_menu.connect_cancel(move |_| tx_clone.send(false).unwrap_or(()));
- let id2 = gtk_menu.connect_selection_done(move |_| tx.send(true).unwrap_or(()));
-
- gtk_menu.popup_at_rect(
- &window,
- &gdk::Rectangle::new(pos.0, pos.1, 0, 0),
- gdk::Gravity::NorthWest,
- gdk::Gravity::NorthWest,
- Some(&event),
- );
-
- loop {
- gtk::main_iteration();
-
- match rx.try_recv() {
- Ok(result) => {
- gtk_menu.disconnect(id);
- gtk_menu.disconnect(id2);
- return result;
- }
- Err(err) => {
- if err.is_disconnected() {
- gtk_menu.disconnect(id);
- gtk_menu.disconnect(id2);
- return false;
- }
- }
- }
- }
- }
-
- false
+ action_group
}
-impl PredefinedMenuItemType {
- #[cfg(feature = "libxdo")]
- fn xdo_keys(&self) -> &str {
- match self {
- PredefinedMenuItemType::Copy => "ctrl+c",
- PredefinedMenuItemType::Cut => "ctrl+X",
- PredefinedMenuItemType::Paste => "ctrl+v",
- PredefinedMenuItemType::SelectAll => "ctrl+a",
- _ => unreachable!(),
- }
- }
+fn get_cursor_pos(window: >k4::Window) -> (i32, i32) {
+ WidgetExt::display(window)
+ .default_seat()
+ .and_then(|s| s.pointer())
+ .map(|p| {
+ let (_, x, y) = p.surface_at_position();
+ (x as _, y as _)
+ })
+ .unwrap_or_default()
}