11 Commits
0.2.0 ... 0.2.1

Author SHA1 Message Date
Tom A. Wagner
46b2175a78 Release v0.2.1 2021-06-06 20:44:10 +02:00
Tom A. Wagner
9aeea6b108 Update dependencies 2021-06-06 20:42:00 +02:00
Tom A. Wagner
c8f94ae302 Use link info callback instead of properties to get ids.
While the properties for a links node ids are not always set, they always are in the link info.

Also, the work done in this commit will easily allow to get the format of links and ports later,
so that we can get the format of them much more reliably,
and we can also get notified of changes to an existing global via the info callback.
2021-06-06 20:28:19 +02:00
Tom A. Wagner
92101d860c Update ARCHITECTURE.md to reflect state having moved to the pipewire thread 2021-06-06 09:36:57 +02:00
Tom A. Wagner
907ef328d2 Move state to pipewire thread instead of the gtk thread.
This will allow easier state-keeping later, when setting up info-listeners on structs.
2021-06-06 09:20:07 +02:00
Tom A. Wagner
118c1ca28c Get node_from, node_to ids from state instead of props when a new link appears.
This is more reliable than assuming the link carries the id of its nodes, as there have been cases where a link was created without those
properties set.
Instead, we can just pull them from the state via the port ids of the link.
2021-06-04 10:30:31 +02:00
Tom A. Wagner
a9aec985b0 pipewire: Edit fixme about port media type 2021-05-25 22:45:13 +02:00
Tom A. Wagner
dce228ff60 Update dependencies
Glib MainContext is now aquired manually because a change in gtk-rs would lead to
a panic when attaching the receiver otherwise, because gtk::init() doesn't
"leak" the default main context anymore.
2021-05-22 14:00:51 +02:00
Jan Vanmullem
24fd54affe references both AUR release & git packages 2021-05-18 16:25:49 +02:00
Mathias Rav
3cd19f2d1d When dragging a node, don't snap its top-left corner to the cursor
Revamp the node dragging implementation, moving it into the GraphView
widget.

When a drag is initiated, the node widget's current position is stored.
Whenever the drag gesture is updated, the node widget's position is set
by adding the relative drag vector to the position at the start of the
drag.

A drag gesture on the node widget rather than the GraphView widget was
considered, but this seems to lead to a weird flickering effect when the
node is moved while the drag gesture on the node is active.

To avoid interfering with the drag handlers on the ports, check if the
GraphView drag gesture targets a port, in which case the handler does
nothing.
2021-05-10 18:21:00 +00:00
Mathias Rav
6d60095da8 Move CSS to its own file 2021-05-09 21:53:27 +02:00
12 changed files with 490 additions and 453 deletions

141
Cargo.lock generated
View File

