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

View file

@ -26,7 +26,7 @@ pub enum DialogMessage {
localized_commit_year: String, localized_commit_year: String,
}, },
RequestComingSoonDialog { RequestComingSoonDialog {
issue: Option<i32>, issue: Option<u32>,
}, },
RequestDemoArtworkDialog, RequestDemoArtworkDialog,
RequestExportDialog, 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. /// A dialog to notify users of an unfinished issue, optionally with an issue number.
pub struct ComingSoonDialog { pub struct ComingSoonDialog {
pub issue: Option<i32>, pub issue: Option<u32>,
} }
impl DialogLayoutHolder for ComingSoonDialog { impl DialogLayoutHolder for ComingSoonDialog {

View file

@ -1,6 +1,6 @@
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon}; use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
use crate::messages::layout::utility_types::widget_prelude::*; 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::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::utility_types::HintData; use crate::messages::tool::utility_types::HintData;
@ -188,7 +188,7 @@ pub enum FrontendMessage {
}, },
UpdateNodeGraph { UpdateNodeGraph {
nodes: Vec<FrontendNode>, nodes: Vec<FrontendNode>,
links: Vec<FrontendNodeLink>, wires: Vec<FrontendNodeWire>,
}, },
UpdateNodeGraphBarLayout { UpdateNodeGraphBarLayout {
#[serde(rename = "layoutTarget")] #[serde(rename = "layoutTarget")]
@ -220,6 +220,10 @@ pub enum FrontendMessage {
layout_target: LayoutTarget, layout_target: LayoutTarget,
diff: Vec<WidgetDiff>, diff: Vec<WidgetDiff>,
}, },
UpdateSubgraphPath {
#[serde(rename = "subgraphPath")]
subgraph_path: Vec<String>,
},
UpdateToolOptionsLayout { UpdateToolOptionsLayout {
#[serde(rename = "layoutTarget")] #[serde(rename = "layoutTarget")]
layout_target: 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(KeyH); modifiers=[Accel], action_dispatch=NodeGraphMessage::ToggleSelectedVisibility),
entry!(KeyDown(KeyL); modifiers=[Accel], action_dispatch=NodeGraphMessage::ToggleSelectedLocked), entry!(KeyDown(KeyL); modifiers=[Accel], action_dispatch=NodeGraphMessage::ToggleSelectedLocked),
entry!(KeyDown(KeyL); modifiers=[Alt], action_dispatch=NodeGraphMessage::ToggleSelectedAsLayersOrNodes), entry!(KeyDown(KeyL); modifiers=[Alt], action_dispatch=NodeGraphMessage::ToggleSelectedAsLayersOrNodes),
entry!(KeyDown(KeyC); modifiers=[Shift], action_dispatch=NodeGraphMessage::PrintSelectedNodeCoordinates),
// //
// TransformLayerMessage // TransformLayerMessage
entry!(KeyDown(Enter); action_dispatch=TransformLayerMessage::ApplyTransformOperation), 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); let callback_message = (number_input.on_update.callback)(number_input);
responses.add(callback_message); 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() { Value::String(str) => match str.as_str() {
"Increment" => responses.add((number_input.increment_callback_increase.callback)(number_input)), "Increment" => responses.add((number_input.increment_callback_increase.callback)(number_input)),
"Decrement" => responses.add((number_input.increment_callback_decrease.callback)(number_input)), "Decrement" => responses.add((number_input.increment_callback_decrease.callback)(number_input)),

View file

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

View file

@ -21,14 +21,11 @@ use crate::node_graph_executor::NodeGraphExecutor;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::FlowType; 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::BlendMode;
use graphene_core::raster::ImageFrame; use graphene_core::raster::ImageFrame;
use graphene_core::renderer::ClickTarget; use graphene_core::renderer::ClickTarget;
use graphene_core::transform::Footprint;
use graphene_core::vector::style::ViewMode; use graphene_core::vector::style::ViewMode;
use graphene_core::{concrete, generic, ProtoNodeIdentifier};
use graphene_std::wasm_application_io::WasmEditorApi;
use glam::{DAffine2, DVec2, IVec2}; use glam::{DAffine2, DVec2, IVec2};
@ -51,7 +48,7 @@ pub struct DocumentMessageHandler {
#[serde(skip)] #[serde(skip)]
navigation_handler: NavigationMessageHandler, navigation_handler: NavigationMessageHandler,
#[serde(skip)] #[serde(skip)]
node_graph_handler: NodeGraphMessageHandler, pub node_graph_handler: NodeGraphMessageHandler,
#[serde(skip)] #[serde(skip)]
overlays_message_handler: OverlaysMessageHandler, overlays_message_handler: OverlaysMessageHandler,
#[serde(skip)] #[serde(skip)]
@ -290,13 +287,14 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
let parent = self let parent = self
.metadata() .metadata()
.deepest_common_ancestor(self.selected_nodes.selected_layers(self.metadata()), true) .deepest_common_ancestor(self.selected_nodes.selected_layers(self.metadata()), true)
.unwrap_or(LayerNodeIdentifier::ROOT); .unwrap_or(LayerNodeIdentifier::ROOT_PARENT);
let insert_index = parent let insert_index = parent
.children(self.metadata()) .children(self.metadata())
.enumerate() .enumerate()
.find_map(|(index, item)| self.selected_nodes.selected_layers(self.metadata()).any(|x| x == item).then_some(index as isize)) .find_map(|(index, item)| self.selected_nodes.selected_layers(self.metadata()).any(|x| x == item).then_some(index as isize))
.unwrap_or(-1); .unwrap_or(-1);
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
responses.add(GraphOperationMessage::NewCustomLayer { responses.add(GraphOperationMessage::NewCustomLayer {
id, id,
@ -310,8 +308,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
DocumentMessage::DebugPrintDocument => { DocumentMessage::DebugPrintDocument => {
info!("{:#?}", self.network); info!("{:#?}", self.network);
} }
DocumentMessage::DeleteLayer { id } => { DocumentMessage::DeleteLayer { layer } => {
responses.add(NodeGraphMessage::DeleteNodes { node_ids: vec![id], reconnect: true }); responses.add(GraphOperationMessage::DeleteLayer { layer, reconnect: true });
responses.add_front(BroadcastEvent::ToolAbort); responses.add_front(BroadcastEvent::ToolAbort);
} }
DocumentMessage::DeleteSelectedLayers => { DocumentMessage::DeleteSelectedLayers => {
@ -319,7 +317,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add_front(BroadcastEvent::SelectionChanged); responses.add_front(BroadcastEvent::SelectionChanged);
for path in self.metadata().shallowest_unique_layers(self.selected_nodes.selected_layers(self.metadata())) { 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 => { DocumentMessage::DeselectAllLayers => {
@ -331,7 +330,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
DocumentMessage::DocumentStructureChanged => { DocumentMessage::DocumentStructureChanged => {
self.update_layers_panel_options_bar_widgets(responses); 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(); let data_buffer: RawBuffer = self.serialize_root();
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer }); responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
} }
@ -395,16 +394,11 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
DocumentMessage::GroupSelectedLayers => { DocumentMessage::GroupSelectedLayers => {
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
let parent = self let Some(parent) = self.metadata().deepest_common_ancestor(self.selected_nodes.selected_layers(self.metadata()), false) else {
.metadata() // Cancel grouping layers across different artboards
.deepest_common_ancestor(self.selected_nodes.selected_layers(self.metadata()), false) // TODO: Group each set of layers for each artboard separately
.unwrap_or(LayerNodeIdentifier::ROOT);
// Cancel grouping layers across different artboards
// TODO: Group each set of layers for each artboard separately
if parent == LayerNodeIdentifier::ROOT {
return; return;
} };
// Move layers in nested unselected folders above the first unselected parent folder // Move layers in nested unselected folders above the first unselected parent folder
let selected_layers = self.selected_nodes.selected_layers(self.metadata()).collect::<Vec<_>>(); let selected_layers = self.selected_nodes.selected_layers(self.metadata()).collect::<Vec<_>>();
@ -429,7 +423,19 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
continue; 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(), node_id: layer.to_node(),
reconnect_to_sibling: true, reconnect_to_sibling: true,
}); });
@ -442,17 +448,17 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
.expect("Current folder should always exist") .expect("Current folder should always exist")
.metadata .metadata
.position; .position;
responses.add(NodeGraphMessage::SetNodePosition {
responses.add(GraphOperationMessage::SetNodePosition {
node_id: layer.to_node(), node_id: layer.to_node(),
position: folder_position, position: folder_position,
}); });
// Insert node right above the folder // 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) // TODO: downstream node can be none if it is the root node
else { let (folder_downstream_node_id, folder_downstream_input_index) =
log::error!("Downstream node should always exist when inserting layer"); DocumentMessageHandler::get_downstream_node(&self.network, &self.metadata, first_unselected_parent_folder).unwrap_or((self.network.exports_metadata.0, 0));
return;
};
responses.add(GraphOperationMessage::InsertNodeBetween { responses.add(GraphOperationMessage::InsertNodeBetween {
post_node_id: folder_downstream_node_id, post_node_id: folder_downstream_node_id,
post_node_input_index: folder_downstream_input_index, 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(), 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(), node_id: first_unselected_parent_folder.to_node(),
shift: IVec2::new(0, 3), shift: IVec2::new(0, 3),
shift_self: true, shift_self: true,
}); });
} }
let calculated_insert_index = DocumentMessageHandler::get_calculated_insert_index(&self.metadata, &self.selected_nodes, parent); let calculated_insert_index = DocumentMessageHandler::get_calculated_insert_index(&self.metadata, &self.selected_nodes, parent);
let folder_id = NodeId(generate_uuid()); let folder_id = NodeId(generate_uuid());
@ -481,7 +486,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
alias: String::new(), 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::SelectedNodesSet { nodes: vec![folder_id] });
responses.add(NodeGraphMessage::RunDocumentGraph); 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)) { if selected_layers.iter().any(|&layer| parent.ancestors(self.metadata()).any(|ancestor| ancestor == layer)) {
return; return;
} }
// Artboards can only have the Output node as the parent. // Artboards can only have `ROOT_PARENT` as the parent.
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; return;
} }
// Disallow inserting layers between artboards. Since only artboards can output to Output node, the layer parent cannot be the output. // 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; 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 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<_>>(); 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. // 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<_>>(); 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 { 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. // 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(), node_id: layer_to_move.to_node(),
reconnect_to_sibling: true, reconnect_to_sibling: true,
}); });
// Reconnect layer_to_move to new parent at insert index. // Reconnect layer_to_move to new parent at insert index.
responses.add(GraphOperationMessage::InsertLayerAtStackIndex { responses.add(GraphOperationMessage::InsertNodeAtStackIndex {
layer_id: layer_to_move.to_node(), node_id: layer_to_move.to_node(),
parent: parent.to_node(), parent: parent,
insert_index, insert_index,
}); });
} }
@ -658,6 +678,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
use crate::messages::tool::common_functionality::graph_modification_utils; 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); 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(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
responses.add(GraphOperationMessage::TransformSet { 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 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 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(last_selected.to_node());
nodes.push(id); nodes.push(id);
@ -780,7 +806,13 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
.skip_while(|&node| node != layer && node != last_selected) .skip_while(|&node| node != layer && node != last_selected)
.skip(1) .skip(1)
.take_while(|&node| node != layer && node != last_selected) .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 { } else {
if ctrl { if ctrl {
// Toggle selection when holding 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())); let folder_paths = self.metadata().folders_sorted_by_most_nested(self.selected_nodes.selected_layers(self.metadata()));
for folder in folder_paths { 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 // Cannot ungroup artboard
let folder_node = self.network.nodes.get(&folder.to_node()).expect("Folder node should always exist"); let folder_node = self.network.nodes.get(&folder.to_node()).expect("Folder node should always exist");
if folder_node.is_artboard() { 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 // 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"); log::error!("Folder should always have a child");
return; return;
}; };
// Move child_layer stack x position to folder stack // 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; let offset = folder_node.metadata.position - child_layer_node.metadata.position;
responses.add(NodeGraphMessage::ShiftUpstream { responses.add(GraphOperationMessage::ShiftUpstream {
node_id: child_layer_node_id, node_id: child_layer.to_node(),
shift: offset, shift: offset,
shift_self: true, shift_self: true,
}); });
// Set the primary input for the node downstream of folder to the first layer node // 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 { 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"); log::error!("Downstream node should always exist when moving layer");
continue; continue;
}; };
// Output_index must be 0 since layers only have 1 output // Output_index must be 0 since layers only have 1 output
let downstream_input = NodeInput::node(child_layer_node_id, 0); let downstream_input = NodeInput::node(child_layer.to_node(), 0);
responses.add(NodeGraphMessage::SetNodeInput { responses.add(GraphOperationMessage::SetNodeInput {
node_id: downstream_node_id, node_id: downstream_node_id,
input_index: downstream_input_index, input_index: downstream_input_index,
input: downstream_input, 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) // 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) { 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 // 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 { 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 { 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; 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 // 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); let bottom_layer_node_input = NodeInput::node(upstream_sibling_id, 0);
responses.add(NodeGraphMessage::SetNodeInput { responses.add(GraphOperationMessage::SetNodeInput {
node_id: last_child_node_id, node_id: last_child_node_id,
input_index: 0, input_index: 0,
input: bottom_layer_node_input, input: bottom_layer_node_input,
}); });
// Shift upstream_sibling down by the height of the child layer stack // 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 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(&last_child_node_id).expect("Last child layer should always exist for last 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 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 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 current_distance = upstream_sibling_node.metadata.position.y - folder_node.metadata.position.y;
let y_offset = target_distance - current_distance + 3; let y_offset = target_distance - current_distance + 3;
responses.add(NodeGraphMessage::ShiftUpstream { responses.add(GraphOperationMessage::ShiftUpstream {
node_id: layer_upstream_sibling_id, node_id: upstream_sibling_id,
shift: IVec2::new(0, y_offset), shift: IVec2::new(0, y_offset),
shift_self: true, shift_self: true,
}); });
} }
// Delete folder and all horizontal inputs, also deletes node in metadata // Delete folder and all horizontal inputs, also deletes node in metadata
responses.add(NodeGraphMessage::DeleteNodes { responses.add(GraphOperationMessage::DeleteLayer { layer: folder, reconnect: true });
node_ids: vec![folder.to_node()],
reconnect: true,
});
} }
responses.add(NodeGraphMessage::RunDocumentGraph); 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 { 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; let document_quad = self.metadata.document_to_viewport.inverse() * viewport_quad;
self.metadata self.metadata
.root() .all_layers()
.descendants(&self.metadata)
.filter(|&layer| self.selected_nodes.layer_visible(layer, self.metadata())) .filter(|&layer| self.selected_nodes.layer_visible(layer, self.metadata()))
.filter(|&layer| !self.selected_nodes.layer_locked(layer, self.metadata())) .filter(|&layer| !self.selected_nodes.layer_locked(layer, self.metadata()))
.filter(|&layer| !is_artboard(layer, network)) .filter(|&layer| !is_artboard(layer, network))
@ -1107,8 +1140,7 @@ impl DocumentMessageHandler {
pub fn click_xray(&self, viewport_location: DVec2) -> impl Iterator<Item = LayerNodeIdentifier> + '_ { pub fn click_xray(&self, viewport_location: DVec2) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
let point = self.metadata.document_to_viewport.inverse().transform_point2(viewport_location); let point = self.metadata.document_to_viewport.inverse().transform_point2(viewport_location);
self.metadata self.metadata
.root() .all_layers()
.descendants(&self.metadata)
.filter(|&layer| self.selected_nodes.layer_visible(layer, self.metadata())) .filter(|&layer| self.selected_nodes.layer_visible(layer, self.metadata()))
.filter(|&layer| !self.selected_nodes.layer_locked(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))) .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> { pub fn find_deepest(&self, node_list: &[LayerNodeIdentifier], network: &NodeNetwork) -> Option<LayerNodeIdentifier> {
node_list node_list
.iter() .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() .copied()
} }
@ -1135,7 +1174,14 @@ impl DocumentMessageHandler {
node_list.truncate( node_list.truncate(
node_list node_list
.iter() .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, .unwrap_or(0) + 1,
); );
node_list node_list
@ -1177,6 +1223,21 @@ impl DocumentMessageHandler {
pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> { pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {
serde_json::from_str(serialized_content).map_err(|e| EditorError::DocumentDeserialization(e.to_string())) 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 { pub fn with_name(name: String, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) -> Self {
@ -1248,9 +1309,9 @@ impl DocumentMessageHandler {
/// [3427872634365736244,18115028555707261608,449479075714955186] /// [3427872634365736244,18115028555707261608,449479075714955186]
/// ``` /// ```
pub fn serialize_root(&self) -> RawBuffer { 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(); 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). // 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; 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) { if let Some(previous_sibling) = layer_to_move.previous_sibling(metadata) {
downstream_layer = Some((previous_sibling.to_node(), false)) downstream_layer = Some((previous_sibling.to_node(), false))
} else if let Some(parent) = layer_to_move.parent(metadata) { } else if let Some(parent) = layer_to_move.parent(metadata) {
downstream_layer = Some((parent.to_node(), true)) if parent != LayerNodeIdentifier::ROOT_PARENT {
downstream_layer = Some((parent.to_node(), true))
}
}; };
// Downstream layer should always exist // 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. /// 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 { pub fn new_layer_parent(&self, include_self: bool) -> LayerNodeIdentifier {
self.metadata() self.metadata()
@ -1882,15 +1936,15 @@ impl DocumentMessageHandler {
IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24) IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24)
.hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into())) .hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into()))
.tooltip(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" }) .tooltip(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
.tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedLocked)) .tooltip_shortcut(action_keys!(GraphOperationMessageDiscriminant::ToggleSelectedLocked))
.on_update(|_| NodeGraphMessage::ToggleSelectedLocked.into()) .on_update(|_| GraphOperationMessage::ToggleSelectedLocked.into())
.disabled(!has_selection) .disabled(!has_selection)
.widget_holder(), .widget_holder(),
IconButton::new(if selection_all_visible { "EyeVisible" } else { "EyeHidden" }, 24) IconButton::new(if selection_all_visible { "EyeVisible" } else { "EyeHidden" }, 24)
.hover_icon(Some((if selection_all_visible { "EyeHide" } else { "EyeShow" }).into())) .hover_icon(Some((if selection_all_visible { "EyeHide" } else { "EyeShow" }).into()))
.tooltip(if selection_all_visible { "Hide Selected" } else { "Show Selected" }) .tooltip(if selection_all_visible { "Hide Selected" } else { "Show Selected" })
.tooltip_shortcut(action_keys!(NodeGraphMessageDiscriminant::ToggleSelectedVisibility)) .tooltip_shortcut(action_keys!(GraphOperationMessageDiscriminant::ToggleSelectedVisibility))
.on_update(|_| NodeGraphMessage::ToggleSelectedVisibility.into()) .on_update(|_| GraphOperationMessage::ToggleSelectedVisibility.into())
.disabled(!has_selection) .disabled(!has_selection)
.widget_holder(), .widget_holder(),
], ],
@ -1944,54 +1998,10 @@ impl DocumentMessageHandler {
fn root_network() -> NodeNetwork { fn root_network() -> NodeNetwork {
{ {
let mut network = NodeNetwork::default(); let mut network = NodeNetwork::default();
let node = graph_craft::document::DocumentNode { network.exports = vec![NodeInput::Value {
name: "Output".into(), tagged_value: TaggedValue::ArtboardGroup(graphene_core::ArtboardGroup::EMPTY),
inputs: vec![NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true), NodeInput::Network(concrete!(WasmEditorApi))], exposed: true,
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 network
} }
} }

View file

@ -4,10 +4,8 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
use crate::messages::prelude::*; use crate::messages::prelude::*;
use bezier_rs::Subpath; use bezier_rs::Subpath;
use graph_craft::document::DocumentNode; use graph_craft::document::{DocumentNode, NodeId, NodeInput};
use graph_craft::document::NodeId; use graphene_core::raster::{BlendMode, ImageFrame};
use graphene_core::raster::BlendMode;
use graphene_core::raster::ImageFrame;
use graphene_core::text::Font; use graphene_core::text::Font;
use graphene_core::uuid::ManipulatorGroupId; use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::brush_stroke::BrushStroke; use graphene_core::vector::brush_stroke::BrushStroke;
@ -30,17 +28,27 @@ pub enum GraphOperationMessage {
node_id: NodeId, node_id: NodeId,
operation: BooleanOperation, operation: BooleanOperation,
}, },
DeleteLayer {
layer: LayerNodeIdentifier,
reconnect: bool,
},
// TODO: Eventually remove this (probably starting late 2024)
DeleteLegacyOutputNode,
DisconnectInput { DisconnectInput {
node_id: NodeId, node_id: NodeId,
input_index: usize, input_index: usize,
}, },
DisconnectNodeFromStack {
node_id: NodeId,
reconnect_to_sibling: bool,
},
FillSet { FillSet {
layer: LayerNodeIdentifier, layer: LayerNodeIdentifier,
fill: Fill, fill: Fill,
}, },
InsertLayerAtStackIndex { InsertNodeAtStackIndex {
layer_id: NodeId, node_id: NodeId,
parent: NodeId, parent: LayerNodeIdentifier,
insert_index: usize, insert_index: usize,
}, },
InsertBooleanOperation { InsertBooleanOperation {
@ -59,7 +67,7 @@ pub enum GraphOperationMessage {
pre_node_output_index: usize, pre_node_output_index: usize,
}, },
MoveSelectedSiblingsToChild { MoveSelectedSiblingsToChild {
new_parent: NodeId, new_parent: LayerNodeIdentifier,
}, },
OpacitySet { OpacitySet {
layer: LayerNodeIdentifier, layer: LayerNodeIdentifier,
@ -146,4 +154,43 @@ pub enum GraphOperationMessage {
parent: LayerNodeIdentifier, parent: LayerNodeIdentifier,
insert_index: isize, 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 crate::messages::prelude::*;
use graph_craft::document::value::TaggedValue; 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::renderer::Quad;
use graphene_core::text::Font; use graphene_core::text::Font;
use graphene_core::vector::style::{Fill, Gradient, GradientType, LineCap, LineJoin, Stroke}; 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)] #[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct GraphOperationMessageHandler {} 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 { impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for GraphOperationMessageHandler {
fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, data: GraphOperationMessageData) { fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, data: GraphOperationMessageData) {
let GraphOperationMessageData { let GraphOperationMessageData {
@ -38,13 +40,19 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
match message { match message {
GraphOperationMessage::AddNodesAsChild { nodes, new_ids, parent, insert_index } => { GraphOperationMessage::AddNodesAsChild { nodes, new_ids, parent, insert_index } => {
let shift = nodes let shift = document_network
.get(&NodeId(0)) .get_root_node()
.and_then(|node| { .and_then(|root_node| {
document_network nodes.get(&root_node.id).and_then(|node| {
.nodes if parent == LayerNodeIdentifier::ROOT_PARENT {
.get(&parent.to_node()) return None;
.map(|layer| layer.metadata.position - node.metadata.position + IVec2::new(-8, 0)) };
let parent_node_id = parent.to_node();
document_network
.nodes
.get(&parent_node_id)
.map(|layer| layer.metadata.position - node.metadata.position + IVec2::new(-8, 0))
})
}) })
.unwrap_or_default(); .unwrap_or_default();
@ -54,24 +62,25 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
// Get the new, non-conflicting id // Get the new, non-conflicting id
let node_id = *new_ids.get(&old_id).unwrap(); 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 // Insert node into network
document_network.nodes.insert(node_id, document_node); document_network.nodes.insert(node_id, document_node);
} }
let Some(new_layer_id) = new_ids.get(&NodeId(0)) else { 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; return;
}; };
let insert_index = if insert_index < 0 { 0 } else { insert_index as usize }; 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] }); responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![*new_layer_id] });
if let Some(upstream_node) = upstream_node { match (downstream_node, upstream_node) {
responses.add(GraphOperationMessage::InsertNodeBetween { (Some(downstream_node), Some(upstream_node)) => responses.add(GraphOperationMessage::InsertNodeBetween {
post_node_id: downstream_node, post_node_id: downstream_node,
post_node_input_index: input_index, post_node_input_index: input_index,
insert_node_output_index: 0, insert_node_output_index: 0,
@ -79,16 +88,28 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
insert_node_input_index: 0, insert_node_input_index: 0,
pre_node_output_index: 0, pre_node_output_index: 0,
pre_node_id: upstream_node, pre_node_id: upstream_node,
}) }),
} else { (Some(downstream_node), None) => responses.add(GraphOperationMessage::SetNodeInput {
responses.add(NodeGraphMessage::SetNodeInput {
node_id: downstream_node, node_id: downstream_node,
input_index: input_index, input_index: input_index,
input: NodeInput::node(*new_layer_id, 0), input: NodeInput::node(*new_layer_id, 0),
}) }),
} (None, Some(upstream_node)) => responses.add(GraphOperationMessage::InsertNodeBetween {
post_node_id: document_network.exports_metadata.0,
responses.add(NodeGraphMessage::ShiftUpstream { 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(GraphOperationMessage::ShiftUpstream {
node_id: *new_layer_id, node_id: *new_layer_id,
shift: IVec2::new(0, 3), shift: IVec2::new(0, 3),
shift_self: true, shift_self: true,
@ -109,44 +130,87 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
); );
document_network.nodes.insert(node_id, new_boolean_operation_node); 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 } => { GraphOperationMessage::DisconnectInput { node_id, input_index } => {
let Some(node_to_disconnect) = document_network.nodes.get(&node_id) else { let Some(existing_input) = document_network
warn!("Node {} not found in DisconnectInput", node_id); .nodes
return; .get(&node_id)
}; .map_or_else(|| document_network.exports.get(input_index), |node| node.inputs.get(input_index))
let Some(node_type) = resolve_document_node_type(&node_to_disconnect.name) else { else {
warn!("Node {} not in library", node_to_disconnect.name); warn!("Could not find input for {node_id} at index {input_index} when disconnecting");
return;
};
let Some(existing_input) = node_to_disconnect.inputs.get(input_index) else {
warn!("Node does not have an input at the selected index");
return; 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 { if let NodeInput::Value { exposed, .. } = &mut input {
*exposed = existing_input.is_exposed(); *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 } => { 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) { 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); modify_inputs.fill_set(fill);
} }
} }
GraphOperationMessage::InsertLayerAtStackIndex { layer_id, 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); 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. // `layer_to_move` should always correspond to a node.
let Some(layer_to_move_node) = document_network.nodes.get(&layer_id) else { let Some(layer_to_move_node) = document_network.nodes.get(&node_id) else {
log::error!("Layer node not found when inserting node {} at index {}", layer_id, insert_index); log::error!("Layer node not found when inserting node {} at index {}", node_id, insert_index);
return; return;
}; };
// Move current layer to post node. // 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 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. // 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 { let offset_to_post_node = if insert_index == 0 {
@ -155,34 +219,46 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
new_position - current_position new_position - current_position
}; };
responses.add(NodeGraphMessage::ShiftUpstream { responses.add(GraphOperationMessage::ShiftUpstream {
node_id: layer_id, node_id: node_id,
shift: offset_to_post_node, shift: offset_to_post_node,
shift_self: true, shift_self: true,
}); });
// Update post_node input to layer_to_move. match (post_node_id, pre_node_id) {
if let Some(upstream_node) = pre_node_id { (Some(post_node_id), Some(pre_node_id)) => responses.add(GraphOperationMessage::InsertNodeBetween {
responses.add(GraphOperationMessage::InsertNodeBetween {
post_node_id: post_node_id, post_node_id: post_node_id,
post_node_input_index: post_node_input_index, post_node_input_index: post_node_input_index,
insert_node_output_index: 0, insert_node_output_index: 0,
insert_node_id: layer_id, insert_node_id: node_id,
insert_node_input_index: 0, insert_node_input_index: 0,
pre_node_output_index: 0, pre_node_output_index: 0,
pre_node_id: upstream_node, pre_node_id: pre_node_id,
}) }),
} else { (None, Some(pre_node_id)) => responses.add(GraphOperationMessage::InsertNodeBetween {
responses.add(NodeGraphMessage::SetNodeInput { 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, node_id: post_node_id,
input_index: post_node_input_index, 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. // Shift stack down, starting at the moved node.
responses.add(NodeGraphMessage::ShiftUpstream { responses.add(GraphOperationMessage::ShiftUpstream {
node_id: layer_id, node_id: node_id,
shift: IVec2::new(0, 3), shift: IVec2::new(0, 3),
shift_self: true, shift_self: true,
}); });
@ -190,16 +266,13 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
GraphOperationMessage::InsertBooleanOperation { operation } => { GraphOperationMessage::InsertBooleanOperation { operation } => {
let mut selected_layers = selected_nodes.selected_layers(&document_metadata); let mut selected_layers = selected_nodes.selected_layers(&document_metadata);
let first_selected_layer = selected_layers.next(); let upper_layer = selected_layers.next();
let second_selected_layer = selected_layers.next(); let lower_layer = selected_layers.next();
let other_selected_layer = selected_layers.next();
let (Some(upper_layer), Some(lower_layer), None) = (first_selected_layer, second_selected_layer, other_selected_layer) else { let Some(upper_layer) = upper_layer else { return };
return;
};
let Some(upper_layer_node) = document_network.nodes.get(&upper_layer.to_node()) 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 { let Some(NodeInput::Node {
node_id: upper_node_id, node_id: upper_node_id,
@ -209,13 +282,13 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
else { else {
return; 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()) {
node_id: lower_node_id, Some(NodeInput::Node {
output_index: lower_output_index, node_id: lower_node_id,
.. output_index: lower_output_index,
}) = lower_layer_node.inputs.get(1).cloned() ..
else { }) => (Some(lower_node_id), Some(lower_output_index)),
return; _ => (None, None),
}; };
let boolean_operation_node_id = NodeId::new(); 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 // 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) {
node_id: boolean_operation_node_id, responses.add(GraphOperationMessage::SetNodeInput {
input_index: 1, node_id: boolean_operation_node_id,
input: NodeInput::node(lower_node_id, lower_output_index), 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) // 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 // 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, node_id: boolean_operation_node_id,
position: upper_layer_node.metadata.position, 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 // 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, node_id: boolean_operation_node_id,
shift: (-8, 0).into(), shift: (-8, 0).into(),
shift_self: true, shift_self: true,
}) });
// Re-render
responses.add(NodeGraphMessage::RunDocumentGraph);
} }
GraphOperationMessage::InsertNodeBetween { GraphOperationMessage::InsertNodeBetween {
post_node_id, post_node_id,
@ -272,11 +350,14 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
pre_node_output_index, pre_node_output_index,
pre_node_id, pre_node_id,
} => { } => {
let Some(post_node) = document_network.nodes.get(&post_node_id) else { let post_node = document_network.nodes.get(&post_node_id);
error!("Post node not found"); let Some((post_node_input_index, _)) = post_node
return; .map_or(&document_network.exports, |post_node| &post_node.inputs)
}; .iter()
let Some((post_node_input_index, _)) = post_node.inputs.iter().enumerate().filter(|input| input.1.is_exposed()).nth(post_node_input_index) else { .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:#?}"); error!("Failed to find input index {post_node_input_index} on node {post_node_id:#?}");
return; return;
}; };
@ -290,35 +371,51 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
}; };
let post_input = NodeInput::node(insert_node_id, insert_node_output_index); 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, node_id: post_node_id,
input_index: post_node_input_index, input_index: post_node_input_index,
input: post_input, input: post_input,
}); });
let insert_input = NodeInput::node(pre_node_id, pre_node_output_index); 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, node_id: insert_node_id,
input_index: insert_node_input_index, input_index: insert_node_input_index,
input: insert_input, input: insert_input,
}); });
} }
GraphOperationMessage::OpacitySet { layer, opacity } => { 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) { 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); modify_inputs.opacity_set(opacity);
} }
} }
GraphOperationMessage::BlendModeSet { layer, blend_mode } => { 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) { 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); modify_inputs.blend_mode_set(blend_mode);
} }
} }
GraphOperationMessage::UpdateBounds { layer, old_bounds, new_bounds } => { 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) { 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); modify_inputs.update_bounds(old_bounds, new_bounds);
} }
} }
GraphOperationMessage::StrokeSet { layer, stroke } => { 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) { 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); modify_inputs.stroke_set(stroke);
} }
@ -329,6 +426,10 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
transform_in, transform_in,
skip_rerender, 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 parent_transform = document_metadata.downstream_transform_to_viewport(layer);
let bounds = LayerBounds::new(document_metadata, 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) { 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, transform_in,
skip_rerender, 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 parent_transform = document_metadata.downstream_transform_to_viewport(layer);
let current_transform = Some(document_metadata.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 } => { 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); 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) { 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); modify_inputs.pivot_set(pivot, bounds);
} }
} }
GraphOperationMessage::Vector { layer, modification } => { 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) { 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 } => { 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) { 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); modify_inputs.brush_modify(strokes);
} }
} }
GraphOperationMessage::MoveSelectedSiblingsToChild { new_parent } => { GraphOperationMessage::MoveSelectedSiblingsToChild { new_parent } => {
let group_layer = LayerNodeIdentifier::new(new_parent, &document_network); let Some(group_parent) = new_parent.parent(&document_metadata) else {
let Some(group_parent) = group_layer.parent(&document_metadata) else { log::error!("Could not find parent for layer {:?}", new_parent);
log::error!("Could not find parent for layer {:?}", group_layer);
return; 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 // 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() { 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 // Disconnect node, then reconnect as new child
responses.add(NodeGraphMessage::DisconnectLayerFromStack { responses.add(GraphOperationMessage::DisconnectNodeFromStack {
node_id: *node_to_move, node_id: *node_to_move,
reconnect_to_sibling: true, reconnect_to_sibling: true,
}); });
responses.add(GraphOperationMessage::InsertLayerAtStackIndex { responses.add(GraphOperationMessage::InsertNodeAtStackIndex {
layer_id: *node_to_move, node_id: *node_to_move,
parent: new_parent, parent: new_parent,
insert_index: 0, insert_index: 0,
}); });
@ -443,7 +562,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
if let Some(artboard_id) = modify_inputs.create_artboard(id, artboard) { if let Some(artboard_id) = modify_inputs.create_artboard(id, artboard) {
responses.add_front(NodeGraphMessage::SelectedNodesSet { nodes: vec![artboard_id] }); 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 { GraphOperationMessage::NewBitmapLayer {
id, id,
@ -489,31 +608,32 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
// Get the new, non-conflicting id // Get the new, non-conflicting id
let node_id = *new_ids.get(&old_id).unwrap(); 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 // 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)) { if let Some(&input) = new_ids.get(&NodeId(0)) {
layer_node.inputs[1] = NodeInput::node(input, 0); layer_node.inputs[1] = NodeInput::node(input, 0);
} }
} }
modify_inputs.responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::RunDocumentGraph);
} else { } else {
error!("Creating new custom layer failed"); 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 } => { GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index } => {
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses); 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) { if let Some(layer) = modify_inputs.create_layer_with_insert_index(id, insert_index, parent) {
modify_inputs.insert_vector_data(subpaths, layer); 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 { GraphOperationMessage::NewTextLayer {
id, 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) { if let Some(layer) = modify_inputs.create_layer_with_insert_index(id, insert_index, parent) {
modify_inputs.insert_text(text, font, size, layer); 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 } => { GraphOperationMessage::ResizeArtboard { id, location, dimensions } => {
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(id, document_network, document_metadata, node_graph, responses) { 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 => { GraphOperationMessage::ClearArtboards => {
let modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses); for &artboard in document_metadata.all_artboards() {
let artboard_nodes = modify_inputs responses.add(GraphOperationMessage::DeleteLayer { layer: artboard, reconnect: true });
.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,
});
} }
load_network_structure(document_network, document_metadata, selected_nodes, collapsed); load_network_structure(document_network, document_metadata, collapsed);
} }
GraphOperationMessage::NewSvg { GraphOperationMessage::NewSvg {
id, id,
@ -572,7 +681,97 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
let mut modify_inputs = ModifyInputsContext::new(document_network, document_metadata, node_graph, responses); 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); 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) { pub fn load_network_structure(document_network: &NodeNetwork, document_metadata: &mut DocumentMetadata, collapsed: &mut CollapsedLayers) {
document_metadata.load_structure(document_network, selected_nodes); document_metadata.load_structure(document_network);
collapsed.0.retain(|&layer| document_metadata.layer_exists(layer)); 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") warn!("Skip image")
} }
usvg::Node::Text(text) => { 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.insert_text(text.chunks.iter().map(|chunk| chunk.text.clone()).collect(), font, 24., layer);
modify_inputs.fill_set(Fill::Solid(Color::BLACK)); 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::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::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use bezier_rs::Subpath; use bezier_rs::Subpath;
use graph_craft::concrete;
use graph_craft::document::value::TaggedValue; 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::raster::{BlendMode, ImageFrame};
use graphene_core::text::Font; use graphene_core::text::Font;
use graphene_core::uuid::ManipulatorGroupId; use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::brush_stroke::BrushStroke; use graphene_core::vector::brush_stroke::BrushStroke;
use graphene_core::vector::style::{Fill, FillType, Stroke}; use graphene_core::vector::style::{Fill, FillType, Stroke};
use graphene_core::Type;
use graphene_core::{Artboard, Color}; use graphene_core::{Artboard, Color};
use graphene_std::vector::ManipulatorPointId; use graphene_std::vector::ManipulatorPointId;
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
use interpreted_executor::node_registry::NODE_REGISTRY;
use glam::{DAffine2, DVec2, IVec2}; use glam::{DAffine2, DVec2, IVec2};
use std::hash::{DefaultHasher, Hash, Hasher};
use super::transform_utils::{self, LayerBounds};
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)] #[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
pub enum TransformIn { pub enum TransformIn {
@ -40,12 +45,14 @@ pub enum VectorDataModification {
UpdateSubpaths { subpaths: Vec<Subpath<ManipulatorGroupId>> }, 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 struct ModifyInputsContext<'a> {
pub document_metadata: &'a mut DocumentMetadata, pub document_metadata: &'a mut DocumentMetadata,
pub document_network: &'a mut NodeNetwork, pub document_network: &'a mut NodeNetwork,
pub node_graph: &'a mut NodeGraphMessageHandler, pub node_graph: &'a mut NodeGraphMessageHandler,
pub responses: &'a mut VecDeque<Message>, 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>, pub layer_node: Option<NodeId>,
} }
@ -53,7 +60,7 @@ impl<'a> ModifyInputsContext<'a> {
/// Get the node network from the document /// 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 { 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 { Self {
outwards_links: document_network.collect_outwards_links(), outwards_wires: document_network.collect_outwards_wires(),
document_network, document_network,
node_graph, node_graph,
responses, responses,
@ -73,7 +80,7 @@ impl<'a> ModifyInputsContext<'a> {
let mut id = id; let mut id = id;
while !document.document_network.nodes.get(&id)?.is_layer { 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); document.layer_node = Some(id);
@ -107,7 +114,7 @@ impl<'a> ModifyInputsContext<'a> {
self.document_network.nodes.insert(id, new_node); 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) Some(id)
} }
@ -123,8 +130,34 @@ impl<'a> ModifyInputsContext<'a> {
Some(new_id) 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. /// 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 /// -----> Post node input_index: 0
/// | if skip_layer_nodes == 0, return (Post node, Some(Layer1), 1) /// | if skip_layer_nodes == 0, return (Post node, Some(Layer1), 1)
/// -> Layer1 input_index: 1 /// -> Layer1 input_index: 1
/// ↑ if skip_layer_nodes == 1, return (Layer1, Some(Layer2), 0) /// ↑ if skip_layer_nodes == 1, return (Layer1, Some(Layer2), 0)
@ -134,11 +167,27 @@ impl<'a> ModifyInputsContext<'a> {
/// ↑ if skip_layer_nodes == 2, return (NonLayerNode, Some(Layer3), 0) /// ↑ if skip_layer_nodes == 2, return (NonLayerNode, Some(Layer3), 0)
/// -> Layer3 input_index: 3 /// -> Layer3 input_index: 3
/// if skip_layer_nodes == 3, return (Layer3, None, 0) /// 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) { pub fn get_post_node_with_index(network: &NodeNetwork, parent: LayerNodeIdentifier, insert_index: usize) -> (Option<NodeId>, Option<NodeId>, usize) {
let mut post_node_input_index = if post_node_id == NodeId(0) { 0 } else { 1 }; 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. // 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; 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 { loop {
if current_index == insert_index { if current_index == insert_index {
break; break;
@ -191,42 +240,46 @@ 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"); 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 // TODO: Smarter placement of layers into artboards https://github.com/GraphiteEditor/Graphite/issues/1507
let mut post_node_id = output_node_id; let mut parent = parent;
if post_node_id == NodeId(0) { if parent == LayerNodeIdentifier::ROOT_PARENT {
// Check if an artboard is connected, and switch post node to the artboard. if let Some(root_node) = self.document_network.get_root_node() {
if let Some(NodeInput::Node { node_id, .. }) = &self.document_network.nodes.get(&post_node_id).expect("Output node should always exist").inputs.get(0) { // If the current root node is the artboard, then the new layer should be a child of the artboard
let input_node = self.document_network.nodes.get(&node_id).expect("First input node should exist"); let current_root_node = self.document_network.nodes.get(&root_node.id).expect("Root node should always exist");
if input_node.is_artboard() { if current_root_node.is_artboard() && current_root_node.is_layer {
post_node_id = *node_id; 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 new_layer_node = resolve_document_node_type("Merge").expect("Merge node").default_document_node();
if let Some(pre_node_id) = pre_node_id { let (post_node_id, pre_node_id, post_node_input_index) = Self::get_post_node_with_index(self.document_network, parent, skip_layer_nodes);
self.insert_between(
new_id, if let Some(post_node_id) = post_node_id {
new_layer_node, if let Some(pre_node_id) = pre_node_id {
NodeInput::node(pre_node_id, 0), self.insert_between(
0, // pre_node is a sibling so it connects to the first input new_id,
post_node_id, new_layer_node,
NodeInput::node(new_id, 0), NodeInput::node(pre_node_id, 0),
post_node_input_index, 0, // pre_node is a sibling so it connects to the first input
IVec2::new(0, 3), post_node_id,
); NodeInput::node(new_id, 0),
post_node_input_index,
IVec2::new(0, 3),
);
} 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 { } else {
let offset = if post_node_input_index == 1 { IVec2::new(-8, 3) } else { IVec2::new(0, 3) }; // If post_node does not exist, then network is empty
self.insert_node_before(new_id, post_node_id, post_node_input_index, new_layer_node, offset); ModifyInputsContext::insert_node_as_primary_export(self.document_network, new_id, new_layer_node);
} }
Some(new_id) 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> { 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 skip_layer_nodes = if insert_index < 0 { (-1 - insert_index) as usize } else { insert_index as usize };
self.create_layer(new_id, parent, skip_layer_nodes)
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)
} }
/// Creates an artboard that outputs to the output node. /// Creates an artboard that outputs to the output node.
pub fn create_artboard(&mut self, new_id: NodeId, artboard: Artboard) -> Option<NodeId> { 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( 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)), Some(NodeInput::value(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroup::EMPTY), true)),
@ -259,33 +304,7 @@ impl<'a> ModifyInputsContext<'a> {
Default::default(), 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. ModifyInputsContext::insert_node_as_primary_export(self.document_network, new_id, artboard_node)
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
} }
pub fn insert_vector_data(&mut self, subpaths: Vec<Subpath<ManipulatorGroupId>>, layer: NodeId) { pub fn insert_vector_data(&mut self, subpaths: Vec<Subpath<ManipulatorGroupId>>, layer: NodeId) {
let shape = { let shape = {
@ -310,7 +329,7 @@ impl<'a> ModifyInputsContext<'a> {
pub fn insert_text(&mut self, text: String, font: Font, size: f64, layer: NodeId) { 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( 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::String(text), false),
NodeInput::value(TaggedValue::Font(font), false), NodeInput::value(TaggedValue::Font(font), false),
NodeInput::value(TaggedValue::F64(size), false), NodeInput::value(TaggedValue::F64(size), false),
@ -348,7 +367,7 @@ impl<'a> ModifyInputsContext<'a> {
self.responses.add(NodeGraphMessage::RunDocumentGraph); 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(); let mut shift_nodes = HashSet::new();
if shift_self { if shift_self {
shift_nodes.insert(node_id); shift_nodes.insert(node_id);
@ -356,7 +375,7 @@ impl<'a> ModifyInputsContext<'a> {
let mut stack = vec![node_id]; let mut stack = vec![node_id];
while let Some(node_id) = stack.pop() { 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 { for input in &node.inputs {
let NodeInput::Node { node_id, .. } = input else { continue }; let NodeInput::Node { node_id, .. } = input else { continue };
if shift_nodes.insert(*node_id) { if shift_nodes.insert(*node_id) {
@ -366,7 +385,7 @@ impl<'a> ModifyInputsContext<'a> {
} }
for node_id in shift_nodes { 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; node.metadata.position += shift;
} }
} }
@ -374,7 +393,19 @@ impl<'a> ModifyInputsContext<'a> {
/// Inserts a new node and modifies the inputs /// 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)) { 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 { let Some(output_node) = self.document_network.nodes.get_mut(&output_node_id) else {
warn!("Output node doesn't exist"); warn!("Output node doesn't exist");
return; return;
@ -411,8 +442,16 @@ impl<'a> ModifyInputsContext<'a> {
let existing_node_id = self let existing_node_id = self
.document_network .document_network
.upstream_flow_back_from_nodes( .upstream_flow_back_from_nodes(
self.layer_node self.layer_node.map_or_else(
.map_or_else(|| self.document_network.exports.iter().map(|output| output.node_id).collect(), |id| vec![id]), || {
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, graph_craft::document::FlowType::HorizontalFlow,
) )
.find(|(node, _)| node.name == name) .find(|(node, _)| node.name == name)
@ -423,7 +462,6 @@ impl<'a> ModifyInputsContext<'a> {
self.modify_new_node(name, update_input); self.modify_new_node(name, update_input);
} }
self.node_graph.network.clear();
self.responses.add(PropertiesPanelMessage::Refresh); self.responses.add(PropertiesPanelMessage::Refresh);
if !skip_rerender { if !skip_rerender {
@ -436,8 +474,16 @@ impl<'a> ModifyInputsContext<'a> {
let existing_nodes: Vec<_> = self let existing_nodes: Vec<_> = self
.document_network .document_network
.upstream_flow_back_from_nodes( .upstream_flow_back_from_nodes(
self.layer_node self.layer_node.map_or_else(
.map_or_else(|| self.document_network.exports.iter().map(|output| output.node_id).collect(), |id| vec![id]), || {
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, graph_craft::document::FlowType::HorizontalFlow,
) )
.filter(|(node, _)| node.name == name) .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) { pub fn fill_set(&mut self, fill: Fill) {
self.modify_inputs("Fill", false, |inputs, _node_id, _metadata| { self.modify_inputs("Fill", false, |inputs, _node_id, _metadata| {
let fill_type = match fill { 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 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 new_bounds_min, mut new_bounds_max] = [DVec2::ZERO, DVec2::ONE];
let mut empty = false; 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]); self.update_bounds([old_bounds_min, old_bounds_max], [new_bounds_min, new_bounds_max]);
if empty { if empty {
if let Some(id) = self.layer_node { self.layer_node.map(|layer_id| LayerNodeIdentifier::new(layer_id, &self.document_network))
self.responses.add(DocumentMessage::DeleteLayer { id }) } else {
} None
} }
} }
@ -620,15 +702,341 @@ impl<'a> ModifyInputsContext<'a> {
if dimensions.x < 0 { if dimensions.x < 0 {
dimensions.x *= -1; dimensions.x *= -1;
location.x += dimensions.x; location.x -= dimensions.x;
} }
if dimensions.y < 0 { if dimensions.y < 0 {
dimensions.y *= -1; dimensions.y *= -1;
location.y += dimensions.y; location.y -= dimensions.y;
} }
inputs[2] = NodeInput::value(TaggedValue::IVec2(location), false); inputs[2] = NodeInput::value(TaggedValue::IVec2(location), false);
inputs[3] = NodeInput::value(TaggedValue::IVec2(dimensions), 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 crate::messages::prelude::*;
use glam::IVec2;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, NodeId, NodeInput}; use graph_craft::document::{DocumentNode, NodeId, NodeInput};
use graph_craft::proto::GraphErrors; use graph_craft::proto::GraphErrors;
@ -12,7 +11,7 @@ pub enum NodeGraphMessage {
// Messages // Messages
Init, Init,
SelectedNodesUpdated, SelectedNodesUpdated,
ConnectNodesByLink { ConnectNodesByWire {
output_node: NodeId, output_node: NodeId,
output_node_connector_index: usize, output_node_connector_index: usize,
input_node: NodeId, input_node: NodeId,
@ -33,14 +32,10 @@ pub enum NodeGraphMessage {
DeleteSelectedNodes { DeleteSelectedNodes {
reconnect: bool, reconnect: bool,
}, },
DisconnectNodes { DisconnectInput {
node_id: NodeId, node_id: NodeId,
input_index: usize, input_index: usize,
}, },
DisconnectLayerFromStack {
node_id: NodeId,
reconnect_to_sibling: bool,
},
EnterNestedNetwork { EnterNestedNetwork {
node: NodeId, node: NodeId,
}, },
@ -49,7 +44,7 @@ pub enum NodeGraphMessage {
node_id: NodeId, node_id: NodeId,
}, },
ExitNestedNetwork { ExitNestedNetwork {
depth_of_nesting: usize, steps_back: usize,
}, },
ExposeInput { ExposeInput {
node_id: NodeId, node_id: NodeId,
@ -76,6 +71,7 @@ pub enum NodeGraphMessage {
PasteNodes { PasteNodes {
serialized_nodes: String, serialized_nodes: String,
}, },
PrintSelectedNodeCoordinates,
RunDocumentGraph, RunDocumentGraph,
SelectedNodesAdd { SelectedNodesAdd {
nodes: Vec<NodeId>, nodes: Vec<NodeId>,
@ -97,12 +93,8 @@ pub enum NodeGraphMessage {
input_index: usize, input_index: usize,
input: NodeInput, input: NodeInput,
}, },
SetNodePosition {
node_id: NodeId,
position: IVec2,
},
SetQualifiedInputValue { SetQualifiedInputValue {
node_path: Vec<NodeId>, node_id: NodeId,
input_index: usize, input_index: usize,
value: TaggedValue, value: TaggedValue,
}, },
@ -110,11 +102,6 @@ pub enum NodeGraphMessage {
ShiftNode { ShiftNode {
node_id: NodeId, node_id: NodeId,
}, },
ShiftUpstream {
node_id: NodeId,
shift: IVec2,
shift_self: bool,
},
SetVisibility { SetVisibility {
node_id: NodeId, node_id: NodeId,
visible: bool, visible: bool,
@ -135,7 +122,7 @@ pub enum NodeGraphMessage {
node_id: NodeId, node_id: NodeId,
is_layer: bool, is_layer: bool,
}, },
ToggleLocked { StartPreviewingWithoutRestore {
node_id: NodeId, node_id: NodeId,
}, },
TogglePreview { 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> { 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 { if let NodeInput::Value {
tagged_value: TaggedValue::String(x), 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> { 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 { if let NodeInput::Value {
tagged_value: TaggedValue::String(x), 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> { 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 { if let NodeInput::Value {
tagged_value: TaggedValue::Bool(x), 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 { 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); 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> { 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| { let from_string = |string: &str| {
string 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> { 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| { let from_string = |string: &str| {
string 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> { 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(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.push(TextLabel::new("Vector data must be supplied through the graph").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 { 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 NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] {
if let &TaggedValue::Color(x) = tagged_value { 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 let image_size = context
.executor .executor
.introspect_node_in_network( .introspect_node_in_network(
context.network, context.document_network,
&imaginate_node, &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), |frame: &IORecord<(), ImageFrame<Color>>| (frame.output.image.width, frame.output.image.height),
) )
.unwrap_or_default(); .unwrap_or_default();
@ -2032,7 +2045,7 @@ pub fn imaginate_properties(document_node: &DocumentNode, node_id: NodeId, conte
let resolution = { let resolution = {
use graphene_std::imaginate::pick_safe_imaginate_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 round = |x: DVec2| {
let (x, y) = pick_safe_imaginate_resolution(x.into()); 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::value::TaggedValue;
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
use graphene_core::Type;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FrontendGraphDataType { pub enum FrontendGraphDataType {
#[default] #[default]
#[serde(rename = "general")]
General, General,
#[serde(rename = "raster")]
Raster, Raster,
#[serde(rename = "color")] VectorData,
Color,
#[serde(rename = "general")]
Text,
#[serde(rename = "vector")]
Subpath,
#[serde(rename = "number")]
Number, Number,
#[serde(rename = "general")] Graphic,
Boolean,
/// Refers to the mathematical vector, with direction and magnitude.
#[serde(rename = "number")]
Vector,
#[serde(rename = "raster")]
GraphicGroup,
#[serde(rename = "artboard")]
Artboard, Artboard,
#[serde(rename = "color")]
Palette,
} }
impl FrontendGraphDataType { impl FrontendGraphDataType {
pub const fn with_tagged_value(value: &TaggedValue) -> Self { pub fn with_type(input: &Type) -> Self {
match value { match TaggedValue::from_type(input) {
TaggedValue::String(_) => Self::Text, TaggedValue::Image(_) | TaggedValue::ImageFrame(_) => Self::Raster,
TaggedValue::F64(_) | TaggedValue::U32(_) | TaggedValue::DAffine2(_) => Self::Number, TaggedValue::Subpaths(_) | TaggedValue::RcSubpath(_) | TaggedValue::VectorData(_) => Self::VectorData,
TaggedValue::Bool(_) => Self::Boolean, TaggedValue::U32(_)
TaggedValue::DVec2(_) | TaggedValue::IVec2(_) => Self::Vector, | TaggedValue::U64(_)
TaggedValue::Image(_) => Self::Raster, | TaggedValue::F64(_)
TaggedValue::ImageFrame(_) => Self::Raster, | TaggedValue::UVec2(_)
TaggedValue::Color(_) => Self::Color, | TaggedValue::IVec2(_)
TaggedValue::RcSubpath(_) | TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::Subpath, | TaggedValue::DVec2(_)
TaggedValue::GraphicGroup(_) => Self::GraphicGroup, | TaggedValue::OptionalDVec2(_)
TaggedValue::Artboard(_) | TaggedValue::ArtboardGroup(_) => Self::Artboard, | TaggedValue::F64Array4(_)
TaggedValue::Palette(_) => Self::Palette, | TaggedValue::VecF64(_)
| TaggedValue::VecDVec2(_) => Self::Number,
TaggedValue::GraphicGroup(_) | TaggedValue::GraphicElement(_) => Self::Graphic,
TaggedValue::ArtboardGroup(_) => Self::Artboard,
_ => Self::General, _ => Self::General,
} }
} }
@ -65,9 +52,9 @@ pub struct FrontendGraphOutput {
pub name: String, pub name: String,
#[serde(rename = "resolvedType")] #[serde(rename = "resolvedType")]
pub resolved_type: Option<String>, pub resolved_type: Option<String>,
pub connected: Option<NodeId>, pub connected: Vec<NodeId>,
#[serde(rename = "connectedIndex")] #[serde(rename = "connectedIndex")]
pub connected_index: Option<usize>, pub connected_index: Vec<usize>,
} }
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
@ -92,19 +79,21 @@ pub struct FrontendNode {
pub locked: bool, pub locked: bool,
pub previewed: bool, pub previewed: bool,
pub errors: Option<String>, 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)] #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeLink { pub struct FrontendNodeWire {
#[serde(rename = "linkStart")] #[serde(rename = "wireStart")]
pub link_start: NodeId, pub wire_start: NodeId,
#[serde(rename = "linkStartOutputIndex")] #[serde(rename = "wireStartOutputIndex")]
pub link_start_output_index: usize, pub wire_start_output_index: usize,
#[serde(rename = "linkEnd")] #[serde(rename = "wireEnd")]
pub link_end: NodeId, pub wire_end: NodeId,
#[serde(rename = "linkEndInputIndex")] #[serde(rename = "wireEndInputIndex")]
pub link_end_input_index: usize, pub wire_end_input_index: usize,
pub dashed: bool,
} }
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] #[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.)); array.push(&JsValue::from(dash_width - 1.));
self.render_context self.render_context
.set_line_dash(&JsValue::from(array)) .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(); .ok();
} else { } else {
let array = js_sys::Array::new(); let array = js_sys::Array::new();
self.render_context self.render_context
.set_line_dash(&JsValue::from(array)) .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(); .ok();
} }
self.render_context.begin_path(); self.render_context.begin_path();

View file

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

View file

@ -34,7 +34,7 @@ impl Default for DocumentMetadata {
fn default() -> Self { fn default() -> Self {
Self { Self {
upstream_transforms: HashMap::new(), upstream_transforms: HashMap::new(),
structure: HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]), structure: HashMap::new(),
artboards: HashSet::new(), artboards: HashSet::new(),
folders: HashSet::new(), folders: HashSet::new(),
hidden: HashSet::new(), hidden: HashSet::new(),
@ -50,13 +50,8 @@ impl Default for DocumentMetadata {
// ================================= // =================================
impl DocumentMetadata { impl DocumentMetadata {
/// Get the root layer from the document
pub const fn root(&self) -> LayerNodeIdentifier {
LayerNodeIdentifier::ROOT
}
pub fn all_layers(&self) -> DescendantsIter<'_> { pub fn all_layers(&self) -> DescendantsIter<'_> {
self.root().descendants(self) LayerNodeIdentifier::ROOT_PARENT.descendants(self)
} }
pub fn layer_exists(&self, layer: LayerNodeIdentifier) -> bool { pub fn layer_exists(&self, layer: LayerNodeIdentifier) -> bool {
@ -114,11 +109,15 @@ impl DocumentMetadata {
} }
pub fn active_artboard(&self) -> LayerNodeIdentifier { 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 { 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 { pub fn is_artboard(&self, layer: LayerNodeIdentifier) -> bool {
@ -147,23 +146,22 @@ impl DocumentMetadata {
impl DocumentMetadata { impl DocumentMetadata {
/// Loads the structure of layer nodes from a node graph. /// Loads the structure of layer nodes from a node graph.
pub fn load_structure(&mut self, graph: &NodeNetwork, selected_nodes: &mut SelectedNodes) { pub fn load_structure(&mut self, graph: &NodeNetwork) {
self.structure = HashMap::from_iter([(LayerNodeIdentifier::ROOT, NodeRelations::default())]); self.structure = HashMap::from_iter([(LayerNodeIdentifier::ROOT_PARENT, NodeRelations::default())]);
self.artboards = HashSet::new(); self.artboards = HashSet::new();
self.folders = HashSet::new(); self.folders = HashSet::new();
self.hidden = HashSet::new(); self.hidden = HashSet::new();
self.locked = HashSet::new(); self.locked = HashSet::new();
// Refers to output node: NodeId(0) // Should refer to output node
let output_node_id = graph.exports[0].node_id;
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![]; let mut awaiting_primary_flow = vec![];
while let Some((horizontal_root_node_id, mut parent_layer_node)) = awaiting_horizontal_flow.pop() { 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); let horizontal_flow_iter = graph.upstream_flow_back_from_nodes(vec![horizontal_root_node_id], FlowType::HorizontalFlow);
// Skip the horizontal_root_node_id node // 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 { if !current_node.visible {
self.hidden.insert(current_node_id); self.hidden.insert(current_node_id);
} }
@ -176,6 +174,7 @@ impl DocumentMetadata {
let current_layer_node = LayerNodeIdentifier::new(current_node_id, graph); let current_layer_node = LayerNodeIdentifier::new(current_node_id, graph);
if !self.structure.contains_key(&current_layer_node) { if !self.structure.contains_key(&current_layer_node) {
awaiting_primary_flow.push((current_node_id, parent_layer_node)); awaiting_primary_flow.push((current_node_id, parent_layer_node));
parent_layer_node.push_child(self, current_layer_node); parent_layer_node.push_child(self, current_layer_node);
parent_layer_node = 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.upstream_transforms.retain(|node, _| graph.nodes.contains_key(node));
self.click_targets.retain(|layer, _| self.structure.contains_key(layer)); 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 { pub fn transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 {
layer layer
.ancestors(self) .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() .copied()
.map(|(footprint, transform)| footprint.transform * transform) .map(|(footprint, transform)| footprint.transform * transform)
.next() .next()
@ -260,6 +264,10 @@ impl DocumentMetadata {
} }
pub fn downstream_transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 { pub fn downstream_transform_to_viewport(&self, layer: LayerNodeIdentifier) -> DAffine2 {
if layer == LayerNodeIdentifier::ROOT_PARENT {
return self.transform_to_viewport(layer);
}
self.upstream_transforms self.upstream_transforms
.get(&layer.to_node()) .get(&layer.to_node())
.copied() .copied()
@ -351,20 +359,23 @@ impl DocumentMetadata {
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct LayerNodeIdentifier(NonZeroU64); pub struct LayerNodeIdentifier(NonZeroU64);
impl Default for LayerNodeIdentifier { impl core::fmt::Debug for LayerNodeIdentifier {
fn default() -> Self { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Self::ROOT 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 { impl Default for LayerNodeIdentifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn default() -> Self {
f.debug_tuple("LayerNodeIdentifier").field(&self.to_node()).finish() Self::ROOT_PARENT
} }
} }
impl LayerNodeIdentifier { 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 /// Construct a [`LayerNodeIdentifier`] without checking if it is a layer node
pub const fn new_unchecked(node_id: NodeId) -> Self { pub const fn new_unchecked(node_id: NodeId) -> Self {
@ -376,7 +387,7 @@ impl LayerNodeIdentifier {
#[track_caller] #[track_caller]
pub fn new(node_id: NodeId, network: &NodeNetwork) -> Self { pub fn new(node_id: NodeId, network: &NodeNetwork) -> Self {
debug_assert!( 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}: {:#?}", "Layer identifier constructed from non-layer node {node_id}: {:#?}",
network.nodes.get(&node_id) network.nodes.get(&node_id)
); );
@ -385,7 +396,9 @@ impl LayerNodeIdentifier {
/// Access the node id of this layer /// Access the node id of this layer
pub fn to_node(self) -> NodeId { 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 /// 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 /// All ancestors of this layer, including self, going to the document root
pub fn ancestors(self, metadata: &DocumentMetadata) -> AxisIter { pub fn ancestors(self, metadata: &DocumentMetadata) -> AxisIter {
AxisIter { AxisIter {
@ -550,13 +571,6 @@ impl LayerNodeIdentifier {
pub fn starts_with(&self, other: Self, metadata: &DocumentMetadata) -> bool { pub fn starts_with(&self, other: Self, metadata: &DocumentMetadata) -> bool {
self.ancestors(metadata).any(|parent| parent == other) 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 { 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 }; let Some(node) = network.nodes.get(&layer.to_node()) else { return false };
node.is_artboard() node.is_artboard()
} }
@ -654,7 +671,7 @@ pub fn is_artboard(layer: LayerNodeIdentifier, network: &NodeNetwork) -> bool {
#[test] #[test]
fn test_tree() { fn test_tree() {
let mut metadata = DocumentMetadata::default(); let mut metadata = DocumentMetadata::default();
let root = metadata.root(); let root = LayerNodeIdentifier::ROOT_PARENT;
let metadata = &mut metadata; let metadata = &mut metadata;
root.push_child(metadata, LayerNodeIdentifier::new_unchecked(NodeId(3))); root.push_child(metadata, LayerNodeIdentifier::new_unchecked(NodeId(3)));
assert_eq!(root.children(metadata).collect::<Vec<_>>(), vec![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 super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use graph_craft::document::NodeId; use graph_craft::document::{NodeId, NodeNetwork};
use serde::ser::SerializeStruct; use serde::ser::SerializeStruct;
@ -57,7 +57,13 @@ pub struct SelectedNodes(pub Vec<NodeId>);
impl SelectedNodes { impl SelectedNodes {
pub fn layer_visible(&self, layer: LayerNodeIdentifier, metadata: &DocumentMetadata) -> bool { 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> + '_ { 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 { 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> + '_ { 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) self.selected_layers(metadata).any(|selected| selected == layer)
} }
pub fn selected_nodes(&self) -> core::slice::Iter<'_, NodeId> { // All selected nodes must be in the same network
self.0.iter() 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> { pub fn selected_nodes_ref(&self) -> &Vec<NodeId> {
@ -105,16 +120,43 @@ impl SelectedNodes {
self.0.retain(f); self.0.retain(f);
} }
pub fn set_selected_nodes(&mut self, new: Vec<NodeId>) { // 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.
self.0 = new; 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);
}
self.0 = new_nodes;
} }
pub fn add_selected_nodes(&mut self, iter: impl IntoIterator<Item = NodeId>) { pub fn add_selected_nodes(&mut self, new: Vec<NodeId>, document_network: &NodeNetwork, network_path: &Vec<NodeId>) {
self.0.extend(iter); 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) { 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) => { OriginalTransforms::Layer(layer_map) => {
layer_map.retain(|layer, _| selected.contains(layer)); layer_map.retain(|layer, _| selected.contains(layer));
for &layer in selected { for &layer in selected {
if layer == LayerNodeIdentifier::ROOT_PARENT {
continue;
}
layer_map.entry(layer).or_insert_with(|| document_metadata.upstream_transform(layer.to_node())); 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 { 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()), selected: active_document.selected_nodes.selected_layers_contains(layer, active_document.metadata()),
visible: active_document.selected_nodes.layer_visible(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()), 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 } => { PortfolioMessage::PasteIntoFolder { clipboard, parent, insert_index } => {
let paste = |entry: &CopyBufferEntry, responses: &mut VecDeque<_>| { let paste = |entry: &CopyBufferEntry, responses: &mut VecDeque<_>| {

View file

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

View file

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

View file

@ -276,7 +276,13 @@ impl SnapManager {
candidates.push(layer); 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 { if candidates.len() > 10 {
warn!("Snap candidate overflow"); 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) { 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); self.collect_anchors(snap_data, point.source_index == 0);
//info!("Points to snap {:#?}", self.points_to_snap);
let mut best = None; let mut best = None;
for candidate in &self.points_to_snap { for candidate in &self.points_to_snap {
// Candidate is not on constraint // Candidate is not on constraint

View file

@ -155,6 +155,10 @@ impl ArtboardToolData {
let Some(movement) = &bounds.selected_edges else { let Some(movement) = &bounds.selected_edges else {
return; 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 center = from_center.then_some(bounds.center_of_transformation);
let (min, size) = movement.new_size(mouse_position, bounds.transform, center, constrain_square, None); 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 size = bounds.bounds[1] - bounds.bounds[0];
let position = bounds.bounds[0] + bounds.transform.inverse().transform_vector2(mouse_position - tool_data.drag_current); 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 { responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap().to_node(), id: tool_data.selected_artboard.unwrap().to_node(),
location: position.round().as_ivec2(), location: position.round().as_ivec2(),
@ -257,7 +265,7 @@ impl Fsm for ArtboardToolFsmState {
} }
(ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => { (ArtboardToolFsmState::Drawing, ArtboardToolMessage::PointerMove { constrain_axis_or_aspect, center }) => {
let mouse_position = input.mouse.position; 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(); let root_transform = document.metadata().document_to_viewport.inverse();
@ -280,11 +288,15 @@ impl Fsm for ArtboardToolFsmState {
let start = start.min(end); let start = start.min(end);
if let Some(artboard) = tool_data.selected_artboard { if let Some(artboard) = tool_data.selected_artboard {
responses.add(GraphOperationMessage::ResizeArtboard { if artboard == LayerNodeIdentifier::ROOT_PARENT {
id: artboard.to_node(), log::error!("Selected artboard cannot be ROOT_PARENT");
location: start.round().as_ivec2(), } else {
dimensions: size.round().as_ivec2(), responses.add(GraphOperationMessage::ResizeArtboard {
}); id: artboard.to_node(),
location: start.round().as_ivec2(),
dimensions: size.round().as_ivec2(),
});
}
} else { } else {
let id = NodeId(generate_uuid()); let id = NodeId(generate_uuid());
@ -395,11 +407,15 @@ impl Fsm for ArtboardToolFsmState {
} }
(_, ArtboardToolMessage::NudgeSelected { delta_x, delta_y }) => { (_, ArtboardToolMessage::NudgeSelected { delta_x, delta_y }) => {
if let Some(bounds) = &mut tool_data.bounding_box_manager { if let Some(bounds) = &mut tool_data.bounding_box_manager {
responses.add(GraphOperationMessage::ResizeArtboard { if tool_data.selected_artboard.unwrap() == LayerNodeIdentifier::ROOT_PARENT {
id: tool_data.selected_artboard.unwrap().to_node(), log::error!("Selected artboard cannot be ROOT_PARENT");
location: DVec2::new(bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y).round().as_ivec2(), } else {
dimensions: (bounds.bounds[1] - bounds.bounds[0]).round().as_ivec2(), 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 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_position = tool_data.transform.inverse().transform_point2(parent_transform);
let layer_document_scale = document.metadata().transform_to_document(parent) * tool_data.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: 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. // 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 let layer_scale = 0.0001_f64 // Safety against division by zero
@ -355,7 +356,7 @@ impl Fsm for BrushToolFsmState {
(BrushToolFsmState::Drawing, BrushToolMessage::PointerMove) => { (BrushToolFsmState::Drawing, BrushToolMessage::PointerMove) => {
if let Some(layer) = tool_data.layer { if let Some(layer) = tool_data.layer {
if let Some(stroke) = tool_data.strokes.last_mut() { 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 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); 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 /// Contains information about the selected gradient handle
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
struct SelectedGradient { struct SelectedGradient {
layer: LayerNodeIdentifier, layer: Option<LayerNodeIdentifier>,
transform: DAffine2, transform: DAffine2,
gradient: Gradient, gradient: Gradient,
dragging: GradientDragTarget, dragging: GradientDragTarget,
@ -138,7 +138,7 @@ impl SelectedGradient {
pub fn new(gradient: Gradient, layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Self { pub fn new(gradient: Gradient, layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> Self {
let transform = gradient_space_transform(layer, document); let transform = gradient_space_transform(layer, document);
Self { Self {
layer, layer: Some(layer),
transform, transform,
gradient, gradient,
dragging: GradientDragTarget::End, dragging: GradientDragTarget::End,
@ -198,10 +198,12 @@ impl SelectedGradient {
/// Update the layer fill to the current gradient /// Update the layer fill to the current gradient
pub fn render_gradient(&mut self, responses: &mut VecDeque<Message>) { pub fn render_gradient(&mut self, responses: &mut VecDeque<Message>) {
self.gradient.transform = self.transform; self.gradient.transform = self.transform;
responses.add(GraphOperationMessage::FillSet { if let Some(layer) = self.layer {
layer: self.layer, responses.add(GraphOperationMessage::FillSet {
fill: Fill::Gradient(self.gradient.clone()), layer,
}); fill: Fill::Gradient(self.gradient.clone()),
});
}
} }
} }
@ -250,7 +252,9 @@ impl Fsm for GradientToolFsmState {
for layer in document.selected_nodes.selected_visible_layers(document.metadata()) { for layer in document.selected_nodes.selected_visible_layers(document.metadata()) {
let Some(gradient) = get_gradient(layer, &document.network) else { continue }; let Some(gradient) = get_gradient(layer, &document.network) else { continue };
let transform = gradient_space_transform(layer, document); 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 Gradient { start, end, positions, .. } = gradient;
let (start, end) = (transform.transform_point2(start), transform.transform_point2(end)); 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 // The gradient has only one point and so should become a fill
if selected_gradient.gradient.positions.len() == 1 { if selected_gradient.gradient.positions.len() == 1 {
responses.add(GraphOperationMessage::FillSet { if let Some(layer) = selected_gradient.layer {
layer: selected_gradient.layer, responses.add(GraphOperationMessage::FillSet {
fill: Fill::Solid(selected_gradient.gradient.positions[0].1), layer,
}); fill: Fill::Solid(selected_gradient.gradient.positions[0].1),
});
}
return self; return self;
} }
@ -367,7 +374,7 @@ impl Fsm for GradientToolFsmState {
if pos.distance_squared(mouse) < tolerance { if pos.distance_squared(mouse) < tolerance {
dragging = true; dragging = true;
tool_data.selected_gradient = Some(SelectedGradient { tool_data.selected_gradient = Some(SelectedGradient {
layer, layer: Some(layer),
transform, transform,
gradient: gradient.clone(), gradient: gradient.clone(),
dragging: GradientDragTarget::Step(index), dragging: GradientDragTarget::Step(index),
@ -381,7 +388,7 @@ impl Fsm for GradientToolFsmState {
if pos.distance_squared(mouse) < tolerance { if pos.distance_squared(mouse) < tolerance {
dragging = true; dragging = true;
tool_data.selected_gradient = Some(SelectedGradient { tool_data.selected_gradient = Some(SelectedGradient {
layer, layer: Some(layer),
transform, transform,
gradient: gradient.clone(), gradient: gradient.clone(),
dragging: dragging_target, dragging: dragging_target,

View file

@ -362,9 +362,9 @@ impl PathToolData {
// TODO: enable snapping // TODO: enable snapping
//self // self
// .snap_manager // .snap_manager
// .start_snap(document, input, document.bounding_boxes(Some(&selected_layers), None, font_cache), true, true); // .start_snap(document, input, document.bounding_boxes(Some(&selected_layers), None, font_cache), true, true);
// Do not snap against handles when anchor is selected // Do not snap against handles when anchor is selected
let mut additional_selected_points = Vec::new(); let mut additional_selected_points = Vec::new();

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 operations = BooleanOperation::list();
let icons = BooleanOperation::icons(); 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) IconButton::new(icon, 24)
.tooltip(operation.to_string()) .tooltip(operation.to_string())
.disabled(!enabled(operation))
.on_update(move |_| GraphOperationMessage::InsertBooleanOperation { operation }.into()) .on_update(move |_| GraphOperationMessage::InsertBooleanOperation { operation }.into())
.widget_holder() .widget_holder()
}) })
@ -194,10 +203,8 @@ impl LayoutHolder for SelectTool {
widgets.extend(self.flip_widgets(disabled)); widgets.extend(self.flip_widgets(disabled));
// Boolean // Boolean
if self.tool_data.selected_layers_count == 2 { widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.extend(self.boolean_widgets(self.tool_data.selected_layers_count));
widgets.extend(self.boolean_widgets());
}
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }])) Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
} }
@ -316,6 +323,14 @@ impl SelectToolData {
let mut new_dragging = Vec::new(); let mut new_dragging = Vec::new();
for layer_ancestors in document.metadata().shallowest_unique_layers(self.layers_dragging.iter().copied().rev()) { for layer_ancestors in document.metadata().shallowest_unique_layers(self.layers_dragging.iter().copied().rev()) {
let Some(layer) = layer_ancestors.last().copied() else { continue }; 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 }; let Some(parent) = layer.parent(&document.metadata) else { continue };
// Moves the layer back to its starting position. // Moves the layer back to its starting position.
@ -345,7 +360,8 @@ impl SelectToolData {
copy_ids.insert(node_id, NodeId((index + 1) as u64)); 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); let insert_index = DocumentMessageHandler::get_calculated_insert_index(&document.metadata, &document.selected_nodes, parent);
@ -368,8 +384,13 @@ impl SelectToolData {
// Delete the duplicated layers // Delete the duplicated layers
for layer_ancestors in document.metadata().shallowest_unique_layers(self.layers_dragging.iter().copied()) { 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 { responses.add(NodeGraphMessage::DeleteNodes {
node_ids: vec![layer_ancestors.last().unwrap().to_node()], node_ids: vec![layer.to_node()],
reconnect: true, reconnect: true,
}); });
} }
@ -382,7 +403,17 @@ impl SelectToolData {
skip_rerender: true, 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 }); responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
self.layers_dragging = original; self.layers_dragging = original;
} }
@ -512,8 +543,8 @@ impl Fsm for SelectToolFsmState {
if tool_data.pivot.is_over(input.mouse.position) { if tool_data.pivot.is_over(input.mouse.position) {
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
//tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); // tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
//tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); // tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
SelectToolFsmState::DraggingPivot SelectToolFsmState::DraggingPivot
} }
@ -526,7 +557,15 @@ impl Fsm for SelectToolFsmState {
if let Some(bounds) = &mut tool_data.bounding_box_manager { if let Some(bounds) = &mut tool_data.bounding_box_manager {
bounds.original_bound_transform = bounds.transform; 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( let mut selected = Selected::new(
&mut bounds.original_transforms, &mut bounds.original_transforms,
&mut bounds.center_of_transformation, &mut bounds.center_of_transformation,
@ -548,7 +587,14 @@ impl Fsm for SelectToolFsmState {
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
if let Some(bounds) = &mut tool_data.bounding_box_manager { 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( let mut selected = Selected::new(
&mut bounds.original_transforms, &mut bounds.original_transforms,
&mut bounds.center_of_transformation, &mut bounds.center_of_transformation,
@ -705,7 +751,14 @@ impl Fsm for SelectToolFsmState {
let pivot_transform = DAffine2::from_translation(pivot); let pivot_transform = DAffine2::from_translation(pivot);
let transformation = pivot_transform * delta * pivot_transform.inverse(); 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 selected = &tool_data.layers_dragging;
let mut selected = Selected::new( let mut selected = Selected::new(
&mut bounds.original_transforms, &mut bounds.original_transforms,
@ -748,7 +801,14 @@ impl Fsm for SelectToolFsmState {
let delta = DAffine2::from_angle(snapped_angle); 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( let mut selected = Selected::new(
&mut bounds.original_transforms, &mut bounds.original_transforms,
&mut bounds.center_of_transformation, &mut bounds.center_of_transformation,
@ -767,7 +827,7 @@ impl Fsm for SelectToolFsmState {
} }
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove(modifier_keys)) => { (SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove(modifier_keys)) => {
let mouse_position = input.mouse.position; 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); tool_data.pivot.set_viewport_position(snapped_mouse_position, document, responses);
// AutoPanning // AutoPanning
@ -884,14 +944,28 @@ impl Fsm for SelectToolFsmState {
tool_data.layers_dragging.extend(replacement_selected_layers.iter()); tool_data.layers_dragging.extend(replacement_selected_layers.iter());
responses.add(NodeGraphMessage::SelectedNodesSet { 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() { } else if let Some(selecting_layer) = tool_data.select_single_layer.take() {
if !tool_data.has_dragged { if !tool_data.has_dragged {
responses.add(NodeGraphMessage::SelectedNodesSet { if selecting_layer == LayerNodeIdentifier::ROOT_PARENT {
nodes: vec![selecting_layer.to_node()], log::error!("selecting_layer should not be ROOT_PARENT");
}); } else {
responses.add(NodeGraphMessage::SelectedNodesSet {
nodes: vec![selecting_layer.to_node()],
});
}
} }
} }
@ -955,7 +1029,18 @@ impl Fsm for SelectToolFsmState {
tool_data.layers_dragging = new_selected.into_iter().collect(); tool_data.layers_dragging = new_selected.into_iter().collect();
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
responses.add(NodeGraphMessage::SelectedNodesSet { 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); responses.add(OverlaysMessage::Draw);
@ -986,7 +1071,13 @@ impl Fsm for SelectToolFsmState {
SelectToolFsmState::Ready { selection } SelectToolFsmState::Ready { selection }
} }
(_, SelectToolMessage::Abort) => { (_, 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() { if let Some(mut bounding_box_overlays) = tool_data.bounding_box_manager.take() {
let mut selected = Selected::new( let mut selected = Selected::new(
&mut bounding_box_overlays.original_transforms, &mut bounding_box_overlays.original_transforms,
@ -1103,27 +1194,47 @@ fn drag_shallowest_manipulation(responses: &mut VecDeque<Message>, selected: Vec
.filter(not_artboard(document)) .filter(not_artboard(document))
.find(|&ancestor| document.selected_nodes.selected_layers_contains(ancestor, document.metadata())); .find(|&ancestor| document.selected_nodes.selected_layers_contains(ancestor, document.metadata()));
let new_selected = ancestor.unwrap_or_else(|| { let new_selected = ancestor.unwrap_or_else(|| layer.ancestors(document.metadata()).filter(not_artboard(document)).last().unwrap_or(layer));
layer
.ancestors(document.metadata())
.take_while(|&layer| layer != LayerNodeIdentifier::ROOT)
.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.retain(|layer| !layer.ancestors(document.metadata()).any(|ancestor| ancestor == new_selected));
tool_data.layers_dragging.push(new_selected); tool_data.layers_dragging.push(new_selected);
} }
responses.add(NodeGraphMessage::SelectedNodesSet { 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) { 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 { 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; 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()] }); responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_selected.to_node()] });
} }

View file

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

View file

@ -2,7 +2,6 @@
use super::tool_prelude::*; use super::tool_prelude::*;
use crate::application::generate_uuid; 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::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
@ -34,8 +33,8 @@ impl Default for TextOptions {
fn default() -> Self { fn default() -> Self {
Self { Self {
font_size: 24, font_size: 24,
font_name: DEFAULT_FONT_FAMILY.into(), font_name: graphene_core::consts::DEFAULT_FONT_FAMILY.into(),
font_style: DEFAULT_FONT_STYLE.into(), font_style: graphene_core::consts::DEFAULT_FONT_STYLE.into(),
fill: ToolColorOptions::new_primary(), fill: ToolColorOptions::new_primary(),
} }
} }
@ -214,8 +213,9 @@ struct TextToolData {
impl TextToolData { impl TextToolData {
/// Set the editing state of the currently modifying layer /// Set the editing state of the currently modifying layer
fn set_editing(&self, editable: bool, font_cache: &FontCache, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) { 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) { 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) { 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>) { 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 { if tool_state == TextToolFsmState::Editing {
self.set_editing(false, font_cache, document, responses); self.set_editing(false, font_cache, document, responses);
} }
@ -427,7 +431,7 @@ impl Fsm for TextToolFsmState {
(TextToolFsmState::Editing, TextToolMessage::TextChange { new_text }) => { (TextToolFsmState::Editing, TextToolMessage::TextChange { new_text }) => {
tool_data.fix_text_bounds(&new_text, document, font_cache, responses); tool_data.fix_text_bounds(&new_text, document, font_cache, responses);
responses.add(NodeGraphMessage::SetQualifiedInputValue { 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, input_index: 1,
value: TaggedValue::String(new_text), 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::OptionalColor(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::VectorData(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::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::ImageFrame(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::Palette(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: #c5c5c5;
--color-data-general-dim: #767676; --color-data-general-dim: #767676;
--color-data-number: #cbbab4;
--color-data-number-dim: #87736b;
--color-data-raster: #e4bb72; --color-data-raster: #e4bb72;
--color-data-raster-dim: #8b7752; --color-data-raster-dim: #8b7752;
--color-data-vector: #65bbe5; --color-data-vectordata: #65bbe5;
--color-data-vector-dim: #4b778c; --color-data-vectordata-dim: #4b778c;
--color-data-color: #dce472; --color-data-number: #cbbab4;
--color-data-color-dim: #898d55; --color-data-number-dim: #87736b;
--color-data-graphic: #6b84e8;
--color-data-graphic-dim: #4a557b;
--color-data-artboard: #70a898; --color-data-artboard: #70a898;
--color-data-artboard-dim: #3a6156; --color-data-artboard-dim: #3a6156;

View file

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

View file

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

View file

@ -6,17 +6,17 @@
import type { NodeGraphState } from "@graphite/state-providers/node-graph"; import type { NodeGraphState } from "@graphite/state-providers/node-graph";
import type { IconName } from "@graphite/utility-functions/icons"; import type { IconName } from "@graphite/utility-functions/icons";
import type { Editor } from "@graphite/wasm-communication/editor"; 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 LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
import LayoutRow from "@graphite/components/layout/LayoutRow.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 IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte"; import TextButton from "@graphite/components/widgets/buttons/TextButton.svelte";
import RadioInput from "@graphite/components/widgets/inputs/RadioInput.svelte"; import RadioInput from "@graphite/components/widgets/inputs/RadioInput.svelte";
import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte"; import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte";
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte"; import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte"; import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
const WHEEL_RATE = (1 / 600) * 3; const WHEEL_RATE = (1 / 600) * 3;
const GRID_COLLAPSE_SPACING = 10; const GRID_COLLAPSE_SPACING = 10;
const GRID_SIZE = 24; const GRID_SIZE = 24;
@ -26,7 +26,7 @@
const editor = getContext<Editor>("editor"); const editor = getContext<Editor>("editor");
const nodeGraph = getContext<NodeGraphState>("nodeGraph"); 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 graph: HTMLDivElement | undefined;
let nodesContainer: HTMLDivElement | undefined; let nodesContainer: HTMLDivElement | undefined;
@ -41,13 +41,13 @@
let boxSelection: Box | undefined = undefined; let boxSelection: Box | undefined = undefined;
let previousSelection: bigint[] = []; let previousSelection: bigint[] = [];
let selectIfNotDragged: undefined | bigint = undefined; let selectIfNotDragged: undefined | bigint = undefined;
let linkInProgressFromConnector: SVGSVGElement | undefined = undefined; let wireInProgressFromConnector: SVGSVGElement | undefined = undefined;
let linkInProgressToConnector: SVGSVGElement | DOMRect | 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 // 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 wireInProgressFromLayerTop: bigint | undefined = undefined;
// let linkInProgressFromLayerBottom: bigint | undefined = undefined; // let wireInProgressFromLayerBottom: bigint | undefined = undefined;
let disconnecting: { nodeId: bigint; inputIndex: number; linkIndex: number } | undefined = undefined; let disconnecting: { nodeId: bigint; inputIndex: number; wireIndex: number } | undefined = undefined;
let nodeLinkPaths: LinkPath[] = []; let nodeWirePaths: WirePath[] = [];
let searchTerm = ""; let searchTerm = "";
let contextMenuOpenCoordinates: { x: number; y: number } | undefined = undefined; let contextMenuOpenCoordinates: { x: number; y: number } | undefined = undefined;
let toggleDisplayAsLayerNodeId: bigint | undefined = undefined; let toggleDisplayAsLayerNodeId: bigint | undefined = undefined;
@ -77,8 +77,8 @@
appearAboveMouse = contextMenuY > height - ADD_NODE_MENU_HEIGHT; appearAboveMouse = contextMenuY > height - ADD_NODE_MENU_HEIGHT;
})(); })();
$: linkPathInProgress = createLinkPathInProgress(linkInProgressFromConnector, linkInProgressToConnector); $: wirePathInProgress = createWirePathInProgress(wireInProgressFromConnector, wireInProgressToConnector);
$: linkPaths = createLinkPaths(linkPathInProgress, nodeLinkPaths); $: wirePaths = createWirePaths(wirePathInProgress, nodeWirePaths);
function calculateGridSpacing(scale: number): number { function calculateGridSpacing(scale: number): number {
const dense = scale * GRID_SIZE; const dense = scale * GRID_SIZE;
@ -129,21 +129,21 @@
return Array.from(categories); return Array.from(categories);
} }
function createLinkPathInProgress(linkInProgressFromConnector?: SVGSVGElement, linkInProgressToConnector?: SVGSVGElement | DOMRect): LinkPath | undefined { function createWirePathInProgress(wireInProgressFromConnector?: SVGSVGElement, wireInProgressToConnector?: SVGSVGElement | DOMRect): WirePath | undefined {
if (linkInProgressFromConnector && linkInProgressToConnector && nodesContainer) { if (wireInProgressFromConnector && wireInProgressToConnector && nodesContainer) {
const from = connectorToNodeIndex(linkInProgressFromConnector); const from = connectorToNodeIndex(wireInProgressFromConnector);
const to = linkInProgressToConnector instanceof SVGSVGElement ? connectorToNodeIndex(linkInProgressToConnector) : undefined; const to = wireInProgressToConnector instanceof SVGSVGElement ? connectorToNodeIndex(wireInProgressToConnector) : undefined;
const linkStart = $nodeGraph.nodes.find((n) => n.id === from?.nodeId)?.isLayer || false; const wireStart = $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; const wireEnd = ($nodeGraph.nodes.find((n) => n.id === to?.nodeId)?.isLayer && to?.index == 0) || false;
return createWirePath(linkInProgressFromConnector, linkInProgressToConnector, linkStart, linkEnd); return createWirePath(wireInProgressFromConnector, wireInProgressToConnector, wireStart, wireEnd, false);
} }
return undefined; return undefined;
} }
function createLinkPaths(linkPathInProgress: LinkPath | undefined, nodeLinkPaths: LinkPath[]): LinkPath[] { function createWirePaths(wirePathInProgress: WirePath | undefined, nodeWirePaths: WirePath[]): WirePath[] {
const maybeLinkPathInProgress = linkPathInProgress ? [linkPathInProgress] : []; const maybeWirePathInProgress = wirePathInProgress ? [wirePathInProgress] : [];
return [...maybeLinkPathInProgress, ...nodeLinkPaths]; return [...maybeWirePathInProgress, ...nodeWirePaths];
} }
async function watchNodes(nodes: FrontendNode[]) { async function watchNodes(nodes: FrontendNode[]) {
@ -152,38 +152,38 @@
if (!outputs[index]) outputs[index] = []; if (!outputs[index]) outputs[index] = [];
}); });
await refreshLinks(); await refreshWires();
} }
function resolveLink(link: FrontendNodeLink): { nodeOutput: SVGSVGElement | undefined; nodeInput: SVGSVGElement | undefined } { function resolveWire(wire: FrontendNodeWire): { nodeOutput: SVGSVGElement | undefined; nodeInput: SVGSVGElement | undefined } {
const outputIndex = Number(link.linkStartOutputIndex); const outputIndex = Number(wire.wireStartOutputIndex);
const inputIndex = Number(link.linkEndInputIndex); const inputIndex = Number(wire.wireEndInputIndex);
const nodeOutputConnectors = outputs[$nodeGraph.nodes.findIndex((n) => n.id === link.linkStart)]; const nodeOutputConnectors = outputs[$nodeGraph.nodes.findIndex((n) => n.id === wire.wireStart)];
const nodeInputConnectors = inputs[$nodeGraph.nodes.findIndex((n) => n.id === link.linkEnd)] || undefined; const nodeInputConnectors = inputs[$nodeGraph.nodes.findIndex((n) => n.id === wire.wireEnd)] || undefined;
const nodeOutput = nodeOutputConnectors?.[outputIndex] as SVGSVGElement | undefined; const nodeOutput = nodeOutputConnectors?.[outputIndex] as SVGSVGElement | undefined;
const nodeInput = nodeInputConnectors?.[inputIndex] as SVGSVGElement | undefined; const nodeInput = nodeInputConnectors?.[inputIndex] as SVGSVGElement | undefined;
return { nodeOutput, nodeInput }; return { nodeOutput, nodeInput };
} }
async function refreshLinks() { async function refreshWires() {
await tick(); await tick();
const links = $nodeGraph.links; const wires = $nodeGraph.wires;
nodeLinkPaths = links.flatMap((link, index) => { nodeWirePaths = wires.flatMap((wire, index) => {
const { nodeInput, nodeOutput } = resolveLink(link); const { nodeInput, nodeOutput } = resolveWire(wire);
if (!nodeInput || !nodeOutput) return []; 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 wireStart = $nodeGraph.nodes.find((n) => n.id === wire.wireStart)?.isLayer || false;
const linkEnd = ($nodeGraph.nodes.find((n) => n.id === link.linkEnd)?.isLayer && Number(link.linkEndInputIndex) == 0) || 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 { function nodeIcon(nodeName: string): IconName {
const iconMap: Record<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 }[] { function buildWirePathLocations(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): { x: number; y: number }[] {
if (!nodesContainer) return []; if (!nodesContainer) return [];
const VERTICAL_LINK_OVERLAP_ON_SHAPED_CAP = 1; const VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP = 1;
const containerBounds = nodesContainer.getBoundingClientRect(); const containerBounds = nodesContainer.getBoundingClientRect();
const outX = verticalOut ? outputBounds.x + outputBounds.width / 2 : outputBounds.x + outputBounds.width - 1; 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 outConnectorX = (outX - containerBounds.x) / transform.scale;
const outConnectorY = (outY - containerBounds.y) / transform.scale; const outConnectorY = (outY - containerBounds.y) / transform.scale;
const inX = verticalIn ? inputBounds.x + inputBounds.width / 2 : inputBounds.x + 1; 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 inConnectorX = (inX - containerBounds.x) / transform.scale;
const inConnectorY = (inY - containerBounds.y) / transform.scale; const inConnectorY = (inY - containerBounds.y) / transform.scale;
const horizontalGap = Math.abs(outConnectorX - inConnectorX); const horizontalGap = Math.abs(outConnectorX - inConnectorX);
const verticalGap = Math.abs(outConnectorY - inConnectorY); 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> // 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)) { // if ((verticalOut && verticalIn) || (!verticalOut && !verticalIn && verticalGap === 0)) {
// return [ // return [
// { x: outConnectorX, y: outConnectorY }, // { x: outConnectorX, y: outConnectorY },
@ -259,14 +259,14 @@
.join(" "); .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 inputPortRect = inputPort instanceof DOMRect ? inputPort : inputPort.getBoundingClientRect();
const outputPortRect = outputPort.getBoundingClientRect(); const outputPortRect = outputPort.getBoundingClientRect();
const pathString = buildWirePathString(outputPortRect, inputPortRect, verticalOut, verticalIn); 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) { function scroll(e: WheelEvent) {
@ -321,9 +321,9 @@
if (e.key.toLowerCase() === "escape") { if (e.key.toLowerCase() === "escape") {
contextMenuOpenCoordinates = undefined; contextMenuOpenCoordinates = undefined;
document.removeEventListener("keydown", keydown); document.removeEventListener("keydown", keydown);
linkInProgressFromConnector = undefined; wireInProgressFromConnector = undefined;
// linkInProgressFromLayerTop = undefined; // wireInProgressFromLayerTop = undefined;
// linkInProgressFromLayerBottom = undefined; // wireInProgressFromLayerBottom = undefined;
} }
} }
@ -374,10 +374,10 @@
// Since the user is clicking elsewhere in the graph, ensure the add nodes list is closed // Since the user is clicking elsewhere in the graph, ensure the add nodes list is closed
if (lmb) { if (lmb) {
contextMenuOpenCoordinates = undefined; contextMenuOpenCoordinates = undefined;
linkInProgressFromConnector = undefined; wireInProgressFromConnector = undefined;
toggleDisplayAsLayerNodeId = undefined; toggleDisplayAsLayerNodeId = undefined;
// linkInProgressFromLayerTop = undefined; // wireInProgressFromLayerTop = undefined;
// linkInProgressFromLayerBottom = undefined; // wireInProgressFromLayerBottom = undefined;
} }
// Alt-click sets the clicked node as previewed // Alt-click sets the clicked node as previewed
@ -390,39 +390,36 @@
const isOutput = Boolean(port.getAttribute("data-port") === "output"); const isOutput = Boolean(port.getAttribute("data-port") === "output");
const frontendNode = (nodeId !== undefined && $nodeGraph.nodes.find((n) => n.id === nodeId)) || undefined; 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) { if (isOutput) {
// Disallow creating additional vertical output links from an already-connected layer // Disallow creating additional vertical output wires from an already-connected layer
if (frontendNode?.isLayer && frontendNode.primaryOutput?.connected !== undefined) return; if (frontendNode?.isLayer && frontendNode.primaryOutput && frontendNode.primaryOutput.connected.length > 0) return;
linkInProgressFromConnector = port; wireInProgressFromConnector = 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 // // 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
// linkInProgressFromLayerTop = nodeId !== undefined && frontendNode?.isLayer ? nodeId : undefined; // wireInProgressFromLayerTop = nodeId !== undefined && frontendNode?.isLayer ? nodeId : undefined;
// linkInProgressFromLayerBottom = undefined; // wireInProgressFromLayerBottom = undefined;
} }
// Input: Begin moving an existing link // Input: Begin moving an existing wire
else { else {
const inputNodeInPorts = Array.from(node.querySelectorAll(`[data-port="input"]`)); const inputNodeInPorts = Array.from(node.querySelectorAll(`[data-port="input"]`));
const inputNodeConnectionIndexSearch = inputNodeInPorts.indexOf(port); const inputNodeConnectionIndexSearch = inputNodeInPorts.indexOf(port);
// const isLayerBottomConnector = frontendNode?.isLayer && inputNodeConnectionIndexSearch === 1;
const inputIndex = inputNodeConnectionIndexSearch > -1 ? inputNodeConnectionIndexSearch : undefined; const inputIndex = inputNodeConnectionIndexSearch > -1 ? inputNodeConnectionIndexSearch : undefined;
if (inputIndex === undefined || nodeId === undefined) return; 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)); const wireIndex = $nodeGraph.wires.filter((wire) => !wire.dashed).findIndex((value) => value.wireEnd === nodeId && value.wireEndInputIndex === BigInt(inputIndex));
if (linkIndex === -1) return; if (wireIndex === -1) return;
const nodeOutputConnectors = nodesContainer?.querySelectorAll(`[data-node="${String($nodeGraph.links[linkIndex].linkStart)}"] [data-port="output"]`) || undefined; const nodeOutputConnectors = nodesContainer?.querySelectorAll(`[data-node="${String($nodeGraph.wires[wireIndex].wireStart)}"] [data-port="output"]`) || undefined;
linkInProgressFromConnector = nodeOutputConnectors?.[Number($nodeGraph.links[linkIndex].linkStartOutputIndex)] as SVGSVGElement | undefined; wireInProgressFromConnector = nodeOutputConnectors?.[Number($nodeGraph.wires[wireIndex].wireStartOutputIndex)] as SVGSVGElement | undefined;
// linkInProgressFromLayerBottom = isLayerBottomConnector ? frontendNode.exposedInputs[0].connected : undefined;
const nodeInputConnectors = nodesContainer?.querySelectorAll(`[data-node="${String($nodeGraph.links[linkIndex].linkEnd)}"] [data-port="input"]`) || undefined; const nodeInputConnectors = nodesContainer?.querySelectorAll(`[data-node="${String($nodeGraph.wires[wireIndex].wireEnd)}"] [data-port="input"]`) || undefined;
linkInProgressToConnector = nodeInputConnectors?.[Number($nodeGraph.links[linkIndex].linkEndInputIndex)] as SVGSVGElement | undefined; wireInProgressToConnector = nodeInputConnectors?.[Number($nodeGraph.wires[wireIndex].wireEndInputIndex)] as SVGSVGElement | undefined;
// linkInProgressFromLayerTop = undefined;
disconnecting = { nodeId: nodeId, inputIndex, linkIndex }; disconnecting = { nodeId: nodeId, inputIndex, wireIndex };
refreshLinks(); refreshWires();
} }
return; return;
@ -480,26 +477,28 @@
panning = true; panning = true;
} }
function doubleClick(_e: MouseEvent) { function doubleClick(e: MouseEvent) {
// const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined; if ((e.target as HTMLElement).closest("[data-visibility-button]")) return;
// const nodeId = node?.getAttribute("data-node") || undefined;
// if (nodeId !== undefined) { const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined;
// const id = BigInt(nodeId); const nodeId = node?.getAttribute("data-node") || undefined;
// editor.handle.enterNestedNetwork(id); if (nodeId !== undefined && !e.altKey) {
// } const id = BigInt(nodeId);
editor.handle.enterNestedNetwork(id);
}
} }
function pointerMove(e: PointerEvent) { function pointerMove(e: PointerEvent) {
if (panning) { if (panning) {
transform.x += e.movementX / transform.scale; transform.x += e.movementX / transform.scale;
transform.y += e.movementY / transform.scale; transform.y += e.movementY / transform.scale;
} else if (linkInProgressFromConnector && !contextMenuOpenCoordinates) { } else if (wireInProgressFromConnector && !contextMenuOpenCoordinates) {
const target = e.target as Element | undefined; const target = e.target as Element | undefined;
const dot = (target?.closest(`[data-port="input"]`) || undefined) as SVGSVGElement | undefined; const dot = (target?.closest(`[data-port="input"]`) || undefined) as SVGSVGElement | undefined;
if (dot) { if (dot) {
linkInProgressToConnector = dot; wireInProgressToConnector = dot;
} else { } else {
linkInProgressToConnector = new DOMRect(e.x, e.y); wireInProgressToConnector = new DOMRect(e.x, e.y);
} }
} else if (draggingNodes) { } else if (draggingNodes) {
const deltaX = Math.round((e.x - draggingNodes.startX) / transform.scale / GRID_SIZE); const deltaX = Math.round((e.x - draggingNodes.startX) / transform.scale / GRID_SIZE);
@ -510,7 +509,7 @@
let stop = false; let stop = false;
const refresh = () => { const refresh = () => {
if (!stop) refreshLinks(); if (!stop) refreshWires();
requestAnimationFrame(refresh); requestAnimationFrame(refresh);
}; };
refresh(); refresh();
@ -560,8 +559,8 @@
return selected.includes(node) || intersetNodeAABB(boxSelect, nodeIndex); return selected.includes(node) || intersetNodeAABB(boxSelect, nodeIndex);
} }
function toggleNodeVisibility(id: bigint) { function toggleNodeVisibilityGraph(id: bigint) {
editor.handle.toggleNodeVisibility(id); editor.handle.toggleNodeVisibilityGraph(id);
} }
function toggleLayerDisplay(displayAsLayer: boolean) { function toggleLayerDisplay(displayAsLayer: boolean) {
@ -602,7 +601,7 @@
const selectedNode = nodesContainer?.querySelector(`[data-node="${String(selectedNodeId)}"]`) || undefined; const selectedNode = nodesContainer?.querySelector(`[data-node="${String(selectedNodeId)}"]`) || undefined;
// Check that neither the primary input or output of the selected node are already connected. // 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 input = selectedNode?.querySelector(`[data-port="input"]`) || undefined;
const output = selectedNode?.querySelector(`[data-port="output"]`) || undefined; const output = selectedNode?.querySelector(`[data-port="output"]`) || undefined;
@ -612,9 +611,9 @@
// Fixes typing for some reason? // Fixes typing for some reason?
const theNodesContainer = nodesContainer; const theNodesContainer = nodesContainer;
// Find the link that the node has been dragged on top of // Find the wire that the node has been dragged on top of
const link = $nodeGraph.links.find((link) => { const wire = $nodeGraph.wires.find((wire) => {
const { nodeInput, nodeOutput } = resolveLink(link); const { nodeInput, nodeOutput } = resolveWire(wire);
if (!nodeInput || !nodeOutput) return false; if (!nodeInput || !nodeOutput) return false;
const wireCurveLocations = buildWirePathLocations(nodeOutput.getBoundingClientRect(), nodeInput.getBoundingClientRect(), false, false); const wireCurveLocations = buildWirePathLocations(nodeOutput.getBoundingClientRect(), nodeInput.getBoundingClientRect(), false, false);
@ -623,7 +622,7 @@
const containerBoundsBounds = theNodesContainer.getBoundingClientRect(); const containerBoundsBounds = theNodesContainer.getBoundingClientRect();
return ( return (
link.linkEnd != selectedNodeId && wire.wireEnd != selectedNodeId &&
editor.handle.rectangleIntersects( editor.handle.rectangleIntersects(
new Float64Array(wireCurveLocations.map((loc) => loc.x)), new Float64Array(wireCurveLocations.map((loc) => loc.x)),
new Float64Array(wireCurveLocations.map((loc) => loc.y)), 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 the node has been dragged on top of the wire then connect it into the middle.
if (link) { if (wire) {
const isLayer = $nodeGraph.nodes.find((n) => n.id === selectedNodeId)?.isLayer; const isLayer = $nodeGraph.nodes.find((n) => n.id === selectedNodeId)?.isLayer;
editor.handle.insertNodeBetween(wire.wireEnd, Number(wire.wireEndInputIndex), 0, selectedNodeId, 0, Number(wire.wireStartOutputIndex), wire.wireStart);
editor.handle.insertNodeBetween(link.linkEnd, Number(link.linkEndInputIndex), 0, selectedNodeId, 0, Number(link.linkStartOutputIndex), link.linkStart);
if (!isLayer) editor.handle.shiftNode(selectedNodeId); if (!isLayer) editor.handle.shiftNode(selectedNodeId);
} }
} }
@ -653,16 +651,16 @@
} }
disconnecting = undefined; disconnecting = undefined;
if (linkInProgressToConnector instanceof SVGSVGElement && linkInProgressFromConnector) { if (wireInProgressToConnector instanceof SVGSVGElement && wireInProgressFromConnector) {
const from = connectorToNodeIndex(linkInProgressFromConnector); const from = connectorToNodeIndex(wireInProgressFromConnector);
const to = connectorToNodeIndex(linkInProgressToConnector); const to = connectorToNodeIndex(wireInProgressToConnector);
if (from !== undefined && to !== undefined) { if (from !== undefined && to !== undefined) {
const { nodeId: outputConnectedNodeID, index: outputNodeConnectionIndex } = from; const { nodeId: outputConnectedNodeID, index: outputNodeConnectionIndex } = from;
const { nodeId: inputConnectedNodeID, index: inputNodeConnectionIndex } = to; 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 the add node menu is already open, we don't want to open it again
if (contextMenuOpenCoordinates) return; if (contextMenuOpenCoordinates) return;
@ -674,7 +672,7 @@
if (!contextMenuOpenCoordinates) return; if (!contextMenuOpenCoordinates) return;
let contextMenuLocation2: { x: number; y: number } = contextMenuOpenCoordinates; 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; return;
} else if (draggingNodes) { } else if (draggingNodes) {
@ -695,8 +693,8 @@
boxSelection = undefined; boxSelection = undefined;
} }
linkInProgressFromConnector = undefined; wireInProgressFromConnector = undefined;
linkInProgressToConnector = undefined; wireInProgressToConnector = undefined;
} }
function createNode(nodeType: string) { function createNode(nodeType: string) {
@ -708,15 +706,15 @@
const inputConnectedNodeID = editor.handle.createNode(nodeType, x, y); const inputConnectedNodeID = editor.handle.createNode(nodeType, x, y);
contextMenuOpenCoordinates = undefined; contextMenuOpenCoordinates = undefined;
if (!linkInProgressFromConnector) return; if (!wireInProgressFromConnector) return;
const from = connectorToNodeIndex(linkInProgressFromConnector); const from = connectorToNodeIndex(wireInProgressFromConnector);
if (from !== undefined) { if (from !== undefined) {
const { nodeId: outputConnectedNodeID, index: outputNodeConnectionIndex } = from; 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 { function nodeBorderMask(nodeWidth: number, primaryInputExists: boolean, parameters: number, primaryOutputExists: boolean, exposedOutputs: number): string {
@ -766,8 +764,15 @@
} }
function dataTypeTooltip(value: FrontendGraphInput | FrontendGraphOutput): string { 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: ${value.dataType}`;
return value.resolvedType ? `Resolved Data: ${value.resolvedType}` : `Unresolved Data: ${dataTypeCapitalized}`; }
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> </script>
@ -784,6 +789,7 @@
style:--grid-offset-y={`${transform.y * transform.scale}px`} style:--grid-offset-y={`${transform.y * transform.scale}px`}
style:--dot-radius={`${dotRadius}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 --> <!-- Right click menu for adding nodes -->
{#if contextMenuOpenCoordinates} {#if contextMenuOpenCoordinates}
<LayoutCol <LayoutCol
@ -844,11 +850,17 @@
{/if} {/if}
</LayoutCol> </LayoutCol>
{/if} {/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`}> <div class="wires" style:transform={`scale(${transform.scale}) translate(${transform.x}px, ${transform.y}px)`} style:transform-origin={`0 0`}>
<svg> <svg>
{#each linkPaths as { pathString, dataType, thick }} {#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})`} style:--data-color-dim={`var(--color-data-${dataType}-dim)`} /> <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} {/each}
</svg> </svg>
</div> </div>
@ -868,8 +880,8 @@
style:--offset-left={(node.position?.x || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundX || 0 : 0)} 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:--offset-top={(node.position?.y || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0)}
style:--clip-path-id={`url(#${clipPathId})`} style:--clip-path-id={`url(#${clipPathId})`}
style:--data-color={`var(--color-data-${node.primaryOutput?.dataType || "general"})`} style:--data-color={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput?.dataType || "general"}-dim)`} style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
style:--label-width={labelWidthGridCells} style:--label-width={labelWidthGridCells}
style:--node-chain-area-left-extension={node.exposedInputs.length === 0 ? 0 : 1.5} style:--node-chain-area-left-extension={node.exposedInputs.length === 0 ? 0 : 1.5}
data-node={node.id} data-node={node.id}
@ -891,14 +903,14 @@
class="port top" class="port top"
data-port="output" data-port="output"
data-datatype={node.primaryOutput.dataType} data-datatype={node.primaryOutput.dataType}
style:--data-color={`var(--color-data-${node.primaryOutput.dataType})`} style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType}-dim)`} style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
bind:this={outputs[nodeIndex][0]} bind:this={outputs[nodeIndex][0]}
> >
<title>{`${dataTypeTooltip(node.primaryOutput)}\nConnected to ${`${node.primaryOutput.connected}, port index ${node.primaryOutput.connectedIndex}` || "nothing"}`}</title> <title>{`${dataTypeTooltip(node.primaryOutput)}\n${connectedToText(node.primaryOutput)}`}</title>
{#if node.primaryOutput.connected} {#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)" /> <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)" /> <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} {/if}
{:else} {:else}
@ -913,14 +925,14 @@
class="port bottom" class="port bottom"
data-port="input" data-port="input"
data-datatype={node.primaryInput?.dataType} data-datatype={node.primaryInput?.dataType}
style:--data-color={`var(--color-data-${node.primaryInput?.dataType})`} style:--data-color={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryInput?.dataType}-dim)`} style:--data-color-dim={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex][0]} bind:this={inputs[nodeIndex][0]}
> >
{#if node.primaryInput} {#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}
{#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)" /> <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} {#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)" /> <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" class="port"
data-port="input" data-port="input"
data-datatype={stackDataInput.dataType} data-datatype={stackDataInput.dataType}
style:--data-color={`var(--color-data-${stackDataInput.dataType})`} style:--data-color={`var(--color-data-${stackDataInput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${stackDataInput.dataType}-dim)`} style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex][1]} bind:this={inputs[nodeIndex][1]}
> >
<title>{`${dataTypeTooltip(stackDataInput)}\nConnected to ${stackDataInput.connected || "nothing"}`}</title> <title>{`${dataTypeTooltip(stackDataInput)}\nConnected to ${stackDataInput.connected !== undefined ? stackDataInput.connected : "nothing"}`}</title>
{#if stackDataInput.connected} {#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)" /> <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} {: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)" /> <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> </div>
<IconButton <IconButton
class={"visibility"} class={"visibility"}
action={(e) => (toggleNodeVisibility(node.id), e?.stopPropagation())} data-visibility-button
action={(e) => (toggleNodeVisibilityGraph(node.id), e?.stopPropagation())}
size={24} size={24}
icon={node.visible ? "EyeVisible" : "EyeHidden"} icon={node.visible ? "EyeVisible" : "EyeHidden"}
tooltip={node.visible ? "Visible" : "Hidden"} 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-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:--offset-top={(node.position?.y || 0) + ($nodeGraph.selected.includes(node.id) ? draggingNodes?.roundY || 0 : 0)}
style:--clip-path-id={`url(#${clipPathId})`} style:--clip-path-id={`url(#${clipPathId})`}
style:--data-color={`var(--color-data-${node.primaryOutput?.dataType || "general"})`} style:--data-color={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput?.dataType || "general"}-dim)`} style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
data-node={node.id} data-node={node.id}
bind:this={nodeElements[nodeIndex]} bind:this={nodeElements[nodeIndex]}
> >
@ -1025,12 +1038,12 @@
class="port primary-port" class="port primary-port"
data-port="input" data-port="input"
data-datatype={node.primaryInput?.dataType} data-datatype={node.primaryInput?.dataType}
style:--data-color={`var(--color-data-${node.primaryInput?.dataType})`} style:--data-color={`var(--color-data-${node.primaryInput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryInput?.dataType}-dim)`} style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex][0]} bind:this={inputs[nodeIndex][0]}
> >
<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 node.primaryInput.connected} {#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)" /> <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} {: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)" /> <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" class="port"
data-port="input" data-port="input"
data-datatype={parameter.dataType} data-datatype={parameter.dataType}
style:--data-color={`var(--color-data-${parameter.dataType})`} style:--data-color={`var(--color-data-${parameter.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${parameter.dataType}-dim)`} style:--data-color-dim={`var(--color-data-${parameter.dataType.toLowerCase()}-dim)`}
bind:this={inputs[nodeIndex][index + (node.primaryInput ? 1 : 0)]} bind:this={inputs[nodeIndex][index + (node.primaryInput ? 1 : 0)]}
> >
<title>{`${dataTypeTooltip(parameter)}\nConnected to ${parameter.connected || "nothing"}`}</title> <title>{`${dataTypeTooltip(parameter)}\nConnected to ${parameter.connected !== undefined ? parameter.connected : "nothing"}`}</title>
{#if parameter.connected} {#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)" /> <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} {: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)" /> <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" class="port primary-port"
data-port="output" data-port="output"
data-datatype={node.primaryOutput.dataType} data-datatype={node.primaryOutput.dataType}
style:--data-color={`var(--color-data-${node.primaryOutput.dataType})`} style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType}-dim)`} style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
bind:this={outputs[nodeIndex][0]} bind:this={outputs[nodeIndex][0]}
> >
<title>{`${dataTypeTooltip(node.primaryOutput)}\nConnected to ${`${node.primaryOutput.connected}, port index ${node.primaryOutput.connectedIndex}` || "nothing"}`}</title> <title>{`${dataTypeTooltip(node.primaryOutput)}\n${connectedToText(node.primaryOutput)}`}</title>
{#if node.primaryOutput.connected} {#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)" /> <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} {: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)" /> <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" class="port"
data-port="output" data-port="output"
data-datatype={parameter.dataType} data-datatype={parameter.dataType}
style:--data-color={`var(--color-data-${parameter.dataType})`} style:--data-color={`var(--color-data-${parameter.dataType.toLowerCase()})`}
style:--data-color-dim={`var(--color-data-${parameter.dataType}-dim)`} style:--data-color-dim={`var(--color-data-${parameter.dataType.toLowerCase()}-dim)`}
bind:this={outputs[nodeIndex][outputIndex + (node.primaryOutput ? 1 : 0)]} bind:this={outputs[nodeIndex][outputIndex + (node.primaryOutput ? 1 : 0)]}
> >
<title>{`${dataTypeTooltip(parameter)}\nConnected to ${`${parameter.connected}, port index ${parameter.connectedIndex}` || "nothing"}`}</title> <title>{`${dataTypeTooltip(parameter)}\n${connectedToText(parameter)}`}</title>
{#if parameter.connected} {#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)" /> <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} {: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)" /> <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-direction: row;
flex-grow: 1; 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 // 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 { &::before {
content: ""; content: "";
@ -1147,11 +1155,23 @@
height: 100%; height: 100%;
background-size: var(--grid-spacing) var(--grid-spacing); 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-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; image-rendering: pixelated;
mix-blend-mode: screen; mix-blend-mode: screen;
} }
> img {
position: absolute;
bottom: 0;
}
.breadcrumb-trail-buttons {
margin-top: 8px;
margin-left: 8px;
}
.context-menu { .context-menu {
width: max-content; width: max-content;
position: absolute; position: absolute;
@ -1235,6 +1255,7 @@
fill: none; fill: none;
stroke: var(--data-color-dim); stroke: var(--data-color-dim);
stroke-width: var(--data-line-width); stroke-width: var(--data-line-width);
stroke-dasharray: var(--data-dasharray);
} }
} }
} }

View file

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

View file

@ -1,15 +1,24 @@
<script lang="ts"> <script lang="ts">
import type { FrontendGraphDataType } from "@graphite/wasm-communication/messages";
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
export let exposed: boolean; export let exposed: boolean;
export let dataType: string; export let dataType: FrontendGraphDataType;
export let tooltip: string | undefined = undefined; export let tooltip: string | undefined = undefined;
// Callbacks // Callbacks
export let action: (e?: MouseEvent) => void; export let action: (e?: MouseEvent) => void;
</script> </script>
<LayoutRow class="parameter-expose-button"> <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"> <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 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 <path

View file

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

View file

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

View file

@ -532,8 +532,8 @@ impl EditorHandle {
/// Set the name for the layer /// Set the name for the layer
#[wasm_bindgen(js_name = setLayerName)] #[wasm_bindgen(js_name = setLayerName)]
pub fn set_layer_name(&self, id: u64, name: String) { pub fn set_layer_name(&self, id: u64, name: String) {
let id = NodeId(id); let layer = LayerNodeIdentifier::new_unchecked(NodeId(id));
let message = NodeGraphMessage::SetName { node_id: id, name }; let message = GraphOperationMessage::SetName { layer, name };
self.dispatch(message); 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 /// Notifies the backend that the user connected a node's primary output to one of another node's inputs
#[wasm_bindgen(js_name = connectNodesByLink)] #[wasm_bindgen(js_name = connectNodesByWire)]
pub fn connect_nodes_by_link(&self, output_node: u64, output_node_connector_index: usize, input_node: u64, input_node_connector_index: usize) { 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 output_node = NodeId(output_node);
let input_node = NodeId(input_node); let input_node = NodeId(input_node);
let message = NodeGraphMessage::ConnectNodesByLink { let message = NodeGraphMessage::ConnectNodesByWire {
output_node, output_node,
output_node_connector_index, output_node_connector_index,
input_node, input_node,
@ -601,7 +601,7 @@ impl EditorHandle {
#[wasm_bindgen(js_name = disconnectNodes)] #[wasm_bindgen(js_name = disconnectNodes)]
pub fn disconnect_nodes(&self, node_id: u64, input_index: usize) { pub fn disconnect_nodes(&self, node_id: u64, input_index: usize) {
let node_id = NodeId(node_id); 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); self.dispatch(message);
} }
@ -649,6 +649,13 @@ impl EditorHandle {
self.dispatch(message); 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 /// Notifies the backend that the selected nodes have been moved
#[wasm_bindgen(js_name = moveSelectedNodes)] #[wasm_bindgen(js_name = moveSelectedNodes)]
pub fn move_selected_nodes(&self, displacement_x: i32, displacement_y: i32) { 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 /// Toggle visibility of a layer or node given its node ID
#[wasm_bindgen(js_name = toggleNodeVisibility)] #[wasm_bindgen(js_name = toggleNodeVisibilityLayerPanel)]
pub fn toggle_node_visibility(&self, id: u64) { 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 node_id = NodeId(id);
let message = NodeGraphMessage::ToggleVisibility { node_id }; let message = NodeGraphMessage::ToggleVisibility { node_id };
self.dispatch(message); self.dispatch(message);
@ -698,15 +713,15 @@ impl EditorHandle {
self.dispatch(message); self.dispatch(message);
let id = NodeId(id); let id = NodeId(id);
let message = DocumentMessage::DeleteLayer { id }; let message = NodeGraphMessage::DeleteNodes { node_ids: vec![id], reconnect: true };
self.dispatch(message); self.dispatch(message);
} }
/// Toggle lock state of a layer from the layer list /// Toggle lock state of a layer from the layer list
#[wasm_bindgen(js_name = toggleLayerLock)] #[wasm_bindgen(js_name = toggleLayerLock)]
pub fn toggle_layer_lock(&self, id: u64) { pub fn toggle_layer_lock(&self, id: u64) {
let id = NodeId(id); let node_id = NodeId(id);
let message = NodeGraphMessage::ToggleLocked { node_id: id }; let message = GraphOperationMessage::ToggleLocked { node_id };
self.dispatch(message); 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> { pub fn solve_spline_first_handle(points: &[DVec2]) -> Vec<DVec2> {
let len_points = points.len(); let len_points = points.len();
// matrix coefficients a, b and c (see https://mathworld.wolfram.com/CubicSpline.html) // 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 // Because the 'a' coefficients are all 1, they need not be stored.
// this algorithm does a variation of the above algorithm. // 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. // 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]; let mut b = vec![DVec2::new(4., 4.); len_points];
b[0] = DVec2::new(2., 2.); b[0] = DVec2::new(2., 2.);
@ -367,26 +367,26 @@ pub fn solve_spline_first_handle(points: &[DVec2]) -> Vec<DVec2> {
} }
// Solve with Thomas algorithm (see https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm) // 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]; c[0] /= -b[0];
d[0] /= -b[0]; d[0] /= -b[0];
#[allow(clippy::assign_op_pattern)] #[allow(clippy::assign_op_pattern)]
for i in 1..len_points { for i in 1..len_points {
b[i] += c[i - 1]; 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-1]
d[i] = d[i] + d[i - 1]; d[i] = d[i] + d[i - 1];
c[i] /= -b[i]; c[i] /= -b[i];
d[i] /= -b[i]; d[i] /= -b[i];
} }
// at this point b[i] == -a[i + 1], a[i] == 0, // At this point b[i] == -a[i + 1] and a[i] == 0.
// do row operations to eliminate 'c' coefficients and solve // Now we do row operations to eliminate 'c' coefficients and solve.
d[len_points - 1] *= -1.; d[len_points - 1] *= -1.;
#[allow(clippy::assign_op_pattern)] #[allow(clippy::assign_op_pattern)]
for i in (0..len_points - 1).rev() { for i in (0..len_points - 1).rev() {
d[i] = d[i] - (c[i] * d[i + 1]); d[i] = d[i] - (c[i] * d[i + 1]);
d[i] *= -1.; //d[i] /= b[i] d[i] *= -1.; // d[i] /= b[i]
} }
d d

View file

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

View file

@ -3,3 +3,7 @@ use crate::raster::Color;
// RENDERING // RENDERING
pub const LAYER_OUTLINE_STROKE_COLOR: Color = Color::BLACK; pub const LAYER_OUTLINE_STROKE_COLOR: Color = Color::BLACK;
pub const LAYER_OUTLINE_STROKE_WEIGHT: f64 = 1.; 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> { pub struct ConstructLayerNode<Stack, GraphicElement> {
graphic_element: GraphicElement,
stack: Stack, stack: Stack,
graphic_element: GraphicElement,
} }
#[node_fn(ConstructLayerNode)] #[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, footprint: crate::transform::Footprint,
graphic_element: impl Node<crate::transform::Footprint, Output = Fut1>, mut stack: impl Node<crate::transform::Footprint, Output = Fut1>,
mut stack: impl Node<crate::transform::Footprint, Output = Fut2>, graphic_element: impl Node<crate::transform::Footprint, Output = Fut2>,
) -> GraphicGroup { ) -> GraphicGroup {
let graphic_element = self.graphic_element.eval(footprint).await; let graphic_element = self.graphic_element.eval(footprint).await;
let mut stack = self.stack.eval(footprint).await; let mut stack = self.stack.eval(footprint).await;
@ -192,16 +192,16 @@ async fn construct_artboard<Fut: Future<Output = GraphicGroup>>(
clip, clip,
} }
} }
pub struct AddArtboardNode<Artboard, ArtboardGroup> { pub struct AddArtboardNode<ArtboardGroup, Artboard> {
artboard: Artboard,
artboards: ArtboardGroup, artboards: ArtboardGroup,
artboard: Artboard,
} }
#[node_fn(AddArtboardNode)] #[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, footprint: Footprint,
artboard: impl Node<Footprint, Output = Fut1>, artboards: impl Node<Footprint, Output = Fut1>,
mut artboards: impl Node<Footprint, Output = Fut2>, artboard: impl Node<Footprint, Output = Fut2>,
) -> ArtboardGroup { ) -> ArtboardGroup {
let artboard = self.artboard.eval(footprint).await; let artboard = self.artboard.eval(footprint).await;
let mut artboards = self.artboards.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)] #[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct EndLetNode<Input, Parameter> { pub struct EndLetNode<Input, Parameter> {
input: Input, input: Input,
paramenter: PhantomData<Parameter>, parameter: PhantomData<Parameter>,
} }
impl<'i, T: 'i, Parameter: 'i + From<T>, Input> Node<'i, T> for EndLetNode<Input, Parameter> impl<'i, T: 'i, Parameter: 'i + From<T>, Input> Node<'i, T> for EndLetNode<Input, Parameter>
where where
@ -192,7 +192,7 @@ where
impl<Input, Parameter> EndLetNode<Input, Parameter> { impl<Input, Parameter> EndLetNode<Input, Parameter> {
pub const fn new(input: Input) -> EndLetNode<Input, Parameter> { pub const fn new(input: Input) -> EndLetNode<Input, Parameter> {
EndLetNode { input, paramenter: PhantomData } EndLetNode { input, parameter: PhantomData }
} }
} }

View file

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

View file

@ -186,7 +186,7 @@ mod test {
let quantized = quantize_color(color, [quant; 4]); let quantized = quantize_color(color, [quant; 4]);
assert_eq!(quantized.0, 0x7f7f7f7f); assert_eq!(quantized.0, 0x7f7f7f7f);
let _dequantized = dequantize_color(quantized, [quant; 4]); let _dequantized = dequantize_color(quantized, [quant; 4]);
//assert_eq!(color, dequantized); // assert_eq!(color, dequantized);
} }
#[test] #[test]

View file

@ -767,7 +767,7 @@ impl Color {
/// ``` /// ```
/// use graphene_core::raster::color::Color; /// use graphene_core::raster::color::Color;
/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap(); /// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();
/// //TODO: Add test /// // TODO: Add test
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn to_rgba8_srgb(&self) -> [u8; 4] { pub fn to_rgba8_srgb(&self) -> [u8; 4] {

View file

@ -118,7 +118,7 @@ impl PartialEq for TypeDescriptor {
(Some(id), Some(other_id)) => id == other_id, (Some(id), Some(other_id)) => id == other_id,
_ => { _ => {
// TODO: Add a flag to disable this warning // TODO: Add a flag to disable this warning
//warn!("TypeDescriptor::eq: comparing types without ids based on name"); // warn!("TypeDescriptor::eq: comparing types without ids based on name");
self.name == other.name self.name == other.name
} }
} }
@ -214,6 +214,15 @@ impl Type {
Self::Future(_) => None, 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 { fn format_type(ty: &str) -> String {

View file

@ -141,7 +141,7 @@ pub fn serialize_gpu(networks: &[ProtoNetwork], io: &ShaderIO) -> anyhow::Result
let mut output_nodes = Vec::new(); let mut output_nodes = Vec::new();
for network in networks { for network in networks {
dbg!(&network); dbg!(&network);
//assert_eq!(network.inputs.len(), io.inputs.iter().filter(|x| !x.is_output()).count()); // assert_eq!(network.inputs.len(), io.inputs.iter().filter(|x| !x.is_output()).count());
#[derive(serde::Serialize, Debug)] #[derive(serde::Serialize, Debug)]
struct Node { struct Node {
id: String, id: String,
@ -215,10 +215,10 @@ pub fn compile(dir: &Path) -> Result<spirv_builder::CompileResult, spirv_builder
.preserve_bindings(true) .preserve_bindings(true)
.release(true) .release(true)
.spirv_metadata(SpirvMetadata::Full) .spirv_metadata(SpirvMetadata::Full)
//.scalar_block_layout(true) // .scalar_block_layout(true)
.relax_logical_pointer(true) .relax_logical_pointer(true)
//.capability(spirv_builder::Capability::Float64) // .capability(spirv_builder::Capability::Float64)
//.capability(spirv_builder::Capability::VariablePointersStorageBuffer) // .capability(spirv_builder::Capability::VariablePointersStorageBuffer)
.extra_arg("no-early-report-zombies") .extra_arg("no-early-report-zombies")
.extra_arg("no-infer-storage-classes") .extra_arg("no-infer-storage-classes")
.extra_arg("spirt-passes=qptr") .extra_arg("spirt-passes=qptr")

View file

@ -4,39 +4,41 @@
#[cfg(target_arch = "spirv")] #[cfg(target_arch = "spirv")]
extern crate spirv_std; extern crate spirv_std;
//#[cfg(target_arch = "spirv")] // #[cfg(target_arch = "spirv")]
//pub mod gpu { // pub mod gpu {
//use super::*; // use super::*;
use spirv_std::spirv;
use spirv_std::glam;
use spirv_std::glam::{UVec3, Vec2, Mat2, BVec2};
#[allow(unused)] use spirv_std::spirv;
#[spirv(compute(threads({{compute_threads}})))] use spirv_std::glam;
pub fn eval ( use spirv_std::glam::{UVec3, Vec2, Mat2, BVec2};
#[spirv(global_invocation_id)] _global_index: UVec3,
{% for input in inputs %}
{{input}},
{% endfor %}
) {
use graphene_core::{Node, NodeMut};
use graphene_core::raster::adjustments::{BlendMode, BlendNode};
use graphene_core::Color;
{% for input in input_nodes %} #[allow(unused)]
let _i{{input.index}} = graphene_core::value::CopiedNode::new(*i{{input.index}}); #[spirv(compute(threads({{compute_threads}})))]
let _{{input.id}} = {{input.fqn}}::new({% for arg in input.args %}{{arg}}, {% endfor %}); pub fn eval (
let {{input.id}} = graphene_core::structural::ComposeNode::new(_i{{input.index}}, _{{input.id}}); #[spirv(global_invocation_id)] _global_index: UVec3,
{% endfor %} {% for input in inputs %}
{{input}},
{% endfor %}
) {
use graphene_core::{Node, NodeMut};
use graphene_core::raster::adjustments::{BlendMode, BlendNode};
use graphene_core::Color;
{% for node in nodes %} {% for input in input_nodes %}
let mut {{node.id}} = {{node.fqn}}::new({% for arg in node.args %}{{arg}}, {% endfor %}); let _i{{input.index}} = graphene_core::value::CopiedNode::new(*i{{input.index}});
{% endfor %} let _{{input.id}} = {{input.fqn}}::new({% for arg in input.args %}{{arg}}, {% endfor %});
let {{input.id}} = graphene_core::structural::ComposeNode::new(_i{{input.index}}, _{{input.id}});
{% endfor %}
{% for output in output_nodes %} {% for node in nodes %}
let v = {{output}}.eval(()); let mut {{node.id}} = {{node.fqn}}::new({% for arg in node.args %}{{arg}}, {% endfor %});
o{{loop.index0}}[(_global_index.y * i0 + _global_index.x) as usize] = v; {% endfor %}
{% endfor %}
// TODO: Write output to buffer {% for output in output_nodes %}
} let v = {{output}}.eval(());
//} o{{loop.index0}}[(_global_index.y * i0 + _global_index.x) as usize] = v;
{% 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; use dyn_any::DynAny;
pub use dyn_any::StaticType; pub use dyn_any::StaticType;
pub use glam::{DAffine2, DVec2, UVec2}; pub use glam::{DAffine2, DVec2, IVec2, UVec2};
use std::hash::Hash; use std::hash::Hash;
pub use std::sync::Arc; pub use std::sync::Arc;
@ -25,6 +25,7 @@ pub enum TaggedValue {
F64(f64), F64(f64),
Bool(bool), Bool(bool),
UVec2(UVec2), UVec2(UVec2),
IVec2(IVec2),
DVec2(DVec2), DVec2(DVec2),
OptionalDVec2(Option<DVec2>), OptionalDVec2(Option<DVec2>),
DAffine2(DAffine2), DAffine2(DAffine2),
@ -69,10 +70,9 @@ pub enum TaggedValue {
Segments(Vec<graphene_core::raster::ImageFrame<Color>>), Segments(Vec<graphene_core::raster::ImageFrame<Color>>),
DocumentNode(DocumentNode), DocumentNode(DocumentNode),
GraphicGroup(graphene_core::GraphicGroup), GraphicGroup(graphene_core::GraphicGroup),
Artboard(graphene_core::Artboard), GraphicElement(graphene_core::GraphicElement),
ArtboardGroup(graphene_core::ArtboardGroup), ArtboardGroup(graphene_core::ArtboardGroup),
Curve(graphene_core::raster::curve::Curve), Curve(graphene_core::raster::curve::Curve),
IVec2(glam::IVec2),
SurfaceFrame(graphene_core::SurfaceFrame), SurfaceFrame(graphene_core::SurfaceFrame),
Footprint(graphene_core::transform::Footprint), Footprint(graphene_core::transform::Footprint),
RenderOutput(RenderOutput), RenderOutput(RenderOutput),
@ -93,6 +93,7 @@ impl Hash for TaggedValue {
Self::F64(x) => x.to_bits().hash(state), Self::F64(x) => x.to_bits().hash(state),
Self::Bool(x) => x.hash(state), Self::Bool(x) => x.hash(state),
Self::UVec2(x) => x.to_array().iter().for_each(|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::DVec2(x) => x.to_array().iter().for_each(|x| x.to_bits().hash(state)),
Self::OptionalDVec2(None) => 0.hash(state), Self::OptionalDVec2(None) => 0.hash(state),
Self::OptionalDVec2(Some(x)) => { Self::OptionalDVec2(Some(x)) => {
@ -150,10 +151,9 @@ impl Hash for TaggedValue {
} }
Self::DocumentNode(x) => x.hash(state), Self::DocumentNode(x) => x.hash(state),
Self::GraphicGroup(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::ArtboardGroup(x) => x.hash(state),
Self::Curve(x) => x.hash(state), Self::Curve(x) => x.hash(state),
Self::IVec2(x) => x.hash(state),
Self::SurfaceFrame(x) => x.hash(state), Self::SurfaceFrame(x) => x.hash(state),
Self::Footprint(x) => x.hash(state), Self::Footprint(x) => x.hash(state),
Self::RenderOutput(x) => x.hash(state), Self::RenderOutput(x) => x.hash(state),
@ -175,6 +175,7 @@ impl<'a> TaggedValue {
TaggedValue::F64(x) => Box::new(x), TaggedValue::F64(x) => Box::new(x),
TaggedValue::Bool(x) => Box::new(x), TaggedValue::Bool(x) => Box::new(x),
TaggedValue::UVec2(x) => Box::new(x), TaggedValue::UVec2(x) => Box::new(x),
TaggedValue::IVec2(x) => Box::new(x),
TaggedValue::DVec2(x) => Box::new(x), TaggedValue::DVec2(x) => Box::new(x),
TaggedValue::OptionalDVec2(x) => Box::new(x), TaggedValue::OptionalDVec2(x) => Box::new(x),
TaggedValue::DAffine2(x) => Box::new(x), TaggedValue::DAffine2(x) => Box::new(x),
@ -218,10 +219,9 @@ impl<'a> TaggedValue {
TaggedValue::Segments(x) => Box::new(x), TaggedValue::Segments(x) => Box::new(x),
TaggedValue::DocumentNode(x) => Box::new(x), TaggedValue::DocumentNode(x) => Box::new(x),
TaggedValue::GraphicGroup(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::ArtboardGroup(x) => Box::new(x),
TaggedValue::Curve(x) => Box::new(x), TaggedValue::Curve(x) => Box::new(x),
TaggedValue::IVec2(x) => Box::new(x),
TaggedValue::SurfaceFrame(x) => Box::new(x), TaggedValue::SurfaceFrame(x) => Box::new(x),
TaggedValue::Footprint(x) => Box::new(x), TaggedValue::Footprint(x) => Box::new(x),
TaggedValue::RenderOutput(x) => Box::new(x), TaggedValue::RenderOutput(x) => Box::new(x),
@ -254,6 +254,7 @@ impl<'a> TaggedValue {
TaggedValue::F64(_) => concrete!(f64), TaggedValue::F64(_) => concrete!(f64),
TaggedValue::Bool(_) => concrete!(bool), TaggedValue::Bool(_) => concrete!(bool),
TaggedValue::UVec2(_) => concrete!(UVec2), TaggedValue::UVec2(_) => concrete!(UVec2),
TaggedValue::IVec2(_) => concrete!(IVec2),
TaggedValue::DVec2(_) => concrete!(DVec2), TaggedValue::DVec2(_) => concrete!(DVec2),
TaggedValue::OptionalDVec2(_) => concrete!(Option<DVec2>), TaggedValue::OptionalDVec2(_) => concrete!(Option<DVec2>),
TaggedValue::Image(_) => concrete!(graphene_core::raster::Image<Color>), 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::Segments(_) => concrete!(graphene_core::raster::IndexNode<Vec<graphene_core::raster::ImageFrame<Color>>>),
TaggedValue::DocumentNode(_) => concrete!(crate::document::DocumentNode), TaggedValue::DocumentNode(_) => concrete!(crate::document::DocumentNode),
TaggedValue::GraphicGroup(_) => concrete!(graphene_core::GraphicGroup), 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::ArtboardGroup(_) => concrete!(graphene_core::ArtboardGroup),
TaggedValue::Curve(_) => concrete!(graphene_core::raster::curve::Curve), TaggedValue::Curve(_) => concrete!(graphene_core::raster::curve::Curve),
TaggedValue::IVec2(_) => concrete!(glam::IVec2),
TaggedValue::SurfaceFrame(_) => concrete!(graphene_core::SurfaceFrame), TaggedValue::SurfaceFrame(_) => concrete!(graphene_core::SurfaceFrame),
TaggedValue::Footprint(_) => concrete!(graphene_core::transform::Footprint), TaggedValue::Footprint(_) => concrete!(graphene_core::transform::Footprint),
TaggedValue::RenderOutput(_) => concrete!(RenderOutput), 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::<f64>() => Ok(TaggedValue::F64(*downcast(input).unwrap())),
x if x == TypeId::of::<bool>() => Ok(TaggedValue::Bool(*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::<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::<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::<Option<DVec2>>() => Ok(TaggedValue::OptionalDVec2(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::raster::Image<Color>>() => Ok(TaggedValue::Image(*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::<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::<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::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::<graphene_core::GraphicElement>() => Ok(TaggedValue::GraphicElement(*downcast(input).unwrap())),
x if x == TypeId::of::<glam::IVec2>() => Ok(TaggedValue::IVec2(*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::<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::<RenderOutput>() => Ok(TaggedValue::RenderOutput(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::WasmSurfaceHandleFrame>() => { 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()))), _ => 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 { pub struct UpcastNode {

View file

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

View file

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

View file

@ -52,9 +52,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
}; };
loop { loop {
//println!("executing");
let _result = (&executor).execute(editor_api.clone()).await?; let _result = (&executor).execute(editor_api.clone()).await?;
//println!("result: {result:?}");
std::thread::sleep(std::time::Duration::from_millis(16)); 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) // 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 { fn begin_scope() -> DocumentNode {
DocumentNode { DocumentNode {
name: "Begin Scope".to_string(), name: "Begin Scope".to_string(),
implementation: DocumentNodeImplementation::Network(NodeNetwork { implementation: DocumentNodeImplementation::Network(NodeNetwork {
imports: vec![NodeId(0)], exports: vec![NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(2), 0)],
exports: vec![NodeOutput::new(NodeId(1), 0), NodeOutput::new(NodeId(2), 0)],
nodes: [ nodes: [
DocumentNode { DocumentNode {
name: "SetNode".to_string(), name: "SetNode".to_string(),
@ -181,7 +123,7 @@ fn begin_scope() -> DocumentNode {
..Default::default() ..Default::default()
}), }),
inputs: vec![NodeInput::Network(concrete!(WasmEditorApi))], inputs: vec![NodeInput::network(concrete!(WasmEditorApi), 0)],
..Default::default() ..Default::default()
} }
} }

View file

@ -285,19 +285,19 @@ mod test {
#[test] #[test]
#[should_panic] #[should_panic]
pub fn dyn_input_invalid_eval_panic() { pub fn dyn_input_invalid_eval_panic() {
//let add = DynAnyNode::new(AddPairNode::new()).into_type_erased(); // let add = DynAnyNode::new(AddPairNode::new()).into_type_erased();
//add.eval(Box::new(&("32", 32u32))); // add.eval(Box::new(&("32", 32u32)));
let dyn_any = DynAnyNode::<(u32, u32), u32, _>::new(FutureWrapperNode { node: AddPairNode::new() }); let dyn_any = DynAnyNode::<(u32, u32), u32, _>::new(FutureWrapperNode { node: AddPairNode::new() });
let type_erased = Box::new(dyn_any) as TypeErasedBox; let type_erased = Box::new(dyn_any) as TypeErasedBox;
let _ref_type_erased = type_erased.as_ref(); let _ref_type_erased = type_erased.as_ref();
//let type_erased = Box::pin(dyn_any) as TypeErasedBox<'_>; // let type_erased = Box::pin(dyn_any) as TypeErasedBox<'_>;
type_erased.eval(Box::new(&("32", 32u32))); type_erased.eval(Box::new(&("32", 32u32)));
} }
#[test] #[test]
pub fn dyn_input_compose() { pub fn dyn_input_compose() {
//let add = DynAnyNode::new(AddPairNode::new()).into_type_erased(); // let add = DynAnyNode::new(AddPairNode::new()).into_type_erased();
//add.eval(Box::new(&("32", 32u32))); // add.eval(Box::new(&("32", 32u32)));
let dyn_any = DynAnyNode::<(u32, u32), u32, _>::new(FutureWrapperNode { node: AddPairNode::new() }); let dyn_any = DynAnyNode::<(u32, u32), u32, _>::new(FutureWrapperNode { node: AddPairNode::new() });
let type_erased = Box::new(dyn_any) as TypeErasedBox<'_>; let type_erased = Box::new(dyn_any) as TypeErasedBox<'_>;
type_erased.eval(Box::new((4u32, 2u32))); type_erased.eval(Box::new((4u32, 2u32)));
@ -306,8 +306,8 @@ mod test {
let type_erased_id = Box::new(any_id) as TypeErasedBox; let type_erased_id = Box::new(any_id) as TypeErasedBox;
let type_erased = ComposeTypeErased::new(NodeContainer::new(type_erased), NodeContainer::new(type_erased_id)); let type_erased = ComposeTypeErased::new(NodeContainer::new(type_erased), NodeContainer::new(type_erased_id));
type_erased.eval(Box::new((4u32, 2u32))); type_erased.eval(Box::new((4u32, 2u32)));
//let downcast: DowncastBothNode<(u32, u32), u32> = DowncastBothNode::new(type_erased.as_ref()); // let downcast: DowncastBothNode<(u32, u32), u32> = DowncastBothNode::new(type_erased.as_ref());
//downcast.eval((4u32, 2u32)); // downcast.eval((4u32, 2u32));
} }
// TODO: Fix this test // TODO: Fix this test

View file

@ -169,11 +169,10 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
log::debug!("inner_network: {inner_network:?}"); log::debug!("inner_network: {inner_network:?}");
let network = NodeNetwork { let network = NodeNetwork {
imports: vec![NodeId(2), NodeId(1)], //vec![0, 1],
#[cfg(feature = "quantization")] #[cfg(feature = "quantization")]
exports: vec![NodeOutput::new(NodeId(5), 0)], exports: vec![NodeInput::node(NodeId(5), 0)],
#[cfg(not(feature = "quantization"))] #[cfg(not(feature = "quantization"))]
exports: vec![NodeOutput::new(NodeId(3), 0)], exports: vec![NodeInput::node(NodeId(3), 0)],
nodes: [ nodes: [
DocumentNode { DocumentNode {
name: "Slice".into(), name: "Slice".into(),
@ -183,19 +182,19 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
}, },
DocumentNode { DocumentNode {
name: "Quantization".into(), 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()), implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
name: "Width".into(), name: "Width".into(),
inputs: vec![NodeInput::Network(concrete!(u32))], inputs: vec![NodeInput::network(concrete!(u32), 0)],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()), implementation: DocumentNodeImplementation::ProtoNode("graphene_core::ops::IdentityNode".into()),
..Default::default() ..Default::default()
}, },
/*DocumentNode { /*DocumentNode {
name: "Index".into(), name: "Index".into(),
//inputs: vec![NodeInput::Network(concrete!(UVec3))], // inputs: vec![NodeInput::Network(concrete!(UVec3))],
inputs: vec![NodeInput::Inline(InlineRust::new("i1.x as usize".into(), concrete![u32]))], inputs: vec![NodeInput::Inline(InlineRust::new("i1.x as usize".into(), concrete![u32]))],
implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::CopiedNode".into()), implementation: DocumentNodeImplementation::ProtoNode("graphene_core::value::CopiedNode".into()),
..Default::default() ..Default::default()
@ -237,7 +236,7 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
NodeInput::node(NodeId(5), 0), NodeInput::node(NodeId(5), 0),
NodeInput::Inline(InlineRust::new( NodeInput::Inline(InlineRust::new(
"|x| o0[(_global_index.y * i1 + _global_index.x) as usize] = x".into(), "|x| o0[(_global_index.y * i1 + _global_index.x) as usize] = x".into(),
//"|x|()".into(), // "|x|()".into(),
Type::Fn(Box::new(concrete!(PackedPixel)), Box::new(concrete!(()))), Type::Fn(Box::new(concrete!(PackedPixel)), Box::new(concrete!(()))),
)), )),
], ],
@ -257,7 +256,7 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
log::debug!("compiling shader"); log::debug!("compiling shader");
let shader = compilation_client::compile( let shader = compilation_client::compile(
proto_networks, proto_networks,
vec![concrete!(u32), concrete!(Color)], //, concrete!(u32)], vec![concrete!(u32), concrete!(Color)],
vec![concrete!(Color)], vec![concrete!(Color)],
ShaderIO { ShaderIO {
#[cfg(feature = "quantization")] #[cfg(feature = "quantization")]
@ -265,7 +264,7 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
ShaderInput::UniformBuffer((), concrete!(u32)), ShaderInput::UniformBuffer((), concrete!(u32)),
ShaderInput::StorageBuffer((), concrete!(PackedPixel)), ShaderInput::StorageBuffer((), concrete!(PackedPixel)),
ShaderInput::UniformBuffer((), concrete!(quantization::QuantizationChannels)), ShaderInput::UniformBuffer((), concrete!(quantization::QuantizationChannels)),
//ShaderInput::Constant(gpu_executor::GPUConstant::GlobalInvocationId), // ShaderInput::Constant(gpu_executor::GPUConstant::GlobalInvocationId),
ShaderInput::OutputBuffer((), concrete!(PackedPixel)), ShaderInput::OutputBuffer((), concrete!(PackedPixel)),
], ],
#[cfg(not(feature = "quantization"))] #[cfg(not(feature = "quantization"))]
@ -282,19 +281,19 @@ async fn create_compute_pass_descriptor<T: Clone + Pixel + StaticTypeSized>(
) )
.await .await
.unwrap(); .unwrap();
//return ImageFrame::empty(); // return ImageFrame::empty();
let len: usize = image.image.data.len(); let len: usize = image.image.data.len();
/* /*
let canvas = editor_api.application_io.create_surface(); let canvas = editor_api.application_io.create_surface();
let surface = unsafe { executor.create_surface(canvas) }.unwrap(); let surface = unsafe { executor.create_surface(canvas) }.unwrap();
//log::debug!("id: {surface:?}"); // log::debug!("id: {surface:?}");
let surface_id = surface.surface_id; let surface_id = surface.surface_id;
let texture = executor.create_texture_buffer(image.image.clone(), TextureBufferOptions::Texture).unwrap(); let texture = executor.create_texture_buffer(image.image.clone(), TextureBufferOptions::Texture).unwrap();
//executor.create_render_pass(texture, surface).unwrap(); // executor.create_render_pass(texture, surface).unwrap();
let frame = SurfaceFrame { let frame = SurfaceFrame {
surface_id, surface_id,
@ -391,7 +390,7 @@ fn map_gpu_single_image(input: Image<Color>, node: String) -> Image<Color> {
inputs: vec![NodeId(0)], inputs: vec![NodeId(0)],
disabled: vec![], disabled: vec![],
previous_outputs: None, previous_outputs: None,
outputs: vec![NodeOutput::new(NodeId(0), 0)], outputs: vec![NodeInput::node(NodeId(0), 0)],
nodes: [( nodes: [(
NodeId(0), NodeId(0),
DocumentNode { DocumentNode {
@ -434,8 +433,7 @@ async fn blend_gpu_image(foreground: ImageFrame<Color>, background: ImageFrame<C
let compiler = graph_craft::graphene_compiler::Compiler {}; let compiler = graph_craft::graphene_compiler::Compiler {};
let network = NodeNetwork { let network = NodeNetwork {
imports: vec![], exports: vec![NodeInput::node(NodeId(0), 0)],
exports: vec![NodeOutput::new(NodeId(0), 0)],
nodes: [DocumentNode { nodes: [DocumentNode {
name: "BlendOp".into(), name: "BlendOp".into(),
inputs: vec![NodeInput::Inline(InlineRust::new( inputs: vec![NodeInput::Inline(InlineRust::new(

View file

@ -2,8 +2,8 @@
#[macro_use] #[macro_use]
extern crate log; extern crate log;
//pub mod value; // pub mod value;
//#![feature(const_type_name)] // #![feature(const_type_name)]
pub mod raster; pub mod raster;

View file

@ -96,8 +96,8 @@ fn create_distribution(data: Vec<f64>, samples: usize, channel: usize) -> Vec<(f
let max = *data.iter().max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)).unwrap(); let max = *data.iter().max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)).unwrap();
let data: Vec<f64> = data.iter().map(|x| x / max).collect(); let data: Vec<f64> = data.iter().map(|x| x / max).collect();
dbg!(max); dbg!(max);
//let data = autoquant::generate_normal_distribution(3.0, 1.1, 1000); // let data = autoquant::generate_normal_distribution(3.0, 1.1, 1000);
//data.iter_mut().for_each(|x| *x = x.abs()); // data.iter_mut().for_each(|x| *x = x.abs());
let mut dist = autoquant::integrate_distribution(data); let mut dist = autoquant::integrate_distribution(data);
autoquant::drop_duplicates(&mut dist); autoquant::drop_duplicates(&mut dist);
let dist = autoquant::normalize_distribution(dist.as_slice()); let dist = autoquant::normalize_distribution(dist.as_slice());

View file

@ -85,6 +85,8 @@ impl DynamicExecutor {
pub fn document_node_types(&self) -> ResolvedDocumentNodeTypes { pub fn document_node_types(&self) -> ResolvedDocumentNodeTypes {
let mut resolved_document_node_types = ResolvedDocumentNodeTypes::default(); 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() { 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(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 { 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) => { ConstructionArgs::Value(value) => {
let upcasted = UpcastNode::new(value.to_owned()); let upcasted = UpcastNode::new(value.to_owned());
let node = Box::new(upcasted) as TypeErasedBox<'_>; 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); self.store_node(node, id);
} }
ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"), ConstructionArgs::Inline(_) => unimplemented!("Inline nodes are not supported yet"),
@ -242,7 +244,7 @@ mod test {
let mut tree = BorrowTree::default(); let mut tree = BorrowTree::default();
let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32)), vec![]); let val_1_protonode = ProtoNode::value(ConstructionArgs::Value(TaggedValue::U32(2u32)), vec![]);
let context = TypingContext::default(); 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(); futures::executor::block_on(future).unwrap();
let _node = tree.get(NodeId(0)).unwrap(); let _node = tree.get(NodeId(0)).unwrap();
let result = futures::executor::block_on(tree.eval(NodeId(0), ())); let result = futures::executor::block_on(tree.eval(NodeId(0), ()));

View file

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

View file

@ -100,7 +100,7 @@ macro_rules! async_node {
graphene_std::any::PanicNode::<$arg, core::pin::Pin<Box<dyn core::future::Future<Output = $type>>>>::new() graphene_std::any::PanicNode::<$arg, core::pin::Pin<Box<dyn core::future::Future<Output = $type>>>>::new()
),*); ),*);
// TODO: Propagate the future type through the node graph // TODO: Propagate the future type through the node graph
//let params = vec![$(Type::Fn(Box::new(concrete!(())), Box::new(Type::Future(Box::new(concrete!($type)))))),*]; // let params = vec![$(Type::Fn(Box::new(concrete!(())), Box::new(Type::Future(Box::new(concrete!($type)))))),*];
let params = vec![$(fn_type!($arg, $type)),*]; let params = vec![$(fn_type!($arg, $type)),*];
let mut node_io = NodeIO::<'_, $input>::to_node_io(&node, params); let mut node_io = NodeIO::<'_, $input>::to_node_io(&node, params);
node_io.input = concrete!(<$input as StaticType>::Static); node_io.input = concrete!(<$input as StaticType>::Static);
@ -177,10 +177,10 @@ macro_rules! raster_node {
}}; }};
} }
//TODO: turn into hashmap // TODO: turn into hashmap
fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> { fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> {
let node_types: Vec<Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)>> = vec![ let node_types: Vec<Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)>> = vec![
//register_node!(graphene_core::ops::IdentityNode, input: Any<'_>, params: []), // register_node!(graphene_core::ops::IdentityNode, input: Any<'_>, params: []),
vec![( vec![(
ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"), ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"),
|_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }), |_| Box::pin(async move { FutureWrapperNode::new(IdentityNode::new()).into_type_erased() }),
@ -400,7 +400,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
Box::pin(async move { Box::pin(async move {
let document_node: DowncastBothNode<(), graph_craft::document::DocumentNode> = DowncastBothNode::new(args[0].clone()); let document_node: DowncastBothNode<(), graph_craft::document::DocumentNode> = DowncastBothNode::new(args[0].clone());
let editor_api: DowncastBothNode<(), WasmEditorApi> = DowncastBothNode::new(args[1].clone()); let editor_api: DowncastBothNode<(), WasmEditorApi> = DowncastBothNode::new(args[1].clone());
//let document_node = ClonedNode::new(document_node.eval(())); // let document_node = ClonedNode::new(document_node.eval(()));
let node = graphene_std::gpu_nodes::MapGpuNode::new(document_node, editor_api); let node = graphene_std::gpu_nodes::MapGpuNode::new(document_node, editor_api);
let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(node); let any: DynAnyNode<ImageFrame<Color>, _, _> = graphene_std::any::DynAnyNode::new(node);
any.into_type_erased() any.into_type_erased()
@ -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_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: 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: 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: 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]), 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]), 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_core::text::TextGeneratorNode<_, _, _>, input: WasmEditorApi, params: [String, graphene_core::text::Font, f64]),
register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []), register_node!(graphene_std::brush::VectorPointsNode, input: VectorData, params: []),
register_node!(graphene_core::ExtractImageFrame, input: WasmEditorApi, 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: graphene_core::vector::VectorData, params: []),
register_node!(graphene_core::ToGraphicElementNode, input: ImageFrame<Color>, params: []), register_node!(graphene_core::ToGraphicElementNode, input: ImageFrame<Color>, params: []),
register_node!(graphene_core::ToGraphicElementNode, input: GraphicGroup, 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: GraphicGroup, params: []),
register_node!(graphene_core::ToGraphicGroupNode, input: Artboard, 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::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(); let mut map: HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
for (id, c, types) in node_types.into_iter().flatten() { for (id, c, types) in node_types.into_iter().flatten() {

View file

@ -98,7 +98,7 @@ enum Asyncness {
} }
fn node_impl_impl(attr: TokenStream, item: TokenStream, asyncness: Asyncness) -> TokenStream { fn node_impl_impl(attr: TokenStream, item: TokenStream, asyncness: Asyncness) -> TokenStream {
//let node_name = parse_macro_input!(attr as Ident); // let node_name = parse_macro_input!(attr as Ident);
let node = parse_macro_input!(attr as syn::PathSegment); let node = parse_macro_input!(attr as syn::PathSegment);
let function = parse_macro_input!(item as ItemFn); let function = parse_macro_input!(item as ItemFn);
@ -202,8 +202,8 @@ fn node_impl_impl(attr: TokenStream, item: TokenStream, asyncness: Asyncness) ->
quote::quote!(#(let #parameter_mutability #parameter_idents = self.#parameter_idents.eval(());)*) quote::quote!(#(let #parameter_mutability #parameter_idents = self.#parameter_idents.eval(());)*)
}; };
let mut body_with_inputs = quote::quote!( let mut body_with_inputs = quote::quote!(
#parameters #parameters
{#body} {#body}
); );
if async_out { if async_out {
body_with_inputs = quote::quote!(Box::pin(async move { #body_with_inputs })); body_with_inputs = quote::quote!(Box::pin(async move { #body_with_inputs }));
@ -317,7 +317,7 @@ fn input_node_bounds(parameter_inputs: Vec<Type>, node_generics: Vec<GenericPara
bounds: Punctuated::from_iter([TypeParamBound::Trait(TraitBound { bounds: Punctuated::from_iter([TypeParamBound::Trait(TraitBound {
paren_token: None, paren_token: None,
modifier: syn::TraitBoundModifier::None, modifier: syn::TraitBoundModifier::None,
lifetimes: None, //syn::parse_quote!(for<'any_input>), lifetimes: None, // syn::parse_quote!(for<'any_input>),
path: syn::parse_quote!(#bound), path: syn::parse_quote!(#bound),
})]), })]),
}) })

View file

@ -42,7 +42,7 @@ impl<'a, I: StaticTypeSized + Sync + Pod + Send, O: StaticTypeSized + Send + Syn
async fn execute_shader<I: Pod + Send + Sync, O: Pod + Send + Sync>(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>, shader: Vec<u32>, data: Vec<I>, entry_point: String) -> Option<Vec<O>> { async fn execute_shader<I: Pod + Send + Sync, O: Pod + Send + Sync>(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>, shader: Vec<u32>, data: Vec<I>, entry_point: String) -> Option<Vec<O>> {
// Loads the shader from WGSL // Loads the shader from WGSL
dbg!(&shader); dbg!(&shader);
//write shader to file // write shader to file
use std::io::Write; use std::io::Write;
let mut file = std::fs::File::create("/tmp/shader.spv").unwrap(); let mut file = std::fs::File::create("/tmp/shader.spv").unwrap();
file.write_all(bytemuck::cast_slice(&shader)).unwrap(); file.write_all(bytemuck::cast_slice(&shader)).unwrap();

View file

@ -160,7 +160,7 @@ impl gpu_executor::GpuExecutor for WgpuExecutor {
usage |= wgpu::BufferUsages::MAP_WRITE | wgpu::BufferUsages::COPY_SRC; 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 { let buffer = self.context.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None, label: None,
contents: bytes.as_ref(), 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> { 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| { let create_buffer = |usage| {
Ok::<_, anyhow::Error>(self.context.device.create_buffer(&BufferDescriptor { Ok::<_, anyhow::Error>(self.context.device.create_buffer(&BufferDescriptor {
label: None, label: None,
@ -294,7 +294,7 @@ impl gpu_executor::GpuExecutor for WgpuExecutor {
println!("{surface_caps:?}"); println!("{surface_caps:?}");
if surface_caps.formats.is_empty() { if surface_caps.formats.is_empty() {
log::warn!("No surface formats available"); log::warn!("No surface formats available");
//return Ok(()); // return Ok(());
} }
let Some(config) = self.surface_config.take() else { return Ok(()) }; let Some(config) = self.surface_config.take() else { return Ok(()) };
let new_config = config.clone(); let new_config = config.clone();
@ -423,7 +423,7 @@ impl gpu_executor::GpuExecutor for WgpuExecutor {
} }
fn create_texture_view(&self, texture: ShaderInput<Self>) -> Result<ShaderInput<Self>> { fn create_texture_view(&self, texture: ShaderInput<Self>) -> Result<ShaderInput<Self>> {
//Ok(ShaderInput::TextureView(texture.create_view(&wgpu::TextureViewDescriptor::default()), ) ) // Ok(ShaderInput::TextureView(texture.create_view(&wgpu::TextureViewDescriptor::default()), ) )
let ShaderInput::TextureBuffer(texture, ty) = &texture else { let ShaderInput::TextureBuffer(texture, ty) = &texture else {
bail!("Tried to create a texture view from a non texture"); bail!("Tried to create a texture view from a non texture");
}; };