diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index b6130ae..7111814 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -113,7 +113,7 @@ jobs:
cargo generate-rpm -o out/$NAME.rpm
mkdir .cargo
cargo vendor -v > .cargo/config.toml
- tar --exclude='out' --exclude='.git*' --exclude='./coppwr/pipewire' --exclude='./coppwr/target' --transform "s/coppwr/$NAME/" -zcvf out/$NAME-vendor.tar.gz -C .. ./coppwr/
+ tar --exclude='out' --exclude='.git*' --exclude='coppwr/pipewire' --exclude='coppwr/target' --transform "s/coppwr/$NAME/" -zcvf out/$NAME-vendor.tar.gz -C .. coppwr/
- uses: actions/upload-artifact@v3
name: Upload packages
with:
diff --git a/Cargo.lock b/Cargo.lock
index b68b339..43181e0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,9 +4,9 @@ version = 3
[[package]]
name = "ab_glyph"
-version = "0.2.21"
+version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5110f1c78cf582855d895ecd0746b653db010cec6d9f5575293f27934d980a39"
+checksum = "b1061f3ff92c2f65800df1f12fc7b4ff44ee14783104187dd04dfee6f11b0fd2"
dependencies = [
"ab_glyph_rasterizer",
"owned_ttf_parser",
@@ -106,9 +106,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
-version = "1.0.5"
+version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783"
+checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
@@ -174,9 +174,9 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]]
name = "ashpd"
-version = "0.6.2"
+version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3affe251686bd936a0afb74b9693e8bf2f193d51da1b9a45d3f1303a9bd2cc7"
+checksum = "cd40a81a15e8f17f4425f55be1fc823de4d65ee23b1599bd6792341ab3f57ede"
dependencies = [
"async-std",
"enumflags2",
@@ -196,7 +196,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c48ccdbf6ca6b121e0f586cbc0e73ae440e56c67c30fa0873b4e110d9c26d2b"
dependencies = [
- "event-listener",
+ "event-listener 2.5.3",
"futures-core",
]
@@ -207,20 +207,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
dependencies = [
"concurrent-queue",
- "event-listener",
+ "event-listener 2.5.3",
"futures-core",
]
[[package]]
name = "async-executor"
-version = "1.5.1"
+version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb"
+checksum = "2c1da3ae8dabd9c00f453a329dfe1fb28da3c0a72e2478cdcd93171740c20499"
dependencies = [
"async-lock",
"async-task",
"concurrent-queue",
- "fastrand 1.9.0",
+ "fastrand 2.0.1",
"futures-lite",
"slab",
]
@@ -266,7 +266,7 @@ dependencies = [
"log",
"parking",
"polling",
- "rustix 0.37.23",
+ "rustix 0.37.25",
"slab",
"socket2",
"waker-fn",
@@ -278,24 +278,23 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
dependencies = [
- "event-listener",
+ "event-listener 2.5.3",
]
[[package]]
name = "async-process"
-version = "1.7.0"
+version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9"
+checksum = "ea6438ba0a08d81529c69b36700fa2f95837bfe3e776ab39cde9c14d9149da88"
dependencies = [
"async-io",
"async-lock",
- "autocfg",
+ "async-signal",
"blocking",
"cfg-if",
- "event-listener",
+ "event-listener 3.0.0",
"futures-lite",
- "rustix 0.37.23",
- "signal-hook",
+ "rustix 0.38.19",
"windows-sys 0.48.0",
]
@@ -307,7 +306,25 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.33",
+ "syn 2.0.38",
+]
+
+[[package]]
+name = "async-signal"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2a5415b7abcdc9cd7d63d6badba5288b2ca017e3fbd4173b8f405449f1a2399"
+dependencies = [
+ "async-io",
+ "async-lock",
+ "atomic-waker",
+ "cfg-if",
+ "futures-core",
+ "futures-io",
+ "rustix 0.38.19",
+ "signal-hook-registry",
+ "slab",
+ "windows-sys 0.48.0",
]
[[package]]
@@ -338,9 +355,9 @@ dependencies = [
[[package]]
name = "async-task"
-version = "4.4.0"
+version = "4.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae"
+checksum = "b9441c6b2fe128a7c2bf680a44c34d0df31ce09e5b7e401fcca3faa483dbc921"
[[package]]
name = "async-trait"
@@ -350,20 +367,14 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.33",
+ "syn 2.0.38",
]
[[package]]
name = "atomic-waker"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3"
-
-[[package]]
-name = "atomic_refcell"
-version = "0.1.11"
+version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "112ef6b3f6cb3cb6fc5b6b494ef7a848492cff1ab0ef4de10b0f7d572861c905"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "atspi"
@@ -415,7 +426,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
- "syn 2.0.33",
+ "syn 2.0.38",
]
[[package]]
@@ -466,17 +477,18 @@ dependencies = [
[[package]]
name = "blocking"
-version = "1.3.1"
+version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65"
+checksum = "8c36a4d0d48574b3dd360b4b7d95cc651d2b6557b6402848a27d4b228a473e2a"
dependencies = [
"async-channel",
"async-lock",
"async-task",
- "atomic-waker",
- "fastrand 1.9.0",
+ "fastrand 2.0.1",
+ "futures-io",
"futures-lite",
- "log",
+ "piper",
+ "tracing",
]
[[package]]
@@ -502,14 +514,14 @@ checksum = "965ab7eb5f8f97d2a083c799f3a1b994fc397b2fe2da5d1da1626ce15a39f2b1"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.33",
+ "syn 2.0.38",
]
[[package]]
name = "byteorder"
-version = "1.4.3"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
@@ -627,15 +639,14 @@ dependencies = [
[[package]]
name = "cocoa-foundation"
-version = "0.1.1"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "931d3837c286f56e3c58423ce4eba12d08db2374461a785c86f672b08b5650d6"
+checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7"
dependencies = [
"bitflags 1.3.2",
"block",
"core-foundation",
"core-graphics-types",
- "foreign-types",
"libc",
"objc",
]
@@ -658,9 +669,9 @@ dependencies = [
[[package]]
name = "concurrent-queue"
-version = "2.2.0"
+version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
+checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400"
dependencies = [
"crossbeam-utils",
]
@@ -682,11 +693,13 @@ checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b"
[[package]]
name = "coppwr"
-version = "1.3.0"
+version = "1.4.0"
dependencies = [
"ashpd",
"eframe",
"egui_dock",
+ "egui_node_graph",
+ "egui_plot",
"pipewire",
"pollster",
]
@@ -801,7 +814,7 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412"
dependencies = [
- "libloading 0.8.0",
+ "libloading 0.8.1",
]
[[package]]
@@ -822,18 +835,18 @@ dependencies = [
[[package]]
name = "ecolor"
-version = "0.22.0"
+version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e479a7fa3f23d4e794f8b2f8b3568dd4e47886ad1b12c9c095e141cb591eb63"
+checksum = "cfdf4e52dbbb615cfd30cf5a5265335c217b5fd8d669593cea74a517d9c605af"
dependencies = [
"bytemuck",
]
[[package]]
name = "eframe"
-version = "0.22.0"
+version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf4596583a2c680c55b6feaa748f74890c4f9cb9c7cb69d6117110444cb65b2f"
+checksum = "26d9efede6c8905d3fc51a5ec9a506d4da4011bbcae0253d0304580fe40af3f5"
dependencies = [
"bytemuck",
"cocoa",
@@ -847,8 +860,10 @@ dependencies = [
"js-sys",
"log",
"objc",
+ "parking_lot",
"percent-encoding",
"raw-window-handle",
+ "static_assertions",
"thiserror",
"wasm-bindgen",
"wasm-bindgen-futures",
@@ -859,9 +874,9 @@ dependencies = [
[[package]]
name = "egui"
-version = "0.22.0"
+version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3aef8ec3ae1b772f340170c65bf27d5b8c28f543a0116c844d2ac08d01123e7"
+checksum = "8bd69fed5fcf4fbb8225b24e80ea6193b61e17a625db105ef0c4d71dde6eb8b7"
dependencies = [
"accesskit",
"ahash",
@@ -872,26 +887,26 @@ dependencies = [
[[package]]
name = "egui-winit"
-version = "0.22.0"
+version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a49155fd4a0a4fb21224407a91de0030847972ef90fc64edb63621caea61cb2"
+checksum = "c15479a96d9fadccf5dac690bdc6373b97b8e1c0dd28367058f25a5298da0195"
dependencies = [
"accesskit_winit",
"arboard",
"egui",
- "instant",
"log",
"raw-window-handle",
"smithay-clipboard",
+ "web-time",
"webbrowser",
"winit",
]
[[package]]
name = "egui_dock"
-version = "0.6.3"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ec07302c1a474f37fe6ef2c6672427880025edc37ac33955e6ea4a11bc6972a"
+checksum = "681b082103898f874b509c0e5ed642c5001ff272bb7adcef32d3d920c26dcf08"
dependencies = [
"duplicate",
"egui",
@@ -900,9 +915,9 @@ dependencies = [
[[package]]
name = "egui_glow"
-version = "0.22.0"
+version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f8c2752cdf1b0ef5fcda59a898cacabad974d4f5880e92a420b2c917022da64"
+checksum = "ce6726c08798822280038bbad2e32f4fc3cbed800cd51c6e34e99cd2d60cc1bc"
dependencies = [
"bytemuck",
"egui",
@@ -913,11 +928,31 @@ dependencies = [
"web-sys",
]
+[[package]]
+name = "egui_node_graph"
+version = "0.4.0"
+source = "git+https://github.com/dimtpap/egui_node_graph.git?rev=ca3cd04ac780eff9e3b1be7e0c8c837df66ff889#ca3cd04ac780eff9e3b1be7e0c8c837df66ff889"
+dependencies = [
+ "egui",
+ "slotmap",
+ "smallvec",
+ "thiserror",
+]
+
+[[package]]
+name = "egui_plot"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f33a00fe8eb1ba56535b3dbacdecc7a1365a328908a97c5f3c81bb466be72b"
+dependencies = [
+ "egui",
+]
+
[[package]]
name = "emath"
-version = "0.22.0"
+version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3857d743a6e0741cdd60b622a74c7a36ea75f5f8f11b793b41d905d2c9721a4b"
+checksum = "1ef2b29de53074e575c18b694167ccbe6e5191f7b25fe65175a0d905a32eeec0"
dependencies = [
"bytemuck",
]
@@ -940,18 +975,17 @@ checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.33",
+ "syn 2.0.38",
]
[[package]]
name = "epaint"
-version = "0.22.0"
+version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09333964d4d57f40a85338ba3ca5ed4716070ab184dcfed966b35491c5c64f3b"
+checksum = "58067b840d009143934d91d8dcb8ded054d8301d7c11a517ace0a99bb1e1595e"
dependencies = [
"ab_glyph",
"ahash",
- "atomic_refcell",
"bytemuck",
"ecolor",
"emath",
@@ -968,25 +1002,14 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
-version = "0.3.3"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
+checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860"
dependencies = [
- "errno-dragonfly",
"libc",
"windows-sys 0.48.0",
]
-[[package]]
-name = "errno-dragonfly"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
-dependencies = [
- "cc",
- "libc",
-]
-
[[package]]
name = "error-code"
version = "2.3.1"
@@ -1003,6 +1026,17 @@ version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
+[[package]]
+name = "event-listener"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29e56284f00d94c1bc7fd3c77027b4623c88c1f53d8d2394c6199f2921dea325"
+dependencies = [
+ "concurrent-queue",
+ "parking",
+ "pin-project-lite",
+]
+
[[package]]
name = "fastrand"
version = "1.9.0"
@@ -1014,9 +1048,9 @@ dependencies = [
[[package]]
name = "fastrand"
-version = "2.0.0"
+version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
+checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "fdeflate"
@@ -1029,9 +1063,9 @@ dependencies = [
[[package]]
name = "flate2"
-version = "1.0.27"
+version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
+checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
dependencies = [
"crc32fast",
"miniz_oxide",
@@ -1105,7 +1139,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.33",
+ "syn 2.0.38",
]
[[package]]
@@ -1275,9 +1309,9 @@ dependencies = [
[[package]]
name = "hashbrown"
-version = "0.14.0"
+version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
+checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12"
[[package]]
name = "heck"
@@ -1287,9 +1321,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
-version = "0.3.2"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
+checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
[[package]]
name = "hex"
@@ -1332,9 +1366,9 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "2.0.0"
+version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
+checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897"
dependencies = [
"equivalent",
"hashbrown",
@@ -1387,9 +1421,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "jobserver"
-version = "0.1.26"
+version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
+checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d"
dependencies = [
"libc",
]
@@ -1432,9 +1466,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
-version = "0.2.148"
+version = "0.2.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
+checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
[[package]]
name = "libloading"
@@ -1448,9 +1482,9 @@ dependencies = [
[[package]]
name = "libloading"
-version = "0.8.0"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d580318f95776505201b28cf98eb1fa5e4be3b689633ba6a3e6cd880ff22d8cb"
+checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
@@ -1490,9 +1524,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]]
name = "linux-raw-sys"
-version = "0.4.7"
+version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"
+checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
[[package]]
name = "lock_api"
@@ -1524,9 +1558,9 @@ dependencies = [
[[package]]
name = "memchr"
-version = "2.6.3"
+version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
+checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "memmap2"
@@ -1689,9 +1723,9 @@ dependencies = [
[[package]]
name = "num-traits"
-version = "0.2.16"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
+checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c"
dependencies = [
"autocfg",
]
@@ -1735,7 +1769,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
- "syn 2.0.33",
+ "syn 2.0.38",
]
[[package]]
@@ -1829,9 +1863,9 @@ dependencies = [
[[package]]
name = "parking"
-version = "2.1.0"
+version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e"
+checksum = "e52c774a4c39359c1d1c52e43f73dd91a75a614652c825408eec30c95a9b2067"
[[package]]
name = "parking_lot"
@@ -1886,6 +1920,17 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+[[package]]
+name = "piper"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4"
+dependencies = [
+ "atomic-waker",
+ "fastrand 2.0.1",
+ "futures-io",
+]
+
[[package]]
name = "pipewire"
version = "0.7.2"
@@ -1966,7 +2011,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
dependencies = [
"once_cell",
- "toml_edit",
+ "toml_edit 0.19.15",
]
[[package]]
@@ -1995,9 +2040,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.67"
+version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
+checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
dependencies = [
"unicode-ident",
]
@@ -2058,9 +2103,9 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.9.5"
+version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
+checksum = "aaac441002f822bc9705a681810a4dd2963094b9ca0ddc41cb963a4c189189ea"
dependencies = [
"aho-corasick",
"memchr",
@@ -2070,9 +2115,9 @@ dependencies = [
[[package]]
name = "regex-automata"
-version = "0.3.8"
+version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
+checksum = "5011c7e263a695dc8ca064cddb722af1be54e517a280b12a5356f98366899e5d"
dependencies = [
"aho-corasick",
"memchr",
@@ -2081,9 +2126,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
-version = "0.7.5"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
+checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rustc-hash"
@@ -2093,9 +2138,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
-version = "0.37.23"
+version = "0.37.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
+checksum = "d4eb579851244c2c03e7c24f501c3432bed80b8f720af1d6e5b0e0f01555a035"
dependencies = [
"bitflags 1.3.2",
"errno",
@@ -2107,14 +2152,14 @@ dependencies = [
[[package]]
name = "rustix"
-version = "0.38.13"
+version = "0.38.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662"
+checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed"
dependencies = [
"bitflags 2.4.0",
"errno",
"libc",
- "linux-raw-sys 0.4.7",
+ "linux-raw-sys 0.4.10",
"windows-sys 0.48.0",
]
@@ -2154,22 +2199,22 @@ dependencies = [
[[package]]
name = "serde"
-version = "1.0.188"
+version = "1.0.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
+checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.188"
+version = "1.0.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
+checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.33",
+ "syn 2.0.38",
]
[[package]]
@@ -2180,7 +2225,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.33",
+ "syn 2.0.38",
]
[[package]]
@@ -2194,9 +2239,9 @@ dependencies = [
[[package]]
name = "sha1"
-version = "0.10.5"
+version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -2209,16 +2254,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
-[[package]]
-name = "signal-hook"
-version = "0.3.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
-dependencies = [
- "libc",
- "signal-hook-registry",
-]
-
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
@@ -2254,15 +2289,15 @@ dependencies = [
[[package]]
name = "smallvec"
-version = "1.11.0"
+version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
+checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
[[package]]
name = "smithay-client-toolkit"
-version = "0.16.0"
+version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f307c47d32d2715eb2e0ece5589057820e0e5e70d07c247d1063e844e107f454"
+checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9"
dependencies = [
"bitflags 1.3.2",
"calloop",
@@ -2328,9 +2363,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.33"
+version = "2.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668"
+checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
dependencies = [
"proc-macro2",
"quote",
@@ -2339,9 +2374,9 @@ dependencies = [
[[package]]
name = "system-deps"
-version = "6.1.1"
+version = "6.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3"
+checksum = "94af52f9402f94aac4948a2518b43359be8d9ce6cd9efc1c4de3b2f7b7e897d6"
dependencies = [
"cfg-expr",
"heck",
@@ -2363,30 +2398,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
dependencies = [
"cfg-if",
- "fastrand 2.0.0",
+ "fastrand 2.0.1",
"redox_syscall",
- "rustix 0.38.13",
+ "rustix 0.38.19",
"windows-sys 0.48.0",
]
[[package]]
name = "thiserror"
-version = "1.0.48"
+version = "1.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7"
+checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.48"
+version = "1.0.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
+checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.33",
+ "syn 2.0.38",
]
[[package]]
@@ -2431,14 +2466,14 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "toml"
-version = "0.7.8"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
+checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
- "toml_edit",
+ "toml_edit 0.20.2",
]
[[package]]
@@ -2455,6 +2490,17 @@ name = "toml_edit"
version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
dependencies = [
"indexmap",
"serde",
@@ -2465,11 +2511,10 @@ dependencies = [
[[package]]
name = "tracing"
-version = "0.1.37"
+version = "0.1.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
+checksum = "ee2ef2af84856a50c1d430afce2fdded0a4ec7eda868db86409b4543df0797f9"
dependencies = [
- "cfg-if",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
@@ -2477,20 +2522,20 @@ dependencies = [
[[package]]
name = "tracing-attributes"
-version = "0.1.26"
+version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
+checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.33",
+ "syn 2.0.38",
]
[[package]]
name = "tracing-core"
-version = "0.1.31"
+version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
+checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
"once_cell",
]
@@ -2503,9 +2548,9 @@ checksum = "49d64318d8311fc2668e48b63969f4343e0a85c4a109aa8460d6672e364b8bd1"
[[package]]
name = "typenum"
-version = "1.16.0"
+version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "uds_windows"
@@ -2582,9 +2627,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "waker-fn"
-version = "1.1.0"
+version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
+checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690"
[[package]]
name = "walkdir"
@@ -2623,7 +2668,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
- "syn 2.0.33",
+ "syn 2.0.38",
"wasm-bindgen-shared",
]
@@ -2657,7 +2702,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.33",
+ "syn 2.0.38",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -2763,11 +2808,21 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "web-time"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8208e3fdbc243c8fd30805721869242a7f6de3e2e9f3b057652ab36e52ae1e87"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
[[package]]
name = "webbrowser"
-version = "0.8.11"
+version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2c79b77f525a2d670cb40619d7d9c673d09e0666f72c591ebd7861f84a87e57"
+checksum = "82b2391658b02c27719fc5a0a73d6e696285138e8b12fba9d4baa70451023c71"
dependencies = [
"core-foundation",
"home",
@@ -2798,9 +2853,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
dependencies = [
"winapi",
]
@@ -2987,9 +3042,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "winit"
-version = "0.28.6"
+version = "0.28.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "866db3f712fffba75d31bf0cdecf357c8aeafd158c5b7ab51dba2a2b2d47f196"
+checksum = "9596d90b45384f5281384ab204224876e8e8bf7d58366d9b795ad99aa9894b94"
dependencies = [
"android-activity",
"bitflags 1.3.2",
@@ -3022,9 +3077,9 @@ dependencies = [
[[package]]
name = "winnow"
-version = "0.5.15"
+version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc"
+checksum = "a3b801d0e0a6726477cc207f60162da452f3a95adb368399bef20a946e06f65c"
dependencies = [
"memchr",
]
@@ -3083,9 +3138,9 @@ dependencies = [
[[package]]
name = "xml-rs"
-version = "0.8.18"
+version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bab77e97b50aee93da431f2cee7cd0f43b4d1da3c408042f2d7d164187774f0a"
+checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
[[package]]
name = "zbus"
@@ -3106,7 +3161,7 @@ dependencies = [
"byteorder",
"derivative",
"enumflags2",
- "event-listener",
+ "event-listener 2.5.3",
"futures-core",
"futures-sink",
"futures-util",
diff --git a/Cargo.toml b/Cargo.toml
index e92fe5d..6a06e67 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "coppwr"
-version = "1.3.0"
+version = "1.4.0"
authors = ["Dimitris Papaioannou "]
edition = "2021"
rust-version = "1.70"
@@ -16,9 +16,11 @@ categories = ["gui", "multimedia"]
[dependencies]
pipewire = {version = "*", git = "https://gitlab.freedesktop.org/dimtpap/pipewire-rs.git", rev = "7bd8b2d3c5d91f56b20c345e97244fff9e58ea0f"}
-eframe = {version = "0.22.0", features = ["wayland"]}
-egui_dock = "0.6.3"
-ashpd = {version = "0.6.2", optional = true}
+egui_node_graph = {version = "*", git = "https://github.com/dimtpap/egui_node_graph.git", rev= "ca3cd04ac780eff9e3b1be7e0c8c837df66ff889"}
+eframe = {version = "0.23.0", features = ["wayland"]}
+egui_plot = "0.23.0"
+egui_dock = "0.8.1"
+ashpd = {version = "0.6.5", optional = true}
pollster = {version = "0.3.0", optional = true}
[features]
diff --git a/README.md b/README.md
index 5162d49..dfba454 100644
--- a/README.md
+++ b/README.md
@@ -18,6 +18,7 @@ If you want to learn the inner workings of PipeWire check out the [docs page on
and its [wiki](https://gitlab.freedesktop.org/pipewire/pipewire/-/wikis/home).
## Features
+- Node graph editing
- Object inspection, creation & destruction
- Process monitoring & profiler statistics
- Metadata editing
@@ -74,6 +75,7 @@ See their usage instructions.
## Credits
- [egui](https://crates.io/crates/egui)+[eframe](https://crates.io/crates/eframe) - Immediate mode GUI for Rust and its desktop/web framework
- [egui_dock](https://crates.io/crates/egui_dock) - Docking support for egui
+- ([A fork](https://github.com/kamirr/egui_node_graph) of) [egui_node_graph](https://crates.io/crates/egui_node_graph) - A helper library to create interactive node graphs using egui
- ([A fork](https://gitlab.freedesktop.org/dimtpap/pipewire-rs/-/tree/coppwr-next) of) [pipewire-rs](https://crates.io/crates/pipewire) - Rust bindings to libpipewire
- [ashpd](https://crates.io/crates/ashpd) - XDG Desktop Portals wrapper
- [pollster](https://crates.io/crates/pollster) - Simple async executor
diff --git a/assets/io.github.dimtpap.coppwr.metainfo.xml b/assets/io.github.dimtpap.coppwr.metainfo.xml
index a6ea399..ab79f9d 100644
--- a/assets/io.github.dimtpap.coppwr.metainfo.xml
+++ b/assets/io.github.dimtpap.coppwr.metainfo.xml
@@ -17,6 +17,7 @@
Features
+ - Node graph editing
- Object inspection, creation and destruction
- Process monitoring and profiler statistics
- Metadata editing
@@ -50,6 +51,14 @@
+
+
+
+ - Added Graph view
+ - Added filtering based on global properties in Global Tracker
-
+
+
+
diff --git a/assets/sc.png b/assets/sc.png
index 4331d22..f78f3cb 100644
Binary files a/assets/sc.png and b/assets/sc.png differ
diff --git a/src/backend/bind.rs b/src/backend/bind.rs
index f1b83d6..22d861c 100644
--- a/src/backend/bind.rs
+++ b/src/backend/bind.rs
@@ -75,7 +75,7 @@ impl BoundGlobal {
let sx = sx.clone();
let id = global.id;
- let (global, _object_listener): (_, Box) = match global.type_ {
+ let (global, object_listener): (_, Box) = match global.type_ {
ObjectType::Module => {
listeners::module(registry.bind::(global)?, id, sx)
}
@@ -108,7 +108,7 @@ impl BoundGlobal {
}
};
- let _proxy_listener = global
+ let proxy_listener = global
.as_proxy()
.add_listener_local()
.removed(proxy_removed)
@@ -116,8 +116,8 @@ impl BoundGlobal {
Ok(Self {
global,
- _object_listener,
- _proxy_listener,
+ _object_listener: object_listener,
+ _proxy_listener: proxy_listener,
})
}
diff --git a/src/ui/app.rs b/src/ui/app.rs
index 7a96a57..a8f13ca 100644
--- a/src/ui/app.rs
+++ b/src/ui/app.rs
@@ -23,7 +23,7 @@ use ashpd::{desktop::screencast::SourceType, enumflags2::BitFlags};
use crate::backend::{self, Event, RemoteInfo};
use super::{
- common::EditableKVList, globals_store::ObjectData, ContextManager, GlobalsStore,
+ common::EditableKVList, globals_store::ObjectData, ContextManager, GlobalsStore, Graph,
MetadataEditor, ObjectCreator, Profiler, WindowedTool,
};
@@ -32,6 +32,7 @@ enum View {
GlobalTracker = 1 << 0,
Profiler = 1 << 1,
ProcessViewer = 1 << 2,
+ Graph = 1 << 3,
}
impl View {
@@ -40,6 +41,7 @@ impl View {
Self::Profiler => "Profiler",
Self::ProcessViewer => "Process Viewer",
Self::GlobalTracker => "Global Tracker",
+ Self::Graph => "Graph",
}
}
}
@@ -51,6 +53,7 @@ struct Inspector {
globals: GlobalsStore,
profiler: Profiler,
+ graph: Graph,
object_creator: WindowedTool,
metadata_editor: WindowedTool,
@@ -62,14 +65,16 @@ impl Inspector {
remote: RemoteInfo,
mainloop_properties: Vec<(String, String)>,
context_properties: Vec<(String, String)>,
+ open_tabs: impl Iterator- ,
) -> Self {
Self {
handle: backend::Handle::run(remote, mainloop_properties, context_properties),
- open_tabs: View::GlobalTracker as u8,
+ open_tabs: open_tabs.fold(0, |acc, v| acc | v as u8),
globals: GlobalsStore::new(),
profiler: Profiler::with_max_profilings(250),
+ graph: Graph::new(),
object_creator: WindowedTool::default(),
metadata_editor: WindowedTool::default(),
@@ -77,7 +82,11 @@ impl Inspector {
}
}
- pub fn views_menu_buttons(&mut self, ui: &mut egui::Ui, tree: &mut egui_dock::Tree) {
+ pub fn views_menu_buttons(
+ &mut self,
+ ui: &mut egui::Ui,
+ dock_state: &mut egui_dock::DockState,
+ ) {
ui.menu_button("View", |ui| {
for (tab, title, description) in [
(
@@ -91,6 +100,7 @@ impl Inspector {
"⏱ Process Viewer",
"Performance measurements of running nodes",
),
+ (View::Graph, "🖧 Graph", "Visual representation of the graph"),
] {
let bit = tab as u8;
ui.add_enabled_ui(self.open_tabs & bit == 0, |ui| {
@@ -100,7 +110,7 @@ impl Inspector {
.clicked()
{
self.open_tabs |= bit;
- tree.push_to_focused_leaf(tab);
+ dock_state.push_to_focused_leaf(tab);
}
});
}
@@ -191,6 +201,7 @@ impl Inspector {
self.metadata_editor.tool.add_metadata(id, name);
}
}
+
_ => {}
}
}
@@ -206,9 +217,44 @@ impl Inspector {
_ => {}
}
}
+ self.graph.remove_item(id);
}
Event::GlobalInfo(id, info) => {
- self.globals.set_global_info(id, Some(info));
+ let Some(global) = self.globals.get_global(id) else {
+ return;
+ };
+
+ // Add to graph
+ {
+ let global_borrow = global.borrow();
+ match *global_borrow.object_type() {
+ ObjectType::Node => {
+ self.graph.add_node(id, global);
+ }
+ ObjectType::Port => {
+ if let Some(parent) = global_borrow.parent_id() {
+ let name = global_borrow.name().cloned().unwrap_or_default();
+ match info[0].1.as_str() {
+ "Input" => {
+ self.graph.add_input_port(id, parent, name);
+ }
+ "Output" => self.graph.add_output_port(id, parent, name),
+ _ => {}
+ }
+ }
+ }
+ ObjectType::Link => {
+ if let Some((output, input)) =
+ info[3].1.parse().ok().zip(info[1].1.parse().ok())
+ {
+ self.graph.add_link(id, output, input);
+ }
+ }
+ _ => {}
+ }
+ }
+
+ global.borrow_mut().set_info(Some(info));
}
Event::GlobalProperties(id, props) => {
self.globals.set_global_props(id, props);
@@ -284,6 +330,9 @@ impl egui_dock::TabViewer for Inspector {
View::GlobalTracker => {
self.globals.show(ui, &self.handle.sx);
}
+ View::Graph => {
+ self.graph.show(ui, &mut self.handle.sx);
+ }
}
}
@@ -295,11 +344,15 @@ impl egui_dock::TabViewer for Inspector {
self.open_tabs &= !(*tab as u8);
true
}
+
+ fn scroll_bars(&self, _tab: &Self::Tab) -> [bool; 2] {
+ [false, false]
+ }
}
enum State {
Connected {
- tabs_tree: egui_dock::Tree,
+ dock_state: egui_dock::DockState,
inspector: Inspector,
about: bool,
},
@@ -329,12 +382,18 @@ impl State {
mainloop_properties: Vec<(String, String)>,
context_properties: Vec<(String, String)>,
) -> Self {
- let mut tabs = Vec::with_capacity(3 /* Number of views */);
+ let mut tabs = Vec::with_capacity(4 /* Number of views */);
+ tabs.push(View::Graph);
tabs.push(View::GlobalTracker);
Self::Connected {
- tabs_tree: egui_dock::Tree::new(tabs),
- inspector: Inspector::new(remote, mainloop_properties, context_properties),
+ inspector: Inspector::new(
+ remote,
+ mainloop_properties,
+ context_properties,
+ tabs.iter().copied(),
+ ),
+ dock_state: egui_dock::DockState::new(tabs),
about: false,
}
}
@@ -361,9 +420,9 @@ impl State {
}
}
-pub struct CoppwrApp(State);
+pub struct App(State);
-impl CoppwrApp {
+impl App {
pub fn new() -> Self {
Self(State::new_connected(
RemoteInfo::default(),
@@ -373,7 +432,7 @@ impl CoppwrApp {
}
}
-impl eframe::App for CoppwrApp {
+impl eframe::App for App {
fn on_exit(&mut self, _: Option<&eframe::glow::Context>) {
self.0.disconnect();
}
@@ -384,7 +443,7 @@ impl eframe::App for CoppwrApp {
match &mut self.0 {
State::Connected {
- tabs_tree,
+ dock_state: tabs_tree,
inspector,
about,
} => {
@@ -446,17 +505,19 @@ impl eframe::App for CoppwrApp {
ui.separator();
- ui.label("This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 3 as published by the Free Software Foundation.");
+ ui.label("This program is free software: you can redistribute it and/or modify it \
+ under the terms of the GNU General Public License version 3 as published \
+ by the Free Software Foundation.");
});
});
inspector.tool_windows(ctx);
let mut style = egui_dock::Style::from_egui(ctx.style().as_ref());
- style.tabs.inner_margin = egui::Margin::symmetric(5., 5.);
+ style.tab.tab_body.inner_margin = egui::Margin::symmetric(5., 5.);
egui_dock::DockArea::new(tabs_tree)
.style(style)
- .scroll_area_in_tabs(false)
+ .show_window_close_buttons(false) // Close buttons on windows do not call TabViewer::on_close
.show(ctx, inspector);
}
State::Unconnected {
diff --git a/src/ui/common.rs b/src/ui/common.rs
index 407ac6f..3108477 100644
--- a/src/ui/common.rs
+++ b/src/ui/common.rs
@@ -43,7 +43,7 @@ pub fn key_val_display(
header: &str,
kv: impl Iterator
- , impl Into)>,
) {
- egui::CollapsingHeader::new(header).show(ui, |ui| {
+ ui.collapsing(header, |ui| {
key_val_table(ui, min_scrolled_height, max_height, |ui| {
for (k, v) in kv {
ui.label(k);
@@ -163,3 +163,132 @@ impl MapEditor {
std::mem::take(&mut self.properties)
}
}
+
+mod kv_matcher {
+ use eframe::egui;
+
+ #[derive(PartialEq, Eq)]
+ enum StringMatchMode {
+ Substring,
+ StartsWith,
+ EndsWith,
+ Exact,
+ }
+
+ impl StringMatchMode {
+ fn matches(&self, haystack: &str, needle: &str) -> bool {
+ match self {
+ Self::Substring => haystack.contains(needle),
+ Self::StartsWith => haystack.starts_with(needle),
+ Self::EndsWith => haystack.ends_with(needle),
+ Self::Exact => haystack == needle,
+ }
+ }
+
+ fn show_selector(&mut self, ui: &mut egui::Ui, id_source: impl std::hash::Hash) {
+ const fn as_user_str(mode: &StringMatchMode) -> &'static str {
+ match mode {
+ StringMatchMode::Substring => "contains",
+ StringMatchMode::StartsWith => "starts with",
+ StringMatchMode::EndsWith => "ends with",
+ StringMatchMode::Exact => "is",
+ }
+ }
+
+ egui::ComboBox::from_id_source(id_source)
+ .selected_text(as_user_str(self))
+ .show_ui(ui, |ui| {
+ for mode in [
+ Self::Substring,
+ Self::StartsWith,
+ Self::EndsWith,
+ Self::Exact,
+ ] {
+ let text = as_user_str(&mode);
+ ui.selectable_value(self, mode, text);
+ }
+ });
+ }
+ }
+
+ struct StringFilter {
+ needle: String,
+ match_mode: StringMatchMode,
+ }
+
+ impl StringFilter {
+ fn test(&self, value: &str) -> bool {
+ self.match_mode.matches(value, &self.needle)
+ }
+
+ fn show(&mut self, ui: &mut egui::Ui, label: &str, text_edit_width: f32) {
+ ui.label(label);
+ self.match_mode.show_selector(ui, label);
+ egui::TextEdit::singleline(&mut self.needle)
+ .hint_text(label)
+ .desired_width(text_edit_width)
+ .show(ui);
+ }
+ }
+
+ impl Default for StringFilter {
+ fn default() -> Self {
+ Self {
+ needle: String::new(),
+ match_mode: StringMatchMode::Substring,
+ }
+ }
+ }
+
+ pub struct KvMatcher {
+ filters: Vec<(StringFilter, StringFilter)>,
+ }
+
+ impl KvMatcher {
+ pub const fn new() -> Self {
+ Self {
+ filters: Vec::new(),
+ }
+ }
+
+ pub fn matches(
+ &self,
+ kv: impl Iterator
- , impl AsRef)> + Clone,
+ ) -> bool {
+ self.filters.iter().all(|(key_filter, value_filter)| {
+ kv.clone()
+ .any(|(k, v)| key_filter.test(k.as_ref()) && value_filter.test(v.as_ref()))
+ })
+ }
+
+ pub fn show(&mut self, ui: &mut egui::Ui) {
+ let mut i = 0usize;
+ self.filters.retain_mut(|(key_filter, value_filter)| {
+ let keep = ui
+ .push_id(i, |ui| {
+ ui.horizontal(|ui| {
+ let keep = !ui.button("Delete").clicked();
+
+ key_filter.show(ui, "Key", ui.available_width() / 4.);
+ value_filter.show(ui, "Value", f32::INFINITY);
+
+ keep
+ })
+ .inner
+ })
+ .inner;
+
+ i += 1;
+
+ keep
+ });
+
+ if ui.button("Add").clicked() {
+ self.filters
+ .push((StringFilter::default(), StringFilter::default()));
+ }
+ }
+ }
+}
+
+pub use kv_matcher::KvMatcher;
diff --git a/src/ui/global.rs b/src/ui/global.rs
index 980383a..4bd02ce 100644
--- a/src/ui/global.rs
+++ b/src/ui/global.rs
@@ -107,7 +107,7 @@ impl ObjectData {
user_permissions,
..
} => {
- egui::CollapsingHeader::new("Permissions").show(ui, |ui| {
+ ui.collapsing("Permissions", |ui| {
if ui.small_button("Get permissions").clicked() {
sx.send(Request::CallObjectMethod(
id,
@@ -228,7 +228,7 @@ impl Global {
}
.and_then(|id| id.parse().ok());
- let mut name = 'name: {
+ let mut name = {
match self.object_type() {
t @ (ObjectType::Device | ObjectType::Node) => {
let lookups = match t {
@@ -238,12 +238,9 @@ impl Global {
unreachable!();
}
};
- for l in lookups {
- if let Some(n) = self.props.get(l) {
- break 'name Some(n);
- }
- }
- None
+ lookups
+ .into_iter()
+ .find_map(|lookup| self.props.get(lookup))
}
ObjectType::Port => self.props.get("port.name"),
ObjectType::Core => self.props.get("core.name"),
@@ -252,10 +249,12 @@ impl Global {
};
if name.is_none() {
- for (k, v) in self.props.iter().filter(|(k, _)| k.contains(".name")) {
- if k == "library.name"
- || k == "factory.name" && *self.object_type() != ObjectType::Factory
- {
+ for (k, v) in self
+ .props
+ .iter()
+ .filter(|(k, _)| k.ends_with(".name") && k.as_str() != "library.name")
+ {
+ if *self.object_type() != ObjectType::Factory && k == "factory.name" {
continue;
}
name = Some(v);
@@ -266,29 +265,58 @@ impl Global {
self.name = name.cloned();
}
- pub fn show(
- &mut self,
- ui: &mut egui::Ui,
- draw_subobjects: bool,
- searched_property: &str,
- sx: &backend::Sender,
- ) {
- ui.group(|ui| {
- ui.set_width(ui.available_width());
+ pub fn show(&mut self, ui: &mut egui::Ui, draw_subobjects: bool, sx: &backend::Sender) {
+ fn subobjects_display(
+ ui: &mut egui::Ui,
+ id_source: Option<&str>,
+ len: usize,
+ subobjects: impl Iterator
- >>,
+ sx: &backend::Sender,
+ ) {
+ let width = ui.available_width() / len as f32 - 6.;
+
+ let sc = egui::ScrollArea::horizontal();
+
+ if let Some(id_source) = id_source {
+ sc.id_source(id_source)
+ } else {
+ sc
+ }
+ .show(ui, |ui| {
+ ui.horizontal(|ui| {
+ for sub in subobjects {
+ ui.with_layout(egui::Layout::top_down_justified(egui::Align::Min), |ui| {
+ ui.set_max_width(width);
+ sub.borrow_mut().show(ui, true, sx);
+ });
+ }
+ });
+ });
+ }
- if let Some(name) = self.name() {
- ui.label(name);
+ ui.group(|ui| {
+ if ui.layout().cross_justify {
+ // Frames don't expand unless the children do
+ ui.set_width(ui.available_width());
}
- ui.horizontal(|ui| {
- ui.label(self.id.to_string());
- ui.label(self.object_type().to_str());
- });
+ ui.scope(|ui| {
+ ui.style_mut().wrap = Some(false);
- ui.with_layout(egui::Layout::default(), |ui| {
- if ui.small_button("Destroy").clicked() {
- sx.send(Request::DestroyObject(self.id)).ok();
+ if let Some(name) = self.name() {
+ ui.label(name);
}
+
+ ui.horizontal(|ui| {
+ ui.label(self.id.to_string());
+ ui.label(self.object_type().to_str());
+ });
+
+ ui.with_layout(egui::Layout::default(), |ui| {
+ if ui.small_button("Destroy").clicked() {
+ sx.send(Request::DestroyObject(self.id)).ok();
+ }
+ });
});
ui.push_id(self.id, |ui| {
@@ -296,15 +324,6 @@ impl Global {
key_val_display(ui, 400f32, f32::INFINITY, "Info", info.iter().cloned());
}
- if !searched_property.is_empty() {
- if let Some(val) = self.props().get(searched_property) {
- ui.horizontal(|ui| {
- ui.label(searched_property);
- ui.label(val);
- });
- }
- }
-
// Clients can have their properties updated
if let ObjectData::Client {
ref mut user_properties,
@@ -342,14 +361,19 @@ impl Global {
if !self.subobjects.is_empty() {
self.subobjects.retain(|sub| sub.upgrade().is_some());
- egui::CollapsingHeader::new(subobjects_header).show(ui, |ui| {
+ ui.collapsing(subobjects_header, |ui| {
let subobjects = self.subobjects.iter().filter_map(std::rc::Weak::upgrade);
if draw_subobjects {
match self.object_type() {
ObjectType::Device | ObjectType::Client => {
- for sub in subobjects {
- sub.borrow_mut().show(ui, true, searched_property, sx);
- }
+ ui.with_layout(
+ egui::Layout::top_down_justified(egui::Align::Min),
+ |ui| {
+ for sub in subobjects {
+ sub.borrow_mut().show(ui, true, sx);
+ }
+ },
+ );
}
ObjectType::Node => {
let mut outs = Vec::with_capacity(self.subobjects.len());
@@ -378,29 +402,24 @@ impl Global {
continue;
}
ui.label(label);
- ui.columns(ports.len(), |ui| {
- for (i, port) in ports.into_iter().enumerate() {
- port.borrow_mut().show(
- &mut ui[i],
- true,
- searched_property,
- sx,
- );
- }
- });
+
+ subobjects_display(
+ ui,
+ Some(label),
+ ports.len(),
+ ports.into_iter(),
+ sx,
+ );
}
}
ObjectType::Port => {
- ui.columns(self.subobjects.len(), |ui| {
- for (i, sub) in subobjects.enumerate() {
- sub.borrow_mut().show(
- &mut ui[i],
- true,
- searched_property,
- sx,
- );
- }
- });
+ subobjects_display(
+ ui,
+ None,
+ self.subobjects.len(),
+ subobjects,
+ sx,
+ );
}
_ => {}
}
diff --git a/src/ui/globals_store.rs b/src/ui/globals_store.rs
index 2fa7ea6..46580fc 100644
--- a/src/ui/globals_store.rs
+++ b/src/ui/globals_store.rs
@@ -19,7 +19,7 @@ use std::{cell::RefCell, collections::BTreeMap, rc::Rc};
use eframe::egui;
use pipewire::types::ObjectType;
-use crate::backend;
+use crate::{backend, ui::common::KvMatcher};
#[path = "global.rs"]
mod global;
@@ -29,8 +29,9 @@ pub struct GlobalsStore {
globals: BTreeMap>>,
group_subobjects: bool,
+
shown_types: u16,
- searched_property: String,
+ properties_filter: KvMatcher,
}
const fn object_type_flag(t: &ObjectType) -> u16 {
@@ -56,7 +57,8 @@ impl GlobalsStore {
group_subobjects: true,
shown_types: u16::MAX,
- searched_property: String::new(),
+
+ properties_filter: KvMatcher::new(),
}
}
@@ -112,12 +114,6 @@ impl GlobalsStore {
self.globals.remove(&id)
}
- pub fn set_global_info(&mut self, id: u32, info: Option>) {
- self.globals
- .entry(id)
- .and_modify(|global| global.borrow_mut().set_info(info));
- }
-
pub fn set_global_props(&mut self, id: u32, props: BTreeMap) {
self.globals
.entry(id)
@@ -145,9 +141,7 @@ impl GlobalsStore {
return false;
}
- if !self.searched_property.is_empty()
- && !global.props().contains_key(&self.searched_property)
- {
+ if !self.properties_filter.matches(global.props().iter()) {
return false;
}
@@ -188,25 +182,26 @@ impl GlobalsStore {
}
});
});
- ui.horizontal(|ui| {
- egui::TextEdit::singleline(&mut self.searched_property)
- .hint_text("Has property")
- .show(ui);
- if ui.small_button("Clear").clicked() {
- self.searched_property.clear();
- }
- });
+
+ ui.separator();
+
+ ui.label("Properties").on_hover_text(
+ "Only globals with properties that match the below filters will be shown",
+ );
+ self.properties_filter.show(ui);
});
ui.separator();
egui::ScrollArea::vertical().show(ui, |ui| {
- for mut global in self.globals.values().filter_map(|global| {
- let global = global.borrow_mut();
- self.satisfies_filters(&global).then_some(global)
- }) {
- global.show(ui, self.group_subobjects, &self.searched_property, sx);
- }
+ ui.with_layout(egui::Layout::top_down_justified(egui::Align::Min), |ui| {
+ for mut global in self.globals.values().filter_map(|global| {
+ let global = global.borrow_mut();
+ self.satisfies_filters(&global).then_some(global)
+ }) {
+ global.show(ui, self.group_subobjects, sx);
+ }
+ });
});
}
}
diff --git a/src/ui/graph.rs b/src/ui/graph.rs
new file mode 100644
index 0000000..5d19681
--- /dev/null
+++ b/src/ui/graph.rs
@@ -0,0 +1,510 @@
+use std::{
+ borrow::Cow,
+ cell::RefCell,
+ collections::BTreeMap,
+ rc::{Rc, Weak},
+};
+
+use eframe::egui;
+use egui_node_graph::{
+ AnyParameterId, DataTypeTrait, GraphEditorState, InputId, NodeDataTrait, NodeId, NodeResponse,
+ OutputId, UserResponseTrait,
+};
+use pipewire::types::ObjectType;
+
+use crate::{
+ backend::{self, Request},
+ ui::globals_store::Global,
+};
+
+// Used to satisfy trait bounds that provide unneded features
+#[derive(Debug, Default, Clone)]
+struct NoOp;
+impl egui_node_graph::WidgetValueTrait for NoOp {
+ type Response = Self;
+ type NodeData = GraphNode;
+ type UserState = backend::Sender;
+
+ fn value_widget(
+ &mut self,
+ _: &str,
+ _: egui_node_graph::NodeId,
+ _: &mut egui::Ui,
+ _: &mut Self::UserState,
+ _: &Self::NodeData,
+ ) -> Vec {
+ Vec::new()
+ }
+}
+impl egui_node_graph::UserResponseTrait for NoOp {}
+impl egui_node_graph::NodeTemplateTrait for NoOp {
+ type NodeData = GraphNode;
+ type DataType = MediaType;
+ type ValueType = Self;
+ type CategoryType = ();
+ type UserState = backend::Sender;
+
+ fn node_finder_categories(&self, _: &mut Self::UserState) -> Vec {
+ Vec::new()
+ }
+
+ fn node_finder_label(&self, _: &mut Self::UserState) -> std::borrow::Cow {
+ Cow::Borrowed("")
+ }
+
+ fn build_node(
+ &self,
+ _: &mut egui_node_graph::Graph,
+ _: &mut Self::UserState,
+ _: egui_node_graph::NodeId,
+ ) {
+ }
+
+ fn node_graph_label(&self, _: &mut Self::UserState) -> String {
+ String::new()
+ }
+
+ fn user_data(&self, _: &mut Self::UserState) -> Self::NodeData {
+ GraphNode::NoOp
+ }
+}
+impl egui_node_graph::NodeTemplateIter for NoOp {
+ type Item = Self;
+ fn all_kinds(&self) -> Vec {
+ Vec::new()
+ }
+}
+
+#[derive(PartialEq, Eq, Clone, Copy)]
+pub enum MediaType {
+ Audio,
+ Video,
+ Midi,
+ Unknown,
+}
+
+impl DataTypeTrait for MediaType {
+ fn data_type_color(&self, _: &mut backend::Sender) -> egui::Color32 {
+ match self {
+ Self::Audio => egui::Color32::BLUE,
+ Self::Video => egui::Color32::YELLOW,
+ Self::Midi => egui::Color32::RED,
+ Self::Unknown => egui::Color32::GRAY,
+ }
+ }
+
+ fn name(&self) -> std::borrow::Cow {
+ match self {
+ Self::Audio => Cow::Borrowed("Audio"),
+ Self::Video => Cow::Borrowed("Video"),
+ Self::Midi => Cow::Borrowed("MIDI"),
+ Self::Unknown => Cow::Borrowed("Unknown"),
+ }
+ }
+}
+
+enum GraphNode {
+ Node {
+ media_type: MediaType,
+ global: Weak>,
+ },
+ NoOp,
+}
+
+impl GraphNode {
+ fn new(media_type: MediaType, global: Weak>) -> Self {
+ Self::Node { media_type, global }
+ }
+}
+
+impl NodeDataTrait for GraphNode {
+ type DataType = MediaType;
+ type Response = NoOp;
+ type ValueType = NoOp;
+ type UserState = backend::Sender;
+
+ fn can_delete(
+ &self,
+ _: egui_node_graph::NodeId,
+ _: &egui_node_graph::Graph,
+ _: &mut Self::UserState,
+ ) -> bool {
+ false
+ }
+
+ fn bottom_ui(
+ &self,
+ ui: &mut egui::Ui,
+ _node_id: egui_node_graph::NodeId,
+ _graph: &egui_node_graph::Graph,
+ sx: &mut Self::UserState,
+ ) -> Vec>
+ where
+ Self::Response: UserResponseTrait,
+ {
+ if let Self::Node { global, .. } = self {
+ if let Some(global) = global.upgrade() {
+ egui::CollapsingHeader::new("Details")
+ .default_open(true)
+ .show_unindented(ui, |ui| {
+ egui::Frame::central_panel(&egui::Style::default())
+ .inner_margin(egui::Margin::same(2.5))
+ .rounding(ui.visuals().noninteractive().rounding)
+ .show(ui, |ui| {
+ ui.set_max_width(500f32);
+ egui::ScrollArea::vertical()
+ .min_scrolled_height(350f32)
+ .max_height(350f32)
+ .show(ui, |ui| {
+ global.borrow_mut().show(ui, true, sx);
+ });
+ });
+ });
+ }
+ };
+
+ Vec::new()
+ }
+}
+
+enum GraphItem {
+ Node(NodeId),
+ InputPort(InputId),
+ OutputPort(OutputId),
+ Link(OutputId, InputId),
+}
+
+impl From for GraphItem {
+ fn from(value: NodeId) -> Self {
+ Self::Node(value)
+ }
+}
+
+impl From for GraphItem {
+ fn from(value: InputId) -> Self {
+ Self::InputPort(value)
+ }
+}
+
+impl From for GraphItem {
+ fn from(value: OutputId) -> Self {
+ Self::OutputPort(value)
+ }
+}
+
+impl From for GraphItem {
+ fn from(value: AnyParameterId) -> Self {
+ match value {
+ AnyParameterId::Input(value) => Self::from(value),
+ AnyParameterId::Output(value) => Self::from(value),
+ }
+ }
+}
+
+impl From<(OutputId, InputId)> for GraphItem {
+ fn from((output, input): (OutputId, InputId)) -> Self {
+ Self::Link(output, input)
+ }
+}
+
+pub struct Graph {
+ editor: egui_node_graph::GraphEditorState,
+ responses: Vec>,
+
+ // Maps PipeWire global IDs to graph items
+ graph_items: BTreeMap,
+}
+
+impl Graph {
+ pub fn new() -> Self {
+ Self {
+ editor: GraphEditorState::default(),
+ responses: Vec::new(),
+ graph_items: BTreeMap::new(),
+ }
+ }
+
+ pub fn add_node(&mut self, id: u32, global: &Rc>) {
+ if self.graph_items.get(&id).is_some() {
+ return;
+ }
+
+ // TODO Use port params to get their media type and move this out of Nodes.
+ let media_type =
+ global
+ .borrow()
+ .props()
+ .get("media.class")
+ .map_or(MediaType::Unknown, |media_class| {
+ let media_class = media_class.to_lowercase();
+ if media_class.contains("audio") {
+ MediaType::Audio
+ } else if media_class.contains("video") {
+ MediaType::Video
+ } else if media_class.contains("midi") {
+ MediaType::Midi
+ } else {
+ MediaType::Unknown
+ }
+ });
+
+ let graph_id = self.editor.graph.add_node(
+ global
+ .borrow()
+ .name()
+ .cloned()
+ .unwrap_or_else(|| format!("{id}")),
+ GraphNode::new(media_type, Rc::downgrade(global)),
+ |_, _| {},
+ );
+
+ self.responses.push(NodeResponse::CreatedNode(graph_id));
+
+ self.graph_items.insert(id, graph_id.into());
+ }
+
+ fn port_graph_node_and_media_type(
+ &self,
+ id: u32,
+ node_id: u32,
+ ) -> Option<(&NodeId, MediaType)> {
+ if self.graph_items.get(&id).is_some() {
+ return None;
+ }
+
+ let Some(GraphItem::Node(node_id)) = self.graph_items.get(&node_id) else {
+ return None;
+ };
+
+ if let GraphNode::Node { ref media_type, .. } =
+ self.editor.graph.nodes.get(*node_id).unwrap().user_data
+ {
+ Some((node_id, *media_type))
+ } else {
+ unreachable!();
+ }
+ }
+
+ pub fn add_input_port(&mut self, id: u32, node_id: u32, name: String) {
+ let Some((node_id, media_type)) = self.port_graph_node_and_media_type(id, node_id) else {
+ return;
+ };
+
+ let graph_id = self.editor.graph.add_wide_input_param(
+ *node_id,
+ name,
+ media_type,
+ NoOp,
+ egui_node_graph::InputParamKind::ConnectionOnly,
+ None,
+ true,
+ );
+
+ self.graph_items.insert(id, graph_id.into());
+ }
+
+ pub fn add_output_port(&mut self, id: u32, node_id: u32, name: String) {
+ let Some((node_id, media_type)) = self.port_graph_node_and_media_type(id, node_id) else {
+ return;
+ };
+
+ let graph_id = self
+ .editor
+ .graph
+ .add_output_param(*node_id, name, media_type);
+
+ self.graph_items.insert(id, graph_id.into());
+ }
+
+ pub fn add_link(&mut self, id: u32, output_port_id: u32, input_port_id: u32) {
+ if self.graph_items.get(&id).is_some() {
+ return;
+ }
+
+ let Some((GraphItem::OutputPort(output), GraphItem::InputPort(input))) = self
+ .graph_items
+ .get(&output_port_id)
+ .zip(self.graph_items.get(&input_port_id))
+ else {
+ return;
+ };
+
+ self.editor.graph.add_connection(*output, *input, 0);
+
+ self.graph_items
+ .insert(id, GraphItem::Link(*output, *input));
+ }
+
+ pub fn remove_item(&mut self, id: u32) {
+ let Some(item) = self.graph_items.remove(&id) else {
+ return;
+ };
+
+ match item {
+ GraphItem::Node(node_id) => {
+ self.responses.push(NodeResponse::DeleteNodeUi(node_id));
+ }
+ GraphItem::OutputPort(output_id) => self.editor.graph.remove_output_param(output_id),
+ GraphItem::InputPort(input_id) => self.editor.graph.remove_input_param(input_id),
+ GraphItem::Link(output_id, input_id) => {
+ self.editor.graph.remove_connection(input_id, output_id);
+ }
+ }
+ }
+
+ pub fn show(&mut self, ui: &mut egui::Ui, sx: &mut backend::Sender) {
+ // Never show the node finder since nodes can't be created manually
+ self.editor.node_finder = None;
+
+ if ui.button("Auto arrange").clicked() {
+ self.editor.node_positions.clear();
+ self.editor.node_order.clear();
+ }
+ ui.separator();
+
+ const NODE_SPACING: egui::Vec2 = egui::vec2(200f32, 100f32);
+
+ let mut next_outputs_only_pos = egui::Pos2::ZERO;
+ let mut next_default_pos =
+ egui::Pos2::new((ui.available_width() - NODE_SPACING.x) / 2., 0f32);
+ let mut next_inputs_only_pos = egui::Pos2::new(
+ ui.available_width() - NODE_SPACING.x - ui.style().spacing.window_margin.right,
+ 0f32,
+ );
+
+ for pos in self.editor.node_positions.values_mut() {
+ // Adjust existing nodes' positions so that they're inside the drawable area
+ pos.x = pos
+ .x
+ .clamp(0., f32::max(0., ui.available_width() - NODE_SPACING.x));
+ pos.y = pos
+ .y
+ .clamp(0., f32::max(0., ui.available_height() - NODE_SPACING.y));
+
+ // Determine next available position for this node's kind
+ for next in [
+ &mut next_inputs_only_pos,
+ &mut next_default_pos,
+ &mut next_outputs_only_pos,
+ ] {
+ if (pos.x - 50f32..=pos.x + 50f32).contains(&next.x)
+ && (next.y..next.y + NODE_SPACING.y).contains(&pos.y)
+ {
+ next.y += NODE_SPACING.y;
+ break;
+ }
+ }
+ }
+
+ // Position unpositioned nodes
+ for (id, node) in &self.editor.graph.nodes {
+ if self.editor.node_positions.contains_key(id) {
+ continue;
+ }
+
+ let GraphNode::Node { ref global, .. } = node.user_data else {
+ unreachable!();
+ };
+
+ self.editor.node_order.push(id);
+
+ let ports = global.upgrade().and_then(|global| {
+ global.borrow().info().and_then(|info| {
+ info[2]
+ .1
+ .parse::()
+ .ok()
+ .zip(info[3].1.parse::().ok())
+ })
+ });
+
+ let pos = if let Some((inputs, outputs)) = ports {
+ if outputs == 0 {
+ &mut next_inputs_only_pos
+ } else if inputs == 0 {
+ &mut next_outputs_only_pos
+ } else {
+ &mut next_default_pos
+ }
+ } else {
+ &mut next_default_pos
+ };
+
+ self.editor.node_positions.insert(id, *pos);
+
+ pos.y += NODE_SPACING.y;
+ }
+
+ ui.scope(|ui| {
+ for response in self
+ .editor
+ .draw_graph_editor(ui, NoOp, sx, std::mem::take(&mut self.responses))
+ .node_responses
+ {
+ match response {
+ NodeResponse::DisconnectEvent { output, input } => {
+ for (id, g) in &self.graph_items {
+ if let GraphItem::Link(o, i) = *g {
+ if output == o && input == i {
+ sx.send(Request::DestroyObject(*id)).ok();
+ break;
+ }
+ }
+ }
+
+ // Discard state change made by the user
+ self.editor.graph.add_connection(output, input, 0);
+ }
+ NodeResponse::ConnectEventEnded { output, input, .. } => {
+ let mut output_port = None;
+ let mut input_port = None;
+
+ for (id, object) in &self.graph_items {
+ match object {
+ GraphItem::InputPort(input_id) => {
+ if input == *input_id {
+ input_port = Some(*id);
+ continue;
+ }
+ }
+ GraphItem::OutputPort(output_id) => {
+ if output == *output_id {
+ output_port = Some(*id);
+ continue;
+ }
+ }
+ GraphItem::Link(o, i) => {
+ if *o == output && *i == input {
+ // Ports are already linked
+ return;
+ }
+ }
+ _ => {}
+ }
+ }
+
+ if let Some((output, input)) = output_port
+ .zip(input_port)
+ .map(|(output, input)| (output.to_string(), input.to_string()))
+ {
+ sx.send(Request::CreateObject(
+ ObjectType::Link,
+ String::from("link-factory"),
+ vec![
+ ("link.output.port".to_owned(), output),
+ ("link.input.port".to_owned(), input),
+ ("object.linger".to_owned(), "true".to_owned()),
+ ],
+ ))
+ .ok();
+ }
+
+ // Discard state change made by the user
+ self.editor.graph.remove_connection(input, output);
+ }
+ _ => {}
+ }
+ }
+ });
+ }
+}
diff --git a/src/ui/mod.rs b/src/ui/mod.rs
index b75510d..caf2a10 100644
--- a/src/ui/mod.rs
+++ b/src/ui/mod.rs
@@ -17,6 +17,7 @@
mod common;
mod context_manager;
mod globals_store;
+mod graph;
mod metadata_editor;
mod object_creator;
mod profiler;
@@ -24,10 +25,11 @@ mod tool;
use context_manager::ContextManager;
use globals_store::GlobalsStore;
+use graph::Graph;
use metadata_editor::MetadataEditor;
use object_creator::ObjectCreator;
use profiler::Profiler;
use tool::{Tool, WindowedTool};
mod app;
-pub use app::CoppwrApp;
+pub use app::App as CoppwrApp;
diff --git a/src/ui/object_creator.rs b/src/ui/object_creator.rs
index 9434bdc..c82c532 100644
--- a/src/ui/object_creator.rs
+++ b/src/ui/object_creator.rs
@@ -85,10 +85,7 @@ impl ObjectCreator {
});
if let Some(factory) = factory {
- ui.horizontal(|ui| {
- ui.label("Creates ");
- ui.label(factory.object_type.to_str());
- });
+ ui.label(format!("Creates {}", factory.object_type.to_str()));
}
ui.separator();
diff --git a/src/ui/profiler.rs b/src/ui/profiler.rs
index 6a6582b..7f61675 100644
--- a/src/ui/profiler.rs
+++ b/src/ui/profiler.rs
@@ -16,10 +16,8 @@
use std::collections::{hash_map::Entry, HashMap};
-use eframe::egui::{
- self,
- plot::{self, Plot, PlotPoints},
-};
+use eframe::egui;
+use egui_plot::{self, Plot, PlotPoints};
use crate::backend::pods::profiler::{Clock, Info, NodeBlock, Profiling};
@@ -31,7 +29,7 @@ use crate::backend::pods::profiler::{Clock, Info, NodeBlock, Profiling};
mod data {
use std::collections::{btree_map::Entry, BTreeMap, VecDeque};
- use eframe::egui::plot::PlotPoints;
+ use egui_plot::PlotPoints;
use crate::backend::pods::profiler::{NodeBlock, Profiling};
@@ -387,28 +385,28 @@ impl Profiler {
let plot = Plot::new(id)
.clamp_grid(true)
- .legend(plot::Legend::default())
- .allow_zoom(plot::AxisBools::new(true, false))
- .allow_drag(plot::AxisBools::new(true, false))
+ .legend(egui_plot::Legend::default())
+ .allow_zoom(egui_plot::AxisBools::new(true, false))
+ .allow_drag(egui_plot::AxisBools::new(true, false))
.label_formatter(|name, value| {
if name.is_empty() {
String::new()
} else {
- format!("{name}: {:.0} us\nProcess cycle: {:.0}", value.y, value.x)
+ format!("{name}: {:.0}us\nProcess cycle: {:.0}", value.y, value.x)
}
})
- .x_axis_formatter(move |x, _| {
+ .x_axis_formatter(move |x, _, _| {
if x.is_sign_negative() || x > max_x as f64 || x % 1. != 0. {
String::new()
} else {
- format!("Process cycle {x:.0}")
+ format!("{x:.0}")
}
})
- .y_axis_formatter(|y, _| {
+ .y_axis_formatter(|y, _, _| {
if y.is_sign_negative() {
String::new()
} else {
- format!("{y} us")
+ format!("{y}us")
}
});
@@ -435,7 +433,7 @@ impl Profiler {
("Period", driver.period()),
("Estimated", driver.estimated()),
] {
- ui.line(plot::Line::new(plot_points).name(name));
+ ui.line(egui_plot::Line::new(plot_points).name(name));
}
});
@@ -447,7 +445,7 @@ impl Profiler {
)
.height(ui[1].available_height() / 2.)
.show(&mut ui[1], |ui| {
- ui.line(plot::Line::new(driver.end_date()).name("Driver End Date"));
+ ui.line(egui_plot::Line::new(driver.end_date()).name("Driver End Date"));
});
});
@@ -474,7 +472,7 @@ impl Profiler {
&mut ui[i],
|ui| {
for client in driver.clients() {
- ui.line(plot::Line::new(measurement(client)).name(client.title()));
+ ui.line(egui_plot::Line::new(measurement(client)).name(client.title()));
}
},
);