@@ -90,8 +90,8 @@ dependencies = [
[[package]]
name = "cairo-rs"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
dependencies = [
"bitflags",
"cairo-sys-rs",
@@ -102,8 +102,8 @@ dependencies = [
[[package]]
name = "cairo-sys-rs"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
dependencies = [
"glib-sys",
"libc",
@@ -112,9 +112,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.67"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787"
[[package]]
name = "cexpr"
@@ -220,9 +220,9 @@ dependencies = [
[[package]]
name = "field-offset"
version = "0.3.3"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf539fba70056b50f40a22e0da30639518a12ee18c35807858a63b158cb6dde7"
checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92"
dependencies = [
"memoffset",
"rustc_version",
@@ -236,24 +236,24 @@ checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
[[package]]
name = "futures-channel"
version = "0.3.14"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce79c6a52a299137a6013061e0cf0e688fce5d7f1bc60125f520912fdb29ec25"
checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.14"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "098cd1c6dda6ca01650f1a37a794245eb73181d0d4d4e955e2f3c37db7af1815"
checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1"
[[package]]
name = "futures-executor"
version = "0.3.14"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f6cb7042eda00f0049b1d2080aa4b93442997ee507eb3828e8bd7577f94c9d"
checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79"
dependencies = [
"futures-core",
"futures-task",
@@ -262,22 +262,23 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.14"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "365a1a1fb30ea1c03a830fdb2158f5236833ac81fa0ad12fe35b29cddc35cb04"
checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1"
[[package]]
name = "futures-task"
version = "0.3.14"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba7aa51095076f3ba6d9a1f702f74bd05ec65f555d70d2033d55ba8d69f581bc"
checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae"
[[package]]
name = "futures-util"
version = "0.3.14"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c144ad54d60f23927f0a6b6d816e4271278b64f005ad65e4e35291d2de9c025"
checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967"
dependencies = [
"autocfg",
"futures-core",
"futures-task",
"pin-project-lite",
@@ -293,8 +294,8 @@ checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
[[package]]
name = "gdk-pixbuf"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
dependencies = [
"gdk-pixbuf-sys",
"gio",
@@ -304,8 +305,8 @@ dependencies = [
[[package]]
name = "gdk-pixbuf-sys"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
dependencies = [
"gio-sys",
"glib-sys",
@@ -317,7 +318,7 @@ dependencies = [
[[package]]
name = "gdk4"
version = "0.1.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7"
dependencies = [
"bitflags",
"cairo-rs",
@@ -332,7 +333,7 @@ dependencies = [
[[package]]
name = "gdk4-sys"
version = "0.1.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",
@@ -347,8 +348,8 @@ dependencies = [
[[package]]
name = "gio"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
dependencies = [
"bitflags",
"futures-channel",
@@ -363,8 +364,8 @@ dependencies = [
[[package]]
name = "gio-sys"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
dependencies = [
"glib-sys",
"gobject-sys",
@@ -375,8 +376,8 @@ dependencies = [
[[package]]
name = "glib"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
dependencies = [
"bitflags",
"futures-channel",
@@ -393,8 +394,8 @@ dependencies = [
[[package]]
name = "glib-macros"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
dependencies = [
"anyhow",
"heck",
@@ -407,8 +408,8 @@ dependencies = [
[[package]]
name = "glib-sys"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
dependencies = [
"libc",
"system-deps",
@@ -422,8 +423,8 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "gobject-sys"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
dependencies = [
"glib-sys",
"libc",
@@ -432,8 +433,8 @@ dependencies = [
[[package]]
name = "graphene-rs"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
dependencies = [
"glib",
"graphene-sys",
@@ -442,8 +443,8 @@ dependencies = [
[[package]]
name = "graphene-sys"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
dependencies = [
"glib-sys",
"libc",
@@ -454,7 +455,7 @@ dependencies = [
[[package]]
name = "gsk4"
version = "0.1.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7"
dependencies = [
"bitflags",
"cairo-rs",
@@ -469,7 +470,7 @@ dependencies = [
[[package]]
name = "gsk4-sys"
version = "0.1.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7"
dependencies = [
"cairo-sys-rs",
"gdk4-sys",
@@ -484,7 +485,7 @@ dependencies = [
[[package]]
name = "gtk4"
version = "0.1.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7"
dependencies = [
"bitflags",
"cairo-rs",
@@ -506,7 +507,7 @@ dependencies = [
[[package]]
name = "gtk4-macros"
version = "0.1.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7"
dependencies = [
"anyhow",
"heck",
@@ -521,7 +522,7 @@ dependencies = [
[[package]]
name = "gtk4-sys"
version = "0.1.0"
source = "git+https://github.com/gtk-rs/gtk4-rs/#e90c5752ba5229f874e912e84bc83739003434ed"
source = "git+https://github.com/gtk-rs/gtk4-rs/#ff9e0b861ccf59d4ced5ea67bdd004f138419cb7"
dependencies = [
"cairo-sys-rs",
"gdk-pixbuf-sys",
@@ -538,16 +539,16 @@ dependencies = [
[[package]]
name = "heck"
version = "0.3.2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "helvum"
version = "0.2.0"
version = "0.2.1"
dependencies = [
"env_logger",
"gtk4",
@@ -607,9 +608,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.94"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
checksum = "789da6d93f1b866ffe175afc5322a4d76c038605a1c3319bb57b06967ca98a36"
[[package]]
name = "libloading"
@@ -624,7 +625,7 @@ dependencies = [
[[package]]
name = "libspa"
version = "0.3.0"
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#f8dc21b0f85f391201e7c6346b121fbd21c02836"
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#62ce08ed59e711c2a438bb06fe60d956ececfb07"
dependencies = [
"bitflags",
"cc",
@@ -639,7 +640,7 @@ dependencies = [
[[package]]
name = "libspa-sys"
version = "0.3.0"
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#f8dc21b0f85f391201e7c6346b121fbd21c02836"
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#62ce08ed59e711c2a438bb06fe60d956ececfb07"
dependencies = [
"bindgen",
"system-deps",
@@ -662,9 +663,9 @@ checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
[[package]]
name = "memoffset"
version = "0.6.3"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d"
checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
dependencies = [
"autocfg",
]
@@ -713,8 +714,8 @@ checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
[[package]]
name = "pango"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
dependencies = [
"bitflags",
"glib",
@@ -725,8 +726,8 @@ dependencies = [
[[package]]
name = "pango-sys"
version = "0.13.0"
source = "git+https://github.com/gtk-rs/gtk-rs#74a02cdd2855a387ebbe700eb509029c8e338d19"
version = "0.14.0"
source = "git+https://github.com/gtk-rs/gtk-rs-core#a6815e75a8803c71b8e7cf7d35bf322376e934d6"
dependencies = [
"glib-sys",
"gobject-sys",
@@ -764,7 +765,7 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pipewire"
version = "0.3.0"
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#f8dc21b0f85f391201e7c6346b121fbd21c02836"
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#62ce08ed59e711c2a438bb06fe60d956ececfb07"
dependencies = [
"anyhow",
"bitflags",
@@ -781,7 +782,7 @@ dependencies = [
[[package]]
name = "pipewire-sys"
version = "0.3.0"
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#f8dc21b0f85f391201e7c6346b121fbd21c02836"
source = "git+https://gitlab.freedesktop.org/pipewire/pipewire-rs?branch=main#62ce08ed59e711c2a438bb06fe60d956ececfb07"
dependencies = [
"bindgen",
"libspa-sys",
@@ -830,9 +831,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.26"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
dependencies = [
"unicode-xid",
]
@@ -910,9 +911,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.125"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
[[package]]
name = "shlex"
@@ -1027,18 +1028,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.24"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
checksum = "fa6f76457f59514c7eeb4e59d891395fab0b2fd1d40723ae737d64153392e9c6"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.24"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
checksum = "8a36768c0fbf1bb15eca10defa29526bda730a2376c2ab4393ccfa16fb1a318d"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -1,6 +1,6 @@
[package]
name = "helvum"
version = "0.2.0"
version = "0.2.1"
authors = ["Tom A. Wagner <tom.a.wagner@protonmail.com>"]
edition = "2018"
license = "GPL-3.0-only"

View File

@@ -11,7 +11,9 @@ More suggestions are welcome!
# Distribution packages
- ArchLinux: [aur/helvum-git](https://aur.archlinux.org/packages/helvum-git)
- ArchLinux:
- [aur/helvum](https://aur.archlinux.org/packages/helvum)
- [aur/helvum-git](https://aur.archlinux.org/packages/helvum-git)
# Building
For compilation, you will need:

View File

@@ -14,41 +14,37 @@ Helvum uses an architecture with the components laid out like this:
│ View │
└────┬─┘
Λ ┆
│<───── updates view
│ ┆
│ ┆<─ notifies of user input
│ ┆ (using signals)
│ ┆
│ ┆
│ V notifies of remote changes
┌┴────────────┐ via messages ┌──────────────────┐
│<───── updates view ┌───────┐
│ ┆ │ State │
│ ┆<─ notifies of user input └───────┘
│ ┆ (using signals) Λ
│ ┆
│ ┆ │<─── updates/reads state
│ V notifies of remote changes
┌┴────────────┐ via messages ┌──────────────────┐
│ Application │<╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ Seperate │
│ Object ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌>│ Pipewire Thread │
────────────┘ request changes to remote └───────────────────┘
via messages Λ
│<─── updates/reads state
V
┌───────┐ V
│ State │ [ Remote Pipewire Server ]
└───────┘
────────────┘ request changes to remote └───────────────────┘
via messages Λ
V
[ Remote Pipewire Server ]
```
The program is split between two threads, with most stuff happening inside the GTK thread.
The GTK thread will sit in a GTK event processing loop, while the pipewire thread will sit in a
pipewire event processing loop.
The program is split between two cooperating threads.
The GTK thread (displayed on the left side) will sit in a GTK event processing loop, while the pipewire thread (displayed on the right side) will sit in a pipewire event processing loop.
The `Application` object inside the GTK thread is the centerpiece of this architecture.
It communicates with the pipewire thread using two channels,
The `Application` object inside the GTK thread communicates with the pipewire thread using two channels,
where each message sent by one thread will trigger the loop of the other thread to invoke a callback
with the received message.
For each change on the remote pipewire server, the `Application` in the GTK thread is notified by the pipewire thread
and updates the view to reflect those changes, and additionally memorizes anything it might need later in the state.
For each change on the remote pipewire server, the pipewire thread updates the state and notifies
the `Application` in the GTK thread if changes are needed.
The `Application` then updates the view to reflect those changes.
Additionally, a user may also make changes using the view.
For each change, the view notifies the `Application` by emitting a matching signal.
The `Application` will then request the pipewire thread to make those changes on the remote. \
The `Application` will then ask the pipewire thread to make those changes on the remote. \
These changes will then be applied to the view like any other remote changes as explained above.
# View Architecture

View File

@@ -1,4 +1,4 @@
use std::{cell::RefCell, collections::HashMap};
use std::cell::RefCell;
use gtk::{
gio,
@@ -11,33 +11,10 @@ use pipewire::{channel::Sender, spa::Direction};
use crate::{
view::{self},
GtkMessage, PipewireLink, PipewireMessage,
GtkMessage, MediaType, PipewireLink, PipewireMessage,
};
#[derive(Debug, Copy, Clone)]
pub enum MediaType {
Audio,
Video,
Midi,
}
// FIXME: This should be in its own .css file.
static STYLE: &str = "
.audio {
background: rgb(50,100,240);
color: black;
}
.video {
background: rgb(200,200,0);
color: black;
}
.midi {
background: rgb(200,0,50);
color: black;
}
";
static STYLE: &str = include_str!("style.css");
mod imp {
use super::*;
@@ -47,7 +24,6 @@ mod imp {
#[derive(Default)]
pub struct Application {
pub(super) graphview: view::GraphView,
pub(super) state: RefCell<State>,
pub(super) pw_sender: OnceCell<RefCell<Sender<GtkMessage>>>,
}
@@ -132,19 +108,12 @@ impl Application {
@weak app => @default-return Continue(true),
move |msg| {
match msg {
PipewireMessage::NodeAdded {
id,
name,
media_type,
} => app.add_node(id, name, media_type),
PipewireMessage::PortAdded {
id,
node_id,
name,
direction,
} => app.add_port(id, name, node_id, direction),
PipewireMessage::LinkAdded { id, link } => app.add_link(id, link),
PipewireMessage::ObjectRemoved { id } => app.remove_global(id),
PipewireMessage::NodeAdded{ id, name } => app.add_node(id,name),
PipewireMessage::PortAdded{ id, node_id, name, direction, media_type} => app.add_port(id,name,node_id,direction,media_type),
PipewireMessage::LinkAdded{ id, node_from, port_from, node_to, port_to} => app.add_link(id,node_from,port_from,node_to,port_to),
PipewireMessage::NodeRemoved { id } => app.remove_node(id),
PipewireMessage::PortRemoved { id, node_id } => app.remove_port(id, node_id),
PipewireMessage::LinkRemoved { id } => app.remove_link(id)
};
Continue(true)
}
@@ -155,40 +124,27 @@ impl Application {
}
/// Add a new node to the view.
pub fn add_node(&self, id: u32, name: String, media_type: Option<MediaType>) {
pub fn add_node(&self, id: u32, name: String) {
info!("Adding node to graph: id {}", id);
let imp = imp::Application::from_instance(self);
imp.state.borrow_mut().insert(
id,
Item::Node {
// widget: node_widget,
media_type,
},
);
imp.graphview.add_node(id, view::Node::new(name.as_str()));
imp::Application::from_instance(self)
.graphview
.add_node(id, view::Node::new(name.as_str()));
}
/// Add a new port to the view.
pub fn add_port(&self, id: u32, name: String, node_id: u32, direction: Direction) {
pub fn add_port(
&self,
id: u32,
name: String,
node_id: u32,
direction: Direction,
media_type: Option<MediaType>,
) {
info!("Adding port to graph: id {}", id);
let imp = imp::Application::from_instance(self);
// Find out the nodes media type so that the port can be colored.
let media_type =
if let Some(Item::Node { media_type, .. }) = imp.state.borrow().get(node_id) {
media_type.to_owned()
} else {
warn!("Node not found for Port {}", id);
None
};
// Save node_id so we can delete this port easily.
imp.state.borrow_mut().insert(id, Item::Port { node_id });
let port = view::Port::new(id, name.as_str(), direction, media_type);
// Create or delete a link if the widget emits the "port-toggled" signal.
@@ -212,77 +168,30 @@ impl Application {
}
/// Add a new link to the view.
pub fn add_link(&self, id: u32, link: PipewireLink) {
pub fn add_link(&self, id: u32, node_from: u32, port_from: u32, node_to: u32, port_to: u32) {
info!("Adding link to graph: id {}", id);
let imp = imp::Application::from_instance(self);
// FIXME: Links should be colored depending on the data they carry (video, audio, midi) like ports are.
imp.state.borrow_mut().insert(
// Update graph to contain the new link.
imp::Application::from_instance(self).graphview.add_link(
id,
Item::Link {
output_port: link.port_from,
input_port: link.port_to,
PipewireLink {
node_from,
port_from,
node_to,
port_to,
},
);
// Update graph to contain the new link.
imp.graphview.add_link(id, link);
}
// Toggle a link between the two specified ports on the remote pipewire server.
fn toggle_link(&self, port_from: u32, port_to: u32) {
let imp = imp::Application::from_instance(self);
let sender = imp.pw_sender.get().expect("pw_sender not set").borrow_mut();
let state = imp.state.borrow_mut();
if let Some(id) = state.get_link_id(port_from, port_to) {
info!("Requesting removal of link with id {}", id);
sender
.send(GtkMessage::DestroyGlobal(id))
.send(GtkMessage::ToggleLink { port_from, port_to })
.expect("Failed to send message");
} else {
info!(
"Requesting creation of link from port id:{} to port id:{}",
port_from, port_to
);
let node_from = state
.get_node_of_port(port_from)
.expect("Requested port not in state");
let node_to = state
.get_node_of_port(port_to)
.expect("Requested port not in state");
sender
.send(GtkMessage::CreateLink(PipewireLink {
node_from,
port_from,
node_to,
port_to,
}))
.expect("Failed to send message");
}
}
/// Handle a global object being removed.
pub fn remove_global(&self, id: u32) {
let imp = imp::Application::from_instance(self);
if let Some(item) = imp.state.borrow_mut().remove(id) {
match item {
Item::Node { .. } => self.remove_node(id),
Item::Port { node_id } => self.remove_port(id, node_id),
Item::Link { .. } => self.remove_link(id),
}
} else {
warn!(
"Attempted to remove item with id {} that is not saved in state",
id
);
}
}
/// Remove the node with the specified id from the view.
@@ -310,83 +219,3 @@ impl Application {
imp.graphview.remove_link(id);
}
}
/// Any pipewire item we need to keep track of.
/// These will be saved in the [`Application`]s `state` struct associated with their id.
enum Item {
Node {
// Keep track of the nodes media type to color ports on it.
media_type: Option<MediaType>,
},
Port {
// Save the id of the node this is on so we can remove the port from it
// when it is deleted.
node_id: u32,
},
// We don't need to memorize anything about links right now, but we need to
// be able to find out an id is a link.
Link {
output_port: u32,
input_port: u32,
},
}
/// This struct keeps track of any relevant items and stores them under their IDs.
///
/// Given two port ids, it can also efficiently find the id of the link that connects them.
#[derive(Default)]
struct State {
/// Map pipewire ids to items.
items: HashMap<u32, Item>,
/// Map `(output port id, input port id)` tuples to the id of the link that connects them.
links: HashMap<(u32, u32), u32>,
}
impl State {
/// Add a new item under the specified id.
fn insert(&mut self, id: u32, item: Item) {
if let Item::Link {
output_port,
input_port,
} = item
{
self.links.insert((output_port, input_port), id);
}
self.items.insert(id, item);
}
/// Get the item that has the specified id.
fn get(&self, id: u32) -> Option<&Item> {
self.items.get(&id)
}
/// Get the id of the link that links the two specified ports.
fn get_link_id(&self, output_port: u32, input_port: u32) -> Option<u32> {
self.links.get(&(output_port, input_port)).copied()
}
/// Remove the item with the specified id, returning it if it exists.
fn remove(&mut self, id: u32) -> Option<Item> {
let removed = self.items.remove(&id);
if let Some(Item::Link {
output_port,
input_port,
}) = removed
{
self.links.remove(&(output_port, input_port));
}
removed
}
/// Convenience function: Get the id of the node a port is on
fn get_node_of_port(&self, port: u32) -> Option<u32> {
if let Some(Item::Port { node_id }) = self.get(port) {
Some(*node_id)
} else {
None
}
}
}

View File

@@ -2,44 +2,59 @@ mod application;
mod pipewire_connection;
mod view;
use application::MediaType;
use gtk::{
glib::{self, PRIORITY_DEFAULT},
prelude::*,
};
use pipewire::spa::Direction;
/// Messages used GTK thread to command the pipewire thread.
/// Messages sent by the GTK thread to notify the pipewire thread.
#[derive(Debug, Clone)]
enum GtkMessage {
/// Create a new link.
CreateLink(PipewireLink),
/// Destroy the global with the specified id.
DestroyGlobal(u32),
/// Toggle a link between the two specified ports.
ToggleLink { port_from: u32, port_to: u32 },
/// Quit the event loop and let the thread finish.
Terminate,
}
/// Messages used pipewire thread to notify the GTK thread.
/// Messages sent by the pipewire thread to notify the GTK thread.
#[derive(Debug, Clone)]
enum PipewireMessage {
/// A new node has appeared.
NodeAdded {
id: u32,
name: String,
media_type: Option<MediaType>,
},
/// A new port has appeared.
PortAdded {
id: u32,
node_id: u32,
name: String,
direction: Direction,
media_type: Option<MediaType>,
},
/// A new link has appeared.
LinkAdded { id: u32, link: PipewireLink },
/// An object was removed
ObjectRemoved { id: u32 },
LinkAdded {
id: u32,
node_from: u32,
port_from: u32,
node_to: u32,
port_to: u32,
},
NodeRemoved {
id: u32,
},
PortRemoved {
id: u32,
node_id: u32,
},
LinkRemoved {
id: u32,
},
}
#[derive(Debug, Copy, Clone)]
pub enum MediaType {
Audio,
Video,
Midi,
}
#[derive(Debug, Clone)]
@@ -54,6 +69,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
gtk::init()?;
// Aquire main context so that we can attach the gtk channel later.
let ctx = glib::MainContext::default();
let _guard = ctx.acquire().unwrap();
// Start the pipewire thread with channels in both directions.
let (gtk_sender, gtk_receiver) = glib::MainContext::channel(PRIORITY_DEFAULT);
let (pw_sender, pw_receiver) = pipewire::channel::channel();

View File

@@ -1,18 +1,28 @@
use std::rc::Rc;
mod state;
use std::{cell::RefCell, collections::HashMap, rc::Rc};
use gtk::glib::{self, clone};
use log::warn;
use log::{debug, info, warn};
use pipewire::{
link::Link,
link::{Link, LinkListener},
prelude::*,
properties,
registry::GlobalObject,
registry::{GlobalObject, Registry},
spa::{Direction, ForeignDict},
types::ObjectType,
Context, MainLoop,
Context, Core, MainLoop,
};
use crate::{application::MediaType, GtkMessage, PipewireMessage};
use crate::{GtkMessage, MediaType, PipewireMessage};
use state::{Item, State};
enum ProxyItem {
Link {
_proxy: Link,
_listener: LinkListener,
},
}
/// The "main" function of the pipewire thread.
pub(super) fn thread_main(
@@ -21,59 +31,60 @@ pub(super) fn thread_main(
) {
let mainloop = MainLoop::new().expect("Failed to create mainloop");
let context = Context::new(&mainloop).expect("Failed to create context");
let core = context.connect(None).expect("Failed to connect to remote");
let core = Rc::new(context.connect(None).expect("Failed to connect to remote"));
let registry = Rc::new(core.get_registry().expect("Failed to get registry"));
// Keep proxies and their listeners alive so that we can receive info events.
let proxies = Rc::new(RefCell::new(HashMap::new()));
let state = Rc::new(RefCell::new(State::new()));
let _receiver = pw_receiver.attach(&mainloop, {
let mainloop = mainloop.clone();
clone!(@weak registry => move |msg| match msg {
GtkMessage::CreateLink(link) => {
if let Err(e) = core.create_object::<Link, _>(
"link-factory",
&properties! {
"link.output.node" => link.node_from.to_string(),
"link.output.port" => link.port_from.to_string(),
"link.input.node" => link.node_to.to_string(),
"link.input.port" => link.port_to.to_string(),
"object.linger" => "1"
},
) {
warn!("Failed to create link: {}", e);
}
}
GtkMessage::DestroyGlobal(id) => {
// FIXME: Handle error
registry.destroy_global(id);
}
clone!(@strong mainloop, @weak core, @weak registry, @strong state => move |msg| match msg {
GtkMessage::ToggleLink { port_from, port_to } => toggle_link(port_from, port_to, &core, &registry, &state),
GtkMessage::Terminate => mainloop.quit(),
})
});
let _listener = registry
.add_listener_local()
.global({
let sender = gtk_sender.clone();
.global(clone!(@strong gtk_sender, @weak registry, @strong proxies, @strong state =>
move |global| match global.type_ {
ObjectType::Node => handle_node(global, &sender),
ObjectType::Port => handle_port(global, &sender),
ObjectType::Link => handle_link(global, &sender),
ObjectType::Node => handle_node(global, &gtk_sender, &state),
ObjectType::Port => handle_port(global, &gtk_sender, &state),
ObjectType::Link => handle_link(global, &gtk_sender, &registry, &proxies, &state),
_ => {
// Other objects are not interesting to us
}
}
})
.global_remove(move |id| {
gtk_sender
.send(PipewireMessage::ObjectRemoved { id })
.expect("Failed to send message")
})
))
.global_remove(clone!(@strong proxies, @strong state => move |id| {
if let Some(item) = state.borrow_mut().remove(id) {
gtk_sender.send(match item {
Item::Node { .. } => PipewireMessage::NodeRemoved {id},
Item::Port { node_id } => PipewireMessage::PortRemoved {id, node_id},
Item::Link { .. } => PipewireMessage::LinkRemoved {id},
}).expect("Failed to send message");
} else {
warn!(
"Attempted to remove item with id {} that is not saved in state",
id
);
}
proxies.borrow_mut().remove(&id);
}))
.register();
mainloop.run();
}
/// Handle a new node being added
fn handle_node(node: &GlobalObject<ForeignDict>, sender: &glib::Sender<PipewireMessage>) {
fn handle_node(
node: &GlobalObject<ForeignDict>,
sender: &glib::Sender<PipewireMessage>,
state: &Rc<RefCell<State>>,
) {
let props = node
.props
.as_ref()
@@ -88,7 +99,7 @@ fn handle_node(node: &GlobalObject<ForeignDict>, sender: &glib::Sender<PipewireM
.unwrap_or_default(),
);
// FIXME: This relies on the node being passed to us by the pipwire server before its port.
// FIXME: Instead of checking these props, the "EnumFormat" parameter should be checked instead.
let media_type = props
.get("media.class")
.map(|class| {
@@ -104,17 +115,25 @@ fn handle_node(node: &GlobalObject<ForeignDict>, sender: &glib::Sender<PipewireM
})
.flatten();
sender
.send(PipewireMessage::NodeAdded {
id: node.id,
name,
state.borrow_mut().insert(
node.id,
Item::Node {
// widget: node_widget,
media_type,
})
},
);
sender
.send(PipewireMessage::NodeAdded { id: node.id, name })
.expect("Failed to send message");
}
/// Handle a new port being added
fn handle_port(port: &GlobalObject<ForeignDict>, sender: &glib::Sender<PipewireMessage>) {
fn handle_port(
port: &GlobalObject<ForeignDict>,
sender: &glib::Sender<PipewireMessage>,
state: &Rc<RefCell<State>>,
) {
let props = port
.props
.as_ref()
@@ -131,52 +150,124 @@ fn handle_port(port: &GlobalObject<ForeignDict>, sender: &glib::Sender<PipewireM
Direction::Output
};
// Find out the nodes media type so that the port can be colored.
let media_type = if let Some(Item::Node { media_type, .. }) = state.borrow().get(node_id) {
media_type.to_owned()
} else {
warn!("Node not found for Port {}", port.id);
None
};
// Save node_id so we can delete this port easily.
state.borrow_mut().insert(port.id, Item::Port { node_id });
sender
.send(PipewireMessage::PortAdded {
id: port.id,
node_id,
name,
direction,
media_type,
})
.expect("Failed to send message");
}
/// Handle a new link being added
fn handle_link(link: &GlobalObject<ForeignDict>, sender: &glib::Sender<PipewireMessage>) {
let props = link
.props
.as_ref()
.expect("Link object is missing properties");
let node_from: u32 = props
.get("link.output.node")
.expect("Link has no link.input.node property")
.parse()
.expect("Could not parse link.input.node property");
let port_from: u32 = props
.get("link.output.port")
.expect("Link has no link.output.port property")
.parse()
.expect("Could not parse link.output.port property");
let node_to: u32 = props
.get("link.input.node")
.expect("Link has no link.input.node property")
.parse()
.expect("Could not parse link.input.node property");
let port_to: u32 = props
.get("link.input.port")
.expect("Link has no link.input.port property")
.parse()
.expect("Could not parse link.input.port property");
fn handle_link(
link: &GlobalObject<ForeignDict>,
sender: &glib::Sender<PipewireMessage>,
registry: &Rc<Registry>,
proxies: &Rc<RefCell<HashMap<u32, ProxyItem>>>,
state: &Rc<RefCell<State>>,
) {
debug!(
"New link (id:{}) appeared, setting up info listener.",
link.id
);
sender
.send(PipewireMessage::LinkAdded {
id: link.id,
link: crate::PipewireLink {
let proxy: Link = registry.bind(link).expect("Failed to bind to link proxy");
let listener = proxy
.add_listener_local()
.info(clone!(@strong state, @strong sender => move |info| {
debug!("Received link info: {:?}", info);
let id = info.id();
let mut state = state.borrow_mut();
if let Some(Item::Link { .. }) = state.get(id) {
// Info was an update - figure out if we should notify the gtk thread
// TODO
} else {
// First time we get info. We can now notify the gtk thread of a new link.
let node_from = info.output_node_id();
let port_from = info.output_port_id();
let node_to = info.input_node_id();
let port_to = info.input_port_id();
state.insert(id, Item::Link {
port_from, port_to
});
sender.send(PipewireMessage::LinkAdded {
id,
node_from,
port_from,
node_to,
port_to,
port_to
}).expect(
"Failed to send message"
);
}
}))
.register();
proxies.borrow_mut().insert(
link.id,
ProxyItem::Link {
_proxy: proxy,
_listener: listener,
},
})
.expect("Failed to send message");
);
}
/// Toggle a link between the two specified ports.
fn toggle_link(
port_from: u32,
port_to: u32,
core: &Rc<Core>,
registry: &Rc<Registry>,
state: &Rc<RefCell<State>>,
) {
let state = state.borrow_mut();
if let Some(id) = state.get_link_id(port_from, port_to) {
info!("Requesting removal of link with id {}", id);
// FIXME: Handle error
registry.destroy_global(id);
} else {
info!(
"Requesting creation of link from port id:{} to port id:{}",
port_from, port_to
);
let node_from = state
.get_node_of_port(port_from)
.expect("Requested port not in state");
let node_to = state
.get_node_of_port(port_to)
.expect("Requested port not in state");
if let Err(e) = core.create_object::<Link, _>(
"link-factory",
&properties! {
"link.output.node" => node_from.to_string(),
"link.output.port" => port_from.to_string(),
"link.input.node" => node_to.to_string(),
"link.input.port" => port_to.to_string(),
"object.linger" => "1"
},
) {
warn!("Failed to create link: {}", e);
}
}
}

View File

@@ -0,0 +1,81 @@
use std::collections::HashMap;
use crate::MediaType;
/// Any pipewire item we need to keep track of.
/// These will be saved in the `State` struct associated with their id.
pub(super) enum Item {
Node {
// Keep track of the nodes media type to color ports on it.
media_type: Option<MediaType>,
},
Port {
// Save the id of the node this is on so we can remove the port from it
// when it is deleted.
node_id: u32,
},
Link {
port_from: u32,
port_to: u32,
},
}
/// This struct keeps track of any relevant items and stores them under their IDs.
///
/// Given two port ids, it can also efficiently find the id of the link that connects them.
#[derive(Default)]
pub(super) struct State {
/// Map pipewire ids to items.
items: HashMap<u32, Item>,
/// Map `(output port id, input port id)` tuples to the id of the link that connects them.
links: HashMap<(u32, u32), u32>,
}
impl State {
/// Create a new, empty state.
pub fn new() -> Self {
Default::default()
}
/// Add a new item under the specified id.
pub fn insert(&mut self, id: u32, item: Item) {
if let Item::Link {
port_from, port_to, ..
} = item
{
self.links.insert((port_from, port_to), id);
}
self.items.insert(id, item);
}
/// Get the item that has the specified id.
pub fn get(&self, id: u32) -> Option<&Item> {
self.items.get(&id)
}
/// Get the id of the link that links the two specified ports.
pub fn get_link_id(&self, output_port: u32, input_port: u32) -> Option<u32> {
self.links.get(&(output_port, input_port)).copied()
}
/// Remove the item with the specified id, returning it if it exists.
pub fn remove(&mut self, id: u32) -> Option<Item> {
let removed = self.items.remove(&id);
if let Some(Item::Link { port_from, port_to }) = removed {
self.links.remove(&(port_from, port_to));
}
removed
}
/// Convenience function: Get the id of the node a port is on
pub fn get_node_of_port(&self, port: u32) -> Option<u32> {
if let Some(Item::Port { node_id }) = self.get(port) {
Some(*node_id)
} else {
None
}
}
}

14
src/style.css Normal file
View File

@@ -0,0 +1,14 @@
.audio {
background: rgb(50,100,240);
color: black;
}
.video {
background: rgb(200,200,0);
color: black;
}
.midi {
background: rgb(200,0,50);
color: black;
}

View File

@@ -1,6 +1,11 @@
use super::Node;
use super::{Node, Port};
use gtk::{gdk, glib, graphene, gsk, prelude::*, subclass::prelude::*};
use gtk::{
glib::{self, clone},
graphene, gsk,
prelude::*,
subclass::prelude::*,
};
use std::collections::HashMap;
@@ -15,7 +20,6 @@ mod imp {
pub struct GraphView {
pub(super) nodes: RefCell<HashMap<u32, Node>>,
pub(super) links: RefCell<HashMap<u32, crate::PipewireLink>>,
pub(super) dragged: Rc<RefCell<Option<gtk::Widget>>>,
}
#[glib::object_subclass]
@@ -34,28 +38,47 @@ mod imp {
fn constructed(&self, obj: &Self::Type) {
self.parent_constructed(obj);
// Move the Node that is currently being dragged to the cursor position as long as Mouse Button 1 is held.
let motion_controller = gtk::EventControllerMotion::new();
motion_controller.connect_motion(|controller, x, y| {
let instance = controller
.widget()
.unwrap()
.dynamic_cast::<Self::Type>()
.unwrap();
let this = imp::GraphView::from_instance(&instance);
let drag_state = Rc::new(RefCell::new(None));
let drag_controller = gtk::GestureDrag::new();
if let Some(ref widget) = *this.dragged.borrow() {
if controller
.current_event()
.unwrap()
.modifier_state()
.contains(gdk::ModifierType::BUTTON1_MASK)
{
instance.move_node(&widget, x as f32, y as f32);
drag_controller.connect_drag_begin(
clone!(@strong drag_state => move |drag_controller, x, y| {
let mut drag_state = drag_state.borrow_mut();
let widget = drag_controller
.widget()
.expect("drag-begin event has no widget")
.dynamic_cast::<Self::Type>()
.expect("drag-begin event is not on the GraphView");
// pick() should at least return the widget itself.
let target = widget.pick(x, y, gtk::PickFlags::DEFAULT).expect("drag-begin pick() did not return a widget");
*drag_state = if target.ancestor(Port::static_type()).is_some() {
// The user targeted a port, so the dragging should be handled by the Port
// component instead of here.
None
} else if let Some(target) = target.ancestor(Node::static_type()) {
// The user targeted a Node without targeting a specific Port.
// Drag the Node around the screen.
let (x, y) = widget.get_node_position(&target);
Some((target, x, y))
} else {
None
}
};
});
obj.add_controller(&motion_controller);
}));
drag_controller.connect_drag_update(
clone!(@strong drag_state => move |drag_controller, x, y| {
let widget = drag_controller
.widget()
.expect("drag-update event has no widget")
.dynamic_cast::<Self::Type>()
.expect("drag-update event is not on the GraphView");
let drag_state = drag_state.borrow();
if let Some((ref node, x1, y1)) = *drag_state {
widget.move_node(node, x1 + x as f32, y1 + y as f32);
}
}
),
);
obj.add_controller(&drag_controller);
}
fn dispose(&self, _obj: &Self::Type) {
@@ -249,8 +272,20 @@ impl GraphView {
self.queue_draw();
}
pub(super) fn set_dragged(&self, widget: Option<gtk::Widget>) {
*imp::GraphView::from_instance(self).dragged.borrow_mut() = widget;
pub(super) fn get_node_position(&self, node: &gtk::Widget) -> (f32, f32) {
let layout_manager = self
.layout_manager()
.expect("Failed to get layout manager")
.dynamic_cast::<gtk::FixedLayout>()
.expect("Failed to cast to FixedLayout");
let node = layout_manager
.layout_child(node)
.expect("Could not get layout child")
.dynamic_cast::<gtk::FixedLayoutChild>()
.expect("Could not cast to FixedLayoutChild");
let transform = node.transform().unwrap_or_default();
transform.to_translate()
}
pub(super) fn move_node(&self, node: &gtk::Widget, x: f32, y: f32) {

View File

@@ -1,5 +1,3 @@
use super::graph_view::GraphView;
use gtk::{glib, prelude::*, subclass::prelude::*};
use pipewire::spa::Direction;
@@ -34,35 +32,6 @@ mod imp {
grid.attach(&label, 0, 0, 2, 1);
let motion_controller = gtk::EventControllerMotion::new();
motion_controller.connect_enter(|controller, _, _| {
// Tell the graphview that the Node is the target of a drag when the mouse enters its label
let widget = controller
.widget()
.expect("Controller with enter event has no widget")
.ancestor(super::Node::static_type())
.expect("Node label does not have a node ancestor widget");
widget
.ancestor(GraphView::static_type())
.expect("Node with enter event is not on graph")
.dynamic_cast::<GraphView>()
.unwrap()
.set_dragged(Some(widget));
});
motion_controller.connect_leave(|controller| {
// Tell the graphview that the Node is no longer the target of a drag when the mouse leaves.
// FIXME: Check that we are the current target before setting none.
controller
.widget()
.expect("Controller with leave event has no widget")
.ancestor(GraphView::static_type())
.expect("Node with leave event is not on graph")
.dynamic_cast::<GraphView>()
.unwrap()
.set_dragged(None);
});
label.add_controller(&motion_controller);
// Display a grab cursor when the mouse is over the label so the user knows the node can be dragged.
label.set_cursor(gtk::gdk::Cursor::from_name("grab", None).as_ref());

View File

@@ -7,7 +7,7 @@ use gtk::{
use log::warn;
use pipewire::spa::Direction;
use crate::application::MediaType;
use crate::MediaType;
mod imp {
use once_cell::{sync::Lazy, unsync::OnceCell};