dash-frontend: Application list

This commit is contained in:
Aleksander
2025-09-28 13:13:37 +02:00
parent eb12a6a319
commit b5a3ba2954
18 changed files with 907 additions and 115 deletions

497
Cargo.lock generated
View File

@@ -449,6 +449,29 @@ dependencies = [
"syn 2.0.106",
]
[[package]]
name = "atk"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b"
dependencies = [
"atk-sys",
"glib 0.18.5",
"libc",
]
[[package]]
name = "atk-sys"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086"
dependencies = [
"glib-sys 0.18.1",
"gobject-sys 0.18.0",
"libc",
"system-deps 6.2.2",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
@@ -775,6 +798,31 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cairo-rs"
version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2"
dependencies = [
"bitflags 2.9.4",
"cairo-sys-rs",
"glib 0.18.5",
"libc",
"once_cell",
"thiserror 1.0.69",
]
[[package]]
name = "cairo-sys-rs"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51"
dependencies = [
"glib-sys 0.18.1",
"libc",
"system-deps 6.2.2",
]
[[package]]
name = "calloop"
version = "0.13.0"
@@ -848,7 +896,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02"
dependencies = [
"smallvec",
"target-lexicon",
"target-lexicon 0.12.16",
]
[[package]]
name = "cfg-expr"
version = "0.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a2c5f3bf25ec225351aa1c8e230d04d880d3bd89dea133537dafad4ae291e5c"
dependencies = [
"smallvec",
"target-lexicon 0.13.2",
]
[[package]]
@@ -1014,7 +1072,7 @@ dependencies = [
"serde_core",
"serde_json",
"toml 0.9.7",
"winnow",
"winnow 0.7.13",
"yaml-rust2",
]
@@ -1369,7 +1427,9 @@ version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
"gio 0.21.2",
"glam",
"gtk",
"log",
"rust-embed",
"wgui",
@@ -1757,6 +1817,16 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "field-offset"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f"
dependencies = [
"memoffset",
"rustc_version",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.2"
@@ -1979,6 +2049,64 @@ dependencies = [
"slab",
]
[[package]]
name = "gdk"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691"
dependencies = [
"cairo-rs",
"gdk-pixbuf",
"gdk-sys",
"gio 0.18.4",
"glib 0.18.5",
"libc",
"pango",
]
[[package]]
name = "gdk-pixbuf"
version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec"
dependencies = [
"gdk-pixbuf-sys",
"gio 0.18.4",
"glib 0.18.5",
"libc",
"once_cell",
]
[[package]]
name = "gdk-pixbuf-sys"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7"
dependencies = [
"gio-sys 0.18.1",
"glib-sys 0.18.1",
"gobject-sys 0.18.0",
"libc",
"system-deps 6.2.2",
]
[[package]]
name = "gdk-sys"
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 0.18.1",
"glib-sys 0.18.1",
"gobject-sys 0.18.0",
"libc",
"pango-sys",
"pkg-config",
"system-deps 6.2.2",
]
[[package]]
name = "generic-array"
version = "0.14.7"
@@ -2038,6 +2166,68 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "gio"
version = "0.18.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-util",
"gio-sys 0.18.1",
"glib 0.18.5",
"libc",
"once_cell",
"pin-project-lite",
"smallvec",
"thiserror 1.0.69",
]
[[package]]
name = "gio"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed68efc12b748a771be2dccc49480d8584004382967c98323245fc3c38b74a42"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-util",
"gio-sys 0.21.2",
"glib 0.21.3",
"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 0.18.1",
"gobject-sys 0.18.0",
"libc",
"system-deps 6.2.2",
"winapi",
]
[[package]]
name = "gio-sys"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "171ed2f6dd927abbe108cfd9eebff2052c335013f5879d55bab0dc1dee19b706"
dependencies = [
"glib-sys 0.21.2",
"gobject-sys 0.21.2",
"libc",
"system-deps 7.0.5",
"windows-sys 0.61.0",
]
[[package]]
name = "gl_generator"
version = "0.14.0"
@@ -2059,18 +2249,183 @@ dependencies = [
"serde",
]
[[package]]
name = "glib"
version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5"
dependencies = [
"bitflags 2.9.4",
"futures-channel",
"futures-core",
"futures-executor",
"futures-task",
"futures-util",
"gio-sys 0.18.1",
"glib-macros 0.18.5",
"glib-sys 0.18.1",
"gobject-sys 0.18.0",
"libc",
"memchr",
"once_cell",
"smallvec",
"thiserror 1.0.69",
]
[[package]]
name = "glib"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1f2cbc4577536c849335878552f42086bfd25a8dcd6f54a18655cf818b20c8f"
dependencies = [
"bitflags 2.9.4",
"futures-channel",
"futures-core",
"futures-executor",
"futures-task",
"futures-util",
"gio-sys 0.21.2",
"glib-macros 0.21.2",
"glib-sys 0.21.2",
"gobject-sys 0.21.2",
"libc",
"memchr",
"smallvec",
]
[[package]]
name = "glib-macros"
version = "0.18.5"
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-error",
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
name = "glib-macros"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55eda916eecdae426d78d274a17b48137acdca6fba89621bd3705f2835bc719f"
dependencies = [
"heck 0.5.0",
"proc-macro-crate 3.4.0",
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
name = "glib-sys"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898"
dependencies = [
"libc",
"system-deps 6.2.2",
]
[[package]]
name = "glib-sys"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d09d3d0fddf7239521674e57b0465dfbd844632fec54f059f7f56112e3f927e1"
dependencies = [
"libc",
"system-deps 7.0.5",
]
[[package]]
name = "glob"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "gobject-sys"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44"
dependencies = [
"glib-sys 0.18.1",
"libc",
"system-deps 6.2.2",
]
[[package]]
name = "gobject-sys"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "538e41d8776173ec107e7b0f2aceced60abc368d7e1d81c1f0e2ecd35f59080d"
dependencies = [
"glib-sys 0.21.2",
"libc",
"system-deps 7.0.5",
]
[[package]]
name = "grid"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12101ecc8225ea6d675bc70263074eab6169079621c2186fe0c66590b2df9681"
[[package]]
name = "gtk"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a"
dependencies = [
"atk",
"cairo-rs",
"field-offset",
"futures-channel",
"gdk",
"gdk-pixbuf",
"gio 0.18.4",
"glib 0.18.5",
"gtk-sys",
"gtk3-macros",
"libc",
"pango",
"pkg-config",
]
[[package]]
name = "gtk-sys"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414"
dependencies = [
"atk-sys",
"cairo-sys-rs",
"gdk-pixbuf-sys",
"gdk-sys",
"gio-sys 0.18.1",
"glib-sys 0.18.1",
"gobject-sys 0.18.0",
"libc",
"pango-sys",
"system-deps 6.2.2",
]
[[package]]
name = "gtk3-macros"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d"
dependencies = [
"proc-macro-crate 1.3.1",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.106",
]
[[package]]
name = "half"
version = "2.6.0"
@@ -2688,7 +3043,7 @@ dependencies = [
"libspa-sys",
"nix 0.27.1",
"nom",
"system-deps",
"system-deps 6.2.2",
]
[[package]]
@@ -2698,7 +3053,7 @@ source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git#ba32202c3c
dependencies = [
"bindgen 0.69.5",
"cc",
"system-deps",
"system-deps 6.2.2",
]
[[package]]
@@ -3113,7 +3468,7 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d"
dependencies = [
"proc-macro-crate",
"proc-macro-crate 3.4.0",
"proc-macro2",
"quote",
"syn 2.0.106",
@@ -3540,6 +3895,31 @@ dependencies = [
"ttf-parser 0.25.1",
]
[[package]]
name = "pango"
version = "0.18.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4"
dependencies = [
"gio 0.18.4",
"glib 0.18.5",
"libc",
"once_cell",
"pango-sys",
]
[[package]]
name = "pango-sys"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5"
dependencies = [
"glib-sys 0.18.1",
"gobject-sys 0.18.0",
"libc",
"system-deps 6.2.2",
]
[[package]]
name = "parking"
version = "2.2.1"
@@ -3721,7 +4101,7 @@ source = "git+https://gitlab.freedesktop.org/galister/pipewire-rs.git#ba32202c3c
dependencies = [
"bindgen 0.69.5",
"libspa-sys",
"system-deps",
"system-deps 6.2.2",
]
[[package]]
@@ -3804,6 +4184,26 @@ dependencies = [
"syn 2.0.106",
]
[[package]]
name = "proc-macro-crate"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
dependencies = [
"once_cell",
"toml_edit 0.19.15",
]
[[package]]
name = "proc-macro-crate"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24"
dependencies = [
"toml_datetime 0.6.3",
"toml_edit 0.20.2",
]
[[package]]
name = "proc-macro-crate"
version = "3.4.0"
@@ -4020,7 +4420,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",
@@ -4848,10 +5248,23 @@ 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 0.8.23",
"toml 0.8.2",
"version-compare",
]
[[package]]
name = "system-deps"
version = "7.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4be53aa0cba896d2dc615bd42bbc130acdcffa239e0a2d965ea5b3b2a86ffdb"
dependencies = [
"cfg-expr 0.20.3",
"heck 0.5.0",
"pkg-config",
"toml 0.8.2",
"version-compare",
]
@@ -4873,6 +5286,12 @@ version = "0.12.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "target-lexicon"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a"
[[package]]
name = "tempfile"
version = "3.22.0"
@@ -5037,14 +5456,14 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "toml"
version = "0.8.23"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
dependencies = [
"serde",
"serde_spanned 0.6.9",
"toml_datetime 0.6.11",
"toml_edit 0.22.27",
"toml_datetime 0.6.3",
"toml_edit 0.20.2",
]
[[package]]
@@ -5057,14 +5476,14 @@ dependencies = [
"serde_spanned 1.0.2",
"toml_datetime 0.7.2",
"toml_parser",
"winnow",
"winnow 0.7.13",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
dependencies = [
"serde",
]
@@ -5080,15 +5499,26 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.22.27"
version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
"indexmap 2.11.4",
"toml_datetime 0.6.3",
"winnow 0.5.40",
]
[[package]]
name = "toml_edit"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
dependencies = [
"indexmap 2.11.4",
"serde",
"serde_spanned 0.6.9",
"toml_datetime 0.6.11",
"winnow",
"toml_datetime 0.6.3",
"winnow 0.5.40",
]
[[package]]
@@ -5100,7 +5530,7 @@ dependencies = [
"indexmap 2.11.4",
"toml_datetime 0.7.2",
"toml_parser",
"winnow",
"winnow 0.7.13",
]
[[package]]
@@ -5109,7 +5539,7 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627"
dependencies = [
"winnow",
"winnow 0.7.13",
]
[[package]]
@@ -5435,7 +5865,7 @@ version = "0.35.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dc929c42c9336fd082079ac3ea30126e4a0dfe36fd2e2b3581303f7d140d20f"
dependencies = [
"proc-macro-crate",
"proc-macro-crate 3.4.0",
"proc-macro2",
"quote",
"syn 2.0.106",
@@ -6282,6 +6712,15 @@ dependencies = [
"xkbcommon-dl",
]
[[package]]
name = "winnow"
version = "0.5.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
dependencies = [
"memchr",
]
[[package]]
name = "winnow"
version = "0.7.13"
@@ -6586,7 +7025,7 @@ dependencies = [
"tracing",
"uds_windows",
"windows-sys 0.60.2",
"winnow",
"winnow 0.7.13",
"zbus_macros",
"zbus_names",
"zvariant",
@@ -6598,7 +7037,7 @@ version = "5.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57e797a9c847ed3ccc5b6254e8bcce056494b375b511b3d6edcec0aeb4defaca"
dependencies = [
"proc-macro-crate",
"proc-macro-crate 3.4.0",
"proc-macro2",
"quote",
"syn 2.0.106",
@@ -6615,7 +7054,7 @@ checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97"
dependencies = [
"serde",
"static_assertions",
"winnow",
"winnow 0.7.13",
"zvariant",
]
@@ -6724,7 +7163,7 @@ dependencies = [
"enumflags2",
"serde",
"url",
"winnow",
"winnow 0.7.13",
"zvariant_derive",
"zvariant_utils",
]
@@ -6735,7 +7174,7 @@ version = "5.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6643fd0b26a46d226bd90d3f07c1b5321fe9bb7f04673cb37ac6d6883885b68e"
dependencies = [
"proc-macro-crate",
"proc-macro-crate 3.4.0",
"proc-macro2",
"quote",
"syn 2.0.106",
@@ -6752,5 +7191,5 @@ dependencies = [
"quote",
"serde",
"syn 2.0.106",
"winnow",
"winnow 0.7.13",
]

View File

@@ -10,3 +10,5 @@ glam = { workspace = true }
log = { workspace = true }
rust-embed = { workspace = true }
chrono = "0.4.42"
gio = "0.21.2"
gtk = "0.18.2"

View File

@@ -1,6 +1,19 @@
<layout>
<include src="t_tab_title.xml" />
<template name="AppEntry">
<Button
id="button" width="116" max_width="140" min_height="100" flex_grow="1"
flex_direction="column" overflow="visible" align_items="center" justify_content="center" gap="8">
<div>
<sprite src="${src}" src_ext="${src_ext}" width="64" height="64" />
</div>
<div align_items="center" justify_content="center">
<label weight="bold" text="${name}" size="12" />
</div>
</Button>
</template>
<elements>
<TabTitle translation="APPLICATIONS" icon="dashboard/apps.svg" />
<!-- placeholders for now -->
@@ -17,5 +30,12 @@
<label text="Search" color="#FFFFFF88" weight="bold" />
</rectangle>
</div>
<div
id="app_list_parent"
flex_direction="row"
flex_wrap="wrap"
justify_content="stretch"
gap="4"
/>
</elements>
</layout>

View File

@@ -8,7 +8,7 @@ use wgui::{
globals::WguiGlobals,
i18n::Translation,
layout::{LayoutParams, RcLayout, WidgetID},
parser::{ParseDocumentParams, ParserState},
parser::{Fetchable, ParseDocumentParams, ParserState},
widget::label::WidgetLabel,
};
@@ -19,6 +19,7 @@ use crate::tab::{
mod assets;
mod tab;
mod util;
mod various;
pub struct Frontend {

View File

@@ -1,10 +1,23 @@
use wgui::parser::{ParseDocumentParams, ParserState};
use std::{collections::HashMap, rc::Rc};
use crate::tab::{Tab, TabParams, TabType};
use wgui::{
components::{self, button::ComponentButton},
layout::WidgetPair,
parser::{Fetchable, ParseDocumentParams, ParserData, ParserState},
taffy::{self, Dimension, prelude::length},
};
use crate::{
tab::{Tab, TabParams, TabType},
util::{self, desktop_finder::DesktopEntry},
};
pub struct TabApps {
#[allow(dead_code)]
pub state: ParserState,
entries: Vec<DesktopEntry>,
app_list: AppList,
}
impl Tab for TabApps {
@@ -13,19 +26,106 @@ impl Tab for TabApps {
}
}
#[derive(Default)]
struct AppList {
data: Vec<ParserData>,
}
impl TabApps {
pub fn new(params: TabParams) -> anyhow::Result<Self> {
let state = wgui::parser::parse_from_assets(
&ParseDocumentParams {
globals: params.globals.clone(),
path: "gui/tab/apps.xml",
extra: Default::default(),
},
params.layout,
params.listeners,
params.parent_id,
pub fn new(mut tab_params: TabParams) -> anyhow::Result<Self> {
let doc_params = &ParseDocumentParams {
globals: tab_params.globals.clone(),
path: "gui/tab/apps.xml",
extra: Default::default(),
};
let mut state = wgui::parser::parse_from_assets(
doc_params,
tab_params.layout,
tab_params.listeners,
tab_params.parent_id,
)?;
Ok(Self { state })
gtk::init()?;
let entries = util::desktop_finder::find_entries()?;
let app_list_parent = state.fetch_widget(&tab_params.layout.state, "app_list_parent")?;
let mut app_list = AppList::default();
app_list.mount_entries(&entries, &mut state, doc_params, &mut tab_params, &app_list_parent)?;
Ok(Self {
app_list,
state,
entries,
})
}
}
impl AppList {
fn mount_entry(
&mut self,
parser_state: &mut ParserState,
doc_params: &ParseDocumentParams,
params: &mut TabParams,
list_parent: &WidgetPair,
entry: &DesktopEntry,
) -> anyhow::Result<()> {
let mut template_params = HashMap::new();
// entry icon
template_params.insert(
Rc::from("src_ext"),
entry
.icon_path
.as_ref()
.map_or_else(|| Rc::from(""), |icon_path| Rc::from(icon_path.as_str())),
);
// entry fallback (question mark) icon
template_params.insert(
Rc::from("src"),
if entry.icon_path.is_none() {
Rc::from("dashboard/terminal.svg")
} else {
Rc::from("")
},
);
template_params.insert(Rc::from("name"), Rc::from(entry.app_name.as_str()));
let data = parser_state.parse_template(
doc_params,
"AppEntry",
params.layout,
params.listeners,
list_parent.id,
template_params,
)?;
let button = data.fetch_component_as::<ComponentButton>("button")?;
button.on_click(Box::new(move |common, evt| {
log::info!("click");
Ok(())
}));
self.data.push(data);
Ok(())
}
fn mount_entries(
&mut self,
entries: &[DesktopEntry],
parser_state: &mut ParserState,
doc_params: &ParseDocumentParams,
params: &mut TabParams,
list_parent: &WidgetPair,
) -> anyhow::Result<()> {
for entry in entries {
self.mount_entry(parser_state, doc_params, params, list_parent, entry)?;
}
Ok(())
}
}

View File

@@ -1,7 +1,7 @@
use wgui::{
components::button::ComponentButton,
i18n::Translation,
parser::{ParseDocumentParams, ParserState},
parser::{Fetchable, ParseDocumentParams, ParserState},
widget::label::WidgetLabel,
};

View File

@@ -0,0 +1,129 @@
use gio::prelude::{AppInfoExt, IconExt};
use gtk::traits::IconThemeExt;
#[derive(Debug)]
pub struct DesktopEntry {
pub exec_path: String,
pub exec_args: Vec<String>,
pub app_name: String,
pub icon_path: Option<String>,
pub categories: Vec<String>,
}
pub struct EntrySearchCell {
pub exec_path: String,
pub exec_args: Vec<String>,
pub app_name: String,
pub icon_name: Option<String>,
pub categories: Vec<String>,
}
const CMD_BLACKLIST: [&str; 1] = [
"lsp-plugins", // LSP Plugins collection. They clutter the application list a lot
];
const CATEGORY_TYPE_BLACKLIST: [&str; 5] = ["GTK", "Qt", "X-XFCE", "X-Bluetooth", "ConsoleOnly"];
pub fn find_entries() -> anyhow::Result<Vec<DesktopEntry>> {
let Some(icon_theme) = gtk::IconTheme::default() else {
anyhow::bail!("Failed to get current icon theme information");
};
let mut res = Vec::<DesktopEntry>::new();
let info = gio::AppInfo::all();
log::debug!("app entry count {}", info.len());
'outer: for app_entry in info {
let Some(app_entry_id) = app_entry.id() else {
log::warn!(
"failed to get desktop entry ID for application named \"{}\"",
app_entry.name()
);
continue;
};
let Some(desktop_app) = gio::DesktopAppInfo::new(&app_entry_id) else {
log::warn!(
"failed to find desktop app file from application named \"{}\"",
app_entry.name()
);
continue;
};
if desktop_app.is_nodisplay() || desktop_app.is_hidden() {
continue;
}
let Some(cmd) = desktop_app.commandline() else {
continue;
};
let name = String::from(desktop_app.name());
let exec = String::from(cmd.to_string_lossy());
for blacklisted in CMD_BLACKLIST {
if exec.contains(blacklisted) {
continue 'outer;
}
}
let (exec_path, exec_args) = match exec.split_once(" ") {
Some((left, right)) => (
String::from(left),
right
.split(" ")
.filter(|arg| !arg.starts_with('%')) // exclude arguments like "%f"
.map(String::from)
.collect(),
),
None => (exec, Vec::new()),
};
let icon_path = match desktop_app.icon() {
Some(icon) => {
if let Some(icon_str) = icon.to_string() {
if let Some(s_icon) = icon_theme.lookup_icon(&icon_str, 128, gtk::IconLookupFlags::GENERIC_FALLBACK) {
s_icon.filename().map(|p| String::from(p.to_string_lossy()))
} else {
None
}
} else {
None
}
}
None => None,
};
let categories: Vec<String> = match desktop_app.categories() {
Some(categories) => categories
.split(";")
.filter(|s| !s.is_empty())
.filter(|s| {
for b in CATEGORY_TYPE_BLACKLIST {
if *s == b {
return false;
}
}
true
})
.map(String::from)
.collect(),
None => Vec::new(),
};
let entry = DesktopEntry {
app_name: name,
categories,
exec_path,
exec_args,
icon_path,
};
res.push(entry);
}
Ok(res)
}

View File

@@ -0,0 +1 @@
pub mod desktop_finder;

View File

@@ -222,6 +222,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
if event.state == ElementState::Pressed {
if event.physical_key == PhysicalKey::Code(KeyCode::F10) {
debug_draw_enabled = !debug_draw_enabled;
if debug_draw_enabled {
log::info!(
"Debug draw enabled\n\tAqua: widget boundary\n\tMagenta: Scissoring (separate render pass)"
);
}
testbed.layout().borrow_mut().mark_redraw();
}
@@ -336,10 +341,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
};
let primitives = wgui::drawing::draw(&draw_params).unwrap();
render_context
let draw_result = render_context
.draw(&mut shared_context, &mut cmd_buf, &primitives)
.unwrap();
if debug_draw_enabled {
log::debug!("pass count: {}", draw_result.pass_count);
}
cmd_buf.end_rendering().unwrap();
let cmd_buf = cmd_buf.build().unwrap();

View File

@@ -16,7 +16,7 @@ use wgui::{
globals::WguiGlobals,
i18n::Translation,
layout::{LayoutParams, RcLayout, Widget},
parser::{ParseDocumentExtra, ParseDocumentParams, ParserState},
parser::{Fetchable, ParseDocumentExtra, ParseDocumentParams, ParserState},
widget::{label::WidgetLabel, rectangle::WidgetRectangle},
};

View File

@@ -1,5 +1,5 @@
use std::{cell::RefCell, rc::Rc};
use taffy::{AlignItems, JustifyContent, prelude::length};
use taffy::{AlignItems, JustifyContent};
use crate::{
animation::{Animation, AnimationEasing},
@@ -18,7 +18,7 @@ use crate::{
};
pub struct Params {
pub text: Translation,
pub text: Option<Translation>, // if unset, label will not be populated
pub color: Option<drawing::Color>,
pub border_color: Option<drawing::Color>,
pub hover_border_color: Option<drawing::Color>,
@@ -31,7 +31,7 @@ pub struct Params {
impl Default for Params {
fn default() -> Self {
Self {
text: Translation::from_raw_text(""),
text: Some(Translation::from_raw_text("")),
color: None,
hover_color: None,
border_color: None,
@@ -232,7 +232,6 @@ pub fn construct<U1, U2>(
// force-override style
style.align_items = Some(AlignItems::Center);
style.justify_content = Some(JustifyContent::Center);
style.padding = length(1.0);
style.overflow.x = taffy::Overflow::Hidden;
style.overflow.y = taffy::Overflow::Hidden;
@@ -277,25 +276,30 @@ pub fn construct<U1, U2>(
let light_text = (color.r + color.g + color.b) < 1.5;
let (id_label, _node_label) = layout.add_child(
id_rect,
WidgetLabel::create(
globals,
WidgetLabelParams {
content: params.text,
style: TextStyle {
weight: Some(FontWeight::Bold),
color: Some(if light_text {
Color::new(1.0, 1.0, 1.0, 1.0)
} else {
Color::new(0.0, 0.0, 0.0, 1.0)
}),
..params.text_style
let id_label = if let Some(content) = params.text {
let (id_label, _node_label) = layout.add_child(
id_rect,
WidgetLabel::create(
globals,
WidgetLabelParams {
content,
style: TextStyle {
weight: Some(FontWeight::Bold),
color: Some(if light_text {
Color::new(1.0, 1.0, 1.0, 1.0)
} else {
Color::new(0.0, 0.0, 0.0, 1.0)
}),
..params.text_style
},
},
},
),
Default::default(),
)?;
),
Default::default(),
)?;
id_label
} else {
WidgetID::default()
};
let data = Rc::new(Data {
id_label,

View File

@@ -21,7 +21,7 @@ pub fn parse_component_button<'a, U1, U2>(
let mut hover_color: Option<Color> = None;
let mut hover_border_color: Option<Color> = None;
let mut round = WLength::Units(4.0);
let mut translation = Translation::default();
let mut translation: Option<Translation> = None;
let attribs: Vec<_> = iter_attribs(file, ctx, &node, false).collect();
let text_style = parse_text_style(&attribs);
@@ -30,10 +30,10 @@ pub fn parse_component_button<'a, U1, U2>(
for (key, value) in attribs {
match key.as_ref() {
"text" => {
translation = Translation::from_raw_text(&value);
translation = Some(Translation::from_raw_text(&value));
}
"translation" => {
translation = Translation::from_translation_key(&value);
translation = Some(Translation::from_translation_key(&value));
}
"round" => {
parse_round(&value, &mut round);

View File

@@ -49,6 +49,11 @@ struct ParserFile {
template_parameters: HashMap<Rc<str>, Rc<str>>,
}
/*
`components` could contain connected listener handles.
Do not drop them unless you don't need to handle any events,
including mouse-hover animations.
*/
#[derive(Default, Clone)]
pub struct ParserData {
pub components_by_id: HashMap<Rc<str>, ComponentWeak>,
@@ -60,6 +65,29 @@ pub struct ParserData {
macro_attribs: HashMap<Rc<str>, MacroAttribs>,
}
pub trait Fetchable {
/// Return a component by its string ID
fn fetch_component_by_id(&self, id: &str) -> anyhow::Result<Component>;
/// Return a component by the ID of the widget that owns it
fn fetch_component_by_widget_id(&self, widget_id: WidgetID) -> anyhow::Result<Component>;
/// Fetch a component by string ID and downcast it to a concrete component type `T` (see `components/mod.rs`)
fn fetch_component_as<T: 'static>(&self, id: &str) -> anyhow::Result<Rc<T>>;
/// Fetch a component by widget ID and downcast it to a concrete component type `T` (see `components/mod.rs`)
fn fetch_component_from_widget_id_as<T: 'static>(&self, widget_id: WidgetID) -> anyhow::Result<Rc<T>>;
/// Return a widget by its string ID
fn get_widget_id(&self, id: &str) -> anyhow::Result<WidgetID>;
/// Retrieve the widget associated with a string ID, returning a `WidgetPair` (id and widget itself)
fn fetch_widget(&self, state: &LayoutState, id: &str) -> anyhow::Result<WidgetPair>;
/// Retrieve a widget by string ID and downcast its inner value to type `T` (see `widget/mod.rs`)
fn fetch_widget_as<'a, T: 'static>(&self, state: &'a LayoutState, id: &str) -> anyhow::Result<RefMut<'a, T>>;
}
impl ParserData {
fn take_results_from(&mut self, from: &mut Self) {
let ids = std::mem::take(&mut from.ids);
@@ -85,19 +113,9 @@ impl ParserData {
}
}
/*
WARNING: this struct could contain valid components with already bound listener handles.
Make sure to store them somewhere in your code.
*/
#[derive(Default)]
pub struct ParserState {
pub data: ParserData,
pub path: PathBuf,
}
impl ParserState {
pub fn fetch_component_by_id(&self, id: &str) -> anyhow::Result<Component> {
let Some(weak) = self.data.components_by_id.get(id) else {
impl Fetchable for ParserData {
fn fetch_component_by_id(&self, id: &str) -> anyhow::Result<Component> {
let Some(weak) = self.components_by_id.get(id) else {
anyhow::bail!("Component by ID \"{id}\" doesn't exist");
};
@@ -108,8 +126,8 @@ impl ParserState {
Ok(Component(component))
}
pub fn fetch_component_by_widget_id(&self, widget_id: WidgetID) -> anyhow::Result<Component> {
let Some(weak) = self.data.components_by_widget_id.get(&widget_id) else {
fn fetch_component_by_widget_id(&self, widget_id: WidgetID) -> anyhow::Result<Component> {
let Some(weak) = self.components_by_widget_id.get(&widget_id) else {
anyhow::bail!("Component by widget ID \"{widget_id:?}\" doesn't exist");
};
@@ -120,37 +138,36 @@ impl ParserState {
Ok(Component(component))
}
pub fn fetch_component_as<T: 'static>(&self, id: &str) -> anyhow::Result<Rc<T>> {
fn fetch_component_as<T: 'static>(&self, id: &str) -> anyhow::Result<Rc<T>> {
let component = self.fetch_component_by_id(id)?;
if !(*component.0).as_any().is::<T>() {
anyhow::bail!("fetch_component_as({id}): type not matching");
}
// safety: we already checked it above, should be safe to directly cast it
// safety: we just checked the type
unsafe { Ok(Rc::from_raw(Rc::into_raw(component.0).cast())) }
}
pub fn fetch_component_from_widget_id_as<T: 'static>(&self, widget_id: WidgetID) -> anyhow::Result<Rc<T>> {
fn fetch_component_from_widget_id_as<T: 'static>(&self, widget_id: WidgetID) -> anyhow::Result<Rc<T>> {
let component = self.fetch_component_by_widget_id(widget_id)?;
if !(*component.0).as_any().is::<T>() {
anyhow::bail!("fetch_component_by_widget_id({widget_id:?}): type not matching");
}
// safety: we already checked it above, should be safe to directly cast it
// safety: we just checked the type
unsafe { Ok(Rc::from_raw(Rc::into_raw(component.0).cast())) }
}
pub fn get_widget_id(&self, id: &str) -> anyhow::Result<WidgetID> {
match self.data.ids.get(id) {
fn get_widget_id(&self, id: &str) -> anyhow::Result<WidgetID> {
match self.ids.get(id) {
Some(id) => Ok(*id),
None => anyhow::bail!("Widget by ID \"{id}\" doesn't exist"),
}
}
// returns widget and its id at once
pub fn fetch_widget(&self, state: &LayoutState, id: &str) -> anyhow::Result<WidgetPair> {
fn fetch_widget(&self, state: &LayoutState, id: &str) -> anyhow::Result<WidgetPair> {
let widget_id = self.get_widget_id(id)?;
let widget = state
.widgets
@@ -162,7 +179,7 @@ impl ParserState {
})
}
pub fn fetch_widget_as<'a, T: 'static>(&self, state: &'a LayoutState, id: &str) -> anyhow::Result<RefMut<'a, T>> {
fn fetch_widget_as<'a, T: 'static>(&self, state: &'a LayoutState, id: &str) -> anyhow::Result<RefMut<'a, T>> {
let widget_id = self.get_widget_id(id)?;
let widget = state
.widgets
@@ -175,8 +192,23 @@ impl ParserState {
Ok(casted)
}
}
pub fn process_template<U1, U2>(
/*
WARNING: this struct could contain valid components with already bound listener handles.
Make sure to store them somewhere in your code.
*/
#[derive(Default)]
pub struct ParserState {
pub data: ParserData,
pub path: PathBuf,
}
impl ParserState {
/// This function is suitable in cases if you don't want to pollute main parser state with dynamic IDs
/// Use `instantiate_template` instead unless you want to handle `components` results yourself.
/// Make sure not to drop them if you want to have your listener handles valid
pub fn parse_template<U1, U2>(
&mut self,
doc_params: &ParseDocumentParams,
template_name: &str,
@@ -184,7 +216,7 @@ impl ParserState {
listeners: &mut EventListenerCollection<U1, U2>,
widget_id: WidgetID,
template_parameters: HashMap<Rc<str>, Rc<str>>,
) -> anyhow::Result<()> {
) -> anyhow::Result<ParserData> {
let Some(template) = self.data.templates.get(template_name) else {
anyhow::bail!("no template named \"{template_name}\" found");
};
@@ -204,13 +236,64 @@ impl ParserState {
};
parse_widget_other_internal(&template.clone(), template_parameters, &file, &mut ctx, widget_id)?;
Ok(ctx.data_local)
}
self.data.take_results_from(&mut ctx.data_local);
/// Instantinate template by saving all the results into the main `ParserState`
pub fn instantiate_template<U1, U2>(
&mut self,
doc_params: &ParseDocumentParams,
template_name: &str,
layout: &mut Layout,
listeners: &mut EventListenerCollection<U1, U2>,
widget_id: WidgetID,
template_parameters: HashMap<Rc<str>, Rc<str>>,
) -> anyhow::Result<()> {
let mut data_local = self.parse_template(
doc_params,
template_name,
layout,
listeners,
widget_id,
template_parameters,
)?;
self.data.take_results_from(&mut data_local);
Ok(())
}
}
// convenience wrapper functions for `data`
impl Fetchable for ParserState {
fn fetch_component_by_id(&self, id: &str) -> anyhow::Result<Component> {
self.data.fetch_component_by_id(id)
}
fn fetch_component_by_widget_id(&self, widget_id: WidgetID) -> anyhow::Result<Component> {
self.data.fetch_component_by_widget_id(widget_id)
}
fn fetch_component_as<T: 'static>(&self, id: &str) -> anyhow::Result<Rc<T>> {
self.data.fetch_component_as(id)
}
fn fetch_component_from_widget_id_as<T: 'static>(&self, widget_id: WidgetID) -> anyhow::Result<Rc<T>> {
self.data.fetch_component_from_widget_id_as(widget_id)
}
fn get_widget_id(&self, id: &str) -> anyhow::Result<WidgetID> {
self.data.get_widget_id(id)
}
fn fetch_widget(&self, state: &LayoutState, id: &str) -> anyhow::Result<WidgetPair> {
self.data.fetch_widget(state, id)
}
fn fetch_widget_as<'a, T: 'static>(&self, state: &'a LayoutState, id: &str) -> anyhow::Result<RefMut<'a, T>> {
self.data.fetch_widget_as(state, id)
}
}
#[derive(Debug, Clone)]
struct MacroAttribs {
attribs: HashMap<Rc<str>, Rc<str>>,

View File

@@ -1,9 +1,6 @@
use crate::{
layout::WidgetID,
parser::{
ParserContext, ParserFile, iter_attribs, parse_children, parse_widget_universal,
style::parse_style,
},
parser::{ParserContext, ParserFile, iter_attribs, parse_children, parse_widget_universal, style::parse_style},
renderer_vk::text::custom_glyph::{CustomGlyphContent, CustomGlyphData},
widget::sprite::{WidgetSprite, WidgetSpriteParams},
};
@@ -24,17 +21,18 @@ pub fn parse_widget_sprite<'a, U1, U2>(
for (key, value) in attribs {
match key.as_ref() {
"src" => {
glyph =
match CustomGlyphContent::from_assets(&mut ctx.layout.state.globals.assets(), &value) {
if !value.is_empty() {
glyph = match CustomGlyphContent::from_assets(&mut ctx.layout.state.globals.assets(), &value) {
Ok(glyph) => Some(glyph),
Err(e) => {
log::warn!("failed to load {value}: {e}");
None
}
}
}
}
"src_ext" => {
if std::fs::exists(value.as_ref()).unwrap_or(false) {
if !value.is_empty() && std::fs::exists(value.as_ref()).unwrap_or(false) {
glyph = CustomGlyphContent::from_file(&value).ok();
}
}
@@ -55,9 +53,7 @@ pub fn parse_widget_sprite<'a, U1, U2>(
log::warn!("No source for sprite node!");
}
let (new_id, _) = ctx
.layout
.add_child(parent_id, WidgetSprite::create(params), style)?;
let (new_id, _) = ctx.layout.add_child(parent_id, WidgetSprite::create(params), style)?;
parse_widget_universal(file, ctx, node, new_id);
parse_children(file, ctx, node, new_id)?;

View File

@@ -160,6 +160,10 @@ pub struct Context {
empty_text: Rc<RefCell<Buffer>>,
}
pub struct ContextDrawResult {
pub pass_count: u32,
}
impl Context {
pub fn new(shared: &mut SharedContext, pixel_scale: f32) -> anyhow::Result<Self> {
let viewport = Viewport::new(&shared.gfx)?;
@@ -216,7 +220,7 @@ impl Context {
shared: &mut SharedContext,
cmd_buf: &mut GfxCommandBuffer,
primitives: &[drawing::RenderPrimitive],
) -> anyhow::Result<()> {
) -> anyhow::Result<ContextDrawResult> {
self.dirty = false;
let atlas = shared.atlas_map.get_mut(self.shared_ctx_key).unwrap();
@@ -279,12 +283,14 @@ impl Context {
}
}
log::info!("count {}", passes.len());
let res = ContextDrawResult {
pass_count: passes.len() as u32,
};
for mut pass in passes {
pass.submit(&shared.gfx, &mut self.viewport, cmd_buf, &mut atlas.text_atlas)?;
}
Ok(())
Ok(res)
}
}

View File

@@ -308,7 +308,7 @@ impl Display {
}
pub fn tick_render(&mut self, renderer: &mut GlesRenderer, time_ms: u64) -> anyhow::Result<()> {
renderer.bind(&mut self.gles_texture)?;
renderer.bind(self.gles_texture.clone())?;
let size = Size::from((i32::from(self.width), i32::from(self.height)));
let damage: Rectangle<i32, smithay::utils::Physical> = Rectangle::from_size(size);

View File

@@ -6,6 +6,7 @@ use wgui::{
drawing::Color,
event::{self, CallbackMetadata, EventListenerKind},
layout::LayoutParams,
parser::Fetchable,
renderer_vk::util,
taffy::{self, prelude::length},
widget::{
@@ -163,7 +164,7 @@ where
}
let template_key = format!("Key{:?}", key.cap_type);
gui_state_key.process_template(
gui_state_key.instantiate_template(
&parse_doc_params,
&template_key,
&mut panel.layout,

View File

@@ -39,8 +39,9 @@ where
let mut params: HashMap<Rc<str>, Rc<str>> = HashMap::new();
params.insert("display".into(), (idx + 1).to_string().into());
params.insert("handle".into(), handle.0.to_string().into());
parser_state
.process_template(doc_params, "Set", layout, listeners, widget, params)?;
parser_state.instantiate_template(
doc_params, "Set", layout, listeners, widget, params,
)?;
}
Ok(())
},