Node network subgraph editing (#1750)

* Breadcrumb visualization, nested network consistency, create definitions for Merge internal nodes

* Add index to network inputs, remove imports usage from flatten network

* Replace NodeOutput with NodeInput::Node

* Fully remove imports field, remove unnecessary identity nodes, move Output node to encapsulating network

* Replace previous_outputs with root_node, fix adding artboard/layer to empty network

* Import/Export UI nodes

* Display input/output types dynamically from compiled network

* Add LayerNodeIdentifer::ROOT_PARENT

* Prevent .to_node() on ROOT_PARENT

* Separate NodeGraphMessage and GraphOperationMessage

* General bug fixes with nested networks

* Change layer color, various bug fixes and improvements

* Fix disconnect and set node input for proto nodes and UI export node

* Dashed line to export for previewed node

* Fix deleting proto nodes and nodes that feed into export

* Allow modifications to nodes outside of nested network

* Get network from Node Id parameter

* Change root_node to previous_root_node

* Get TaggedValue from proto node implementation type when disconnecting

* Improve preview functionality and state

* Artboard position and delete children fix

* Name inputs/outputs based on DocumentNodeDefinition or type, fix new artboard/layer insertion

* replace "Link" with "Wire", adjust previewing

* Various bug fixes and improvements

* Modify Sample and Poisson-Disk points, fix incorrect input index and deleting currently viewed node

* Open demo artwork

* Fix opening already upgraded documents and refactor FrontendGraphDataType usages

* Fix deleting within network and other bugs

* Get default node input from compiled network when copying, fix previews, tests, demo artwork

* Code cleanup

* Hide EditorApi and add a comment describing unresolved Import node input types

* Code review

* Replace placeholder ROOT_PARENT NodeId with std::u64::MAX

* Breadcrumb padding

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
adamgerhant 2024-06-02 01:01:56 -07:00 committed by GitHub
parent e4d3faa52a
commit 6d74abb4de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
77 changed files with 3924 additions and 2327 deletions

317
Cargo.lock generated
View file

@ -10,9 +10,9 @@ checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
[[package]]
name = "ab_glyph"
version = "0.2.25"
version = "0.2.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f90148830dac590fac7ccfe78ec4a8ea404c60f75a24e16407a71f0f40de775"
checksum = "2e53b0a3d5760cd2ba9b787ae0c6440ad18ee294ff71b05e3381c900a7d16cfd"
dependencies = [
"ab_glyph_rasterizer",
"owned_ttf_parser",
@ -46,7 +46,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"getrandom 0.2.14",
"getrandom 0.2.15",
"once_cell",
"version_check",
"zerocopy",
@ -126,9 +126,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.82"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3"
[[package]]
name = "approx"
@ -331,7 +331,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -366,7 +366,7 @@ checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -401,9 +401,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "autocfg"
version = "1.2.0"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "autoquant"
@ -552,9 +552,9 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "base64"
version = "0.22.0"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bezier-rs"
@ -730,7 +730,7 @@ checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -810,9 +810,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.95"
version = "1.0.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b"
checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4"
dependencies = [
"jobserver",
"libc",
@ -1187,7 +1187,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -1197,7 +1197,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
dependencies = [
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -1238,7 +1238,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -1249,7 +1249,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
dependencies = [
"darling_core",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -1287,7 +1287,7 @@ checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -1392,7 +1392,7 @@ dependencies = [
"dyn-any",
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -1454,7 +1454,7 @@ checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -1478,9 +1478,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.8"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"libc",
"windows-sys 0.52.0",
@ -1683,7 +1683,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -1797,7 +1797,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -1971,9 +1971,9 @@ dependencies = [
[[package]]
name = "getrandom"
version = "0.2.14"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
@ -2400,7 +2400,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -3078,14 +3078,13 @@ dependencies = [
[[package]]
name = "json-patch"
version = "1.2.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55ff1e1486799e3f64129f8ccad108b38290df9cd7015cd31bed17239f0789d6"
checksum = "ec9ad60d674508f3ca8f380a928cfe7b096bc729c4e2dbfe3852bc45da3ab30b"
dependencies = [
"serde",
"serde_json",
"thiserror",
"treediff",
]
[[package]]
@ -3150,7 +3149,7 @@ dependencies = [
[[package]]
name = "kurbo"
version = "0.11.0"
source = "git+https://github.com/linebender/kurbo.git#46820b0963e3b37912f91c66225a401096533e16"
source = "git+https://github.com/linebender/kurbo.git#2e16a976d9c9f1f860e855c1b0f48a314ffeb969"
dependencies = [
"arrayvec",
"serde",
@ -3595,7 +3594,7 @@ version = "0.0.0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -3645,9 +3644,9 @@ dependencies = [
[[package]]
name = "num"
version = "0.4.2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3135b08af27d103b0a51f2ae0f8632117b7b185ccf931445affa8df530576a41"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint",
"num-complex",
@ -3659,20 +3658,19 @@ dependencies = [
[[package]]
name = "num-bigint"
version = "0.4.4"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-complex"
version = "0.4.5"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
"serde",
@ -3692,7 +3690,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -3706,9 +3704,9 @@ dependencies = [
[[package]]
name = "num-iter"
version = "0.1.44"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
"autocfg",
"num-integer",
@ -3717,11 +3715,10 @@ dependencies = [
[[package]]
name = "num-rational"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"autocfg",
"num-bigint",
"num-integer",
"num-traits",
@ -3729,9 +3726,9 @@ dependencies = [
[[package]]
name = "num-traits"
version = "0.2.18"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
"libm",
@ -3795,7 +3792,7 @@ dependencies = [
"proc-macro-crate 1.3.1",
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -3807,7 +3804,7 @@ dependencies = [
"proc-macro-crate 3.1.0",
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -3977,7 +3974,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -4046,11 +4043,11 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "owned_ttf_parser"
version = "0.20.0"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4586edfe4c648c71797a74c84bacb32b52b212eff5dfe2bb9f2c599844023e7"
checksum = "6b41438d2fc63c46c74a2203bf5ccd82c41ba04347b2fcf5754f230b167067d5"
dependencies = [
"ttf-parser 0.20.0",
"ttf-parser 0.21.1",
]
[[package]]
@ -4109,9 +4106,9 @@ dependencies = [
[[package]]
name = "paste"
version = "1.0.14"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pathdiff"
@ -4137,9 +4134,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "petgraph"
version = "0.6.4"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
dependencies = [
"fixedbitset",
"indexmap 2.2.6",
@ -4249,7 +4246,7 @@ dependencies = [
"phf_shared 0.11.2",
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -4302,7 +4299,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -4451,9 +4448,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
version = "1.0.81"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba"
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
dependencies = [
"unicode-ident",
]
@ -4543,7 +4540,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom 0.2.14",
"getrandom 0.2.15",
"serde",
]
@ -4661,7 +4658,7 @@ version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891"
dependencies = [
"getrandom 0.2.14",
"getrandom 0.2.15",
"libredox 0.1.3",
"thiserror",
]
@ -4820,7 +4817,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.14",
"getrandom 0.2.15",
"libc",
"spin",
"untrusted",
@ -4847,9 +4844,9 @@ checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f"
[[package]]
name = "rustc-demangle"
version = "0.1.23"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustc-hash"
@ -4912,9 +4909,9 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.15"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47"
checksum = "092474d1a01ea8278f69e6a358998405fae5b8b963ddaeb2b0b04a128bf1dfb0"
[[package]]
name = "rustybuzz"
@ -4950,9 +4947,9 @@ dependencies = [
[[package]]
name = "ryu"
version = "1.0.17"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "safe_arch"
@ -5018,11 +5015,11 @@ dependencies = [
[[package]]
name = "security-framework"
version = "2.10.0"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6"
checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0"
dependencies = [
"bitflags 1.3.2",
"bitflags 2.5.0",
"core-foundation",
"core-foundation-sys",
"libc",
@ -5031,9 +5028,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
version = "2.10.0"
version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef"
checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7"
dependencies = [
"core-foundation-sys",
"libc",
@ -5061,18 +5058,18 @@ dependencies = [
[[package]]
name = "semver"
version = "1.0.22"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
dependencies = [
"serde",
]
[[package]]
name = "serde"
version = "1.0.199"
version = "1.0.201"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a"
checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c"
dependencies = [
"serde_derive",
]
@ -5090,20 +5087,20 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.199"
version = "1.0.201"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc"
checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
name = "serde_json"
version = "1.0.116"
version = "1.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3"
dependencies = [
"indexmap 2.2.6",
"itoa 1.0.11",
@ -5129,7 +5126,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -5159,7 +5156,7 @@ version = "3.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20"
dependencies = [
"base64 0.22.0",
"base64 0.22.1",
"chrono",
"hex",
"indexmap 1.9.3",
@ -5180,7 +5177,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -5452,8 +5449,8 @@ dependencies = [
[[package]]
name = "specta"
version = "2.0.0-rc.9"
source = "git+https://github.com/oscartbeaumont/specta.git#823b7fc78da6f12acf866d657b2793d5c8f7536b"
version = "2.0.0-rc.12"
source = "git+https://github.com/oscartbeaumont/specta.git#f3bc75ec6120ea0fc6cd8e2e2e3aebef049e93cb"
dependencies = [
"glam",
"once_cell",
@ -5465,8 +5462,8 @@ dependencies = [
[[package]]
name = "specta-macros"
version = "2.0.0-rc.9"
source = "git+https://github.com/oscartbeaumont/specta.git#823b7fc78da6f12acf866d657b2793d5c8f7536b"
version = "2.0.0-rc.10"
source = "git+https://github.com/oscartbeaumont/specta.git#f3bc75ec6120ea0fc6cd8e2e2e3aebef049e93cb"
dependencies = [
"Inflector",
"proc-macro2",
@ -5608,9 +5605,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.60"
version = "2.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3"
checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704"
dependencies = [
"proc-macro2",
"quote",
@ -5772,9 +5769,9 @@ checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f"
[[package]]
name = "tauri"
version = "1.6.2"
version = "1.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "047aefcc7721bfb8024a9bc39d4719112262610502de7a224fa62c4570cd78d4"
checksum = "13ce04f77bcd40bb57ec7061725c9c415d30b2bf80257637b857ee067f2fa198"
dependencies = [
"anyhow",
"bytes",
@ -5830,14 +5827,14 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "1.5.1"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9914a4715e0b75d9f387a285c7e26b5bbfeb1249ad9f842675a82481565c532"
checksum = "ab30cba12974d0f9b09794f61e72cad6da2142d3ceb81e519321bab86ce53312"
dependencies = [
"anyhow",
"cargo_toml",
"dirs-next",
"heck 0.4.1",
"heck 0.5.0",
"json-patch",
"semver",
"serde",
@ -5849,9 +5846,9 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "1.4.2"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1554c5857f65dbc377cefb6b97c8ac77b1cb2a90d30d3448114d5d6b48a77fc"
checksum = "c3a1d90db526a8cdfd54444ad3f34d8d4d58fa5c536463915942393743bd06f8"
dependencies = [
"base64 0.21.7",
"brotli",
@ -5875,11 +5872,11 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "1.4.3"
version = "1.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "277abf361a3a6993ec16bcbb179de0d6518009b851090a01adfea12ac89fa875"
checksum = "6a582d75414250122e4a597b9dd7d3c910a2c77906648fc2ac9353845ff0feec"
dependencies = [
"heck 0.4.1",
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 1.0.109",
@ -5889,9 +5886,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "0.14.2"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf2d0652aa2891ff3e9caa2401405257ea29ab8372cce01f186a5825f1bd0e76"
checksum = "cd7ffddf36d450791018e63a3ddf54979b9581d9644c584a5fb5611e6b5f20b4"
dependencies = [
"gtk",
"http 0.2.12",
@ -5910,9 +5907,9 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "0.14.5"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "067c56fc153b3caf406d7cd6de4486c80d1d66c0f414f39e94cb2f5543f6445f"
checksum = "ef2af45aeb15b1cadb4ca91248423f4438a0864b836298cecb436892afbfdff4"
dependencies = [
"arboard",
"cocoa",
@ -5931,15 +5928,15 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "1.5.3"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75ad0bbb31fccd1f4c56275d0a5c3abdf1f59999f72cb4ef8b79b4ed42082a21"
checksum = "450b17a7102e5d46d4bdabae0d1590fd27953e704e691fc081f06c06d2253b35"
dependencies = [
"brotli",
"ctor",
"dunce",
"glob",
"heck 0.4.1",
"heck 0.5.0",
"html5ever",
"infer",
"json-patch",
@ -6031,22 +6028,22 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
[[package]]
name = "thiserror"
version = "1.0.59"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa"
checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.59"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66"
checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -6169,7 +6166,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -6194,16 +6191,15 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.10"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
"tracing",
]
[[package]]
@ -6282,7 +6278,7 @@ dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"winnow 0.6.7",
"winnow 0.6.8",
]
[[package]]
@ -6349,7 +6345,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -6405,15 +6401,6 @@ dependencies = [
"petgraph",
]
[[package]]
name = "treediff"
version = "4.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d127780145176e2b5d16611cc25a900150e86e9fd79d3bde6ff3a37359c9cb5"
dependencies = [
"serde_json",
]
[[package]]
name = "try-lock"
version = "0.2.5"
@ -6432,6 +6419,12 @@ version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4"
[[package]]
name = "ttf-parser"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8"
[[package]]
name = "typenum"
version = "1.17.0"
@ -6575,7 +6568,7 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0"
dependencies = [
"getrandom 0.2.14",
"getrandom 0.2.15",
]
[[package]]
@ -6759,7 +6752,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
"wasm-bindgen-shared",
]
@ -6793,7 +6786,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -7187,9 +7180,9 @@ dependencies = [
[[package]]
name = "wide"
version = "0.7.17"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f0e39d2c603fdc0504b12b458cf1f34e0b937ed2f4f2dc20796e3e86f34e11f"
checksum = "5925f89e85af9e6e776bedb11ddeb2365b85cb56c13bfb30223e4b6398d30bb6"
dependencies = [
"bytemuck",
"safe_arch",
@ -7337,7 +7330,7 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -7348,7 +7341,7 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
@ -7720,9 +7713,9 @@ dependencies = [
[[package]]
name = "winnow"
version = "0.6.7"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14b9415ee827af173ebb3f15f9083df5a122eb93572ec28741fb153356ea2578"
checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d"
dependencies = [
"memchr",
]
@ -7769,9 +7762,9 @@ dependencies = [
[[package]]
name = "wry"
version = "0.24.8"
version = "0.24.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a04e72739ee84a218e3dbf8625888eadc874285637003ed21ab96a1bbbb538ec"
checksum = "3c689900e022bb67b0d9728fb817bbef2b9da7ebd6c79aade5f0c32fe4c18c73"
dependencies = [
"base64 0.13.1",
"block",
@ -7828,9 +7821,9 @@ dependencies = [
[[package]]
name = "x11rb"
version = "0.13.0"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a"
checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12"
dependencies = [
"as-raw-xcb-connection",
"gethostname",
@ -7843,9 +7836,9 @@ dependencies = [
[[package]]
name = "x11rb-protocol"
version = "0.13.0"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34"
checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d"
[[package]]
name = "xattr"
@ -7907,9 +7900,9 @@ checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
[[package]]
name = "zbus"
version = "4.1.2"
version = "4.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9ff46f2a25abd690ed072054733e0bc3157e3d4c45f41bd183dce09c2ff8ab9"
checksum = "e5915716dff34abef1351d2b10305b019c8ef33dcf6c72d31a6e227d5d9d7a21"
dependencies = [
"async-broadcast",
"async-executor",
@ -7921,7 +7914,6 @@ dependencies = [
"async-task",
"async-trait",
"blocking",
"derivative",
"enumflags2",
"event-listener 5.3.0",
"futures-core",
@ -7946,14 +7938,13 @@ dependencies = [
[[package]]
name = "zbus_macros"
version = "4.1.2"
version = "4.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e0e3852c93dcdb49c9462afe67a2a468f7bd464150d866e861eaf06208633e0"
checksum = "66fceb36d0c1c4a6b98f3ce40f410e64e5a134707ed71892e1b178abc4c695d4"
dependencies = [
"proc-macro-crate 3.1.0",
"proc-macro2",
"quote",
"regex",
"syn 1.0.109",
"zvariant_utils",
]
@ -7971,29 +7962,29 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.7.32"
version = "0.7.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.32"
version = "0.7.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.60",
"syn 2.0.63",
]
[[package]]
name = "zvariant"
version = "4.0.2"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c1b3ca6db667bfada0f1ebfc94b2b1759ba25472ee5373d4551bb892616389a"
checksum = "877ef94e5e82b231d2a309c531f191a8152baba8241a7939ee04bd76b0171308"
dependencies = [
"endi",
"enumflags2",
@ -8004,9 +7995,9 @@ dependencies = [
[[package]]
name = "zvariant_derive"
version = "4.0.2"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7a4b236063316163b69039f77ce3117accb41a09567fd24c168e43491e521bc"
checksum = "b7ca98581cc6a8120789d8f1f0997e9053837d6aa5346cbb43454d7121be6e39"
dependencies = [
"proc-macro-crate 3.1.0",
"proc-macro2",
@ -8017,9 +8008,9 @@ dependencies = [
[[package]]
name = "zvariant_utils"
version = "1.1.0"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00bedb16a193cc12451873fee2a1bc6550225acece0e36f333e68326c73c8172"
checksum = "75fa7291bdd68cd13c4f97cc9d78cbf16d96305856dfc7ac942aeff4c2de7d5a"
dependencies = [
"proc-macro2",
"quote",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -88,11 +88,8 @@ mod test {
block_on(crate::node_graph_executor::run_node_graph());
let mut res = VecDeque::new();
editor.poll_node_graph_evaluation(&mut res);
//println!("node_graph_poll: {res:#?}");
//println!("in: {message:#?}");
let res = editor.handle_message(message);
//println!("out: {res:#?}");
responses.push(res);
}
let responses = responses.pop().unwrap();

View file

@ -71,10 +71,6 @@ pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848";
pub const COLOR_OVERLAY_WHITE: &str = "#ffffff";
pub const COLOR_OVERLAY_GRAY: &str = "#cccccc";
// Fonts
pub const DEFAULT_FONT_FAMILY: &str = "Cabin";
pub const DEFAULT_FONT_STYLE: &str = "Normal (400)";
// Document
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
pub const FILE_SAVE_SUFFIX: &str = ".graphite";

View file

@ -1,4 +1,3 @@
use crate::consts::{DEFAULT_FONT_FAMILY, DEFAULT_FONT_STYLE};
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
use crate::messages::dialog::DialogMessageData;
use crate::messages::prelude::*;
@ -99,7 +98,7 @@ impl Dispatcher {
queue.add(MenuBarMessage::SendLayout);
// Load the default font
let font = Font::new(DEFAULT_FONT_FAMILY.into(), DEFAULT_FONT_STYLE.into());
let font = Font::new(graphene_core::consts::DEFAULT_FONT_FAMILY.into(), graphene_core::consts::DEFAULT_FONT_STYLE.into());
queue.add(FrontendMessage::TriggerFontLoad { font, is_default: true });
}
Message::Batched(messages) => {
@ -307,7 +306,7 @@ mod test {
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
editor.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
parent: LayerNodeIdentifier::ROOT,
parent: LayerNodeIdentifier::ROOT_PARENT,
insert_index: -1,
});
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
@ -341,7 +340,7 @@ mod test {
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
editor.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
parent: LayerNodeIdentifier::ROOT,
parent: LayerNodeIdentifier::ROOT_PARENT,
insert_index: -1,
});
@ -385,12 +384,12 @@ mod test {
editor.draw_rect(0., 800., 12., 200.);
editor.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
parent: LayerNodeIdentifier::ROOT,
parent: LayerNodeIdentifier::ROOT_PARENT,
insert_index: -1,
});
editor.handle_message(PortfolioMessage::PasteIntoFolder {
clipboard: Clipboard::Internal,
parent: LayerNodeIdentifier::ROOT,
parent: LayerNodeIdentifier::ROOT_PARENT,
insert_index: -1,
});

View file

@ -26,7 +26,7 @@ pub enum DialogMessage {
localized_commit_year: String,
},
RequestComingSoonDialog {
issue: Option<i32>,
issue: Option<u32>,
},
RequestDemoArtworkDialog,
RequestExportDialog,

View file

@ -3,7 +3,7 @@ use crate::messages::prelude::*;
/// A dialog to notify users of an unfinished issue, optionally with an issue number.
pub struct ComingSoonDialog {
pub issue: Option<i32>,
pub issue: Option<u32>,
}
impl DialogLayoutHolder for ComingSoonDialog {

View file

@ -1,6 +1,6 @@
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::utility_types::{FrontendNode, FrontendNodeLink, FrontendNodeType};
use crate::messages::portfolio::document::node_graph::utility_types::{FrontendNode, FrontendNodeType, FrontendNodeWire};
use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer};
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::HintData;
@ -188,7 +188,7 @@ pub enum FrontendMessage {
},
UpdateNodeGraph {
nodes: Vec<FrontendNode>,
links: Vec<FrontendNodeLink>,
wires: Vec<FrontendNodeWire>,
},
UpdateNodeGraphBarLayout {
#[serde(rename = "layoutTarget")]
@ -220,6 +220,10 @@ pub enum FrontendMessage {
layout_target: LayoutTarget,
diff: Vec<WidgetDiff>,
},
UpdateSubgraphPath {
#[serde(rename = "subgraphPath")]
subgraph_path: Vec<String>,
},
UpdateToolOptionsLayout {
#[serde(rename = "layoutTarget")]
layout_target: LayoutTarget,

View file

@ -62,6 +62,7 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(KeyH); modifiers=[Accel], action_dispatch=NodeGraphMessage::ToggleSelectedVisibility),
entry!(KeyDown(KeyL); modifiers=[Accel], action_dispatch=NodeGraphMessage::ToggleSelectedLocked),
entry!(KeyDown(KeyL); modifiers=[Alt], action_dispatch=NodeGraphMessage::ToggleSelectedAsLayersOrNodes),
entry!(KeyDown(KeyC); modifiers=[Shift], action_dispatch=NodeGraphMessage::PrintSelectedNodeCoordinates),
//
// TransformLayerMessage
entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation),

View file

@ -186,6 +186,7 @@ impl LayoutMessageHandler {
let callback_message = (number_input.on_update.callback)(number_input);
responses.add(callback_message);
}
// TODO: This crashes when the cursor is in a text box, such as in the Text node, and the transform node is clicked (https://github.com/GraphiteEditor/Graphite/issues/1761)
Value::String(str) => match str.as_str() {
"Increment" => responses.add((number_input.increment_callback_increase.callback)(number_input)),
"Decrement" => responses.add((number_input.increment_callback_decrease.callback)(number_input)),

View file

@ -45,7 +45,7 @@ pub enum DocumentMessage {
CreateEmptyFolder,
DebugPrintDocument,
DeleteLayer {
id: NodeId,
layer: LayerNodeIdentifier,
},
DeleteSelectedLayers,
DeselectAllLayers,

View file

@ -21,14 +21,11 @@ use crate::node_graph_executor::NodeGraphExecutor;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::FlowType;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, DocumentNodeMetadata, NodeId, NodeInput, NodeNetwork, NodeOutput};
use graph_craft::document::{NodeId, NodeInput, NodeNetwork};
use graphene_core::raster::BlendMode;
use graphene_core::raster::ImageFrame;
use graphene_core::renderer::ClickTarget;
use graphene_core::transform::Footprint;
use graphene_core::vector::style::ViewMode;
use graphene_core::{concrete, generic, ProtoNodeIdentifier};
use graphene_std::wasm_application_io::WasmEditorApi;
use glam::{DAffine2, DVec2, IVec2};
@ -51,7 +48,7 @@ pub struct DocumentMessageHandler {
#[serde(skip)]
navigation_handler: NavigationMessageHandler,
#[serde(skip)]
node_graph_handler: NodeGraphMessageHandler,
pub node_graph_handler: NodeGraphMessageHandler,
#[serde(skip)]
overlays_message_handler: OverlaysMessageHandler,
#[serde(skip)]
@ -290,13 +287,14 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
let parent = self
.metadata()
.deepest_common_ancestor(self.selected_nodes.selected_layers(self.metadata()), true)
.unwrap_or(LayerNodeIdentifier::ROOT);
.unwrap_or(LayerNodeIdentifier::ROOT_PARENT);
let insert_index = parent
.children(self.metadata())
.enumerate()
.find_map(|(index, item)| self.selected_nodes.selected_layers(self.metadata()).any(|x| x == item).then_some(index as isize))
.unwrap_or(-1);
responses.add(DocumentMessage::StartTransaction);
responses.add(GraphOperationMessage::NewCustomLayer {
id,
@ -310,8 +308,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
DocumentMessage::DebugPrintDocument => {
info!("{:#?}", self.network);
}
DocumentMessage::DeleteLayer { id } => {
responses.add(NodeGraphMessage::DeleteNodes { node_ids: vec![id], reconnect: true });
DocumentMessage::DeleteLayer { layer } => {
responses.add(GraphOperationMessage::DeleteLayer { layer, reconnect: true });
responses.add_front(BroadcastEvent::ToolAbort);
}
DocumentMessage::DeleteSelectedLayers => {
@ -319,7 +317,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add_front(BroadcastEvent::SelectionChanged);
for path in self.metadata().shallowest_unique_layers(self.selected_nodes.selected_layers(self.metadata())) {
responses.add_front(DocumentMessage::DeleteLayer { id: path.last().unwrap().to_node() });
// `path` will never include `ROOT_PARENT`, so this is safe
responses.add_front(DocumentMessage::DeleteLayer { layer: *path.last().unwrap() });
}
}
DocumentMessage::DeselectAllLayers => {
@ -331,7 +330,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
DocumentMessage::DocumentStructureChanged => {
self.update_layers_panel_options_bar_widgets(responses);
self.metadata.load_structure(&self.network, &mut self.selected_nodes);
self.metadata.load_structure(&self.network);
let data_buffer: RawBuffer = self.serialize_root();
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
}
@ -395,16 +394,11 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
DocumentMessage::GroupSelectedLayers => {
responses.add(DocumentMessage::StartTransaction);
let parent = self
.metadata()
.deepest_common_ancestor(self.selected_nodes.selected_layers(self.metadata()), false)
.unwrap_or(LayerNodeIdentifier::ROOT);
let Some(parent) = self.metadata().deepest_common_ancestor(self.selected_nodes.selected_layers(self.metadata()), false) else {
// Cancel grouping layers across different artboards
// TODO: Group each set of layers for each artboard separately
if parent == LayerNodeIdentifier::ROOT {
return;
}
};
// Move layers in nested unselected folders above the first unselected parent folder
let selected_layers = self.selected_nodes.selected_layers(self.metadata()).collect::<Vec<_>>();
@ -429,7 +423,19 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
continue;
}
responses.add(NodeGraphMessage::DisconnectLayerFromStack {
// `ROOT_PARENT` cannot be selected, so this should never be true
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("ROOT_PARENT cannot be deleted");
continue;
}
// `first_unselected_parent_folder` must be a child of `parent`, so it cannot be the `ROOT_PARENT`
if first_unselected_parent_folder == LayerNodeIdentifier::ROOT_PARENT {
log::error!("first_unselected_parent_folder cannot be ROOT_PARENT");
continue;
}
responses.add(GraphOperationMessage::DisconnectNodeFromStack {
node_id: layer.to_node(),
reconnect_to_sibling: true,
});
@ -442,17 +448,17 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
.expect("Current folder should always exist")
.metadata
.position;
responses.add(NodeGraphMessage::SetNodePosition {
responses.add(GraphOperationMessage::SetNodePosition {
node_id: layer.to_node(),
position: folder_position,
});
// Insert node right above the folder
let Some((folder_downstream_node_id, folder_downstream_input_index)) = DocumentMessageHandler::get_downstream_node(&self.network, &self.metadata, first_unselected_parent_folder)
else {
log::error!("Downstream node should always exist when inserting layer");
return;
};
// TODO: downstream node can be none if it is the root node
let (folder_downstream_node_id, folder_downstream_input_index) =
DocumentMessageHandler::get_downstream_node(&self.network, &self.metadata, first_unselected_parent_folder).unwrap_or((self.network.exports_metadata.0, 0));
responses.add(GraphOperationMessage::InsertNodeBetween {
post_node_id: folder_downstream_node_id,
post_node_input_index: folder_downstream_input_index,
@ -463,13 +469,12 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
pre_node_id: first_unselected_parent_folder.to_node(),
});
responses.add(NodeGraphMessage::ShiftUpstream {
responses.add(GraphOperationMessage::ShiftUpstream {
node_id: first_unselected_parent_folder.to_node(),
shift: IVec2::new(0, 3),
shift_self: true,
});
}
let calculated_insert_index = DocumentMessageHandler::get_calculated_insert_index(&self.metadata, &self.selected_nodes, parent);
let folder_id = NodeId(generate_uuid());
@ -481,7 +486,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
alias: String::new(),
});
responses.add(GraphOperationMessage::MoveSelectedSiblingsToChild { new_parent: folder_id });
let parent = LayerNodeIdentifier::new_unchecked(folder_id);
responses.add(GraphOperationMessage::MoveSelectedSiblingsToChild { new_parent: parent });
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![folder_id] });
responses.add(NodeGraphMessage::RunDocumentGraph);
@ -533,16 +539,17 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
if selected_layers.iter().any(|&layer| parent.ancestors(self.metadata()).any(|ancestor| ancestor == layer)) {
return;
}
// Artboards can only have the Output node as the parent.
if selected_layers.iter().any(|&layer| self.metadata.is_artboard(layer)) && parent != LayerNodeIdentifier::ROOT {
// Artboards can only have `ROOT_PARENT` as the parent.
if selected_layers.iter().any(|&layer| self.metadata.is_artboard(layer)) && parent != LayerNodeIdentifier::ROOT_PARENT {
return;
}
// Disallow inserting layers between artboards. Since only artboards can output to Output node, the layer parent cannot be the output.
if !selected_layers.iter().any(|&layer| self.metadata.is_artboard(layer)) && parent == LayerNodeIdentifier::ROOT {
if !selected_layers.iter().any(|&layer| self.metadata.is_artboard(layer)) && parent == LayerNodeIdentifier::ROOT_PARENT {
return;
}
let mut insert_index = if insert_index < 0 { 0 } else { insert_index as usize };
let insert_index = self.update_insert_index(&selected_layers, parent, insert_index);
let layer_above_insertion = if insert_index == 0 { Some(parent) } else { parent.children(&self.metadata).nth(insert_index - 1) };
let binding = self.metadata.shallowest_unique_layers(self.selected_nodes.selected_layers(&self.metadata));
let get_last_elements = binding.iter().map(|x| x.last().expect("empty path")).collect::<Vec<_>>();
@ -550,15 +557,28 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
// TODO: The `.collect()` is necessary to avoid borrowing issues with `self`. See if this can be avoided to improve performance.
let ordered_last_elements = self.metadata.all_layers().filter(|layer| get_last_elements.contains(&layer)).rev().collect::<Vec<_>>();
for layer_to_move in ordered_last_elements {
if layer_to_move
.upstream_siblings(&self.metadata)
.any(|layer| layer_above_insertion.is_some_and(|layer_above_insertion| layer_above_insertion == layer))
{
insert_index -= 1;
}
// `layer_to_move` should never be `ROOT_PARENT`, since it is not included in `all_layers()`
if layer_to_move == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Layer to move cannot be root parent");
continue;
}
// Disconnect layer to move and reconnect downstream node to upstream sibling if it exists.
responses.add(NodeGraphMessage::DisconnectLayerFromStack {
responses.add(GraphOperationMessage::DisconnectNodeFromStack {
node_id: layer_to_move.to_node(),
reconnect_to_sibling: true,
});
// Reconnect layer_to_move to new parent at insert index.
responses.add(GraphOperationMessage::InsertLayerAtStackIndex {
layer_id: layer_to_move.to_node(),
parent: parent.to_node(),
responses.add(GraphOperationMessage::InsertNodeAtStackIndex {
node_id: layer_to_move.to_node(),
parent: parent,
insert_index,
});
}
@ -658,6 +678,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
use crate::messages::tool::common_functionality::graph_modification_utils;
let layer = graph_modification_utils::new_image_layer(image_frame, NodeId(generate_uuid()), self.new_layer_parent(true), responses);
// `layer` cannot be `ROOT_PARENT` since it is the newly created layer
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
responses.add(GraphOperationMessage::TransformSet {
@ -771,6 +792,11 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
// If we have shift pressed and a layer already selected then fill the range
if let Some(last_selected) = self.layer_range_selection_reference.filter(|_| shift) {
if last_selected == LayerNodeIdentifier::ROOT_PARENT {
log::error!("ROOT_PARENT cannot be selected in SelectLayer");
return;
}
nodes.push(last_selected.to_node());
nodes.push(id);
@ -780,7 +806,13 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
.skip_while(|&node| node != layer && node != last_selected)
.skip(1)
.take_while(|&node| node != layer && node != last_selected)
.for_each(|node| nodes.push(node.to_node()));
.for_each(|node| {
if node == LayerNodeIdentifier::ROOT_PARENT {
log::error!("ROOT_PARENT should not exist in all_layers")
} else {
nodes.push(node.to_node())
}
});
} else {
if ctrl {
// Toggle selection when holding ctrl
@ -930,6 +962,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
let folder_paths = self.metadata().folders_sorted_by_most_nested(self.selected_nodes.selected_layers(self.metadata()));
for folder in folder_paths {
if folder == LayerNodeIdentifier::ROOT_PARENT {
log::error!("ROOT_PARENT cannot be selected when ungrouping selected layers");
continue;
}
// Cannot ungroup artboard
let folder_node = self.network.nodes.get(&folder.to_node()).expect("Folder node should always exist");
if folder_node.is_artboard() {
@ -937,29 +973,30 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
}
// Get first child layer node that feeds into the secondary input for the folder
let Some(child_layer_node_id) = folder.first_child(&self.metadata).map(|child_layer| child_layer.to_node()) else {
let Some(child_layer) = folder.first_child(&self.metadata) else {
log::error!("Folder should always have a child");
return;
};
// Move child_layer stack x position to folder stack
let child_layer_node = self.network.nodes.get(&child_layer_node_id).expect("Child node should always exist for layer");
let child_layer_node = self.network.nodes.get(&child_layer.to_node()).expect("Child node should always exist for layer");
let offset = folder_node.metadata.position - child_layer_node.metadata.position;
responses.add(NodeGraphMessage::ShiftUpstream {
node_id: child_layer_node_id,
responses.add(GraphOperationMessage::ShiftUpstream {
node_id: child_layer.to_node(),
shift: offset,
shift_self: true,
});
// Set the primary input for the node downstream of folder to the first layer node
// TODO: downstream node can be none if it is the root node. A layer group connected directly to the export cannot be ungrouped
let Some((downstream_node_id, downstream_input_index)) = DocumentMessageHandler::get_downstream_node(&self.network, &self.metadata, folder) else {
log::error!("Downstream node should always exist when moving layer");
continue;
};
// Output_index must be 0 since layers only have 1 output
let downstream_input = NodeInput::node(child_layer_node_id, 0);
responses.add(NodeGraphMessage::SetNodeInput {
let downstream_input = NodeInput::node(child_layer.to_node(), 0);
responses.add(GraphOperationMessage::SetNodeInput {
node_id: downstream_node_id,
input_index: downstream_input_index,
input: downstream_input,
@ -967,10 +1004,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
// Get the node that feeds into the primary input for the folder (if it exists)
if let Some(NodeInput::Node { node_id, .. }) = self.network.nodes.get(&folder.to_node()).expect("Folder should always exist").inputs.get(0) {
let layer_upstream_sibling_id = *node_id;
let upstream_sibling_id = *node_id;
// Get the node at the bottom of the first layer node stack
let mut last_child_node_id = child_layer_node_id;
let mut last_child_node_id = child_layer.to_node();
loop {
let Some(NodeInput::Node { node_id, .. }) = self.network.nodes.get(&last_child_node_id).expect("Child node should always exist").inputs.get(0) else {
break;
@ -979,35 +1016,32 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
}
// Connect the primary input of the bottom layer of the node to the upstream sibling
let bottom_layer_node_input = NodeInput::node(layer_upstream_sibling_id, 0);
responses.add(NodeGraphMessage::SetNodeInput {
let bottom_layer_node_input = NodeInput::node(upstream_sibling_id, 0);
responses.add(GraphOperationMessage::SetNodeInput {
node_id: last_child_node_id,
input_index: 0,
input: bottom_layer_node_input,
});
// Shift upstream_sibling down by the height of the child layer stack
let top_of_stack = self.network.nodes.get(&child_layer_node_id).expect("Child layer should always exist for child layer id");
let bottom_of_stack = self.network.nodes.get(&last_child_node_id).expect("Last child layer should always exist for last child layer id");
let top_of_stack = self.network.nodes.get(&child_layer.to_node()).expect("Child layer should always exist for child layer id");
let bottom_of_stack = self.network.nodes.get(&child_layer.to_node()).expect("Last child layer should always exist for last child layer id");
let target_distance = bottom_of_stack.metadata.position.y - top_of_stack.metadata.position.y;
let folder_node = self.network.nodes.get(&folder.to_node()).expect("Folder node should always exist");
let upstream_sibling_node = self.network.nodes.get(&layer_upstream_sibling_id).expect("Upstream sibling node should always exist");
let upstream_sibling_node = self.network.nodes.get(&upstream_sibling_id).expect("Upstream sibling node should always exist");
let current_distance = upstream_sibling_node.metadata.position.y - folder_node.metadata.position.y;
let y_offset = target_distance - current_distance + 3;
responses.add(NodeGraphMessage::ShiftUpstream {
node_id: layer_upstream_sibling_id,
responses.add(GraphOperationMessage::ShiftUpstream {
node_id: upstream_sibling_id,
shift: IVec2::new(0, y_offset),
shift_self: true,
});
}
// Delete folder and all horizontal inputs, also deletes node in metadata
responses.add(NodeGraphMessage::DeleteNodes {
node_ids: vec![folder.to_node()],
reconnect: true,
});
responses.add(GraphOperationMessage::DeleteLayer { layer: folder, reconnect: true });
}
responses.add(NodeGraphMessage::RunDocumentGraph);
@ -1093,8 +1127,7 @@ impl DocumentMessageHandler {
pub fn intersect_quad<'a>(&'a self, viewport_quad: graphene_core::renderer::Quad, network: &'a NodeNetwork) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
let document_quad = self.metadata.document_to_viewport.inverse() * viewport_quad;
self.metadata
.root()
.descendants(&self.metadata)
.all_layers()
.filter(|&layer| self.selected_nodes.layer_visible(layer, self.metadata()))
.filter(|&layer| !self.selected_nodes.layer_locked(layer, self.metadata()))
.filter(|&layer| !is_artboard(layer, network))
@ -1107,8 +1140,7 @@ impl DocumentMessageHandler {
pub fn click_xray(&self, viewport_location: DVec2) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
let point = self.metadata.document_to_viewport.inverse().transform_point2(viewport_location);
self.metadata
.root()
.descendants(&self.metadata)
.all_layers()
.filter(|&layer| self.selected_nodes.layer_visible(layer, self.metadata()))
.filter(|&layer| !self.selected_nodes.layer_locked(layer, self.metadata()))
.filter_map(|layer| self.metadata.click_target(layer).map(|targets| (layer, targets)))
@ -1120,7 +1152,14 @@ impl DocumentMessageHandler {
pub fn find_deepest(&self, node_list: &[LayerNodeIdentifier], network: &NodeNetwork) -> Option<LayerNodeIdentifier> {
node_list
.iter()
.find(|&&layer| !network.nodes.get(&layer.to_node()).map(|node| node.layer_has_child_layers(network)).unwrap_or_default())
.find(|&&layer| {
if layer != LayerNodeIdentifier::ROOT_PARENT {
!network.nodes.get(&layer.to_node()).map(|node| node.layer_has_child_layers(network)).unwrap_or_default()
} else {
log::error!("ROOT_PARENT should not exist in find_deepest");
false
}
})
.copied()
}
@ -1135,7 +1174,14 @@ impl DocumentMessageHandler {
node_list.truncate(
node_list
.iter()
.position(|&layer| !network.nodes.get(&layer.to_node()).map(|node| node.layer_has_child_layers(network)).unwrap_or_default())
.position(|&layer| {
if layer != LayerNodeIdentifier::ROOT_PARENT {
!network.nodes.get(&layer.to_node()).map(|node| node.layer_has_child_layers(network)).unwrap_or_default()
} else {
log::error!("ROOT_PARENT should not exist in click_list_any");
false
}
})
.unwrap_or(0) + 1,
);
node_list
@ -1177,6 +1223,21 @@ impl DocumentMessageHandler {
pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {
serde_json::from_str(serialized_content).map_err(|e| EditorError::DocumentDeserialization(e.to_string()))
// TODO: Use this to upgrade demo artwork with outdated document node internals from their definitions. Delete when it's no longer needed.
// Used for upgrading old internal networks for demo artwork nodes. Will reset all node internals for any opened file
// match serde_json::from_str::<Self>(serialized_content).map_err(|e| EditorError::DocumentDeserialization(e.to_string())) {
// Ok(mut document) => {
// for (_, node) in &mut document.network.nodes {
// let node_definition = crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type(&node.name).unwrap();
// let default_definition_node = node_definition.default_document_node();
// node.implementation = default_definition_node.implementation.clone();
// }
// Ok(document)
// }
// Err(e) => Err(e),
// }
}
pub fn with_name(name: String, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) -> Self {
@ -1248,9 +1309,9 @@ impl DocumentMessageHandler {
/// [3427872634365736244,18115028555707261608,449479075714955186]
/// ```
pub fn serialize_root(&self) -> RawBuffer {
let mut structure_section = vec![LayerNodeIdentifier::ROOT.to_node().0];
let mut structure_section = vec![NodeId(0).0];
let mut data_section = Vec::new();
self.serialize_structure(self.metadata().root(), &mut structure_section, &mut data_section, &mut vec![]);
self.serialize_structure(LayerNodeIdentifier::ROOT_PARENT, &mut structure_section, &mut data_section, &mut vec![]);
// Remove the ROOT element. Prepend `L`, the length (excluding the ROOT) of the structure section (which happens to be where the ROOT element was).
structure_section[0] = structure_section.len() as u64 - 1;
@ -1365,7 +1426,9 @@ impl DocumentMessageHandler {
if let Some(previous_sibling) = layer_to_move.previous_sibling(metadata) {
downstream_layer = Some((previous_sibling.to_node(), false))
} else if let Some(parent) = layer_to_move.parent(metadata) {
if parent != LayerNodeIdentifier::ROOT_PARENT {
downstream_layer = Some((parent.to_node(), true))
}
};
// Downstream layer should always exist
@ -1399,15 +1462,6 @@ impl DocumentMessageHandler {
})
}
/// When working with an insert index, deleting the layers may cause the insert index to point to a different location (if the layer being deleted was located before the insert index).
///
/// This function updates the insert index so that it points to the same place after the specified `layers` are deleted.
fn update_insert_index(&self, layers: &[LayerNodeIdentifier], parent: LayerNodeIdentifier, insert_index: isize) -> usize {
let take_amount = if insert_index < 0 { usize::MAX } else { insert_index as usize };
let layer_ids_above = parent.children(self.metadata()).take(take_amount);
layer_ids_above.filter(|layer_id| !layers.contains(layer_id)).count() as usize
}
/// Finds the parent folder which, based on the current selections, should be the container of any newly added layers.
pub fn new_layer_parent(&self, include_self: bool) -> LayerNodeIdentifier {
self.metadata()
@ -1882,15 +1936,15 @@ impl DocumentMessageHandler {
IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24)
.hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into()))
.tooltip(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
.tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedLocked))
.on_update(|_| NodeGraphMessage::ToggleSelectedLocked.into())
.tooltip_shortcut(action_keys!(GraphOperationMessageDiscriminant::ToggleSelectedLocked))
.on_update(|_| GraphOperationMessage::ToggleSelectedLocked.into())
.disabled(!has_selection)
.widget_holder(),
IconButton::new(if selection_all_visible { "EyeVisible" } else { "EyeHidden" }, 24)
.hover_icon(Some((if selection_all_visible { "EyeHide" } else { "EyeShow" }).into()))
.tooltip(if selection_all_visible { "Hide Selected" } else { "Show Selected" })
.tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedVisibility))
.on_update(|_| NodeGraphMessage::ToggleSelectedVisibility.into())
.tooltip_shortcut(action_keys!(GraphOperationMessageDiscriminant::ToggleSelectedVisibility))
.on_update(|_| GraphOperationMessage::ToggleSelectedVisibility.into())
.disabled(!has_selection)
.widget_holder(),
],
@ -1944,54 +1998,10 @@ impl DocumentMessageHandler {
fn root_network() -> NodeNetwork {
{
let mut network = NodeNetwork::default();
let node = graph_craft::document::DocumentNode {
name: "Output".into(),
inputs: vec![NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true), NodeInput::Network(concrete!(WasmEditorApi))],
implementation: graph_craft::document::DocumentNodeImplementation::Network(NodeNetwork {
imports: vec![NodeId(3), NodeId(0)],
exports: vec![NodeOutput::new(NodeId(3), 0)],
nodes: [
DocumentNode {
name: "EditorApi".to_string(),
inputs: vec![NodeInput::Network(concrete!(WasmEditorApi))],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")),
..Default::default()
},
DocumentNode {
name: "Create Canvas".to_string(),
inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")),
skip_deduplication: true,
..Default::default()
},
DocumentNode {
name: "Cache".to_string(),
manual_composition: Some(concrete!(())),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode<_, _>")),
..Default::default()
},
DocumentNode {
name: "RenderNode".to_string(),
inputs: vec![
NodeInput::node(NodeId(0), 0),
NodeInput::Network(graphene_core::Type::Fn(Box::new(concrete!(Footprint)), Box::new(generic!(T)))),
NodeInput::node(NodeId(2), 0),
],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RenderNode<_, _, _>")),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
metadata: DocumentNodeMetadata::position((8, 4)),
..Default::default()
};
network.push_node(node);
network.exports = vec![NodeInput::Value {
tagged_value: TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::EMPTY),
exposed: true,
}];
network
}
}

View file

@ -4,10 +4,8 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
use crate::messages::prelude::*;
use bezier_rs::Subpath;
use graph_craft::document::DocumentNode;
use graph_craft::document::NodeId;
use graphene_core::raster::BlendMode;
use graphene_core::raster::ImageFrame;
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
use graphene_core::raster::{BlendMode, ImageFrame};
use graphene_core::text::Font;
use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::brush_stroke::BrushStroke;
@ -30,17 +28,27 @@ pub enum GraphOperationMessage {
node_id: NodeId,
operation: BooleanOperation,
},
DeleteLayer {
layer: LayerNodeIdentifier,
reconnect: bool,
},
// TODO: Eventually remove this (probably starting late 2024)
DeleteLegacyOutputNode,
DisconnectInput {
node_id: NodeId,
input_index: usize,
},
DisconnectNodeFromStack {
node_id: NodeId,
reconnect_to_sibling: bool,
},
FillSet {
layer: LayerNodeIdentifier,
fill: Fill,
},
InsertLayerAtStackIndex {
layer_id: NodeId,
parent: NodeId,
InsertNodeAtStackIndex {
node_id: NodeId,
parent: LayerNodeIdentifier,
insert_index: usize,
},
InsertBooleanOperation {
@ -59,7 +67,7 @@ pub enum GraphOperationMessage {
pre_node_output_index: usize,
},
MoveSelectedSiblingsToChild {
new_parent: NodeId,
new_parent: LayerNodeIdentifier,
},
OpacitySet {
layer: LayerNodeIdentifier,
@ -146,4 +154,43 @@ pub enum GraphOperationMessage {
parent: LayerNodeIdentifier,
insert_index: isize,
},
ShiftUpstream {
node_id: NodeId,
shift: IVec2,
shift_self: bool,
},
SetNodePosition {
node_id: NodeId,
position: IVec2,
},
SetName {
layer: LayerNodeIdentifier,
name: String,
},
SetNameImpl {
layer: LayerNodeIdentifier,
name: String,
},
SetNodeInput {
node_id: NodeId,
input_index: usize,
input: NodeInput,
},
ToggleSelectedVisibility,
ToggleVisibility {
node_id: NodeId,
},
SetVisibility {
node_id: NodeId,
visible: bool,
},
StartPreviewingWithoutRestore,
ToggleSelectedLocked,
ToggleLocked {
node_id: NodeId,
},
SetLocked {
node_id: NodeId,
locked: bool,
},
}

View file

@ -6,7 +6,7 @@ use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers
use crate::messages::prelude::*;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork};
use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork, Previewing};
use graphene_core::renderer::Quad;
use graphene_core::text::Font;
use graphene_core::vector::style::{Fill, Gradient, GradientType, LineCap, LineJoin, Stroke};
@ -26,6 +26,8 @@ pub struct GraphOperationMessageData<'a> {
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct GraphOperationMessageHandler {}
// GraphOperationMessageHandler always modified the document network. This is so changes to the layers panel will only affect the document network.
// For changes to the selected network, use NodeGraphMessageHandler. No NodeGraphMessage's should be added here, since they will affect the selected nested network.
impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for GraphOperationMessageHandler {
fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, data: GraphOperationMessageData) {
let GraphOperationMessageData {
@ -38,14 +40,20 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
match message {
GraphOperationMessage::AddNodesAsChild { nodes, new_ids, parent, insert_index } => {
let shift = nodes
.get(&NodeId(0))
.and_then(|node| {
let shift = document_network
.get_root_node()
.and_then(|root_node| {
nodes.get(&root_node.id).and_then(|node| {
if parent == LayerNodeIdentifier::ROOT_PARENT {
return None;
};
let parent_node_id = parent.to_node();
document_network
.nodes
.get(&parent.to_node())
.get(&parent_node_id)
.map(|layer| layer.metadata.position - node.metadata.position + IVec2::new(-8, 0))
})
})
.unwrap_or_default();
for (old_id, mut document_node) in nodes {
@ -54,24 +62,25 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
// Get the new, non-conflicting id
let node_id = *new_ids.get(&old_id).unwrap();
document_node = document_node.map_ids(NodeGraphMessageHandler::default_node_input, &new_ids);
let default_inputs = NodeGraphMessageHandler::get_default_inputs(document_network, &Vec::new(), node_id, &node_graph.resolved_types, &document_node);
document_node = document_node.map_ids(default_inputs, &new_ids);
// Insert node into network
document_network.nodes.insert(node_id, document_node);
}
let Some(new_layer_id) = new_ids.get(&NodeId(0)) else {
log::error!("Could not get layer node when adding as child");
error!("Could not get layer node when adding as child");
return;
};
let insert_index = if insert_index < 0 { 0 } else { insert_index as usize };
let (downstream_node, upstream_node, input_index) = ModifyInputsContext::get_post_node_with_index(document_network, parent.to_node(), insert_index);
let (downstream_node, upstream_node, input_index) = ModifyInputsContext::get_post_node_with_index(document_network, parent, insert_index);
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![*new_layer_id] });
if let Some(upstream_node) = upstream_node {
responses.add(GraphOperationMessage::InsertNodeBetween {
match (downstream_node, upstream_node) {
(Some(downstream_node), Some(upstream_node)) => responses.add(GraphOperationMessage::InsertNodeBetween {
post_node_id: downstream_node,
post_node_input_index: input_index,
insert_node_output_index: 0,
@ -79,16 +88,28 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
insert_node_input_index: 0,
pre_node_output_index: 0,
pre_node_id: upstream_node,
})
} else {
responses.add(NodeGraphMessage::SetNodeInput {
}),
(Some(downstream_node), None) => responses.add(GraphOperationMessage::SetNodeInput {
node_id: downstream_node,
input_index: input_index,
input: NodeInput::node(*new_layer_id, 0),
})
}),
(None, Some(upstream_node)) => responses.add(GraphOperationMessage::InsertNodeBetween {
post_node_id: document_network.exports_metadata.0,
post_node_input_index: 0,
insert_node_output_index: 0,
insert_node_id: *new_layer_id,
insert_node_input_index: 0,
pre_node_output_index: 0,
pre_node_id: upstream_node,
}),
(None, None) => {
if let Some(primary_export) = document_network.exports.get_mut(0) {
*primary_export = NodeInput::node(*new_layer_id, 0)
}
responses.add(NodeGraphMessage::ShiftUpstream {
}
};
responses.add(GraphOperationMessage::ShiftUpstream {
node_id: *new_layer_id,
shift: IVec2::new(0, 3),
shift_self: true,
@ -109,44 +130,87 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
);
document_network.nodes.insert(node_id, new_boolean_operation_node);
}
GraphOperationMessage::DeleteLayer { layer, reconnect } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot delete ROOT_PARENT");
return;
}
ModifyInputsContext::delete_nodes(document_network, selected_nodes, vec![layer.to_node()], reconnect, responses, Vec::new(), &node_graph.resolved_types);
load_network_structure(document_network, document_metadata, collapsed);
responses.add(NodeGraphMessage::RunDocumentGraph);
}
// TODO: Eventually remove this (probably starting late 2024)
GraphOperationMessage::DeleteLegacyOutputNode => {
if document_network.nodes.iter().any(|(node_id, node)| node.name == "Output" && *node_id == NodeId(0)) {
ModifyInputsContext::delete_nodes(document_network, selected_nodes, vec![NodeId(0)], true, responses, Vec::new(), &node_graph.resolved_types);
}
}
// Make sure to also update NodeGraphMessage::DisconnectInput when changing this
GraphOperationMessage::DisconnectInput { node_id, input_index } => {
let Some(node_to_disconnect) = document_network.nodes.get(&node_id) else {
warn!("Node {} not found in DisconnectInput", node_id);
return;
};
let Some(node_type) = resolve_document_node_type(&node_to_disconnect.name) else {
warn!("Node {} not in library", node_to_disconnect.name);
return;
};
let Some(existing_input) = node_to_disconnect.inputs.get(input_index) else {
warn!("Node does not have an input at the selected index");
let Some(existing_input) = document_network
.nodes
.get(&node_id)
.map_or_else(|| document_network.exports.get(input_index), |node| node.inputs.get(input_index))
else {
warn!("Could not find input for {node_id} at index {input_index} when disconnecting");
return;
};
let mut input = node_type.inputs[input_index].default.clone();
let tagged_value = TaggedValue::from_type(&ModifyInputsContext::get_input_type(document_network, &Vec::new(), node_id, &node_graph.resolved_types, input_index));
let mut input = NodeInput::value(tagged_value, true);
if let NodeInput::Value { exposed, .. } = &mut input {
*exposed = existing_input.is_exposed();
}
responses.add(NodeGraphMessage::SetNodeInput { node_id, input_index, input });
if node_id == document_network.exports_metadata.0 {
// Since it is only possible to drag the solid line, there must be a root_node_to_restore
if let Previewing::Yes { .. } = document_network.previewing {
responses.add(GraphOperationMessage::StartPreviewingWithoutRestore);
}
// If there is no preview, then disconnect
else {
responses.add(GraphOperationMessage::SetNodeInput { node_id, input_index, input });
}
} else {
responses.add(GraphOperationMessage::SetNodeInput { node_id, input_index, input });
}
if document_network.connected_to_output(node_id) {
responses.add(NodeGraphMessage::RunDocumentGraph);
}
responses.add(NodeGraphMessage::SendGraph);
}
GraphOperationMessage::DisconnectNodeFromStack { node_id, reconnect_to_sibling } => {
ModifyInputsContext::remove_references_from_network(document_network, node_id, reconnect_to_sibling, &Vec::new(), &node_graph.resolved_types);
responses.add(GraphOperationMessage::DisconnectInput { node_id, input_index: 0 });
}
GraphOperationMessage::FillSet { layer, fill } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run FillSet on ROOT_PARENT");
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.fill_set(fill);
}
}
GraphOperationMessage::InsertLayerAtStackIndex { layer_id, parent, insert_index } => {
let (post_node_id, pre_node_id, post_node_input_index) = ModifyInputsContext::get_post_node_with_index(&document_network, parent, insert_index);
GraphOperationMessage::InsertNodeAtStackIndex { node_id, parent, insert_index } => {
let (post_node_id, pre_node_id, post_node_input_index) = ModifyInputsContext::get_post_node_with_index(document_network, parent, insert_index);
// `layer_to_move` should always correspond to a node.
let Some(layer_to_move_node) = document_network.nodes.get(&layer_id) else {
log::error!("Layer node not found when inserting node {} at index {}", layer_id, insert_index);
let Some(layer_to_move_node) = document_network.nodes.get(&node_id) else {
log::error!("Layer node not found when inserting node {} at index {}", node_id, insert_index);
return;
};
// Move current layer to post node.
let post_node = document_network.nodes.get(&post_node_id).expect("Post node id should always refer to a node");
let current_position = layer_to_move_node.metadata.position;
let new_position = post_node.metadata.position;
let new_position = if let Some(post_node_id) = post_node_id {
document_network.nodes.get(&post_node_id).expect("Post node id should always refer to a node").metadata.position
} else if let Some(root_node) = document_network.get_root_node() {
document_network.nodes.get(&root_node.id).expect("Root node id should always refer to a node").metadata.position + IVec2::new(8, -3)
} else {
document_network.exports_metadata.1
};
// If moved to top of a layer stack, move to the left of the post node. If moved within a stack, move directly on the post node. The stack will be shifted down later.
let offset_to_post_node = if insert_index == 0 {
@ -155,34 +219,46 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
new_position - current_position
};
responses.add(NodeGraphMessage::ShiftUpstream {
node_id: layer_id,
responses.add(GraphOperationMessage::ShiftUpstream {
node_id: node_id,
shift: offset_to_post_node,
shift_self: true,
});
// Update post_node input to layer_to_move.
if let Some(upstream_node) = pre_node_id {
responses.add(GraphOperationMessage::InsertNodeBetween {
match (post_node_id, pre_node_id) {
(Some(post_node_id), Some(pre_node_id)) => responses.add(GraphOperationMessage::InsertNodeBetween {
post_node_id: post_node_id,
post_node_input_index: post_node_input_index,
insert_node_output_index: 0,
insert_node_id: layer_id,
insert_node_id: node_id,
insert_node_input_index: 0,
pre_node_output_index: 0,
pre_node_id: upstream_node,
})
} else {
responses.add(NodeGraphMessage::SetNodeInput {
pre_node_id: pre_node_id,
}),
(None, Some(pre_node_id)) => responses.add(GraphOperationMessage::InsertNodeBetween {
post_node_id: document_network.exports_metadata.0,
post_node_input_index: 0,
insert_node_output_index: 0,
insert_node_id: node_id,
insert_node_input_index: 0,
pre_node_output_index: 0,
pre_node_id: pre_node_id,
}),
(Some(post_node_id), None) => responses.add(GraphOperationMessage::SetNodeInput {
node_id: post_node_id,
input_index: post_node_input_index,
input: NodeInput::node(layer_id, 0),
})
input: NodeInput::node(node_id, 0),
}),
(None, None) => {
if let Some(primary_export) = document_network.exports.get_mut(0) {
*primary_export = NodeInput::node(node_id, 0)
}
}
}
// Shift stack down, starting at the moved node.
responses.add(NodeGraphMessage::ShiftUpstream {
node_id: layer_id,
responses.add(GraphOperationMessage::ShiftUpstream {
node_id: node_id,
shift: IVec2::new(0, 3),
shift_self: true,
});
@ -190,16 +266,13 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
GraphOperationMessage::InsertBooleanOperation { operation } => {
let mut selected_layers = selected_nodes.selected_layers(&document_metadata);
let first_selected_layer = selected_layers.next();
let second_selected_layer = selected_layers.next();
let other_selected_layer = selected_layers.next();
let upper_layer = selected_layers.next();
let lower_layer = selected_layers.next();
let (Some(upper_layer), Some(lower_layer), None) = (first_selected_layer, second_selected_layer, other_selected_layer) else {
return;
};
let Some(upper_layer) = upper_layer else { return };
let Some(upper_layer_node) = document_network.nodes.get(&upper_layer.to_node()) else { return };
let Some(lower_layer_node) = document_network.nodes.get(&lower_layer.to_node()) else { return };
let lower_layer_node = lower_layer.and_then(|lower_layer| document_network.nodes.get(&lower_layer.to_node()));
let Some(NodeInput::Node {
node_id: upper_node_id,
@ -209,13 +282,13 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
else {
return;
};
let Some(NodeInput::Node {
let (lower_node_id, lower_output_index) = match lower_layer_node.and_then(|lower_layer_node| lower_layer_node.inputs.get(1).cloned()) {
Some(NodeInput::Node {
node_id: lower_node_id,
output_index: lower_output_index,
..
}) = lower_layer_node.inputs.get(1).cloned()
else {
return;
}) => (Some(lower_node_id), Some(lower_output_index)),
_ => (None, None),
};
let boolean_operation_node_id = NodeId::new();
@ -241,27 +314,32 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
});
// Connect the lower chain to the Boolean Operation node's lower input
responses.add(NodeGraphMessage::SetNodeInput {
if let (Some(lower_layer), Some(lower_node_id), Some(lower_output_index)) = (lower_layer, lower_node_id, lower_output_index) {
responses.add(GraphOperationMessage::SetNodeInput {
node_id: boolean_operation_node_id,
input_index: 1,
input: NodeInput::node(lower_node_id, lower_output_index),
});
// Delete the lower layer (but its chain is kept since it's still used by the Boolean Operation node)
responses.add(DocumentMessage::DeleteLayer { id: lower_layer.to_node() });
responses.add(GraphOperationMessage::DeleteLayer { layer: lower_layer, reconnect: true });
}
// Put the Boolean Operation where the output layer is located, since this is the correct shift relative to its left input chain
responses.add(NodeGraphMessage::SetNodePosition {
responses.add(GraphOperationMessage::SetNodePosition {
node_id: boolean_operation_node_id,
position: upper_layer_node.metadata.position,
});
// After the previous step, the Boolean Operation node is overlapping the upper layer, so we need to shift and its entire chain to the left by its width plus some padding
responses.add(NodeGraphMessage::ShiftUpstream {
responses.add(GraphOperationMessage::ShiftUpstream {
node_id: boolean_operation_node_id,
shift: (-8, 0).into(),
shift_self: true,
})
});
// Re-render
responses.add(NodeGraphMessage::RunDocumentGraph);
}
GraphOperationMessage::InsertNodeBetween {
post_node_id,
@ -272,11 +350,14 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
pre_node_output_index,
pre_node_id,
} => {
let Some(post_node) = document_network.nodes.get(&post_node_id) else {
error!("Post node not found");
return;
};
let Some((post_node_input_index, _)) = post_node.inputs.iter().enumerate().filter(|input| input.1.is_exposed()).nth(post_node_input_index) else {
let post_node = document_network.nodes.get(&post_node_id);
let Some((post_node_input_index, _)) = post_node
.map_or(&document_network.exports, |post_node| &post_node.inputs)
.iter()
.enumerate()
.filter(|input| input.1.is_exposed())
.nth(post_node_input_index)
else {
error!("Failed to find input index {post_node_input_index} on node {post_node_id:#?}");
return;
};
@ -290,35 +371,51 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
};
let post_input = NodeInput::node(insert_node_id, insert_node_output_index);
responses.add(NodeGraphMessage::SetNodeInput {
responses.add(GraphOperationMessage::SetNodeInput {
node_id: post_node_id,
input_index: post_node_input_index,
input: post_input,
});
let insert_input = NodeInput::node(pre_node_id, pre_node_output_index);
responses.add(NodeGraphMessage::SetNodeInput {
responses.add(GraphOperationMessage::SetNodeInput {
node_id: insert_node_id,
input_index: insert_node_input_index,
input: insert_input,
});
}
GraphOperationMessage::OpacitySet { layer, opacity } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run OpacitySet on ROOT_PARENT");
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.opacity_set(opacity);
}
}
GraphOperationMessage::BlendModeSet { layer, blend_mode } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run BlendModeSet on ROOT_PARENT");
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.blend_mode_set(blend_mode);
}
}
GraphOperationMessage::UpdateBounds { layer, old_bounds, new_bounds } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run UpdateBounds on ROOT_PARENT");
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.update_bounds(old_bounds, new_bounds);
}
}
GraphOperationMessage::StrokeSet { layer, stroke } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run StrokeSet on ROOT_PARENT");
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.stroke_set(stroke);
}
@ -329,6 +426,10 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
transform_in,
skip_rerender,
} => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run TransformChange on ROOT_PARENT");
return;
}
let parent_transform = document_metadata.downstream_transform_to_viewport(layer);
let bounds = LayerBounds::new(document_metadata, layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
@ -341,6 +442,10 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
transform_in,
skip_rerender,
} => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run TransformSet on ROOT_PARENT");
return;
}
let parent_transform = document_metadata.downstream_transform_to_viewport(layer);
let current_transform = Some(document_metadata.transform_to_viewport(layer));
@ -350,25 +455,39 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
}
}
GraphOperationMessage::TransformSetPivot { layer, pivot } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run TransformSetPivot on ROOT_PARENT");
return;
}
let bounds = LayerBounds::new(document_metadata, layer);
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.pivot_set(pivot, bounds);
}
}
GraphOperationMessage::Vector { layer, modification } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run Vector on ROOT_PARENT");
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.vector_modify(modification);
let previous_layer = modify_inputs.vector_modify(modification);
if let Some(layer) = previous_layer {
responses.add(GraphOperationMessage::DeleteLayer { layer, reconnect: true })
}
}
}
GraphOperationMessage::Brush { layer, strokes } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run Brush on ROOT_PARENT");
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer.to_node(), document_network, document_metadata, node_graph, responses) {
modify_inputs.brush_modify(strokes);
}
}
GraphOperationMessage::MoveSelectedSiblingsToChild { new_parent } => {
let group_layer = LayerNodeIdentifier::new(new_parent, &document_network);
let Some(group_parent) = group_layer.parent(&document_metadata) else {
log::error!("Could not find parent for layer {:?}", group_layer);
let Some(group_parent) = new_parent.parent(&document_metadata) else {
log::error!("Could not find parent for layer {:?}", new_parent);
return;
};
@ -417,14 +536,14 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
// Start with the furthest upstream node, move it as a child of the new folder, and continue downstream for each layer in vec
for node_to_move in selected_siblings.iter().rev() {
// Connect downstream node to upstream node, or disconnect downstream node if upstream node doesn't exist
responses.add(NodeGraphMessage::DisconnectLayerFromStack {
// Disconnect node, then reconnect as new child
responses.add(GraphOperationMessage::DisconnectNodeFromStack {
node_id: *node_to_move,
reconnect_to_sibling: true,
});
responses.add(GraphOperationMessage::InsertLayerAtStackIndex {
layer_id: *node_to_move,
responses.add(GraphOperationMessage::InsertNodeAtStackIndex {
node_id: *node_to_move,
parent: new_parent,
insert_index: 0,
});
@ -443,7 +562,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
if let Some(artboard_id) = modify_inputs.create_artboard(id, artboard) {
responses.add_front(NodeGraphMessage::SelectedNodesSet { nodes: vec![artboard_id] });
}
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
load_network_structure(document_network, document_metadata, collapsed);
}
GraphOperationMessage::NewBitmapLayer {
id,
@ -489,31 +608,32 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
// Get the new, non-conflicting id
let node_id = *new_ids.get(&old_id).unwrap();
document_node = document_node.map_ids(NodeGraphMessageHandler::default_node_input, &new_ids);
let default_inputs = NodeGraphMessageHandler::get_default_inputs(document_network, &Vec::new(), node_id, &node_graph.resolved_types, &document_node);
document_node = document_node.map_ids(default_inputs, &new_ids);
// Insert node into network
modify_inputs.document_network.nodes.insert(node_id, document_node);
document_network.nodes.insert(node_id, document_node);
}
if let Some(layer_node) = modify_inputs.document_network.nodes.get_mut(&layer) {
if let Some(layer_node) = document_network.nodes.get_mut(&layer) {
if let Some(&input) = new_ids.get(&NodeId(0)) {
layer_node.inputs[1] = NodeInput::node(input, 0);
}
}
modify_inputs.responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::RunDocumentGraph);
} else {
error!("Creating new custom layer failed");
}
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
load_network_structure(document_network, document_metadata, collapsed);
}
GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index } => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer_with_insert_index(id, insert_index, parent) {
modify_inputs.insert_vector_data(subpaths, layer);
}
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
load_network_structure(document_network, document_metadata, collapsed);
}
GraphOperationMessage::NewTextLayer {
id,
@ -527,7 +647,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
if let Some(layer) = modify_inputs.create_layer_with_insert_index(id, insert_index, parent) {
modify_inputs.insert_text(text, font, size, layer);
}
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
load_network_structure(document_network, document_metadata, collapsed);
}
GraphOperationMessage::ResizeArtboard { id, location, dimensions } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(id, document_network, document_metadata, node_graph, responses) {
@ -535,21 +655,10 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
}
}
GraphOperationMessage::ClearArtboards => {
let modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
let artboard_nodes = modify_inputs
.document_network
.nodes
.iter()
.filter(|(_, node)| node.is_artboard())
.map(|(id, _)| *id)
.collect::<Vec<_>>();
for artboard in artboard_nodes {
responses.add(NodeGraphMessage::DeleteNodes {
node_ids: vec![artboard],
reconnect: true,
});
for &artboard in document_metadata.all_artboards() {
responses.add(GraphOperationMessage::DeleteLayer { layer: artboard, reconnect: true });
}
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
load_network_structure(document_network, document_metadata, collapsed);
}
GraphOperationMessage::NewSvg {
id,
@ -572,7 +681,97 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses);
import_usvg_node(&mut modify_inputs, &usvg::Node::Group(Box::new(tree.root)), transform, id, parent, insert_index);
load_network_structure(document_network, document_metadata, selected_nodes, collapsed);
load_network_structure(document_network, document_metadata, collapsed);
}
GraphOperationMessage::SetNodePosition { node_id, position } => {
let Some(node) = document_network.nodes.get_mut(&node_id) else {
log::error!("Failed to find node {node_id} when setting position");
return;
};
node.metadata.position = position;
}
GraphOperationMessage::SetName { layer, name } => {
responses.add(DocumentMessage::StartTransaction);
responses.add(GraphOperationMessage::SetNameImpl { layer, name });
}
GraphOperationMessage::SetNameImpl { layer, name } => {
let Some(node) = document_network.nodes.get_mut(&layer.to_node()) else { return };
node.alias = name;
responses.add(NodeGraphMessage::SendGraph);
}
GraphOperationMessage::SetNodeInput { node_id, input_index, input } => {
if ModifyInputsContext::set_input(document_network, node_id, input_index, input, true) {
load_network_structure(document_network, document_metadata, collapsed);
}
}
GraphOperationMessage::ShiftUpstream { node_id, shift, shift_self } => {
ModifyInputsContext::shift_upstream(document_network, node_id, shift, shift_self);
}
GraphOperationMessage::ToggleSelectedVisibility => {
responses.add(DocumentMessage::StartTransaction);
// If any of the selected nodes are hidden, show them all. Otherwise, hide them all.
let visible = !selected_nodes.selected_layers(&document_metadata).all(|layer| document_metadata.node_is_visible(layer.to_node()));
for layer in selected_nodes.selected_layers(&document_metadata) {
responses.add(GraphOperationMessage::SetVisibility { node_id: layer.to_node(), visible });
}
}
GraphOperationMessage::ToggleVisibility { node_id } => {
let visible = !document_metadata.node_is_visible(node_id);
responses.add(DocumentMessage::StartTransaction);
responses.add(GraphOperationMessage::SetVisibility { node_id, visible });
}
GraphOperationMessage::SetVisibility { node_id, visible } => {
// Set what we determined shall be the visibility of the node
let Some(node) = document_network.nodes.get_mut(&node_id) else {
log::error!("Could not get node {:?} in GraphOperationMessage::SetVisibility", node_id);
return;
};
node.visible = visible;
// Only generate node graph if one of the selected nodes is connected to the output
if document_network.connected_to_output(node_id) {
responses.add(NodeGraphMessage::RunDocumentGraph);
}
document_metadata.load_structure(document_network);
responses.add(NodeGraphMessage::SelectedNodesUpdated);
responses.add(PropertiesPanelMessage::Refresh);
}
GraphOperationMessage::StartPreviewingWithoutRestore => {
document_network.start_previewing_without_restore();
}
GraphOperationMessage::ToggleSelectedLocked => {
responses.add(DocumentMessage::StartTransaction);
// If any of the selected nodes are hidden, show them all. Otherwise, hide them all.
let visible = !selected_nodes.selected_layers(&document_metadata).all(|layer| document_metadata.node_is_locked(layer.to_node()));
for layer in selected_nodes.selected_layers(&document_metadata) {
responses.add(GraphOperationMessage::SetVisibility { node_id: layer.to_node(), visible });
}
}
GraphOperationMessage::ToggleLocked { node_id } => {
let Some(node) = document_network.nodes.get(&node_id) else {
log::error!("Cannot get node {:?} in GraphOperationMessage::ToggleLocked", node_id);
return;
};
let locked = !node.locked;
responses.add(DocumentMessage::StartTransaction);
responses.add(GraphOperationMessage::SetLocked { node_id, locked });
}
GraphOperationMessage::SetLocked { node_id, locked } => {
let Some(node) = document_network.nodes.get_mut(&node_id) else { return };
node.locked = locked;
if document_network.connected_to_output(node_id) {
responses.add(NodeGraphMessage::RunDocumentGraph);
}
document_metadata.load_structure(document_network);
responses.add(NodeGraphMessage::SelectedNodesUpdated)
}
}
}
@ -582,8 +781,8 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
}
}
pub fn load_network_structure(document_network: &NodeNetwork, document_metadata: &mut DocumentMetadata, selected_nodes: &mut SelectedNodes, collapsed: &mut CollapsedLayers) {
document_metadata.load_structure(document_network, selected_nodes);
pub fn load_network_structure(document_network: &NodeNetwork, document_metadata: &mut DocumentMetadata, collapsed: &mut CollapsedLayers) {
document_metadata.load_structure(document_network);
collapsed.0.retain(|&layer| document_metadata.layer_exists(layer));
}
@ -637,7 +836,7 @@ fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node,
warn!("Skip image")
}
usvg::Node::Text(text) => {
let font = Font::new(crate::consts::DEFAULT_FONT_FAMILY.to_string(), crate::consts::DEFAULT_FONT_STYLE.to_string());
let font = Font::new(graphene_core::consts::DEFAULT_FONT_FAMILY.to_string(), graphene_core::consts::DEFAULT_FONT_STYLE.to_string());
modify_inputs.insert_text(text.chunks.iter().map(|chunk| chunk.text.clone()).collect(), font, 24., layer);
modify_inputs.fill_set(Fill::Solid(Color::BLACK));
}

View file

@ -1,21 +1,26 @@
use super::transform_utils::{self, LayerBounds};
use crate::messages::portfolio::document::node_graph::document_node_types::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
use crate::messages::prelude::*;
use bezier_rs::Subpath;
use graph_craft::concrete;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, DocumentNode, NodeId, NodeInput, NodeNetwork};
use graph_craft::document::{generate_uuid, DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, Previewing};
use graphene_core::raster::{BlendMode, ImageFrame};
use graphene_core::text::Font;
use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::brush_stroke::BrushStroke;
use graphene_core::vector::style::{Fill, FillType, Stroke};
use graphene_core::Type;
use graphene_core::{Artboard, Color};
use graphene_std::vector::ManipulatorPointId;
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
use interpreted_executor::node_registry::NODE_REGISTRY;
use glam::{DAffine2, DVec2, IVec2};
use super::transform_utils::{self, LayerBounds};
use std::hash::{DefaultHasher, Hash, Hasher};
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
pub enum TransformIn {
@ -40,12 +45,14 @@ pub enum VectorDataModification {
UpdateSubpaths { subpaths: Vec<Subpath<ManipulatorGroupId>> },
}
// TODO: Generalize for any network, rewrite as static functions since there only a few fields are used for each function, so when calling only the necessary data will be provided
/// NodeGraphMessage or GraphOperationMessage cannot be added in ModifyInputsContext, since the functions are called by both messages handlers
pub struct ModifyInputsContext<'a> {
pub document_metadata: &'a mut DocumentMetadata,
pub document_network: &'a mut NodeNetwork,
pub node_graph: &'a mut NodeGraphMessageHandler,
pub responses: &'a mut VecDeque<Message>,
pub outwards_links: HashMap<NodeId, Vec<NodeId>>,
pub outwards_wires: HashMap<NodeId, Vec<NodeId>>,
pub layer_node: Option<NodeId>,
}
@ -53,7 +60,7 @@ impl<'a> ModifyInputsContext<'a> {
/// Get the node network from the document
pub fn new(document_network: &'a mut NodeNetwork, document_metadata: &'a mut DocumentMetadata, node_graph: &'a mut NodeGraphMessageHandler, responses: &'a mut VecDeque<Message>) -> Self {
Self {
outwards_links: document_network.collect_outwards_links(),
outwards_wires: document_network.collect_outwards_wires(),
document_network,
node_graph,
responses,
@ -73,7 +80,7 @@ impl<'a> ModifyInputsContext<'a> {
let mut id = id;
while !document.document_network.nodes.get(&id)?.is_layer {
id = document.outwards_links.get(&id)?.first().copied()?;
id = document.outwards_wires.get(&id)?.first().copied()?;
}
document.layer_node = Some(id);
@ -107,7 +114,7 @@ impl<'a> ModifyInputsContext<'a> {
self.document_network.nodes.insert(id, new_node);
self.shift_upstream(id, shift_upstream, false);
ModifyInputsContext::shift_upstream(self.document_network, id, shift_upstream, false);
Some(id)
}
@ -123,6 +130,32 @@ impl<'a> ModifyInputsContext<'a> {
Some(new_id)
}
/// Inserts a node as an export. If there is already a root node connected to the export, that node will be connected to the new node at node_input_index
pub fn insert_node_as_primary_export(document_network: &mut NodeNetwork, id: NodeId, mut new_node: DocumentNode) -> Option<NodeId> {
assert!(!document_network.nodes.contains_key(&id), "Creating already existing node");
if let Some(root_node) = document_network.get_root_node() {
let previous_root_node = document_network.nodes.get_mut(&root_node.id).expect("Root node should always exist");
// Insert whatever non artboard node previously fed into export as a child of the new node
let node_input_index = if new_node.is_artboard() && !previous_root_node.is_artboard() { 1 } else { 0 };
new_node.inputs[node_input_index] = NodeInput::node(root_node.id, root_node.output_index);
ModifyInputsContext::shift_upstream(document_network, root_node.id, IVec2::new(8, 0), true);
}
let Some(export) = document_network.exports.get_mut(0) else {
log::error!("Could not get primary export when adding node");
return None;
};
*export = NodeInput::node(id, 0);
document_network.nodes.insert(id, new_node);
ModifyInputsContext::shift_upstream(document_network, id, IVec2::new(-8, 3), false);
Some(id)
}
/// Starts at any folder, or the output, and skips layer nodes based on insert_index. Non layer nodes are always skipped. Returns the post node id, pre node id, and the input index.
/// -----> Post node input_index: 0
/// | if skip_layer_nodes == 0, return (Post node, Some(Layer1), 1)
@ -134,11 +167,27 @@ impl<'a> ModifyInputsContext<'a> {
/// ↑ if skip_layer_nodes == 2, return (NonLayerNode, Some(Layer3), 0)
/// -> Layer3 input_index: 3
/// if skip_layer_nodes == 3, return (Layer3, None, 0)
pub fn get_post_node_with_index(network: &NodeNetwork, mut post_node_id: NodeId, insert_index: usize) -> (NodeId, Option<NodeId>, usize) {
let mut post_node_input_index = if post_node_id == NodeId(0) { 0 } else { 1 };
pub fn get_post_node_with_index(network: &NodeNetwork, parent: LayerNodeIdentifier, insert_index: usize) -> (Option<NodeId>, Option<NodeId>, usize) {
let post_node_information = if parent != LayerNodeIdentifier::ROOT_PARENT {
Some((parent.to_node(), 1))
} else {
network.get_root_node().map(|root_node| (root_node.id, 0))
};
let Some((mut post_node_id, mut post_node_input_index)) = post_node_information else {
return (None, None, 0);
};
// Skip layers based on skip_layer_nodes, which inserts the new layer at a certain index of the layer stack.
let mut current_index = 0;
if parent == LayerNodeIdentifier::ROOT_PARENT {
if insert_index == 0 {
return (None, Some(post_node_id), 0);
}
current_index += 1;
}
loop {
if current_index == insert_index {
break;
@ -191,28 +240,28 @@ impl<'a> ModifyInputsContext<'a> {
}
}
(post_node_id, pre_node_id, post_node_input_index)
(Some(post_node_id), pre_node_id, post_node_input_index)
}
pub fn create_layer(&mut self, new_id: NodeId, output_node_id: NodeId, skip_layer_nodes: usize) -> Option<NodeId> {
pub fn create_layer(&mut self, new_id: NodeId, parent: LayerNodeIdentifier, skip_layer_nodes: usize) -> Option<NodeId> {
assert!(!self.document_network.nodes.contains_key(&new_id), "Creating already existing layer");
// Get the node which the new layer will output to (post node). First check if the output_node_id is the Output node, and set the output_node_id to the top-most artboard,
// if there is one. Then skip layers based on skip_layer_nodes from the post_node.
// TODO: Smarter placement of layers into artboards https://github.com/GraphiteEditor/Graphite/issues/1507
let mut post_node_id = output_node_id;
if post_node_id == NodeId(0) {
// Check if an artboard is connected, and switch post node to the artboard.
if let Some(NodeInput::Node { node_id, .. }) = &self.document_network.nodes.get(&post_node_id).expect("Output node should always exist").inputs.get(0) {
let input_node = self.document_network.nodes.get(&node_id).expect("First input node should exist");
if input_node.is_artboard() {
post_node_id = *node_id;
let mut parent = parent;
if parent == LayerNodeIdentifier::ROOT_PARENT {
if let Some(root_node) = self.document_network.get_root_node() {
// If the current root node is the artboard, then the new layer should be a child of the artboard
let current_root_node = self.document_network.nodes.get(&root_node.id).expect("Root node should always exist");
if current_root_node.is_artboard() && current_root_node.is_layer {
parent = LayerNodeIdentifier::new(root_node.id, &self.document_network);
}
}
}
let (post_node_id, pre_node_id, post_node_input_index) = Self::get_post_node_with_index(self.document_network, post_node_id, skip_layer_nodes);
let new_layer_node = resolve_document_node_type("Merge").expect("Merge node").default_document_node();
let (post_node_id, pre_node_id, post_node_input_index) = Self::get_post_node_with_index(self.document_network, parent, skip_layer_nodes);
if let Some(post_node_id) = post_node_id {
if let Some(pre_node_id) = pre_node_id {
self.insert_between(
new_id,
@ -227,6 +276,10 @@ impl<'a> ModifyInputsContext<'a> {
} else {
let offset = if post_node_input_index == 1 { IVec2::new(-8, 3) } else { IVec2::new(0, 3) };
self.insert_node_before(new_id, post_node_id, post_node_input_index, new_layer_node, offset);
};
} else {
// If post_node does not exist, then network is empty
ModifyInputsContext::insert_node_as_primary_export(self.document_network, new_id, new_layer_node);
}
Some(new_id)
@ -234,19 +287,11 @@ impl<'a> ModifyInputsContext<'a> {
pub fn create_layer_with_insert_index(&mut self, new_id: NodeId, insert_index: isize, parent: LayerNodeIdentifier) -> Option<NodeId> {
let skip_layer_nodes = if insert_index < 0 { (-1 - insert_index) as usize } else { insert_index as usize };
let output_node_id = if parent == LayerNodeIdentifier::ROOT {
self.document_network.original_outputs()[0].node_id
} else {
parent.to_node()
};
self.create_layer(new_id, output_node_id, skip_layer_nodes)
self.create_layer(new_id, parent, skip_layer_nodes)
}
/// Creates an artboard that outputs to the output node.
pub fn create_artboard(&mut self, new_id: NodeId, artboard: Artboard) -> Option<NodeId> {
let output_node_id = self.document_network.original_outputs()[0].node_id;
let artboard_node = resolve_document_node_type("Artboard").expect("Node").to_document_node_default_inputs(
[
Some(NodeInput::value(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroup::EMPTY), true)),
@ -259,33 +304,7 @@ impl<'a> ModifyInputsContext<'a> {
Default::default(),
);
// Get node that feeds into output. If it exists, connect the new artboard node in between. Else connect the new artboard directly to output.
let output_node_primary_input = self.document_network.nodes.get(&output_node_id)?.inputs.get(0);
let created_node_id = if let NodeInput::Node { node_id, .. } = &output_node_primary_input? {
let pre_node = self.document_network.nodes.get(node_id)?;
// If the node currently connected the Output is an artboard, connect to input 0 (Artboards input) of the new artboard. Else connect to the Over input.
let artboard_input_index = if pre_node.is_artboard() { 0 } else { 1 };
self.insert_between(
new_id,
artboard_node,
NodeInput::node(*node_id, 0),
artboard_input_index,
output_node_id,
NodeInput::node(new_id, 0),
0,
IVec2::new(0, 3),
)
} else {
self.insert_node_before(new_id, output_node_id, 0, artboard_node, IVec2::new(-8, 3))
};
if let Some(new_id) = created_node_id {
let new_child = LayerNodeIdentifier::new_unchecked(new_id);
LayerNodeIdentifier::ROOT.push_front_child(self.document_metadata, new_child);
}
created_node_id
ModifyInputsContext::insert_node_as_primary_export(self.document_network, new_id, artboard_node)
}
pub fn insert_vector_data(&mut self, subpaths: Vec<Subpath<ManipulatorGroupId>>, layer: NodeId) {
let shape = {
@ -310,7 +329,7 @@ impl<'a> ModifyInputsContext<'a> {
pub fn insert_text(&mut self, text: String, font: Font, size: f64, layer: NodeId) {
let text = resolve_document_node_type("Text").expect("Text node does not exist").to_document_node(
[
NodeInput::Network(graph_craft::concrete!(graphene_std::wasm_application_io::WasmEditorApi)),
NodeInput::network(graph_craft::concrete!(graphene_std::wasm_application_io::WasmEditorApi), 0),
NodeInput::value(TaggedValue::String(text), false),
NodeInput::value(TaggedValue::Font(font), false),
NodeInput::value(TaggedValue::F64(size), false),
@ -348,7 +367,7 @@ impl<'a> ModifyInputsContext<'a> {
self.responses.add(NodeGraphMessage::RunDocumentGraph);
}
pub fn shift_upstream(&mut self, node_id: NodeId, shift: IVec2, shift_self: bool) {
pub fn shift_upstream(network: &mut NodeNetwork, node_id: NodeId, shift: IVec2, shift_self: bool) {
let mut shift_nodes = HashSet::new();
if shift_self {
shift_nodes.insert(node_id);
@ -356,7 +375,7 @@ impl<'a> ModifyInputsContext<'a> {
let mut stack = vec![node_id];
while let Some(node_id) = stack.pop() {
let Some(node) = self.document_network.nodes.get(&node_id) else { continue };
let Some(node) = network.nodes.get(&node_id) else { continue };
for input in &node.inputs {
let NodeInput::Node { node_id, .. } = input else { continue };
if shift_nodes.insert(*node_id) {
@ -366,7 +385,7 @@ impl<'a> ModifyInputsContext<'a> {
}
for node_id in shift_nodes {
if let Some(node) = self.document_network.nodes.get_mut(&node_id) {
if let Some(node) = network.nodes.get_mut(&node_id) {
node.metadata.position += shift;
}
}
@ -374,7 +393,19 @@ impl<'a> ModifyInputsContext<'a> {
/// Inserts a new node and modifies the inputs
pub fn modify_new_node(&mut self, name: &'static str, update_input: impl FnOnce(&mut Vec<NodeInput>, NodeId, &DocumentMetadata)) {
let output_node_id = self.layer_node.unwrap_or(self.document_network.exports[0].node_id);
let output_node_id = self.layer_node.or_else(|| {
if let Some(NodeInput::Node { node_id, .. }) = self.document_network.exports.get(0) {
Some(*node_id)
} else {
log::error!("Could not modify new node with empty network");
None
}
});
let Some(output_node_id) = output_node_id else {
warn!("Output node id doesn't exist");
return;
};
let Some(output_node) = self.document_network.nodes.get_mut(&output_node_id) else {
warn!("Output node doesn't exist");
return;
@ -411,8 +442,16 @@ impl<'a> ModifyInputsContext<'a> {
let existing_node_id = self
.document_network
.upstream_flow_back_from_nodes(
self.layer_node
.map_or_else(|| self.document_network.exports.iter().map(|output| output.node_id).collect(), |id| vec![id]),
self.layer_node.map_or_else(
|| {
self.document_network
.exports
.iter()
.filter_map(|output| if let NodeInput::Node { node_id, .. } = output { Some(*node_id) } else { None })
.collect()
},
|id| vec![id],
),
graph_craft::document::FlowType::HorizontalFlow,
)
.find(|(node, _)| node.name == name)
@ -423,7 +462,6 @@ impl<'a> ModifyInputsContext<'a> {
self.modify_new_node(name, update_input);
}
self.node_graph.network.clear();
self.responses.add(PropertiesPanelMessage::Refresh);
if !skip_rerender {
@ -436,8 +474,16 @@ impl<'a> ModifyInputsContext<'a> {
let existing_nodes: Vec<_> = self
.document_network
.upstream_flow_back_from_nodes(
self.layer_node
.map_or_else(|| self.document_network.exports.iter().map(|output| output.node_id).collect(), |id| vec![id]),
self.layer_node.map_or_else(
|| {
self.document_network
.exports
.iter()
.filter_map(|output| if let NodeInput::Node { node_id, .. } = output { Some(node_id.clone()) } else { None })
.collect()
},
|id| vec![id],
),
graph_craft::document::FlowType::HorizontalFlow,
)
.filter(|(node, _)| node.name == name)
@ -456,6 +502,41 @@ impl<'a> ModifyInputsContext<'a> {
}
}
/// Returns true if the network structure is updated
pub fn set_input(network: &mut NodeNetwork, node_id: NodeId, input_index: usize, input: NodeInput, is_document_network: bool) -> bool {
if let Some(node) = network.nodes.get_mut(&node_id) {
let Some(node_input) = node.inputs.get_mut(input_index) else {
log::error!("Tried to set input {input_index} to {input:?}, but the index was invalid. Node {node_id}:\n{node:#?}");
return false;
};
let structure_changed = node_input.as_node().is_some() || input.as_node().is_some();
*node_input = input;
// Only load network structure for changes to document_network
structure_changed && is_document_network
} else if node_id == network.exports_metadata.0 {
let Some(export) = network.exports.get_mut(input_index) else {
log::error!("Tried to set export {input_index} to {input:?}, but the index was invalid. Network:\n{network:#?}");
return false;
};
*export = input;
if let NodeInput::Node { node_id, output_index, .. } = *export {
network.update_root_node(node_id, output_index);
} else if let NodeInput::Value { .. } = *export {
if input_index == 0 {
network.stop_preview();
}
} else {
log::error!("Network export input not supported");
}
// Only load network structure for changes to document_network
is_document_network
} else {
false
}
}
pub fn fill_set(&mut self, fill: Fill) {
self.modify_inputs("Fill", false, |inputs, _node_id, _metadata| {
let fill_type = match fill {
@ -566,7 +647,7 @@ impl<'a> ModifyInputsContext<'a> {
});
}
pub fn vector_modify(&mut self, modification: VectorDataModification) {
pub fn vector_modify(&mut self, modification: VectorDataModification) -> Option<LayerNodeIdentifier> {
let [mut old_bounds_min, mut old_bounds_max] = [DVec2::ZERO, DVec2::ONE];
let [mut new_bounds_min, mut new_bounds_max] = [DVec2::ZERO, DVec2::ONE];
let mut empty = false;
@ -600,10 +681,11 @@ impl<'a> ModifyInputsContext<'a> {
});
self.update_bounds([old_bounds_min, old_bounds_max], [new_bounds_min, new_bounds_max]);
if empty {
if let Some(id) = self.layer_node {
self.responses.add(DocumentMessage::DeleteLayer { id })
}
self.layer_node.map(|layer_id| LayerNodeIdentifier::new(layer_id, &self.document_network))
} else {
None
}
}
@ -620,15 +702,341 @@ impl<'a> ModifyInputsContext<'a> {
if dimensions.x < 0 {
dimensions.x *= -1;
location.x += dimensions.x;
location.x -= dimensions.x;
}
if dimensions.y < 0 {
dimensions.y *= -1;
location.y += dimensions.y;
location.y -= dimensions.y;
}
inputs[2] = NodeInput::value(TaggedValue::IVec2(location), false);
inputs[3] = NodeInput::value(TaggedValue::IVec2(dimensions), false);
});
}
/// Deletes all nodes in `node_ids` and any sole dependents in the horizontal chain if the node to delete is a layer node.
pub fn delete_nodes(
document_network: &mut NodeNetwork,
selected_nodes: &mut SelectedNodes,
node_ids: Vec<NodeId>,
reconnect: bool,
responses: &mut VecDeque<Message>,
network_path: Vec<NodeId>,
resolved_types: &ResolvedDocumentNodeTypes,
) {
let Some(network) = document_network.nested_network_for_selected_nodes(&network_path, selected_nodes.selected_nodes_ref().iter()) else {
return;
};
let mut delete_nodes = HashSet::new();
for node_id in &node_ids {
delete_nodes.insert(*node_id);
if !reconnect {
continue;
};
let Some(node) = network.nodes.get(&node_id) else {
continue;
};
let child_id = node.inputs.get(1).and_then(|input| if let NodeInput::Node { node_id, .. } = input { Some(node_id) } else { None });
let Some(child_id) = child_id else {
continue;
};
let outward_wires = network.collect_outwards_wires();
for (_, upstream_id) in network.upstream_flow_back_from_nodes(vec![*child_id], graph_craft::document::FlowType::UpstreamFlow) {
// This does a downstream traversal starting from the current node, and ending at either a node in the `delete_nodes` set or the output.
// If the traversal find as child node of a node in the `delete_nodes` set, then it is a sole dependent. If the output node is eventually reached, then it is not a sole dependent.
let mut stack = vec![upstream_id];
let mut can_delete = true;
while let Some(current_node) = stack.pop() {
let Some(downstream_nodes) = outward_wires.get(&current_node) else { continue };
for downstream_node in downstream_nodes {
// If the traversal reaches the root node, and the root node should not be deleted, then the current node is not a sole dependent
if network
.get_root_node()
.is_some_and(|root_node| root_node.id == *downstream_node && !delete_nodes.contains(&root_node.id))
{
can_delete = false;
} else if !delete_nodes.contains(downstream_node) {
stack.push(*downstream_node);
}
// Continue traversing over the downstream sibling, which happens if the current node is a sibling to a node in node_ids
else {
for deleted_node_id in &node_ids {
let Some(output_node) = network.nodes.get(&deleted_node_id) else { continue };
let Some(input) = output_node.inputs.get(0) else { continue };
if let NodeInput::Node { node_id, .. } = input {
if *node_id == current_node {
stack.push(*deleted_node_id);
}
}
}
}
}
}
if can_delete {
delete_nodes.insert(upstream_id);
}
}
}
let network_path = if selected_nodes
.selected_nodes_ref()
.iter()
.any(|node_id| document_network.nodes.contains_key(node_id) || document_network.exports_metadata.0 == *node_id || document_network.imports_metadata.0 == *node_id)
{
Vec::new()
} else {
network_path.clone()
};
selected_nodes.add_selected_nodes(delete_nodes.iter().cloned().collect(), document_network, &network_path);
for delete_node_id in delete_nodes {
ModifyInputsContext::remove_node(document_network, selected_nodes, delete_node_id, reconnect, responses, &network_path, resolved_types);
}
}
/// Tries to remove a node from the network, returning `true` on success.
fn remove_node(
document_network: &mut NodeNetwork,
selected_nodes: &mut SelectedNodes,
node_id: NodeId,
reconnect: bool,
responses: &mut VecDeque<Message>,
network_path: &Vec<NodeId>,
resolved_types: &ResolvedDocumentNodeTypes,
) -> bool {
if !ModifyInputsContext::remove_references_from_network(document_network, node_id, reconnect, &network_path, resolved_types) {
log::error!("could not remove_references_from_network");
return false;
}
let Some(network) = document_network.nested_network_mut(&network_path) else { return false };
network.nodes.remove(&node_id);
selected_nodes.retain_selected_nodes(|&id| id != node_id || id == network.exports_metadata.0 || id == network.imports_metadata.0);
responses.add(BroadcastEvent::SelectionChanged);
true
}
pub fn remove_references_from_network(
document_network: &mut NodeNetwork,
deleting_node_id: NodeId,
reconnect: bool,
network_path: &Vec<NodeId>,
resolved_types: &ResolvedDocumentNodeTypes,
) -> bool {
let Some(network) = document_network.nested_network(network_path) else { return false };
let mut reconnect_to_input: Option<NodeInput> = None;
if reconnect {
// Check whether the being-deleted node's first (primary) input is a node
if let Some(node) = network.nodes.get(&deleting_node_id) {
// Reconnect to the node below when deleting a layer node.
if matches!(&node.inputs.get(0), Some(NodeInput::Node { .. })) || matches!(&node.inputs.get(0), Some(NodeInput::Network { .. })) {
reconnect_to_input = Some(node.inputs[0].clone());
}
}
}
let mut nodes_to_set_input = Vec::new();
// Boolean flag if the downstream input can be reconnected to the upstream node
let mut can_reconnect = true;
for (node_id, input_index, input) in network
.nodes
.iter()
.filter_map(|(node_id, node)| {
if *node_id == deleting_node_id {
None
} else {
Some(node.inputs.iter().enumerate().map(|(index, input)| (*node_id, index, input)))
}
})
.flatten()
.chain(network.exports.iter().enumerate().map(|(index, input)| (network.exports_metadata.0, index, input)))
{
let NodeInput::Node { node_id: upstream_node_id, .. } = input else { continue };
if *upstream_node_id != deleting_node_id {
continue;
}
// Do not reconnect export to import until (#1762) is solved
if node_id == network.exports_metadata.0 && reconnect_to_input.as_ref().is_some_and(|reconnect| matches!(reconnect, NodeInput::Network { .. })) {
can_reconnect = false;
}
// Do not reconnect to EditorApi network input in the document network.
if network_path.is_empty() && reconnect_to_input.as_ref().is_some_and(|reconnect| matches!(reconnect, NodeInput::Network { .. })) {
can_reconnect = false;
}
// Only reconnect if the output index for the node to be deleted is 0
if can_reconnect && reconnect_to_input.is_some() {
// None means to use reconnect_to_input, which can be safely unwrapped
nodes_to_set_input.push((node_id, input_index, None));
// Only one node can be reconnected
can_reconnect = false;
} else {
// Disconnect input
let tagged_value = TaggedValue::from_type(&ModifyInputsContext::get_input_type(document_network, network_path, node_id, resolved_types, input_index));
let value_input = NodeInput::value(tagged_value, true);
nodes_to_set_input.push((node_id, input_index, Some(value_input)));
}
}
let Some(network) = document_network.nested_network_mut(network_path) else { return false };
if let Previewing::Yes { root_node_to_restore } = network.previewing {
if let Some(root_node_to_restore) = root_node_to_restore {
if root_node_to_restore.id == deleting_node_id {
network.start_previewing_without_restore();
}
}
}
let is_document_network = network_path.is_empty();
for (node_id, input_index, value_input) in nodes_to_set_input {
if let Some(value_input) = value_input {
// Disconnect input to root node only if not previewing
if node_id != network.exports_metadata.0 || matches!(&network.previewing, Previewing::No) {
ModifyInputsContext::set_input(network, node_id, input_index, value_input, is_document_network);
} else if let Previewing::Yes { root_node_to_restore } = network.previewing {
if let Some(root_node) = root_node_to_restore {
if node_id == root_node.id {
network.start_previewing_without_restore();
} else {
ModifyInputsContext::set_input(network, node_id, input_index, NodeInput::node(root_node.id, root_node.output_index), is_document_network);
}
} else {
ModifyInputsContext::set_input(network, node_id, input_index, value_input, is_document_network);
}
}
}
// Reconnect to node upstream of the deleted node
else if node_id != network.exports_metadata.0 || matches!(network.previewing, Previewing::No) {
if let Some(reconnect_to_input) = reconnect_to_input.clone() {
ModifyInputsContext::set_input(network, node_id, input_index, reconnect_to_input, is_document_network);
}
}
// Reconnect previous root node to the export, or disconnect export
else if let Previewing::Yes { root_node_to_restore } = network.previewing {
if let Some(root_node) = root_node_to_restore {
ModifyInputsContext::set_input(network, node_id, input_index, NodeInput::node(root_node.id, root_node.output_index), is_document_network);
} else if let Some(reconnect_to_input) = reconnect_to_input.clone() {
ModifyInputsContext::set_input(network, node_id, input_index, reconnect_to_input, is_document_network);
network.start_previewing_without_restore();
}
}
}
true
}
/// Get the [`Type`] for any `node_i`d and `input_index`. The `network_path` is the path to the encapsulating node (including the encapsulating node). The `node_id` is the selected node.
pub fn get_input_type(document_network: &NodeNetwork, network_path: &Vec<NodeId>, node_id: NodeId, resolved_types: &ResolvedDocumentNodeTypes, input_index: usize) -> Type {
let Some(network) = document_network.nested_network(&network_path) else {
log::error!("Could not get network in get_tagged_value");
return concrete!(());
};
// TODO: Store types for all document nodes, not just the compiled proto nodes, which currently skips isolated nodes
let node_id_path = &[&network_path[..], &[node_id]].concat();
let input_type = resolved_types.inputs.get(&graph_craft::document::Source {
node: node_id_path.clone(),
index: input_index,
});
if let Some(input_type) = input_type {
input_type.clone()
} else if node_id == network.exports_metadata.0 {
if let Some(parent_node_id) = network_path.last() {
let mut parent_path = network_path.clone();
parent_path.pop();
let parent_node = document_network
.nested_network(&parent_path)
.expect("Parent path should always exist")
.nodes
.get(&parent_node_id)
.expect("Last path node should always exist in parent network");
let output_types = NodeGraphMessageHandler::get_output_types(parent_node, &resolved_types, network_path);
output_types.iter().nth(input_index).map_or_else(
|| {
warn!("Could not find output type for export node {node_id}");
concrete!(())
},
|output_type| output_type.clone().map_or(concrete!(()), |output| output),
)
} else {
concrete!(graphene_core::ArtboardGroup)
}
} else {
// TODO: Once there is type inference (#1621), replace this workaround approach when disconnecting node inputs with NodeInput::Node(ToDefaultNode),
// TODO: which would be a new node that implements the Default trait (i.e. `Default::default()`)
// Resolve types from proto nodes in node_registry
let Some(node) = network.nodes.get(&node_id) else {
return concrete!(());
};
fn get_type_from_node(node: &DocumentNode, input_index: usize) -> Type {
match &node.implementation {
DocumentNodeImplementation::ProtoNode(protonode) => {
let Some(node_io_hashmap) = NODE_REGISTRY.get(&protonode) else {
log::error!("Could not get hashmap for proto node: {protonode:?}");
return concrete!(());
};
let mut all_node_io_types = node_io_hashmap.keys().collect::<Vec<_>>();
all_node_io_types.sort_by_key(|node_io_types| {
let mut hasher = DefaultHasher::new();
node_io_types.hash(&mut hasher);
hasher.finish()
});
let Some(node_types) = all_node_io_types.first() else {
log::error!("Could not get node_types from hashmap");
return concrete!(());
};
let skip_footprint = if node.manual_composition.is_some() { 1 } else { 0 };
let Some(input_type) = std::iter::once(node_types.input.clone())
.chain(node_types.parameters.clone().into_iter())
.nth(input_index + skip_footprint)
else {
log::error!("Could not get type");
return concrete!(());
};
input_type
}
DocumentNodeImplementation::Network(network) => {
for node in &network.nodes {
for (network_node_input_index, input) in node.1.inputs.iter().enumerate() {
if let NodeInput::Network { import_index, .. } = input {
if *import_index == input_index {
return get_type_from_node(&node.1, network_node_input_index);
}
}
}
}
// Input is disconnected
concrete!(())
}
_ => concrete!(()),
}
}
get_type_from_node(node, input_index)
}
}
}

View file

@ -1,6 +1,5 @@
use crate::messages::prelude::*;
use glam::IVec2;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
use graph_craft::proto::GraphErrors;
@ -12,7 +11,7 @@ pub enum NodeGraphMessage {
// Messages
Init,
SelectedNodesUpdated,
ConnectNodesByLink {
ConnectNodesByWire {
output_node: NodeId,
output_node_connector_index: usize,
input_node: NodeId,
@ -33,14 +32,10 @@ pub enum NodeGraphMessage {
DeleteSelectedNodes {
reconnect: bool,
},
DisconnectNodes {
DisconnectInput {
node_id: NodeId,
input_index: usize,
},
DisconnectLayerFromStack {
node_id: NodeId,
reconnect_to_sibling: bool,
},
EnterNestedNetwork {
node: NodeId,
},
@ -49,7 +44,7 @@ pub enum NodeGraphMessage {
node_id: NodeId,
},
ExitNestedNetwork {
depth_of_nesting: usize,
steps_back: usize,
},
ExposeInput {
node_id: NodeId,
@ -76,6 +71,7 @@ pub enum NodeGraphMessage {
PasteNodes {
serialized_nodes: String,
},
PrintSelectedNodeCoordinates,
RunDocumentGraph,
SelectedNodesAdd {
nodes: Vec<NodeId>,
@ -97,12 +93,8 @@ pub enum NodeGraphMessage {
input_index: usize,
input: NodeInput,
},
SetNodePosition {
node_id: NodeId,
position: IVec2,
},
SetQualifiedInputValue {
node_path: Vec<NodeId>,
node_id: NodeId,
input_index: usize,
value: TaggedValue,
},
@ -110,11 +102,6 @@ pub enum NodeGraphMessage {
ShiftNode {
node_id: NodeId,
},
ShiftUpstream {
node_id: NodeId,
shift: IVec2,
shift_self: bool,
},
SetVisibility {
node_id: NodeId,
visible: bool,
@ -135,7 +122,7 @@ pub enum NodeGraphMessage {
node_id: NodeId,
is_layer: bool,
},
ToggleLocked {
StartPreviewingWithoutRestore {
node_id: NodeId,
},
TogglePreview {

View file

@ -80,7 +80,7 @@ fn start_widgets(document_node: &DocumentNode, node_id: NodeId, index: usize, na
}
fn text_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> Vec<WidgetHolder> {
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Text, blank_assist);
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
if let NodeInput::Value {
tagged_value: TaggedValue::String(x),
@ -99,7 +99,7 @@ fn text_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name
}
fn text_area_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> Vec<WidgetHolder> {
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Text, blank_assist);
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
if let NodeInput::Value {
tagged_value: TaggedValue::String(x),
@ -118,7 +118,7 @@ fn text_area_widget(document_node: &DocumentNode, node_id: NodeId, index: usize,
}
fn bool_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> Vec<WidgetHolder> {
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Boolean, blank_assist);
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
if let NodeInput::Value {
tagged_value: TaggedValue::Bool(x),
@ -137,7 +137,7 @@ fn bool_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name
}
fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, x: &str, y: &str, unit: &str, min: Option<f64>, mut assist: impl FnMut(&mut Vec<WidgetHolder>)) -> LayoutGroup {
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Vector, false);
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Number, false);
assist(&mut widgets);
@ -230,7 +230,7 @@ fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name
}
fn vec_f64_input(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, text_props: TextInput, blank_assist: bool) -> Vec<WidgetHolder> {
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Vector, blank_assist);
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Number, blank_assist);
let from_string = |string: &str| {
string
@ -259,7 +259,7 @@ fn vec_f64_input(document_node: &DocumentNode, node_id: NodeId, index: usize, na
}
fn vec_dvec2_input(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, text_props: TextInput, blank_assist: bool) -> Vec<WidgetHolder> {
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Vector, blank_assist);
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Number, blank_assist);
let from_string = |string: &str| {
string
@ -322,7 +322,7 @@ fn font_inputs(document_node: &DocumentNode, node_id: NodeId, index: usize, name
}
fn vector_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, blank_assist: bool) -> Vec<WidgetHolder> {
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Subpath, blank_assist);
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::VectorData, blank_assist);
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.push(TextLabel::new("Vector data must be supplied through the graph").widget_holder());
@ -869,7 +869,7 @@ fn gradient_positions(rows: &mut Vec<LayoutGroup>, document_node: &DocumentNode,
}
fn color_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, color_props: ColorButton, blank_assist: bool) -> LayoutGroup {
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Color, blank_assist);
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::General, blank_assist);
if let NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] {
if let &TaggedValue::Color(x) = tagged_value {
@ -2022,9 +2022,22 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
let image_size = context
.executor
.introspect_node_in_network(
context.network,
context.document_network,
&imaginate_node,
|network| network.imports.first().copied(),
|network| {
network
.nodes
.iter()
.find(|node| {
node.1
.inputs
.iter()
.find(|node_input| if let NodeInput::Network { import_index, .. } = node_input { *import_index == 0 } else { false })
.is_some()
})
.map(|(node_id, _)| node_id)
.copied()
},
|frame: &IORecord<(), ImageFrame<Color>>| (frame.output.image.width, frame.output.image.height),
)
.unwrap_or_default();
@ -2032,7 +2045,7 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
let resolution = {
use graphene_std::imaginate::pick_safe_imaginate_resolution;
let mut widgets = start_widgets(document_node, node_id, resolution_index, "Resolution", FrontendGraphDataType::Vector, false);
let mut widgets = start_widgets(document_node, node_id, resolution_index, "Resolution", FrontendGraphDataType::Number, false);
let round = |x: DVec2| {
let (x, y) = pick_safe_imaginate_resolution(x.into());

View file

@ -1,48 +1,35 @@
use graph_craft::document::value::TaggedValue;
use graph_craft::document::NodeId;
use graphene_core::Type;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FrontendGraphDataType {
#[default]
#[serde(rename = "general")]
General,
#[serde(rename = "raster")]
Raster,
#[serde(rename = "color")]
Color,
#[serde(rename = "general")]
Text,
#[serde(rename = "vector")]
Subpath,
#[serde(rename = "number")]
VectorData,
Number,
#[serde(rename = "general")]
Boolean,
/// Refers to the mathematical vector, with direction and magnitude.
#[serde(rename = "number")]
Vector,
#[serde(rename = "raster")]
GraphicGroup,
#[serde(rename = "artboard")]
Graphic,
Artboard,
#[serde(rename = "color")]
Palette,
}
impl FrontendGraphDataType {
pub const fn with_tagged_value(value: &TaggedValue) -> Self {
match value {
TaggedValue::String(_) => Self::Text,
TaggedValue::F64(_) | TaggedValue::U32(_) | TaggedValue::DAffine2(_) => Self::Number,
TaggedValue::Bool(_) => Self::Boolean,
TaggedValue::DVec2(_) | TaggedValue::IVec2(_) => Self::Vector,
TaggedValue::Image(_) => Self::Raster,
TaggedValue::ImageFrame(_) => Self::Raster,
TaggedValue::Color(_) => Self::Color,
TaggedValue::RcSubpath(_) | TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::Subpath,
TaggedValue::GraphicGroup(_) => Self::GraphicGroup,
TaggedValue::Artboard(_) | TaggedValue::ArtboardGroup(_) => Self::Artboard,
TaggedValue::Palette(_) => Self::Palette,
pub fn with_type(input: &Type) -> Self {
match TaggedValue::from_type(input) {
TaggedValue::Image(_) | TaggedValue::ImageFrame(_) => Self::Raster,
TaggedValue::Subpaths(_) | TaggedValue::RcSubpath(_) | TaggedValue::VectorData(_) => Self::VectorData,
TaggedValue::U32(_)
| TaggedValue::U64(_)
| TaggedValue::F64(_)
| TaggedValue::UVec2(_)
| TaggedValue::IVec2(_)
| TaggedValue::DVec2(_)
| TaggedValue::OptionalDVec2(_)
| TaggedValue::F64Array4(_)
| TaggedValue::VecF64(_)
| TaggedValue::VecDVec2(_) => Self::Number,
TaggedValue::GraphicGroup(_) | TaggedValue::GraphicElement(_) => Self::Graphic,
TaggedValue::ArtboardGroup(_) => Self::Artboard,
_ => Self::General,
}
}
@ -65,9 +52,9 @@ pub struct FrontendGraphOutput {
pub name: String,
#[serde(rename = "resolvedType")]
pub resolved_type: Option<String>,
pub connected: Option<NodeId>,
pub connected: Vec<NodeId>,
#[serde(rename = "connectedIndex")]
pub connected_index: Option<usize>,
pub connected_index: Vec<usize>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
@ -92,19 +79,21 @@ pub struct FrontendNode {
pub locked: bool,
pub previewed: bool,
pub errors: Option<String>,
#[serde(rename = "uiOnly")]
pub ui_only: bool,
}
// (link_start, link_end, link_end_input_index)
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeLink {
#[serde(rename = "linkStart")]
pub link_start: NodeId,
#[serde(rename = "linkStartOutputIndex")]
pub link_start_output_index: usize,
#[serde(rename = "linkEnd")]
pub link_end: NodeId,
#[serde(rename = "linkEndInputIndex")]
pub link_end_input_index: usize,
pub struct FrontendNodeWire {
#[serde(rename = "wireStart")]
pub wire_start: NodeId,
#[serde(rename = "wireStartOutputIndex")]
pub wire_start_output_index: usize,
#[serde(rename = "wireEnd")]
pub wire_end: NodeId,
#[serde(rename = "wireEndInputIndex")]
pub wire_end_input_index: usize,
pub dashed: bool,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]

View file

@ -48,13 +48,13 @@ impl OverlayContext {
array.push(&JsValue::from(dash_width - 1.));
self.render_context
.set_line_dash(&JsValue::from(array))
.map_err(|error| log::debug!("Error drawing dashed line: {:?}", error))
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
.ok();
} else {
let array = js_sys::Array::new();
self.render_context
.set_line_dash(&JsValue::from(array))
.map_err(|error| log::debug!("Error drawing dashed line: {:?}", error))
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
.ok();
}
self.render_context.begin_path();

View file

@ -35,7 +35,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
responses,
nested_path: &node_graph_message_handler.network,
executor,
network,
document_network: network,
metadata,
};

View file

@ -34,7 +34,7 @@ impl Default for DocumentMetadata {
fn default() -> Self {
Self {
upstream_transforms: HashMap::new(),
structure: HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]),
structure: HashMap::new(),
artboards: HashSet::new(),
folders: HashSet::new(),
hidden: HashSet::new(),
@ -50,13 +50,8 @@ impl Default for DocumentMetadata {
// =================================
impl DocumentMetadata {
/// Get the root layer from the document
pub const fn root(&self) -> LayerNodeIdentifier {
LayerNodeIdentifier::ROOT
}
pub fn all_layers(&self) -> DescendantsIter<'_> {
self.root().descendants(self)
LayerNodeIdentifier::ROOT_PARENT.descendants(self)
}
pub fn layer_exists(&self, layer: LayerNodeIdentifier) -> bool {
@ -114,11 +109,15 @@ impl DocumentMetadata {
}
pub fn active_artboard(&self) -> LayerNodeIdentifier {
self.artboards.iter().next().copied().unwrap_or(LayerNodeIdentifier::ROOT)
self.artboards.iter().next().copied().unwrap_or(LayerNodeIdentifier::ROOT_PARENT)
}
pub fn all_artboards(&self) -> &HashSet<LayerNodeIdentifier> {
&self.artboards
}
pub fn is_folder(&self, layer: LayerNodeIdentifier) -> bool {
layer == LayerNodeIdentifier::ROOT || self.folders.contains(&layer)
self.folders.contains(&layer)
}
pub fn is_artboard(&self, layer: LayerNodeIdentifier) -> bool {
@ -147,23 +146,22 @@ impl DocumentMetadata {
impl DocumentMetadata {
/// Loads the structure of layer nodes from a node graph.
pub fn load_structure(&mut self, graph: &NodeNetwork, selected_nodes: &mut SelectedNodes) {
self.structure = HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]);
pub fn load_structure(&mut self, graph: &NodeNetwork) {
self.structure = HashMap::from_iter([(LayerNodeIdentifier::ROOT_PARENT, NodeRelations::default())]);
self.artboards = HashSet::new();
self.folders = HashSet::new();
self.hidden = HashSet::new();
self.locked = HashSet::new();
// Refers to output node: NodeId(0)
let output_node_id = graph.exports[0].node_id;
// Should refer to output node
let mut awaiting_horizontal_flow = vec![(output_node_id, LayerNodeIdentifier::ROOT)];
let mut awaiting_horizontal_flow = vec![(NodeId(std::u64::MAX), LayerNodeIdentifier::ROOT_PARENT)];
let mut awaiting_primary_flow = vec![];
while let Some((horizontal_root_node_id, mut parent_layer_node)) = awaiting_horizontal_flow.pop() {
let horizontal_flow_iter = graph.upstream_flow_back_from_nodes(vec![horizontal_root_node_id], FlowType::HorizontalFlow);
// Skip the horizontal_root_node_id node
for (current_node, current_node_id) in horizontal_flow_iter.skip(1) {
for (current_node, current_node_id) in horizontal_flow_iter.skip(if horizontal_root_node_id == NodeId(std::u64::MAX) { 0 } else { 1 }) {
if !current_node.visible {
self.hidden.insert(current_node_id);
}
@ -176,6 +174,7 @@ impl DocumentMetadata {
let current_layer_node = LayerNodeIdentifier::new(current_node_id, graph);
if !self.structure.contains_key(&current_layer_node) {
awaiting_primary_flow.push((current_node_id, parent_layer_node));
parent_layer_node.push_child(self, current_layer_node);
parent_layer_node = current_layer_node;
@ -224,7 +223,6 @@ impl DocumentMetadata {
}
}
selected_nodes.0.retain(|node| graph.nodes.contains_key(node));
self.upstream_transforms.retain(|node, _| graph.nodes.contains_key(node));
self.click_targets.retain(|layer, _| self.structure.contains_key(layer));
}
@ -248,7 +246,13 @@ impl DocumentMetadata {
pub fn transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 {
layer
.ancestors(self)
.filter_map(|ancestor_layer| self.upstream_transforms.get(&ancestor_layer.to_node()))
.filter_map(|ancestor_layer| {
if ancestor_layer != LayerNodeIdentifier::ROOT_PARENT {
self.upstream_transforms.get(&ancestor_layer.to_node())
} else {
None
}
})
.copied()
.map(|(footprint, transform)| footprint.transform * transform)
.next()
@ -260,6 +264,10 @@ impl DocumentMetadata {
}
pub fn downstream_transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 {
if layer == LayerNodeIdentifier::ROOT_PARENT {
return self.transform_to_viewport(layer);
}
self.upstream_transforms
.get(&layer.to_node())
.copied()
@ -351,20 +359,23 @@ impl DocumentMetadata {
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct LayerNodeIdentifier(NonZeroU64);
impl Default for LayerNodeIdentifier {
fn default() -> Self {
Self::ROOT
impl core::fmt::Debug for LayerNodeIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let node_id = if *self != LayerNodeIdentifier::ROOT_PARENT { self.to_node() } else { NodeId(0) };
f.debug_tuple("LayerNodeIdentifier").field(&node_id).finish()
}
}
impl core::fmt::Debug for LayerNodeIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("LayerNodeIdentifier").field(&self.to_node()).finish()
impl Default for LayerNodeIdentifier {
fn default() -> Self {
Self::ROOT_PARENT
}
}
impl LayerNodeIdentifier {
pub const ROOT: Self = LayerNodeIdentifier::new_unchecked(NodeId(0));
/// A conceptual node used to represent the UI-only "Export" node
pub const ROOT_PARENT: Self = LayerNodeIdentifier::new_unchecked(NodeId(0));
/// Construct a [`LayerNodeIdentifier`] without checking if it is a layer node
pub const fn new_unchecked(node_id: NodeId) -> Self {
@ -376,7 +387,7 @@ impl LayerNodeIdentifier {
#[track_caller]
pub fn new(node_id: NodeId, network: &NodeNetwork) -> Self {
debug_assert!(
node_id == LayerNodeIdentifier::ROOT.to_node() || network.nodes.get(&node_id).is_some_and(|node| node.is_layer),
network.nodes.get(&node_id).is_some_and(|node| node.is_layer),
"Layer identifier constructed from non-layer node {node_id}: {:#?}",
network.nodes.get(&node_id)
);
@ -385,7 +396,9 @@ impl LayerNodeIdentifier {
/// Access the node id of this layer
pub fn to_node(self) -> NodeId {
NodeId(u64::from(self.0) - 1)
let id = NodeId(u64::from(self.0) - 1);
debug_assert!(id != NodeId(0), "LayerNodeIdentifier::ROOT_PARENT cannot be converted to NodeId");
id
}
/// Access the parent layer if possible
@ -427,6 +440,14 @@ impl LayerNodeIdentifier {
}
}
pub fn upstream_siblings(self, metadata: &DocumentMetadata) -> AxisIter {
AxisIter {
layer_node: Some(self),
next_node: Self::next_sibling,
metadata,
}
}
/// All ancestors of this layer, including self, going to the document root
pub fn ancestors(self, metadata: &DocumentMetadata) -> AxisIter {
AxisIter {
@ -550,13 +571,6 @@ impl LayerNodeIdentifier {
pub fn starts_with(&self, other: Self, metadata: &DocumentMetadata) -> bool {
self.ancestors(metadata).any(|parent| parent == other)
}
pub fn child_of_root(&self, metadata: &DocumentMetadata) -> Self {
self.ancestors(metadata)
.filter(|&layer| layer != LayerNodeIdentifier::ROOT)
.last()
.expect("There should be a layer before the root")
}
}
// ========
@ -647,6 +661,9 @@ struct NodeRelations {
// ================
pub fn is_artboard(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
if layer == LayerNodeIdentifier::ROOT_PARENT {
return false;
}
let Some(node) = network.nodes.get(&layer.to_node()) else { return false };
node.is_artboard()
}
@ -654,7 +671,7 @@ pub fn is_artboard(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
#[test]
fn test_tree() {
let mut metadata = DocumentMetadata::default();
let root = metadata.root();
let root = LayerNodeIdentifier::ROOT_PARENT;
let metadata = &mut metadata;
root.push_child(metadata, LayerNodeIdentifier::new_unchecked(NodeId(3)));
assert_eq!(root.children(metadata).collect::<Vec<_>>(), vec![LayerNodeIdentifier::new_unchecked(NodeId(3))]);

View file

@ -1,6 +1,6 @@
use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use graph_craft::document::NodeId;
use graph_craft::document::{NodeId, NodeNetwork};
use serde::ser::SerializeStruct;
@ -57,7 +57,13 @@ pub struct SelectedNodes(pub Vec<NodeId>);
impl SelectedNodes {
pub fn layer_visible(&self, layer: LayerNodeIdentifier, metadata: &DocumentMetadata) -> bool {
layer.ancestors(metadata).all(|layer| metadata.node_is_visible(layer.to_node()))
layer.ancestors(metadata).all(|layer| {
if layer != LayerNodeIdentifier::ROOT_PARENT {
metadata.node_is_visible(layer.to_node())
} else {
true
}
})
}
pub fn selected_visible_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
@ -65,7 +71,13 @@ impl SelectedNodes {
}
pub fn layer_locked(&self, layer: LayerNodeIdentifier, metadata: &DocumentMetadata) -> bool {
layer.ancestors(metadata).any(|layer| metadata.node_is_locked(layer.to_node()))
layer.ancestors(metadata).any(|layer| {
if layer != LayerNodeIdentifier::ROOT_PARENT {
metadata.node_is_locked(layer.to_node())
} else {
false
}
})
}
pub fn selected_unlocked_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
@ -89,8 +101,11 @@ impl SelectedNodes {
self.selected_layers(metadata).any(|selected| selected == layer)
}
pub fn selected_nodes(&self) -> core::slice::Iter<'_, NodeId> {
self.0.iter()
// All selected nodes must be in the same network
pub fn selected_nodes<'a>(&'a self, network: &'a NodeNetwork) -> impl Iterator<Item = &NodeId> + '_ {
self.0
.iter()
.filter(|node_id| network.nodes.contains_key(*node_id) || **node_id == network.imports_metadata.0 || **node_id == network.exports_metadata.0)
}
pub fn selected_nodes_ref(&self) -> &Vec<NodeId> {
@ -105,16 +120,43 @@ impl SelectedNodes {
self.0.retain(f);
}
pub fn set_selected_nodes(&mut self, new: Vec<NodeId>) {
self.0 = new;
// TODO: This function is run when a node in the layer panel is currently selected, and a new node is selected in the graph, as well as when a node is currently selected in the graph and a node in the layer panel is selected. These are fundamentally different operations, since different nodes should be selected in each case, but cannot be distinguished. Currently it is not possible to shift+click a node in the node graph while a layer is selected. Instead of set_selected_nodes, add_selected_nodes should be used.
pub fn set_selected_nodes(&mut self, new: Vec<NodeId>, document_network: &NodeNetwork, network_path: &Vec<NodeId>) {
let Some(network) = document_network.nested_network(network_path) else { return };
let mut new_nodes = new;
// If any nodes to add are in the document network, clear selected nodes in the current network
if new_nodes.iter().any(|node_to_add| document_network.nodes.contains_key(node_to_add)) {
new_nodes.retain(|selected_node| {
document_network.nodes.contains_key(selected_node) || document_network.imports_metadata.0 == *selected_node || document_network.exports_metadata.0 == *selected_node
});
}
// If not, then clear any nodes that are not in the current network
else {
new_nodes.retain(|selected_node| network.nodes.contains_key(selected_node) || network.imports_metadata.0 == *selected_node || network.exports_metadata.0 == *selected_node);
}
pub fn add_selected_nodes(&mut self, iter: impl IntoIterator<Item = NodeId>) {
self.0.extend(iter);
self.0 = new_nodes;
}
pub fn add_selected_nodes(&mut self, new: Vec<NodeId>, document_network: &NodeNetwork, network_path: &Vec<NodeId>) {
let Some(network) = document_network.nested_network(network_path) else { return };
// If the nodes to add are in the document network, clear selected nodes in the current network
if new.iter().any(|node_to_add| document_network.nodes.contains_key(node_to_add)) {
self.retain_selected_nodes(|selected_node| {
document_network.nodes.contains_key(selected_node) || document_network.imports_metadata.0 == *selected_node || document_network.exports_metadata.0 == *selected_node
});
} else {
self.retain_selected_nodes(|selected_node| network.nodes.contains_key(selected_node) || network.imports_metadata.0 == *selected_node || network.exports_metadata.0 == *selected_node);
}
self.0.extend(new);
}
pub fn clear_selected_nodes(&mut self) {
self.set_selected_nodes(Vec::new());
self.0 = Vec::new();
}
}

View file

@ -36,6 +36,9 @@ impl OriginalTransforms {
OriginalTransforms::Layer(layer_map) => {
layer_map.retain(|layer, _| selected.contains(layer));
for &layer in selected {
if layer == LayerNodeIdentifier::ROOT_PARENT {
continue;
}
layer_map.entry(layer).or_insert_with(|| document_metadata.upstream_transform(layer.to_node()));
}
}

View file

@ -205,7 +205,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
};
buffer.push(CopyBufferEntry {
nodes: NodeGraphMessageHandler::copy_nodes(active_document.network(), &copy_ids).collect(),
nodes: NodeGraphMessageHandler::copy_nodes(
active_document.network(),
&active_document.node_graph_handler.network,
&active_document.node_graph_handler.resolved_types,
&copy_ids,
)
.collect(),
selected: active_document.selected_nodes.selected_layers_contains(layer, active_document.metadata()),
visible: active_document.selected_nodes.layer_visible(layer, active_document.metadata()),
locked: active_document.selected_nodes.layer_locked(layer, active_document.metadata()),
@ -378,6 +384,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
}
}
}
// TODO: Eventually remove this (probably starting late 2024)
responses.add(GraphOperationMessage::DeleteLegacyOutputNode);
}
PortfolioMessage::PasteIntoFolder { clipboard, parent, insert_index } => {
let paste = |entry: &CopyBufferEntry, responses: &mut VecDeque<_>| {

View file

@ -227,6 +227,7 @@ pub struct NodeGraphLayer<'a> {
impl<'a> NodeGraphLayer<'a> {
/// Get the layer node from the document
pub fn new(layer: LayerNodeIdentifier, network: &'a NodeNetwork) -> Self {
debug_assert!(layer != LayerNodeIdentifier::ROOT_PARENT, "Cannot create new NodeGraphLayer from ROOT_PARENT");
Self {
node_graph: network,
layer_node: layer.to_node(),

View file

@ -33,6 +33,11 @@ impl Resize {
let Some(layer) = self.layer else {
return None;
};
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Resize layer cannot be ROOT_PARENT");
}
if !document.network().nodes.contains_key(&layer.to_node()) {
self.layer.take();
return None;

View file

@ -276,7 +276,13 @@ impl SnapManager {
candidates.push(layer);
}
}
add_candidates(LayerNodeIdentifier::ROOT, snap_data, quad, &mut candidates);
if let Some(root) = snap_data.document.network.get_root_node() {
if snap_data.document.network.nodes.get(&root.id).expect("Root should always be a node in find_candidates").is_layer {
add_candidates(LayerNodeIdentifier::new(root.id, &snap_data.document.network), snap_data, quad, &mut candidates);
}
}
if candidates.len() > 10 {
warn!("Snap candidate overflow");
}

View file

@ -203,7 +203,6 @@ impl LayerSnapper {
}
pub fn snap_anchors(&mut self, snap_data: &mut SnapData, point: &SnapCandidatePoint, snap_results: &mut SnapResults, c: SnapConstraint, constrained_point: DVec2) {
self.collect_anchors(snap_data, point.source_index == 0);
//info!("Points to snap {:#?}", self.points_to_snap);
let mut best = None;
for candidate in &self.points_to_snap {
// Candidate is not on constraint

View file

@ -155,6 +155,10 @@ impl ArtboardToolData {
let Some(movement) = &bounds.selected_edges else {
return;
};
if self.selected_artboard.unwrap() == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Selected artboard cannot be ROOT_PARENT");
return;
}
let center = from_center.then_some(bounds.center_of_transformation);
let (min, size) = movement.new_size(mouse_position, bounds.transform, center, constrain_square, None);
@ -233,6 +237,10 @@ impl Fsm for ArtboardToolFsmState {
let size = bounds.bounds[1] - bounds.bounds[0];
let position = bounds.bounds[0] + bounds.transform.inverse().transform_vector2(mouse_position - tool_data.drag_current);
if tool_data.selected_artboard.unwrap() == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Selected artboard cannot be ROOT_PARENT");
return ArtboardToolFsmState::Ready;
}
responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap().to_node(),
location: position.round().as_ivec2(),
@ -257,7 +265,7 @@ impl Fsm for ArtboardToolFsmState {
}
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
let mouse_position = input.mouse.position;
let snapped_mouse_position = mouse_position; //tool_data.snap_manager.snap_position(responses, document, mouse_position);
let snapped_mouse_position = mouse_position;
let root_transform = document.metadata().document_to_viewport.inverse();
@ -280,11 +288,15 @@ impl Fsm for ArtboardToolFsmState {
let start = start.min(end);
if let Some(artboard) = tool_data.selected_artboard {
if artboard == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Selected artboard cannot be ROOT_PARENT");
} else {
responses.add(GraphOperationMessage::ResizeArtboard {
id: artboard.to_node(),
location: start.round().as_ivec2(),
dimensions: size.round().as_ivec2(),
});
}
} else {
let id = NodeId(generate_uuid());
@ -395,12 +407,16 @@ impl Fsm for ArtboardToolFsmState {
}
(_, ArtboardToolMessage::NudgeSelected { delta_x, delta_y }) => {
if let Some(bounds) = &mut tool_data.bounding_box_manager {
if tool_data.selected_artboard.unwrap() == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Selected artboard cannot be ROOT_PARENT");
} else {
responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap().to_node(),
location: DVec2::new(bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y).round().as_ivec2(),
dimensions: (bounds.bounds[1] - bounds.bounds[0]).round().as_ivec2(),
});
}
}
ArtboardToolFsmState::Ready
}

View file

@ -323,6 +323,7 @@ impl Fsm for BrushToolFsmState {
let layer_position = tool_data.transform.inverse().transform_point2(parent_transform);
let layer_document_scale = document.metadata().transform_to_document(parent) * tool_data.transform;
// TODO: Also scale it based on the input image ('Background' parameter).
// TODO: Resizing the input image results in a different brush size from the chosen diameter.
let layer_scale = 0.0001_f64 // Safety against division by zero
@ -355,7 +356,7 @@ impl Fsm for BrushToolFsmState {
(BrushToolFsmState::Drawing, BrushToolMessage::PointerMove) => {
if let Some(layer) = tool_data.layer {
if let Some(stroke) = tool_data.strokes.last_mut() {
let parent = layer.parent(document.metadata()).unwrap_or_default();
let parent = layer.parent(document.metadata()).unwrap_or(LayerNodeIdentifier::ROOT_PARENT);
let parent_position = document.metadata().transform_to_viewport(parent).inverse().transform_point2(input.mouse.position);
let layer_position = tool_data.transform.inverse().transform_point2(parent_position);

View file

@ -128,7 +128,7 @@ pub enum GradientDragTarget {
/// Contains information about the selected gradient handle
#[derive(Clone, Debug, Default)]
struct SelectedGradient {
layer: LayerNodeIdentifier,
layer: Option<LayerNodeIdentifier>,
transform: DAffine2,
gradient: Gradient,
dragging: GradientDragTarget,
@ -138,7 +138,7 @@ impl SelectedGradient {
pub fn new(gradient: Gradient, layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Self {
let transform = gradient_space_transform(layer, document);
Self {
layer,
layer: Some(layer),
transform,
gradient,
dragging: GradientDragTarget::End,
@ -198,12 +198,14 @@ impl SelectedGradient {
/// Update the layer fill to the current gradient
pub fn render_gradient(&mut self, responses: &mut VecDeque<Message>) {
self.gradient.transform = self.transform;
if let Some(layer) = self.layer {
responses.add(GraphOperationMessage::FillSet {
layer: self.layer,
layer,
fill: Fill::Gradient(self.gradient.clone()),
});
}
}
}
impl GradientTool {
/// Get the gradient type of the selected gradient (if it exists)
@ -250,7 +252,9 @@ impl Fsm for GradientToolFsmState {
for layer in document.selected_nodes.selected_visible_layers(document.metadata()) {
let Some(gradient) = get_gradient(layer, &document.network) else { continue };
let transform = gradient_space_transform(layer, document);
let dragging = selected.filter(|selected| selected.layer == layer).map(|selected| selected.dragging);
let dragging = selected
.filter(|selected| selected.layer.map_or(false, |selected_layer| selected_layer == layer))
.map(|selected| selected.dragging);
let Gradient { start, end, positions, .. } = gradient;
let (start, end) = (transform.transform_point2(start), transform.transform_point2(end));
@ -289,10 +293,13 @@ impl Fsm for GradientToolFsmState {
// The gradient has only one point and so should become a fill
if selected_gradient.gradient.positions.len() == 1 {
if let Some(layer) = selected_gradient.layer {
responses.add(GraphOperationMessage::FillSet {
layer: selected_gradient.layer,
layer,
fill: Fill::Solid(selected_gradient.gradient.positions[0].1),
});
}
return self;
}
@ -367,7 +374,7 @@ impl Fsm for GradientToolFsmState {
if pos.distance_squared(mouse) < tolerance {
dragging = true;
tool_data.selected_gradient = Some(SelectedGradient {
layer,
layer: Some(layer),
transform,
gradient: gradient.clone(),
dragging: GradientDragTarget::Step(index),
@ -381,7 +388,7 @@ impl Fsm for GradientToolFsmState {
if pos.distance_squared(mouse) < tolerance {
dragging = true;
tool_data.selected_gradient = Some(SelectedGradient {
layer,
layer: Some(layer),
transform,
gradient: gradient.clone(),
dragging: dragging_target,

View file

@ -147,12 +147,21 @@ impl SelectTool {
})
}
fn boolean_widgets(&self) -> impl Iterator<Item = WidgetHolder> {
fn boolean_widgets(&self, selected_count: usize) -> impl Iterator<Item = WidgetHolder> {
let enabled = move |operation| {
if operation == BooleanOperation::Union {
(1..=2).contains(&selected_count)
} else {
selected_count == 2
}
};
let operations = BooleanOperation::list();
let icons = BooleanOperation::icons();
operations.into_iter().zip(icons.into_iter()).map(|(operation, icon)| {
operations.into_iter().zip(icons.into_iter()).map(move |(operation, icon)| {
IconButton::new(icon, 24)
.tooltip(operation.to_string())
.disabled(!enabled(operation))
.on_update(move |_| GraphOperationMessage::InsertBooleanOperation { operation }.into())
.widget_holder()
})
@ -194,10 +203,8 @@ impl LayoutHolder for SelectTool {
widgets.extend(self.flip_widgets(disabled));
// Boolean
if self.tool_data.selected_layers_count == 2 {
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.extend(self.boolean_widgets());
}
widgets.extend(self.boolean_widgets(self.tool_data.selected_layers_count));
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
}
@ -316,6 +323,14 @@ impl SelectToolData {
let mut new_dragging = Vec::new();
for layer_ancestors in document.metadata().shallowest_unique_layers(self.layers_dragging.iter().copied().rev()) {
let Some(layer) = layer_ancestors.last().copied() else { continue };
// `layer` cannot be `ROOT_PARENT`, since `ROOT_PARENT` cannot be part of `layers_dragging`
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("ROOT_PARENT cannot be in layers_dragging");
continue;
}
// `parent` can be `ROOT_PARENT`
let Some(parent) = layer.parent(&document.metadata) else { continue };
// Moves the layer back to its starting position.
@ -345,7 +360,8 @@ impl SelectToolData {
copy_ids.insert(node_id, NodeId((index + 1) as u64));
});
};
let nodes: HashMap<NodeId, DocumentNode> = NodeGraphMessageHandler::copy_nodes(document.network(), &copy_ids).collect();
let nodes: HashMap<NodeId, DocumentNode> =
NodeGraphMessageHandler::copy_nodes(document.network(), &document.node_graph_handler.network, &document.node_graph_handler.resolved_types, &copy_ids).collect();
let insert_index = DocumentMessageHandler::get_calculated_insert_index(&document.metadata, &document.selected_nodes, parent);
@ -368,8 +384,13 @@ impl SelectToolData {
// Delete the duplicated layers
for layer_ancestors in document.metadata().shallowest_unique_layers(self.layers_dragging.iter().copied()) {
let layer = layer_ancestors.last().unwrap();
if *layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("ROOT_PARENT cannot be in layers_dragging");
continue;
}
responses.add(NodeGraphMessage::DeleteNodes {
node_ids: vec![layer_ancestors.last().unwrap().to_node()],
node_ids: vec![layer.to_node()],
reconnect: true,
});
}
@ -382,7 +403,17 @@ impl SelectToolData {
skip_rerender: true,
});
}
let nodes = original.iter().map(|layer| layer.to_node()).collect();
let nodes = original
.iter()
.filter_map(|layer| {
if *layer != LayerNodeIdentifier::ROOT_PARENT {
Some(layer.to_node())
} else {
log::error!("ROOT_PARENT cannot be part of non_duplicated_layers");
None
}
})
.collect();
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
self.layers_dragging = original;
}
@ -526,7 +557,15 @@ impl Fsm for SelectToolFsmState {
if let Some(bounds) = &mut tool_data.bounding_box_manager {
bounds.original_bound_transform = bounds.transform;
tool_data.layers_dragging.retain(|layer| document.network.nodes.contains_key(&layer.to_node()));
tool_data.layers_dragging.retain(|layer| {
if *layer != LayerNodeIdentifier::ROOT_PARENT {
document.network.nodes.contains_key(&layer.to_node())
} else {
log::error!("ROOT_PARENT should not be part of layers_dragging");
false
}
});
let mut selected = Selected::new(
&mut bounds.original_transforms,
&mut bounds.center_of_transformation,
@ -548,7 +587,14 @@ impl Fsm for SelectToolFsmState {
responses.add(DocumentMessage::StartTransaction);
if let Some(bounds) = &mut tool_data.bounding_box_manager {
tool_data.layers_dragging.retain(|layer| document.network().nodes.contains_key(&layer.to_node()));
tool_data.layers_dragging.retain(|layer| {
if *layer != LayerNodeIdentifier::ROOT_PARENT {
document.network.nodes.contains_key(&layer.to_node())
} else {
log::error!("ROOT_PARENT should not be part of layers_dragging");
false
}
});
let mut selected = Selected::new(
&mut bounds.original_transforms,
&mut bounds.center_of_transformation,
@ -705,7 +751,14 @@ impl Fsm for SelectToolFsmState {
let pivot_transform = DAffine2::from_translation(pivot);
let transformation = pivot_transform * delta * pivot_transform.inverse();
tool_data.layers_dragging.retain(|layer| document.network().nodes.contains_key(&layer.to_node()));
tool_data.layers_dragging.retain(|layer| {
if *layer != LayerNodeIdentifier::ROOT_PARENT {
document.network.nodes.contains_key(&layer.to_node())
} else {
log::error!("ROOT_PARENT should not be part of layers_dragging");
false
}
});
let selected = &tool_data.layers_dragging;
let mut selected = Selected::new(
&mut bounds.original_transforms,
@ -748,7 +801,14 @@ impl Fsm for SelectToolFsmState {
let delta = DAffine2::from_angle(snapped_angle);
tool_data.layers_dragging.retain(|layer| document.network().nodes.contains_key(&layer.to_node()));
tool_data.layers_dragging.retain(|layer| {
if *layer != LayerNodeIdentifier::ROOT_PARENT {
document.network().nodes.contains_key(&layer.to_node())
} else {
log::error!("ROOT_PARENT should not be part of replacement_selected_layers");
false
}
});
let mut selected = Selected::new(
&mut bounds.original_transforms,
&mut bounds.center_of_transformation,
@ -767,7 +827,7 @@ impl Fsm for SelectToolFsmState {
}
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove(modifier_keys)) => {
let mouse_position = input.mouse.position;
let snapped_mouse_position = mouse_position; //tool_data.snap_manager.snap_position(responses, document, mouse_position);
let snapped_mouse_position = mouse_position;
tool_data.pivot.set_viewport_position(snapped_mouse_position, document, responses);
// AutoPanning
@ -884,16 +944,30 @@ impl Fsm for SelectToolFsmState {
tool_data.layers_dragging.extend(replacement_selected_layers.iter());
responses.add(NodeGraphMessage::SelectedNodesSet {
nodes: replacement_selected_layers.iter().map(|layer| layer.to_node()).collect(),
nodes: replacement_selected_layers
.iter()
.filter_map(|layer| {
if *layer != LayerNodeIdentifier::ROOT_PARENT {
Some(layer.to_node())
} else {
log::error!("ROOT_PARENT cannot be part of replacement_selected_layers");
None
}
})
.collect(),
});
}
} else if let Some(selecting_layer) = tool_data.select_single_layer.take() {
if !tool_data.has_dragged {
if selecting_layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("selecting_layer should not be ROOT_PARENT");
} else {
responses.add(NodeGraphMessage::SelectedNodesSet {
nodes: vec![selecting_layer.to_node()],
});
}
}
}
tool_data.has_dragged = false;
tool_data.layer_selected_on_start = None;
@ -955,7 +1029,18 @@ impl Fsm for SelectToolFsmState {
tool_data.layers_dragging = new_selected.into_iter().collect();
responses.add(DocumentMessage::StartTransaction);
responses.add(NodeGraphMessage::SelectedNodesSet {
nodes: tool_data.layers_dragging.iter().map(|layer| layer.to_node()).collect(),
nodes: tool_data
.layers_dragging
.iter()
.filter_map(|layer| {
if *layer != LayerNodeIdentifier::ROOT_PARENT {
Some(layer.to_node())
} else {
log::error!("ROOT_PARENT cannot be part of tool_data.layers_dragging");
None
}
})
.collect(),
});
}
responses.add(OverlaysMessage::Draw);
@ -986,7 +1071,13 @@ impl Fsm for SelectToolFsmState {
SelectToolFsmState::Ready { selection }
}
(_, SelectToolMessage::Abort) => {
tool_data.layers_dragging.retain(|layer| document.network().nodes.contains_key(&layer.to_node()));
tool_data.layers_dragging.retain(|layer| {
if *layer != LayerNodeIdentifier::ROOT_PARENT {
document.network().nodes.contains_key(&layer.to_node())
} else {
false
}
});
if let Some(mut bounding_box_overlays) = tool_data.bounding_box_manager.take() {
let mut selected = Selected::new(
&mut bounding_box_overlays.original_transforms,
@ -1103,27 +1194,47 @@ fn drag_shallowest_manipulation(responses: &mut VecDeque<Message>, selected: Vec
.filter(not_artboard(document))
.find(|&ancestor| document.selected_nodes.selected_layers_contains(ancestor, document.metadata()));
let new_selected = ancestor.unwrap_or_else(|| {
layer
.ancestors(document.metadata())
.take_while(|&layer| layer != LayerNodeIdentifier::ROOT)
.filter(not_artboard(document))
.last()
.unwrap_or(layer)
});
let new_selected = ancestor.unwrap_or_else(|| layer.ancestors(document.metadata()).filter(not_artboard(document)).last().unwrap_or(layer));
tool_data.layers_dragging.retain(|layer| !layer.ancestors(document.metadata()).any(|ancestor| ancestor == new_selected));
tool_data.layers_dragging.push(new_selected);
}
responses.add(NodeGraphMessage::SelectedNodesSet {
nodes: tool_data.layers_dragging.iter().map(|layer| layer.to_node()).collect(),
nodes: tool_data
.layers_dragging
.iter()
.filter_map(|layer| {
if *layer != LayerNodeIdentifier::ROOT_PARENT {
Some(layer.to_node())
} else {
log::error!("ROOT_PARENT cannot be part of tool_data.layers_dragging");
None
}
})
.collect(),
});
}
fn drag_deepest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<LayerNodeIdentifier>, tool_data: &mut SelectToolData, document: &DocumentMessageHandler) {
tool_data.layers_dragging.append(&mut vec![document.find_deepest(&selected, &document.network).unwrap_or_default()]);
tool_data
.layers_dragging
.append(&mut vec![document.find_deepest(&selected, &document.network).unwrap_or(LayerNodeIdentifier::new(
document.network.get_root_node().expect("Root node should exist when dragging layers").id,
&document.network,
))]);
responses.add(NodeGraphMessage::SelectedNodesSet {
nodes: tool_data.layers_dragging.iter().map(|layer| layer.to_node()).collect(),
nodes: tool_data
.layers_dragging
.iter()
.filter_map(|layer| {
if *layer != LayerNodeIdentifier::ROOT_PARENT {
Some(layer.to_node())
} else {
log::error!("ROOT_PARENT cannot be part of tool_data.layers_dragging");
None
}
})
.collect(),
});
}
@ -1141,6 +1252,11 @@ fn edit_layer_shallowest_manipulation(document: &DocumentMessageHandler, layer:
return;
};
if new_selected == LayerNodeIdentifier::ROOT_PARENT {
log::error!("new_selected cannot be ROOT_PARENT");
return;
}
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_selected.to_node()] });
}

View file

@ -200,10 +200,7 @@ impl Fsm for SplineToolFsmState {
return self;
};
match (self, event) {
(_, SplineToolMessage::CanvasTransformed) => {
// tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
self
}
(_, SplineToolMessage::CanvasTransformed) => self,
(SplineToolFsmState::Ready, SplineToolMessage::DragStart) => {
responses.add(DocumentMessage::StartTransaction);
responses.add(DocumentMessage::DeselectAllLayers);
@ -211,9 +208,7 @@ impl Fsm for SplineToolFsmState {
let parent = document.new_layer_parent(true);
let transform = document.metadata().transform_to_viewport(parent);
//tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
//tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
let snapped_position = input.mouse.position; //tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
let snapped_position = input.mouse.position;
let pos = transform.inverse().transform_point2(snapped_position);
@ -241,7 +236,7 @@ impl Fsm for SplineToolFsmState {
let Some(layer) = tool_data.layer else {
return SplineToolFsmState::Ready;
};
let snapped_position = input.mouse.position; //tool_data.snap_manager.snap_position(responses, document, input.mouse.position);
let snapped_position = input.mouse.position;
let transform = document.metadata().transform_to_viewport(layer);
let pos = transform.inverse().transform_point2(snapped_position);

View file

@ -2,7 +2,6 @@
use super::tool_prelude::*;
use crate::application::generate_uuid;
use crate::consts::{DEFAULT_FONT_FAMILY, DEFAULT_FONT_STYLE};
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
@ -34,8 +33,8 @@ impl Default for TextOptions {
fn default() -> Self {
Self {
font_size: 24,
font_name: DEFAULT_FONT_FAMILY.into(),
font_style: DEFAULT_FONT_STYLE.into(),
font_name: graphene_core::consts::DEFAULT_FONT_FAMILY.into(),
font_style: graphene_core::consts::DEFAULT_FONT_STYLE.into(),
fill: ToolColorOptions::new_primary(),
}
}
@ -214,8 +213,9 @@ struct TextToolData {
impl TextToolData {
/// Set the editing state of the currently modifying layer
fn set_editing(&self, editable: bool, font_cache: &FontCache, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
// TODO: Should always set visibility for document network, but `node_id` is not a layer so it crashes
if let Some(node_id) = graph_modification_utils::get_fill_id(self.layer, &document.network) {
responses.add(NodeGraphMessage::SetVisibility { node_id, visible: !editable });
responses.add(GraphOperationMessage::SetVisibility { node_id, visible: !editable });
}
if let Some(editing_text) = self.editing_text.as_ref().filter(|_| editable) {
@ -248,6 +248,10 @@ impl TextToolData {
}
fn start_editing_layer(&mut self, layer: LayerNodeIdentifier, tool_state: TextToolFsmState, document: &DocumentMessageHandler, font_cache: &FontCache, responses: &mut VecDeque<Message>) {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot edit ROOT_PARENT in TextTooLData")
}
if tool_state == TextToolFsmState::Editing {
self.set_editing(false, font_cache, document, responses);
}
@ -427,7 +431,7 @@ impl Fsm for TextToolFsmState {
(TextToolFsmState::Editing, TextToolMessage::TextChange { new_text }) => {
tool_data.fix_text_bounds(&new_text, document, font_cache, responses);
responses.add(NodeGraphMessage::SetQualifiedInputValue {
node_path: vec![graph_modification_utils::get_text_id(tool_data.layer, &document.network).unwrap()],
node_id: graph_modification_utils::get_text_id(tool_data.layer, &document.network).unwrap(),
input_index: 1,
value: TaggedValue::String(new_text),
});

View file

@ -643,7 +643,6 @@ impl NodeGraphExecutor {
TaggedValue::OptionalColor(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::VectorData(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::GraphicGroup(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::Artboard(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::ImageFrame(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::Palette(render_object) => Self::debug_render(render_object, transform, responses),
_ => {

View file

@ -107,14 +107,14 @@
--color-data-general: #c5c5c5;
--color-data-general-dim: #767676;
--color-data-number: #cbbab4;
--color-data-number-dim: #87736b;
--color-data-raster: #e4bb72;
--color-data-raster-dim: #8b7752;
--color-data-vector: #65bbe5;
--color-data-vector-dim: #4b778c;
--color-data-color: #dce472;
--color-data-color-dim: #898d55;
--color-data-vectordata: #65bbe5;
--color-data-vectordata-dim: #4b778c;
--color-data-number: #cbbab4;
--color-data-number-dim: #87736b;
--color-data-graphic: #6b84e8;
--color-data-graphic-dim: #4a557b;
--color-data-artboard: #70a898;
--color-data-artboard-dim: #3a6156;

View file

@ -614,7 +614,7 @@
}
.color-vector {
fill: var(--color-data-vector);
fill: var(--color-data-vectordata);
}
.color-raster {

View file

@ -129,8 +129,8 @@
return currentFolder;
}
function toggleNodeVisibility(id: bigint) {
editor.handle.toggleNodeVisibility(id);
function toggleNodeVisibilityLayerPanel(id: bigint) {
editor.handle.toggleNodeVisibilityLayerPanel(id);
}
function toggleLayerLock(id: bigint) {
@ -430,7 +430,7 @@
<IconButton
class={"status-toggle"}
classes={{ inactive: !listing.entry.parentsVisible }}
action={(e) => (toggleNodeVisibility(listing.entry.id), e?.stopPropagation())}
action={(e) => (toggleNodeVisibilityLayerPanel(listing.entry.id), e?.stopPropagation())}
size={24}
icon={listing.entry.visible ? "EyeVisible" : "EyeHidden"}
hoverIcon={listing.entry.visible ? "EyeHide" : "EyeShow"}

View file

@ -6,17 +6,17 @@
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
import type { IconName } from "@graphite/utility-functions/icons";
import type { Editor } from "@graphite/wasm-communication/editor";
import type { FrontendNodeLink, FrontendNodeType, FrontendNode, FrontendGraphInput, FrontendGraphOutput } from "@graphite/wasm-communication/messages";
import type { FrontendNodeWire, FrontendNodeType, FrontendNode, FrontendGraphInput, FrontendGraphOutput, FrontendGraphDataType } from "@graphite/wasm-communication/messages";
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
import BreadcrumbTrailButtons from "@graphite/components/widgets/buttons/BreadcrumbTrailButtons.svelte";
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte";
import RadioInput from "@graphite/components/widgets/inputs/RadioInput.svelte";
import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte";
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
const WHEEL_RATE = (1 / 600) * 3;
const GRID_COLLAPSE_SPACING = 10;
const GRID_SIZE = 24;
@ -26,7 +26,7 @@
const editor = getContext<Editor>("editor");
const nodeGraph = getContext<NodeGraphState>("nodeGraph");
type LinkPath = { pathString: string; dataType: string; thick: boolean };
type WirePath = { pathString: string; dataType: FrontendGraphDataType; thick: boolean; dashed: boolean };
let graph: HTMLDivElement | undefined;
let nodesContainer: HTMLDivElement | undefined;
@ -41,13 +41,13 @@
let boxSelection: Box | undefined = undefined;
let previousSelection: bigint[] = [];
let selectIfNotDragged: undefined | bigint = undefined;
let linkInProgressFromConnector: SVGSVGElement | undefined = undefined;
let linkInProgressToConnector: SVGSVGElement | DOMRect | undefined = undefined;
let wireInProgressFromConnector: SVGSVGElement | undefined = undefined;
let wireInProgressToConnector: SVGSVGElement | DOMRect | undefined = undefined;
// TODO: Using this not-complete code, or another better approach, make it so the dragged in-progress connector correctly handles showing/hiding the SVG shape of the connector caps
// let linkInProgressFromLayerTop: bigint | undefined = undefined;
// let linkInProgressFromLayerBottom: bigint | undefined = undefined;
let disconnecting: { nodeId: bigint; inputIndex: number; linkIndex: number } | undefined = undefined;
let nodeLinkPaths: LinkPath[] = [];
// let wireInProgressFromLayerTop: bigint | undefined = undefined;
// let wireInProgressFromLayerBottom: bigint | undefined = undefined;
let disconnecting: { nodeId: bigint; inputIndex: number; wireIndex: number } | undefined = undefined;
let nodeWirePaths: WirePath[] = [];
let searchTerm = "";
let contextMenuOpenCoordinates: { x: number; y: number } | undefined = undefined;
let toggleDisplayAsLayerNodeId: bigint | undefined = undefined;
@ -77,8 +77,8 @@
appearAboveMouse = contextMenuY > height - ADD_NODE_MENU_HEIGHT;
})();
$: linkPathInProgress = createLinkPathInProgress(linkInProgressFromConnector, linkInProgressToConnector);
$: linkPaths = createLinkPaths(linkPathInProgress, nodeLinkPaths);
$: wirePathInProgress = createWirePathInProgress(wireInProgressFromConnector, wireInProgressToConnector);
$: wirePaths = createWirePaths(wirePathInProgress, nodeWirePaths);
function calculateGridSpacing(scale: number): number {
const dense = scale * GRID_SIZE;
@ -129,21 +129,21 @@
return Array.from(categories);
}
function createLinkPathInProgress(linkInProgressFromConnector?: SVGSVGElement, linkInProgressToConnector?: SVGSVGElement | DOMRect): LinkPath | undefined {
if (linkInProgressFromConnector && linkInProgressToConnector && nodesContainer) {
const from = connectorToNodeIndex(linkInProgressFromConnector);
const to = linkInProgressToConnector instanceof SVGSVGElement ? connectorToNodeIndex(linkInProgressToConnector) : undefined;
function createWirePathInProgress(wireInProgressFromConnector?: SVGSVGElement, wireInProgressToConnector?: SVGSVGElement | DOMRect): WirePath | undefined {
if (wireInProgressFromConnector && wireInProgressToConnector && nodesContainer) {
const from = connectorToNodeIndex(wireInProgressFromConnector);
const to = wireInProgressToConnector instanceof SVGSVGElement ? connectorToNodeIndex(wireInProgressToConnector) : undefined;
const linkStart = $nodeGraph.nodes.find((n) => n.id === from?.nodeId)?.isLayer || false;
const linkEnd = ($nodeGraph.nodes.find((n) => n.id === to?.nodeId)?.isLayer && to?.index == 0) || false;
return createWirePath(linkInProgressFromConnector, linkInProgressToConnector, linkStart, linkEnd);
const wireStart = $nodeGraph.nodes.find((n) => n.id === from?.nodeId)?.isLayer || false;
const wireEnd = ($nodeGraph.nodes.find((n) => n.id === to?.nodeId)?.isLayer && to?.index == 0) || false;
return createWirePath(wireInProgressFromConnector, wireInProgressToConnector, wireStart, wireEnd, false);
}
return undefined;
}
function createLinkPaths(linkPathInProgress: LinkPath | undefined, nodeLinkPaths: LinkPath[]): LinkPath[] {
const maybeLinkPathInProgress = linkPathInProgress ? [linkPathInProgress] : [];
return [...maybeLinkPathInProgress, ...nodeLinkPaths];
function createWirePaths(wirePathInProgress: WirePath | undefined, nodeWirePaths: WirePath[]): WirePath[] {
const maybeWirePathInProgress = wirePathInProgress ? [wirePathInProgress] : [];
return [...maybeWirePathInProgress, ...nodeWirePaths];
}
async function watchNodes(nodes: FrontendNode[]) {
@ -152,38 +152,38 @@
if (!outputs[index]) outputs[index] = [];
});
await refreshLinks();
await refreshWires();
}
function resolveLink(link: FrontendNodeLink): { nodeOutput: SVGSVGElement | undefined; nodeInput: SVGSVGElement | undefined } {
const outputIndex = Number(link.linkStartOutputIndex);
const inputIndex = Number(link.linkEndInputIndex);
function resolveWire(wire: FrontendNodeWire): { nodeOutput: SVGSVGElement | undefined; nodeInput: SVGSVGElement | undefined } {
const outputIndex = Number(wire.wireStartOutputIndex);
const inputIndex = Number(wire.wireEndInputIndex);
const nodeOutputConnectors = outputs[$nodeGraph.nodes.findIndex((n) => n.id === link.linkStart)];
const nodeInputConnectors = inputs[$nodeGraph.nodes.findIndex((n) => n.id === link.linkEnd)] || undefined;
const nodeOutputConnectors = outputs[$nodeGraph.nodes.findIndex((n) => n.id === wire.wireStart)];
const nodeInputConnectors = inputs[$nodeGraph.nodes.findIndex((n) => n.id === wire.wireEnd)] || undefined;
const nodeOutput = nodeOutputConnectors?.[outputIndex] as SVGSVGElement | undefined;
const nodeInput = nodeInputConnectors?.[inputIndex] as SVGSVGElement | undefined;
return { nodeOutput, nodeInput };
}
async function refreshLinks() {
async function refreshWires() {
await tick();
const links = $nodeGraph.links;
nodeLinkPaths = links.flatMap((link, index) => {
const { nodeInput, nodeOutput } = resolveLink(link);
const wires = $nodeGraph.wires;
nodeWirePaths = wires.flatMap((wire, index) => {
const { nodeInput, nodeOutput } = resolveWire(wire);
if (!nodeInput || !nodeOutput) return [];
if (disconnecting?.linkIndex === index) return [];
if (disconnecting?.wireIndex === index) return [];
const linkStart = $nodeGraph.nodes.find((n) => n.id === link.linkStart)?.isLayer || false;
const linkEnd = ($nodeGraph.nodes.find((n) => n.id === link.linkEnd)?.isLayer && Number(link.linkEndInputIndex) == 0) || false;
const wireStart = $nodeGraph.nodes.find((n) => n.id === wire.wireStart)?.isLayer || false;
const wireEnd = ($nodeGraph.nodes.find((n) => n.id === wire.wireEnd)?.isLayer && Number(wire.wireEndInputIndex) == 0) || false;
return [createWirePath(nodeOutput, nodeInput.getBoundingClientRect(), linkStart, linkEnd)];
return [createWirePath(nodeOutput, nodeInput.getBoundingClientRect(), wireStart, wireEnd, wire.dashed)];
});
}
onMount(refreshLinks);
onMount(refreshWires);
function nodeIcon(nodeName: string): IconName {
const iconMap: Record<string, IconName> = {
@ -195,24 +195,24 @@
function buildWirePathLocations(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): { x: number; y: number }[] {
if (!nodesContainer) return [];
const VERTICAL_LINK_OVERLAP_ON_SHAPED_CAP = 1;
const VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP = 1;
const containerBounds = nodesContainer.getBoundingClientRect();
const outX = verticalOut ? outputBounds.x + outputBounds.width / 2 : outputBounds.x + outputBounds.width - 1;
const outY = verticalOut ? outputBounds.y + VERTICAL_LINK_OVERLAP_ON_SHAPED_CAP : outputBounds.y + outputBounds.height / 2;
const outY = verticalOut ? outputBounds.y + VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : outputBounds.y + outputBounds.height / 2;
const outConnectorX = (outX - containerBounds.x) / transform.scale;
const outConnectorY = (outY - containerBounds.y) / transform.scale;
const inX = verticalIn ? inputBounds.x + inputBounds.width / 2 : inputBounds.x + 1;
const inY = verticalIn ? inputBounds.y + inputBounds.height - VERTICAL_LINK_OVERLAP_ON_SHAPED_CAP : inputBounds.y + inputBounds.height / 2;
const inY = verticalIn ? inputBounds.y + inputBounds.height - VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : inputBounds.y + inputBounds.height / 2;
const inConnectorX = (inX - containerBounds.x) / transform.scale;
const inConnectorY = (inY - containerBounds.y) / transform.scale;
const horizontalGap = Math.abs(outConnectorX - inConnectorX);
const verticalGap = Math.abs(outConnectorY - inConnectorY);
// TODO: Finish this commented out code replacement for the code below it based on this diagram: <https://files.keavon.com/-/InsubstantialElegantQueenant/capture.png>
// // Straight: stacking lines which are always straight, or a straight horizontal link between two aligned nodes
// // Straight: stacking lines which are always straight, or a straight horizontal wire between two aligned nodes
// if ((verticalOut && verticalIn) || (!verticalOut && !verticalIn && verticalGap === 0)) {
// return [
// { x: outConnectorX, y: outConnectorY },
@ -259,14 +259,14 @@
.join(" ");
}
function createWirePath(outputPort: SVGSVGElement, inputPort: SVGSVGElement | DOMRect, verticalOut: boolean, verticalIn: boolean): LinkPath {
function createWirePath(outputPort: SVGSVGElement, inputPort: SVGSVGElement | DOMRect, verticalOut: boolean, verticalIn: boolean, dashed: boolean): WirePath {
const inputPortRect = inputPort instanceof DOMRect ? inputPort : inputPort.getBoundingClientRect();
const outputPortRect = outputPort.getBoundingClientRect();
const pathString = buildWirePathString(outputPortRect, inputPortRect, verticalOut, verticalIn);
const dataType = outputPort.getAttribute("data-datatype") || "general";
const dataType = (outputPort.getAttribute("data-datatype") as FrontendGraphDataType) || "General";
return { pathString, dataType, thick: verticalIn && verticalOut };
return { pathString, dataType, thick: verticalIn && verticalOut, dashed };
}
function scroll(e: WheelEvent) {
@ -321,9 +321,9 @@
if (e.key.toLowerCase() === "escape") {
contextMenuOpenCoordinates = undefined;
document.removeEventListener("keydown", keydown);
linkInProgressFromConnector = undefined;
// linkInProgressFromLayerTop = undefined;
// linkInProgressFromLayerBottom = undefined;
wireInProgressFromConnector = undefined;
// wireInProgressFromLayerTop = undefined;
// wireInProgressFromLayerBottom = undefined;
}
}
@ -374,10 +374,10 @@
// Since the user is clicking elsewhere in the graph, ensure the add nodes list is closed
if (lmb) {
contextMenuOpenCoordinates = undefined;
linkInProgressFromConnector = undefined;
wireInProgressFromConnector = undefined;
toggleDisplayAsLayerNodeId = undefined;
// linkInProgressFromLayerTop = undefined;
// linkInProgressFromLayerBottom = undefined;
// wireInProgressFromLayerTop = undefined;
// wireInProgressFromLayerBottom = undefined;
}
// Alt-click sets the clicked node as previewed
@ -390,39 +390,36 @@
const isOutput = Boolean(port.getAttribute("data-port") === "output");
const frontendNode = (nodeId !== undefined && $nodeGraph.nodes.find((n) => n.id === nodeId)) || undefined;
// Output: Begin dragging out a new link
// Output: Begin dragging out a new wire
if (isOutput) {
// Disallow creating additional vertical output links from an already-connected layer
if (frontendNode?.isLayer && frontendNode.primaryOutput?.connected !== undefined) return;
// Disallow creating additional vertical output wires from an already-connected layer
if (frontendNode?.isLayer && frontendNode.primaryOutput && frontendNode.primaryOutput.connected.length > 0) return;
linkInProgressFromConnector = port;
// // Since we are just beginning to drag out a link from the top, we know the in-progress link exists from this layer's top and has no connection to any other layer bottom yet
// linkInProgressFromLayerTop = nodeId !== undefined && frontendNode?.isLayer ? nodeId : undefined;
// linkInProgressFromLayerBottom = undefined;
wireInProgressFromConnector = port;
// // Since we are just beginning to drag out a wire from the top, we know the in-progress wire exists from this layer's top and has no connection to any other layer bottom yet
// wireInProgressFromLayerTop = nodeId !== undefined && frontendNode?.isLayer ? nodeId : undefined;
// wireInProgressFromLayerBottom = undefined;
}
// Input: Begin moving an existing link
// Input: Begin moving an existing wire
else {
const inputNodeInPorts = Array.from(node.querySelectorAll(`[data-port="input"]`));
const inputNodeConnectionIndexSearch = inputNodeInPorts.indexOf(port);
// const isLayerBottomConnector = frontendNode?.isLayer && inputNodeConnectionIndexSearch === 1;
const inputIndex = inputNodeConnectionIndexSearch > -1 ? inputNodeConnectionIndexSearch : undefined;
if (inputIndex === undefined || nodeId === undefined) return;
// Set the link to draw from the input that a previous link was on
// Set the wire to draw from the input that a previous wire was on
const linkIndex = $nodeGraph.links.findIndex((value) => value.linkEnd === nodeId && value.linkEndInputIndex === BigInt(inputIndex));
if (linkIndex === -1) return;
const wireIndex = $nodeGraph.wires.filter((wire) => !wire.dashed).findIndex((value) => value.wireEnd === nodeId && value.wireEndInputIndex === BigInt(inputIndex));
if (wireIndex === -1) return;
const nodeOutputConnectors = nodesContainer?.querySelectorAll(`[data-node="${String($nodeGraph.links[linkIndex].linkStart)}"] [data-port="output"]`) || undefined;
linkInProgressFromConnector = nodeOutputConnectors?.[Number($nodeGraph.links[linkIndex].linkStartOutputIndex)] as SVGSVGElement | undefined;
// linkInProgressFromLayerBottom = isLayerBottomConnector ? frontendNode.exposedInputs[0].connected : undefined;
const nodeOutputConnectors = nodesContainer?.querySelectorAll(`[data-node="${String($nodeGraph.wires[wireIndex].wireStart)}"] [data-port="output"]`) || undefined;
wireInProgressFromConnector = nodeOutputConnectors?.[Number($nodeGraph.wires[wireIndex].wireStartOutputIndex)] as SVGSVGElement | undefined;
const nodeInputConnectors = nodesContainer?.querySelectorAll(`[data-node="${String($nodeGraph.links[linkIndex].linkEnd)}"] [data-port="input"]`) || undefined;
linkInProgressToConnector = nodeInputConnectors?.[Number($nodeGraph.links[linkIndex].linkEndInputIndex)] as SVGSVGElement | undefined;
// linkInProgressFromLayerTop = undefined;
const nodeInputConnectors = nodesContainer?.querySelectorAll(`[data-node="${String($nodeGraph.wires[wireIndex].wireEnd)}"] [data-port="input"]`) || undefined;
wireInProgressToConnector = nodeInputConnectors?.[Number($nodeGraph.wires[wireIndex].wireEndInputIndex)] as SVGSVGElement | undefined;
disconnecting = { nodeId: nodeId, inputIndex, linkIndex };
refreshLinks();
disconnecting = { nodeId: nodeId, inputIndex, wireIndex };
refreshWires();
}
return;
@ -480,26 +477,28 @@
panning = true;
}
function doubleClick(_e: MouseEvent) {
// const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined;
// const nodeId = node?.getAttribute("data-node") || undefined;
// if (nodeId !== undefined) {
// const id = BigInt(nodeId);
// editor.handle.enterNestedNetwork(id);
// }
function doubleClick(e: MouseEvent) {
if ((e.target as HTMLElement).closest("[data-visibility-button]")) return;
const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined;
const nodeId = node?.getAttribute("data-node") || undefined;
if (nodeId !== undefined && !e.altKey) {
const id = BigInt(nodeId);
editor.handle.enterNestedNetwork(id);
}
}
function pointerMove(e: PointerEvent) {
if (panning) {
transform.x += e.movementX / transform.scale;
transform.y += e.movementY / transform.scale;
} else if (linkInProgressFromConnector && !contextMenuOpenCoordinates) {
} else if (wireInProgressFromConnector && !contextMenuOpenCoordinates) {
const target = e.target as Element | undefined;
const dot = (target?.closest(`[data-port="input"]`) || undefined) as SVGSVGElement | undefined;
if (dot) {
linkInProgressToConnector = dot;
wireInProgressToConnector = dot;
} else {
linkInProgressToConnector = new DOMRect(e.x, e.y);
wireInProgressToConnector = new DOMRect(e.x, e.y);
}
} else if (draggingNodes) {
const deltaX = Math.round((e.x - draggingNodes.startX) / transform.scale / GRID_SIZE);
@ -510,7 +509,7 @@
let stop = false;
const refresh = () => {
if (!stop) refreshLinks();
if (!stop) refreshWires();
requestAnimationFrame(refresh);
};
refresh();
@ -560,8 +559,8 @@
return selected.includes(node) || intersetNodeAABB(boxSelect, nodeIndex);
}
function toggleNodeVisibility(id: bigint) {
editor.handle.toggleNodeVisibility(id);
function toggleNodeVisibilityGraph(id: bigint) {
editor.handle.toggleNodeVisibilityGraph(id);
}
function toggleLayerDisplay(displayAsLayer: boolean) {
@ -602,7 +601,7 @@
const selectedNode = nodesContainer?.querySelector(`[data-node="${String(selectedNodeId)}"]`) || undefined;
// Check that neither the primary input or output of the selected node are already connected.
const notConnected = $nodeGraph.links.findIndex((link) => link.linkStart === selectedNodeId || (link.linkEnd === selectedNodeId && link.linkEndInputIndex === BigInt(0))) === -1;
const notConnected = $nodeGraph.wires.findIndex((wire) => wire.wireStart === selectedNodeId || (wire.wireEnd === selectedNodeId && wire.wireEndInputIndex === BigInt(0))) === -1;
const input = selectedNode?.querySelector(`[data-port="input"]`) || undefined;
const output = selectedNode?.querySelector(`[data-port="output"]`) || undefined;
@ -612,9 +611,9 @@
// Fixes typing for some reason?
const theNodesContainer = nodesContainer;
// Find the link that the node has been dragged on top of
const link = $nodeGraph.links.find((link) => {
const { nodeInput, nodeOutput } = resolveLink(link);
// Find the wire that the node has been dragged on top of
const wire = $nodeGraph.wires.find((wire) => {
const { nodeInput, nodeOutput } = resolveWire(wire);
if (!nodeInput || !nodeOutput) return false;
const wireCurveLocations = buildWirePathLocations(nodeOutput.getBoundingClientRect(), nodeInput.getBoundingClientRect(), false, false);
@ -623,7 +622,7 @@
const containerBoundsBounds = theNodesContainer.getBoundingClientRect();
return (
link.linkEnd != selectedNodeId &&
wire.wireEnd != selectedNodeId &&
editor.handle.rectangleIntersects(
new Float64Array(wireCurveLocations.map((loc) => loc.x)),
new Float64Array(wireCurveLocations.map((loc) => loc.y)),
@ -635,11 +634,10 @@
);
});
// If the node has been dragged on top of the link then connect it into the middle.
if (link) {
// If the node has been dragged on top of the wire then connect it into the middle.
if (wire) {
const isLayer = $nodeGraph.nodes.find((n) => n.id === selectedNodeId)?.isLayer;
editor.handle.insertNodeBetween(link.linkEnd, Number(link.linkEndInputIndex), 0, selectedNodeId, 0, Number(link.linkStartOutputIndex), link.linkStart);
editor.handle.insertNodeBetween(wire.wireEnd, Number(wire.wireEndInputIndex), 0, selectedNodeId, 0, Number(wire.wireStartOutputIndex), wire.wireStart);
if (!isLayer) editor.handle.shiftNode(selectedNodeId);
}
}
@ -653,16 +651,16 @@
}
disconnecting = undefined;
if (linkInProgressToConnector instanceof SVGSVGElement && linkInProgressFromConnector) {
const from = connectorToNodeIndex(linkInProgressFromConnector);
const to = connectorToNodeIndex(linkInProgressToConnector);
if (wireInProgressToConnector instanceof SVGSVGElement && wireInProgressFromConnector) {
const from = connectorToNodeIndex(wireInProgressFromConnector);
const to = connectorToNodeIndex(wireInProgressToConnector);
if (from !== undefined && to !== undefined) {
const { nodeId: outputConnectedNodeID, index: outputNodeConnectionIndex } = from;
const { nodeId: inputConnectedNodeID, index: inputNodeConnectionIndex } = to;
editor.handle.connectNodesByLink(outputConnectedNodeID, outputNodeConnectionIndex, inputConnectedNodeID, inputNodeConnectionIndex);
editor.handle.connectNodesByWire(outputConnectedNodeID, outputNodeConnectionIndex, inputConnectedNodeID, inputNodeConnectionIndex);
}
} else if (linkInProgressFromConnector && !initialDisconnecting) {
} else if (wireInProgressFromConnector && !initialDisconnecting) {
// If the add node menu is already open, we don't want to open it again
if (contextMenuOpenCoordinates) return;
@ -674,7 +672,7 @@
if (!contextMenuOpenCoordinates) return;
let contextMenuLocation2: { x: number; y: number } = contextMenuOpenCoordinates;
linkInProgressToConnector = new DOMRect((contextMenuLocation2.x + transform.x) * transform.scale + graphBounds.x, (contextMenuLocation2.y + transform.y) * transform.scale + graphBounds.y);
wireInProgressToConnector = new DOMRect((contextMenuLocation2.x + transform.x) * transform.scale + graphBounds.x, (contextMenuLocation2.y + transform.y) * transform.scale + graphBounds.y);
return;
} else if (draggingNodes) {
@ -695,8 +693,8 @@
boxSelection = undefined;
}
linkInProgressFromConnector = undefined;
linkInProgressToConnector = undefined;
wireInProgressFromConnector = undefined;
wireInProgressToConnector = undefined;
}
function createNode(nodeType: string) {
@ -708,15 +706,15 @@
const inputConnectedNodeID = editor.handle.createNode(nodeType, x, y);
contextMenuOpenCoordinates = undefined;
if (!linkInProgressFromConnector) return;
const from = connectorToNodeIndex(linkInProgressFromConnector);
if (!wireInProgressFromConnector) return;
const from = connectorToNodeIndex(wireInProgressFromConnector);
if (from !== undefined) {
const { nodeId: outputConnectedNodeID, index: outputNodeConnectionIndex } = from;
editor.handle.connectNodesByLink(outputConnectedNodeID, outputNodeConnectionIndex, inputConnectedNodeID, inputNodeConnectionIndex);
editor.handle.connectNodesByWire(outputConnectedNodeID, outputNodeConnectionIndex, inputConnectedNodeID, inputNodeConnectionIndex);
}
linkInProgressFromConnector = undefined;
wireInProgressFromConnector = undefined;
}
function nodeBorderMask(nodeWidth: number, primaryInputExists: boolean, parameters: number, primaryOutputExists: boolean, exposedOutputs: number): string {
@ -766,8 +764,15 @@
}
function dataTypeTooltip(value: FrontendGraphInput | FrontendGraphOutput): string {
const dataTypeCapitalized = `${value.dataType[0].toUpperCase()}${value.dataType.slice(1)}`;
return value.resolvedType ? `Resolved Data: ${value.resolvedType}` : `Unresolved Data: ${dataTypeCapitalized}`;
return value.resolvedType ? `Resolved Data: ${value.resolvedType}` : `Unresolved Data: ${value.dataType}`;
}
function connectedToText(output: FrontendGraphOutput): string {
if (output.connected.length === 0) {
return "Connected to nothing";
} else {
return output.connected.map((nodeId, index) => `Connected to ${nodeId}, port index ${output.connectedIndex[index]}`).join("\n");
}
}
</script>
@ -784,6 +789,7 @@
style:--grid-offset-y={`${transform.y * transform.scale}px`}
style:--dot-radius={`${dotRadius}px`}
>
<BreadcrumbTrailButtons labels={["Document"].concat($nodeGraph.subgraphPath)} action={(index) => editor.handle.exitNestedNetwork($nodeGraph.subgraphPath?.length - index)} />
<!-- Right click menu for adding nodes -->
{#if contextMenuOpenCoordinates}
<LayoutCol
@ -844,11 +850,17 @@
{/if}
</LayoutCol>
{/if}
<!-- Node connection links -->
<!-- Node connection wires -->
<div class="wires" style:transform={`scale(${transform.scale}) translate(${transform.x}px, ${transform.y}px)`} style:transform-origin={`0 0`}>
<svg>
{#each linkPaths as { pathString, dataType, thick }}
<path d={pathString} style:--data-line-width={`${thick ? 8 : 2}px`} style:--data-color={`var(--color-data-${dataType})`} style:--data-color-dim={`var(--color-data-${dataType}-dim)`} />
{#each wirePaths as { pathString, dataType, thick, dashed }}
<path
d={pathString}
style:--data-line-width={`${thick ? 8 : 2}px`}
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
style:--data-dasharray={`3,${dashed ? 2 : 0}`}
/>
{/each}
</svg>
</div>
@ -868,8 +880,8 @@
style:--offset-left={(node.position?.x || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0)}
style:--offset-top={(node.position?.y || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0)}
style:--clip-path-id={`url(#${clipPathId})`}
style:--data-color={`var(--color-data-${node.primaryOutput?.dataType || "general"})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput?.dataType || "general"}-dim)`}
style:--data-color={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
style:--label-width={labelWidthGridCells}
style:--node-chain-area-left-extension={node.exposedInputs.length === 0 ? 0 : 1.5}
data-node={node.id}
@ -891,14 +903,14 @@
class="port top"
data-port="output"
data-datatype={node.primaryOutput.dataType}
style:--data-color={`var(--color-data-${node.primaryOutput.dataType})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType}-dim)`}
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
bind:this={outputs[nodeIndex][0]}
>
<title>{`${dataTypeTooltip(node.primaryOutput)}\nConnected to ${`${node.primaryOutput.connected}, port index ${node.primaryOutput.connectedIndex}` || "nothing"}`}</title>
{#if node.primaryOutput.connected}
<title>{`${dataTypeTooltip(node.primaryOutput)}\n${connectedToText(node.primaryOutput)}`}</title>
{#if node.primaryOutput.connected.length > 0}
<path d="M0,6.953l2.521,-1.694a2.649,2.649,0,0,1,2.959,0l2.52,1.694v5.047h-8z" fill="var(--data-color)" />
{#if Number(node.primaryOutput?.connectedIndex) === 0 && $nodeGraph.nodes.find((n) => n.id === node.primaryOutput?.connected)?.isLayer}
{#if Number(node.primaryOutput?.connectedIndex) === 0 && $nodeGraph.nodes.find((n) => node.primaryOutput?.connected.includes(n.id))?.isLayer}
<path d="M0,-3.5h8v8l-2.521,-1.681a2.666,2.666,0,0,0,-2.959,0l-2.52,1.681z" fill="var(--data-color-dim)" />
{/if}
{:else}
@ -913,14 +925,14 @@
class="port bottom"
data-port="input"
data-datatype={node.primaryInput?.dataType}
style:--data-color={`var(--color-data-${node.primaryInput?.dataType})`}
style:--data-color-dim={`var(--color-data-${node.primaryInput?.dataType}-dim)`}
style:--data-color={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex][0]}
>
{#if node.primaryInput}
<title>{`${dataTypeTooltip(node.primaryInput)}\nConnected to ${node.primaryInput?.connected || "nothing"}`}</title>
<title>{`${dataTypeTooltip(node.primaryInput)}\nConnected to ${node.primaryInput?.connected !== undefined ? node.primaryInput.connected : "nothing"}`}</title>
{/if}
{#if node.primaryInput?.connected}
{#if node.primaryInput?.connected !== undefined}
<path d="M0,0H8V8L5.479,6.319a2.666,2.666,0,0,0-2.959,0L0,8Z" fill="var(--data-color)" />
{#if $nodeGraph.nodes.find((n) => n.id === node.primaryInput?.connected)?.isLayer}
<path d="M0,10.95l2.52,-1.69c0.89,-0.6,2.06,-0.6,2.96,0l2.52,1.69v5.05h-8v-5.05z" fill="var(--data-color-dim)" />
@ -939,12 +951,12 @@
class="port"
data-port="input"
data-datatype={stackDataInput.dataType}
style:--data-color={`var(--color-data-${stackDataInput.dataType})`}
style:--data-color-dim={`var(--color-data-${stackDataInput.dataType}-dim)`}
style:--data-color={`var(--color-data-${stackDataInput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex][1]}
>
<title>{`${dataTypeTooltip(stackDataInput)}\nConnected to ${stackDataInput.connected || "nothing"}`}</title>
{#if stackDataInput.connected}
<title>{`${dataTypeTooltip(stackDataInput)}\nConnected to ${stackDataInput.connected !== undefined ? stackDataInput.connected : "nothing"}`}</title>
{#if stackDataInput.connected !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color-dim)" />
@ -960,7 +972,8 @@
</div>
<IconButton
class={"visibility"}
action={(e) => (toggleNodeVisibility(node.id), e?.stopPropagation())}
data-visibility-button
action={(e) => (toggleNodeVisibilityGraph(node.id), e?.stopPropagation())}
size={24}
icon={node.visible ? "EyeVisible" : "EyeHidden"}
tooltip={node.visible ? "Visible" : "Hidden"}
@ -991,8 +1004,8 @@
style:--offset-left={(node.position?.x || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0)}
style:--offset-top={(node.position?.y || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0)}
style:--clip-path-id={`url(#${clipPathId})`}
style:--data-color={`var(--color-data-${node.primaryOutput?.dataType || "general"})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput?.dataType || "general"}-dim)`}
style:--data-color={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
data-node={node.id}
bind:this={nodeElements[nodeIndex]}
>
@ -1025,12 +1038,12 @@
class="port primary-port"
data-port="input"
data-datatype={node.primaryInput?.dataType}
style:--data-color={`var(--color-data-${node.primaryInput?.dataType})`}
style:--data-color-dim={`var(--color-data-${node.primaryInput?.dataType}-dim)`}
style:--data-color={`var(--color-data-${node.primaryInput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex][0]}
>
<title>{`${dataTypeTooltip(node.primaryInput)}\nConnected to ${node.primaryInput.connected || "nothing"}`}</title>
{#if node.primaryInput.connected}
<title>{`${dataTypeTooltip(node.primaryInput)}\nConnected to ${node.primaryInput.connected !== undefined ? node.primaryInput.connected : "nothing"}`}</title>
{#if node.primaryInput.connected !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color-dim)" />
@ -1045,12 +1058,12 @@
class="port"
data-port="input"
data-datatype={parameter.dataType}
style:--data-color={`var(--color-data-${parameter.dataType})`}
style:--data-color-dim={`var(--color-data-${parameter.dataType}-dim)`}
style:--data-color={`var(--color-data-${parameter.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${parameter.dataType.toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex][index + (node.primaryInput ? 1 : 0)]}
>
<title>{`${dataTypeTooltip(parameter)}\nConnected to ${parameter.connected || "nothing"}`}</title>
{#if parameter.connected}
<title>{`${dataTypeTooltip(parameter)}\nConnected to ${parameter.connected !== undefined ? parameter.connected : "nothing"}`}</title>
{#if parameter.connected !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color-dim)" />
@ -1068,12 +1081,12 @@
class="port primary-port"
data-port="output"
data-datatype={node.primaryOutput.dataType}
style:--data-color={`var(--color-data-${node.primaryOutput.dataType})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType}-dim)`}
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
bind:this={outputs[nodeIndex][0]}
>
<title>{`${dataTypeTooltip(node.primaryOutput)}\nConnected to ${`${node.primaryOutput.connected}, port index ${node.primaryOutput.connectedIndex}` || "nothing"}`}</title>
{#if node.primaryOutput.connected}
<title>{`${dataTypeTooltip(node.primaryOutput)}\n${connectedToText(node.primaryOutput)}`}</title>
{#if node.primaryOutput.connected !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color-dim)" />
@ -1087,12 +1100,12 @@
class="port"
data-port="output"
data-datatype={parameter.dataType}
style:--data-color={`var(--color-data-${parameter.dataType})`}
style:--data-color-dim={`var(--color-data-${parameter.dataType}-dim)`}
style:--data-color={`var(--color-data-${parameter.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${parameter.dataType.toLowerCase()}-dim)`}
bind:this={outputs[nodeIndex][outputIndex + (node.primaryOutput ? 1 : 0)]}
>
<title>{`${dataTypeTooltip(parameter)}\nConnected to ${`${parameter.connected}, port index ${parameter.connectedIndex}` || "nothing"}`}</title>
{#if parameter.connected}
<title>{`${dataTypeTooltip(parameter)}\n${connectedToText(parameter)}`}</title>
{#if parameter.connected !== undefined}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color)" />
{:else}
<path d="M0,6.306A1.474,1.474,0,0,0,2.356,7.724L7.028,5.248c1.3-.687,1.3-1.809,0-2.5L2.356.276A1.474,1.474,0,0,0,0,1.694Z" fill="var(--data-color-dim)" />
@ -1134,11 +1147,6 @@
flex-direction: row;
flex-grow: 1;
> img {
position: absolute;
bottom: 0;
}
// We're displaying the dotted grid in a pseudo-element because `image-rendering` is an inherited property and we don't want it to apply to child elements
&::before {
content: "";
@ -1147,11 +1155,23 @@
height: 100%;
background-size: var(--grid-spacing) var(--grid-spacing);
background-position: calc(var(--grid-offset-x) - var(--dot-radius)) calc(var(--grid-offset-y) - var(--dot-radius));
background-image: radial-gradient(circle at var(--dot-radius) var(--dot-radius), var(--color-3-darkgray) var(--dot-radius), transparent 0);
background-image: radial-gradient(circle at var(--dot-radius) var(--dot-radius), var(--color-f-white) var(--dot-radius), transparent 0),
radial-gradient(circle at var(--dot-radius) var(--dot-radius), var(--color-3-darkgray) var(--dot-radius), transparent 0);
background-repeat: no-repeat, repeat;
image-rendering: pixelated;
mix-blend-mode: screen;
}
> img {
position: absolute;
bottom: 0;
}
.breadcrumb-trail-buttons {
margin-top: 8px;
margin-left: 8px;
}
.context-menu {
width: max-content;
position: absolute;
@ -1235,6 +1255,7 @@
fill: none;
stroke: var(--data-color-dim);
stroke-width: var(--data-line-width);
stroke-dasharray: var(--data-dasharray);
}
}
}

View file

@ -41,7 +41,7 @@
hoverIcon={widgetData.visible ? "EyeHide" : "EyeShow"}
size={24}
action={(e) => {
editor.handle.toggleNodeVisibility(widgetData.id);
editor.handle.toggleNodeVisibilityLayerPanel(widgetData.id);
e?.stopPropagation();
}}
class={widgetData.visible ? "show-only-on-hover" : ""}

View file

@ -1,15 +1,24 @@
<script lang="ts">
import type { FrontendGraphDataType } from "@graphite/wasm-communication/messages";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
export let exposed: boolean;
export let dataType: string;
export let dataType: FrontendGraphDataType;
export let tooltip: string | undefined = undefined;
// Callbacks
export let action: (e?: MouseEvent) => void;
</script>
<LayoutRow class="parameter-expose-button">
<button class:exposed style:--data-type-color={`var(--color-data-${dataType})`} style:--data-type-color-dim={`var(--color-data-${dataType}-dim)`} on:click={action} title={tooltip} tabindex="-1">
<button
class:exposed
style:--data-type-color={`var(--color-data-${dataType.toLowerCase()})`}
style:--data-type-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
on:click={action}
title={tooltip}
tabindex="-1"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">
<path class="interior" d="M0,7.882c0,1.832,1.325,2.63,2.945,1.772L8.785,6.56c1.62-.858,1.62-2.262,0-3.12L2.945.345C1.325-.512,0,.285,0,2.118Z" />
<path

View file

@ -3,38 +3,39 @@ import { writable } from "svelte/store";
import { type Editor } from "@graphite/wasm-communication/editor";
import {
type FrontendNode,
type FrontendNodeLink,
type FrontendNodeWire as FrontendNodeWire,
type FrontendNodeType,
UpdateNodeGraph,
UpdateNodeGraphSelection,
UpdateNodeTypes,
UpdateNodeThumbnail,
UpdateSubgraphPath,
UpdateZoomWithScroll,
UpdateNodeGraphSelection,
} from "@graphite/wasm-communication/messages";
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function createNodeGraphState(editor: Editor) {
const { subscribe, update } = writable({
nodes: [] as FrontendNode[],
links: [] as FrontendNodeLink[],
wires: [] as FrontendNodeWire[],
nodeTypes: [] as FrontendNodeType[],
zoomWithScroll: false as boolean,
thumbnails: new Map<bigint, string>(),
selected: [] as bigint[],
subgraphPath: [] as string[],
});
// Set up message subscriptions on creation
editor.subscriptions.subscribeJsMessage(UpdateNodeGraph, (updateNodeGraph) => {
update((state) => {
state.nodes = updateNodeGraph.nodes;
state.links = updateNodeGraph.links;
const newThumbnails = new Map<bigint, string>();
// Transfer over any preexisting thumbnails from itself
state.nodes.forEach((node) => {
const thumbnail = state.thumbnails.get(node.id);
if (thumbnail) newThumbnails.set(node.id, thumbnail);
state.wires = updateNodeGraph.wires;
return state;
});
state.thumbnails = newThumbnails;
});
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphSelection, (updateNodeGraphSelection) => {
update((state) => {
state.selected = updateNodeGraphSelection.selected;
return state;
});
});
@ -50,15 +51,15 @@ export function createNodeGraphState(editor: Editor) {
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateZoomWithScroll, (updateZoomWithScroll) => {
editor.subscriptions.subscribeJsMessage(UpdateSubgraphPath, (UpdateSubgraphPath) => {
update((state) => {
state.zoomWithScroll = updateZoomWithScroll.zoomWithScroll;
state.subgraphPath = UpdateSubgraphPath.subgraphPath;
return state;
});
});
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphSelection, (updateNodeGraphSelection) => {
editor.subscriptions.subscribeJsMessage(UpdateZoomWithScroll, (updateZoomWithScroll) => {
update((state) => {
state.selected = updateNodeGraphSelection.selected;
state.zoomWithScroll = updateZoomWithScroll.zoomWithScroll;
return state;
});
});

View file

@ -29,8 +29,8 @@ export class UpdateNodeGraph extends JsMessage {
@Type(() => FrontendNode)
readonly nodes!: FrontendNode[];
@Type(() => FrontendNodeLink)
readonly links!: FrontendNodeLink[];
@Type(() => FrontendNodeWire)
readonly wires!: FrontendNodeWire[];
}
export class UpdateNodeTypes extends JsMessage {
@ -54,6 +54,10 @@ export class UpdateOpenDocumentsList extends JsMessage {
readonly openDocuments!: FrontendDocumentDetails[];
}
export class UpdateSubgraphPath extends JsMessage {
readonly subgraphPath!: string[];
}
export class UpdateZoomWithScroll extends JsMessage {
readonly zoomWithScroll!: boolean;
}
@ -80,7 +84,7 @@ export class FrontendDocumentDetails extends DocumentDetails {
readonly id!: bigint;
}
export type FrontendGraphDataType = "general" | "number" | "raster" | "vector" | "color" | "artboard";
export type FrontendGraphDataType = "General" | "Raster" | "VectorData" | "Number" | "Graphic" | "Artboard";
export class FrontendGraphInput {
readonly dataType!: FrontendGraphDataType;
@ -99,9 +103,9 @@ export class FrontendGraphOutput {
readonly resolvedType!: string | undefined;
readonly connected!: bigint | undefined;
readonly connected!: bigint[];
readonly connectedIndex!: bigint | undefined;
readonly connectedIndex!: bigint[];
}
export class FrontendNode {
@ -133,16 +137,20 @@ export class FrontendNode {
readonly unlocked!: boolean;
readonly errors!: string | undefined;
readonly uiOnly!: boolean;
}
export class FrontendNodeLink {
readonly linkStart!: bigint;
export class FrontendNodeWire {
readonly wireStart!: bigint;
readonly linkStartOutputIndex!: bigint;
readonly wireStartOutputIndex!: bigint;
readonly linkEnd!: bigint;
readonly wireEnd!: bigint;
readonly linkEndInputIndex!: bigint;
readonly wireEndInputIndex!: bigint;
readonly dashed!: boolean;
}
export class FrontendNodeType {
@ -935,7 +943,7 @@ export class TextAreaInput extends WidgetProps {
export class ParameterExposeButton extends WidgetProps {
exposed!: boolean;
dataType!: string;
dataType!: FrontendGraphDataType;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
@ -1335,6 +1343,7 @@ export const messageMakers: Record<string, MessageMaker> = {
UpdateOpenDocumentsList,
UpdatePropertyPanelOptionsLayout,
UpdatePropertyPanelSectionsLayout,
UpdateSubgraphPath,
UpdateToolOptionsLayout,
UpdateToolShelfLayout,
UpdateWorkingColorsLayout,

View file

@ -532,8 +532,8 @@ impl EditorHandle {
/// Set the name for the layer
#[wasm_bindgen(js_name = setLayerName)]
pub fn set_layer_name(&self, id: u64, name: String) {
let id = NodeId(id);
let message = NodeGraphMessage::SetName { node_id: id, name };
let layer = LayerNodeIdentifier::new_unchecked(NodeId(id));
let message = GraphOperationMessage::SetName { layer, name };
self.dispatch(message);
}
@ -552,11 +552,11 @@ impl EditorHandle {
}
/// Notifies the backend that the user connected a node's primary output to one of another node's inputs
#[wasm_bindgen(js_name = connectNodesByLink)]
pub fn connect_nodes_by_link(&self, output_node: u64, output_node_connector_index: usize, input_node: u64, input_node_connector_index: usize) {
#[wasm_bindgen(js_name = connectNodesByWire)]
pub fn connect_nodes_by_wire(&self, output_node: u64, output_node_connector_index: usize, input_node: u64, input_node_connector_index: usize) {
let output_node = NodeId(output_node);
let input_node = NodeId(input_node);
let message = NodeGraphMessage::ConnectNodesByLink {
let message = NodeGraphMessage::ConnectNodesByWire {
output_node,
output_node_connector_index,
input_node,
@ -601,7 +601,7 @@ impl EditorHandle {
#[wasm_bindgen(js_name = disconnectNodes)]
pub fn disconnect_nodes(&self, node_id: u64, input_index: usize) {
let node_id = NodeId(node_id);
let message = NodeGraphMessage::DisconnectNodes { node_id, input_index };
let message = NodeGraphMessage::DisconnectInput { node_id, input_index };
self.dispatch(message);
}
@ -649,6 +649,13 @@ impl EditorHandle {
self.dispatch(message);
}
/// Go back a certain number of nested levels
#[wasm_bindgen(js_name = exitNestedNetwork)]
pub fn exit_nested_network(&self, steps_back: usize) {
let message = NodeGraphMessage::ExitNestedNetwork { steps_back };
self.dispatch(message);
}
/// Notifies the backend that the selected nodes have been moved
#[wasm_bindgen(js_name = moveSelectedNodes)]
pub fn move_selected_nodes(&self, displacement_x: i32, displacement_y: i32) {
@ -684,8 +691,16 @@ impl EditorHandle {
}
/// Toggle visibility of a layer or node given its node ID
#[wasm_bindgen(js_name = toggleNodeVisibility)]
pub fn toggle_node_visibility(&self, id: u64) {
#[wasm_bindgen(js_name = toggleNodeVisibilityLayerPanel)]
pub fn toggle_node_visibility_layer(&self, id: u64) {
let node_id = NodeId(id);
let message = GraphOperationMessage::ToggleVisibility { node_id };
self.dispatch(message);
}
/// Toggle visibility of a layer or node given its node ID
#[wasm_bindgen(js_name = toggleNodeVisibilityGraph)]
pub fn toggle_node_visibility_graph(&self, id: u64) {
let node_id = NodeId(id);
let message = NodeGraphMessage::ToggleVisibility { node_id };
self.dispatch(message);
@ -698,15 +713,15 @@ impl EditorHandle {
self.dispatch(message);
let id = NodeId(id);
let message = DocumentMessage::DeleteLayer { id };
let message = NodeGraphMessage::DeleteNodes { node_ids: vec![id], reconnect: true };
self.dispatch(message);
}
/// Toggle lock state of a layer from the layer list
#[wasm_bindgen(js_name = toggleLayerLock)]
pub fn toggle_layer_lock(&self, id: u64) {
let id = NodeId(id);
let message = NodeGraphMessage::ToggleLocked { node_id: id };
let node_id = NodeId(id);
let message = GraphOperationMessage::ToggleLocked { node_id };
self.dispatch(message);
}

View file

@ -346,10 +346,10 @@ impl<ManipulatorGroupId: crate::Identifier> Subpath<ManipulatorGroupId> {
pub fn solve_spline_first_handle(points: &[DVec2]) -> Vec<DVec2> {
let len_points = points.len();
// matrix coefficients a, b and c (see https://mathworld.wolfram.com/CubicSpline.html)
// because the 'a' coefficients are all 1 they need not be stored
// this algorithm does a variation of the above algorithm.
// Instead of using the traditional cubic: a + bt + ct^2 + dt^3, we use the bezier cubic.
// Matrix coefficients a, b and c (see https://mathworld.wolfram.com/CubicSpline.html).
// Because the 'a' coefficients are all 1, they need not be stored.
// This algorithm does a variation of the above algorithm.
// Instead of using the traditional cubic (a + bt + ct^2 + dt^3), we use the bezier cubic.
let mut b = vec![DVec2::new(4., 4.); len_points];
b[0] = DVec2::new(2., 2.);
@ -367,21 +367,21 @@ pub fn solve_spline_first_handle(points: &[DVec2]) -> Vec<DVec2> {
}
// Solve with Thomas algorithm (see https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm)
// do row operations to eliminate `a` coefficients
// Now we do row operations to eliminate `a` coefficients.
c[0] /= -b[0];
d[0] /= -b[0];
#[allow(clippy::assign_op_pattern)]
for i in 1..len_points {
b[i] += c[i - 1];
// for some reason the below line makes the borrow checker mad
// For some reason this `+=` version makes the borrow checker mad:
// d[i] += d[i-1]
d[i] = d[i] + d[i - 1];
c[i] /= -b[i];
d[i] /= -b[i];
}
// at this point b[i] == -a[i + 1], a[i] == 0,
// do row operations to eliminate 'c' coefficients and solve
// At this point b[i] == -a[i + 1] and a[i] == 0.
// Now we do row operations to eliminate 'c' coefficients and solve.
d[len_points - 1] *= -1.;
#[allow(clippy::assign_op_pattern)]
for i in (0..len_points - 1).rev() {

View file

@ -37,9 +37,7 @@ fn main() {
fn add_network() -> NodeNetwork {
NodeNetwork {
imports: vec![],
exports: vec![NodeOutput::new(NodeId(0), 0)],
previous_outputs: None,
exports: vec![NodeInput::node(NodeId(0), 0)],
nodes: [DocumentNode {
name: "Blend Image".into(),
inputs: vec![NodeInput::Inline(InlineRust::new(
@ -67,5 +65,6 @@ fn add_network() -> NodeNetwork {
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}
}

View file

@ -3,3 +3,7 @@ use crate::raster::Color;
// RENDERING
pub const LAYER_OUTLINE_STROKE_COLOR: Color = Color::BLACK;
pub const LAYER_OUTLINE_STROKE_WEIGHT: f64 = 1.;
// Fonts
pub const DEFAULT_FONT_FAMILY: &str = "Cabin";
pub const DEFAULT_FONT_STYLE: &str = "Normal (400)";

View file

@ -134,16 +134,16 @@ impl ArtboardGroup {
}
}
pub struct ConstructLayerNode<GraphicElement, Stack> {
graphic_element: GraphicElement,
pub struct ConstructLayerNode<Stack, GraphicElement> {
stack: Stack,
graphic_element: GraphicElement,
}
#[node_fn(ConstructLayerNode)]
async fn construct_layer<Data: Into<GraphicElement>, Fut1: Future<Output = Data>, Fut2: Future<Output = GraphicGroup>>(
async fn construct_layer<Data: Into<GraphicElement>, Fut1: Future<Output = GraphicGroup>, Fut2: Future<Output = Data>>(
footprint: crate::transform::Footprint,
graphic_element: impl Node<crate::transform::Footprint, Output = Fut1>,
mut stack: impl Node<crate::transform::Footprint, Output = Fut2>,
mut stack: impl Node<crate::transform::Footprint, Output = Fut1>,
graphic_element: impl Node<crate::transform::Footprint, Output = Fut2>,
) -> GraphicGroup {
let graphic_element = self.graphic_element.eval(footprint).await;
let mut stack = self.stack.eval(footprint).await;
@ -192,16 +192,16 @@ async fn construct_artboard<Fut: Future<Output = GraphicGroup>>(
clip,
}
}
pub struct AddArtboardNode<Artboard, ArtboardGroup> {
artboard: Artboard,
pub struct AddArtboardNode<ArtboardGroup, Artboard> {
artboards: ArtboardGroup,
artboard: Artboard,
}
#[node_fn(AddArtboardNode)]
async fn add_artboard<Data: Into<Artboard>, Fut1: Future<Output = Data>, Fut2: Future<Output = ArtboardGroup>>(
async fn add_artboard<Data: Into<Artboard>, Fut1: Future<Output = ArtboardGroup>, Fut2: Future<Output = Data>>(
footprint: Footprint,
artboard: impl Node<Footprint, Output = Fut1>,
mut artboards: impl Node<Footprint, Output = Fut2>,
artboards: impl Node<Footprint, Output = Fut1>,
artboard: impl Node<Footprint, Output = Fut2>,
) -> ArtboardGroup {
let artboard = self.artboard.eval(footprint).await;
let mut artboards = self.artboards.eval(footprint).await;

View file

@ -177,7 +177,7 @@ impl<T> LetNode<T> {
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct EndLetNode<Input, Parameter> {
input: Input,
paramenter: PhantomData<Parameter>,
parameter: PhantomData<Parameter>,
}
impl<'i, T: 'i, Parameter: 'i + From<T>, Input> Node<'i, T> for EndLetNode<Input, Parameter>
where
@ -192,7 +192,7 @@ where
impl<Input, Parameter> EndLetNode<Input, Parameter> {
pub const fn new(input: Input) -> EndLetNode<Input, Parameter> {
EndLetNode { input, paramenter: PhantomData }
EndLetNode { input, parameter: PhantomData }
}
}

View file

@ -431,7 +431,7 @@ mod test {
assert_eq!(value.eval(()), Ok(&4u32));
// let type_erased_clone = clone as &dyn for<'a> Node<'a, &'a u32, Output = u32>;
let map_result = MapResultNode::new(ValueNode::new(FnNode::new(|x: &u32| *x)));
//et type_erased = &map_result as &dyn for<'a> Node<'a, Result<&'a u32, ()>, Output = Result<u32, ()>>;
// let type_erased = &map_result as &dyn for<'a> Node<'a, Result<&'a u32, ()>, Output = Result<u32, ()>>;
assert_eq!(map_result.eval(Ok(&4u32)), Ok(4u32));
let fst = value.then(map_result);
// let type_erased = &fst as &dyn for<'a> Node<'a, (), Output = Result<u32, ()>>;
@ -439,7 +439,7 @@ mod test {
}
#[test]
pub fn flat_map_result() {
let fst = ValueNode(Ok(&4u32)).then(CloneNode::new()); //.then(FlatMapResultNode::new(FnNode::new(|x| Ok(x))));
let fst = ValueNode(Ok(&4u32)).then(CloneNode::new());
let fn_node: FnNode<_, &u32, Result<&u32, _>> = FnNode::new(|_| Err(8u32));
assert_eq!(fn_node.eval(&4u32), Err(8u32));
let flat_map = FlatMapResultNode::new(ValueNode::new(fn_node));

View file

@ -214,6 +214,15 @@ impl Type {
Self::Future(_) => None,
}
}
pub fn nested_type(self) -> Type {
match self {
Self::Generic(_) => self,
Self::Concrete(_) => self,
Self::Fn(_, output) => output.nested_type(),
Self::Future(_) => self,
}
}
}
fn format_type(ty: &str) -> String {

View file

@ -7,6 +7,7 @@ extern crate spirv_std;
// #[cfg(target_arch = "spirv")]
// pub mod gpu {
// use super::*;
use spirv_std::spirv;
use spirv_std::glam;
use spirv_std::glam::{UVec3, Vec2, Mat2, BVec2};
@ -39,4 +40,5 @@ extern crate spirv_std;
{% endfor %}
// TODO: Write output to buffer
}
// }

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,7 @@ use graphene_core::{Color, Node, Type};
use dyn_any::DynAny;
pub use dyn_any::StaticType;
pub use glam::{DAffine2, DVec2, UVec2};
pub use glam::{DAffine2, DVec2, IVec2, UVec2};
use std::hash::Hash;
pub use std::sync::Arc;
@ -25,6 +25,7 @@ pub enum TaggedValue {
F64(f64),
Bool(bool),
UVec2(UVec2),
IVec2(IVec2),
DVec2(DVec2),
OptionalDVec2(Option<DVec2>),
DAffine2(DAffine2),
@ -69,10 +70,9 @@ pub enum TaggedValue {
Segments(Vec<graphene_core::raster::ImageFrame<Color>>),
DocumentNode(DocumentNode),
GraphicGroup(graphene_core::GraphicGroup),
Artboard(graphene_core::Artboard),
GraphicElement(graphene_core::GraphicElement),
ArtboardGroup(graphene_core::ArtboardGroup),
Curve(graphene_core::raster::curve::Curve),
IVec2(glam::IVec2),
SurfaceFrame(graphene_core::SurfaceFrame),
Footprint(graphene_core::transform::Footprint),
RenderOutput(RenderOutput),
@ -93,6 +93,7 @@ impl Hash for TaggedValue {
Self::F64(x) => x.to_bits().hash(state),
Self::Bool(x) => x.hash(state),
Self::UVec2(x) => x.to_array().iter().for_each(|x| x.hash(state)),
Self::IVec2(x) => x.hash(state),
Self::DVec2(x) => x.to_array().iter().for_each(|x| x.to_bits().hash(state)),
Self::OptionalDVec2(None) => 0.hash(state),
Self::OptionalDVec2(Some(x)) => {
@ -150,10 +151,9 @@ impl Hash for TaggedValue {
}
Self::DocumentNode(x) => x.hash(state),
Self::GraphicGroup(x) => x.hash(state),
Self::Artboard(x) => x.hash(state),
Self::GraphicElement(x) => x.hash(state),
Self::ArtboardGroup(x) => x.hash(state),
Self::Curve(x) => x.hash(state),
Self::IVec2(x) => x.hash(state),
Self::SurfaceFrame(x) => x.hash(state),
Self::Footprint(x) => x.hash(state),
Self::RenderOutput(x) => x.hash(state),
@ -175,6 +175,7 @@ impl<'a> TaggedValue {
TaggedValue::F64(x) => Box::new(x),
TaggedValue::Bool(x) => Box::new(x),
TaggedValue::UVec2(x) => Box::new(x),
TaggedValue::IVec2(x) => Box::new(x),
TaggedValue::DVec2(x) => Box::new(x),
TaggedValue::OptionalDVec2(x) => Box::new(x),
TaggedValue::DAffine2(x) => Box::new(x),
@ -218,10 +219,9 @@ impl<'a> TaggedValue {
TaggedValue::Segments(x) => Box::new(x),
TaggedValue::DocumentNode(x) => Box::new(x),
TaggedValue::GraphicGroup(x) => Box::new(x),
TaggedValue::Artboard(x) => Box::new(x),
TaggedValue::GraphicElement(x) => Box::new(x),
TaggedValue::ArtboardGroup(x) => Box::new(x),
TaggedValue::Curve(x) => Box::new(x),
TaggedValue::IVec2(x) => Box::new(x),
TaggedValue::SurfaceFrame(x) => Box::new(x),
TaggedValue::Footprint(x) => Box::new(x),
TaggedValue::RenderOutput(x) => Box::new(x),
@ -254,6 +254,7 @@ impl<'a> TaggedValue {
TaggedValue::F64(_) => concrete!(f64),
TaggedValue::Bool(_) => concrete!(bool),
TaggedValue::UVec2(_) => concrete!(UVec2),
TaggedValue::IVec2(_) => concrete!(IVec2),
TaggedValue::DVec2(_) => concrete!(DVec2),
TaggedValue::OptionalDVec2(_) => concrete!(Option<DVec2>),
TaggedValue::Image(_) => concrete!(graphene_core::raster::Image<Color>),
@ -297,10 +298,9 @@ impl<'a> TaggedValue {
TaggedValue::Segments(_) => concrete!(graphene_core::raster::IndexNode<Vec<graphene_core::raster::ImageFrame<Color>>>),
TaggedValue::DocumentNode(_) => concrete!(crate::document::DocumentNode),
TaggedValue::GraphicGroup(_) => concrete!(graphene_core::GraphicGroup),
TaggedValue::Artboard(_) => concrete!(graphene_core::Artboard),
TaggedValue::GraphicElement(_) => concrete!(graphene_core::GraphicElement),
TaggedValue::ArtboardGroup(_) => concrete!(graphene_core::ArtboardGroup),
TaggedValue::Curve(_) => concrete!(graphene_core::raster::curve::Curve),
TaggedValue::IVec2(_) => concrete!(glam::IVec2),
TaggedValue::SurfaceFrame(_) => concrete!(graphene_core::SurfaceFrame),
TaggedValue::Footprint(_) => concrete!(graphene_core::transform::Footprint),
TaggedValue::RenderOutput(_) => concrete!(RenderOutput),
@ -322,6 +322,7 @@ impl<'a> TaggedValue {
x if x == TypeId::of::<f64>() => Ok(TaggedValue::F64(*downcast(input).unwrap())),
x if x == TypeId::of::<bool>() => Ok(TaggedValue::Bool(*downcast(input).unwrap())),
x if x == TypeId::of::<UVec2>() => Ok(TaggedValue::UVec2(*downcast(input).unwrap())),
x if x == TypeId::of::<IVec2>() => Ok(TaggedValue::IVec2(*downcast(input).unwrap())),
x if x == TypeId::of::<DVec2>() => Ok(TaggedValue::DVec2(*downcast(input).unwrap())),
x if x == TypeId::of::<Option<DVec2>>() => Ok(TaggedValue::OptionalDVec2(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::Image<Color>>() => Ok(TaggedValue::Image(*downcast(input).unwrap())),
@ -364,8 +365,8 @@ impl<'a> TaggedValue {
x if x == TypeId::of::<graphene_core::raster::IndexNode<Vec<graphene_core::raster::ImageFrame<Color>>>>() => Ok(TaggedValue::Segments(*downcast(input).unwrap())),
x if x == TypeId::of::<crate::document::DocumentNode>() => Ok(TaggedValue::DocumentNode(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::GraphicGroup>() => Ok(TaggedValue::GraphicGroup(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::Artboard>() => Ok(TaggedValue::Artboard(*downcast(input).unwrap())),
x if x == TypeId::of::<glam::IVec2>() => Ok(TaggedValue::IVec2(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::GraphicElement>() => Ok(TaggedValue::GraphicElement(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::ArtboardGroup>() => Ok(TaggedValue::ArtboardGroup(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::SurfaceFrame>() => Ok(TaggedValue::SurfaceFrame(*downcast(input).unwrap())),
x if x == TypeId::of::<RenderOutput>() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::WasmSurfaceHandleFrame>() => {
@ -379,6 +380,93 @@ impl<'a> TaggedValue {
_ => Err(format!("Cannot convert {:?} to TaggedValue", DynAny::type_name(input.as_ref()))),
}
}
pub fn from_type(input: &Type) -> Self {
match input {
Type::Generic(_) => {
log::warn!("Generic type should be resolved");
TaggedValue::None
}
Type::Concrete(concrete_type) => {
let Some(internal_id) = concrete_type.id else {
return TaggedValue::None;
};
use std::any::TypeId;
// TODO: Add default implementations for types such as TaggedValue::Subpaths, and use the defaults here and in document_node_types
// Tries using the default for the tagged value type. If it not implemented, then uses the default used in document_node_types. If it is not used there, then TaggedValue::None is returned.
match internal_id {
x if x == TypeId::of::<()>() => TaggedValue::None,
x if x == TypeId::of::<String>() => TaggedValue::String(Default::default()),
x if x == TypeId::of::<u32>() => TaggedValue::U32(Default::default()),
x if x == TypeId::of::<u64>() => TaggedValue::U64(Default::default()),
x if x == TypeId::of::<f64>() => TaggedValue::F64(Default::default()),
x if x == TypeId::of::<bool>() => TaggedValue::Bool(Default::default()),
x if x == TypeId::of::<UVec2>() => TaggedValue::UVec2(Default::default()),
x if x == TypeId::of::<IVec2>() => TaggedValue::IVec2(Default::default()),
x if x == TypeId::of::<DVec2>() => TaggedValue::DVec2(Default::default()),
x if x == TypeId::of::<Option<DVec2>>() => TaggedValue::OptionalDVec2(Default::default()),
x if x == TypeId::of::<graphene_core::raster::Image<Color>>() => TaggedValue::Image(Default::default()),
x if x == TypeId::of::<ImaginateCache>() => TaggedValue::ImaginateCache(Default::default()),
x if x == TypeId::of::<graphene_core::raster::ImageFrame<Color>>() => TaggedValue::ImageFrame(Default::default()),
x if x == TypeId::of::<graphene_core::raster::Color>() => TaggedValue::Color(Default::default()),
x if x == TypeId::of::<Vec<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>>() => TaggedValue::Subpaths(vec![]),
x if x == TypeId::of::<Arc<bezier_rs::Subpath<graphene_core::uuid::ManipulatorGroupId>>>() => TaggedValue::None,
x if x == TypeId::of::<BlendMode>() => TaggedValue::BlendMode(Default::default()),
x if x == TypeId::of::<ImaginateSamplingMethod>() => TaggedValue::ImaginateSamplingMethod(Default::default()),
x if x == TypeId::of::<ImaginateMaskStartingFill>() => TaggedValue::ImaginateMaskStartingFill(Default::default()),
x if x == TypeId::of::<ImaginateController>() => TaggedValue::ImaginateController(Default::default()),
x if x == TypeId::of::<DAffine2>() => TaggedValue::DAffine2(Default::default()),
x if x == TypeId::of::<LuminanceCalculation>() => TaggedValue::LuminanceCalculation(Default::default()),
x if x == TypeId::of::<graphene_core::vector::VectorData>() => TaggedValue::VectorData(Default::default()),
x if x == TypeId::of::<graphene_core::vector::style::Fill>() => TaggedValue::Fill(Default::default()),
x if x == TypeId::of::<graphene_core::vector::style::Stroke>() => TaggedValue::Stroke(Default::default()),
x if x == TypeId::of::<Vec<f64>>() => TaggedValue::VecF64(Default::default()),
x if x == TypeId::of::<Vec<DVec2>>() => TaggedValue::VecDVec2(Default::default()),
x if x == TypeId::of::<graphene_core::raster::RedGreenBlue>() => TaggedValue::RedGreenBlue(graphene_core::raster::RedGreenBlue::Red),
x if x == TypeId::of::<graphene_core::raster::RedGreenBlueAlpha>() => TaggedValue::RedGreenBlueAlpha(graphene_core::raster::RedGreenBlueAlpha::Red),
x if x == TypeId::of::<graphene_core::raster::NoiseType>() => TaggedValue::NoiseType(graphene_core::raster::NoiseType::Perlin),
x if x == TypeId::of::<graphene_core::raster::FractalType>() => TaggedValue::FractalType(graphene_core::raster::FractalType::None),
x if x == TypeId::of::<graphene_core::raster::CellularDistanceFunction>() => TaggedValue::CellularDistanceFunction(graphene_core::raster::CellularDistanceFunction::Euclidean),
x if x == TypeId::of::<graphene_core::raster::CellularReturnType>() => TaggedValue::CellularReturnType(graphene_core::raster::CellularReturnType::Nearest),
x if x == TypeId::of::<graphene_core::raster::DomainWarpType>() => TaggedValue::DomainWarpType(graphene_core::raster::DomainWarpType::None),
x if x == TypeId::of::<graphene_core::raster::RelativeAbsolute>() => TaggedValue::RelativeAbsolute(graphene_core::raster::RelativeAbsolute::Relative),
x if x == TypeId::of::<graphene_core::raster::SelectiveColorChoice>() => TaggedValue::SelectiveColorChoice(graphene_core::raster::SelectiveColorChoice::Reds),
x if x == TypeId::of::<graphene_core::vector::style::LineCap>() => TaggedValue::LineCap(graphene_core::vector::style::LineCap::Butt),
x if x == TypeId::of::<graphene_core::vector::style::LineJoin>() => TaggedValue::LineJoin(graphene_core::vector::style::LineJoin::Miter),
x if x == TypeId::of::<graphene_core::vector::style::FillType>() => TaggedValue::FillType(graphene_core::vector::style::FillType::Solid),
x if x == TypeId::of::<graphene_core::vector::style::GradientType>() => TaggedValue::GradientType(Default::default()),
x if x == TypeId::of::<Vec<(f64, graphene_core::Color)>>() => TaggedValue::GradientPositions(Default::default()),
x if x == TypeId::of::<graphene_core::quantization::QuantizationChannels>() => TaggedValue::Quantization(Default::default()),
x if x == TypeId::of::<Option<graphene_core::Color>>() => TaggedValue::OptionalColor(Default::default()),
x if x == TypeId::of::<Vec<graphene_core::uuid::ManipulatorGroupId>>() => TaggedValue::ManipulatorGroupIds(Default::default()),
x if x == TypeId::of::<graphene_core::text::Font>() => TaggedValue::Font(graphene_core::text::Font::new(
graphene_core::consts::DEFAULT_FONT_FAMILY.into(),
graphene_core::consts::DEFAULT_FONT_STYLE.into(),
)),
x if x == TypeId::of::<Vec<graphene_core::vector::brush_stroke::BrushStroke>>() => TaggedValue::BrushStrokes(Default::default()),
x if x == TypeId::of::<BrushCache>() => TaggedValue::BrushCache(Default::default()),
x if x == TypeId::of::<graphene_core::raster::IndexNode<Vec<graphene_core::raster::ImageFrame<Color>>>>() => TaggedValue::Segments(Default::default()),
x if x == TypeId::of::<crate::document::DocumentNode>() => TaggedValue::DocumentNode(Default::default()),
x if x == TypeId::of::<graphene_core::GraphicGroup>() => TaggedValue::GraphicGroup(Default::default()),
x if x == TypeId::of::<graphene_core::GraphicElement>() => TaggedValue::GraphicElement(Default::default()),
x if x == TypeId::of::<graphene_core::Artboard>() => TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::EMPTY),
x if x == TypeId::of::<graphene_core::ArtboardGroup>() => TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::EMPTY),
x if x == TypeId::of::<graphene_core::SurfaceFrame>() => TaggedValue::None,
x if x == TypeId::of::<RenderOutput>() => TaggedValue::None,
x if x == TypeId::of::<graphene_core::WasmSurfaceHandleFrame>() => TaggedValue::None,
x if x == TypeId::of::<graphene_core::transform::Footprint>() => TaggedValue::Footprint(Default::default()),
x if x == TypeId::of::<Vec<Color>>() => TaggedValue::Palette(Default::default()),
x if x == TypeId::of::<graphene_core::vector::misc::CentroidType>() => TaggedValue::CentroidType(Default::default()),
x if x == TypeId::of::<graphene_core::vector::misc::BooleanOperation>() => TaggedValue::BooleanOperation(Default::default()),
_ => TaggedValue::None,
}
}
Type::Fn(_, output) => TaggedValue::from_type(output),
Type::Future(_) => {
log::warn!("Future type not used");
TaggedValue::None
}
}
}
}
pub struct UpcastNode {

View file

@ -15,7 +15,7 @@ impl Compiler {
network.flatten(id);
}
network.remove_redundant_id_nodes();
network.remove_dead_nodes();
network.remove_dead_nodes(0);
let proto_networks = network.into_proto_networks();
let proto_networks_result: Vec<ProtoNetwork> = proto_networks

View file

@ -536,7 +536,6 @@ impl ProtoNetwork {
}
}
}
debug!("Sorted order {sorted:?}");
sorted
}*/

View file

@ -52,9 +52,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
};
loop {
//println!("executing");
let _result = (&executor).execute(editor_api.clone()).await?;
//println!("result: {result:?}");
std::thread::sleep(std::time::Duration::from_millis(16));
}
}
@ -92,67 +90,11 @@ fn create_executor(_document_string: String) -> Result<DynamicExecutor, Box<dyn
// Ok(executor)
}
pub fn wrap_network_in_scope(mut network: NodeNetwork) -> NodeNetwork {
let node_ids = network.nodes.keys().copied().collect::<Vec<_>>();
network.generate_node_paths(&[]);
for id in node_ids {
network.flatten(id);
}
let mut network_inputs = Vec::new();
let mut input_type = None;
for (id, node) in network.nodes.iter() {
for input in node.inputs.iter() {
if let NodeInput::Network(_) = input {
if input_type.is_none() {
input_type = Some(input.clone());
}
assert_eq!(input, input_type.as_ref().unwrap(), "Networks wrapped in scope must have the same input type");
network_inputs.push(*id);
}
}
}
let len = network_inputs.len();
network.imports = network_inputs;
// if the network has no inputs, it doesn't need to be wrapped in a scope
if len == 0 {
return network;
}
let inner_network = DocumentNode {
name: "Scope".to_string(),
implementation: DocumentNodeImplementation::Network(network),
inputs: core::iter::repeat(NodeInput::node(NodeId(0), 1)).take(len).collect(),
..Default::default()
};
// wrap the inner network in a scope
let nodes = vec![
begin_scope(),
inner_network,
DocumentNode {
name: "End Scope".to_string(),
implementation: DocumentNodeImplementation::proto("graphene_core::memo::EndLetNode<_, _>"),
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(1), 0)],
..Default::default()
},
];
NodeNetwork {
imports: vec![NodeId(0)],
exports: vec![NodeOutput::new(NodeId(2), 0)],
nodes: nodes.into_iter().enumerate().map(|(id, node)| (NodeId(id as u64), node)).collect(),
..Default::default()
}
}
fn begin_scope() -> DocumentNode {
DocumentNode {
name: "Begin Scope".to_string(),
implementation: DocumentNodeImplementation::Network(NodeNetwork {
imports: vec![NodeId(0)],
exports: vec![NodeOutput::new(NodeId(1), 0), NodeOutput::new(NodeId(2), 0)],
exports: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(2), 0)],
nodes: [
DocumentNode {
name: "SetNode".to_string(),
@ -181,7 +123,7 @@ fn begin_scope() -> DocumentNode {
..Default::default()
}),
inputs: vec![NodeInput::Network(concrete!(WasmEditorApi))],
inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 0)],
..Default::default()
}
}

View file

@ -169,11 +169,10 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
log::debug!("inner_network: {inner_network:?}");
let network = NodeNetwork {
imports: vec![NodeId(2), NodeId(1)], //vec![0, 1],
#[cfg(feature = "quantization")]
exports: vec![NodeOutput::new(NodeId(5), 0)],
exports: vec![NodeInput::node(NodeId(5), 0)],
#[cfg(not(feature = "quantization"))]
exports: vec![NodeOutput::new(NodeId(3), 0)],
exports: vec![NodeInput::node(NodeId(3), 0)],
nodes: [
DocumentNode {
name: "Slice".into(),
@ -183,13 +182,13 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
},
DocumentNode {
name: "Quantization".into(),
inputs: vec![NodeInput::Network(concrete!(quantization::Quantization))],
inputs: vec![NodeInput::network(concrete!(quantization::Quantization), 1)],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()),
..Default::default()
},
DocumentNode {
name: "Width".into(),
inputs: vec![NodeInput::Network(concrete!(u32))],
inputs: vec![NodeInput::network(concrete!(u32), 0)],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()),
..Default::default()
},
@ -257,7 +256,7 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
log::debug!("compiling shader");
let shader = compilation_client::compile(
proto_networks,
vec![concrete!(u32), concrete!(Color)], //, concrete!(u32)],
vec![concrete!(u32), concrete!(Color)],
vec![concrete!(Color)],
ShaderIO {
#[cfg(feature = "quantization")]
@ -391,7 +390,7 @@ fn map_gpu_single_image(input: Image<Color>, node: String) -> Image<Color> {
inputs: vec![NodeId(0)],
disabled: vec![],
previous_outputs: None,
outputs: vec![NodeOutput::new(NodeId(0), 0)],
outputs: vec![NodeInput::node(NodeId(0), 0)],
nodes: [(
NodeId(0),
DocumentNode {
@ -434,8 +433,7 @@ async fn blend_gpu_image(foreground: ImageFrame<Color>, background: ImageFrame<C
let compiler = graph_craft::graphene_compiler::Compiler {};
let network = NodeNetwork {
imports: vec![],
exports: vec![NodeOutput::new(NodeId(0), 0)],
exports: vec![NodeInput::node(NodeId(0), 0)],
nodes: [DocumentNode {
name: "BlendOp".into(),
inputs: vec![NodeInput::Inline(InlineRust::new(

View file

@ -85,6 +85,8 @@ impl DynamicExecutor {
pub fn document_node_types(&self) -> ResolvedDocumentNodeTypes {
let mut resolved_document_node_types = ResolvedDocumentNodeTypes::default();
// TODO: https://github.com/GraphiteEditor/Graphite/issues/1767
// TODO: Non exposed inputs are not added to the inputs_source_map, so they are not included in the resolved_document_node_types. The type is still available in the typing_context. This only affects the UI-only "Import" node.
for (source, &(protonode_id, protonode_index)) in self.tree.inputs_source_map() {
let Some(node_io) = self.typing_context.type_of(protonode_id) else { continue };
let Some(ty) = [&node_io.input].into_iter().chain(&node_io.parameters).nth(protonode_index) else {
@ -206,7 +208,7 @@ impl BorrowTree {
ConstructionArgs::Value(value) => {
let upcasted = UpcastNode::new(value.to_owned());
let node = Box::new(upcasted) as TypeErasedBox<'_>;
let node = NodeContainer::new(node);
let node: std::rc::Rc<NodeContainer> = NodeContainer::new(node);
self.store_node(node, id);
}
ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"),
@ -242,7 +244,7 @@ mod test {
let mut tree = BorrowTree::default();
let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32)), vec![]);
let context = TypingContext::default();
let future = tree.push_node(NodeId(0), val_1_protonode, &context); //.await.unwrap();
let future = tree.push_node(NodeId(0), val_1_protonode, &context);
futures::executor::block_on(future).unwrap();
let _node = tree.get(NodeId(0)).unwrap();
let result = futures::executor::block_on(tree.eval(NodeId(0), ()));

View file

@ -15,14 +15,13 @@ mod tests {
fn add_network() -> NodeNetwork {
NodeNetwork {
imports: vec![NodeId(0), NodeId(0)],
exports: vec![NodeOutput::new(NodeId(1), 0)],
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [
(
NodeId(0),
DocumentNode {
name: "Cons".into(),
inputs: vec![NodeInput::Network(concrete!(u32)), NodeInput::Network(concrete!(&u32))],
inputs: vec![NodeInput::network(concrete!(u32), 0), NodeInput::network(concrete!(&u32), 1)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::structural::ConsNode<_, _>")),
..Default::default()
},
@ -44,14 +43,13 @@ mod tests {
}
let network = NodeNetwork {
imports: vec![NodeId(0)],
exports: vec![NodeOutput::new(NodeId(0), 0)],
exports: vec![NodeInput::node(NodeId(0), 0)],
nodes: [(
NodeId(0),
DocumentNode {
name: "Inc".into(),
inputs: vec![
NodeInput::Network(concrete!(u32)),
NodeInput::network(concrete!(u32), 0),
NodeInput::Value {
tagged_value: graph_craft::document::value::TaggedValue::U32(1u32),
exposed: false,
@ -84,15 +82,14 @@ mod tests {
use graph_craft::*;
let network = NodeNetwork {
imports: vec![NodeId(0)],
exports: vec![NodeOutput::new(NodeId(1), 0)],
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [
// Simple identity node taking a number as input from outside the graph
(
NodeId(0),
DocumentNode {
name: "id".into(),
inputs: vec![NodeInput::Network(concrete!(u32))],
inputs: vec![NodeInput::network(concrete!(u32), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")),
..Default::default()
},

View file

@ -700,7 +700,6 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_std::wasm_application_io::RenderNode<_, _, _>, input: WasmEditorApi, output: RenderOutput, fn_params: [Footprint => Vec<Color>, () => Arc<WasmSurfaceHandle>]),
async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: VectorData, fn_params: [Footprint => VectorData, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]),
async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: WasmSurfaceHandleFrame, fn_params: [Footprint => WasmSurfaceHandleFrame, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]),
async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: WasmSurfaceHandleFrame, fn_params: [Footprint => WasmSurfaceHandleFrame, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]),
async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: ImageFrame<Color>, fn_params: [Footprint => ImageFrame<Color>, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]),
async_node!(graphene_core::transform::TransformNode<_, _, _, _, _, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => GraphicGroup, () => DVec2, () => f64, () => DVec2, () => DVec2, () => DVec2]),
register_node!(graphene_core::transform::SetTransformNode<_>, input: VectorData, params: [VectorData]),
@ -800,7 +799,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
register_node!(graphene_core::text::TextGeneratorNode<_, _, _>, input: WasmEditorApi, params: [String, graphene_core::text::Font, f64]),
register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []),
register_node!(graphene_core::ExtractImageFrame, input: WasmEditorApi, params: []),
async_node!(graphene_core::ConstructLayerNode<_, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => graphene_core::GraphicElement, Footprint => GraphicGroup]),
async_node!(graphene_core::ConstructLayerNode<_, _>, input: Footprint, output: GraphicGroup, fn_params: [Footprint => GraphicGroup, Footprint => graphene_core::GraphicElement]),
register_node!(graphene_core::ToGraphicElementNode, input: graphene_core::vector::VectorData, params: []),
register_node!(graphene_core::ToGraphicElementNode, input: ImageFrame<Color>, params: []),
register_node!(graphene_core::ToGraphicElementNode, input: GraphicGroup, params: []),
@ -810,7 +809,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
register_node!(graphene_core::ToGraphicGroupNode, input: GraphicGroup, params: []),
register_node!(graphene_core::ToGraphicGroupNode, input: Artboard, params: []),
async_node!(graphene_core::ConstructArtboardNode<_, _, _, _, _>, input: Footprint, output: Artboard, fn_params: [Footprint => GraphicGroup, () => glam::IVec2, () => glam::IVec2, () => Color, () => bool]),
async_node!(graphene_core::AddArtboardNode<_, _>, input: Footprint, output: ArtboardGroup, fn_params: [Footprint => Artboard, Footprint => ArtboardGroup]),
async_node!(graphene_core::AddArtboardNode<_, _>, input: Footprint, output: ArtboardGroup, fn_params: [Footprint => ArtboardGroup, Footprint => Artboard]),
];
let mut map: HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
for (id, c, types) in node_types.into_iter().flatten() {

View file

@ -160,7 +160,7 @@ impl gpu_executor::GpuExecutor for WgpuExecutor {
usage |= wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC;
}
log::debug!("Creating storage buffer with usage {:?} and len: {}", usage, bytes.len());
log::warn!("Creating storage buffer with usage {:?} and len: {}", usage, bytes.len());
let buffer = self.context.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None,
contents: bytes.as_ref(),
@ -207,7 +207,7 @@ impl gpu_executor::GpuExecutor for WgpuExecutor {
}
fn create_output_buffer(&self, len: usize, ty: Type, cpu_readable: bool) -> Result<WgpuShaderInput> {
log::debug!("Creating output buffer with len: {len}");
log::warn!("Creating output buffer with len: {len}");
let create_buffer = |usage| {
Ok::<_, anyhow::Error>(self.context.device.create_buffer(&BufferDescriptor {
label: None,