diff --git a/.github/workflows/build-dev-and-ci.yml b/.github/workflows/build-dev-and-ci.yml index ff3f25151e..e62ea3bca0 100644 --- a/.github/workflows/build-dev-and-ci.yml +++ b/.github/workflows/build-dev-and-ci.yml @@ -4,7 +4,7 @@ on: push: branches: - master - pull_request: + pull_request: {} env: CARGO_TERM_COLOR: always INDEX_HTML_HEAD_REPLACEMENT: @@ -13,9 +13,10 @@ jobs: build: runs-on: self-hosted permissions: - contents: read + contents: write deployments: write pull-requests: write + actions: write env: RUSTC_WRAPPER: /usr/bin/sccache CARGO_INCREMENTAL: 0 @@ -47,9 +48,11 @@ jobs: rustc --version - name: ✂ Replace template in of index.html + if: github.ref != 'refs/heads/master' + env: + INDEX_HTML_HEAD_REPLACEMENT: "" run: | # Remove the INDEX_HTML_HEAD_REPLACEMENT environment variable for build links (not master deploys) - git rev-parse --abbrev-ref HEAD | grep master > /dev/null || export INDEX_HTML_HEAD_REPLACEMENT="" sed -i "s||$INDEX_HTML_HEAD_REPLACEMENT|" frontend/index.html - name: 🌐 Build Graphite web code @@ -70,6 +73,19 @@ jobs: projectName: graphite-dev directory: frontend/dist + - name: 💬 Comment build link URL to commit hash page on GitHub + if: github.ref == 'refs/heads/master' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh api \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + /repos/${{ github.repository }}/commits/$(git rev-parse HEAD)/comments \ + -f body="| 📦 **Build Complete for** $(git rev-parse HEAD) | + |-| + | ${{ steps.cloudflare.outputs.url }} |" + - name: 👕 Lint Graphite web formatting env: NODE_ENV: production @@ -91,6 +107,51 @@ jobs: run: | mold -run cargo test --all-features --workspace + - name: 📃 Generate code documentation info for website + if: github.ref == 'refs/heads/master' + run: | + cargo test --package graphite-editor --lib -- messages::message::test::generate_message_tree + mkdir -p artifacts-generated + mv hierarchical_message_system_tree.txt artifacts-generated/hierarchical_message_system_tree.txt + + - name: 💿 Obtain cache of auto-generated code docs artifacts, to check if they've changed + if: github.ref == 'refs/heads/master' + id: cache-website-code-docs + uses: actions/cache/restore@v3 + with: + path: artifacts + key: website-code-docs + + - name: 🔍 Check if auto-generated code docs artifacts changed + if: github.ref == 'refs/heads/master' + id: website-code-docs-changed + run: | + if ! diff --brief --recursive artifacts-generated artifacts; then + echo "Auto-generated code docs artifacts have changed." + rm -rf artifacts + mv artifacts-generated artifacts + echo "changed=true" >> $GITHUB_OUTPUT + else + echo "Auto-generated code docs artifacts have not changed." + rm -rf artifacts + rm -rf artifacts-generated + fi + + - name: 💾 Save cache of auto-generated code docs artifacts + if: steps.website-code-docs-changed.outputs.changed == 'true' + uses: actions/cache/save@v3 + with: + path: artifacts + key: ${{ steps.cache-website-code-docs.outputs.cache-primary-key }} + + - name: ♻️ Trigger website rebuild if the auto-generated code docs artifacts have changed + if: steps.website-code-docs-changed.outputs.changed == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + rm -rf artifacts + gh workflow run website.yml --ref master + # miri: # runs-on: self-hosted diff --git a/.github/workflows/comment-!build-commands.yml b/.github/workflows/comment-!build-commands.yml index a8fa55e275..8d8a0ae096 100644 --- a/.github/workflows/comment-!build-commands.yml +++ b/.github/workflows/comment-!build-commands.yml @@ -73,9 +73,10 @@ jobs: rustc --version - name: ✂ Replace template in of index.html + env: + INDEX_HTML_HEAD_REPLACEMENT: "" run: | # Remove the INDEX_HTML_HEAD_REPLACEMENT environment variable for build links (not master deploys) - export INDEX_HTML_HEAD_REPLACEMENT="" sed -i "s||$INDEX_HTML_HEAD_REPLACEMENT|" frontend/index.html - name: ⌨ Set build command based on comment diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml index 0451d4226e..4b9a6ed409 100644 --- a/.github/workflows/website.yml +++ b/.github/workflows/website.yml @@ -9,6 +9,7 @@ on: pull_request: paths: - website/** + workflow_dispatch: {} env: CARGO_TERM_COLOR: always INDEX_HTML_HEAD_INCLUSION: @@ -30,6 +31,14 @@ jobs: with: tool: zola@0.20.0 + - name: 🔍 Check if `website/other` directory changed + uses: dorny/paths-filter@v3 + id: changes + with: + filters: | + website-other: + - "website/other/**" + - name: ✂ Replace template in of index.html run: | # Remove the INDEX_HTML_HEAD_INCLUSION environment variable for build links (not master deploys) @@ -43,16 +52,8 @@ jobs: npm run install-fonts zola --config config.toml build --minify - - name: 🔍 Check if `website/other` directory changed - uses: dorny/paths-filter@v3 - id: changes - with: - filters: | - other: - - "website/other/**" - - name: 💿 Restore cache of `website/other/dist` directory, if available and `website/other` didn't change - if: steps.changes.outputs.other != 'true' + if: steps.changes.outputs.website-other != 'true' id: cache-website-other-dist uses: actions/cache/restore@v3 with: @@ -80,8 +81,32 @@ jobs: - name: 🚚 Move `website/other/dist` contents to `website/public` run: | + mkdir -p website/public mv website/other/dist/* website/public + - name: 💿 Obtain cache of auto-generated code docs artifacts + id: cache-website-code-docs + uses: actions/cache/restore@v3 + with: + path: artifacts + key: website-code-docs + + - name: 📁 Fallback in case auto-generated code docs artifacts weren't cached + if: steps.cache-website-code-docs.outputs.cache-hit != 'true' + run: | + echo "🦀 Initial system version of Rust:" + rustc --version + rustup update stable + echo "🦀 Latest updated version of Rust:" + rustc --version + cargo test --package graphite-editor --lib -- messages::message::test::generate_message_tree + mkdir artifacts + mv hierarchical_message_system_tree.txt artifacts/hierarchical_message_system_tree.txt + + - name: 🚚 Move `artifacts` contents to `website/public` + run: | + mv artifacts/* website/public + - name: 📤 Publish to Cloudflare Pages id: cloudflare uses: cloudflare/pages-action@1 diff --git a/.gitignore b/.gitignore index 5700f4e89b..bdfa416126 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ profile.json flamegraph.svg .idea/ .direnv +hierarchical_message_system_tree.txt diff --git a/Cargo.lock b/Cargo.lock index da16bddb21..5487e27d28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "Inflector" @@ -10,9 +10,9 @@ checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" [[package]] name = "ab_glyph" -version = "0.2.29" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3672c180e71eeaaac3a541fbbc5f5ad4def8b747c595ad30d674e43049f7b0" +checksum = "1e0f4f6fbdc5ee39f2ede9f5f3ec79477271a6d6a2baff22310d51736bda6cea" dependencies = [ "ab_glyph_rasterizer", "owned_ttf_parser", @@ -20,9 +20,9 @@ dependencies = [ [[package]] name = "ab_glyph_rasterizer" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" +checksum = "b2187590a23ab1e3df8681afdf0987c48504d80291f002fcdb651f0ef5e25169" [[package]] name = "addr2line" @@ -35,21 +35,21 @@ dependencies = [ [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.2.15", + "getrandom 0.3.3", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -63,9 +63,12 @@ dependencies = [ [[package]] name = "aligned-vec" -version = "0.5.0" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1" +checksum = "dc890384c8602f339876ded803c97ad529f3842aba97f6392b3dba0dd171769b" +dependencies = [ + "equator", +] [[package]] name = "alloc-no-stdlib" @@ -95,7 +98,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" dependencies = [ "android-properties", - "bitflags 2.9.0", + "bitflags 2.9.1", "cc", "cesu8", "jni", @@ -138,9 +141,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -153,44 +156,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", - "once_cell", + "once_cell_polyfill", "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.97" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "arbitrary" @@ -206,7 +209,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -233,7 +236,7 @@ version = "0.38.0+1.3.281" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" dependencies = [ - "libloading 0.8.6", + "libloading 0.8.8", ] [[package]] @@ -267,15 +270,15 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "av1-grain" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf" +checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8" dependencies = [ "anyhow", "arrayvec", @@ -287,18 +290,18 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e" +checksum = "19135c0c7a60bfee564dbe44ab5ce0557c6bf3884e5291a50be76a15640c4fbd" dependencies = [ "arrayvec", ] [[package]] name = "axum" -version = "0.8.1" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" +checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" dependencies = [ "axum-core", "bytes", @@ -309,7 +312,7 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", - "itoa 1.0.15", + "itoa", "matchit", "memchr", "mime", @@ -330,12 +333,12 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "http-body-util", @@ -350,9 +353,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -434,9 +437,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] @@ -501,9 +504,9 @@ dependencies = [ [[package]] name = "brotli" -version = "7.0.0" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -512,9 +515,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "4.0.2" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -528,9 +531,9 @@ checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" @@ -543,13 +546,13 @@ dependencies = [ [[package]] name = "bytemuck_derive" -version = "1.8.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" +checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -579,7 +582,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cairo-sys-rs", "glib", "libc", @@ -604,7 +607,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "log", "polling", "rustix 0.38.44", @@ -626,9 +629,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" dependencies = [ "serde", ] @@ -674,9 +677,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.16" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "jobserver", "libc", @@ -712,9 +715,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -730,9 +733,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", @@ -772,9 +775,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.31" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "027bb0d98429ae334a8698531da7077bdf906419543a35a55c2cb1b66437d767" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" dependencies = [ "clap_builder", "clap_derive", @@ -782,9 +785,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.31" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5589e0cba072e0f3d23791efac0fd8627b49c829c196a492e88168e6a669d863" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" dependencies = [ "anstream", "anstyle", @@ -794,21 +797,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.28" +version = "4.5.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "codespan-reporting" @@ -842,9 +845,9 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "colored" @@ -931,9 +934,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -964,8 +967,8 @@ version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ - "bitflags 2.9.0", - "core-foundation 0.10.0", + "bitflags 2.9.1", + "core-foundation 0.10.1", "core-graphics-types 0.2.0", "foreign-types 0.5.0", "libc", @@ -988,8 +991,8 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ - "bitflags 2.9.0", - "core-foundation 0.10.0", + "bitflags 2.9.1", + "core-foundation 0.10.1", "libc", ] @@ -1058,9 +1061,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] @@ -1092,9 +1095,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" @@ -1108,15 +1111,15 @@ dependencies = [ [[package]] name = "cssparser" -version = "0.27.2" +version = "0.29.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" dependencies = [ "cssparser-macros", "dtoa-short", - "itoa 0.4.8", + "itoa", "matches", - "phf 0.8.0", + "phf 0.10.1", "proc-macro2", "quote", "smallvec", @@ -1130,7 +1133,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1140,14 +1143,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "cursor-icon" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" [[package]] name = "darling" @@ -1170,7 +1173,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1181,7 +1184,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1213,15 +1216,15 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.19" +version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1258,7 +1261,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1273,7 +1276,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2 0.6.1", ] @@ -1285,7 +1288,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1294,7 +1297,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.8.6", + "libloading 0.8.8", ] [[package]] @@ -1311,13 +1314,13 @@ dependencies = [ [[package]] name = "dlopen2_derive" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" +checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1337,9 +1340,9 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "dpi" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" dependencies = [ "serde", ] @@ -1381,7 +1384,7 @@ dependencies = [ "dyn-any", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1398,9 +1401,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "embed-resource" -version = "3.0.2" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbc6e0d8e0c03a655b53ca813f0463d2c956bc4db8138dbc89f120b066551e3" +checksum = "0963f530273dc3022ab2bdc3fcd6d488e850256f2284a82b7413cb9481ee85dd" dependencies = [ "cc", "memchr", @@ -1437,17 +1440,37 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", "env_filter", - "humantime", + "jiff", "log", ] +[[package]] +name = "equator" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4711b213838dfee0117e3be6ac926007d7f433d7bbe33595975d4190cb07e6fc" +dependencies = [ + "equator-macro", +] + +[[package]] +name = "equator-macro" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1466,12 +1489,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.10" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -1556,9 +1579,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -1578,15 +1601,15 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "font-types" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d868ec188a98bb014c606072edd47e52e7ab7297db943b0b28503121e1d037bd" +checksum = "1fa6a5e5a77b5f3f7f9e32879f484aa5b3632ddfbe568a16266c904a6f32cdaf" dependencies = [ "bytemuck", ] @@ -1612,9 +1635,9 @@ dependencies = [ [[package]] name = "fontconfig-parser" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fcfcd44ca6e90c921fee9fa665d530b21ef1327a4c1a6c5250ea44b776ada7" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" dependencies = [ "roxmltree", ] @@ -1683,7 +1706,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1784,7 +1807,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -1958,34 +1981,36 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.11.1+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi 0.13.3+wasi-0.2.2", - "windows-targets 0.52.6", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] name = "gif" -version = "0.13.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" dependencies = [ "color_quant", "weezl", @@ -2042,9 +2067,9 @@ dependencies = [ [[package]] name = "glam" -version = "0.29.2" +version = "0.29.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc46dd3ec48fdd8e693a98d2b8bafae273a2d54c1de02a2a7e3d57d501f39677" +checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee" dependencies = [ "serde", ] @@ -2055,7 +2080,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "futures-channel", "futures-core", "futures-executor", @@ -2083,7 +2108,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -2140,7 +2165,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbcd2dba93594b227a1f57ee09b8b9da8892c34d55aa332e034a228d0fe6a171" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "gpu-alloc-types", ] @@ -2150,7 +2175,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98ff03b468aa837d70984d55f5d3f846f6ec31fe34bbb97c4f85219caeee1ca4" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -2167,11 +2192,11 @@ dependencies = [ [[package]] name = "gpu-descriptor" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca" +checksum = "b89c83349105e3732062a895becfc71a8f921bb71ecbbdd8ff99263e3b53a0ca" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "gpu-descriptor-types", "hashbrown 0.15.4", ] @@ -2182,7 +2207,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf242682df893b86f33a73828fb09ca4b2d3bb6cc95249707fc684d27484b91" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -2281,7 +2306,7 @@ dependencies = [ "num-traits", "parley", "petgraph 0.7.1", - "rand 0.9.0", + "rand 0.9.1", "rand_chacha 0.9.0", "rustc-hash 2.1.1", "serde", @@ -2302,7 +2327,7 @@ dependencies = [ "log", "math-parser", "node-macro", - "rand 0.9.0", + "rand 0.9.1", ] [[package]] @@ -2334,7 +2359,7 @@ dependencies = [ "image", "ndarray", "node-macro", - "rand 0.9.0", + "rand 0.9.1", "rand_chacha 0.9.0", "serde", "specta", @@ -2363,7 +2388,7 @@ dependencies = [ "log", "ndarray", "node-macro", - "rand 0.9.0", + "rand 0.9.1", "rand_chacha 0.9.0", "reqwest", "tokio", @@ -2413,7 +2438,7 @@ name = "graphite-editor" version = "0.0.0" dependencies = [ "bezier-rs", - "bitflags 2.9.0", + "bitflags 2.9.1", "derivative", "dyn-any", "env_logger", @@ -2451,7 +2476,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -2521,7 +2546,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -2536,9 +2561,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.8" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" dependencies = [ "atomic-waker", "bytes", @@ -2546,7 +2571,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.7.1", + "indexmap 2.10.0", "slab", "tokio", "tokio-util", @@ -2555,9 +2580,9 @@ dependencies = [ [[package]] name = "half" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "bytemuck", "cfg-if", @@ -2596,15 +2621,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.4.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - -[[package]] -name = "hermit-abi" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -2620,27 +2639,25 @@ checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df" [[package]] name = "html5ever" -version = "0.26.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" dependencies = [ "log", "mac", "markup5ever", - "proc-macro2", - "quote", - "syn 1.0.109", + "match_token", ] [[package]] name = "http" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", - "itoa 1.0.15", + "itoa", ] [[package]] @@ -2655,12 +2672,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "pin-project-lite", @@ -2678,12 +2695,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hyper" version = "1.6.0" @@ -2698,7 +2709,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.15", + "itoa", "pin-project-lite", "smallvec", "tokio", @@ -2707,11 +2718,10 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.5" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "futures-util", "http", "hyper", "hyper-util", @@ -2741,21 +2751,28 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "dc2fdfdbff08affe55bb779f33b053aa1fe5dd5b54c257343c17edfa55711bdb" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", "http", "http-body", "hyper", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -2780,7 +2797,7 @@ dependencies = [ "quote", "serde", "serde_json", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -2794,16 +2811,17 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core 0.61.2", ] [[package]] @@ -2838,16 +2856,30 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ "displaydoc", + "potential_utf", "yoke", "zerofrom", "zerovec", ] +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap 0.8.0", + "tinystr 0.8.1", + "writeable 0.6.1", + "zerovec", +] + [[package]] name = "icu_locid" version = "1.5.0" @@ -2855,37 +2887,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", + "litemap 0.7.5", + "tinystr 0.7.6", + "writeable 0.5.5", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" dependencies = [ "displaydoc", "icu_collections", @@ -2893,67 +2904,54 @@ dependencies = [ "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "1.5.1" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "potential_utf", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", + "icu_locale_core", "stable_deref_trait", - "tinystr", - "writeable", + "tinystr 0.8.1", + "writeable 0.6.1", "yoke", "zerofrom", + "zerotrie", "zerovec", ] -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.99", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -2973,9 +2971,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", @@ -2983,16 +2981,16 @@ dependencies = [ [[package]] name = "image" -version = "0.25.5" +version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" dependencies = [ "bytemuck", "byteorder-lite", "color_quant", "exr", "gif", - "image-webp 0.2.1", + "image-webp 0.2.3", "num-traits", "png", "qoi", @@ -3016,9 +3014,9 @@ dependencies = [ [[package]] name = "image-webp" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" +checksum = "f6970fe7a5300b4b42e62c52efa0187540a5bef546c60edaf554ef595d2e6f0b" dependencies = [ "byteorder-lite", "quick-error", @@ -3049,9 +3047,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", "hashbrown 0.15.4", @@ -3081,7 +3079,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -3108,6 +3106,16 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "is-docker" version = "0.2.0" @@ -3123,7 +3131,7 @@ version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi 0.5.0", + "hermit-abi", "libc", "windows-sys 0.59.0", ] @@ -3162,12 +3170,6 @@ dependencies = [ "either", ] -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - [[package]] name = "itoa" version = "1.0.15" @@ -3197,6 +3199,30 @@ dependencies = [ "system-deps", ] +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "jni" version = "0.21.1" @@ -3221,18 +3247,19 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.3", "libc", ] [[package]] name = "jpeg-decoder" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" [[package]] name = "js-sys" @@ -3272,7 +3299,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "serde", "unicode-segmentation", ] @@ -3284,7 +3311,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aae1df220ece3c0ada96b8153459b67eebe9ae9212258bb0134ae60416fdf76" dependencies = [ "libc", - "libloading 0.8.6", + "libloading 0.8.8", "pkg-config", ] @@ -3296,14 +3323,13 @@ checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" [[package]] name = "kuchikiki" -version = "0.8.2" +version = "0.8.8-speedreader" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" dependencies = [ "cssparser", "html5ever", - "indexmap 1.9.3", - "matches", + "indexmap 2.10.0", "selectors", ] @@ -3356,9 +3382,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.170" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libfuzzer-sys" @@ -3382,29 +3408,29 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.53.2", ] [[package]] name = "libm" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "libc", - "redox_syscall 0.5.10", + "redox_syscall 0.5.13", ] [[package]] @@ -3415,9 +3441,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" @@ -3425,6 +3451,12 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + [[package]] name = "litrs" version = "0.4.1" @@ -3433,9 +3465,9 @@ checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -3443,9 +3475,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.26" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "loop9" @@ -3456,6 +3488,12 @@ dependencies = [ "imgref", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "mac" version = "0.1.1" @@ -3473,18 +3511,29 @@ dependencies = [ [[package]] name = "markup5ever" -version = "0.11.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" dependencies = [ "log", - "phf 0.10.1", - "phf_codegen 0.10.0", + "phf 0.11.3", + "phf_codegen 0.11.3", "string_cache", "string_cache_codegen", "tendril", ] +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "matches" version = "0.1.10" @@ -3511,9 +3560,9 @@ dependencies = [ [[package]] name = "matrixmultiply" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +checksum = "a06de3016e9fae57a36fd14dba131fccf49f74b40b7fbdb472f96e361ec71a08" dependencies = [ "autocfg", "rawpointer", @@ -3531,9 +3580,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" @@ -3559,7 +3608,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block", "core-graphics-types 0.1.3", "foreign-types 0.5.0", @@ -3582,9 +3631,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", @@ -3592,20 +3641,20 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] name = "muda" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de14a9b5d569ca68d7c891d613b390cf5ab4f851c77aaa2f9e435555d3d9492" +checksum = "58b89bf91c19bf036347f1ab85a81c560f08c0667c8601bece664d860a600988" dependencies = [ "crossbeam-channel", "dpi", @@ -3630,11 +3679,11 @@ checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f" dependencies = [ "arrayvec", "bit-set", - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg_aliases 0.1.1", "codespan-reporting", "hexf-parse", - "indexmap 2.7.1", + "indexmap 2.10.0", "log", "petgraph 0.6.5", "rustc-hash 1.1.0", @@ -3682,7 +3731,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "jni-sys", "log", "ndk-sys 0.5.0+25.2.9519653", @@ -3697,7 +3746,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "jni-sys", "log", "ndk-sys 0.6.0+11769913", @@ -3747,7 +3796,7 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -3805,7 +3854,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -3839,23 +3888,24 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +checksum = "a973b4e44ce6cad84ce69d797acf9a044532e4184c4f267913d1b546a0727b7a" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d" dependencies = [ "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -3905,11 +3955,11 @@ dependencies = [ [[package]] name = "objc2-app-kit" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5906f93257178e2f7ae069efb89fbd6ee94f0592740b5f8a1512ca498814d0fb" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2 0.6.1", "libc", "objc2 0.6.1", @@ -3919,27 +3969,27 @@ dependencies = [ "objc2-core-graphics", "objc2-core-image", "objc2-foundation 0.3.1", - "objc2-quartz-core 0.3.0", + "objc2-quartz-core 0.3.1", ] [[package]] name = "objc2-cloud-kit" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c1948a9be5f469deadbd6bcb86ad7ff9e47b4f632380139722f7d9840c0d42c" +checksum = "17614fdcd9b411e6ff1117dfb1d0150f908ba83a7df81b1f118005fe0a8ea15d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2 0.6.1", "objc2-foundation 0.3.1", ] [[package]] name = "objc2-core-data" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f860f8e841f6d32f754836f51e6bc7777cd7e7053cf18528233f6811d3eceb4" +checksum = "291fbbf7d29287518e8686417cf7239c74700fd4b607623140a7d4a3c834329d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2 0.6.1", "objc2-foundation 0.3.1", ] @@ -3950,18 +4000,19 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "dispatch2", "objc2 0.6.1", ] [[package]] name = "objc2-core-graphics" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dca602628b65356b6513290a21a6405b4d4027b8b250f0b98dddbb28b7de02" +checksum = "989c6c68c13021b5c2d6b71456ebb0f9dc78d752e86a98da7c716f4f9470f5a4" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", + "dispatch2", "objc2 0.6.1", "objc2-core-foundation", "objc2-io-surface", @@ -3969,9 +4020,9 @@ dependencies = [ [[package]] name = "objc2-core-image" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ffa6bea72bf42c78b0b34e89c0bafac877d5f80bf91e159a5d96ea7f693ca56" +checksum = "79b3dc0cc4386b6ccf21c157591b34a7f44c8e75b064f85502901ab2188c007e" dependencies = [ "objc2 0.6.1", "objc2-foundation 0.3.1", @@ -3983,7 +4034,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ba833d4a1cb1aac330f8c973fd92b6ff1858e4aef5cdd00a255eefb28022fb5" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2-core-foundation", ] @@ -4014,7 +4065,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2 0.5.1", "libc", "objc2 0.5.2", @@ -4026,7 +4077,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2 0.6.1", "libc", "objc2 0.6.1", @@ -4035,11 +4086,11 @@ dependencies = [ [[package]] name = "objc2-io-surface" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "161a8b87e32610086e1a7a9e9ec39f84459db7b3a0881c1f16ca5a2605581c19" +checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2 0.6.1", "objc2-core-foundation", ] @@ -4050,7 +4101,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -4062,7 +4113,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", @@ -4071,22 +4122,22 @@ dependencies = [ [[package]] name = "objc2-quartz-core" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb3794501bb1bee12f08dcad8c61f2a5875791ad1c6f47faa71a0f033f20071" +checksum = "90ffb6a0cd5f182dc964334388560b12a57f7b74b3e2dec5e2722aa2dfb2ccd5" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2 0.6.1", "objc2-foundation 0.3.1", ] [[package]] name = "objc2-ui-kit" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "777a571be14a42a3990d4ebedaeb8b54cd17377ec21b92e8200ac03797b3bee1" +checksum = "25b1312ad7bc8a0e92adae17aa10f90aae1fb618832f9b993b022b591027daed" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "objc2 0.6.1", "objc2-core-foundation", "objc2-foundation 0.3.1", @@ -4094,11 +4145,11 @@ dependencies = [ [[package]] name = "objc2-web-kit" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b717127e4014b0f9f3e8bba3d3f2acec81f1bde01f656823036e823ed2c94dce" +checksum = "91672909de8b1ce1c2252e95bbee8c1649c9ad9d14b9248b3d7b4c47903c47ad" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "block2 0.6.1", "objc2 0.6.1", "objc2-app-kit", @@ -4117,9 +4168,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.3" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" [[package]] name = "oorandom" @@ -4141,11 +4198,11 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.72" +version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cfg-if", "foreign-types 0.3.2", "libc", @@ -4162,7 +4219,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -4173,9 +4230,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.107" +version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", @@ -4200,9 +4257,9 @@ dependencies = [ [[package]] name = "os_pipe" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" +checksum = "db335f4760b14ead6290116f2427bf33a14d4f0617d49f78a246de10c1831224" dependencies = [ "libc", "windows-sys 0.59.0", @@ -4244,9 +4301,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -4254,13 +4311,13 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.10", + "redox_syscall 0.5.13", "smallvec", "windows-targets 0.52.6", ] @@ -4333,9 +4390,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", "thiserror 2.0.12", @@ -4344,9 +4401,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" +checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" dependencies = [ "pest", "pest_generator", @@ -4354,24 +4411,23 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" +checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "pest_meta" -version = "2.7.15" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" +checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" dependencies = [ - "once_cell", "pest", "sha2", ] @@ -4383,7 +4439,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset 0.4.2", - "indexmap 2.7.1", + "indexmap 2.10.0", ] [[package]] @@ -4393,7 +4449,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset 0.5.7", - "indexmap 2.7.1", + "indexmap 2.10.0", ] [[package]] @@ -4402,9 +4458,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" dependencies = [ - "phf_macros 0.8.0", "phf_shared 0.8.0", - "proc-macro-hack", ] [[package]] @@ -4413,7 +4467,9 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" dependencies = [ + "phf_macros 0.10.0", "phf_shared 0.10.0", + "proc-macro-hack", ] [[package]] @@ -4438,12 +4494,12 @@ dependencies = [ [[package]] name = "phf_codegen" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", + "phf_generator 0.11.3", + "phf_shared 0.11.3", ] [[package]] @@ -4478,12 +4534,12 @@ dependencies = [ [[package]] name = "phf_macros" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", + "phf_generator 0.10.0", + "phf_shared 0.10.0", "proc-macro-hack", "proc-macro2", "quote", @@ -4500,7 +4556,7 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -4556,13 +4612,13 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plist" -version = "1.7.1" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d" +checksum = "3d77244ce2d584cd84f6a15f86195b8c9b2a0dfbfd817c09e0464244091a58ed" dependencies = [ "base64 0.22.1", - "indexmap 2.7.1", - "quick-xml 0.32.0", + "indexmap 2.10.0", + "quick-xml", "serde", "time", ] @@ -4610,24 +4666,24 @@ dependencies = [ [[package]] name = "polling" -version = "3.7.4" +version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" +checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.4.0", + "hermit-abi", "pin-project-lite", - "rustix 0.38.44", + "rustix 1.0.7", "tracing", "windows-sys 0.59.0", ] [[package]] name = "portable-atomic" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" @@ -4638,6 +4694,15 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -4646,11 +4711,11 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -4709,7 +4774,7 @@ version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ - "toml_edit 0.22.24", + "toml_edit 0.22.27", ] [[package]] @@ -4755,7 +4820,7 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -4766,30 +4831,30 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.94" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afbdc74edc00b6f6a218ca6a5364d6226a259d4b8ea1af4a0ea063f27e179f4d" +checksum = "3eb8486b569e12e2c32ad3e204dbaba5e4b5b216e9367044f25f1dba42341773" dependencies = [ "profiling-procmacros", ] [[package]] name = "profiling-procmacros" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" +checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b" dependencies = [ "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -4825,29 +4890,21 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.32.0" +version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" -dependencies = [ - "memchr", -] - -[[package]] -name = "quick-xml" -version = "0.37.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" dependencies = [ "memchr", ] [[package]] name = "quinn" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" dependencies = [ "bytes", + "cfg_aliases 0.2.1", "pin-project-lite", "quinn-proto", "quinn-udp", @@ -4857,17 +4914,19 @@ dependencies = [ "thiserror 2.0.12", "tokio", "tracing", + "web-time 1.1.0", ] [[package]] name = "quinn-proto" -version = "0.11.9" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" dependencies = [ "bytes", - "getrandom 0.2.15", - "rand 0.8.5", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.1", "ring", "rustc-hash 2.1.1", "rustls", @@ -4881,9 +4940,9 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.10" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" dependencies = [ "cfg_aliases 0.2.1", "libc", @@ -4895,13 +4954,19 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.39" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.7.3" @@ -4929,13 +4994,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.3", - "zerocopy 0.8.23", ] [[package]] @@ -4983,7 +5047,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", ] [[package]] @@ -4992,7 +5056,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.3", ] [[package]] @@ -5056,9 +5120,9 @@ dependencies = [ [[package]] name = "ravif" -version = "0.11.11" +version = "0.11.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" +checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b" dependencies = [ "avif-serialize", "imgref", @@ -5108,7 +5172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f9e8a4f503e5c8750e4cd3b32a4e090035c46374b305a15c70bad833dca05f" dependencies = [ "bytemuck", - "font-types 0.8.3", + "font-types 0.8.4", ] [[package]] @@ -5142,11 +5206,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.10" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -5155,11 +5219,31 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.16", "libredox", "thiserror 2.0.12", ] +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "regex" version = "1.11.1" @@ -5197,9 +5281,9 @@ checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" [[package]] name = "reqwest" -version = "0.12.12" +version = "0.12.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ "base64 0.22.1", "bytes", @@ -5217,28 +5301,25 @@ dependencies = [ "hyper-rustls", "hyper-tls", "hyper-util", - "ipnet", "js-sys", "log", "mime", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", "quinn", "rustls", - "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls", "tokio-util", "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", @@ -5246,7 +5327,6 @@ dependencies = [ "wasm-streams", "web-sys", "webpki-roots", - "windows-registry", ] [[package]] @@ -5277,13 +5357,13 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.13" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.16", "libc", "untrusted", "windows-sys 0.52.0", @@ -5296,7 +5376,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64 0.21.7", - "bitflags 2.9.0", + "bitflags 2.9.1", "serde", "serde_derive", ] @@ -5309,9 +5389,9 @@ checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" [[package]] name = "rustc-hash" @@ -5340,7 +5420,7 @@ version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys 0.4.15", @@ -5349,22 +5429,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.1" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dade4812df5c384711475be5fcd8c162555352945401aed22a35bffeab61f657" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "errno", "libc", - "linux-raw-sys 0.9.2", + "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.23.23" +version = "0.23.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" dependencies = [ "once_cell", "ring", @@ -5374,29 +5454,21 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ "web-time 1.1.0", + "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "e4a72fe2bcf7a6ac6fd7d0b9e5cb68aeb7d4c0a0271730218b3e92d43b4eb435" dependencies = [ "ring", "rustls-pki-types", @@ -5405,9 +5477,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "rustybuzz" @@ -5415,7 +5487,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c85d1ccd519e61834798eb52c4e886e8c2d7d698dd3d6ce0b1b47eb8557f1181" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "bytemuck", "core_maths", "log", @@ -5466,6 +5538,30 @@ dependencies = [ "uuid", ] +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1375ba8ef45a6f15d83fa8748f1079428295d403d6ea991d09ab100155fbc06d" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "schemars_derive" version = "0.8.22" @@ -5475,7 +5571,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -5509,7 +5605,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -5528,22 +5624,20 @@ dependencies = [ [[package]] name = "selectors" -version = "0.22.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" dependencies = [ "bitflags 1.3.2", "cssparser", "derive_more", "fxhash", "log", - "matches", "phf 0.8.0", "phf_codegen 0.8.0", "precomputed-hash", "servo_arc", "smallvec", - "thin-slice", ] [[package]] @@ -5594,7 +5688,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -5605,7 +5699,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -5614,7 +5708,7 @@ version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ - "itoa 1.0.15", + "itoa", "memchr", "ryu", "serde", @@ -5626,7 +5720,7 @@ version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" dependencies = [ - "itoa 1.0.15", + "itoa", "serde", ] @@ -5638,14 +5732,14 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -5657,22 +5751,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.15", + "itoa", "ryu", "serde", ] [[package]] name = "serde_with" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.7.1", + "indexmap 2.10.0", + "schemars 0.9.0", + "schemars 1.0.3", "serde", "serde_derive", "serde_json", @@ -5682,14 +5778,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -5716,9 +5812,9 @@ dependencies = [ [[package]] name = "servo_arc" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" dependencies = [ "nodrop", "stable_deref_trait", @@ -5726,9 +5822,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -5737,12 +5833,13 @@ dependencies = [ [[package]] name = "shared_child" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fa9338aed9a1df411814a5b2252f7cd206c55ae9bf2fa763f8de84603aa60c" +checksum = "c2778001df1384cf20b6dc5a5a90f48da35539885edaaefd887f8d744e939c0b" dependencies = [ "libc", - "windows-sys 0.59.0", + "sigchld", + "windows-sys 0.60.2", ] [[package]] @@ -5751,6 +5848,36 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "sigchld" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1219ef50fc0fdb04fcc243e6aa27f855553434ffafe4fa26554efb78b5b4bf89" +dependencies = [ + "libc", + "os_pipe", + "signal-hook", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + [[package]] name = "simd-adler32" version = "0.3.7" @@ -5819,12 +5946,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "slotmap" @@ -5850,7 +5974,7 @@ version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "calloop", "calloop-wayland-source", "cursor-icon", @@ -5880,9 +6004,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -5904,7 +6028,7 @@ dependencies = [ "objc2-foundation 0.2.2", "objc2-quartz-core 0.2.2", "raw-window-handle", - "redox_syscall 0.5.10", + "redox_syscall 0.5.13", "wasm-bindgen", "web-sys", "windows-sys 0.59.0", @@ -5956,7 +6080,7 @@ dependencies = [ "Inflector", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -5974,7 +6098,7 @@ version = "0.3.0+sdk-1.3.268.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] [[package]] @@ -6043,9 +6167,9 @@ checksum = "94afda9cd163c04f6bee8b4bf2501c91548deae308373c436f36aeff3cf3c4a3" [[package]] name = "svg_fmt" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce5d813d71d82c4cbc1742135004e4a79fd870214c155443451c139c9470a0aa" +checksum = "0193cc4331cfd2f3d2011ef287590868599a2f33c3e69bc22c1a3d3acf9e02fb" [[package]] name = "svgtypes" @@ -6092,9 +6216,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.99" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -6112,13 +6236,13 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -6127,7 +6251,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -6157,12 +6281,12 @@ dependencies = [ [[package]] name = "tao" -version = "0.32.8" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63c8b1020610b9138dd7b1e06cf259ae91aa05c30f3bd0d6b42a03997b92dec1" +checksum = "49c380ca75a231b87b6c9dd86948f035012e7171d1a7c40a9c2890489a7ffd8a" dependencies = [ - "bitflags 2.9.0", - "core-foundation 0.10.0", + "bitflags 2.9.1", + "core-foundation 0.10.1", "core-graphics 0.24.0", "crossbeam-channel", "dispatch", @@ -6188,8 +6312,8 @@ dependencies = [ "tao-macros", "unicode-segmentation", "url", - "windows 0.60.0", - "windows-core 0.60.1", + "windows 0.61.3", + "windows-core 0.61.2", "windows-version", "x11-dl", ] @@ -6202,7 +6326,7 @@ checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -6213,17 +6337,16 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.4.1" +version = "2.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d08db1ff9e011e04014e737ec022610d756c0eae0b3b3a9037bccaf3003173a" +checksum = "124e129c9c0faa6bec792c5948c89e86c90094133b0b9044df0ce5f0a8efaa0d" dependencies = [ "anyhow", "bytes", "dirs", "dunce", "embed_plist", - "futures-util", - "getrandom 0.2.15", + "getrandom 0.3.3", "glob", "gtk", "heck 0.5.0", @@ -6236,6 +6359,7 @@ dependencies = [ "objc2 0.6.1", "objc2-app-kit", "objc2-foundation 0.3.1", + "objc2-ui-kit", "percent-encoding", "plist", "raw-window-handle", @@ -6258,14 +6382,14 @@ dependencies = [ "webkit2gtk", "webview2-com", "window-vibrancy", - "windows 0.60.0", + "windows 0.61.3", ] [[package]] name = "tauri-build" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fd20e4661c2cce65343319e6e8da256958f5af958cafc47c0d0af66a55dcd17" +checksum = "12f025c389d3adb83114bec704da973142e82fc6ec799c7c750c5e21cefaec83" dependencies = [ "anyhow", "cargo_toml", @@ -6273,7 +6397,7 @@ dependencies = [ "glob", "heck 0.5.0", "json-patch", - "schemars", + "schemars 0.8.22", "semver", "serde", "serde_json", @@ -6285,9 +6409,9 @@ dependencies = [ [[package]] name = "tauri-codegen" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458258b19032450ccf975840116ecf013e539eadbb74420bd890e8c56ab2b1a4" +checksum = "f5df493a1075a241065bc865ed5ef8d0fbc1e76c7afdc0bf0eccfaa7d4f0e406" dependencies = [ "base64 0.22.1", "brotli", @@ -6301,7 +6425,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "syn 2.0.99", + "syn 2.0.104", "tauri-utils", "thiserror 2.0.12", "time", @@ -6312,28 +6436,28 @@ dependencies = [ [[package]] name = "tauri-macros" -version = "2.1.1" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d402813d3b9c773a0fa58697c457c771f10e735498fdcb7b343264d18e5a601f" +checksum = "f237fbea5866fa5f2a60a21bea807a2d6e0379db070d89c3a10ac0f2d4649bbc" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", "tauri-codegen", "tauri-utils", ] [[package]] name = "tauri-plugin" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4190775d6ff73fe66d9af44c012739a2659720efd9c0e1e56a918678038699d" +checksum = "1d9a0bd00bf1930ad1a604d08b0eb6b2a9c1822686d65d7f4731a7723b8901d3" dependencies = [ "anyhow", "glob", "plist", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "tauri-utils", @@ -6343,15 +6467,15 @@ dependencies = [ [[package]] name = "tauri-plugin-fs" -version = "2.2.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88371e340ad2f07409a3b68294abe73f20bc9c1bc1b631a31dc37a3d0161f682" +checksum = "c341290d31991dbca38b31d412c73dfbdb070bb11536784f19dd2211d13b778f" dependencies = [ "anyhow", "dunce", "glob", "percent-encoding", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "serde_repr", @@ -6361,14 +6485,13 @@ dependencies = [ "thiserror 2.0.12", "toml", "url", - "uuid", ] [[package]] name = "tauri-plugin-http" -version = "2.4.3" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40dcd6c922a1885e1f0bcebc6768fec6e005bd4b9001c5d90a2f5d4cab297729" +checksum = "b0c1a38da944b357ffa23bafd563b1579f18e6fbd118fcd84769406d35dcc5c7" dependencies = [ "bytes", "cookie_store", @@ -6376,7 +6499,7 @@ dependencies = [ "http", "regex", "reqwest", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "tauri", @@ -6390,16 +6513,16 @@ dependencies = [ [[package]] name = "tauri-plugin-shell" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d5eb3368b959937ad2aeaf6ef9a8f5d11e01ffe03629d3530707bbcb27ff5d" +checksum = "2b9ffadec5c3523f11e8273465cacb3d86ea7652a28e6e2a2e9b5c182f791d25" dependencies = [ "encoding_rs", "log", "open", "os_pipe", "regex", - "schemars", + "schemars 0.8.22", "serde", "serde_json", "shared_child", @@ -6411,29 +6534,31 @@ dependencies = [ [[package]] name = "tauri-runtime" -version = "2.5.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00ada7ac2f9276f09b8c3afffd3215fd5d9bff23c22df8a7c70e7ef67cacd532" +checksum = "9e7bb73d1bceac06c20b3f755b2c8a2cb13b20b50083084a8cf3700daf397ba4" dependencies = [ "cookie", "dpi", "gtk", "http", "jni", + "objc2 0.6.1", + "objc2-ui-kit", "raw-window-handle", "serde", "serde_json", "tauri-utils", "thiserror 2.0.12", "url", - "windows 0.60.0", + "windows 0.61.3", ] [[package]] name = "tauri-runtime-wry" -version = "2.5.1" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2e5842c57e154af43a20a49c7efee0ce2578c20b4c2bdf266852b422d2e421" +checksum = "902b5aa9035e16f342eb64f8bf06ccdc2808e411a2525ed1d07672fa4e780bad" dependencies = [ "gtk", "http", @@ -6452,15 +6577,15 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "windows 0.60.0", + "windows 0.61.3", "wry", ] [[package]] name = "tauri-utils" -version = "2.3.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f037e66c7638cc0a2213f61566932b9a06882b8346486579c90e4b019bac447" +checksum = "41743bbbeb96c3a100d234e5a0b60a46d5aa068f266160862c7afdbf828ca02e" dependencies = [ "anyhow", "brotli", @@ -6479,7 +6604,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "schemars", + "schemars 0.8.22", "semver", "serde", "serde-untagged", @@ -6496,25 +6621,25 @@ dependencies = [ [[package]] name = "tauri-winres" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56eaa45f707bedf34d19312c26d350bc0f3c59a47e58e8adbeecdc850d2c13a0" +checksum = "e8d321dbc6f998d825ab3f0d62673e810c861aac2d0de2cc2c395328f1d113b4" dependencies = [ "embed-resource", + "indexmap 2.10.0", "toml", ] [[package]] name = "tempfile" -version = "3.18.0" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ - "cfg-if", "fastrand", - "getrandom 0.3.1", + "getrandom 0.3.3", "once_cell", - "rustix 1.0.1", + "rustix 1.0.7", "windows-sys 0.59.0", ] @@ -6538,12 +6663,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "thin-slice" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" - [[package]] name = "thiserror" version = "1.0.69" @@ -6570,7 +6689,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -6581,7 +6700,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -6602,7 +6721,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", - "itoa 1.0.15", + "itoa", "num-conv", "powerfmt", "serde", @@ -6657,6 +6776,15 @@ name = "tinystr" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" dependencies = [ "displaydoc", "zerovec", @@ -6689,9 +6817,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.45.0" +version = "1.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779" dependencies = [ "backtrace", "bytes", @@ -6711,7 +6839,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -6736,9 +6864,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", @@ -6749,21 +6877,21 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.20" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.24", + "toml_edit 0.22.27", ] [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] @@ -6774,7 +6902,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.10.0", "toml_datetime", "winnow 0.5.40", ] @@ -6785,24 +6913,31 @@ version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.10.0", "toml_datetime", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.24" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.7.1", + "indexmap 2.10.0", "serde", "serde_spanned", "toml_datetime", - "winnow 0.7.3", + "toml_write", + "winnow 0.7.11", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tower" version = "0.5.2" @@ -6819,6 +6954,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -6844,18 +6997,18 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", ] [[package]] name = "tray-icon" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d433764348e7084bad2c5ea22c96c71b61b17afe3a11645710f533bd72b6a2b5" +checksum = "2da75ec677957aa21f6e0b361df0daab972f13a5bee3606de0638fd4ee1c666a" dependencies = [ "crossbeam-channel", "dirs", @@ -7076,12 +7229,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -7096,19 +7243,21 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom 0.3.1", + "getrandom 0.3.3", + "js-sys", "serde", + "wasm-bindgen", ] [[package]] name = "v_frame" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b" +checksum = "666b7727c8875d6ab5db9533418d7c764233ac9c0cff1d469aec8fa127597be2" dependencies = [ "aligned-vec", "num-traits", @@ -7221,15 +7370,15 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasi" -version = "0.13.3+wasi-0.2.2" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ "wit-bindgen-rt", ] @@ -7256,7 +7405,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -7291,7 +7440,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -7320,9 +7469,9 @@ dependencies = [ [[package]] name = "wayland-backend" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7208998eaa3870dad37ec8836979581506e0c5c64c20c9e79e9d2a10d6f47bf" +checksum = "fe770181423e5fc79d3e2a7f4410b7799d5aab1de4372853de3c6aa13ca24121" dependencies = [ "cc", "downcast-rs", @@ -7334,11 +7483,11 @@ dependencies = [ [[package]] name = "wayland-client" -version = "0.31.8" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2120de3d33638aaef5b9f4472bff75f07c56379cf76ea320bd3a3d65ecaf73f" +checksum = "978fa7c67b0847dbd6a9f350ca2569174974cd4082737054dbb7fbb79d7d9a61" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "rustix 0.38.44", "wayland-backend", "wayland-scanner", @@ -7350,16 +7499,16 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "cursor-icon", "wayland-backend", ] [[package]] name = "wayland-cursor" -version = "0.31.8" +version = "0.31.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a93029cbb6650748881a00e4922b076092a6a08c11e7fbdb923f064b23968c5d" +checksum = "a65317158dec28d00416cb16705934070aef4f8393353d41126c54264ae0f182" dependencies = [ "rustix 0.38.44", "wayland-client", @@ -7372,7 +7521,7 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-scanner", @@ -7384,7 +7533,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -7397,7 +7546,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "wayland-backend", "wayland-client", "wayland-protocols", @@ -7411,7 +7560,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "896fdafd5d28145fce7958917d69f2fd44469b1d4e861cb5961bcbeebc6d1484" dependencies = [ "proc-macro2", - "quick-xml 0.37.2", + "quick-xml", "quote", ] @@ -7503,24 +7652,24 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.8" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502" dependencies = [ "rustls-pki-types", ] [[package]] name = "webview2-com" -version = "0.36.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0d606f600e5272b514dbb66539dd068211cc20155be8d3958201b4b5bd79ed3" +checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" dependencies = [ "webview2-com-macros", "webview2-com-sys", - "windows 0.60.0", - "windows-core 0.60.1", - "windows-implement 0.59.0", + "windows 0.61.3", + "windows-core 0.61.2", + "windows-implement 0.60.0", "windows-interface 0.59.1", ] @@ -7532,25 +7681,25 @@ checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "webview2-com-sys" -version = "0.36.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb27fccd3c27f68e9a6af1bcf48c2d82534b8675b83608a4d81446d095a17ac" +checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" dependencies = [ "thiserror 2.0.12", - "windows 0.60.0", - "windows-core 0.60.1", + "windows 0.61.3", + "windows-core 0.61.2", ] [[package]] name = "weezl" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" [[package]] name = "wgpu" @@ -7585,11 +7734,11 @@ checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a" dependencies = [ "arrayvec", "bit-vec", - "bitflags 2.9.0", + "bitflags 2.9.1", "bytemuck", "cfg_aliases 0.1.1", "document-features", - "indexmap 2.7.1", + "indexmap 2.10.0", "log", "naga", "once_cell", @@ -7631,7 +7780,7 @@ dependencies = [ "arrayvec", "ash", "bit-set", - "bitflags 2.9.0", + "bitflags 2.9.1", "block", "bytemuck", "cfg_aliases 0.1.1", @@ -7644,7 +7793,7 @@ dependencies = [ "js-sys", "khronos-egl", "libc", - "libloading 0.8.6", + "libloading 0.8.8", "log", "metal", "naga", @@ -7672,7 +7821,7 @@ version = "23.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "js-sys", "web-sys", ] @@ -7735,12 +7884,12 @@ dependencies = [ [[package]] name = "windows" -version = "0.60.0" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf874e74c7a99773e62b1c671427abf01a425e77c3d3fb9fb1e4883ea934529" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ "windows-collections", - "windows-core 0.60.1", + "windows-core 0.61.2", "windows-future", "windows-link", "windows-numerics", @@ -7748,20 +7897,11 @@ dependencies = [ [[package]] name = "windows-collections" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5467f79cc1ba3f52ebb2ed41dbb459b8e7db636cc3429458d9a852e15bc24dec" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-core 0.60.1", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", + "windows-core 0.61.2", ] [[package]] @@ -7779,25 +7919,26 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.60.1" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca21a92a9cae9bf4ccae5cf8368dce0837100ddf6e6d57936749e85f152f6247" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.59.0", + "windows-implement 0.60.0", "windows-interface 0.59.1", "windows-link", - "windows-result 0.3.1", - "windows-strings 0.3.1", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] name = "windows-future" -version = "0.1.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a787db4595e7eb80239b74ce8babfb1363d8e343ab072f2ffe901400c03349f0" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "windows-core 0.60.1", + "windows-core 0.61.2", "windows-link", + "windows-threading", ] [[package]] @@ -7808,18 +7949,18 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "windows-implement" -version = "0.59.0" +version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -7830,7 +7971,7 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -7841,34 +7982,34 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] name = "windows-link" -version = "0.1.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-numerics" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "005dea54e2f6499f2cee279b8f703b3cf3b5734a2d8d21867c8f44003182eeed" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "windows-core 0.60.1", + "windows-core 0.61.2", "windows-link", ] [[package]] name = "windows-registry" -version = "0.2.0" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ - "windows-result 0.2.0", - "windows-strings 0.1.0", - "windows-targets 0.52.6", + "windows-link", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] @@ -7882,9 +8023,9 @@ dependencies = [ [[package]] name = "windows-result" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06374efe858fab7e4f881500e6e86ec8bc28f9462c47e5a9941a0142ad86b189" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] @@ -7901,9 +8042,9 @@ dependencies = [ [[package]] name = "windows-strings" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] @@ -7944,6 +8085,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -7983,7 +8133,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", @@ -7991,10 +8141,35 @@ dependencies = [ ] [[package]] -name = "windows-version" -version = "0.1.3" +name = "windows-targets" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bfbcc4996dd183ff1376a20ade1242da0d2dcaff83cc76710a588d24fd4c5db" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-version" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04a5c6627e310a23ad2358483286c7df260c964eb2d003d8efd6d0f4e79265c" dependencies = [ "windows-link", ] @@ -8017,6 +8192,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -8035,6 +8216,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -8053,12 +8240,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -8077,6 +8276,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -8095,6 +8300,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -8113,6 +8324,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -8131,6 +8348,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winit" version = "0.29.15" @@ -8140,7 +8363,7 @@ dependencies = [ "ahash", "android-activity", "atomic-waker", - "bitflags 2.9.0", + "bitflags 2.9.1", "bytemuck", "calloop", "cfg_aliases 0.1.1", @@ -8190,38 +8413,32 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.3" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] [[package]] name = "winreg" -version = "0.52.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" dependencies = [ "cfg-if", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "wit-bindgen-rt" -version = "0.33.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", ] -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - [[package]] name = "writeable" version = "0.5.5" @@ -8229,10 +8446,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] -name = "wry" -version = "0.50.5" +name = "writeable" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b19b78efae8b853c6c817e8752fc1dbf9cab8a8ffe9c30f399bd750ccf0f0730" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "wry" +version = "0.52.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12a714d9ba7075aae04a6e50229d6109e3d584774b99a6a8c60de1698ca111b9" dependencies = [ "base64 0.22.1", "block2 0.6.1", @@ -8266,8 +8489,8 @@ dependencies = [ "webkit2gtk", "webkit2gtk-sys", "webview2-com", - "windows 0.60.0", - "windows-core 0.60.1", + "windows 0.61.3", + "windows-core 0.61.2", "windows-version", "x11-dl", ] @@ -8302,7 +8525,7 @@ dependencies = [ "as-raw-xcb-connection", "gethostname", "libc", - "libloading 0.8.6", + "libloading 0.8.8", "once_cell", "rustix 0.38.44", "x11rb-protocol", @@ -8316,9 +8539,9 @@ checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" [[package]] name = "xcursor" -version = "0.3.8" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" [[package]] name = "xkbcommon-dl" @@ -8326,7 +8549,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.9.1", "dlib", "log", "once_cell", @@ -8341,9 +8564,9 @@ checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" [[package]] name = "xml-rs" -version = "0.8.25" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b940ebc25896e71dd073bad2dbaa2abfe97b0a391415e22ad1326d9c54e3c4" +checksum = "a62ce76d9b56901b19a74f19431b0d8b3bc7ca4ad685a746dfd78ca8f4fc6bda" [[package]] name = "xmlwriter" @@ -8365,9 +8588,9 @@ checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" [[package]] name = "yoke" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" dependencies = [ "serde", "stable_deref_trait", @@ -8377,13 +8600,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", "synstructure", ] @@ -8395,43 +8618,22 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ - "byteorder", - "zerocopy-derive 0.7.35", -] - -[[package]] -name = "zerocopy" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" -dependencies = [ - "zerocopy-derive 0.8.23", + "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -8451,7 +8653,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", "synstructure", ] @@ -8462,10 +8664,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] -name = "zerovec" -version = "0.10.4" +name = "zerotrie" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" dependencies = [ "yoke", "zerofrom", @@ -8474,13 +8687,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.3" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.99", + "syn 2.0.104", ] [[package]] @@ -8500,9 +8713,9 @@ dependencies = [ [[package]] name = "zune-jpeg" -version = "0.4.14" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028" +checksum = "7384255a918371b5af158218d131530f694de9ad3815ebdd0453a940485cb0fa" dependencies = [ "zune-core", ] diff --git a/about.toml b/about.toml index 279ba1068f..796e061fd4 100644 --- a/about.toml +++ b/about.toml @@ -6,6 +6,7 @@ accepted = [ "BSD-3-Clause", "BSL-1.0", "CC0-1.0", + "CDLA-Permissive-2.0", "ISC", "MIT-0", "MIT", @@ -14,6 +15,7 @@ accepted = [ "Unicode-3.0", "Unicode-DFS-2016", "Zlib", + "NCSA", ] workarounds = ["ring"] ignore-build-dependencies = true diff --git a/deny.toml b/deny.toml index 556f12c11e..610902ac35 100644 --- a/deny.toml +++ b/deny.toml @@ -75,11 +75,14 @@ allow = [ "BSD-3-Clause", "BSL-1.0", "CC0-1.0", + "CDLA-Permissive-2.0", "ISC", + "MIT-0", "MIT", "MPL-2.0", "OpenSSL", "Unicode-3.0", + "Unicode-DFS-2016", "Zlib", "NCSA", ] diff --git a/editor/src/consts.rs b/editor/src/consts.rs index 9adba6d908..58585e2dab 100644 --- a/editor/src/consts.rs +++ b/editor/src/consts.rs @@ -61,6 +61,7 @@ pub const SELECTION_DRAG_ANGLE: f64 = 90.; pub const PIVOT_CROSSHAIR_THICKNESS: f64 = 1.; pub const PIVOT_CROSSHAIR_LENGTH: f64 = 9.; pub const PIVOT_DIAMETER: f64 = 5.; +pub const DOWEL_PIN_RADIUS: f64 = 4.; // COMPASS ROSE pub const COMPASS_ROSE_RING_INNER_DIAMETER: f64 = 13.; @@ -133,8 +134,8 @@ pub const SCALE_EFFECT: f64 = 0.5; // COLORS pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff"; -pub const COLOR_OVERLAY_BLUE_50: &str = "rgba(0, 168, 255, 0.5)"; pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848"; +pub const COLOR_OVERLAY_YELLOW_DULL: &str = "#d7ba8b"; pub const COLOR_OVERLAY_GREEN: &str = "#63ce63"; pub const COLOR_OVERLAY_RED: &str = "#ef5454"; pub const COLOR_OVERLAY_GRAY: &str = "#cccccc"; diff --git a/editor/src/dispatcher.rs b/editor/src/dispatcher.rs index 809bf48614..9408d7d927 100644 --- a/editor/src/dispatcher.rs +++ b/editor/src/dispatcher.rs @@ -40,7 +40,6 @@ impl DispatcherMessageHandlers { /// The last occurrence of the message in the message queue is sufficient to ensure correct behavior. /// In addition, these messages do not change any state in the backend (aside from caches). const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[ - MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::NodeGraph(NodeGraphMessageDiscriminant::SendGraph))), MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel( PropertiesPanelMessageDiscriminant::Refresh, ))), @@ -141,6 +140,7 @@ impl Dispatcher { let graphene_std::renderer::RenderMetadata { upstream_footprints: footprints, local_transforms, + first_instance_source_id, click_targets, clip_targets, } = render_metadata; @@ -150,6 +150,7 @@ impl Dispatcher { DocumentMessage::UpdateUpstreamTransforms { upstream_footprints: footprints, local_transforms, + first_instance_source_id, }, DocumentMessage::UpdateClickTargets { click_targets }, DocumentMessage::UpdateClipTargets { clip_targets }, diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 80d80b5305..2c59945136 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -15,3 +15,4 @@ pub mod node_graph_executor; #[cfg(test)] pub mod test_utils; pub mod utility_traits; +pub mod utility_types; diff --git a/editor/src/messages/animation/animation_message_handler.rs b/editor/src/messages/animation/animation_message_handler.rs index eb7ceba2e0..211d22c87c 100644 --- a/editor/src/messages/animation/animation_message_handler.rs +++ b/editor/src/messages/animation/animation_message_handler.rs @@ -24,7 +24,7 @@ enum AnimationState { }, } -#[derive(Default, Debug, Clone, PartialEq)] +#[derive(Default, Debug, Clone, PartialEq, ExtractField)] pub struct AnimationMessageHandler { /// Used to re-send the UI on the next frame after playback starts live_preview_recently_zero: bool, @@ -57,6 +57,7 @@ impl AnimationMessageHandler { } } +#[message_handler_data] impl MessageHandler for AnimationMessageHandler { fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/broadcast/broadcast_message_handler.rs b/editor/src/messages/broadcast/broadcast_message_handler.rs index 51d64b5852..489df47ab7 100644 --- a/editor/src/messages/broadcast/broadcast_message_handler.rs +++ b/editor/src/messages/broadcast/broadcast_message_handler.rs @@ -1,10 +1,11 @@ use crate::messages::prelude::*; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct BroadcastMessageHandler { listeners: HashMap>, } +#[message_handler_data] impl MessageHandler for BroadcastMessageHandler { fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/debug/debug_message_handler.rs b/editor/src/messages/debug/debug_message_handler.rs index 6044ce9108..064ad2510c 100644 --- a/editor/src/messages/debug/debug_message_handler.rs +++ b/editor/src/messages/debug/debug_message_handler.rs @@ -1,11 +1,12 @@ use super::utility_types::MessageLoggingVerbosity; use crate::messages::prelude::*; -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct DebugMessageHandler { pub message_logging_verbosity: MessageLoggingVerbosity, } +#[message_handler_data] impl MessageHandler for DebugMessageHandler { fn process_message(&mut self, message: DebugMessage, responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/dialog/dialog_message_handler.rs b/editor/src/messages/dialog/dialog_message_handler.rs index 3e9c80e46a..8e6bbde2f8 100644 --- a/editor/src/messages/dialog/dialog_message_handler.rs +++ b/editor/src/messages/dialog/dialog_message_handler.rs @@ -2,19 +2,21 @@ use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArt use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::prelude::*; +#[derive(ExtractField)] pub struct DialogMessageData<'a> { pub portfolio: &'a PortfolioMessageHandler, pub preferences: &'a PreferencesMessageHandler, } /// Stores the dialogs which require state. These are the ones that have their own message handlers, and are not the ones defined in `simple_dialogs`. -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, ExtractField)] pub struct DialogMessageHandler { export_dialog: ExportDialogMessageHandler, new_document_dialog: NewDocumentDialogMessageHandler, preferences_dialog: PreferencesDialogMessageHandler, } +#[message_handler_data] impl MessageHandler> for DialogMessageHandler { fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque, data: DialogMessageData) { let DialogMessageData { portfolio, preferences } = data; diff --git a/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs b/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs index 1bcf4b7003..179aab4f96 100644 --- a/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs +++ b/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs @@ -3,12 +3,13 @@ use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::prelude::*; +#[derive(ExtractField)] pub struct ExportDialogMessageData<'a> { pub portfolio: &'a PortfolioMessageHandler, } /// A dialog to allow users to customize their file export. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, ExtractField)] pub struct ExportDialogMessageHandler { pub file_type: FileType, pub scale_factor: f64, @@ -31,6 +32,7 @@ impl Default for ExportDialogMessageHandler { } } +#[message_handler_data] impl MessageHandler> for ExportDialogMessageHandler { fn process_message(&mut self, message: ExportDialogMessage, responses: &mut VecDeque, data: ExportDialogMessageData) { let ExportDialogMessageData { portfolio } = data; diff --git a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs index 2e94f876c7..539180117c 100644 --- a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs +++ b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs @@ -4,13 +4,14 @@ use glam::{IVec2, UVec2}; use graph_craft::document::NodeId; /// A dialog to allow users to set some initial options about a new document. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct NewDocumentDialogMessageHandler { pub name: String, pub infinite: bool, pub dimensions: UVec2, } +#[message_handler_data] impl MessageHandler for NewDocumentDialogMessageHandler { fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs b/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs index d4790a4d0e..882fc6db5c 100644 --- a/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs +++ b/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs @@ -1,17 +1,19 @@ use crate::consts::{VIEWPORT_ZOOM_WHEEL_RATE, VIEWPORT_ZOOM_WHEEL_RATE_CHANGE}; use crate::messages::layout::utility_types::widget_prelude::*; -use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle; +use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle; use crate::messages::preferences::SelectionMode; use crate::messages::prelude::*; +#[derive(ExtractField)] pub struct PreferencesDialogMessageData<'a> { pub preferences: &'a PreferencesMessageHandler, } /// A dialog to allow users to customize Graphite editor options -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct PreferencesDialogMessageHandler {} +#[message_handler_data] impl MessageHandler> for PreferencesDialogMessageHandler { fn process_message(&mut self, message: PreferencesDialogMessage, responses: &mut VecDeque, data: PreferencesDialogMessageData) { let PreferencesDialogMessageData { preferences } = data; diff --git a/editor/src/messages/frontend/frontend_message.rs b/editor/src/messages/frontend/frontend_message.rs index 7c9b90c341..c24ebc405c 100644 --- a/editor/src/messages/frontend/frontend_message.rs +++ b/editor/src/messages/frontend/frontend_message.rs @@ -1,9 +1,10 @@ use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon}; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::node_graph::utility_types::{ - BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, FrontendNodeWire, Transform, WirePath, + BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, Transform, }; use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer}; +use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate}; use crate::messages::prelude::*; use crate::messages::tool::utility_types::HintData; use graph_craft::document::NodeId; @@ -250,12 +251,16 @@ pub enum FrontendMessage { UpdateMouseCursor { cursor: MouseCursorIcon, }, - UpdateNodeGraph { + UpdateNodeGraphNodes { nodes: Vec, - wires: Vec, - #[serde(rename = "wiresDirectNotGridAligned")] - wires_direct_not_grid_aligned: bool, }, + UpdateVisibleNodes { + nodes: Vec, + }, + UpdateNodeGraphWires { + wires: Vec, + }, + ClearAllNodeGraphWires, UpdateNodeGraphControlBarLayout { #[serde(rename = "layoutTarget")] layout_target: LayoutTarget, diff --git a/editor/src/messages/globals/globals_message_handler.rs b/editor/src/messages/globals/globals_message_handler.rs index 5651555f60..9a72ffa6dc 100644 --- a/editor/src/messages/globals/globals_message_handler.rs +++ b/editor/src/messages/globals/globals_message_handler.rs @@ -1,8 +1,9 @@ use crate::messages::prelude::*; -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct GlobalsMessageHandler {} +#[message_handler_data] impl MessageHandler for GlobalsMessageHandler { fn process_message(&mut self, message: GlobalsMessage, _responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/input_mapper/input_mapper_message_handler.rs b/editor/src/messages/input_mapper/input_mapper_message_handler.rs index eba3867213..b26a1b4c40 100644 --- a/editor/src/messages/input_mapper/input_mapper_message_handler.rs +++ b/editor/src/messages/input_mapper/input_mapper_message_handler.rs @@ -6,16 +6,18 @@ use crate::messages::portfolio::utility_types::KeyboardPlatformLayout; use crate::messages::prelude::*; use std::fmt::Write; +#[derive(ExtractField)] pub struct InputMapperMessageData<'a> { pub input: &'a InputPreprocessorMessageHandler, pub actions: ActionList, } -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct InputMapperMessageHandler { mapping: Mapping, } +#[message_handler_data] impl MessageHandler> for InputMapperMessageHandler { fn process_message(&mut self, message: InputMapperMessage, responses: &mut VecDeque, data: InputMapperMessageData) { let InputMapperMessageData { input, actions } = data; diff --git a/editor/src/messages/input_mapper/input_mappings.rs b/editor/src/messages/input_mapper/input_mappings.rs index 6bb703cd0a..ad3f42a3d3 100644 --- a/editor/src/messages/input_mapper/input_mappings.rs +++ b/editor/src/messages/input_mapper/input_mappings.rs @@ -225,7 +225,7 @@ pub fn input_mappings() -> Mapping { entry!(KeyDown(Backspace); action_dispatch=PathToolMessage::Delete), entry!(KeyUp(MouseLeft); action_dispatch=PathToolMessage::DragStop { extend_selection: Shift, shrink_selection: Alt }), entry!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter { extend_selection: Shift, shrink_selection: Alt }), - entry!(DoubleClick(MouseButton::Left); action_dispatch=PathToolMessage::FlipSmoothSharp), + entry!(DoubleClick(MouseButton::Left); action_dispatch=PathToolMessage::DoubleClick { extend_selection: Shift, shrink_selection: Alt }), entry!(KeyDown(ArrowRight); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: 0. }), entry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }), entry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }), diff --git a/editor/src/messages/input_mapper/key_mapping/key_mapping_message_handler.rs b/editor/src/messages/input_mapper/key_mapping/key_mapping_message_handler.rs index 53c626bf54..81f249ec5e 100644 --- a/editor/src/messages/input_mapper/key_mapping/key_mapping_message_handler.rs +++ b/editor/src/messages/input_mapper/key_mapping/key_mapping_message_handler.rs @@ -2,16 +2,18 @@ use crate::messages::input_mapper::input_mapper_message_handler::InputMapperMess use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup; use crate::messages::prelude::*; +#[derive(ExtractField)] pub struct KeyMappingMessageData<'a> { pub input: &'a InputPreprocessorMessageHandler, pub actions: ActionList, } -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct KeyMappingMessageHandler { mapping_handler: InputMapperMessageHandler, } +#[message_handler_data] impl MessageHandler> for KeyMappingMessageHandler { fn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque, data: KeyMappingMessageData) { let KeyMappingMessageData { input, actions } = data; diff --git a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs index bf90e5881f..144652fdc5 100644 --- a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs +++ b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs @@ -6,11 +6,12 @@ use crate::messages::prelude::*; use glam::DVec2; use std::time::Duration; +#[derive(ExtractField)] pub struct InputPreprocessorMessageData { pub keyboard_platform: KeyboardPlatformLayout, } -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct InputPreprocessorMessageHandler { pub frame_time: FrameTimeInfo, pub time: u64, @@ -19,6 +20,7 @@ pub struct InputPreprocessorMessageHandler { pub viewport_bounds: ViewportBounds, } +#[message_handler_data] impl MessageHandler for InputPreprocessorMessageHandler { fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque, data: InputPreprocessorMessageData) { let InputPreprocessorMessageData { keyboard_platform } = data; diff --git a/editor/src/messages/layout/layout_message_handler.rs b/editor/src/messages/layout/layout_message_handler.rs index ee8e506ca8..7beec22fe9 100644 --- a/editor/src/messages/layout/layout_message_handler.rs +++ b/editor/src/messages/layout/layout_message_handler.rs @@ -6,7 +6,7 @@ use graphene_std::text::Font; use graphene_std::vector::style::{FillChoice, GradientStops}; use serde_json::Value; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct LayoutMessageHandler { layouts: [Layout; LayoutTarget::LayoutTargetLength as usize], } @@ -342,6 +342,15 @@ impl LayoutMessageHandler { } } +pub fn custom_data() -> MessageData { + // TODO: When is resolved and released, + // TODO: use to get + // TODO: the line number instead of hardcoding it to the magic number on the following line. + // TODO: Also, utilize the line number in the actual output, since it is currently unused. + MessageData::new(String::from("Function"), vec![(String::from("Fn(&MessageDiscriminant) -> Option"), 350)], file!()) +} + +#[message_handler_data(CustomData)] impl Option> MessageHandler for LayoutMessageHandler { fn process_message(&mut self, message: LayoutMessage, responses: &mut std::collections::VecDeque, action_input_mapping: F) { match message { diff --git a/editor/src/messages/layout/utility_types/widgets/input_widgets.rs b/editor/src/messages/layout/utility_types/widgets/input_widgets.rs index b76f23a067..893b301cd8 100644 --- a/editor/src/messages/layout/utility_types/widgets/input_widgets.rs +++ b/editor/src/messages/layout/utility_types/widgets/input_widgets.rs @@ -471,6 +471,8 @@ pub struct ReferencePointInput { pub disabled: bool, + pub tooltip: String, + // Callbacks #[serde(skip)] #[derivative(Debug = "ignore", PartialEq = "ignore")] diff --git a/editor/src/messages/message.rs b/editor/src/messages/message.rs index 80323d9309..fbe4170023 100644 --- a/editor/src/messages/message.rs +++ b/editor/src/messages/message.rs @@ -45,3 +45,86 @@ impl specta::Type for MessageDiscriminant { specta::DataType::Any } } + +#[cfg(test)] +mod test { + use super::*; + use std::io::Write; + + #[test] + fn generate_message_tree() { + let result = Message::build_message_tree(); + let mut file = std::fs::File::create("../hierarchical_message_system_tree.txt").unwrap(); + file.write_all(format!("{} `{}`\n", result.name(), result.path()).as_bytes()).unwrap(); + if let Some(variants) = result.variants() { + for (i, variant) in variants.iter().enumerate() { + let is_last = i == variants.len() - 1; + print_tree_node(variant, "", is_last, &mut file); + } + } + } + + fn print_tree_node(tree: &DebugMessageTree, prefix: &str, is_last: bool, file: &mut std::fs::File) { + // Print the current node + let (branch, child_prefix) = if tree.has_message_handler_data_fields() || tree.has_message_handler_fields() { + ("├── ", format!("{}│ ", prefix)) + } else { + if is_last { + ("└── ", format!("{} ", prefix)) + } else { + ("├── ", format!("{}│ ", prefix)) + } + }; + + if tree.path().is_empty() { + file.write_all(format!("{}{}{}\n", prefix, branch, tree.name()).as_bytes()).unwrap(); + } else { + file.write_all(format!("{}{}{} `{}`\n", prefix, branch, tree.name(), tree.path()).as_bytes()).unwrap(); + } + + // Print children if any + if let Some(variants) = tree.variants() { + let len = variants.len(); + for (i, variant) in variants.iter().enumerate() { + let is_last_child = i == len - 1; + print_tree_node(variant, &child_prefix, is_last_child, file); + } + } + + // Print handler field if any + if let Some(data) = tree.message_handler_fields() { + let len = data.fields().len(); + let (branch, child_prefix) = if tree.has_message_handler_data_fields() { + ("├── ", format!("{}│ ", prefix)) + } else { + ("└── ", format!("{} ", prefix)) + }; + if data.path().is_empty() { + file.write_all(format!("{}{}{}\n", prefix, branch, data.name()).as_bytes()).unwrap(); + } else { + file.write_all(format!("{}{}{} `{}`\n", prefix, branch, data.name(), data.path()).as_bytes()).unwrap(); + } + for (i, field) in data.fields().iter().enumerate() { + let is_last_field = i == len - 1; + let branch = if is_last_field { "└── " } else { "├── " }; + + file.write_all(format!("{}{}{}\n", child_prefix, branch, field.0).as_bytes()).unwrap(); + } + } + + // Print data field if any + if let Some(data) = tree.message_handler_data_fields() { + let len = data.fields().len(); + if data.path().is_empty() { + file.write_all(format!("{}{}{}\n", prefix, "└── ", data.name()).as_bytes()).unwrap(); + } else { + file.write_all(format!("{}{}{} `{}`\n", prefix, "└── ", data.name(), data.path()).as_bytes()).unwrap(); + } + for (i, field) in data.fields().iter().enumerate() { + let is_last_field = i == len - 1; + let branch = if is_last_field { "└── " } else { "├── " }; + file.write_all(format!("{}{}{}\n", format!("{} ", prefix), branch, field.0).as_bytes()).unwrap(); + } + } + } +} diff --git a/editor/src/messages/portfolio/document/document_message.rs b/editor/src/messages/portfolio/document/document_message.rs index 18ede64be8..ae3576d2a1 100644 --- a/editor/src/messages/portfolio/document/document_message.rs +++ b/editor/src/messages/portfolio/document/document_message.rs @@ -182,6 +182,7 @@ pub enum DocumentMessage { UpdateUpstreamTransforms { upstream_footprints: HashMap, local_transforms: HashMap, + first_instance_source_id: HashMap>, }, UpdateClickTargets { click_targets: HashMap>, diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 41938131cf..784c3ba3ef 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -38,6 +38,7 @@ use graphene_std::vector::click_target::{ClickTarget, ClickTargetType}; use graphene_std::vector::style::ViewMode; use std::time::Duration; +#[derive(ExtractField)] pub struct DocumentMessageData<'a> { pub document_id: DocumentId, pub ipp: &'a InputPreprocessorMessageHandler, @@ -48,7 +49,7 @@ pub struct DocumentMessageData<'a> { pub device_pixel_ratio: f64, } -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)] #[serde(default)] pub struct DocumentMessageHandler { // ====================== @@ -168,6 +169,7 @@ impl Default for DocumentMessageHandler { } } +#[message_handler_data] impl MessageHandler> for DocumentMessageHandler { fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque, data: DocumentMessageData) { let DocumentMessageData { @@ -444,6 +446,7 @@ impl MessageHandler> for DocumentMessag DocumentMessage::EnterNestedNetwork { node_id } => { self.breadcrumb_network_path.push(node_id); self.selection_network_path.clone_from(&self.breadcrumb_network_path); + responses.add(NodeGraphMessage::UnloadWires); responses.add(NodeGraphMessage::SendGraph); responses.add(DocumentMessage::ZoomCanvasToFitAll); responses.add(NodeGraphMessage::SetGridAlignedEdges); @@ -473,9 +476,10 @@ impl MessageHandler> for DocumentMessag self.breadcrumb_network_path.pop(); self.selection_network_path.clone_from(&self.breadcrumb_network_path); } + responses.add(NodeGraphMessage::UnloadWires); + responses.add(NodeGraphMessage::SendGraph); responses.add(DocumentMessage::PTZUpdate); responses.add(NodeGraphMessage::SetGridAlignedEdges); - responses.add(NodeGraphMessage::SendGraph); } DocumentMessage::FlipSelectedLayers { flip_axis } => { let scale = match flip_axis { @@ -525,6 +529,7 @@ impl MessageHandler> for DocumentMessag } } DocumentMessage::GraphViewOverlay { open } => { + let opened = !self.graph_view_overlay_open && open; self.graph_view_overlay_open = open; responses.add(FrontendMessage::UpdateGraphViewOverlay { open }); @@ -537,6 +542,9 @@ impl MessageHandler> for DocumentMessag responses.add(DocumentMessage::RenderRulers); responses.add(DocumentMessage::RenderScrollbars); + if opened { + responses.add(NodeGraphMessage::UnloadWires); + } if open { responses.add(ToolMessage::DeactivateTools); responses.add(OverlaysMessage::Draw); // Clear the overlays @@ -744,6 +752,7 @@ impl MessageHandler> for DocumentMessag // Nudge translation without resizing if !resize { let transform = DAffine2::from_translation(DVec2::from_angle(-self.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y))); + responses.add(SelectToolMessage::ShiftSelectedNodes { offset: transform.translation }); for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) { responses.add(GraphOperationMessage::TransformChange { @@ -1179,6 +1188,7 @@ impl MessageHandler> for DocumentMessag OverlaysType::HoverOutline => visibility_settings.hover_outline = visible, OverlaysType::SelectionOutline => visibility_settings.selection_outline = visible, OverlaysType::Pivot => visibility_settings.pivot = visible, + OverlaysType::Origin => visibility_settings.origin = visible, OverlaysType::Path => visibility_settings.path = visible, OverlaysType::Anchors => { visibility_settings.anchors = visible; @@ -1299,8 +1309,10 @@ impl MessageHandler> for DocumentMessag DocumentMessage::UpdateUpstreamTransforms { upstream_footprints, local_transforms, + first_instance_source_id, } => { self.network_interface.update_transforms(upstream_footprints, local_transforms); + self.network_interface.update_first_instance_source_id(first_instance_source_id); } DocumentMessage::UpdateClickTargets { click_targets } => { // TODO: Allow non layer nodes to have click targets @@ -1708,6 +1720,14 @@ impl DocumentMessageHandler { .reduce(graphene_std::renderer::Quad::combine_bounds) } + pub fn selected_visible_and_unlock_layers_bounding_box_document(&self) -> Option<[DVec2; 2]> { + self.network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&self.network_interface) + .map(|layer| self.metadata().nonzero_bounding_box(layer)) + .reduce(graphene_std::renderer::Quad::combine_bounds) + } + pub fn document_network(&self) -> &NodeNetwork { self.network_interface.document_network() } @@ -1878,6 +1898,7 @@ impl DocumentMessageHandler { responses.add(NodeGraphMessage::SelectedNodesUpdated); responses.add(NodeGraphMessage::ForceRunDocumentGraph); // TODO: Remove once the footprint is used to load the imports/export distances from the edge + responses.add(NodeGraphMessage::UnloadWires); responses.add(NodeGraphMessage::SetGridAlignedEdges); responses.add(Message::StartBuffer); Some(previous_network) @@ -1909,7 +1930,8 @@ impl DocumentMessageHandler { responses.add(PortfolioMessage::UpdateOpenDocumentsList); responses.add(NodeGraphMessage::SelectedNodesUpdated); responses.add(NodeGraphMessage::ForceRunDocumentGraph); - + responses.add(NodeGraphMessage::UnloadWires); + responses.add(NodeGraphMessage::SendWires); Some(previous_network) } @@ -2066,7 +2088,7 @@ impl DocumentMessageHandler { /// Loads all of the fonts in the document. pub fn load_layer_resources(&self, responses: &mut VecDeque) { let mut fonts = HashSet::new(); - for (_node_id, node) in self.document_network().recursive_nodes() { + for (_node_id, node, _) in self.document_network().recursive_nodes() { for input in &node.inputs { if let Some(TaggedValue::Font(font)) = input.as_value() { fonts.insert(font.clone()); @@ -2259,6 +2281,24 @@ impl DocumentMessageHandler { ] }, }, + LayoutGroup::Row { + widgets: { + let mut checkbox_id = CheckboxId::default(); + vec![ + CheckboxInput::new(self.overlays_visibility_settings.pivot) + .on_update(|optional_input: &CheckboxInput| { + DocumentMessage::SetOverlaysVisibility { + visible: optional_input.checked, + overlays_type: Some(OverlaysType::Origin), + } + .into() + }) + .for_label(checkbox_id.clone()) + .widget_holder(), + TextLabel::new("Transform Origin".to_string()).for_checkbox(&mut checkbox_id).widget_holder(), + ] + }, + }, LayoutGroup::Row { widgets: { let mut checkbox_id = CheckboxId::default(); diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs index abb1aa8594..31ea831218 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message.rs @@ -3,7 +3,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate; use crate::messages::prelude::*; use bezier_rs::Subpath; -use glam::{DAffine2, DVec2, IVec2}; +use glam::{DAffine2, IVec2}; use graph_craft::document::NodeId; use graphene_std::Artboard; use graphene_std::brush::brush_stroke::BrushStroke; @@ -52,10 +52,6 @@ pub enum GraphOperationMessage { transform_in: TransformIn, skip_rerender: bool, }, - TransformSetPivot { - layer: LayerNodeIdentifier, - pivot: DVec2, - }, Vector { layer: LayerNodeIdentifier, modification_type: VectorModificationType, diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs index 375ae5ef0f..931a15e3bf 100644 --- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs @@ -21,17 +21,19 @@ struct ArtboardInfo { merge_node: NodeId, } +#[derive(ExtractField)] pub struct GraphOperationMessageData<'a> { pub network_interface: &'a mut NodeNetworkInterface, pub collapsed: &'a mut CollapsedLayers, pub node_graph: &'a mut NodeGraphMessageHandler, } -#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize, ExtractField)] 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. +#[message_handler_data] impl MessageHandler> for GraphOperationMessageHandler { fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque, data: GraphOperationMessageData) { let network_interface = data.network_interface; @@ -89,15 +91,6 @@ impl MessageHandler> for Gr modify_inputs.transform_set(transform, transform_in, skip_rerender); } } - GraphOperationMessage::TransformSetPivot { layer, pivot } => { - if layer == LayerNodeIdentifier::ROOT_PARENT { - log::error!("Cannot run TransformSetPivot on ROOT_PARENT"); - return; - } - if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) { - modify_inputs.pivot_set(pivot); - } - } GraphOperationMessage::Vector { layer, modification_type } => { if layer == LayerNodeIdentifier::ROOT_PARENT { log::error!("Cannot run Vector on ROOT_PARENT"); diff --git a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs index 311e22aaa9..23a878f044 100644 --- a/editor/src/messages/portfolio/document/graph_operation/utility_types.rs +++ b/editor/src/messages/portfolio/document/graph_operation/utility_types.rs @@ -4,7 +4,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector}; use crate::messages::prelude::*; use bezier_rs::Subpath; -use glam::{DAffine2, DVec2, IVec2}; +use glam::{DAffine2, IVec2}; use graph_craft::concrete; use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput}; @@ -97,6 +97,8 @@ impl<'a> ModifyInputsContext<'a> { }; } + let layer_input_connector = post_node_input_connector.clone(); + // Sink post_node down to the end of the non layer chain that feeds into post_node, such that pre_node is the layer node at insert_index + 1, or None if insert_index is the last layer loop { let pre_node_output_connector = network_interface.upstream_output_connector(&post_node_input_connector, &[]); @@ -105,6 +107,11 @@ impl<'a> ModifyInputsContext<'a> { Some(OutputConnector::Node { node_id: pre_node_id, .. }) if !network_interface.is_layer(&pre_node_id, &[]) => { // Update post_node_input_connector for the next iteration post_node_input_connector = InputConnector::node(pre_node_id, 0); + // Insert directly under layer if moving to the end of a layer stack that ends with a non layer node that does not have an exposed primary input + let primary_is_exposed = network_interface.input_from_connector(&post_node_input_connector, &[]).is_some_and(|input| input.is_exposed()); + if !primary_is_exposed { + return layer_input_connector; + } } _ => break, // Break if pre_node_output_connector is None or if pre_node_id is a layer } @@ -451,12 +458,6 @@ impl<'a> ModifyInputsContext<'a> { } } - pub fn pivot_set(&mut self, new_pivot: DVec2) { - let Some(transform_node_id) = self.existing_node_id("Transform", true) else { return }; - - self.set_input_with_refresh(InputConnector::node(transform_node_id, 5), NodeInput::value(TaggedValue::DVec2(new_pivot), false), false); - } - pub fn vector_modify(&mut self, modification_type: VectorModificationType) { let Some(path_node_id) = self.existing_node_id("Path", true) else { return }; self.network_interface.vector_modify(&path_node_id, modification_type); diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs index 929c850160..0d732edd04 100644 --- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs +++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs @@ -13,6 +13,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo}; use glam::{DAffine2, DVec2}; use graph_craft::document::NodeId; +#[derive(ExtractField)] pub struct NavigationMessageData<'a> { pub network_interface: &'a mut NodeNetworkInterface, pub breadcrumb_network_path: &'a [NodeId], @@ -23,7 +24,7 @@ pub struct NavigationMessageData<'a> { pub preferences: &'a PreferencesMessageHandler, } -#[derive(Debug, Clone, PartialEq, Default)] +#[derive(Debug, Clone, PartialEq, Default, ExtractField)] pub struct NavigationMessageHandler { navigation_operation: NavigationOperation, mouse_position: ViewportPosition, @@ -31,6 +32,7 @@ pub struct NavigationMessageHandler { abortable_pan_start: Option, } +#[message_handler_data] impl MessageHandler> for NavigationMessageHandler { fn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque, data: NavigationMessageData) { let NavigationMessageData { diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs index af9b7b5c13..c88094b7e4 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs @@ -5,8 +5,8 @@ use super::node_properties::{self, ParameterWidgetsInfo}; use super::utility_types::FrontendNodeType; use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::utility_types::network_interface::{ - DocumentNodeMetadata, DocumentNodePersistentMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata, NumberInputSettings, - PropertiesRow, Vec2InputSettings, WidgetOverride, + DocumentNodeMetadata, DocumentNodePersistentMetadata, InputMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata, + NumberInputSettings, Vec2InputSettings, WidgetOverride, }; use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::prelude::Message; @@ -21,6 +21,7 @@ use graphene_std::extract_xy::XY; use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha}; use graphene_std::raster_types::{CPU, RasterDataTable}; use graphene_std::text::{Font, TypesettingConfig}; +#[allow(unused_imports)] use graphene_std::transform::Footprint; use graphene_std::vector::VectorDataTable; use graphene_std::*; @@ -37,26 +38,14 @@ pub struct NodePropertiesContext<'a> { impl NodePropertiesContext<'_> { pub fn call_widget_override(&mut self, node_id: &NodeId, index: usize) -> Option> { - let input_properties_row = self.network_interface.input_properties_row(node_id, index, self.selection_network_path)?; + let input_properties_row = self.network_interface.persistent_input_metadata(node_id, index, self.selection_network_path)?; if let Some(widget_override) = &input_properties_row.widget_override { let Some(widget_override_lambda) = INPUT_OVERRIDES.get(widget_override) else { log::error!("Could not get widget override '{widget_override}' lambda in call_widget_override"); return None; }; widget_override_lambda(*node_id, index, self) - .map(|layout_group| { - let Some(input_properties_row) = self.network_interface.input_properties_row(node_id, index, self.selection_network_path) else { - log::error!("Could not get input properties row in call_widget_override"); - return Vec::new(); - }; - match &input_properties_row.input_data.get("tooltip").and_then(|tooltip| tooltip.as_str()) { - Some(tooltip) => layout_group.into_iter().map(|widget| widget.with_tooltip(*tooltip)).collect::>(), - _ => layout_group, - } - }) - .map_err(|error| { - log::error!("Error in widget override lambda: {}", error); - }) + .map_err(|error| log::error!("Error in widget override lambda: {}", error)) .ok() } else { None @@ -100,12 +89,12 @@ fn static_nodes() -> Vec { category: "General", node_template: NodeTemplate { document_node: DocumentNode { - implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"), + implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), inputs: vec![NodeInput::value(TaggedValue::None, true)], ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![("In", "TODO").into()], + input_metadata: vec![("In", "TODO").into()], output_names: vec!["Out".to_string()], ..Default::default() }, @@ -119,14 +108,14 @@ fn static_nodes() -> Vec { category: "Debug", node_template: NodeTemplate { document_node: DocumentNode { - implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), + implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER), inputs: vec![NodeInput::value(TaggedValue::None, true)], manual_composition: Some(generic!(T)), skip_deduplication: true, ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![("In", "TODO").into()], + input_metadata: vec![("In", "TODO").into()], output_names: vec!["Out".to_string()], ..Default::default() }, @@ -160,19 +149,19 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { inputs: vec![NodeInput::network(generic!(T), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), + implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")), + implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")), + implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -223,7 +212,7 @@ fn static_nodes() -> Vec { }, ..Default::default() }), - input_properties: vec![("Data", "TODO").into()], + input_metadata: vec![("Data", "TODO").into()], output_names: vec!["Data".to_string()], ..Default::default() }, @@ -242,21 +231,21 @@ fn static_nodes() -> Vec { // Secondary (left) input type coercion DocumentNode { inputs: vec![NodeInput::network(generic!(T), 1)], - implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToElementNode"), + implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_element::IDENTIFIER), manual_composition: Some(concrete!(Context)), ..Default::default() }, // Primary (bottom) input type coercion DocumentNode { inputs: vec![NodeInput::network(generic!(T), 0)], - implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToGroupNode"), + implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_group::IDENTIFIER), manual_composition: Some(concrete!(Context)), ..Default::default() }, // The monitor node is used to display a thumbnail in the UI DocumentNode { inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), + implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER), manual_composition: Some(concrete!(Context)), skip_deduplication: true, ..Default::default() @@ -268,7 +257,7 @@ fn static_nodes() -> Vec { NodeInput::node(NodeId(2), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath), ], - implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::LayerNode"), + implementation: DocumentNodeImplementation::ProtoNode(graphic_element::layer::IDENTIFIER), ..Default::default() }, ] @@ -285,7 +274,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![("Graphical Data", "TODO").into(), ("Over", "TODO").into()], + input_metadata: vec![("Graphical Data", "TODO").into(), ("Over", "TODO").into()], output_names: vec!["Out".to_string()], node_type_metadata: NodeTypePersistentMetadata::layer(IVec2::new(0, 0)), network_metadata: Some(NodeNetworkMetadata { @@ -349,7 +338,7 @@ fn static_nodes() -> Vec { // Ensure this ID is kept in sync with the ID in set_alias so that the name input is kept in sync with the alias DocumentNode { manual_composition: Some(generic!(T)), - implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToArtboardNode"), + implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_artboard::IDENTIFIER), inputs: vec![ NodeInput::network(concrete!(TaggedValue), 1), NodeInput::value(TaggedValue::String(String::from("Artboard")), false), @@ -364,7 +353,7 @@ fn static_nodes() -> Vec { // TODO: Check if thumbnail is reversed DocumentNode { inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), + implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER), manual_composition: Some(generic!(T)), skip_deduplication: true, ..Default::default() @@ -376,7 +365,7 @@ fn static_nodes() -> Vec { NodeInput::node(NodeId(1), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath), ], - implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::AppendArtboardNode"), + implementation: DocumentNodeImplementation::ProtoNode(graphic_element::append_artboard::IDENTIFIER), ..Default::default() }, ] @@ -397,10 +386,10 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![ + input_metadata: vec![ ("Artboards", "TODO").into(), - PropertiesRow::with_override("Contents", "TODO", WidgetOverride::Hidden), - PropertiesRow::with_override( + InputMetadata::with_name_description_override("Contents", "TODO", WidgetOverride::Hidden), + InputMetadata::with_name_description_override( "Location", "TODO", WidgetOverride::Vec2(Vec2InputSettings { @@ -410,7 +399,7 @@ fn static_nodes() -> Vec { ..Default::default() }), ), - PropertiesRow::with_override( + InputMetadata::with_name_description_override( "Dimensions", "TODO", WidgetOverride::Vec2(Vec2InputSettings { @@ -420,7 +409,7 @@ fn static_nodes() -> Vec { ..Default::default() }), ), - PropertiesRow::with_override("Background", "TODO", WidgetOverride::Custom("artboard_background".to_string())), + InputMetadata::with_name_description_override("Background", "TODO", WidgetOverride::Custom("artboard_background".to_string())), ("Clip", "TODO").into(), ], output_names: vec!["Out".to_string()], @@ -478,13 +467,13 @@ fn static_nodes() -> Vec { DocumentNode { inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)], manual_composition: Some(concrete!(Context)), - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::LoadResourceNode")), + implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::load_resource::IDENTIFIER), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(0), 0)], manual_composition: Some(concrete!(Context)), - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::DecodeImageNode")), + implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::decode_image::IDENTIFIER), ..Default::default() }, ] @@ -498,7 +487,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![("Empty", "TODO").into(), ("URL", "TODO").into()], + input_metadata: vec![("Empty", "TODO").into(), ("URL", "TODO").into()], output_names: vec!["Image".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -534,6 +523,7 @@ fn static_nodes() -> Vec { description: Cow::Borrowed("Loads an image from a given URL"), properties: None, }, + #[cfg(feature = "gpu")] DocumentNodeDefinition { identifier: "Create Canvas", category: "Debug: GPU", @@ -544,14 +534,14 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { inputs: vec![NodeInput::scope("editor-api")], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), + implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER), skip_deduplication: true, ..Default::default() }, DocumentNode { manual_composition: Some(concrete!(Context)), inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), + implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), ..Default::default() }, ] @@ -599,99 +589,7 @@ fn static_nodes() -> Vec { description: Cow::Borrowed("Creates a new canvas object."), properties: None, }, - DocumentNodeDefinition { - identifier: "Draw Canvas", - category: "Debug: GPU", - node_template: NodeTemplate { - document_node: DocumentNode { - implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(3), 0)], - nodes: [ - DocumentNode { - inputs: vec![NodeInput::network(concrete!(RasterDataTable), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, RasterDataTable>")), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::scope("editor-api")], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), - skip_deduplication: true, - ..Default::default() - }, - DocumentNode { - manual_composition: Some(concrete!(Context)), - inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(2), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::DrawImageFrameNode")), - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }), - inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)], - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![("In", "TODO").into()], - output_names: vec!["Canvas".to_string()], - network_metadata: Some(NodeNetworkMetadata { - persistent_metadata: NodeNetworkPersistentMetadata { - node_metadata: [ - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Into".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Create Canvas".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 2)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Cache".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 2)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Draw Canvas".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)), - ..Default::default() - }, - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }, - ..Default::default() - }), - ..Default::default() - }, - }, - description: Cow::Borrowed("Draws raster data to a canvas element."), - properties: None, - }, + #[cfg(all(feature = "gpu", target_arch = "wasm32"))] DocumentNodeDefinition { identifier: "Rasterize", category: "Raster", @@ -702,20 +600,20 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { inputs: vec![NodeInput::scope("editor-api")], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), + implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER), manual_composition: Some(concrete!(Context)), skip_deduplication: true, ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), + implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), manual_composition: Some(concrete!(Context)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::network(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RasterizeNode")), + implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::rasterize::IDENTIFIER), manual_composition: Some(concrete!(Context)), ..Default::default() }, @@ -740,7 +638,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![("Artwork", "TODO").into(), ("Footprint", "TODO").into()], + input_metadata: vec![("Artwork", "TODO").into(), ("Footprint", "TODO").into()], output_names: vec!["Canvas".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -790,7 +688,7 @@ fn static_nodes() -> Vec { node_template: NodeTemplate { document_node: DocumentNode { manual_composition: Some(concrete!(Context)), - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::raster::NoisePatternNode")), + implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::std_nodes::noise_pattern::IDENTIFIER), inputs: vec![ NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::Bool(true), false), @@ -812,23 +710,23 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![ + input_metadata: vec![ ("Spacer", "TODO").into(), ("Clip", "TODO").into(), ("Seed", "TODO").into(), - PropertiesRow::with_override("Scale", "TODO", WidgetOverride::Custom("noise_properties_scale".to_string())), - PropertiesRow::with_override("Noise Type", "TODO", WidgetOverride::Custom("noise_properties_noise_type".to_string())), - PropertiesRow::with_override("Domain Warp Type", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_type".to_string())), - PropertiesRow::with_override("Domain Warp Amplitude", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_amplitude".to_string())), - PropertiesRow::with_override("Fractal Type", "TODO", WidgetOverride::Custom("noise_properties_fractal_type".to_string())), - PropertiesRow::with_override("Fractal Octaves", "TODO", WidgetOverride::Custom("noise_properties_fractal_octaves".to_string())), - PropertiesRow::with_override("Fractal Lacunarity", "TODO", WidgetOverride::Custom("noise_properties_fractal_lacunarity".to_string())), - PropertiesRow::with_override("Fractal Gain", "TODO", WidgetOverride::Custom("noise_properties_fractal_gain".to_string())), - PropertiesRow::with_override("Fractal Weighted Strength", "TODO", WidgetOverride::Custom("noise_properties_fractal_weighted_strength".to_string())), - PropertiesRow::with_override("Fractal Ping Pong Strength", "TODO", WidgetOverride::Custom("noise_properties_ping_pong_strength".to_string())), - PropertiesRow::with_override("Cellular Distance Function", "TODO", WidgetOverride::Custom("noise_properties_cellular_distance_function".to_string())), - PropertiesRow::with_override("Cellular Return Type", "TODO", WidgetOverride::Custom("noise_properties_cellular_return_type".to_string())), - PropertiesRow::with_override("Cellular Jitter", "TODO", WidgetOverride::Custom("noise_properties_cellular_jitter".to_string())), + InputMetadata::with_name_description_override("Scale", "TODO", WidgetOverride::Custom("noise_properties_scale".to_string())), + InputMetadata::with_name_description_override("Noise Type", "TODO", WidgetOverride::Custom("noise_properties_noise_type".to_string())), + InputMetadata::with_name_description_override("Domain Warp Type", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_type".to_string())), + InputMetadata::with_name_description_override("Domain Warp Amplitude", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_amplitude".to_string())), + InputMetadata::with_name_description_override("Fractal Type", "TODO", WidgetOverride::Custom("noise_properties_fractal_type".to_string())), + InputMetadata::with_name_description_override("Fractal Octaves", "TODO", WidgetOverride::Custom("noise_properties_fractal_octaves".to_string())), + InputMetadata::with_name_description_override("Fractal Lacunarity", "TODO", WidgetOverride::Custom("noise_properties_fractal_lacunarity".to_string())), + InputMetadata::with_name_description_override("Fractal Gain", "TODO", WidgetOverride::Custom("noise_properties_fractal_gain".to_string())), + InputMetadata::with_name_description_override("Fractal Weighted Strength", "TODO", WidgetOverride::Custom("noise_properties_fractal_weighted_strength".to_string())), + InputMetadata::with_name_description_override("Fractal Ping Pong Strength", "TODO", WidgetOverride::Custom("noise_properties_ping_pong_strength".to_string())), + InputMetadata::with_name_description_override("Cellular Distance Function", "TODO", WidgetOverride::Custom("noise_properties_cellular_distance_function".to_string())), + InputMetadata::with_name_description_override("Cellular Return Type", "TODO", WidgetOverride::Custom("noise_properties_cellular_return_type".to_string())), + InputMetadata::with_name_description_override("Cellular Jitter", "TODO", WidgetOverride::Custom("noise_properties_cellular_jitter".to_string())), ], output_names: vec!["Image".to_string()], ..Default::default() @@ -855,7 +753,7 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(RasterDataTable), 0), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false), ], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), + implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -864,7 +762,7 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(RasterDataTable), 0), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false), ], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), + implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -873,7 +771,7 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(RasterDataTable), 0), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false), ], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), + implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -882,7 +780,7 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(RasterDataTable), 0), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false), ], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), + implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -897,7 +795,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![("Image", "TODO").into()], + input_metadata: vec![("Image", "TODO").into()], output_names: vec!["Red".to_string(), "Green".to_string(), "Blue".to_string(), "Alpha".to_string()], has_primary_output: false, network_metadata: Some(NodeNetworkMetadata { @@ -960,13 +858,13 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { inputs: vec![NodeInput::network(concrete!(RasterDataTable), 0), NodeInput::value(TaggedValue::XY(XY::X), false)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::extract_xy::ExtractXyNode")), + implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::network(concrete!(RasterDataTable), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::extract_xy::ExtractXyNode")), + implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -982,7 +880,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![("Coordinate", "TODO").into()], + input_metadata: vec![("Coordinate", "TODO").into()], output_names: vec!["X".to_string(), "Y".to_string()], has_primary_output: false, network_metadata: Some(NodeNetworkMetadata { @@ -1036,7 +934,7 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(BrushCache), 2), ], manual_composition: Some(concrete!(Context)), - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::brush::BrushNode")), + implementation: DocumentNodeImplementation::ProtoNode(brush::brush::brush::IDENTIFIER), ..Default::default() }] .into_iter() @@ -1048,12 +946,12 @@ fn static_nodes() -> Vec { inputs: vec![ NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true), NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false), - NodeInput::value(TaggedValue::BrushCache(BrushCache::new_proto()), false), + NodeInput::value(TaggedValue::BrushCache(BrushCache::default()), false), ], ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![("Background", "TODO").into(), ("Trace", "TODO").into(), ("Cache", "TODO").into()], + input_metadata: vec![("Background", "TODO").into(), ("Trace", "TODO").into(), ("Cache", "TODO").into()], output_names: vec!["Image".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -1084,13 +982,13 @@ fn static_nodes() -> Vec { category: "Debug", node_template: NodeTemplate { document_node: DocumentNode { - implementation: DocumentNodeImplementation::proto("graphene_core::memo::MemoNode"), + implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)], manual_composition: Some(concrete!(Context)), ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![("Image", "TODO").into()], + input_metadata: vec![("Image", "TODO").into()], output_names: vec!["Image".to_string()], ..Default::default() }, @@ -1103,13 +1001,13 @@ fn static_nodes() -> Vec { category: "Debug", node_template: NodeTemplate { document_node: DocumentNode { - implementation: DocumentNodeImplementation::proto("graphene_core::memo::ImpureMemoNode"), + implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER), inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)], manual_composition: Some(concrete!(Context)), ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![("Image", "TODO").into()], + input_metadata: vec![("Image", "TODO").into()], output_names: vec!["Image".to_string()], ..Default::default() }, @@ -1117,164 +1015,6 @@ fn static_nodes() -> Vec { description: Cow::Borrowed("TODO"), properties: None, }, - DocumentNodeDefinition { - identifier: "Storage", - category: "Debug: GPU", - node_template: NodeTemplate { - document_node: DocumentNode { - implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(2), 0)], - nodes: [ - DocumentNode { - inputs: vec![NodeInput::scope("editor-api")], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode")), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::network(concrete!(Vec), 0), NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::StorageNode")), - ..Default::default() - }, - DocumentNode { - manual_composition: Some(concrete!(Context)), - inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }), - inputs: vec![NodeInput::value(TaggedValue::None, true)], - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![("In", "TODO").into()], - output_names: vec!["Storage".to_string()], - network_metadata: Some(NodeNetworkMetadata { - persistent_metadata: NodeNetworkPersistentMetadata { - node_metadata: [ - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Extract Executor".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Create Storage".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Cache".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)), - ..Default::default() - }, - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }, - ..Default::default() - }), - ..Default::default() - }, - }, - description: Cow::Borrowed("TODO"), - properties: None, - }, - DocumentNodeDefinition { - identifier: "Create Output Buffer", - category: "Debug: GPU", - node_template: NodeTemplate { - document_node: DocumentNode { - implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(2), 0)], - nodes: [ - DocumentNode { - inputs: vec![NodeInput::scope("editor-api")], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode")), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::network(concrete!(usize), 0), NodeInput::node(NodeId(0), 0), NodeInput::network(concrete!(Type), 1)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateOutputBufferNode")), - ..Default::default() - }, - DocumentNode { - manual_composition: Some(concrete!(Context)), - inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }), - inputs: vec![NodeInput::value(TaggedValue::None, true), NodeInput::value(TaggedValue::None, true)], - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![("In", "TODO").into(), ("In", "TODO").into()], - output_names: vec!["Output Buffer".to_string()], - network_metadata: Some(NodeNetworkMetadata { - persistent_metadata: NodeNetworkPersistentMetadata { - node_metadata: [ - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Extract Executor".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Create Output Buffer".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Cache".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)), - ..Default::default() - }, - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }, - ..Default::default() - }), - ..Default::default() - }, - }, - description: Cow::Borrowed("TODO"), - properties: None, - }, #[cfg(feature = "gpu")] DocumentNodeDefinition { identifier: "Create GPU Surface", @@ -1287,13 +1027,13 @@ fn static_nodes() -> Vec { DocumentNode { manual_composition: Some(concrete!(Context)), inputs: vec![NodeInput::scope("editor-api")], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")), + implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::create_gpu_surface::IDENTIFIER), ..Default::default() }, DocumentNode { manual_composition: Some(concrete!(Context)), inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode")), + implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER), ..Default::default() }, ] @@ -1341,87 +1081,6 @@ fn static_nodes() -> Vec { description: Cow::Borrowed("TODO"), properties: None, }, - #[cfg(feature = "gpu")] - DocumentNodeDefinition { - identifier: "Upload Texture", - category: "Debug: GPU", - node_template: NodeTemplate { - document_node: DocumentNode { - implementation: DocumentNodeImplementation::Network(NodeNetwork { - exports: vec![NodeInput::node(NodeId(2), 0)], - nodes: [ - DocumentNode { - inputs: vec![NodeInput::scope("editor-api")], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<&WgpuExecutor>")), - ..Default::default() - }, - DocumentNode { - inputs: vec![NodeInput::network(concrete!(RasterDataTable), 0), NodeInput::node(NodeId(0), 0)], - manual_composition: Some(generic!(T)), - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::UploadTextureNode")), - ..Default::default() - }, - DocumentNode { - manual_composition: Some(generic!(T)), - inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode")), - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }), - inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)], - ..Default::default() - }, - persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![("In", "TODO").into()], - output_names: vec!["Texture".to_string()], - network_metadata: Some(NodeNetworkMetadata { - persistent_metadata: NodeNetworkPersistentMetadata { - node_metadata: [ - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Extract Executor".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Upload Texture".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)), - ..Default::default() - }, - ..Default::default() - }, - DocumentNodeMetadata { - persistent_metadata: DocumentNodePersistentMetadata { - display_name: "Cache".to_string(), - node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)), - ..Default::default() - }, - ..Default::default() - }, - ] - .into_iter() - .enumerate() - .map(|(id, node)| (NodeId(id as u64), node)) - .collect(), - ..Default::default() - }, - ..Default::default() - }), - ..Default::default() - }, - }, - description: Cow::Borrowed("TODO"), - properties: None, - }, DocumentNodeDefinition { identifier: "Extract", category: "Debug", @@ -1432,7 +1091,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![("Node", "TODO").into()], + input_metadata: vec![("Node", "TODO").into()], output_names: vec!["Document Node".to_string()], ..Default::default() }, @@ -1478,15 +1137,19 @@ fn static_nodes() -> Vec { nodes: vec![ DocumentNode { inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)], - implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), + implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER), manual_composition: Some(generic!(T)), skip_deduplication: true, ..Default::default() }, DocumentNode { - inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::network(concrete!(graphene_std::vector::VectorModification), 1)], + inputs: vec![ + NodeInput::node(NodeId(0), 0), + NodeInput::network(concrete!(graphene_std::vector::VectorModification), 1), + NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath), + ], manual_composition: Some(generic!(T)), - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::vector_data::modification::PathModifyNode")), + implementation: DocumentNodeImplementation::ProtoNode(vector::path_modify::IDENTIFIER), ..Default::default() }, ] @@ -1503,7 +1166,7 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![("Vector Data", "TODO").into(), ("Modification", "TODO").into()], + input_metadata: vec![("Vector Data", "TODO").into(), ("Modification", "TODO").into()], output_names: vec!["Vector Data".to_string()], network_metadata: Some(NodeNetworkMetadata { persistent_metadata: NodeNetworkPersistentMetadata { @@ -1544,7 +1207,7 @@ fn static_nodes() -> Vec { category: "Text", node_template: NodeTemplate { document_node: DocumentNode { - implementation: DocumentNodeImplementation::proto("graphene_std::text::TextNode"), + implementation: DocumentNodeImplementation::ProtoNode(text::text::IDENTIFIER), manual_composition: Some(concrete!(Context)), inputs: vec![ NodeInput::scope("editor-api"), @@ -1563,11 +1226,11 @@ fn static_nodes() -> Vec { ..Default::default() }, persistent_node_metadata: DocumentNodePersistentMetadata { - input_properties: vec![ + input_metadata: vec![ ("Editor API", "TODO").into(), - PropertiesRow::with_override("Text", "TODO", WidgetOverride::Custom("text_area".to_string())), - PropertiesRow::with_override("Font", "TODO", WidgetOverride::Custom("text_font".to_string())), - PropertiesRow::with_override( + InputMetadata::with_name_description_override("Text", "TODO", WidgetOverride::Custom("text_area".to_string())), + InputMetadata::with_name_description_override("Font", "TODO", WidgetOverride::Custom("text_font".to_string())), + InputMetadata::with_name_description_override( "Size", "TODO", WidgetOverride::Number(NumberInputSettings { @@ -1576,7 +1239,7 @@ fn static_nodes() -> Vec { ..Default::default() }), ), - PropertiesRow::with_override( + InputMetadata::with_name_description_override( "Line Height", "TODO", WidgetOverride::Number(NumberInputSettings { @@ -1586,7 +1249,7 @@ fn static_nodes() -> Vec { ..Default::default() }), ), - PropertiesRow::with_override( + InputMetadata::with_name_description_override( "Character Spacing", "TODO", WidgetOverride::Number(NumberInputSettings { @@ -1596,7 +1259,7 @@ fn static_nodes() -> Vec { ..Default::default() }), ), - PropertiesRow::with_override( + InputMetadata::with_name_description_override( "Max Width", "TODO", WidgetOverride::Number(NumberInputSettings { @@ -1606,7 +1269,7 @@ fn static_nodes() -> Vec { ..Default::default() }), ), - PropertiesRow::with_override( + InputMetadata::with_name_description_override( "Max Height", "TODO", WidgetOverride::Number(NumberInputSettings { @@ -1616,7 +1279,7 @@ fn static_nodes() -> Vec { ..Default::default() }), ), - PropertiesRow::with_override( + InputMetadata::with_name_description_override( "Tilt", "Faux italic", WidgetOverride::Number(NumberInputSettings { @@ -1645,14 +1308,13 @@ fn static_nodes() -> Vec { NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false), NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false), - NodeInput::value(TaggedValue::DVec2(DVec2::splat(0.5)), false), ], implementation: DocumentNodeImplementation::Network(NodeNetwork { exports: vec![NodeInput::node(NodeId(1), 0)], nodes: [ DocumentNode { inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)], - implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), + implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER), manual_composition: Some(generic!(T)), skip_deduplication: true, ..Default::default() @@ -1664,10 +1326,9 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(f64), 2), NodeInput::network(concrete!(DVec2), 3), NodeInput::network(concrete!(DVec2), 4), - NodeInput::network(concrete!(DVec2), 5), ], manual_composition: Some(concrete!(Context)), - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::TransformNode")), + implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER), ..Default::default() }, ] @@ -1708,9 +1369,9 @@ fn static_nodes() -> Vec { }, ..Default::default() }), - input_properties: vec![ + input_metadata: vec![ ("Vector Data", "TODO").into(), - PropertiesRow::with_override( + InputMetadata::with_name_description_override( "Translation", "TODO", WidgetOverride::Vec2(Vec2InputSettings { @@ -1720,8 +1381,8 @@ fn static_nodes() -> Vec { ..Default::default() }), ), - PropertiesRow::with_override("Rotation", "TODO", WidgetOverride::Custom("transform_rotation".to_string())), - PropertiesRow::with_override( + InputMetadata::with_name_description_override("Rotation", "TODO", WidgetOverride::Custom("transform_rotation".to_string())), + InputMetadata::with_name_description_override( "Scale", "TODO", WidgetOverride::Vec2(Vec2InputSettings { @@ -1731,8 +1392,7 @@ fn static_nodes() -> Vec { ..Default::default() }), ), - PropertiesRow::with_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())), - PropertiesRow::with_override("Pivot", "TODO", WidgetOverride::Hidden), + InputMetadata::with_name_description_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())), ], output_names: vec!["Data".to_string()], ..Default::default() @@ -1751,25 +1411,25 @@ fn static_nodes() -> Vec { nodes: vec![ DocumentNode { inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_path_bool::BooleanOperationNode")), + implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), + implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")), + implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(2), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")), + implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -1831,7 +1491,7 @@ fn static_nodes() -> Vec { }, ..Default::default() }), - input_properties: vec![("Group of Paths", "TODO").into(), ("Operation", "TODO").into()], + input_metadata: vec![("Group of Paths", "TODO").into(), ("Operation", "TODO").into()], output_names: vec!["Vector".to_string()], ..Default::default() }, @@ -1849,7 +1509,7 @@ fn static_nodes() -> Vec { nodes: [ DocumentNode { inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SubpathSegmentLengthsNode")), + implementation: DocumentNodeImplementation::ProtoNode(vector::subpath_segment_lengths::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -1864,25 +1524,25 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(bool), 6), NodeInput::node(NodeId(0), 0), ], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SamplePolylineNode")), + implementation: DocumentNodeImplementation::ProtoNode(vector::sample_polyline::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), + implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(2), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")), + implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(3), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")), + implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -1957,10 +1617,10 @@ fn static_nodes() -> Vec { }, ..Default::default() }), - input_properties: vec![ + input_metadata: vec![ ("Vector Data", "The shape to be resampled and converted into a polyline.").into(), - Into::::into(("Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_SPACING)), - PropertiesRow::with_override( + ("Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_SPACING).into(), + InputMetadata::with_name_description_override( "Separation", node_properties::SAMPLE_POLYLINE_TOOLTIP_SEPARATION, WidgetOverride::Number(NumberInputSettings { @@ -1969,7 +1629,7 @@ fn static_nodes() -> Vec { ..Default::default() }), ), - PropertiesRow::with_override( + InputMetadata::with_name_description_override( "Quantity", node_properties::SAMPLE_POLYLINE_TOOLTIP_QUANTITY, WidgetOverride::Number(NumberInputSettings { @@ -1978,7 +1638,7 @@ fn static_nodes() -> Vec { ..Default::default() }), ), - PropertiesRow::with_override( + InputMetadata::with_name_description_override( "Start Offset", node_properties::SAMPLE_POLYLINE_TOOLTIP_START_OFFSET, WidgetOverride::Number(NumberInputSettings { @@ -1987,7 +1647,7 @@ fn static_nodes() -> Vec { ..Default::default() }), ), - PropertiesRow::with_override( + InputMetadata::with_name_description_override( "Stop Offset", node_properties::SAMPLE_POLYLINE_TOOLTIP_STOP_OFFSET, WidgetOverride::Number(NumberInputSettings { @@ -1996,7 +1656,7 @@ fn static_nodes() -> Vec { ..Default::default() }), ), - Into::::into(("Adaptive Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING)), + ("Adaptive Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING).into(), ], output_names: vec!["Vector".to_string()], ..Default::default() @@ -2020,24 +1680,24 @@ fn static_nodes() -> Vec { NodeInput::network(concrete!(u32), 2), ], manual_composition: Some(generic!(T)), - implementation: DocumentNodeImplementation::proto("graphene_core::vector::PoissonDiskPointsNode"), + implementation: DocumentNodeImplementation::ProtoNode(vector::poisson_disk_points::IDENTIFIER), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(0), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), + implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(1), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")), + implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, DocumentNode { inputs: vec![NodeInput::node(NodeId(2), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")), + implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER), manual_composition: Some(generic!(T)), ..Default::default() }, @@ -2100,9 +1760,9 @@ fn static_nodes() -> Vec { }, ..Default::default() }), - input_properties: vec![ + input_metadata: vec![ ("Vector Data", "TODO").into(), - PropertiesRow::with_override( + InputMetadata::with_name_description_override( "Separation Disk Diameter", "TODO", WidgetOverride::Number(NumberInputSettings { @@ -2113,7 +1773,7 @@ fn static_nodes() -> Vec { ..Default::default() }), ), - PropertiesRow::with_override( + InputMetadata::with_name_description_override( "Seed", "TODO", WidgetOverride::Number(NumberInputSettings { @@ -2156,7 +1816,7 @@ fn static_node_properties() -> NodeProperties { map.insert("sample_polyline_properties".to_string(), Box::new(node_properties::sample_polyline_properties)); map.insert( "identity_properties".to_string(), - Box::new(|_node_id, _context| node_properties::string_properties("The identity node simply passes its data through.")), + Box::new(|_node_id, _context| node_properties::string_properties("The identity node passes its data through.")), ); map.insert( "monitor_properties".to_string(), @@ -2176,7 +1836,7 @@ fn static_input_properties() -> InputProperties { map.insert( "string".to_string(), Box::new(|node_id, index, context| { - let Some(value) = context.network_interface.input_metadata(&node_id, index, "string_properties", context.selection_network_path) else { + let Some(value) = context.network_interface.input_data(&node_id, index, "string_properties", context.selection_network_path) else { return Err(format!("Could not get string properties for node {}", node_id)); }; let Some(string) = value.as_str() else { @@ -2188,37 +1848,36 @@ fn static_input_properties() -> InputProperties { map.insert( "number".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let mut number_input = NumberInput::default(); if let Some(unit) = context .network_interface - .input_metadata(&node_id, index, "unit", context.selection_network_path) + .input_data(&node_id, index, "unit", context.selection_network_path) .and_then(|value| value.as_str()) { number_input = number_input.unit(unit); } if let Some(min) = context .network_interface - .input_metadata(&node_id, index, "min", context.selection_network_path) + .input_data(&node_id, index, "min", context.selection_network_path) .and_then(|value| value.as_f64()) { number_input = number_input.min(min); } if let Some(max) = context .network_interface - .input_metadata(&node_id, index, "max", context.selection_network_path) + .input_data(&node_id, index, "max", context.selection_network_path) .and_then(|value| value.as_f64()) { number_input = number_input.max(max); } if let Some(step) = context .network_interface - .input_metadata(&node_id, index, "step", context.selection_network_path) + .input_data(&node_id, index, "step", context.selection_network_path) .and_then(|value| value.as_f64()) { number_input = number_input.step(step); } - if let Some(mode) = context.network_interface.input_metadata(&node_id, index, "mode", context.selection_network_path).map(|value| { + if let Some(mode) = context.network_interface.input_data(&node_id, index, "mode", context.selection_network_path).map(|value| { let mode: NumberInputMode = serde_json::from_value(value.clone()).unwrap(); mode }) { @@ -2226,87 +1885,83 @@ fn static_input_properties() -> InputProperties { } if let Some(range_min) = context .network_interface - .input_metadata(&node_id, index, "range_min", context.selection_network_path) + .input_data(&node_id, index, "range_min", context.selection_network_path) .and_then(|value| value.as_f64()) { number_input = number_input.range_min(Some(range_min)); } if let Some(range_max) = context .network_interface - .input_metadata(&node_id, index, "range_max", context.selection_network_path) + .input_data(&node_id, index, "range_max", context.selection_network_path) .and_then(|value| value.as_f64()) { number_input = number_input.range_max(Some(range_max)); } if let Some(is_integer) = context .network_interface - .input_metadata(&node_id, index, "is_integer", context.selection_network_path) + .input_data(&node_id, index, "is_integer", context.selection_network_path) .and_then(|value| value.as_bool()) { number_input = number_input.is_integer(is_integer); } let blank_assist = context .network_interface - .input_metadata(&node_id, index, "blank_assist", context.selection_network_path) + .input_data(&node_id, index, "blank_assist", context.selection_network_path) .and_then(|value| value.as_bool()) .unwrap_or_else(|| { log::error!("Could not get blank assist when displaying number input for node {node_id}, index {index}"); true }); + Ok(vec![LayoutGroup::Row { - widgets: node_properties::number_widget(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, blank_assist), number_input), + widgets: node_properties::number_widget(ParameterWidgetsInfo::new(node_id, index, blank_assist, context), number_input), }]) }), ); map.insert( "vec2".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let x = context .network_interface - .input_metadata(&node_id, index, "x", context.selection_network_path) + .input_data(&node_id, index, "x", context.selection_network_path) .and_then(|value| value.as_str()) .unwrap_or_else(|| { log::error!("Could not get x for vec2 input"); "" - }); + }) + .to_string(); let y = context .network_interface - .input_metadata(&node_id, index, "y", context.selection_network_path) + .input_data(&node_id, index, "y", context.selection_network_path) .and_then(|value| value.as_str()) .unwrap_or_else(|| { log::error!("Could not get y for vec2 input"); "" - }); + }) + .to_string(); let unit = context .network_interface - .input_metadata(&node_id, index, "unit", context.selection_network_path) + .input_data(&node_id, index, "unit", context.selection_network_path) .and_then(|value| value.as_str()) .unwrap_or_else(|| { log::error!("Could not get unit for vec2 input"); "" - }); + }) + .to_string(); let min = context .network_interface - .input_metadata(&node_id, index, "min", context.selection_network_path) + .input_data(&node_id, index, "min", context.selection_network_path) .and_then(|value| value.as_f64()); - Ok(vec![node_properties::coordinate_widget( - ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), - x, - y, - unit, - min, - )]) + Ok(vec![node_properties::coordinate_widget(ParameterWidgetsInfo::new(node_id, index, true, context), &x, &y, &unit, min)]) }), ); map.insert( "noise_properties_scale".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; let scale = node_properties::number_widget( - ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), + ParameterWidgetsInfo::new(node_id, index, true, context), NumberInput::default().min(0.).disabled(!coherent_noise_active), ); Ok(vec![scale.into()]) @@ -2315,20 +1970,16 @@ fn static_input_properties() -> InputProperties { map.insert( "noise_properties_noise_type".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; - let noise_type_row = enum_choice::() - .for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true)) - .property_row(); + let noise_type_row = enum_choice::().for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)).property_row(); Ok(vec![noise_type_row, LayoutGroup::Row { widgets: Vec::new() }]) }), ); map.insert( "noise_properties_domain_warp_type".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; let domain_warp_type = enum_choice::() - .for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true)) + .for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)) .disabled(!coherent_noise_active) .property_row(); Ok(vec![domain_warp_type]) @@ -2337,10 +1988,9 @@ fn static_input_properties() -> InputProperties { map.insert( "noise_properties_domain_warp_amplitude".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let (_, coherent_noise_active, _, _, domain_warp_active, _) = node_properties::query_noise_pattern_state(node_id, context)?; let domain_warp_amplitude = node_properties::number_widget( - ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), + ParameterWidgetsInfo::new(node_id, index, true, context), NumberInput::default().min(0.).disabled(!coherent_noise_active || !domain_warp_active), ); Ok(vec![domain_warp_amplitude.into(), LayoutGroup::Row { widgets: Vec::new() }]) @@ -2349,10 +1999,9 @@ fn static_input_properties() -> InputProperties { map.insert( "noise_properties_fractal_type".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; let fractal_type_row = enum_choice::() - .for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true)) + .for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)) .disabled(!coherent_noise_active) .property_row(); Ok(vec![fractal_type_row]) @@ -2361,10 +2010,9 @@ fn static_input_properties() -> InputProperties { map.insert( "noise_properties_fractal_octaves".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?; let fractal_octaves = node_properties::number_widget( - ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), + ParameterWidgetsInfo::new(node_id, index, true, context), NumberInput::default() .mode_range() .min(1.) @@ -2379,10 +2027,9 @@ fn static_input_properties() -> InputProperties { map.insert( "noise_properties_fractal_lacunarity".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?; let fractal_lacunarity = node_properties::number_widget( - ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), + ParameterWidgetsInfo::new(node_id, index, true, context), NumberInput::default() .mode_range() .min(0.) @@ -2395,10 +2042,9 @@ fn static_input_properties() -> InputProperties { map.insert( "noise_properties_fractal_gain".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?; let fractal_gain = node_properties::number_widget( - ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), + ParameterWidgetsInfo::new(node_id, index, true, context), NumberInput::default() .mode_range() .min(0.) @@ -2411,10 +2057,9 @@ fn static_input_properties() -> InputProperties { map.insert( "noise_properties_fractal_weighted_strength".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?; let fractal_weighted_strength = node_properties::number_widget( - ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), + ParameterWidgetsInfo::new(node_id, index, true, context), NumberInput::default() .mode_range() .min(0.) @@ -2427,10 +2072,9 @@ fn static_input_properties() -> InputProperties { map.insert( "noise_properties_ping_pong_strength".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let (fractal_active, coherent_noise_active, _, ping_pong_active, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?; let fractal_ping_pong_strength = node_properties::number_widget( - ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), + ParameterWidgetsInfo::new(node_id, index, true, context), NumberInput::default() .mode_range() .min(0.) @@ -2443,10 +2087,9 @@ fn static_input_properties() -> InputProperties { map.insert( "noise_properties_cellular_distance_function".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; let cellular_distance_function_row = enum_choice::() - .for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true)) + .for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)) .disabled(!coherent_noise_active || !cellular_noise_active) .property_row(); Ok(vec![cellular_distance_function_row]) @@ -2455,10 +2098,9 @@ fn static_input_properties() -> InputProperties { map.insert( "noise_properties_cellular_return_type".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; let cellular_return_type = enum_choice::() - .for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true)) + .for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)) .disabled(!coherent_noise_active || !cellular_noise_active) .property_row(); Ok(vec![cellular_return_type]) @@ -2467,10 +2109,9 @@ fn static_input_properties() -> InputProperties { map.insert( "noise_properties_cellular_jitter".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?; let cellular_jitter = node_properties::number_widget( - ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), + ParameterWidgetsInfo::new(node_id, index, true, context), NumberInput::default() .mode_range() .range_min(Some(0.)) @@ -2483,7 +2124,7 @@ fn static_input_properties() -> InputProperties { map.insert( "brightness".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; + let document_node = node_properties::get_document_node(node_id, context)?; let is_use_classic = document_node .inputs .iter() @@ -2494,7 +2135,7 @@ fn static_input_properties() -> InputProperties { .unwrap_or(false); let (b_min, b_max) = if is_use_classic { (-100., 100.) } else { (-100., 150.) }; let brightness = node_properties::number_widget( - ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), + ParameterWidgetsInfo::new(node_id, index, true, context), NumberInput::default().mode_range().range_min(Some(b_min)).range_max(Some(b_max)).unit("%").display_decimal_places(2), ); Ok(vec![brightness.into()]) @@ -2503,7 +2144,8 @@ fn static_input_properties() -> InputProperties { map.insert( "contrast".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; + let document_node = node_properties::get_document_node(node_id, context)?; + let is_use_classic = document_node .inputs .iter() @@ -2514,7 +2156,7 @@ fn static_input_properties() -> InputProperties { .unwrap_or(false); let (c_min, c_max) = if is_use_classic { (-100., 100.) } else { (-50., 100.) }; let contrast = node_properties::number_widget( - ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), + ParameterWidgetsInfo::new(node_id, index, true, context), NumberInput::default().mode_range().range_min(Some(c_min)).range_max(Some(c_max)).unit("%").display_decimal_places(2), ); Ok(vec![contrast.into()]) @@ -2523,21 +2165,16 @@ fn static_input_properties() -> InputProperties { map.insert( "assign_colors_gradient".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; - let gradient_row = node_properties::color_widget( - ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), - ColorInput::default().allow_none(false), - ); + let gradient_row = node_properties::color_widget(ParameterWidgetsInfo::new(node_id, index, true, context), ColorInput::default().allow_none(false)); Ok(vec![gradient_row]) }), ); map.insert( "assign_colors_seed".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let randomize_enabled = node_properties::query_assign_colors_randomize(node_id, context)?; let seed_row = node_properties::number_widget( - ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), + ParameterWidgetsInfo::new(node_id, index, true, context), NumberInput::default().min(0.).int().disabled(!randomize_enabled), ); Ok(vec![seed_row.into()]) @@ -2546,10 +2183,9 @@ fn static_input_properties() -> InputProperties { map.insert( "assign_colors_repeat_every".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; let randomize_enabled = node_properties::query_assign_colors_randomize(node_id, context)?; let repeat_every_row = node_properties::number_widget( - ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), + ParameterWidgetsInfo::new(node_id, index, true, context), NumberInput::default().min(0.).int().disabled(randomize_enabled), ); Ok(vec![repeat_every_row.into()]) @@ -2558,33 +2194,24 @@ fn static_input_properties() -> InputProperties { map.insert( "mask_stencil".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; - let mask = node_properties::color_widget(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), ColorInput::default()); + let mask = node_properties::color_widget(ParameterWidgetsInfo::new(node_id, index, true, context), ColorInput::default()); Ok(vec![mask]) }), ); map.insert( "spline_input".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; Ok(vec![LayoutGroup::Row { - widgets: node_properties::array_of_coordinates_widget( - ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), - TextInput::default().centered(true), - ), + widgets: node_properties::array_of_coordinates_widget(ParameterWidgetsInfo::new(node_id, index, true, context), TextInput::default().centered(true)), }]) }), ); map.insert( "transform_rotation".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; - - let mut widgets = node_properties::start_widgets( - ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), - super::utility_types::FrontendGraphDataType::Number, - ); + let mut widgets = node_properties::start_widgets(ParameterWidgetsInfo::new(node_id, index, true, context)); + let document_node = node_properties::get_document_node(node_id, context)?; let Some(input) = document_node.inputs.get(index) else { return Err("Input not found in transform rotation input override".to_string()); }; @@ -2613,13 +2240,9 @@ fn static_input_properties() -> InputProperties { map.insert( "transform_skew".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; - - let mut widgets = node_properties::start_widgets( - ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), - super::utility_types::FrontendGraphDataType::Number, - ); + let mut widgets = node_properties::start_widgets(ParameterWidgetsInfo::new(node_id, index, true, context)); + let document_node = node_properties::get_document_node(node_id, context)?; let Some(input) = document_node.inputs.get(index) else { return Err("Input not found in transform skew input override".to_string()); }; @@ -2661,17 +2284,15 @@ fn static_input_properties() -> InputProperties { map.insert( "text_area".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; Ok(vec![LayoutGroup::Row { - widgets: node_properties::text_area_widget(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true)), + widgets: node_properties::text_area_widget(ParameterWidgetsInfo::new(node_id, index, true, context)), }]) }), ); map.insert( "text_font".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; - let (font, style) = node_properties::font_inputs(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true)); + let (font, style) = node_properties::font_inputs(ParameterWidgetsInfo::new(node_id, index, true, context)); let mut result = vec![LayoutGroup::Row { widgets: font }]; if let Some(style) = style { result.push(LayoutGroup::Row { widgets: style }); @@ -2682,9 +2303,8 @@ fn static_input_properties() -> InputProperties { map.insert( "artboard_background".to_string(), Box::new(|node_id, index, context| { - let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?; Ok(vec![node_properties::color_widget( - ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), + ParameterWidgetsInfo::new(node_id, index, true, context), ColorInput::default().allow_none(false), )]) }), @@ -2698,11 +2318,11 @@ pub fn resolve_document_node_type(identifier: &str) -> Option<&DocumentNodeDefin pub fn collect_node_types() -> Vec { // Create a mapping from registry ID to document node identifier - let id_to_identifier_map: HashMap = DOCUMENT_NODE_TYPES + let id_to_identifier_map: HashMap = DOCUMENT_NODE_TYPES .iter() .filter_map(|definition| { - if let DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) = &definition.node_template.document_node.implementation { - Some((name.to_string(), definition.identifier)) + if let DocumentNodeImplementation::ProtoNode(name) = &definition.node_template.document_node.implementation { + Some((name.clone(), definition.identifier)) } else { None } @@ -2710,28 +2330,28 @@ pub fn collect_node_types() -> Vec { .collect(); let mut extracted_node_types = Vec::new(); - let node_registry = graphene_std::registry::NODE_REGISTRY.lock().unwrap(); - let node_metadata = graphene_std::registry::NODE_METADATA.lock().unwrap(); + let node_registry = registry::NODE_REGISTRY.lock().unwrap(); + let node_metadata = registry::NODE_METADATA.lock().unwrap(); for (id, metadata) in node_metadata.iter() { if let Some(implementations) = node_registry.get(id) { let identifier = match id_to_identifier_map.get(id) { - Some(&id) => id.to_string(), + Some(&id) => id, None => continue, }; // Extract category from metadata (already creates an owned String) - let category = metadata.category.unwrap_or_default().to_string(); + let category = metadata.category.unwrap_or_default(); // Extract input types (already creates owned Strings) let input_types = implementations .iter() - .flat_map(|(_, node_io)| node_io.inputs.iter().map(|ty| ty.clone().nested_type().to_string())) - .collect::>() + .flat_map(|(_, node_io)| node_io.inputs.iter().map(|ty| ty.nested_type().to_cow_string())) + .collect::>>() .into_iter() - .collect::>(); + .collect::>>(); // Create a FrontendNodeType - let node_type = FrontendNodeType::with_owned_strings_and_input_types(identifier, category, input_types); + let node_type = FrontendNodeType::with_input_types(identifier, category, input_types); // Store the created node_type extracted_node_types.push(node_type); @@ -2747,8 +2367,8 @@ pub fn collect_node_types() -> Vec { .document_node .inputs .iter() - .filter_map(|node_input| node_input.as_value().map(|node_value| node_value.ty().nested_type().to_string())) - .collect::>(); + .filter_map(|node_input| node_input.as_value().map(|node_value| node_value.ty().nested_type().to_cow_string())) + .collect::>>(); FrontendNodeType::with_input_types(definition.identifier, definition.category, input_types) }) @@ -2831,7 +2451,7 @@ impl DocumentNodeDefinition { log::error!("Path is not valid for network"); return; }; - nested_node_metadata.persistent_metadata.input_properties.resize_with(input_length, PropertiesRow::default); + nested_node_metadata.persistent_metadata.input_metadata.resize_with(input_length, InputMetadata::default); // Recurse over all sub-nodes if the current node is a network implementation let mut current_path = path.clone(); @@ -2850,7 +2470,7 @@ impl DocumentNodeDefinition { } else { // Base case let input_len = node_template.document_node.inputs.len(); - node_template.persistent_node_metadata.input_properties.resize_with(input_len, PropertiesRow::default); + node_template.persistent_node_metadata.input_metadata.resize_with(input_len, InputMetadata::default); if let DocumentNodeImplementation::Network(node_template_network) = &node_template.document_node.implementation { for sub_node_id in node_template_network.nodes.keys().cloned().collect::>() { populate_input_properties(node_template, vec![sub_node_id]); @@ -2862,6 +2482,10 @@ impl DocumentNodeDefinition { // Set the reference to the node definition template.persistent_node_metadata.reference = Some(self.identifier.to_string()); + // If the display name is empty and it is not a merge node, then set it to the reference + if template.persistent_node_metadata.display_name.is_empty() && self.identifier != "Merge" { + template.persistent_node_metadata.display_name = self.identifier.to_string(); + } template } diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs index 1339621dc4..85ce4b5162 100644 --- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs +++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions/document_node_derive.rs @@ -1,5 +1,5 @@ use super::DocumentNodeDefinition; -use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, NodeTemplate, PropertiesRow, WidgetOverride}; +use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, InputMetadata, NodeTemplate, WidgetOverride}; use graph_craft::ProtoNodeIdentifier; use graph_craft::document::*; use graphene_std::registry::*; @@ -21,7 +21,7 @@ pub(super) fn post_process_nodes(mut custom: Vec) -> Vec }; } - let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap(); + let node_registry = NODE_REGISTRY.lock().unwrap(); 'outer: for (id, metadata) in NODE_METADATA.lock().unwrap().iter() { for node in custom.iter() { let DocumentNodeDefinition { @@ -32,7 +32,7 @@ pub(super) fn post_process_nodes(mut custom: Vec) -> Vec .. } = node; match implementation { - DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) if name == id => continue 'outer, + DocumentNodeImplementation::ProtoNode(name) if name == id => continue 'outer, _ => (), } } @@ -67,13 +67,13 @@ pub(super) fn post_process_nodes(mut custom: Vec) -> Vec }, persistent_node_metadata: DocumentNodePersistentMetadata { // TODO: Store information for input overrides in the node macro - input_properties: fields + input_metadata: fields .iter() .map(|f| match f.widget_override { RegistryWidgetOverride::None => (f.name, f.description).into(), - RegistryWidgetOverride::Hidden => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Hidden), - RegistryWidgetOverride::String(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::String(str.to_string())), - RegistryWidgetOverride::Custom(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Custom(str.to_string())), + RegistryWidgetOverride::Hidden => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Hidden), + RegistryWidgetOverride::String(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::String(str.to_string())), + RegistryWidgetOverride::Custom(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Custom(str.to_string())), }) .collect(), output_names: vec![output_type.to_string()], diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs index d6f9a6bedf..a730f47cad 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs @@ -33,6 +33,7 @@ pub enum NodeGraphMessage { node_id: Option, node_type: String, xy: Option<(i32, i32)>, + add_transaction: bool, }, CreateWire { output_connector: OutputConnector, @@ -123,6 +124,9 @@ pub enum NodeGraphMessage { }, SendClickTargets, EndSendClickTargets, + UnloadWires, + SendWires, + UpdateVisibleNodes, SendGraph, SetGridAlignedEdges, SetInputValue { diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 49256220c8..828fb4a8ae 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -1,4 +1,4 @@ -use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeWire, WirePath}; +use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendGraphInput, FrontendGraphOutput, FrontendNode}; use super::{document_node_definitions, node_properties}; use crate::consts::GRID_SIZE; use crate::messages::input_mapper::utility_types::macros::action_keys; @@ -13,6 +13,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{ self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource, }; use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry}; +use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire}; use crate::messages::prelude::*; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode; @@ -26,7 +27,7 @@ use graphene_std::*; use renderer::Quad; use std::cmp::Ordering; -#[derive(Debug)] +#[derive(Debug, ExtractField)] pub struct NodeGraphHandlerData<'a> { pub network_interface: &'a mut NodeNetworkInterface, pub selection_network_path: &'a [NodeId], @@ -40,7 +41,7 @@ pub struct NodeGraphHandlerData<'a> { pub preferences: &'a PreferencesMessageHandler, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, ExtractField)] pub struct NodeGraphMessageHandler { // TODO: Remove network and move to NodeNetworkInterface pub network: Vec, @@ -67,6 +68,7 @@ pub struct NodeGraphMessageHandler { select_if_not_dragged: Option, /// The start of the dragged line (cannot be moved), stored in node graph coordinates pub wire_in_progress_from_connector: Option, + wire_in_progress_type: FrontendGraphDataType, /// The end point of the dragged line (cannot be moved), stored in node graph coordinates pub wire_in_progress_to_connector: Option, /// State for the context menu popups. @@ -77,15 +79,20 @@ pub struct NodeGraphMessageHandler { auto_panning: AutoPanning, /// The node to preview on mouse up if alt-clicked preview_on_mouse_up: Option, - // The index of the import that is being moved + /// The index of the import that is being moved reordering_import: Option, - // The index of the export that is being moved + /// The index of the export that is being moved reordering_export: Option, - // The end index of the moved port + /// The end index of the moved port end_index: Option, + /// Used to keep track of what nodes are sent to the front end so that only visible ones are sent to the frontend + frontend_nodes: Vec, + /// Used to keep track of what wires are sent to the front end so the old ones can be removed + frontend_wires: HashSet<(NodeId, usize)>, } /// NodeGraphMessageHandler always modifies the network which the selected nodes are in. No GraphOperationMessages should be added here, since those messages will always affect the document network. +#[message_handler_data] impl<'a> MessageHandler> for NodeGraphMessageHandler { fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque, data: NodeGraphHandlerData<'a>) { let NodeGraphHandlerData { @@ -175,7 +182,12 @@ impl<'a> MessageHandler> for NodeGrap responses.add(PropertiesPanelMessage::Refresh); responses.add(NodeGraphMessage::RunDocumentGraph); } - NodeGraphMessage::CreateNodeFromContextMenu { node_id, node_type, xy } => { + NodeGraphMessage::CreateNodeFromContextMenu { + node_id, + node_type, + xy, + add_transaction, + } => { let (x, y) = if let Some((x, y)) = xy { (x, y) } else if let Some(node_graph_ptz) = network_interface.node_graph_ptz(breadcrumb_network_path) { @@ -197,7 +209,10 @@ impl<'a> MessageHandler> for NodeGrap let node_template = document_node_type.default_node_template(); self.context_menu = None; - responses.add(DocumentMessage::AddTransaction); + if add_transaction { + responses.add(DocumentMessage::AddTransaction); + } + responses.add(NodeGraphMessage::InsertNode { node_id, node_template: node_template.clone(), @@ -220,13 +235,7 @@ impl<'a> MessageHandler> for NodeGrap }; // Ensure connection is to correct input of new node. If it does not have an input then do not connect - if let Some((input_index, _)) = node_template - .document_node - .inputs - .iter() - .enumerate() - .find(|(_, input)| input.is_exposed_to_frontend(selection_network_path.is_empty())) - { + if let Some((input_index, _)) = node_template.document_node.inputs.iter().enumerate().find(|(_, input)| input.is_exposed()) { responses.add(NodeGraphMessage::CreateWire { output_connector: *output_connector, input_connector: InputConnector::node(node_id, input_index), @@ -236,6 +245,7 @@ impl<'a> MessageHandler> for NodeGrap } self.wire_in_progress_from_connector = None; + self.wire_in_progress_type = FrontendGraphDataType::General; self.wire_in_progress_to_connector = None; } responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None }); @@ -367,9 +377,14 @@ impl<'a> MessageHandler> for NodeGrap responses.add(DocumentMessage::CommitTransaction); // Update the graph UI and re-render - responses.add(PropertiesPanelMessage::Refresh); - responses.add(NodeGraphMessage::SendGraph); - responses.add(NodeGraphMessage::RunDocumentGraph); + if graph_view_overlay_open { + responses.add(PropertiesPanelMessage::Refresh); + responses.add(NodeGraphMessage::SendGraph); + } else { + responses.add(DocumentMessage::GraphViewOverlay { open: true }); + responses.add(NavigationMessage::FitViewportToSelection); + responses.add(DocumentMessage::ZoomCanvasTo100Percent); + } } NodeGraphMessage::InsertNode { node_id, node_template } => { network_interface.insert_node(node_id, node_template, selection_network_path); @@ -629,6 +644,7 @@ impl<'a> MessageHandler> for NodeGrap // Abort dragging a wire if self.wire_in_progress_from_connector.is_some() { self.wire_in_progress_from_connector = None; + self.wire_in_progress_type = FrontendGraphDataType::General; self.wire_in_progress_to_connector = None; responses.add(DocumentMessage::AbortTransaction); responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None }); @@ -707,6 +723,7 @@ impl<'a> MessageHandler> for NodeGrap if self.context_menu.is_some() { self.context_menu = None; self.wire_in_progress_from_connector = None; + self.wire_in_progress_type = FrontendGraphDataType::General; self.wire_in_progress_to_connector = None; responses.add(FrontendMessage::UpdateContextMenuInformation { context_menu_information: self.context_menu.clone(), @@ -740,6 +757,7 @@ impl<'a> MessageHandler> for NodeGrap }; let Some(output_connector) = output_connector else { return }; self.wire_in_progress_from_connector = network_interface.output_position(&output_connector, selection_network_path); + self.wire_in_progress_type = FrontendGraphDataType::from_type(&network_interface.input_type(clicked_input, breadcrumb_network_path).0); return; } @@ -749,6 +767,15 @@ impl<'a> MessageHandler> for NodeGrap self.initial_disconnecting = false; self.wire_in_progress_from_connector = network_interface.output_position(&clicked_output, selection_network_path); + if let Some((output_type, source)) = clicked_output + .node_id() + .map(|node_id| network_interface.output_type(&node_id, clicked_output.index(), breadcrumb_network_path)) + { + self.wire_in_progress_type = FrontendGraphDataType::displayed_type(&output_type, &source); + } else { + self.wire_in_progress_type = FrontendGraphDataType::General; + } + self.update_node_graph_hints(responses); return; } @@ -895,9 +922,18 @@ impl<'a> MessageHandler> for NodeGrap false } }); + let vector_wire = build_vector_wire( + wire_in_progress_from_connector, + wire_in_progress_to_connector, + from_connector_is_layer, + to_connector_is_layer, + GraphWireStyle::Direct, + ); + let mut path_string = String::new(); + let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY); let wire_path = WirePath { - path_string: Self::build_wire_path_string(wire_in_progress_from_connector, wire_in_progress_to_connector, from_connector_is_layer, to_connector_is_layer), - data_type: FrontendGraphDataType::General, + path_string, + data_type: self.wire_in_progress_type, thick: false, dashed: false, }; @@ -941,7 +977,7 @@ impl<'a> MessageHandler> for NodeGrap self.update_node_graph_hints(responses); } else if self.reordering_import.is_some() { let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else { - log::error!("Could not get modify import export in PointerUp"); + log::error!("Could not get modify import export in PointerMove"); return; }; // Find the first import that is below the mouse position @@ -961,7 +997,7 @@ impl<'a> MessageHandler> for NodeGrap responses.add(FrontendMessage::UpdateImportReorderIndex { index: self.end_index }); } else if self.reordering_export.is_some() { let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else { - log::error!("Could not get modify import export in PointerUp"); + log::error!("Could not get modify import export in PointerMove"); return; }; // Find the first export that is below the mouse position @@ -1043,15 +1079,13 @@ impl<'a> MessageHandler> for NodeGrap // Get the compatible type from the output connector let compatible_type = output_connector.and_then(|output_connector| { output_connector.node_id().and_then(|node_id| { - let output_index = output_connector.index(); // Get the output types from the network interface - let output_types = network_interface.output_types(&node_id, selection_network_path); + let (output_type, type_source) = network_interface.output_type(&node_id, output_connector.index(), selection_network_path); - // Extract the type if available - output_types.get(output_index).and_then(|type_option| type_option.as_ref()).map(|(output_type, _)| { - // Create a search term based on the type - format!("type:{}", output_type.clone().nested_type()) - }) + match type_source { + TypeSource::RandomProtonodeImplementation | TypeSource::Error(_) => None, + _ => Some(format!("type:{}", output_type.nested_type())), + } }) }); let appear_right_of_mouse = if ipp.mouse.position.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. }; @@ -1117,107 +1151,56 @@ impl<'a> MessageHandler> for NodeGrap let has_primary_output_connection = network_interface .outward_wires(selection_network_path) .is_some_and(|outward_wires| outward_wires.get(&OutputConnector::node(selected_node_id, 0)).is_some_and(|outward_wires| !outward_wires.is_empty())); - let Some(network) = network_interface.nested_network(selection_network_path) else { - return; - }; - if let Some(selected_node) = network.nodes.get(&selected_node_id) { - // Check if any downstream node has any input that feeds into the primary export of the selected node - let primary_input_is_value = selected_node.inputs.first().is_some_and(|first_input| first_input.as_value().is_some()); - // Check that neither the primary input or output of the selected node are already connected. - if !has_primary_output_connection && primary_input_is_value { + if !has_primary_output_connection { + let Some(network) = network_interface.nested_network(selection_network_path) else { + return; + }; + let Some(selected_node) = network.nodes.get(&selected_node_id) else { + return; + }; + // Check that the first visible input is disconnected + let selected_node_input_connect_index = selected_node + .inputs + .iter() + .enumerate() + .find(|input| input.1.is_exposed()) + .filter(|input| input.1.as_value().is_some()) + .map(|input| input.0); + if let Some(selected_node_input_connect_index) = selected_node_input_connect_index { let Some(bounding_box) = network_interface.node_bounding_box(&selected_node_id, selection_network_path) else { log::error!("Could not get bounding box for node: {selected_node_id}"); return; }; - // TODO: Cache all wire locations if this is a performance issue - let overlapping_wires = Self::collect_wires(network_interface, selection_network_path) - .into_iter() - .filter(|frontend_wire| { - // Prevent inserting on a link that is connected upstream to the selected node - if network_interface - .upstream_flow_back_from_nodes(vec![selected_node_id], selection_network_path, network_interface::FlowType::UpstreamFlow) - .any(|upstream_id| { - frontend_wire.wire_end.node_id().is_some_and(|wire_end_id| wire_end_id == upstream_id) - || frontend_wire.wire_start.node_id().is_some_and(|wire_start_id| wire_start_id == upstream_id) - }) { - return false; - } + let mut wires_to_check = network_interface.node_graph_input_connectors(selection_network_path).into_iter().collect::>(); + // Prevent inserting on a link that is connected upstream to the selected node + for upstream_node in network_interface.upstream_flow_back_from_nodes(vec![selected_node_id], selection_network_path, network_interface::FlowType::UpstreamFlow) { + for input_index in 0..network_interface.number_of_inputs(&upstream_node, selection_network_path) { + wires_to_check.remove(&InputConnector::node(upstream_node, input_index)); + } + } + let overlapping_wires = wires_to_check + .into_iter() + .filter_map(|input| { // Prevent inserting a layer into a chain if network_interface.is_layer(&selected_node_id, selection_network_path) - && frontend_wire - .wire_start - .node_id() - .is_some_and(|wire_start_id| network_interface.is_chain(&wire_start_id, selection_network_path)) + && input.node_id().is_some_and(|input_node_id| network_interface.is_chain(&input_node_id, selection_network_path)) { - return false; + return None; } - - let Some(input_position) = network_interface.input_position(&frontend_wire.wire_end, selection_network_path) else { - log::error!("Could not get input port position for {:?}", frontend_wire.wire_end); - return false; - }; - - let Some(output_position) = network_interface.output_position(&frontend_wire.wire_start, selection_network_path) else { - log::error!("Could not get output port position for {:?}", frontend_wire.wire_start); - return false; - }; - - let start_node_is_layer = frontend_wire - .wire_end - .node_id() - .is_some_and(|wire_start_id| network_interface.is_layer(&wire_start_id, selection_network_path)); - let end_node_is_layer = frontend_wire - .wire_end - .node_id() - .is_some_and(|wire_end_id| network_interface.is_layer(&wire_end_id, selection_network_path)); - - let locations = Self::build_wire_path_locations(output_position, input_position, start_node_is_layer, end_node_is_layer); - let bezier = bezier_rs::Bezier::from_cubic_dvec2( - (locations[0].x, locations[0].y).into(), - (locations[1].x, locations[1].y).into(), - (locations[2].x, locations[2].y).into(), - (locations[3].x, locations[3].y).into(), - ); - - !bezier.rectangle_intersections(bounding_box[0], bounding_box[1]).is_empty() || bezier.is_contained_within(bounding_box[0], bounding_box[1]) - }) - .collect::>() - .into_iter() - .filter_map(|mut wire| { - if let Some(end_node_id) = wire.wire_end.node_id() { - let Some(actual_index_from_exposed) = (0..network_interface.number_of_inputs(&end_node_id, selection_network_path)) - .filter(|&input_index| { - network_interface - .input_from_connector(&InputConnector::Node { node_id: end_node_id, input_index }, selection_network_path) - .is_some_and(|input| input.is_exposed_to_frontend(selection_network_path.is_empty())) - }) - .nth(wire.wire_end.input_index()) - else { - log::error!("Could not get exposed input index for {:?}", wire.wire_end); - return None; - }; - wire.wire_end = InputConnector::Node { - node_id: end_node_id, - input_index: actual_index_from_exposed, - }; - } - Some(wire) + let (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?; + wire.rectangle_intersections_exist(bounding_box[0], bounding_box[1]).then_some((input, is_stack)) }) .collect::>(); - - let is_stack_wire = |wire: &FrontendNodeWire| match (wire.wire_start.node_id(), wire.wire_end.node_id(), wire.wire_end.input_index()) { - (Some(start_id), Some(end_id), input_index) => { - input_index == 0 && network_interface.is_layer(&start_id, selection_network_path) && network_interface.is_layer(&end_id, selection_network_path) - } - _ => false, - }; - // Prioritize vertical thick lines and cancel if there are multiple potential wires let mut node_wires = Vec::new(); let mut stack_wires = Vec::new(); - for wire in overlapping_wires { - if is_stack_wire(&wire) { stack_wires.push(wire) } else { node_wires.push(wire) } + for (overlapping_wire_input, is_stack) in overlapping_wires { + if is_stack { + stack_wires.push(overlapping_wire_input) + } else { + node_wires.push(overlapping_wire_input) + } } let overlapping_wire = if network_interface.is_layer(&selected_node_id, selection_network_path) { @@ -1234,29 +1217,13 @@ impl<'a> MessageHandler> for NodeGrap None }; if let Some(overlapping_wire) = overlapping_wire { - let Some(network) = network_interface.nested_network(selection_network_path) else { - return; - }; - // Ensure connection is to first visible input of selected node. If it does not have an input then do not connect - if let Some((selected_node_input_index, _)) = network - .nodes - .get(&selected_node_id) - .unwrap() - .inputs - .iter() - .enumerate() - .find(|(_, input)| input.is_exposed_to_frontend(selection_network_path.is_empty())) - { - responses.add(NodeGraphMessage::InsertNodeBetween { - node_id: selected_node_id, - input_connector: overlapping_wire.wire_end, - insert_node_input_index: selected_node_input_index, - }); - - responses.add(NodeGraphMessage::RunDocumentGraph); - - responses.add(NodeGraphMessage::SendGraph); - } + responses.add(NodeGraphMessage::InsertNodeBetween { + node_id: selected_node_id, + input_connector: *overlapping_wire, + insert_node_input_index: selected_node_input_connect_index, + }); + responses.add(NodeGraphMessage::RunDocumentGraph); + responses.add(NodeGraphMessage::SendGraph); } } } @@ -1283,6 +1250,7 @@ impl<'a> MessageHandler> for NodeGrap self.begin_dragging = false; self.box_selection_start = None; self.wire_in_progress_from_connector = None; + self.wire_in_progress_type = FrontendGraphDataType::General; self.wire_in_progress_to_connector = None; self.reordering_export = None; self.reordering_import = None; @@ -1357,23 +1325,52 @@ impl<'a> MessageHandler> for NodeGrap click_targets: Some(network_interface.collect_frontend_click_targets(breadcrumb_network_path)), }), NodeGraphMessage::EndSendClickTargets => responses.add(FrontendMessage::UpdateClickTargets { click_targets: None }), + NodeGraphMessage::UnloadWires => { + for input in network_interface.node_graph_input_connectors(breadcrumb_network_path) { + network_interface.unload_wire(&input, breadcrumb_network_path); + } + + responses.add(FrontendMessage::ClearAllNodeGraphWires); + } + NodeGraphMessage::SendWires => { + let wires = self.collect_wires(network_interface, preferences.graph_wire_style, breadcrumb_network_path); + responses.add(FrontendMessage::UpdateNodeGraphWires { wires }); + } + NodeGraphMessage::UpdateVisibleNodes => { + let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else { + return; + }; + + let viewport_bbox = ipp.document_bounds(); + let document_bbox: [DVec2; 2] = viewport_bbox.map(|p| network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(p)); + + let mut nodes = Vec::new(); + for node_id in &self.frontend_nodes { + let Some(node_bbox) = network_interface.node_bounding_box(node_id, breadcrumb_network_path) else { + log::error!("Could not get bbox for node: {:?}", node_id); + continue; + }; + + if node_bbox[1].x >= document_bbox[0].x && node_bbox[0].x <= document_bbox[1].x && node_bbox[1].y >= document_bbox[0].y && node_bbox[0].y <= document_bbox[1].y { + nodes.push(*node_id); + } + } + + responses.add(FrontendMessage::UpdateVisibleNodes { nodes }); + } NodeGraphMessage::SendGraph => { responses.add(NodeGraphMessage::UpdateLayerPanel); responses.add(DocumentMessage::DocumentStructureChanged); responses.add(PropertiesPanelMessage::Refresh); if breadcrumb_network_path == selection_network_path && graph_view_overlay_open { - // TODO: Implement culling of nodes and wires whose bounding boxes are outside of the viewport - let wires = Self::collect_wires(network_interface, breadcrumb_network_path); let nodes = self.collect_nodes(network_interface, breadcrumb_network_path); + self.frontend_nodes = nodes.iter().map(|node| node.id).collect(); + responses.add(FrontendMessage::UpdateNodeGraphNodes { nodes }); + responses.add(NodeGraphMessage::UpdateVisibleNodes); + let (layer_widths, chain_widths, has_left_input_wire) = network_interface.collect_layer_widths(breadcrumb_network_path); - let wires_direct_not_grid_aligned = preferences.graph_wire_style.is_direct(); responses.add(NodeGraphMessage::UpdateImportsExports); - responses.add(FrontendMessage::UpdateNodeGraph { - nodes, - wires, - wires_direct_not_grid_aligned, - }); responses.add(FrontendMessage::UpdateLayerWidths { layer_widths, chain_widths, @@ -1455,6 +1452,8 @@ impl<'a> MessageHandler> for NodeGrap Ordering::Equal => {} } } + + responses.add(NodeGraphMessage::SendWires); } NodeGraphMessage::ToggleSelectedAsLayersOrNodes => { let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else { @@ -1474,6 +1473,8 @@ impl<'a> MessageHandler> for NodeGrap } NodeGraphMessage::ShiftNodePosition { node_id, x, y } => { network_interface.shift_absolute_node_position(&node_id, IVec2::new(x, y), selection_network_path); + + responses.add(NodeGraphMessage::SendWires); } NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer } => { if is_layer && !network_interface.is_eligible_to_be_layer(&node_id, selection_network_path) { @@ -1487,6 +1488,7 @@ impl<'a> MessageHandler> for NodeGrap }); responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::SendGraph); + responses.add(NodeGraphMessage::SendWires); } NodeGraphMessage::SetDisplayName { node_id, @@ -1623,7 +1625,7 @@ impl<'a> MessageHandler> for NodeGrap // } let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else { - log::error!("Could not get network metadata in PointerMove"); + log::error!("Could not get network metadata in UpdateBoxSelection"); return; }; @@ -1689,7 +1691,8 @@ impl<'a> MessageHandler> for NodeGrap ) .into_iter() .next(); - + responses.add(NodeGraphMessage::UpdateVisibleNodes); + responses.add(NodeGraphMessage::SendWires); responses.add(FrontendMessage::UpdateImportsExports { imports, exports, @@ -1835,6 +1838,7 @@ impl NodeGraphMessageHandler { node_id: Some(node_id), node_type: node_type.clone(), xy: None, + add_transaction: true, } .into(), NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(), @@ -2151,69 +2155,39 @@ impl NodeGraphMessageHandler { } } - fn collect_wires(network_interface: &NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec { - let Some(network) = network_interface.nested_network(breadcrumb_network_path) else { - log::error!("Could not get network when collecting wires"); - return Vec::new(); - }; - let mut wires = network - .nodes + fn collect_wires(&mut self, network_interface: &mut NodeNetworkInterface, graph_wire_style: GraphWireStyle, breadcrumb_network_path: &[NodeId]) -> Vec { + let mut added_wires = network_interface + .node_graph_input_connectors(breadcrumb_network_path) .iter() - .flat_map(|(wire_end, node)| node.inputs.iter().filter(|input| input.is_exposed()).enumerate().map(move |(index, input)| (input, wire_end, index))) - .filter_map(|(input, &wire_end, wire_end_input_index)| { - match *input { - NodeInput::Node { - node_id: wire_start, - output_index: wire_start_output_index, - // TODO: add ui for lambdas - lambda: _, - } => Some(FrontendNodeWire { - wire_start: OutputConnector::node(wire_start, wire_start_output_index), - wire_end: InputConnector::node(wire_end, wire_end_input_index), - dashed: false, - }), - NodeInput::Network { import_index, .. } => Some(FrontendNodeWire { - wire_start: OutputConnector::Import(import_index), - wire_end: InputConnector::node(wire_end, wire_end_input_index), - dashed: false, - }), - _ => None, - } - }) + .filter_map(|connector| network_interface.newly_loaded_input_wire(connector, graph_wire_style, breadcrumb_network_path)) .collect::>(); - // Connect primary export to root node, since previewing a node will change the primary export - if let Some(root_node) = network_interface.root_node(breadcrumb_network_path) { - wires.push(FrontendNodeWire { - wire_start: OutputConnector::node(root_node.node_id, root_node.output_index), - wire_end: InputConnector::Export(0), - dashed: false, - }); + let changed_wire_inputs = added_wires.iter().map(|update| (update.id, update.input_index)).collect::>(); + self.frontend_wires.extend(changed_wire_inputs); + + let mut orphaned_wire_inputs = self.frontend_wires.clone(); + self.frontend_wires = network_interface + .node_graph_wire_inputs(breadcrumb_network_path) + .iter() + .filter_map(|visible_wire_input| orphaned_wire_inputs.take(visible_wire_input)) + .collect::>(); + added_wires.extend(orphaned_wire_inputs.into_iter().map(|(id, input_index)| WirePathUpdate { + id, + input_index, + wire_path_update: None, + })); + + if let Some(wire_to_root) = network_interface.wire_to_root(graph_wire_style, breadcrumb_network_path) { + added_wires.push(wire_to_root); + } else { + added_wires.push(WirePathUpdate { + id: NodeId(u64::MAX), + input_index: usize::MAX, + wire_path_update: None, + }) } - // Connect rest of exports to their actual export field since they are not affected by previewing. Only connect the primary export if it is dashed - for (i, export) in network.exports.iter().enumerate() { - let dashed = matches!(network_interface.previewing(breadcrumb_network_path), Previewing::Yes { .. }) && i == 0; - if dashed || i != 0 { - if let NodeInput::Node { node_id, output_index, .. } = export { - wires.push(FrontendNodeWire { - wire_start: OutputConnector::Node { - node_id: *node_id, - output_index: *output_index, - }, - wire_end: InputConnector::Export(i), - dashed, - }); - } else if let NodeInput::Network { import_index, .. } = *export { - wires.push(FrontendNodeWire { - wire_start: OutputConnector::Import(import_index), - wire_end: InputConnector::Export(i), - dashed, - }) - } - } - } - wires + added_wires } fn collect_nodes(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec { @@ -2237,6 +2211,7 @@ impl NodeGraphMessageHandler { log::error!("Could not get position for node {node_id}"); } } + let mut frontend_inputs_lookup = frontend_inputs_lookup(breadcrumb_network_path, network_interface); let Some(network) = network_interface.nested_network(breadcrumb_network_path) else { log::error!("Could not get nested network when collecting nodes"); @@ -2252,13 +2227,14 @@ impl NodeGraphMessageHandler { let node_id_path = [breadcrumb_network_path, (&[node_id])].concat(); let inputs = frontend_inputs_lookup.remove(&node_id).unwrap_or_default(); + let mut inputs = inputs.into_iter().map(|input| { input.map(|input| FrontendGraphInput { data_type: FrontendGraphDataType::displayed_type(&input.ty, &input.type_source), - resolved_type: Some(format!("{:?}", &input.ty)), + resolved_type: format!("{:?}", &input.ty), valid_types: input.valid_types.iter().map(|ty| ty.to_string()).collect(), - name: input.input_name.unwrap_or_else(|| input.ty.nested_type().to_string()), - description: input.input_description.unwrap_or_default(), + name: input.input_name, + description: input.input_description, connected_to: input.output_connector, }) }); @@ -2266,20 +2242,16 @@ impl NodeGraphMessageHandler { let primary_input = inputs.next().flatten(); let exposed_inputs = inputs.flatten().collect(); - let output_types = network_interface.output_types(&node_id, breadcrumb_network_path); - let primary_output_type = output_types.first().cloned().flatten(); - let frontend_data_type = if let Some((output_type, type_source)) = &primary_output_type { - FrontendGraphDataType::displayed_type(output_type, type_source) - } else { - FrontendGraphDataType::General - }; + let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path); + let frontend_data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source); + let connected_to = outward_wires.get(&OutputConnector::node(node_id, 0)).cloned().unwrap_or_default(); - let primary_output = if network_interface.has_primary_output(&node_id, breadcrumb_network_path) && !output_types.is_empty() { + let primary_output = if network_interface.has_primary_output(&node_id, breadcrumb_network_path) { Some(FrontendGraphOutput { data_type: frontend_data_type, name: "Output 1".to_string(), description: String::new(), - resolved_type: primary_output_type.map(|(input, _)| format!("{input:?}")), + resolved_type: format!("{:?}", output_type), connected_to, }) } else { @@ -2287,15 +2259,13 @@ impl NodeGraphMessageHandler { }; let mut exposed_outputs = Vec::new(); - for (index, exposed_output) in output_types.iter().enumerate() { - if index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) { + for output_index in 0..network_interface.number_of_outputs(&node_id, breadcrumb_network_path) { + if output_index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) { continue; } - let frontend_data_type = if let Some((output_type, type_source)) = &exposed_output { - FrontendGraphDataType::displayed_type(output_type, type_source) - } else { - FrontendGraphDataType::General - }; + let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path); + let data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source); + let Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get(&node_id) else { log::error!("Could not get node_metadata when getting output for {node_id}"); continue; @@ -2303,17 +2273,17 @@ impl NodeGraphMessageHandler { let output_name = node_metadata .persistent_metadata .output_names - .get(index) - .map(|output_name| output_name.to_string()) + .get(output_index) + .cloned() .filter(|output_name| !output_name.is_empty()) - .unwrap_or_else(|| exposed_output.clone().map(|(output_type, _)| output_type.nested_type().to_string()).unwrap_or_default()); + .unwrap_or_else(|| output_type.nested_type().to_string()); - let connected_to = outward_wires.get(&OutputConnector::node(node_id, index)).cloned().unwrap_or_default(); + let connected_to = outward_wires.get(&OutputConnector::node(node_id, output_index)).cloned().unwrap_or_default(); exposed_outputs.push(FrontendGraphOutput { - data_type: frontend_data_type, + data_type, name: output_name, description: String::new(), - resolved_type: exposed_output.clone().map(|(input, _)| format!("{input:?}")), + resolved_type: format!("{:?}", output_type), connected_to, }); } @@ -2416,9 +2386,9 @@ impl NodeGraphMessageHandler { network_interface.upstream_flow_back_from_nodes(vec![node_id], &[], network_interface::FlowType::HorizontalFlow).last().is_some_and(|node_id| network_interface.document_node(&node_id, &[]).map_or_else(||{log::error!("Could not get node {node_id} in update_layer_panel"); false}, |node| { if network_interface.is_layer(&node_id, &[]) { - node.inputs.iter().filter(|input| input.is_exposed_to_frontend(true)).nth(1).is_some_and(|input| input.as_value().is_some()) + node.inputs.iter().filter(|input| input.is_exposed()).nth(1).is_some_and(|input| input.as_value().is_some()) } else { - node.inputs.iter().filter(|input| input.is_exposed_to_frontend(true)).nth(0).is_some_and(|input| input.as_value().is_some()) + node.inputs.iter().filter(|input| input.is_exposed()).nth(0).is_some_and(|input| input.as_value().is_some()) } })) ); @@ -2467,66 +2437,6 @@ impl NodeGraphMessageHandler { } } - fn build_wire_path_string(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> String { - let locations = Self::build_wire_path_locations(output_position, input_position, vertical_out, vertical_in); - let smoothing = 0.5; - let delta01 = DVec2::new((locations[1].x - locations[0].x) * smoothing, (locations[1].y - locations[0].y) * smoothing); - let delta23 = DVec2::new((locations[3].x - locations[2].x) * smoothing, (locations[3].y - locations[2].y) * smoothing); - format!( - "M{},{} L{},{} C{},{} {},{} {},{} L{},{}", - locations[0].x, - locations[0].y, - locations[1].x, - locations[1].y, - locations[1].x + delta01.x, - locations[1].y + delta01.y, - locations[2].x - delta23.x, - locations[2].y - delta23.y, - locations[2].x, - locations[2].y, - locations[3].x, - locations[3].y - ) - } - - fn build_wire_path_locations(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> Vec { - let horizontal_gap = (output_position.x - input_position.x).abs(); - let vertical_gap = (output_position.y - input_position.y).abs(); - // TODO: Finish this commented out code replacement for the code below it based on this diagram: - // // Straight: stacking lines which are always straight, or a straight horizontal wire between two aligned nodes - // if ((verticalOut && vertical_in) || (!verticalOut && !vertical_in && vertical_gap === 0)) { - // return [ - // { x: output_position.x, y: output_position.y }, - // { x: input_position.x, y: input_position.y }, - // ]; - // } - - // // L-shape bend - // if (verticalOut !== vertical_in) { - // } - - let curve_length = 24.; - let curve_falloff_rate = curve_length * std::f64::consts::PI * 2.; - - let horizontal_curve_amount = -(2_f64.powf((-10. * horizontal_gap) / curve_falloff_rate)) + 1.; - let vertical_curve_amount = -(2_f64.powf((-10. * vertical_gap) / curve_falloff_rate)) + 1.; - let horizontal_curve = horizontal_curve_amount * curve_length; - let vertical_curve = vertical_curve_amount * curve_length; - - vec![ - output_position, - DVec2::new( - if vertical_out { output_position.x } else { output_position.x + horizontal_curve }, - if vertical_out { output_position.y - vertical_curve } else { output_position.y }, - ), - DVec2::new( - if vertical_in { input_position.x } else { input_position.x - horizontal_curve }, - if vertical_in { input_position.y + vertical_curve } else { input_position.y }, - ), - DVec2::new(input_position.x, input_position.y), - ] - } - pub fn update_node_graph_hints(&self, responses: &mut VecDeque) { // A wire is in progress and its start and end connectors are set let wiring = self.wire_in_progress_from_connector.is_some(); @@ -2570,8 +2480,8 @@ impl NodeGraphMessageHandler { #[derive(Default)] struct InputLookup { - input_name: Option, - input_description: Option, + input_name: String, + input_description: String, ty: Type, type_source: TypeSource, valid_types: Vec, @@ -2586,34 +2496,31 @@ fn frontend_inputs_lookup(breadcrumb_network_path: &[NodeId], network_interface: return Default::default(); }; let mut frontend_inputs_lookup = HashMap::new(); - for (&node_id, node) in network.nodes.iter() { - let mut inputs = Vec::with_capacity(node.inputs.len()); - for (index, input) in node.inputs.iter().enumerate() { - let is_exposed = input.is_exposed_to_frontend(breadcrumb_network_path.is_empty()); - - // Skip not exposed inputs (they still get an entry to help with finding the primary input) - if !is_exposed { - inputs.push(None); - continue; - } - + for (node_id, index, output_connector, is_exposed) in network + .nodes + .iter() + .flat_map(|(node_id, node)| { + node.inputs + .iter() + .enumerate() + .map(|(index, input)| (*node_id, index, OutputConnector::from_input(input), input.is_exposed())) + }) + .collect::>() + { + // Skip not exposed inputs (they still get an entry to help with finding the primary input) + let lookup = if !is_exposed { + None + } else { // Get the name from the metadata here (since it also requires a reference to the `network_interface`) - let input_name = network_interface - .input_name(node_id, index, breadcrumb_network_path) - .filter(|s| !s.is_empty()) - .map(|name| name.to_string()); - let input_description = network_interface.input_description(node_id, index, breadcrumb_network_path).map(|description| description.to_string()); - // Get the output connector that feeds into this input (done here as well for simplicity) - let connector = OutputConnector::from_input(input); - - inputs.push(Some(InputLookup { + let (input_name, input_description) = network_interface.displayed_input_name_and_description(&node_id, index, breadcrumb_network_path); + Some(InputLookup { input_name, input_description, - output_connector: connector, + output_connector, ..Default::default() - })); - } - frontend_inputs_lookup.insert(node_id, inputs); + }) + }; + frontend_inputs_lookup.entry(node_id).or_insert_with(Vec::new).push(lookup); } for (&node_id, value) in frontend_inputs_lookup.iter_mut() { @@ -2656,6 +2563,7 @@ impl Default for NodeGraphMessageHandler { select_if_not_dragged: None, wire_in_progress_from_connector: None, wire_in_progress_to_connector: None, + wire_in_progress_type: FrontendGraphDataType::General, context_menu: None, deselect_on_pointer_up: None, auto_panning: Default::default(), @@ -2663,6 +2571,8 @@ impl Default for NodeGraphMessageHandler { reordering_export: None, reordering_import: None, end_index: None, + frontend_nodes: Vec::new(), + frontend_wires: HashSet::new(), } } } diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs index 018182c106..82e2ee97dc 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs @@ -60,17 +60,12 @@ pub fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphData "Expose this parameter as a node input in the graph" }) .on_update(move |_parameter| { - Message::Batched(Box::new([ - NodeGraphMessage::ExposeInput { - input_connector: InputConnector::node(node_id, index), - set_to_exposed: !exposed, - start_transaction: true, - } - .into(), - DocumentMessage::GraphViewOverlay { open: true }.into(), - NavigationMessage::FitViewportToSelection.into(), - DocumentMessage::ZoomCanvasTo100Percent.into(), - ])) + Message::Batched(Box::new([NodeGraphMessage::ExposeInput { + input_connector: InputConnector::node(node_id, index), + set_to_exposed: !exposed, + start_transaction: true, + } + .into()])) }) .widget_holder() } @@ -85,28 +80,31 @@ pub fn add_blank_assist(widgets: &mut Vec) { ]); } -pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo, data_type: FrontendGraphDataType) -> Vec { - start_widgets_exposable(parameter_widgets_info, data_type, true) -} - -pub fn start_widgets_exposable(parameter_widgets_info: ParameterWidgetsInfo, data_type: FrontendGraphDataType, exposable: bool) -> Vec { +pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo) -> Vec { let ParameterWidgetsInfo { document_node, node_id, index, name, description, + input_type, blank_assist, + exposeable, } = parameter_widgets_info; + let Some(document_node) = document_node else { + log::warn!("A widget failed to be built because its document node is invalid."); + return vec![]; + }; + let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; }; - let description = if description != "TODO" { description } else { "" }; + let description = if description != "TODO" { description } else { String::new() }; let mut widgets = Vec::with_capacity(6); - if exposable { - widgets.push(expose_widget(node_id, index, data_type, input.is_exposed())); + if exposeable { + widgets.push(expose_widget(node_id, index, input_type, input.is_exposed())); } widgets.push(TextLabel::new(name).tooltip(description).widget_holder()); if blank_assist { @@ -126,18 +124,6 @@ pub(crate) fn property_from_type( step: Option, context: &mut NodePropertiesContext, ) -> Result, Vec> { - let Some(network) = context.network_interface.nested_network(context.selection_network_path) else { - log::warn!("A widget failed to be built for node {node_id}, index {index} because the network could not be determined"); - return Err(vec![]); - }; - let Some(document_node) = network.nodes.get(&node_id) else { - log::warn!("A widget failed to be built for node {node_id}, index {index} because the document node does not exist"); - return Err(vec![]); - }; - - let name = context.network_interface.input_name(node_id, index, context.selection_network_path).unwrap_or_default(); - let description = context.network_interface.input_description(node_id, index, context.selection_network_path).unwrap_or_default(); - let (mut number_min, mut number_max, range) = number_options; let mut number_input = NumberInput::default(); if let Some((range_start, range_end)) = range { @@ -158,7 +144,7 @@ pub(crate) fn property_from_type( let min = |x: f64| number_min.unwrap_or(x); let max = |x: f64| number_max.unwrap_or(x); - let default_info = ParameterWidgetsInfo::new(document_node, node_id, index, name, description, true); + let default_info = ParameterWidgetsInfo::new(node_id, index, true, context); let mut extra_widgets = vec![]; let widgets = match ty { @@ -176,6 +162,7 @@ pub(crate) fn property_from_type( Some("SeedValue") => number_widget(default_info, number_input.int().min(min(0.))).into(), Some("Resolution") => coordinate_widget(default_info, "W", "H", unit.unwrap_or(" px"), Some(64.)), Some("PixelSize") => coordinate_widget(default_info, "X", "Y", unit.unwrap_or(" px"), None), + Some("TextArea") => text_area_widget(default_info).into(), // For all other types, use TypeId-based matching _ => { @@ -247,7 +234,7 @@ pub(crate) fn property_from_type( // OTHER // ===== _ => { - let mut widgets = start_widgets(default_info, FrontendGraphDataType::General); + let mut widgets = start_widgets(default_info); widgets.extend_from_slice(&[ Separator::new(SeparatorType::Unrelated).widget_holder(), TextLabel::new("-") @@ -277,8 +264,9 @@ pub(crate) fn property_from_type( pub fn text_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec { let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; - let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General); + let mut widgets = start_widgets(parameter_widgets_info); + let Some(document_node) = document_node else { return Vec::new() }; let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; @@ -298,8 +286,9 @@ pub fn text_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec Vec { let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; - let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General); + let mut widgets = start_widgets(parameter_widgets_info); + let Some(document_node) = document_node else { return Vec::new() }; let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; @@ -319,8 +308,9 @@ pub fn text_area_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec Vec { let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; - let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General); + let mut widgets = start_widgets(parameter_widgets_info); + let Some(document_node) = document_node else { return Vec::new() }; let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; @@ -341,8 +331,9 @@ pub fn bool_widget(parameter_widgets_info: ParameterWidgetsInfo, checkbox_input: pub fn reference_point_widget(parameter_widgets_info: ParameterWidgetsInfo, disabled: bool) -> Vec { let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; - let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General); + let mut widgets = start_widgets(parameter_widgets_info); + let Some(document_node) = document_node else { return Vec::new() }; let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; @@ -371,7 +362,7 @@ pub fn reference_point_widget(parameter_widgets_info: ParameterWidgetsInfo, disa pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widgets: &mut Vec) -> LayoutGroup { let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; - let mut location_widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General); + let mut location_widgets = start_widgets(parameter_widgets_info); location_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); let mut scale_widgets = vec![TextLabel::new("").widget_holder()]; @@ -382,10 +373,12 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg add_blank_assist(&mut resolution_widgets); resolution_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); + let Some(document_node) = document_node else { return LayoutGroup::default() }; let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return Vec::new().into(); }; + if let Some(&TaggedValue::Footprint(footprint)) = input.as_non_exposed_value() { let top_left = footprint.transform.transform_point2(DVec2::ZERO); let bounds = footprint.scale(); @@ -517,8 +510,9 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option) -> LayoutGroup { let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; - let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number); + let mut widgets = start_widgets(parameter_widgets_info); + let Some(document_node) = document_node else { return LayoutGroup::default() }; let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return LayoutGroup::Row { widgets: vec![] }; @@ -629,7 +623,7 @@ pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text_input: TextInput) -> Vec { let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; - let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number); + let mut widgets = start_widgets(parameter_widgets_info); let from_string = |string: &str| { string @@ -641,6 +635,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text .map(TaggedValue::VecF64) }; + let Some(document_node) = document_node else { return Vec::new() }; let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; @@ -660,7 +655,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec { let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; - let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number); + let mut widgets = start_widgets(parameter_widgets_info); let from_string = |string: &str| { string @@ -672,6 +667,7 @@ pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo, .map(TaggedValue::VecDVec2) }; + let Some(document_node) = document_node else { return Vec::new() }; let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; @@ -691,11 +687,12 @@ pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo, pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec, Option>) { let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; - let mut first_widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General); + let mut first_widgets = start_widgets(parameter_widgets_info); let mut second_widgets = None; let from_font_input = |font: &FontInput| TaggedValue::Font(Font::new(font.font_family.clone(), font.font_style.clone())); + let Some(document_node) = document_node else { return (Vec::new(), None) }; let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return (vec![], None); @@ -725,7 +722,7 @@ pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec Vec { - let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::VectorData); + let mut widgets = start_widgets(parameter_widgets_info); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(TextLabel::new("Vector data is supplied through the node graph").widget_holder()); @@ -734,7 +731,7 @@ pub fn vector_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec Vec { - let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Raster); + let mut widgets = start_widgets(parameter_widgets_info); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(TextLabel::new("Raster data is supplied through the node graph").widget_holder()); @@ -743,7 +740,7 @@ pub fn raster_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec Vec { - let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Group); + let mut widgets = start_widgets(parameter_widgets_info); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(TextLabel::new("Group data is supplied through the node graph").widget_holder()); @@ -754,8 +751,9 @@ pub fn group_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec Vec { let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; - let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number); + let mut widgets = start_widgets(parameter_widgets_info); + let Some(document_node) = document_node else { return Vec::new() }; let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; @@ -825,7 +823,8 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup { let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; - let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General); + let mut widgets = start_widgets(parameter_widgets_info); + let Some(document_node) = document_node else { return LayoutGroup::default() }; let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return LayoutGroup::Row { widgets: vec![] }; @@ -859,8 +858,9 @@ pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Layout pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button: ColorInput) -> LayoutGroup { let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; - let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General); + let mut widgets = start_widgets(parameter_widgets_info); + let Some(document_node) = document_node else { return LayoutGroup::default() }; // Return early with just the label if the input is exposed to the graph, meaning we don't want to show the color picker widget in the Properties panel let NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] else { return LayoutGroup::Row { widgets }; @@ -913,8 +913,9 @@ pub fn font_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup pub fn curve_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup { let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info; - let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General); + let mut widgets = start_widgets(parameter_widgets_info); + let Some(document_node) = document_node else { return LayoutGroup::default() }; let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return LayoutGroup::Row { widgets: vec![] }; @@ -939,14 +940,11 @@ pub fn get_document_node<'a>(node_id: NodeId, context: &'a NodePropertiesContext network.nodes.get(&node_id).ok_or(format!("node {node_id} not found in get_document_node")) } -pub fn query_node_and_input_info<'a>(node_id: NodeId, input_index: usize, context: &'a NodePropertiesContext<'a>) -> Result<(&'a DocumentNode, &'a str, &'a str), String> { +pub fn query_node_and_input_info<'a>(node_id: NodeId, input_index: usize, context: &'a mut NodePropertiesContext<'a>) -> Result<(&'a DocumentNode, String, String), String> { + let (name, description) = context.network_interface.displayed_input_name_and_description(&node_id, input_index, context.selection_network_path); let document_node = get_document_node(node_id, context)?; - let input_name = context.network_interface.input_name(node_id, input_index, context.selection_network_path).unwrap_or_else(|| { - log::warn!("input name not found in query_node_and_input_info"); - "" - }); - let input_description = context.network_interface.input_description(node_id, input_index, context.selection_network_path).unwrap_or_default(); - Ok((document_node, input_name, input_description)) + + Ok((document_node, name, description)) } pub fn query_noise_pattern_state(node_id: NodeId, context: &NodePropertiesContext) -> Result<(bool, bool, bool, bool, bool, bool), String> { @@ -995,6 +993,9 @@ pub fn query_assign_colors_randomize(node_id: NodeId, context: &NodePropertiesCo pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { use graphene_std::raster::brightness_contrast::*; + // Use Classic + let use_classic = bool_widget(ParameterWidgetsInfo::new(node_id, UseClassicInput::INDEX, true, context), CheckboxInput::default()); + let document_node = match get_document_node(node_id, context) { Ok(document_node) => document_node, Err(err) => { @@ -1002,12 +1003,6 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node return Vec::new(); } }; - - // Use Classic - let use_classic = bool_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, UseClassicInput::INDEX, true, context), - CheckboxInput::default(), - ); let use_classic_value = match document_node.inputs[UseClassicInput::INDEX].as_value() { Some(TaggedValue::Bool(use_classic_choice)) => *use_classic_choice, _ => false, @@ -1015,7 +1010,7 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node // Brightness let brightness = number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, BrightnessInput::INDEX, true, context), + ParameterWidgetsInfo::new(node_id, BrightnessInput::INDEX, true, context), NumberInput::default() .unit("%") .mode_range() @@ -1026,7 +1021,7 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node // Contrast let contrast = number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, ContrastInput::INDEX, true, context), + ParameterWidgetsInfo::new(node_id, ContrastInput::INDEX, true, context), NumberInput::default() .unit("%") .mode_range() @@ -1047,6 +1042,11 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { use graphene_std::raster::channel_mixer::*; + let is_monochrome = bool_widget(ParameterWidgetsInfo::new(node_id, MonochromeInput::INDEX, true, context), CheckboxInput::default()); + let mut parameter_info = ParameterWidgetsInfo::new(node_id, OutputChannelInput::INDEX, true, context); + parameter_info.exposeable = false; + let output_channel = enum_choice::().for_socket(parameter_info).property_row(); + let document_node = match get_document_node(node_id, context) { Ok(document_node) => document_node, Err(err) => { @@ -1054,22 +1054,12 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper return Vec::new(); } }; - // Monochrome - let is_monochrome = bool_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, MonochromeInput::INDEX, true, context), - CheckboxInput::default(), - ); let is_monochrome_value = match document_node.inputs[MonochromeInput::INDEX].as_value() { Some(TaggedValue::Bool(monochrome_choice)) => *monochrome_choice, _ => false, }; - // Output channel choice - let output_channel = enum_choice::() - .for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, OutputChannelInput::INDEX, true, context)) - .exposable(false) - .property_row(); let output_channel_value = match &document_node.inputs[OutputChannelInput::INDEX].as_value() { Some(TaggedValue::RedGreenBlue(choice)) => choice, _ => { @@ -1086,10 +1076,10 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper (false, RedGreenBlue::Blue) => (BlueRInput::INDEX, BlueGInput::INDEX, BlueBInput::INDEX, BlueCInput::INDEX), }; let number_input = NumberInput::default().mode_range().min(-200.).max(200.).unit("%"); - let red = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, red_output_index, true, context), number_input.clone()); - let green = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, green_output_index, true, context), number_input.clone()); - let blue = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, blue_output_index, true, context), number_input.clone()); - let constant = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, constant_output_index, true, context), number_input); + let red = number_widget(ParameterWidgetsInfo::new(node_id, red_output_index, true, context), number_input.clone()); + let green = number_widget(ParameterWidgetsInfo::new(node_id, green_output_index, true, context), number_input.clone()); + let blue = number_widget(ParameterWidgetsInfo::new(node_id, blue_output_index, true, context), number_input.clone()); + let constant = number_widget(ParameterWidgetsInfo::new(node_id, constant_output_index, true, context), number_input); // Monochrome let mut layout = vec![LayoutGroup::Row { widgets: is_monochrome }]; @@ -1110,6 +1100,10 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { use graphene_std::raster::selective_color::*; + let mut default_info = ParameterWidgetsInfo::new(node_id, ColorsInput::INDEX, true, context); + default_info.exposeable = false; + let colors = enum_choice::().for_socket(default_info).property_row(); + let document_node = match get_document_node(node_id, context) { Ok(document_node) => document_node, Err(err) => { @@ -1117,13 +1111,7 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp return Vec::new(); } }; - // Colors choice - let colors = enum_choice::() - .for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, ColorsInput::INDEX, true, context)) - .exposable(false) - .property_row(); - let colors_choice = match &document_node.inputs[ColorsInput::INDEX].as_value() { Some(TaggedValue::SelectiveColorChoice(choice)) => choice, _ => { @@ -1131,7 +1119,6 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp return vec![]; } }; - // CMYK let (c_index, m_index, y_index, k_index) = match colors_choice { SelectiveColorChoice::Reds => (RCInput::INDEX, RMInput::INDEX, RYInput::INDEX, RKInput::INDEX), @@ -1145,14 +1132,14 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp SelectiveColorChoice::Blacks => (KCInput::INDEX, KMInput::INDEX, KYInput::INDEX, KKInput::INDEX), }; let number_input = NumberInput::default().mode_range().min(-100.).max(100.).unit("%"); - let cyan = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, c_index, true, context), number_input.clone()); - let magenta = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, m_index, true, context), number_input.clone()); - let yellow = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, y_index, true, context), number_input.clone()); - let black = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, k_index, true, context), number_input); + let cyan = number_widget(ParameterWidgetsInfo::new(node_id, c_index, true, context), number_input.clone()); + let magenta = number_widget(ParameterWidgetsInfo::new(node_id, m_index, true, context), number_input.clone()); + let yellow = number_widget(ParameterWidgetsInfo::new(node_id, y_index, true, context), number_input.clone()); + let black = number_widget(ParameterWidgetsInfo::new(node_id, k_index, true, context), number_input); // Mode let mode = enum_choice::() - .for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, ModeInput::INDEX, true, context)) + .for_socket(ParameterWidgetsInfo::new(node_id, ModeInput::INDEX, true, context)) .property_row(); vec![ @@ -1171,19 +1158,19 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { use graphene_std::vector::generator_nodes::grid::*; - let document_node = match get_document_node(node_id, context) { - Ok(document_node) => document_node, - Err(err) => { - log::error!("Could not get document node in exposure_properties: {err}"); - return Vec::new(); - } - }; let grid_type = enum_choice::() - .for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, GridTypeInput::INDEX, true, context)) + .for_socket(ParameterWidgetsInfo::new(node_id, GridTypeInput::INDEX, true, context)) .property_row(); let mut widgets = vec![grid_type]; + let document_node = match get_document_node(node_id, context) { + Ok(document_node) => document_node, + Err(err) => { + log::error!("Could not get document node in grid_properties: {err}"); + return Vec::new(); + } + }; let Some(grid_type_input) = document_node.inputs.get(GridTypeInput::INDEX) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; @@ -1191,36 +1178,24 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte if let Some(&TaggedValue::GridType(grid_type)) = grid_type_input.as_non_exposed_value() { match grid_type { GridType::Rectangular => { - let spacing = coordinate_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::::INDEX, true, context), - "W", - "H", - " px", - Some(0.), - ); + let spacing = coordinate_widget(ParameterWidgetsInfo::new(node_id, SpacingInput::::INDEX, true, context), "W", "H", " px", Some(0.)); widgets.push(spacing); } GridType::Isometric => { let spacing = LayoutGroup::Row { widgets: number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::::INDEX, true, context), + ParameterWidgetsInfo::new(node_id, SpacingInput::::INDEX, true, context), NumberInput::default().label("H").min(0.).unit(" px"), ), }; - let angles = coordinate_widget(ParameterWidgetsInfo::from_index(document_node, node_id, AnglesInput::INDEX, true, context), "", "", "°", None); + let angles = coordinate_widget(ParameterWidgetsInfo::new(node_id, AnglesInput::INDEX, true, context), "", "", "°", None); widgets.extend([spacing, angles]); } } } - let columns = number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, ColumnsInput::INDEX, true, context), - NumberInput::default().min(1.), - ); - let rows = number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, RowsInput::INDEX, true, context), - NumberInput::default().min(1.), - ); + let columns = number_widget(ParameterWidgetsInfo::new(node_id, ColumnsInput::INDEX, true, context), NumberInput::default().min(1.)); + let rows = number_widget(ParameterWidgetsInfo::new(node_id, RowsInput::INDEX, true, context), NumberInput::default().min(1.)); widgets.extend([LayoutGroup::Row { widgets: columns }, LayoutGroup::Row { widgets: rows }]); @@ -1322,26 +1297,14 @@ pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodeProp let is_quantity = matches!(current_spacing, Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity))); let spacing = enum_choice::() - .for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::INDEX, true, context)) + .for_socket(ParameterWidgetsInfo::new(node_id, SpacingInput::INDEX, true, context)) .property_row(); - let separation = number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, SeparationInput::INDEX, true, context), - NumberInput::default().min(0.).unit(" px"), - ); - let quantity = number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, QuantityInput::INDEX, true, context), - NumberInput::default().min(2.).int(), - ); - let start_offset = number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, StartOffsetInput::INDEX, true, context), - NumberInput::default().min(0.).unit(" px"), - ); - let stop_offset = number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, StopOffsetInput::INDEX, true, context), - NumberInput::default().min(0.).unit(" px"), - ); + let separation = number_widget(ParameterWidgetsInfo::new(node_id, SeparationInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px")); + let quantity = number_widget(ParameterWidgetsInfo::new(node_id, QuantityInput::INDEX, true, context), NumberInput::default().min(2.).int()); + let start_offset = number_widget(ParameterWidgetsInfo::new(node_id, StartOffsetInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px")); + let stop_offset = number_widget(ParameterWidgetsInfo::new(node_id, StopOffsetInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px")); let adaptive_spacing = bool_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, AdaptiveSpacingInput::INDEX, true, context), + ParameterWidgetsInfo::new(node_id, AdaptiveSpacingInput::INDEX, true, context), CheckboxInput::default().disabled(is_quantity), ); @@ -1361,23 +1324,10 @@ pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodeProp pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { use graphene_std::raster::exposure::*; - let document_node = match get_document_node(node_id, context) { - Ok(document_node) => document_node, - Err(err) => { - log::error!("Could not get document node in exposure_properties: {err}"); - return Vec::new(); - } - }; - let exposure = number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, ExposureInput::INDEX, true, context), - NumberInput::default().min(-20.).max(20.), - ); - let offset = number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, OffsetInput::INDEX, true, context), - NumberInput::default().min(-0.5).max(0.5), - ); + let exposure = number_widget(ParameterWidgetsInfo::new(node_id, ExposureInput::INDEX, true, context), NumberInput::default().min(-20.).max(20.)); + let offset = number_widget(ParameterWidgetsInfo::new(node_id, OffsetInput::INDEX, true, context), NumberInput::default().min(-0.5).max(0.5)); let gamma_correction = number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, GammaCorrectionInput::INDEX, true, context), + ParameterWidgetsInfo::new(node_id, GammaCorrectionInput::INDEX, true, context), NumberInput::default().min(0.01).max(9.99).increment_step(0.1), ); @@ -1391,6 +1341,14 @@ pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesC pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { use graphene_std::vector::generator_nodes::rectangle::*; + // Corner Radius + let mut corner_radius_row_1 = start_widgets(ParameterWidgetsInfo::new(node_id, CornerRadiusInput::::INDEX, true, context)); + corner_radius_row_1.push(Separator::new(SeparatorType::Unrelated).widget_holder()); + + let mut corner_radius_row_2 = vec![Separator::new(SeparatorType::Unrelated).widget_holder()]; + corner_radius_row_2.push(TextLabel::new("").widget_holder()); + add_blank_assist(&mut corner_radius_row_2); + let document_node = match get_document_node(node_id, context) { Ok(document_node) => document_node, Err(err) => { @@ -1398,23 +1356,6 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties return Vec::new(); } }; - // Size X - let size_x = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, WidthInput::INDEX, true, context), NumberInput::default()); - - // Size Y - let size_y = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, HeightInput::INDEX, true, context), NumberInput::default()); - - // Corner Radius - let mut corner_radius_row_1 = start_widgets( - ParameterWidgetsInfo::from_index(document_node, node_id, CornerRadiusInput::::INDEX, true, context), - FrontendGraphDataType::Number, - ); - corner_radius_row_1.push(Separator::new(SeparatorType::Unrelated).widget_holder()); - - let mut corner_radius_row_2 = vec![Separator::new(SeparatorType::Unrelated).widget_holder()]; - corner_radius_row_2.push(TextLabel::new("").widget_holder()); - add_blank_assist(&mut corner_radius_row_2); - let Some(input) = document_node.inputs.get(IndividualCornerRadiiInput::INDEX) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; @@ -1508,8 +1449,14 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties corner_radius_row_2.push(input_widget); } + // Size X + let size_x = number_widget(ParameterWidgetsInfo::new(node_id, WidthInput::INDEX, true, context), NumberInput::default()); + + // Size Y + let size_y = number_widget(ParameterWidgetsInfo::new(node_id, HeightInput::INDEX, true, context), NumberInput::default()); + // Clamped - let clamped = bool_widget(ParameterWidgetsInfo::from_index(document_node, node_id, ClampedInput::INDEX, true, context), CheckboxInput::default()); + let clamped = bool_widget(ParameterWidgetsInfo::new(node_id, ClampedInput::INDEX, true, context), CheckboxInput::default()); vec![ LayoutGroup::Row { widgets: size_x }, @@ -1561,7 +1508,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper if let Some(field) = graphene_std::registry::NODE_METADATA .lock() .unwrap() - .get(&proto_node_identifier.name.clone().into_owned()) + .get(proto_node_identifier) .and_then(|metadata| metadata.fields.get(input_index)) { number_options = (field.number_min, field.number_max, field.number_mode_range); @@ -1638,6 +1585,8 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { use graphene_std::vector::fill::*; + let mut widgets_first_row = start_widgets(ParameterWidgetsInfo::new(node_id, FillInput::::INDEX, true, context)); + let document_node = match get_document_node(node_id, context) { Ok(document_node) => document_node, Err(err) => { @@ -1646,11 +1595,6 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte } }; - let mut widgets_first_row = start_widgets( - ParameterWidgetsInfo::from_index(document_node, node_id, FillInput::::INDEX, true, context), - FrontendGraphDataType::General, - ); - let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), &Some(&TaggedValue::OptionalColor(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = ( &document_node.inputs[FillInput::::INDEX].as_value(), &document_node.inputs[BackupColorInput::INDEX].as_value(), @@ -1829,47 +1773,42 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) - return Vec::new(); } }; + let join_value = match &document_node.inputs[JoinInput::INDEX].as_value() { + Some(TaggedValue::StrokeJoin(x)) => x, + _ => &StrokeJoin::Miter, + }; - let color = color_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, ColorInput::>::INDEX, true, context), - crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(), - ); - let weight = number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, WeightInput::INDEX, true, context), - NumberInput::default().unit(" px").min(0.), - ); - let align = enum_choice::() - .for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, AlignInput::INDEX, true, context)) - .property_row(); - let cap = enum_choice::() - .for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, CapInput::INDEX, true, context)) - .property_row(); - let join = enum_choice::() - .for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, JoinInput::INDEX, true, context)) - .property_row(); - let miter_limit = number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, MiterLimitInput::INDEX, true, context), - NumberInput::default().min(0.).disabled({ - let join_value = match &document_node.inputs[JoinInput::INDEX].as_value() { - Some(TaggedValue::StrokeJoin(x)) => x, - _ => &StrokeJoin::Miter, - }; - join_value != &StrokeJoin::Miter - }), - ); - let paint_order = enum_choice::() - .for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, PaintOrderInput::INDEX, true, context)) - .property_row(); let dash_lengths_val = match &document_node.inputs[DashLengthsInput::INDEX].as_value() { Some(TaggedValue::VecF64(x)) => x, _ => &vec![], }; - let dash_lengths = array_of_number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, DashLengthsInput::INDEX, true, context), - TextInput::default().centered(true), + let has_dash_lengths = dash_lengths_val.is_empty(); + let miter_limit_disabled = join_value != &StrokeJoin::Miter; + + let color = color_widget( + ParameterWidgetsInfo::new(node_id, ColorInput::>::INDEX, true, context), + crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(), ); - let number_input = NumberInput::default().unit(" px").disabled(dash_lengths_val.is_empty()); - let dash_offset = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, DashOffsetInput::INDEX, true, context), number_input); + let weight = number_widget(ParameterWidgetsInfo::new(node_id, WeightInput::INDEX, true, context), NumberInput::default().unit(" px").min(0.)); + let align = enum_choice::() + .for_socket(ParameterWidgetsInfo::new(node_id, AlignInput::INDEX, true, context)) + .property_row(); + let cap = enum_choice::().for_socket(ParameterWidgetsInfo::new(node_id, CapInput::INDEX, true, context)).property_row(); + let join = enum_choice::() + .for_socket(ParameterWidgetsInfo::new(node_id, JoinInput::INDEX, true, context)) + .property_row(); + + let miter_limit = number_widget( + ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context), + NumberInput::default().min(0.).disabled(miter_limit_disabled), + ); + let paint_order = enum_choice::() + .for_socket(ParameterWidgetsInfo::new(node_id, PaintOrderInput::INDEX, true, context)) + .property_row(); + let disabled_number_input = NumberInput::default().unit(" px").disabled(has_dash_lengths); + let dash_lengths = array_of_number_widget(ParameterWidgetsInfo::new(node_id, DashLengthsInput::INDEX, true, context), TextInput::default().centered(true)); + let number_input = disabled_number_input; + let dash_offset = number_widget(ParameterWidgetsInfo::new(node_id, DashOffsetInput::INDEX, true, context), number_input); vec![ color, @@ -1887,6 +1826,13 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) - pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { use graphene_std::vector::offset_path::*; + let number_input = NumberInput::default().unit(" px"); + let distance = number_widget(ParameterWidgetsInfo::new(node_id, DistanceInput::INDEX, true, context), number_input); + + let join = enum_choice::() + .for_socket(ParameterWidgetsInfo::new(node_id, JoinInput::INDEX, true, context)) + .property_row(); + let document_node = match get_document_node(node_id, context) { Ok(document_node) => document_node, Err(err) => { @@ -1894,13 +1840,6 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte return Vec::new(); } }; - let number_input = NumberInput::default().unit(" px"); - let distance = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, DistanceInput::INDEX, true, context), number_input); - - let join = enum_choice::() - .for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, JoinInput::INDEX, true, context)) - .property_row(); - let number_input = NumberInput::default().min(0.).disabled({ let join_val = match &document_node.inputs[JoinInput::INDEX].as_value() { Some(TaggedValue::StrokeJoin(x)) => x, @@ -1908,7 +1847,7 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte }; join_val != &StrokeJoin::Miter }); - let miter_limit = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, MiterLimitInput::INDEX, true, context), number_input); + let miter_limit = number_widget(ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context), number_input); vec![LayoutGroup::Row { widgets: distance }, join, LayoutGroup::Row { widgets: miter_limit }] } @@ -1916,20 +1855,16 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec { use graphene_std::math_nodes::math::*; - let document_node = match get_document_node(node_id, context) { - Ok(document_node) => document_node, - Err(err) => { - log::error!("Could not get document node in offset_path_properties: {err}"); - return Vec::new(); - } - }; - let expression = (|| { - let mut widgets = start_widgets( - ParameterWidgetsInfo::from_index(document_node, node_id, ExpressionInput::INDEX, true, context), - FrontendGraphDataType::General, - ); + let mut widgets = start_widgets(ParameterWidgetsInfo::new(node_id, ExpressionInput::INDEX, true, context)); + let document_node = match get_document_node(node_id, context) { + Ok(document_node) => document_node, + Err(err) => { + log::error!("Could not get document node in offset_path_properties: {err}"); + return Vec::new(); + } + }; let Some(input) = document_node.inputs.get(ExpressionInput::INDEX) else { log::warn!("A widget failed to be built because its node's input index is invalid."); return vec![]; @@ -1962,10 +1897,7 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> } widgets })(); - let operand_b = number_widget( - ParameterWidgetsInfo::from_index(document_node, node_id, OperandBInput::::INDEX, true, context), - NumberInput::default(), - ); + let operand_b = number_widget(ParameterWidgetsInfo::new(node_id, OperandBInput::::INDEX, true, context), NumberInput::default()); let operand_a_hint = vec![TextLabel::new("(Operand A is the primary input)").widget_holder()]; vec![ @@ -1976,44 +1908,37 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> } pub struct ParameterWidgetsInfo<'a> { - document_node: &'a DocumentNode, + document_node: Option<&'a DocumentNode>, node_id: NodeId, index: usize, - name: &'a str, - description: &'a str, + name: String, + description: String, + input_type: FrontendGraphDataType, blank_assist: bool, + exposeable: bool, } impl<'a> ParameterWidgetsInfo<'a> { - pub fn new(document_node: &'a DocumentNode, node_id: NodeId, index: usize, name: &'a str, description: &'a str, blank_assist: bool) -> ParameterWidgetsInfo<'a> { + pub fn new(node_id: NodeId, index: usize, blank_assist: bool, context: &'a mut NodePropertiesContext) -> ParameterWidgetsInfo<'a> { + let (name, description) = context.network_interface.displayed_input_name_and_description(&node_id, index, context.selection_network_path); + let input_type = FrontendGraphDataType::from_type(&context.network_interface.input_type(&InputConnector::node(node_id, index), context.selection_network_path).0); + let document_node = context.network_interface.document_node(&node_id, context.selection_network_path); + ParameterWidgetsInfo { document_node, node_id, index, name, description, + input_type, blank_assist, - } - } - - pub fn from_index(document_node: &'a DocumentNode, node_id: NodeId, index: usize, blank_assist: bool, context: &'a NodePropertiesContext) -> ParameterWidgetsInfo<'a> { - let name = context.network_interface.input_name(node_id, index, context.selection_network_path).unwrap_or_default(); - let description = context.network_interface.input_description(node_id, index, context.selection_network_path).unwrap_or_default(); - - Self { - document_node, - node_id, - index, - name, - description, - blank_assist, + exposeable: true, } } } pub mod choice { use super::ParameterWidgetsInfo; - use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType; use crate::messages::tool::tool_messages::tool_prelude::*; use graph_craft::document::value::TaggedValue; use graphene_std::registry::{ChoiceTypeStatic, ChoiceWidgetHint}; @@ -2046,11 +1971,7 @@ pub mod choice { impl EnumChoice { pub fn for_socket(self, parameter_info: ParameterWidgetsInfo) -> ForSocket { - ForSocket { - widget_factory: self, - parameter_info, - exposable: true, - } + ForSocket { widget_factory: self, parameter_info } } /// Not yet implemented! @@ -2141,7 +2062,6 @@ pub mod choice { pub struct ForSocket<'p, W> { widget_factory: W, parameter_info: ParameterWidgetsInfo<'p>, - exposable: bool, } impl<'p, W> ForSocket<'p, W> @@ -2158,14 +2078,14 @@ pub mod choice { } } - pub fn exposable(self, exposable: bool) -> Self { - Self { exposable, ..self } - } - pub fn property_row(self) -> LayoutGroup { let ParameterWidgetsInfo { document_node, node_id, index, .. } = self.parameter_info; + let Some(document_node) = document_node else { + log::error!("Could not get document node when building property row for node {:?}", node_id); + return LayoutGroup::Row { widgets: Vec::new() }; + }; - let mut widgets = super::start_widgets_exposable(self.parameter_info, FrontendGraphDataType::General, self.exposable); + let mut widgets = super::start_widgets(self.parameter_info); let Some(input) = document_node.inputs.get(index) else { log::warn!("A widget failed to be built because its node's input index is invalid."); diff --git a/editor/src/messages/portfolio/document/node_graph/utility_types.rs b/editor/src/messages/portfolio/document/node_graph/utility_types.rs index f09b2690e2..e752c9d7ce 100644 --- a/editor/src/messages/portfolio/document/node_graph/utility_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/utility_types.rs @@ -2,6 +2,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Inp use graph_craft::document::NodeId; use graph_craft::document::value::TaggedValue; use graphene_std::Type; +use std::borrow::Cow; #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] pub enum FrontendGraphDataType { @@ -15,7 +16,7 @@ pub enum FrontendGraphDataType { } impl FrontendGraphDataType { - fn with_type(input: &Type) -> Self { + pub fn from_type(input: &Type) -> Self { match TaggedValue::from_type_or_none(input) { TaggedValue::Image(_) | TaggedValue::RasterData(_) => Self::Raster, TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::VectorData, @@ -38,7 +39,7 @@ impl FrontendGraphDataType { pub fn displayed_type(input: &Type, type_source: &TypeSource) -> Self { match type_source { TypeSource::Error(_) | TypeSource::RandomProtonodeImplementation => Self::General, - _ => Self::with_type(input), + _ => Self::from_type(input), } } } @@ -50,7 +51,7 @@ pub struct FrontendGraphInput { pub name: String, pub description: String, #[serde(rename = "resolvedType")] - pub resolved_type: Option, + pub resolved_type: String, #[serde(rename = "validTypes")] pub valid_types: Vec, #[serde(rename = "connectedTo")] @@ -64,7 +65,7 @@ pub struct FrontendGraphOutput { pub name: String, pub description: String, #[serde(rename = "resolvedType")] - pub resolved_type: Option, + pub resolved_type: String, #[serde(rename = "connectedTo")] pub connected_to: Vec, } @@ -96,44 +97,27 @@ pub struct FrontendNode { pub ui_only: bool, } -#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] -pub struct FrontendNodeWire { - #[serde(rename = "wireStart")] - pub wire_start: OutputConnector, - #[serde(rename = "wireEnd")] - pub wire_end: InputConnector, - pub dashed: bool, -} - #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] pub struct FrontendNodeType { - pub name: String, - pub category: String, + pub name: Cow<'static, str>, + pub category: Cow<'static, str>, #[serde(rename = "inputTypes")] - pub input_types: Option>, + pub input_types: Option>>, } impl FrontendNodeType { - pub fn new(name: &'static str, category: &'static str) -> Self { + pub fn new(name: impl Into>, category: impl Into>) -> Self { Self { - name: name.to_string(), - category: category.to_string(), + name: name.into(), + category: category.into(), input_types: None, } } - pub fn with_input_types(name: &'static str, category: &'static str, input_types: Vec) -> Self { + pub fn with_input_types(name: impl Into>, category: impl Into>, input_types: Vec>) -> Self { Self { - name: name.to_string(), - category: category.to_string(), - input_types: Some(input_types), - } - } - - pub fn with_owned_strings_and_input_types(name: String, category: String, input_types: Vec) -> Self { - Self { - name, - category, + name: name.into(), + category: category.into(), input_types: Some(input_types), } } @@ -153,16 +137,6 @@ pub struct Transform { pub y: f64, } -#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] -pub struct WirePath { - #[serde(rename = "pathString")] - pub path_string: String, - #[serde(rename = "dataType")] - pub data_type: FrontendGraphDataType, - pub thick: bool, - pub dashed: bool, -} - #[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] pub struct BoxSelection { #[serde(rename = "startX")] @@ -217,39 +191,10 @@ pub struct FrontendClickTargets { pub modify_import_export: Vec, } -#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] +#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] pub enum Direction { Up, Down, Left, Right, } - -#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)] -pub enum GraphWireStyle { - #[default] - Direct = 0, - GridAligned = 1, -} - -impl std::fmt::Display for GraphWireStyle { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - GraphWireStyle::GridAligned => write!(f, "Grid-Aligned"), - GraphWireStyle::Direct => write!(f, "Direct"), - } - } -} - -impl GraphWireStyle { - pub fn tooltip_description(&self) -> &'static str { - match self { - GraphWireStyle::GridAligned => "Wires follow the grid, running in straight lines between nodes", - GraphWireStyle::Direct => "Wires bend to run at an angle directly between nodes", - } - } - - pub fn is_direct(&self) -> bool { - *self == GraphWireStyle::Direct - } -} diff --git a/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs b/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs index 0db542da75..d4bb518e68 100644 --- a/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs +++ b/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs @@ -1,13 +1,14 @@ use super::utility_types::{OverlayProvider, OverlaysVisibilitySettings}; use crate::messages::prelude::*; +#[derive(ExtractField)] pub struct OverlaysMessageData<'a> { pub visibility_settings: OverlaysVisibilitySettings, pub ipp: &'a InputPreprocessorMessageHandler, pub device_pixel_ratio: f64, } -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct OverlaysMessageHandler { pub overlay_providers: HashSet, #[cfg(target_arch = "wasm32")] @@ -16,6 +17,7 @@ pub struct OverlaysMessageHandler { context: Option, } +#[message_handler_data] impl MessageHandler> for OverlaysMessageHandler { fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque, data: OverlaysMessageData) { let OverlaysMessageData { visibility_settings, ipp, .. } = data; diff --git a/editor/src/messages/portfolio/document/overlays/utility_functions.rs b/editor/src/messages/portfolio/document/overlays/utility_functions.rs index 1e012b867a..463971c65c 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_functions.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_functions.rs @@ -119,7 +119,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) { let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue }; - let transform = document.metadata().transform_to_viewport(layer); + let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface); if display_path { overlay_context.outline_vector(&vector_data, transform); } @@ -196,7 +196,7 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: & continue; }; //let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz); - let transform = document.metadata().transform_to_viewport(layer); + let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface); let selected = shape_editor.selected_shape_state.get(&layer); let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point)); diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs index a17d711a38..3d12ba4961 100644 --- a/editor/src/messages/portfolio/document/overlays/utility_types.rs +++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs @@ -1,12 +1,12 @@ use super::utility_functions::overlay_canvas_context; use crate::consts::{ - COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, - COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, + COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, + COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, }; use crate::messages::prelude::Message; use bezier_rs::{Bezier, Subpath}; use core::borrow::Borrow; -use core::f64::consts::{FRAC_PI_2, TAU}; +use core::f64::consts::{FRAC_PI_2, PI, TAU}; use glam::{DAffine2, DVec2}; use graphene_std::Color; use graphene_std::math::quad::Quad; @@ -33,12 +33,14 @@ pub enum OverlaysType { HoverOutline, SelectionOutline, Pivot, + Origin, Path, Anchors, Handles, } #[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] +#[serde(default)] pub struct OverlaysVisibilitySettings { pub all: bool, pub artboard_name: bool, @@ -49,6 +51,7 @@ pub struct OverlaysVisibilitySettings { pub hover_outline: bool, pub selection_outline: bool, pub pivot: bool, + pub origin: bool, pub path: bool, pub anchors: bool, pub handles: bool, @@ -66,6 +69,7 @@ impl Default for OverlaysVisibilitySettings { hover_outline: true, selection_outline: true, pivot: true, + origin: true, path: true, anchors: true, handles: true, @@ -110,6 +114,10 @@ impl OverlaysVisibilitySettings { self.all && self.pivot } + pub fn origin(&self) -> bool { + self.all && self.origin + } + pub fn path(&self) -> bool { self.all && self.path } @@ -423,10 +431,7 @@ impl OverlayContext { pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) { let sign = scale.signum(); - let mut fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()) - .unwrap() - .with_alpha(0.05) - .to_rgba_hex_srgb(); + let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb(); fill_color.insert(0, '#'); let fill_color = Some(fill_color.as_str()); self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None, None); @@ -463,10 +468,7 @@ impl OverlayContext { // Hover ring if show_hover_ring { - let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) - .unwrap() - .with_alpha(0.5) - .to_rgba_hex_srgb(); + let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.5).to_rgba_hex_srgb(); fill_color.insert(0, '#'); self.render_context.set_line_width(HOVER_RING_STROKE_WIDTH); @@ -550,6 +552,36 @@ impl OverlayContext { self.end_dpi_aware_transform(); } + pub fn dowel_pin(&mut self, position: DVec2, angle: f64, color: Option<&str>) { + let (x, y) = (position.round() - DVec2::splat(0.5)).into(); + let color = color.unwrap_or(COLOR_OVERLAY_YELLOW_DULL); + + self.start_dpi_aware_transform(); + + // Draw the background circle with a white fill and blue outline + self.render_context.begin_path(); + self.render_context.arc(x, y, DOWEL_PIN_RADIUS, 0., TAU).expect("Failed to draw the circle"); + self.render_context.set_fill_style_str(COLOR_OVERLAY_WHITE); + self.render_context.fill(); + self.render_context.set_stroke_style_str(color); + self.render_context.stroke(); + + // Draw the two blue filled sectors + self.render_context.begin_path(); + // Top-left sector + self.render_context.move_to(x, y); + self.render_context.arc(x, y, DOWEL_PIN_RADIUS, FRAC_PI_2 + angle, PI + angle).expect("Failed to draw arc"); + self.render_context.close_path(); + // Bottom-right sector + self.render_context.move_to(x, y); + self.render_context.arc(x, y, DOWEL_PIN_RADIUS, PI + FRAC_PI_2 + angle, TAU + angle).expect("Failed to draw arc"); + self.render_context.close_path(); + self.render_context.set_fill_style_str(color); + self.render_context.fill(); + + self.end_dpi_aware_transform(); + } + /// Used by the Pen and Path tools to outline the path of the shape. pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) { self.start_dpi_aware_transform(); @@ -599,9 +631,11 @@ impl OverlayContext { pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) { self.start_dpi_aware_transform(); + let color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb(); + self.render_context.begin_path(); self.bezier_command(bezier, transform, true); - self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50); + self.render_context.set_stroke_style_str(&color); self.render_context.set_line_width(4.); self.render_context.stroke(); @@ -731,11 +765,11 @@ impl OverlayContext { // └──┴──┴──┴──┘ let pixels = [(0, 0), (2, 2)]; for &(x, y) in &pixels { - let index = (x + y * PATTERN_WIDTH as usize) * 4; + let index = (x + y * PATTERN_WIDTH) * 4; data[index..index + 4].copy_from_slice(&color.to_rgba8_srgb()); } - let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(wasm_bindgen::Clamped(&mut data), PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap(); + let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(wasm_bindgen::Clamped(&data), PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap(); pattern_context.put_image_data(&image_data, 0., 0.).unwrap(); let pattern = self.render_context.create_pattern_with_offscreen_canvas(&pattern_canvas, "repeat").unwrap().unwrap(); @@ -780,6 +814,36 @@ impl OverlayContext { self.render_context.fill_text(text, 0., 0.).expect("Failed to draw the text at the calculated position"); self.render_context.reset_transform().expect("Failed to reset the render context transform"); } + + pub fn translation_box(&mut self, translation: DVec2, quad: Quad, typed_string: Option) { + if translation.x.abs() > 1e-3 { + self.dashed_line(quad.top_left(), quad.top_right(), None, None, Some(2.), Some(2.), Some(0.5)); + + let width = match typed_string { + Some(ref typed_string) => typed_string, + None => &format!("{:.2}", translation.x).trim_end_matches('0').trim_end_matches('.').to_string(), + }; + let x_transform = DAffine2::from_translation((quad.top_left() + quad.top_right()) / 2.); + self.text(width, COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]); + } + + if translation.y.abs() > 1e-3 { + self.dashed_line(quad.top_left(), quad.bottom_left(), None, None, Some(2.), Some(2.), Some(0.5)); + + let height = match typed_string { + Some(ref typed_string) => typed_string, + None => &format!("{:.2}", translation.y).trim_end_matches('0').trim_end_matches('.').to_string(), + }; + let y_transform = DAffine2::from_translation((quad.top_left() + quad.bottom_left()) / 2.); + let height_pivot = if translation.x > -1e-3 { Pivot::Start } else { Pivot::End }; + self.text(height, COLOR_OVERLAY_BLUE, None, y_transform, 3., [height_pivot, Pivot::Middle]); + } + + if translation.x.abs() > 1e-3 && translation.y.abs() > 1e-3 { + self.line(quad.top_right(), quad.bottom_right(), None, None); + self.line(quad.bottom_left(), quad.bottom_right(), None, None); + } + } } pub enum Pivot { diff --git a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs index a783b24aae..3ca9f350f3 100644 --- a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs @@ -4,9 +4,10 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions: use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::prelude::*; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct PropertiesPanelMessageHandler {} +#[message_handler_data] impl MessageHandler)> for PropertiesPanelMessageHandler { fn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque, (persistent_data, data): (&PersistentData, PropertiesPanelMessageHandlerData)) { let PropertiesPanelMessageHandlerData { diff --git a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs index 23887db3e5..0b6f65bc87 100644 --- a/editor/src/messages/portfolio/document/utility_types/document_metadata.rs +++ b/editor/src/messages/portfolio/document/utility_types/document_metadata.rs @@ -1,6 +1,8 @@ use super::network_interface::NodeNetworkInterface; use crate::messages::portfolio::document::graph_operation::transform_utils; use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; +use crate::messages::portfolio::document::utility_types::network_interface::FlowType; +use crate::messages::tool::common_functionality::graph_modification_utils; use glam::{DAffine2, DVec2}; use graph_craft::document::NodeId; use graphene_std::math::quad::Quad; @@ -16,10 +18,11 @@ use std::num::NonZeroU64; // TODO: To avoid storing a stateful snapshot of some other system's state (which is easily to accidentally get out of sync), // TODO: it might be better to have a system that can query the state of the node network on demand. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub struct DocumentMetadata { pub upstream_footprints: HashMap, pub local_transforms: HashMap, + pub first_instance_source_ids: HashMap>, pub structure: HashMap, pub click_targets: HashMap>, pub clip_targets: HashSet, @@ -28,20 +31,6 @@ pub struct DocumentMetadata { pub document_to_viewport: DAffine2, } -impl Default for DocumentMetadata { - fn default() -> Self { - Self { - upstream_footprints: HashMap::new(), - local_transforms: HashMap::new(), - structure: HashMap::new(), - vector_modify: HashMap::new(), - click_targets: HashMap::new(), - clip_targets: HashSet::new(), - document_to_viewport: DAffine2::IDENTITY, - } - } -} - // ================================= // DocumentMetadata: Layer iterators // ================================= @@ -91,6 +80,36 @@ impl DocumentMetadata { footprint * local_transform } + pub fn transform_to_viewport_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 { + // We're not allowed to convert the root parent to a node id + if layer == LayerNodeIdentifier::ROOT_PARENT { + return self.document_to_viewport; + } + + let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport); + + let mut use_local = true; + let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface); + if let Some(path_node) = graph_layer.upstream_node_id_from_name("Path") { + if let Some(&source) = self.first_instance_source_ids.get(&layer.to_node()) { + if !network_interface + .upstream_flow_back_from_nodes(vec![path_node], &[], FlowType::HorizontalFlow) + .any(|upstream| Some(upstream) == source) + { + use_local = false; + info!("Local transform is invalid — using the identity for the local transform instead") + } + } + } + let local_transform = use_local.then(|| self.local_transforms.get(&layer.to_node()).copied()).flatten().unwrap_or_default(); + + footprint * local_transform + } + + pub fn transform_to_document_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 { + self.document_to_viewport.inverse() * self.transform_to_viewport_if_feeds(layer, network_interface) + } + pub fn transform_to_viewport_with_first_transform_node_if_group(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 { let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport); let local_transform = self.local_transforms.get(&layer.to_node()).copied(); diff --git a/editor/src/messages/portfolio/document/utility_types/mod.rs b/editor/src/messages/portfolio/document/utility_types/mod.rs index e9ad9ae117..8bed0dbb85 100644 --- a/editor/src/messages/portfolio/document/utility_types/mod.rs +++ b/editor/src/messages/portfolio/document/utility_types/mod.rs @@ -5,3 +5,4 @@ pub mod misc; pub mod network_interface; pub mod nodes; pub mod transformation; +pub mod wires; diff --git a/editor/src/messages/portfolio/document/utility_types/network_interface.rs b/editor/src/messages/portfolio/document/utility_types/network_interface.rs index 2566650e70..404d3486a0 100644 --- a/editor/src/messages/portfolio/document/utility_types/network_interface.rs +++ b/editor/src/messages/portfolio/document/utility_types/network_interface.rs @@ -5,6 +5,7 @@ use crate::consts::{EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP, EXPORTS_TO_TOP_EDGE_PIXEL_G use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; use crate::messages::portfolio::document::node_graph::document_node_definitions::{DocumentNodeDefinition, resolve_document_node_type}; use crate::messages::portfolio::document::node_graph::utility_types::{Direction, FrontendClickTargets, FrontendGraphDataType, FrontendGraphInput, FrontendGraphOutput}; +use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire}; use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::tool_messages::tool_prelude::NumberInputMode; use bezier_rs::Subpath; @@ -59,6 +60,27 @@ impl PartialEq for NodeNetworkInterface { } } +impl NodeNetworkInterface { + /// Add DocumentNodePath input to the PathModifyNode protonode + pub fn migrate_path_modify_node(&mut self) { + fix_network(&mut self.network); + fn fix_network(network: &mut NodeNetwork) { + for node in network.nodes.values_mut() { + if let Some(network) = node.implementation.get_network_mut() { + fix_network(network); + } + if let DocumentNodeImplementation::ProtoNode(protonode) = &node.implementation { + if protonode.name.contains("PathModifyNode") { + if node.inputs.len() < 3 { + node.inputs.push(NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)); + } + } + } + } + } + } +} + // Public immutable getters for the network interface impl NodeNetworkInterface { // TODO: Make private and use .field_name getter methods @@ -311,7 +333,7 @@ impl NodeNetworkInterface { log::error!("Could not get node {node_id} in number_of_displayed_inputs"); return 0; }; - node.inputs.iter().filter(|input| input.is_exposed_to_frontend(network_path.is_empty())).count() + node.inputs.iter().filter(|input| input.is_exposed()).count() } pub fn number_of_inputs(&self, node_id: &NodeId, network_path: &[NodeId]) -> usize { @@ -456,6 +478,12 @@ impl NodeNetworkInterface { node_template } + /// Try and get the [`DocumentNodeDefinition`] for a node + pub fn get_node_definition(&self, network_path: &[NodeId], node_id: NodeId) -> Option<&DocumentNodeDefinition> { + let metadata = self.node_metadata(&node_id, network_path)?; + resolve_document_node_type(metadata.persistent_metadata.reference.as_ref()?) + } + pub fn input_from_connector(&self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<&NodeInput> { let Some(network) = self.nested_network(network_path) else { log::error!("Could not get network in input_from_connector"); @@ -473,12 +501,6 @@ impl NodeNetworkInterface { } } - /// Try and get the [`DocumentNodeDefinition`] for a node - pub fn get_node_definition(&self, network_path: &[NodeId], node_id: NodeId) -> Option<&DocumentNodeDefinition> { - let metadata = self.node_metadata(&node_id, network_path)?; - resolve_document_node_type(metadata.persistent_metadata.reference.as_ref()?) - } - /// Try and get the [`Type`] for any [`InputConnector`] based on the `self.resolved_types`. fn node_type_from_compiled(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Option<(Type, TypeSource)> { let (node_id, input_index) = match *input_connector { @@ -489,11 +511,8 @@ impl NodeNetworkInterface { return Some((concrete!(graphene_std::ArtboardGroupTable), TypeSource::OuterMostExportDefault)); }; - let output_type = self.output_types(encapsulating_node_id, encapsulating_node_id_path).into_iter().nth(export_index).flatten(); - if output_type.is_none() { - warn!("Could not find output type for export node"); - } - return output_type; + let output_type = self.output_type(encapsulating_node_id, export_index, encapsulating_node_id_path); + return Some(output_type); } }; let Some(node) = self.document_node(&node_id, network_path) else { @@ -655,7 +674,7 @@ impl NodeNetworkInterface { let input_type = self.input_type(&InputConnector::node(*node_id, iterator_index), network_path).0; // Value inputs are stored as concrete, so they are compared to the nested type. Node inputs are stored as fn, so they are compared to the entire type. // For example a node input of (Footprint) -> VectorData would not be compatible with () -> VectorData - node_io.inputs[iterator_index].clone().nested_type() == &input_type || node_io.inputs[iterator_index] == input_type + node_io.inputs.get(iterator_index).map(|ty| ty.nested_type().clone()).as_ref() == Some(&input_type) || node_io.inputs.get(iterator_index) == Some(&input_type) }); if valid_implementation { node_io.inputs.get(*input_index).cloned() } else { None } }) @@ -699,60 +718,51 @@ impl NodeNetworkInterface { /// /// This function assumes that export indices and node IDs always exist within their respective /// collections. It will panic if these assumptions are violated. - pub fn output_types(&self, node_id: &NodeId, network_path: &[NodeId]) -> Vec> { + /// + pub fn output_type(&self, node_id: &NodeId, output_index: usize, network_path: &[NodeId]) -> (Type, TypeSource) { let Some(implementation) = self.implementation(node_id, network_path) else { - log::error!("Could not get node {node_id} in output_types"); - return Vec::new(); + log::error!("Could not get output type for node {node_id} output index {output_index}. This node is no longer supported, and needs to be upgraded."); + return (concrete!(()), TypeSource::Error("Could not get implementation")); }; - let mut output_types = Vec::new(); - // If the node is not a protonode, get types by traversing across exports until a proto node is reached. match &implementation { graph_craft::document::DocumentNodeImplementation::Network(internal_network) => { - for export in internal_network.exports.iter() { - match export { - NodeInput::Node { - node_id: nested_node_id, - output_index, - .. - } => { - let nested_output_types = self.output_types(nested_node_id, &[network_path, &[*node_id]].concat()); - let Some(nested_nodes_output_types) = nested_output_types.get(*output_index) else { - log::error!("Could not get nested nodes output in output_types"); - return Vec::new(); - }; - output_types.push(nested_nodes_output_types.clone()); - } - NodeInput::Value { tagged_value, .. } => { - output_types.push(Some((tagged_value.ty(), TypeSource::TaggedValue))); - } - - NodeInput::Network { .. } => { - // https://github.com/GraphiteEditor/Graphite/issues/1762 - log::error!("Network input type cannot be connected to export"); - return Vec::new(); - } - NodeInput::Scope(_) => todo!(), - NodeInput::Inline(_) => todo!(), - NodeInput::Reflection(_) => todo!(), + let Some(export) = internal_network.exports.get(output_index) else { + return (concrete!(()), TypeSource::Error("Could not get export index")); + }; + match export { + NodeInput::Node { + node_id: nested_node_id, + output_index, + .. + } => self.output_type(nested_node_id, *output_index, &[network_path, &[*node_id]].concat()), + NodeInput::Value { tagged_value, .. } => (tagged_value.ty(), TypeSource::TaggedValue), + NodeInput::Network { .. } => { + // let mut encapsulating_path = network_path.to_vec(); + // let encapsulating_node = encapsulating_path.pop().expect("No imports exist in document network"); + // self.input_type(&InputConnector::node(encapsulating_node, *import_index), network_path) + (concrete!(()), TypeSource::Error("Could not type from network")) } + NodeInput::Scope(_) => todo!(), + NodeInput::Inline(_) => todo!(), + NodeInput::Reflection(_) => todo!(), } } graph_craft::document::DocumentNodeImplementation::ProtoNode(protonode) => { let node_id_path = &[network_path, &[*node_id]].concat(); - let primary_output_type = self.resolved_types.types.get(node_id_path).map(|ty| (ty.output.clone(), TypeSource::Compiled)).or_else(|| { - let node_types = random_protonode_implementation(protonode)?; - Some((node_types.return_value.clone(), TypeSource::RandomProtonodeImplementation)) - }); - - output_types.push(primary_output_type); - } - graph_craft::document::DocumentNodeImplementation::Extract => { - output_types.push(Some((concrete!(()), TypeSource::Error("extract node")))); + self.resolved_types + .types + .get(node_id_path) + .map(|ty| (ty.output.clone(), TypeSource::Compiled)) + .or_else(|| { + let node_types = random_protonode_implementation(protonode)?; + Some((node_types.return_value.clone(), TypeSource::RandomProtonodeImplementation)) + }) + .unwrap_or((concrete!(()), TypeSource::Error("Could not get protonode implementation"))) } + graph_craft::document::DocumentNodeImplementation::Extract => (concrete!(()), TypeSource::Error("extract node")), } - output_types } pub fn position(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option { @@ -782,10 +792,6 @@ impl NodeNetworkInterface { .filter_map(|(import_index, click_target)| { // Get import name from parent node metadata input, which must match the number of imports. // Empty string means to use type, or "Import + index" if type can't be determined - let properties_row = self - .encapsulating_node_metadata(network_path) - .and_then(|encapsulating_metadata| encapsulating_metadata.persistent_metadata.input_properties.get(*import_index).cloned()) - .unwrap_or_default(); let mut import_metadata = None; @@ -796,12 +802,7 @@ impl NodeNetworkInterface { let (input_type, type_source) = self.input_type(&InputConnector::node(encapsulating_node_id, *import_index), &encapsulating_path); let data_type = FrontendGraphDataType::displayed_type(&input_type, &type_source); - let input_name = properties_row.input_name.as_str(); - let import_name = if input_name.is_empty() { - input_type.clone().nested_type().to_string() - } else { - input_name.to_string() - }; + let (name, description) = self.displayed_input_name_and_description(&encapsulating_node_id, *import_index, &encapsulating_path); let connected_to = self .outward_wires(network_path) @@ -815,9 +816,9 @@ impl NodeNetworkInterface { import_metadata = Some(( FrontendGraphOutput { data_type, - name: import_name, - description: String::new(), - resolved_type: Some(format!("{input_type:?}")), + name, + description, + resolved_type: format!("{:?}", input_type), connected_to, }, click_target, @@ -835,51 +836,14 @@ impl NodeNetworkInterface { import_export_ports .input_ports .iter() - .filter_map(|(export_index, click_target)| { - let Some(network) = self.nested_network(network_path) else { - log::error!("Could not get network in frontend_exports"); - return None; - }; + .map(|(export_index, click_target)| { + let export_type = self.input_type(&InputConnector::Export(*export_index), network_path); + let data_type = FrontendGraphDataType::displayed_type(&export_type.0, &TypeSource::TaggedValue); - let Some(export) = network.exports.get(*export_index) else { - log::error!("Could not get export {export_index} in frontend_exports"); - return None; - }; - - let (frontend_data_type, input_type) = if let NodeInput::Node { node_id, output_index, .. } = export { - let output_types = self.output_types(node_id, network_path); - - if let Some((output_type, type_source)) = output_types.get(*output_index).cloned().flatten() { - (FrontendGraphDataType::displayed_type(&output_type, &type_source), Some((output_type, type_source))) - } else { - (FrontendGraphDataType::General, None) - } - } else if let NodeInput::Value { tagged_value, .. } = export { - ( - FrontendGraphDataType::displayed_type(&tagged_value.ty(), &TypeSource::TaggedValue), - Some((tagged_value.ty(), TypeSource::TaggedValue)), - ) - // TODO: Get type from parent node input when is possible - // else if let NodeInput::Network { import_type, .. } = export { - // (FrontendGraphDataType::with_type(import_type), Some(import_type.clone())) - // } - } else { - (FrontendGraphDataType::General, None) - }; - - // First import index is visually connected to the root node instead of its actual export input so previewing does not change the connection - let connected_to = if *export_index == 0 { - self.root_node(network_path).map(|root_node| OutputConnector::node(root_node.node_id, root_node.output_index)) - } else if let NodeInput::Node { node_id, output_index, .. } = export { - Some(OutputConnector::node(*node_id, *output_index)) - } else if let NodeInput::Network { import_index, .. } = export { - Some(OutputConnector::Import(*import_index)) - } else { - None - }; + let connected_to = self.upstream_output_connector(&InputConnector::Export(*export_index), network_path); // Get export name from parent node metadata input, which must match the number of exports. - // Empty string means to use type, or "Export + index" if type can't be determined + // Empty string means to use type, or "Export + index" if type is empty determined let export_name = if network_path.is_empty() { "Canvas".to_string() } else { @@ -890,24 +854,23 @@ impl NodeNetworkInterface { let export_name = if !export_name.is_empty() { export_name + } else if *export_type.0.nested_type() != concrete!(()) { + export_type.0.nested_type().to_string() } else { - input_type - .clone() - .map(|(input_type, _)| input_type.nested_type().to_string()) - .unwrap_or(format!("Export {}", export_index + 1)) + format!("Export {}", *export_index + 1) }; - Some(( + ( FrontendGraphInput { - data_type: frontend_data_type, + data_type, name: export_name, description: String::new(), - resolved_type: input_type.map(|(export_type, _source)| format!("{export_type:?}")), + resolved_type: format!("{:?}", export_type.0), valid_types: self.valid_input_types(&InputConnector::Export(*export_index), network_path).iter().map(|ty| ty.to_string()).collect(), connected_to, }, click_target, - )) + ) }) .filter_map(|(export_metadata, output_port)| output_port.bounding_box().map(|bounding_box| (export_metadata, bounding_box[0].x as i32, bounding_box[0].y as i32))) .collect::>() @@ -956,14 +919,7 @@ impl NodeNetworkInterface { log::error!("Could not get node {node_id} in upstream_nodes_below_layer"); continue; }; - potential_upstream_nodes.extend( - chain_node - .inputs - .iter() - .filter(|input| input.is_exposed_to_frontend(network_path.is_empty())) - .skip(1) - .filter_map(|node_input| node_input.as_node()), - ) + potential_upstream_nodes.extend(chain_node.inputs.iter().filter(|input| input.is_exposed()).skip(1).filter_map(|node_input| node_input.as_node())) } // Get the node feeding into the left input of the chain @@ -976,7 +932,7 @@ impl NodeNetworkInterface { if let Some(primary_node_id) = current_node .inputs .iter() - .filter(|input| input.is_exposed_to_frontend(network_path.is_empty())) + .filter(|input| input.is_exposed()) .nth(if self.is_layer(¤t_node_id, network_path) { 1 } else { 0 }) .and_then(|left_input| left_input.as_node()) { @@ -1110,7 +1066,7 @@ impl NodeNetworkInterface { pub fn reference(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&Option> { let Some(node_metadata) = self.node_metadata(node_id, network_path) else { - log::error!("Could not get reference"); + log::error!("Could not get reference for node: {:?}", node_id); return None; }; Some(&node_metadata.persistent_metadata.reference) @@ -1124,55 +1080,39 @@ impl NodeNetworkInterface { Some(&node.implementation) } - pub fn input_name<'a>(&'a self, node_id: NodeId, index: usize, network_path: &[NodeId]) -> Option<&'a str> { - let Some(input_row) = self.input_properties_row(&node_id, index, network_path) else { - log::error!("Could not get input_name for node {node_id} index {index}"); - return None; + pub fn input_data(&self, node_id: &NodeId, index: usize, key: &str, network_path: &[NodeId]) -> Option<&Value> { + let metadata = self + .node_metadata(node_id, network_path) + .and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get(index))?; + metadata.persistent_metadata.input_data.get(key) + } + pub fn persistent_input_metadata(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&InputPersistentMetadata> { + let metadata = self + .node_metadata(node_id, network_path) + .and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get(index))?; + Some(&metadata.persistent_metadata) + } + + fn transient_input_metadata(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&InputTransientMetadata> { + let metadata = self + .node_metadata(node_id, network_path) + .and_then(|node_metadata| node_metadata.persistent_metadata.input_metadata.get(index))?; + Some(&metadata.transient_metadata) + } + + /// Returns the input name to display in the properties panel. If the name is empty then the type is used. + pub fn displayed_input_name_and_description(&mut self, node_id: &NodeId, input_index: usize, network_path: &[NodeId]) -> (String, String) { + let Some(input_metadata) = self.persistent_input_metadata(node_id, input_index, network_path) else { + log::warn!("input metadata not found in displayed_input_name_and_description"); + return (String::new(), String::new()); }; - let name = input_row.input_name.as_str(); - if !name.is_empty() { - Some(name) + let description = input_metadata.input_description.to_string(); + let name = if input_metadata.input_name.is_empty() { + self.input_type(&InputConnector::node(*node_id, input_index), network_path).0.nested_type().to_string() } else { - let node_definition = resolve_document_node_type(self.reference(&node_id, network_path)?.as_ref()?)?; - let rows = &node_definition.node_template.persistent_node_metadata.input_properties; - - rows.get(index).map(|row| row.input_name.as_str()) - } - } - - pub fn input_description<'a>(&'a self, node_id: NodeId, index: usize, network_path: &[NodeId]) -> Option<&'a str> { - let Some(input_row) = self.input_properties_row(&node_id, index, network_path) else { - log::error!("Could not get input_row in input_description"); - return None; + input_metadata.input_name.to_string() }; - let description = input_row.input_description.as_str(); - if !description.is_empty() && description != "TODO" { - Some(description) - } else { - let node_definition = resolve_document_node_type(self.reference(&node_id, network_path)?.as_ref()?)?; - let rows = &node_definition.node_template.persistent_node_metadata.input_properties; - - rows.get(index).map(|row| row.input_description.as_str()) - } - } - - pub fn input_properties_row(&self, node_id: &NodeId, index: usize, network_path: &[NodeId]) -> Option<&PropertiesRow> { - self.node_metadata(node_id, network_path) - .and_then(|node_metadata| node_metadata.persistent_metadata.input_properties.get(index)) - } - - pub fn insert_input_properties_row(&mut self, node_id: &NodeId, index: usize, network_path: &[NodeId], row: PropertiesRow) { - let _ = self - .node_metadata_mut(node_id, network_path) - .map(|node_metadata| node_metadata.persistent_metadata.input_properties.insert(index - 1, row)); - } - - pub fn input_metadata(&self, node_id: &NodeId, index: usize, field: &str, network_path: &[NodeId]) -> Option<&Value> { - let Some(input_row) = self.input_properties_row(node_id, index, network_path) else { - log::error!("Could not get input_row in get_input_metadata"); - return None; - }; - input_row.input_data.get(field) + (name, description) } /// Returns the display name of the node. If the display name is empty, it will return "Untitled Node" or "Untitled Layer" depending on the node type. @@ -1182,6 +1122,7 @@ impl NodeNetworkInterface { .expect("Could not get persistent node metadata in untitled_layer_label") .persistent_metadata .is_layer(); + let Some(reference) = self.reference(node_id, network_path) else { log::error!("Could not get reference in untitled_layer_label"); return "".to_string(); @@ -1195,7 +1136,7 @@ impl NodeNetworkInterface { }; if display_name.is_empty() { - if is_layer && *reference == Some("Merge".to_string()) { + if is_layer { "Untitled Layer".to_string() } else { reference.clone().unwrap_or("Untitled Node".to_string()) @@ -1974,6 +1915,7 @@ impl NodeNetworkInterface { if !network_metadata.transient_metadata.import_export_ports.is_loaded() { self.load_import_export_ports(network_path); } + let Some(network_metadata) = self.network_metadata(network_path) else { log::error!("Could not get nested network_metadata in export_ports"); return None; @@ -2064,6 +2006,30 @@ impl NodeNetworkInterface { return; }; network_metadata.transient_metadata.import_export_ports.unload(); + + // Always unload all wires connected to them as well + let number_of_imports = self.number_of_imports(network_path); + let Some(outward_wires) = self.outward_wires(network_path) else { + log::error!("Could not get outward wires in remove_import"); + return; + }; + let mut input_connectors = Vec::new(); + for import_index in 0..number_of_imports { + let Some(outward_wires_for_import) = outward_wires.get(&OutputConnector::Import(import_index)).cloned() else { + log::error!("Could not get outward wires for import in remove_import"); + return; + }; + input_connectors.extend(outward_wires_for_import); + } + let Some(network) = self.nested_network(network_path) else { + return; + }; + for export_index in 0..network.exports.len() { + input_connectors.push(InputConnector::Export(export_index)); + } + for input in &input_connectors { + self.unload_wire(input, network_path); + } } pub fn modify_import_export(&mut self, network_path: &[NodeId]) -> Option<&ModifyImportExportClickTarget> { @@ -2334,7 +2300,7 @@ impl NodeNetworkInterface { return; }; network_metadata.transient_metadata.all_nodes_bounding_box.unload(); - network_metadata.transient_metadata.import_export_ports.unload(); + self.unload_import_export_ports(network_path); } pub fn outward_wires(&mut self, network_path: &[NodeId]) -> Option<&HashMap>> { @@ -2508,6 +2474,293 @@ impl NodeNetworkInterface { } } + pub fn get_input_center(&mut self, input: &InputConnector, network_path: &[NodeId]) -> Option { + let (ports, index) = match input { + InputConnector::Node { node_id, input_index } => { + let node_click_target = self.node_click_targets(node_id, network_path)?; + (&node_click_target.port_click_targets, input_index) + } + InputConnector::Export(export_index) => { + let ports = self.import_export_ports(network_path)?; + (ports, export_index) + } + }; + ports + .input_ports + .iter() + .find_map(|(input_index, click_target)| if index == input_index { click_target.bounding_box_center() } else { None }) + } + + pub fn get_output_center(&mut self, output: &OutputConnector, network_path: &[NodeId]) -> Option { + let (ports, index) = match output { + OutputConnector::Node { node_id, output_index } => { + let node_click_target = self.node_click_targets(node_id, network_path)?; + (&node_click_target.port_click_targets, output_index) + } + OutputConnector::Import(import_index) => { + let ports = self.import_export_ports(network_path)?; + (ports, import_index) + } + }; + ports + .output_ports + .iter() + .find_map(|(input_index, click_target)| if index == input_index { click_target.bounding_box_center() } else { None }) + } + + pub fn newly_loaded_input_wire(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option { + if !self.wire_is_loaded(input, network_path) { + self.load_wire(input, graph_wire_style, network_path); + } else { + return None; + } + + let wire = match input { + InputConnector::Node { node_id, input_index } => { + let input_metadata = self.transient_input_metadata(node_id, *input_index, network_path)?; + let TransientMetadata::Loaded(wire) = &input_metadata.wire else { + log::error!("Could not load wire for input: {:?}", input); + return None; + }; + wire.clone() + } + InputConnector::Export(export_index) => { + let network_metadata = self.network_metadata(network_path)?; + let Some(TransientMetadata::Loaded(wire)) = network_metadata.transient_metadata.wires.get(*export_index) else { + log::error!("Could not load wire for input: {:?}", input); + return None; + }; + wire.clone() + } + }; + Some(wire) + } + + pub fn wire_is_loaded(&mut self, input: &InputConnector, network_path: &[NodeId]) -> bool { + match input { + InputConnector::Node { node_id, input_index } => { + let Some(input_metadata) = self.transient_input_metadata(node_id, *input_index, network_path) else { + log::error!("Input metadata should always exist for input"); + return false; + }; + input_metadata.wire.is_loaded() + } + InputConnector::Export(export_index) => { + let Some(network_metadata) = self.network_metadata(network_path) else { + return false; + }; + match network_metadata.transient_metadata.wires.get(*export_index) { + Some(wire) => wire.is_loaded(), + None => false, + } + } + } + } + + fn load_wire(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) { + let dashed = match self.previewing(network_path) { + Previewing::Yes { .. } => match input { + InputConnector::Node { .. } => false, + InputConnector::Export(export_index) => *export_index == 0, + }, + Previewing::No => false, + }; + let Some(wire) = self.wire_path_from_input(input, graph_wire_style, dashed, network_path) else { + return; + }; + match input { + InputConnector::Node { node_id, input_index } => { + let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else { return }; + let Some(input_metadata) = node_metadata.persistent_metadata.input_metadata.get_mut(*input_index) else { + log::error!("Node metadata must exist on node: {input:?}"); + return; + }; + let wire_update = WirePathUpdate { + id: *node_id, + input_index: *input_index, + wire_path_update: Some(wire), + }; + input_metadata.transient_metadata.wire = TransientMetadata::Loaded(wire_update); + } + InputConnector::Export(export_index) => { + let Some(network_metadata) = self.network_metadata_mut(network_path) else { return }; + if *export_index >= network_metadata.transient_metadata.wires.len() { + network_metadata.transient_metadata.wires.resize(export_index + 1, TransientMetadata::Unloaded); + } + let Some(input_metadata) = network_metadata.transient_metadata.wires.get_mut(*export_index) else { + return; + }; + let wire_update = WirePathUpdate { + id: NodeId(u64::MAX), + input_index: *export_index, + wire_path_update: Some(wire), + }; + *input_metadata = TransientMetadata::Loaded(wire_update); + } + } + } + + pub fn all_input_connectors(&self, network_path: &[NodeId]) -> Vec { + let mut input_connectors = Vec::new(); + let Some(network) = self.nested_network(network_path) else { + log::error!("Could not get nested network in all_input_connectors"); + return Vec::new(); + }; + for export_index in 0..network.exports.len() { + input_connectors.push(InputConnector::Export(export_index)); + } + for (node_id, node) in &network.nodes { + for input_index in 0..node.inputs.len() { + input_connectors.push(InputConnector::node(*node_id, input_index)); + } + } + input_connectors + } + + pub fn node_graph_input_connectors(&self, network_path: &[NodeId]) -> Vec { + self.all_input_connectors(network_path) + .into_iter() + .filter(|input| self.input_from_connector(input, network_path).is_some_and(|input| input.is_exposed())) + .collect() + } + + /// Maps to the frontend representation of a wire start. Includes disconnected value wire inputs. + pub fn node_graph_wire_inputs(&self, network_path: &[NodeId]) -> Vec<(NodeId, usize)> { + self.node_graph_input_connectors(network_path) + .iter() + .map(|input| match input { + InputConnector::Node { node_id, input_index } => (*node_id, *input_index), + InputConnector::Export(export_index) => (NodeId(u64::MAX), *export_index), + }) + .chain(std::iter::once((NodeId(u64::MAX), usize::MAX))) + .collect() + } + + fn unload_wires_for_node(&mut self, node_id: &NodeId, network_path: &[NodeId]) { + let number_of_outputs = self.number_of_outputs(node_id, network_path); + let Some(outward_wires) = self.outward_wires(network_path) else { + log::error!("Could not get outward wires in reorder_export"); + return; + }; + let mut input_connectors = Vec::new(); + for output_index in 0..number_of_outputs { + let Some(inputs) = outward_wires.get(&OutputConnector::node(*node_id, output_index)) else { + continue; + }; + input_connectors.extend(inputs.clone()) + } + for input_index in 0..self.number_of_inputs(node_id, network_path) { + input_connectors.push(InputConnector::node(*node_id, input_index)); + } + for input in input_connectors { + self.unload_wire(&input, network_path); + } + } + + pub fn unload_wire(&mut self, input: &InputConnector, network_path: &[NodeId]) { + match input { + InputConnector::Node { node_id, input_index } => { + let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else { + return; + }; + let Some(input_metadata) = node_metadata.persistent_metadata.input_metadata.get_mut(*input_index) else { + log::error!("Node metadata must exist on node: {input:?}"); + return; + }; + input_metadata.transient_metadata.wire = TransientMetadata::Unloaded; + } + InputConnector::Export(export_index) => { + let Some(network_metadata) = self.network_metadata_mut(network_path) else { + return; + }; + if *export_index >= network_metadata.transient_metadata.wires.len() { + network_metadata.transient_metadata.wires.resize(export_index + 1, TransientMetadata::Unloaded); + } + let Some(input_metadata) = network_metadata.transient_metadata.wires.get_mut(*export_index) else { + return; + }; + *input_metadata = TransientMetadata::Unloaded; + } + } + } + + /// When previewing, there may be a second path to the root node. + pub fn wire_to_root(&mut self, graph_wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option { + let input = InputConnector::Export(0); + let current_export = self.upstream_output_connector(&input, network_path)?; + + let root_node = match self.previewing(network_path) { + Previewing::Yes { root_node_to_restore } => root_node_to_restore, + Previewing::No => None, + }?; + + if Some(root_node.node_id) == current_export.node_id() { + return None; + } + let Some(input_position) = self.get_input_center(&input, network_path) else { + log::error!("Could not get dom rect for wire end in root node: {:?}", input); + return None; + }; + let upstream_output = OutputConnector::node(root_node.node_id, root_node.output_index); + let Some(output_position) = self.get_output_center(&upstream_output, network_path) else { + log::error!("Could not get dom rect for wire start in root node: {:?}", upstream_output); + return None; + }; + let vertical_end = input.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path) && input.input_index() == 0); + let vertical_start: bool = upstream_output.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path)); + let thick = vertical_end && vertical_start; + let vector_wire = build_vector_wire(output_position, input_position, vertical_start, vertical_end, graph_wire_style); + + let mut path_string = String::new(); + let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY); + let data_type = FrontendGraphDataType::from_type(&self.input_type(&input, network_path).0); + let wire_path_update = Some(WirePath { + path_string, + data_type, + thick, + dashed: false, + }); + + Some(WirePathUpdate { + id: NodeId(u64::MAX), + input_index: usize::MAX, + wire_path_update, + }) + } + + /// Returns the vector subpath and a boolean of whether the wire should be thick. + pub fn vector_wire_from_input(&mut self, input: &InputConnector, wire_style: GraphWireStyle, network_path: &[NodeId]) -> Option<(Subpath, bool)> { + let Some(input_position) = self.get_input_center(input, network_path) else { + log::error!("Could not get dom rect for wire end: {:?}", input); + return None; + }; + // An upstream output could not be found, so the wire does not exist, but it should still be loaded as as empty vector + let Some(upstream_output) = self.upstream_output_connector(input, network_path) else { + return Some((Subpath::from_anchors(std::iter::empty(), false), false)); + }; + let Some(output_position) = self.get_output_center(&upstream_output, network_path) else { + log::error!("Could not get dom rect for wire start: {:?}", upstream_output); + return None; + }; + let vertical_end = input.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path) && input.input_index() == 0); + let vertical_start = upstream_output.node_id().is_some_and(|node_id| self.is_layer(&node_id, network_path)); + let thick = vertical_end && vertical_start; + Some((build_vector_wire(output_position, input_position, vertical_start, vertical_end, wire_style), thick)) + } + + pub fn wire_path_from_input(&mut self, input: &InputConnector, graph_wire_style: GraphWireStyle, dashed: bool, network_path: &[NodeId]) -> Option { + let (vector_wire, thick) = self.vector_wire_from_input(input, graph_wire_style, network_path)?; + let mut path_string = String::new(); + let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY); + let data_type = FrontendGraphDataType::from_type(&self.input_type(input, network_path).0); + Some(WirePath { + path_string, + data_type, + thick, + dashed, + }) + } + pub fn node_click_targets(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNodeClickTargets> { self.try_load_node_click_targets(node_id, network_path); self.try_get_node_click_targets(node_id, network_path) @@ -2746,17 +2999,14 @@ impl NodeNetworkInterface { return; }; node_metadata.transient_metadata.click_targets.unload(); + self.unload_wires_for_node(node_id, network_path); } pub fn unload_upstream_node_click_targets(&mut self, node_ids: Vec, network_path: &[NodeId]) { let upstream_nodes = self.upstream_flow_back_from_nodes(node_ids, network_path, FlowType::UpstreamFlow).collect::>(); for upstream_id in &upstream_nodes { - let Some(node_metadata) = self.node_metadata_mut(upstream_id, network_path) else { - log::error!("Could not get node_metadata for node {upstream_id}"); - return; - }; - node_metadata.transient_metadata.click_targets.unload(); + self.unload_node_click_targets(upstream_id, network_path); } } @@ -2768,11 +3018,7 @@ impl NodeNetworkInterface { let upstream_nodes = network.nodes.keys().cloned().collect::>(); for upstream_id in &upstream_nodes { - let Some(node_metadata) = self.node_metadata_mut(upstream_id, network_path) else { - log::error!("Could not get node_metadata for node {upstream_id}"); - return; - }; - node_metadata.transient_metadata.click_targets.unload(); + self.unload_node_click_targets(upstream_id, network_path); } } } @@ -2915,8 +3161,8 @@ impl NodeNetworkInterface { log::error!("Could not get node {node_id} in is_eligible_to_be_layer"); return false; }; - let input_count = node.inputs.iter().take(2).filter(|input| input.is_exposed_to_frontend(network_path.is_empty())).count(); - let parameters_hidden = node.inputs.iter().skip(2).all(|input| !input.is_exposed_to_frontend(network_path.is_empty())); + let input_count = node.inputs.iter().take(2).filter(|input| input.is_exposed()).count(); + let parameters_hidden = node.inputs.iter().skip(2).all(|input| !input.is_exposed()); let output_count = self.number_of_outputs(node_id, network_path); self.node_metadata(node_id, network_path) @@ -3080,7 +3326,7 @@ impl NodeNetworkInterface { }; let mut displayed_index = 0; for i in 0..*input_index { - if node.inputs[i].is_exposed_to_frontend(network_path.is_empty()) { + if node.inputs[i].is_exposed() { displayed_index += 1; } } @@ -3288,6 +3534,11 @@ impl NodeNetworkInterface { self.document_metadata.local_transforms = local_transforms; } + /// Update the cached first instance source id of the layers + pub fn update_first_instance_source_id(&mut self, new: HashMap>) { + self.document_metadata.first_instance_source_ids = new; + } + /// Update the cached click targets of the layers pub fn update_click_targets(&mut self, new_click_targets: HashMap>) { self.document_metadata.click_targets = new_click_targets; @@ -3417,9 +3668,10 @@ impl NodeNetworkInterface { } // Update the click targets for the encapsulating node, if it exists. There is no encapsulating node if the network is the document network - if let Some(encapsulating_node_metadata_mut) = self.encapsulating_node_metadata_mut(network_path) { - encapsulating_node_metadata_mut.transient_metadata.click_targets.unload(); - }; + let mut path = network_path.to_vec(); + if let Some(encapsulating_node) = path.pop() { + self.unload_node_click_targets(&encapsulating_node, &path); + } // If the export is inserted as the first input or second input, and the parent network is the document_network, then it may have affected the document metadata structure if network_path.len() == 1 && (insert_index == 0 || insert_index == 1) { @@ -3464,9 +3716,9 @@ impl NodeNetworkInterface { }; let new_input = (input_name, input_description).into(); if insert_index == -1 { - node_metadata.persistent_metadata.input_properties.push(new_input); + node_metadata.persistent_metadata.input_metadata.push(new_input); } else { - node_metadata.persistent_metadata.input_properties.insert(insert_index as usize, new_input); + node_metadata.persistent_metadata.input_metadata.insert(insert_index as usize, new_input); } // Clear the reference to the nodes definition @@ -3600,7 +3852,7 @@ impl NodeNetworkInterface { log::error!("Could not get encapsulating node metadata in remove_export"); return; }; - encapsulating_node_metadata.persistent_metadata.input_properties.remove(import_index); + encapsulating_node_metadata.persistent_metadata.input_metadata.remove(import_index); encapsulating_node_metadata.persistent_metadata.reference = None; // Update the metadata for the encapsulating node @@ -3737,8 +3989,8 @@ impl NodeNetworkInterface { return; }; - let properties_row = encapsulating_node_metadata.persistent_metadata.input_properties.remove(start_index); - encapsulating_node_metadata.persistent_metadata.input_properties.insert(end_index, properties_row); + let properties_row = encapsulating_node_metadata.persistent_metadata.input_metadata.remove(start_index); + encapsulating_node_metadata.persistent_metadata.input_metadata.insert(end_index, properties_row); encapsulating_node_metadata.persistent_metadata.reference = None; // Update the metadata for the outer network @@ -3799,9 +4051,8 @@ impl NodeNetworkInterface { self.unload_stack_dependents(network_path); } - // TODO: Eventually remove this document upgrade code - /// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts - pub fn replace_implementation(&mut self, node_id: &NodeId, network_path: &[NodeId], implementation: DocumentNodeImplementation) { + /// Replaces the implementation and corresponding metadata. + pub fn replace_implementation(&mut self, node_id: &NodeId, network_path: &[NodeId], new_template: &mut NodeTemplate) { let Some(network) = self.network_mut(network_path) else { log::error!("Could not get nested network in set_implementation"); return; @@ -3810,17 +4061,74 @@ impl NodeNetworkInterface { log::error!("Could not get node in set_implementation"); return; }; - node.implementation = implementation; - } - - // TODO: Eventually remove this document upgrade code - /// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts - pub fn replace_implementation_metadata(&mut self, node_id: &NodeId, network_path: &[NodeId], metadata: DocumentNodePersistentMetadata) { - let Some(node_metadata) = self.node_metadata_mut(node_id, network_path) else { - log::error!("Could not get network metadata in set implementation"); + let new_implementation = std::mem::take(&mut new_template.document_node.implementation); + let _ = std::mem::replace(&mut node.implementation, new_implementation); + let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { + log::error!("Could not get metadata in set_implementation"); return; }; - node_metadata.persistent_metadata.network_metadata = metadata.network_metadata; + let new_metadata = std::mem::take(&mut new_template.persistent_node_metadata.network_metadata); + let _ = std::mem::replace(&mut metadata.persistent_metadata.network_metadata, new_metadata); + } + + /// Replaces the inputs and corresponding metadata. + pub fn replace_inputs(&mut self, node_id: &NodeId, network_path: &[NodeId], new_template: &mut NodeTemplate) -> Option> { + let Some(network) = self.network_mut(network_path) else { + log::error!("Could not get nested network in set_implementation"); + return None; + }; + let Some(node) = network.nodes.get_mut(node_id) else { + log::error!("Could not get node in set_implementation"); + return None; + }; + let new_inputs = std::mem::take(&mut new_template.document_node.inputs); + let old_inputs = std::mem::replace(&mut node.inputs, new_inputs); + let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { + log::error!("Could not get metadata in set_implementation"); + return None; + }; + let new_metadata = std::mem::take(&mut new_template.persistent_node_metadata.input_metadata); + let _ = std::mem::replace(&mut metadata.persistent_metadata.input_metadata, new_metadata); + Some(old_inputs) + } + + /// Used when opening an old document to add the persistent metadata for each input if it doesnt exist, which is where the name/description are saved. + pub fn validate_input_metadata(&mut self, node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId]) { + let number_of_inputs = node.inputs.len(); + let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { return }; + for added_input_index in metadata.persistent_metadata.input_metadata.len()..number_of_inputs { + let reference = metadata.persistent_metadata.reference.as_ref(); + let definition = reference.and_then(|reference| resolve_document_node_type(reference)); + let input_metadata = definition + .and_then(|definition| definition.node_template.persistent_node_metadata.input_metadata.get(added_input_index)) + .cloned(); + metadata.persistent_metadata.input_metadata.push(input_metadata.unwrap_or_default()); + } + } + + /// Used to ensure the display name is the reference name in case it is empty. + pub fn validate_display_name_metadata(&mut self, node_id: &NodeId, network_path: &[NodeId]) { + let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { return }; + if metadata.persistent_metadata.display_name.is_empty() { + if let Some(reference) = metadata.persistent_metadata.reference.clone() { + // Keep the name for merge nodes as empty + if reference != "Merge" { + metadata.persistent_metadata.display_name = reference; + } + } + } + } + + // When opening an old document to ensure the output names match the number of exports + pub fn validate_output_names(&mut self, node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId]) { + if let DocumentNodeImplementation::Network(network) = &node.implementation { + let number_of_exports = network.exports.len(); + let Some(metadata) = self.node_metadata_mut(node_id, network_path) else { + log::error!("Could not get metadata for node: {:?}", node_id); + return; + }; + metadata.persistent_metadata.output_names.resize(number_of_exports, "".to_string()); + } } /// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts @@ -3845,19 +4153,6 @@ impl NodeNetworkInterface { node.manual_composition = manual_composition; } - /// Keep metadata in sync with the new implementation if this is used by anything other than the upgrade scripts - pub fn replace_inputs(&mut self, node_id: &NodeId, inputs: Vec, network_path: &[NodeId]) -> Vec { - let Some(network) = self.network_mut(network_path) else { - log::error!("Could not get nested network in replace_inputs"); - return Vec::new(); - }; - let Some(node) = network.nodes.get_mut(node_id) else { - log::error!("Could not get node in replace_inputs"); - return Vec::new(); - }; - std::mem::replace(&mut node.inputs, inputs) - } - pub fn set_input(&mut self, input_connector: &InputConnector, new_input: NodeInput, network_path: &[NodeId]) { if matches!(input_connector, InputConnector::Export(_)) && matches!(new_input, NodeInput::Network { .. }) { // TODO: Add support for flattening NodeInput::Network exports in flatten_with_fns https://github.com/GraphiteEditor/Graphite/issues/1762 @@ -4013,20 +4308,22 @@ impl NodeNetworkInterface { } self.unload_upstream_node_click_targets(vec![*upstream_node_id], network_path); self.unload_stack_dependents(network_path); - self.try_set_upstream_to_chain(input_connector, network_path); } // If a connection is made to the imports (NodeInput::Value { .. } | NodeInput::Scope { .. } | NodeInput::Inline { .. }, NodeInput::Network { .. }) => { self.unload_outward_wires(network_path); + self.unload_wire(input_connector, network_path); } // If a connection to the imports is disconnected (NodeInput::Network { .. }, NodeInput::Value { .. } | NodeInput::Scope { .. } | NodeInput::Inline { .. }) => { self.unload_outward_wires(network_path); + self.unload_wire(input_connector, network_path); } // If a node is disconnected. (NodeInput::Node { .. }, NodeInput::Value { .. } | NodeInput::Scope { .. } | NodeInput::Inline { .. }) => { self.unload_outward_wires(network_path); + self.unload_wire(input_connector, network_path); if let Some((old_upstream_node_id, previous_position)) = previous_metadata { let old_upstream_node_is_layer = self.is_layer(&old_upstream_node_id, network_path); @@ -4170,8 +4467,8 @@ impl NodeNetworkInterface { self.unload_outward_wires(network_path); } - /// Used to insert a node template with no node/network inputs into the network. - pub fn insert_node(&mut self, node_id: NodeId, node_template: NodeTemplate, network_path: &[NodeId]) { + /// Used to insert a node template with no node/network inputs into the network and returns the a NodeTemplate with information from the previous node, if it existed. + pub fn insert_node(&mut self, node_id: NodeId, node_template: NodeTemplate, network_path: &[NodeId]) -> Option { let has_node_or_network_input = node_template .document_node .inputs @@ -4180,24 +4477,29 @@ impl NodeNetworkInterface { assert!(has_node_or_network_input, "Cannot insert node with node or network inputs. Use insert_node_group instead"); let Some(network) = self.network_mut(network_path) else { log::error!("Network not found in insert_node"); - return; + return None; }; - network.nodes.insert(node_id, node_template.document_node); + let previous_node = network.nodes.insert(node_id, node_template.document_node); self.transaction_modified(); let Some(network_metadata) = self.network_metadata_mut(network_path) else { log::error!("Network not found in insert_node"); - return; + return None; }; let node_metadata = DocumentNodeMetadata { persistent_metadata: node_template.persistent_node_metadata, transient_metadata: DocumentNodeTransientMetadata::default(), }; - network_metadata.persistent_metadata.node_metadata.insert(node_id, node_metadata); + let previous_metadata = network_metadata.persistent_metadata.node_metadata.insert(node_id, node_metadata); self.unload_all_nodes_bounding_box(network_path); - self.unload_node_click_targets(&node_id, network_path) + self.unload_node_click_targets(&node_id, network_path); + + previous_node.zip(previous_metadata).map(|(document_node, node_metadata)| NodeTemplate { + document_node, + persistent_node_metadata: node_metadata.persistent_metadata, + }) } /// Deletes all nodes in `node_ids` and any sole dependents in the horizontal chain if the node to delete is a layer node. @@ -4307,7 +4609,7 @@ impl NodeNetworkInterface { let reconnect_to_input = self.document_node(node_id, network_path).and_then(|node| { node.inputs .iter() - .find(|input| input.is_exposed_to_frontend(network_path.is_empty())) + .find(|input| input.is_exposed()) .filter(|input| matches!(input, NodeInput::Node { .. } | NodeInput::Network { .. })) .cloned() }); @@ -4463,25 +4765,21 @@ impl NodeNetworkInterface { let name_changed = match index { ImportOrExport::Import(import_index) => { - let Some(input_properties) = encapsulating_node.persistent_metadata.input_properties.get_mut(import_index) else { + let Some(input_properties) = encapsulating_node.persistent_metadata.input_metadata.get_mut(import_index) else { log::error!("Could not get input properties in set_import_export_name"); return; }; // Only return false if the previous value is the same as the current value - std::mem::swap(&mut input_properties.input_name, &mut name); - input_properties.input_name != name + std::mem::swap(&mut input_properties.persistent_metadata.input_name, &mut name); + input_properties.persistent_metadata.input_name != name } ImportOrExport::Export(export_index) => { let Some(export_name) = encapsulating_node.persistent_metadata.output_names.get_mut(export_index) else { log::error!("Could not get export_name in set_import_export_name"); return; }; - if *export_name == name { - false - } else { - *export_name = name; - true - } + std::mem::swap(export_name, &mut name); + *export_name != name } }; if name_changed { @@ -4752,6 +5050,7 @@ impl NodeNetworkInterface { log::error!("Could not set stack position for non layer node {node_id}"); } } + self.unload_upstream_node_click_targets(vec![*node_id], network_path); } /// Sets the position of a node to a stack position without changing its y offset @@ -5378,7 +5677,6 @@ impl NodeNetworkInterface { self.unload_all_nodes_bounding_box(network_path); } - // TODO: Run the auto layout system to make space for the new nodes /// Disconnect the layers primary output and the input to the last non layer node feeding into it through primary flow, reconnects, then moves the layer to the new layer and stack index pub fn move_layer_to_stack(&mut self, layer: LayerNodeIdentifier, mut parent: LayerNodeIdentifier, mut insert_index: usize, network_path: &[NodeId]) { // Prevent moving an artboard anywhere but to the ROOT_PARENT child stack @@ -5679,34 +5977,6 @@ impl NodeNetworkInterface { self.force_set_upstream_to_chain(node_id, network_path); } } - - pub fn iter_recursive(&self) -> NodesRecursiveIter<'_> { - NodesRecursiveIter { - stack: vec![&self.network], - current_slice: None, - } - } -} - -pub struct NodesRecursiveIter<'a> { - stack: Vec<&'a NodeNetwork>, - current_slice: Option>, -} - -impl<'a> Iterator for NodesRecursiveIter<'a> { - type Item = (NodeId, &'a DocumentNode); - fn next(&mut self) -> Option { - loop { - if let Some((id, node)) = self.current_slice.as_mut().and_then(|iter| iter.next()) { - if let DocumentNodeImplementation::Network(network) = &node.implementation { - self.stack.push(network); - } - return Some((*id, node)); - } - let network = self.stack.pop()?; - self.current_slice = Some(network.nodes.iter()); - } - } } #[derive(PartialEq)] @@ -5762,7 +6032,11 @@ impl Iterator for FlowIter<'_> { } } -/// Represents the source of a resolved type (for debugging) +// TODO: Refactor to be Unknown, Compiled(Type) for NodeInput::Node, or Value(Type) for NodeInput::Value +/// Represents the source of a resolved type (for debugging). +/// There will be two valid types list. One for the current valid types that will not cause a node graph error, +/// based on the other inputs to that node and returned during compilation. THe other list will be all potential +/// Valid types, based on the protonode implementation/downstream users. #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] pub enum TypeSource { Compiled, @@ -5787,7 +6061,7 @@ pub enum ImportOrExport { } /// Represents an input connector with index based on the [`DocumentNode::inputs`] index, not the visible input index -#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] pub enum InputConnector { #[serde(rename = "node")] Node { @@ -6099,14 +6373,14 @@ pub struct NodeNetworkTransientMetadata { // node_group_bounding_box: Vec<(Subpath, Vec)>, /// Cache for all outward wire connections pub outward_wires: TransientMetadata>>, - // TODO: Cache all wire paths instead of calculating in Graph.svelte - // pub wire_paths: Vec /// All export connector click targets pub import_export_ports: TransientMetadata, /// Click targets for adding, removing, and moving import/export ports pub modify_import_export: TransientMetadata, // Distance to the edges of the network, where the import/export ports are displayed. Rounded to nearest grid space when the panning ends. pub rounded_network_edge_distance: TransientMetadata, + // Wires from the exports + pub wires: Vec>, } #[derive(Debug, Clone)] @@ -6212,116 +6486,90 @@ pub enum WidgetOverride { } // TODO: Custom deserialization/serialization to ensure number of properties row matches number of node inputs -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct PropertiesRow { +#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct InputPersistentMetadata { /// A general datastore than can store key value pairs of any types for any input - // TODO: This could be simplified to just Value, and key value pairs could be stored as the Value::Object variant + /// Each instance of the input node needs to store its own data, since it can lose the reference to its + /// node definition if the node signature is modified by the user. For example adding/removing/renaming an import/export of a network node. pub input_data: HashMap, // An input can override a widget, which would otherwise be automatically generated from the type // The string is the identifier to the widget override function stored in INPUT_OVERRIDES pub widget_override: Option, - #[serde(skip)] + /// An empty input name means to use the type as the name. pub input_name: String, - #[serde(skip)] + /// Displayed as the tooltip. pub input_description: String, } -impl Default for PropertiesRow { - fn default() -> Self { - ("", "TODO").into() +impl InputPersistentMetadata { + pub fn with_name(mut self, input_name: &str) -> Self { + self.input_name = input_name.to_string(); + self } -} - -impl From<(&str, &str)> for PropertiesRow { - fn from(input_name_and_description: (&str, &str)) -> Self { - PropertiesRow::with_override(input_name_and_description.0, input_name_and_description.1, WidgetOverride::None) - } -} - -impl PropertiesRow { - pub fn with_override(input_name: &str, input_description: &str, widget_override: WidgetOverride) -> Self { - let mut input_data = HashMap::new(); - let input_name = input_name.to_string(); - let input_description = input_description.to_string(); - + pub fn with_override(mut self, widget_override: WidgetOverride) -> Self { match widget_override { - WidgetOverride::None => PropertiesRow { - input_data, - widget_override: None, - input_name, - input_description, - }, - WidgetOverride::Hidden => PropertiesRow { - input_data, - widget_override: Some("hidden".to_string()), - input_name, - input_description, - }, + // Uses the default widget for the type + WidgetOverride::None => { + self.widget_override = None; + } + WidgetOverride::Hidden => { + self.widget_override = Some("hidden".to_string()); + } WidgetOverride::String(string_properties) => { - input_data.insert("string_properties".to_string(), Value::String(string_properties)); - PropertiesRow { - input_data, - widget_override: Some("string".to_string()), - input_name, - input_description, - } + self.input_data.insert("string_properties".to_string(), Value::String(string_properties)); + self.widget_override = Some("string".to_string()); } WidgetOverride::Number(mut number_properties) => { if let Some(unit) = number_properties.unit.take() { - input_data.insert("unit".to_string(), json!(unit)); + self.input_data.insert("unit".to_string(), json!(unit)); } if let Some(min) = number_properties.min.take() { - input_data.insert("min".to_string(), json!(min)); + self.input_data.insert("min".to_string(), json!(min)); } if let Some(max) = number_properties.max.take() { - input_data.insert("max".to_string(), json!(max)); + self.input_data.insert("max".to_string(), json!(max)); } if let Some(step) = number_properties.step.take() { - input_data.insert("step".to_string(), json!(step)); + self.input_data.insert("step".to_string(), json!(step)); } if let Some(range_min) = number_properties.range_min.take() { - input_data.insert("range_min".to_string(), json!(range_min)); + self.input_data.insert("range_min".to_string(), json!(range_min)); } if let Some(range_max) = number_properties.range_max.take() { - input_data.insert("range_max".to_string(), json!(range_max)); - } - input_data.insert("mode".to_string(), json!(number_properties.mode)); - input_data.insert("is_integer".to_string(), Value::Bool(number_properties.is_integer)); - input_data.insert("blank_assist".to_string(), Value::Bool(number_properties.blank_assist)); - PropertiesRow { - input_data, - widget_override: Some("number".to_string()), - input_name, - input_description, + self.input_data.insert("range_max".to_string(), json!(range_max)); } + self.input_data.insert("mode".to_string(), json!(number_properties.mode)); + self.input_data.insert("is_integer".to_string(), Value::Bool(number_properties.is_integer)); + self.input_data.insert("blank_assist".to_string(), Value::Bool(number_properties.blank_assist)); + self.widget_override = Some("number".to_string()); } WidgetOverride::Vec2(vec2_properties) => { - input_data.insert("x".to_string(), json!(vec2_properties.x)); - input_data.insert("y".to_string(), json!(vec2_properties.y)); - input_data.insert("unit".to_string(), json!(vec2_properties.unit)); + self.input_data.insert("x".to_string(), json!(vec2_properties.x)); + self.input_data.insert("y".to_string(), json!(vec2_properties.y)); + self.input_data.insert("unit".to_string(), json!(vec2_properties.unit)); if let Some(min) = vec2_properties.min { - input_data.insert("min".to_string(), json!(min)); - } - PropertiesRow { - input_data, - widget_override: Some("vec2".to_string()), - input_name, - input_description, + self.input_data.insert("min".to_string(), json!(min)); } + self.widget_override = Some("vec2".to_string()); } - WidgetOverride::Custom(lambda_name) => PropertiesRow { - input_data, - widget_override: Some(lambda_name), - input_name, - input_description, - }, - } - } - - pub fn with_tooltip(mut self, tooltip: &str) -> Self { - self.input_data.insert("tooltip".to_string(), json!(tooltip)); + WidgetOverride::Custom(lambda_name) => { + self.widget_override = Some(lambda_name); + } + }; self } + + pub fn with_description(mut self, tooltip: &str) -> Self { + self.input_description = tooltip.to_string(); + self + } +} + +#[derive(Debug, Clone, Default)] +struct InputTransientMetadata { + wire: TransientMetadata, + // downstream_protonode: populated for all inputs after each compile + // types: populated for each protonode after each } // TODO: Eventually remove this migration document upgrade code @@ -6361,7 +6609,7 @@ pub struct DocumentNodePersistentMetadata { pub display_name: String, /// Stores metadata to override the properties in the properties panel for each input. These can either be generated automatically based on the type, or with a custom function. /// Must match the length of node inputs - pub input_properties: Vec, + pub input_metadata: Vec, #[serde(deserialize_with = "migrate_output_names")] pub output_names: Vec, /// Indicates to the UI if a primary output should be drawn for this node. @@ -6386,7 +6634,7 @@ impl Default for DocumentNodePersistentMetadata { DocumentNodePersistentMetadata { reference: None, display_name: String::new(), - input_properties: Vec::new(), + input_metadata: Vec::new(), output_names: Vec::new(), has_primary_output: true, pinned: false, @@ -6403,6 +6651,48 @@ impl DocumentNodePersistentMetadata { } } +#[derive(Debug, Default, serde::Serialize, serde::Deserialize)] +pub struct InputMetadata { + pub persistent_metadata: InputPersistentMetadata, + #[serde(skip)] + transient_metadata: InputTransientMetadata, +} + +impl Clone for InputMetadata { + fn clone(&self) -> Self { + InputMetadata { + persistent_metadata: self.persistent_metadata.clone(), + transient_metadata: Default::default(), + } + } +} + +impl PartialEq for InputMetadata { + fn eq(&self, other: &Self) -> bool { + self.persistent_metadata == other.persistent_metadata + } +} + +impl From<(&str, &str)> for InputMetadata { + fn from(input_name_and_description: (&str, &str)) -> Self { + InputMetadata { + persistent_metadata: InputPersistentMetadata::default() + .with_name(input_name_and_description.0) + .with_description(input_name_and_description.1), + ..Default::default() + } + } +} + +impl InputMetadata { + pub fn with_name_description_override(input_name: &str, tooltip: &str, widget_override: WidgetOverride) -> Self { + InputMetadata { + persistent_metadata: InputPersistentMetadata::default().with_name(input_name).with_description(tooltip).with_override(widget_override), + ..Default::default() + } + } +} + /// Persistent metadata for each node in the network, which must be included when creating, serializing, and deserializing saving a node. #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct DocumentNodePersistentMetadataInputNames { @@ -6423,17 +6713,59 @@ pub struct DocumentNodePersistentMetadataInputNames { impl From for DocumentNodePersistentMetadata { fn from(old: DocumentNodePersistentMetadataInputNames) -> Self { - let input_properties = old - .reference - .as_ref() - .and_then(|reference| resolve_document_node_type(reference)) - .map(|definition| definition.node_template.persistent_node_metadata.input_properties.clone()) - .unwrap_or(old.input_names.into_iter().map(|name| (name.as_str(), "").into()).collect()); + DocumentNodePersistentMetadata { + input_metadata: Vec::new(), + ..old.into() + } + } +} +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct DocumentNodePersistentMetadataPropertiesRow { + pub reference: Option, + #[serde(default)] + pub display_name: String, + pub input_properties: Vec, + #[serde(deserialize_with = "migrate_output_names")] + pub output_names: Vec, + #[serde(default = "return_true")] + pub has_primary_output: bool, + #[serde(default)] + pub locked: bool, + #[serde(default)] + pub pinned: bool, + pub node_type_metadata: NodeTypePersistentMetadata, + pub network_metadata: Option, +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct PropertiesRow { + pub input_data: HashMap, + pub widget_override: Option, + #[serde(skip)] + pub input_name: String, + #[serde(skip)] + pub input_description: String, +} + +impl From for DocumentNodePersistentMetadata { + fn from(old: DocumentNodePersistentMetadataPropertiesRow) -> Self { + let mut input_metadata = Vec::new(); + for properties_row in old.input_properties { + input_metadata.push(InputMetadata { + persistent_metadata: InputPersistentMetadata { + input_data: properties_row.input_data, + widget_override: properties_row.widget_override, + input_name: properties_row.input_name, + input_description: properties_row.input_description, + }, + ..Default::default() + }) + } DocumentNodePersistentMetadata { reference: old.reference, display_name: old.display_name, - input_properties, + input_metadata: Vec::new(), output_names: old.output_names, has_primary_output: old.has_primary_output, locked: old.locked, @@ -6446,6 +6778,7 @@ impl From for DocumentNodePersistentMe #[derive(serde::Serialize, serde::Deserialize)] enum NodePersistentMetadataVersions { + DocumentNodePersistentMetadataPropertiesRow(DocumentNodePersistentMetadataPropertiesRow), NodePersistentMetadataInputNames(DocumentNodePersistentMetadataInputNames), NodePersistentMetadata(DocumentNodePersistentMetadata), } @@ -6457,12 +6790,16 @@ where use serde::Deserialize; let value = Value::deserialize(deserializer)?; - - serde_json::from_value::(value.clone()).or_else(|_| { - serde_json::from_value::(value) - .map(DocumentNodePersistentMetadata::from) - .map_err(serde::de::Error::custom) - }) + if let Ok(document) = serde_json::from_value::(value.clone()) { + return Ok(document); + }; + if let Ok(document) = serde_json::from_value::(value.clone()) { + return Ok(document.into()); + }; + match serde_json::from_value::(value.clone()) { + Ok(document) => Ok(document.into()), + Err(e) => Err(serde::de::Error::custom(e)), + } } #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] @@ -6626,7 +6963,7 @@ impl Default for NavigationMetadata { // PartialEq required by message handlers /// All persistent editor and Graphene data for a node. Used to serialize and deserialize a node, pass it through the editor, and create definitions. -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Default, PartialEq, serde::Serialize, serde::Deserialize)] pub struct NodeTemplate { pub document_node: DocumentNode, pub persistent_node_metadata: DocumentNodePersistentMetadata, diff --git a/editor/src/messages/portfolio/document/utility_types/nodes.rs b/editor/src/messages/portfolio/document/utility_types/nodes.rs index d81f1693d7..66369026b3 100644 --- a/editor/src/messages/portfolio/document/utility_types/nodes.rs +++ b/editor/src/messages/portfolio/document/utility_types/nodes.rs @@ -1,5 +1,7 @@ use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use super::network_interface::NodeNetworkInterface; +use crate::messages::tool::common_functionality::graph_modification_utils; +use glam::DVec2; use graph_craft::document::{NodeId, NodeNetwork}; use serde::ser::SerializeStruct; @@ -98,6 +100,22 @@ impl SelectedNodes { .filter(move |&layer| self.layer_visible(layer, network_interface) && !self.layer_locked(layer, network_interface)) } + pub fn selected_visible_and_unlocked_layers_mean_average_origin<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> DVec2 { + let (sum, count) = self + .selected_visible_and_unlocked_layers(network_interface) + .map(|layer| graph_modification_utils::get_viewport_origin(layer, network_interface)) + .fold((glam::DVec2::ZERO, 0), |(sum, count), item| (sum + item, count + 1)); + if count == 0 { DVec2::ZERO } else { sum / count as f64 } + } + + pub fn selected_visible_and_unlocked_median_points<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> DVec2 { + let (sum, count) = self + .selected_visible_and_unlocked_layers(network_interface) + .map(|layer| graph_modification_utils::get_viewport_center(layer, network_interface)) + .fold((glam::DVec2::ZERO, 0), |(sum, count), item| (sum + item, count + 1)); + if count == 0 { DVec2::ZERO } else { sum / count as f64 } + } + pub fn selected_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator + 'a { metadata.all_layers().filter(|layer| self.0.contains(&layer.to_node())) } diff --git a/editor/src/messages/portfolio/document/utility_types/transformation.rs b/editor/src/messages/portfolio/document/utility_types/transformation.rs index e0149939a5..cefcb61418 100644 --- a/editor/src/messages/portfolio/document/utility_types/transformation.rs +++ b/editor/src/messages/portfolio/document/utility_types/transformation.rs @@ -4,7 +4,6 @@ use crate::messages::portfolio::document::graph_operation::transform_utils; use crate::messages::portfolio::document::graph_operation::utility_types::{ModifyInputsContext, TransformIn}; use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use crate::messages::prelude::*; -use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::utility_types::ToolType; use glam::{DAffine2, DMat2, DVec2}; @@ -537,17 +536,6 @@ impl<'a> Selected<'a> { } } - pub fn mean_average_of_pivots(&mut self) -> DVec2 { - let xy_summation = self - .selected - .iter() - .map(|&layer| graph_modification_utils::get_viewport_pivot(layer, self.network_interface)) - .reduce(|a, b| a + b) - .unwrap_or_default(); - - xy_summation / self.selected.len() as f64 - } - pub fn center_of_aabb(&mut self) -> DVec2 { let [min, max] = self .selected diff --git a/editor/src/messages/portfolio/document/utility_types/wires.rs b/editor/src/messages/portfolio/document/utility_types/wires.rs new file mode 100644 index 0000000000..9f85c82670 --- /dev/null +++ b/editor/src/messages/portfolio/document/utility_types/wires.rs @@ -0,0 +1,589 @@ +use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType; +use bezier_rs::{ManipulatorGroup, Subpath}; +use glam::{DVec2, IVec2}; +use graphene_std::uuid::NodeId; +use graphene_std::vector::PointId; + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] +pub struct WirePath { + #[serde(rename = "pathString")] + pub path_string: String, + #[serde(rename = "dataType")] + pub data_type: FrontendGraphDataType, + pub thick: bool, + pub dashed: bool, +} + +#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] +pub struct WirePathUpdate { + pub id: NodeId, + #[serde(rename = "inputIndex")] + pub input_index: usize, + // If none, then remove the wire from the map + #[serde(rename = "wirePathUpdate")] + pub wire_path_update: Option, +} + +#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)] +pub enum GraphWireStyle { + #[default] + Direct = 0, + GridAligned = 1, +} + +impl std::fmt::Display for GraphWireStyle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + GraphWireStyle::GridAligned => write!(f, "Grid-Aligned"), + GraphWireStyle::Direct => write!(f, "Direct"), + } + } +} + +impl GraphWireStyle { + pub fn tooltip_description(&self) -> &'static str { + match self { + GraphWireStyle::GridAligned => "Wires follow the grid, running in straight lines between nodes", + GraphWireStyle::Direct => "Wires bend to run at an angle directly between nodes", + } + } + + pub fn is_direct(&self) -> bool { + *self == GraphWireStyle::Direct + } +} + +pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool, graph_wire_style: GraphWireStyle) -> Subpath { + let grid_spacing = 24.; + match graph_wire_style { + GraphWireStyle::Direct => { + let horizontal_gap = (output_position.x - input_position.x).abs(); + let vertical_gap = (output_position.y - input_position.y).abs(); + + let curve_length = grid_spacing; + let curve_falloff_rate = curve_length * std::f64::consts::TAU; + + let horizontal_curve_amount = -(2_f64.powf((-10. * horizontal_gap) / curve_falloff_rate)) + 1.; + let vertical_curve_amount = -(2_f64.powf((-10. * vertical_gap) / curve_falloff_rate)) + 1.; + let horizontal_curve = horizontal_curve_amount * curve_length; + let vertical_curve = vertical_curve_amount * curve_length; + + let locations = [ + output_position, + DVec2::new( + if vertical_out { output_position.x } else { output_position.x + horizontal_curve }, + if vertical_out { output_position.y - vertical_curve } else { output_position.y }, + ), + DVec2::new( + if vertical_in { input_position.x } else { input_position.x - horizontal_curve }, + if vertical_in { input_position.y + vertical_curve } else { input_position.y }, + ), + DVec2::new(input_position.x, input_position.y), + ]; + + let smoothing = 0.5; + let delta01 = DVec2::new((locations[1].x - locations[0].x) * smoothing, (locations[1].y - locations[0].y) * smoothing); + let delta23 = DVec2::new((locations[3].x - locations[2].x) * smoothing, (locations[3].y - locations[2].y) * smoothing); + + Subpath::new( + vec![ + ManipulatorGroup { + anchor: locations[0], + in_handle: None, + out_handle: None, + id: PointId::generate(), + }, + ManipulatorGroup { + anchor: locations[1], + in_handle: None, + out_handle: Some(locations[1] + delta01), + id: PointId::generate(), + }, + ManipulatorGroup { + anchor: locations[2], + in_handle: Some(locations[2] - delta23), + out_handle: None, + id: PointId::generate(), + }, + ManipulatorGroup { + anchor: locations[3], + in_handle: None, + out_handle: None, + id: PointId::generate(), + }, + ], + false, + ) + } + GraphWireStyle::GridAligned => { + let locations = straight_wire_paths(output_position, input_position, vertical_out, vertical_in); + straight_wire_subpath(locations) + } + } +} + +fn straight_wire_paths(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> Vec { + let grid_spacing = 24; + let line_width = 2; + + let in_x = input_position.x as i32; + let in_y = input_position.y as i32; + let out_x = output_position.x as i32; + let out_y = output_position.y as i32; + + let mid_x = (in_x + out_x) / 2 + (((in_x + out_x) / 2) % grid_spacing); + let mid_y = (in_y + out_y) / 2 + (((in_y + out_y) / 2) % grid_spacing); + let mid_y_alternate = (in_y + in_y) / 2 - (((in_y + in_y) / 2) % grid_spacing); + + let x1 = out_x; + let x2 = out_x + grid_spacing; + let x3 = in_x - 2 * grid_spacing; + let x4 = in_x; + let x5 = in_x - 2 * grid_spacing + line_width; + let x6 = out_x + grid_spacing + line_width; + let x7 = out_x + 2 * grid_spacing + line_width; + let x8 = in_x + line_width; + let x9 = out_x + 2 * grid_spacing; + let x10 = mid_x + line_width; + let x11 = out_x - grid_spacing; + let x12 = out_x - 4 * grid_spacing; + let x13 = mid_x; + let x14 = in_x + grid_spacing; + let x15 = in_x - 4 * grid_spacing; + let x16 = in_x + 8 * grid_spacing; + let x17 = mid_x - 2 * line_width; + let x18 = out_x + grid_spacing - 2 * line_width; + let x19 = out_x - 2 * line_width; + let x20 = mid_x - line_width; + + let y1 = out_y; + let y2 = out_y - grid_spacing; + let y3 = in_y; + let y4 = out_y - grid_spacing + 5 * line_width + 1; + let y5 = in_y - 2 * grid_spacing; + let y6 = out_y + 4 * line_width; + let y7 = out_y + 5 * line_width; + let y8 = out_y - 2 * grid_spacing + 5 * line_width + 1; + let y9 = out_y + 6 * line_width; + let y10 = in_y + 2 * grid_spacing; + let y111 = in_y + grid_spacing + 6 * line_width + 1; + let y12 = in_y + grid_spacing - 5 * line_width + 1; + let y13 = in_y - grid_spacing; + let y14 = in_y + grid_spacing; + let y15 = mid_y; + let y16 = mid_y_alternate; + + let wire1 = vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x5, y4), IVec2::new(x5, y3), IVec2::new(x4, y3)]; + + let wire2 = vec![IVec2::new(x1, y1), IVec2::new(x1, y16), IVec2::new(x3, y16), IVec2::new(x3, y3), IVec2::new(x4, y3)]; + + let wire3 = vec![ + IVec2::new(x1, y1), + IVec2::new(x1, y4), + IVec2::new(x12, y4), + IVec2::new(x12, y10), + IVec2::new(x3, y10), + IVec2::new(x3, y3), + IVec2::new(x4, y3), + ]; + + let wire4 = vec![ + IVec2::new(x1, y1), + IVec2::new(x1, y4), + IVec2::new(x13, y4), + IVec2::new(x13, y10), + IVec2::new(x3, y10), + IVec2::new(x3, y3), + IVec2::new(x4, y3), + ]; + + if out_y == in_y && out_x > in_x && (vertical_out || !vertical_in) { + return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x3, y2), IVec2::new(x3, y3), IVec2::new(x4, y3)]; + } + + // `outConnector` point and `inConnector` point lying on the same horizontal grid line and `outConnector` point lies to the right of `inConnector` point + if out_y == in_y && out_x > in_x && (vertical_out || !vertical_in) { + return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x3, y2), IVec2::new(x3, y3), IVec2::new(x4, y3)]; + }; + + // Handle straight lines + if out_y == in_y || (out_x == in_x && vertical_out) { + return vec![IVec2::new(x1, y1), IVec2::new(x4, y3)]; + }; + + // Handle standard right-angle paths + // Start vertical, then horizontal + + // `outConnector` point lies to the left of `inConnector` point + if vertical_out && in_x > out_x { + // `outConnector` point lies above `inConnector` point + if out_y < in_y { + // `outConnector` point lies on the vertical grid line 4 units to the left of `inConnector` point point + if -4 * grid_spacing <= out_x - in_x && out_x - in_x < -3 * grid_spacing { + return wire1; + }; + + // `outConnector` point lying on vertical grid lines 3 and 2 units to the left of `inConnector` point + if -3 * grid_spacing <= out_x - in_x && out_x - in_x <= -grid_spacing { + if -2 * grid_spacing <= out_y - in_y && out_y - in_y <= -grid_spacing { + return vec![IVec2::new(x1, y1), IVec2::new(x1, y2), IVec2::new(x2, y2), IVec2::new(x2, y3), IVec2::new(x4, y3)]; + }; + + if -grid_spacing <= out_y - in_y && out_y - in_y <= 0 { + return vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x6, y4), IVec2::new(x6, y3), IVec2::new(x4, y3)]; + }; + + return vec![ + IVec2::new(x1, y1), + IVec2::new(x1, y4), + IVec2::new(x7, y4), + IVec2::new(x7, y5), + IVec2::new(x3, y5), + IVec2::new(x3, y3), + IVec2::new(x4, y3), + ]; + } + + // `outConnector` point lying on vertical grid line 1 units to the left of `inConnector` point + if -grid_spacing < out_x - in_x && out_x - in_x <= 0 { + // `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point + if -2 * grid_spacing <= out_y - in_y && out_y - in_y <= -grid_spacing { + return vec![IVec2::new(x1, y6), IVec2::new(x2, y6), IVec2::new(x8, y3)]; + }; + + // `outConnector` point lying on the same horizontal grid line as `inConnector` point + if -grid_spacing <= out_y - in_y && out_y - in_y <= 0 { + return vec![IVec2::new(x1, y7), IVec2::new(x4, y3)]; + }; + + return vec![ + IVec2::new(x1, y1), + IVec2::new(x1, y2), + IVec2::new(x9, y2), + IVec2::new(x9, y5), + IVec2::new(x3, y5), + IVec2::new(x3, y3), + IVec2::new(x4, y3), + ]; + } + + return vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x10, y4), IVec2::new(x10, y3), IVec2::new(x4, y3)]; + } + + // `outConnector` point lies below `inConnector` point + // `outConnector` point lying on vertical grid line 1 unit to the left of `inConnector` point + if -grid_spacing <= out_x - in_x && out_x - in_x <= 0 { + // `outConnector` point lying on the horizontal grid lines 1 and 2 units below the `inConnector` point + if 0 <= out_y - in_y && out_y - in_y <= 2 * grid_spacing { + return vec![IVec2::new(x1, y6), IVec2::new(x11, y6), IVec2::new(x11, y3), IVec2::new(x4, y3)]; + }; + + return wire2; + } + + return vec![IVec2::new(x1, y1), IVec2::new(x1, y3), IVec2::new(x4, y3)]; + } + + // `outConnector` point lies to the right of `inConnector` point + if vertical_out && in_x <= out_x { + // `outConnector` point lying on any horizontal grid line above `inConnector` point + if out_y < in_y { + // `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point + if -2 * grid_spacing < out_y - in_y && out_y - in_y <= -grid_spacing { + return wire1; + }; + + // `outConnector` point lying on the same horizontal grid line as `inConnector` point + if -grid_spacing < out_y - in_y && out_y - in_y <= 0 { + return vec![IVec2::new(x1, y1), IVec2::new(x1, y8), IVec2::new(x5, y8), IVec2::new(x5, y3), IVec2::new(x4, y3)]; + }; + + // `outConnector` point lying on vertical grid lines 1 and 2 units to the right of `inConnector` point + if grid_spacing <= out_x - in_x && out_x - in_x <= 3 * grid_spacing { + return vec![ + IVec2::new(x1, y1), + IVec2::new(x1, y4), + IVec2::new(x9, y4), + IVec2::new(x9, y5), + IVec2::new(x3, y5), + IVec2::new(x3, y3), + IVec2::new(x4, y3), + ]; + } + + return vec![ + IVec2::new(x1, y1), + IVec2::new(x1, y4), + IVec2::new(x10, y4), + IVec2::new(x10, y5), + IVec2::new(x5, y5), + IVec2::new(x5, y3), + IVec2::new(x4, y3), + ]; + } + + // `outConnector` point lies below `inConnector` point + if out_y - in_y <= grid_spacing { + // `outConnector` point lies on the horizontal grid line 1 unit below the `inConnector` Point + if 0 <= out_x - in_x && out_x - in_x <= 13 * grid_spacing { + return vec![IVec2::new(x1, y9), IVec2::new(x3, y9), IVec2::new(x3, y3), IVec2::new(x4, y3)]; + }; + + if 13 < out_x - in_x && out_x - in_x <= 18 * grid_spacing { + return wire3; + }; + + return wire4; + } + + // `outConnector` point lies on the horizontal grid line 2 units below `outConnector` point + if grid_spacing <= out_y - in_y && out_y - in_y <= 2 * grid_spacing { + if 0 <= out_x - in_x && out_x - in_x <= 13 * grid_spacing { + return vec![IVec2::new(x1, y7), IVec2::new(x5, y7), IVec2::new(x5, y3), IVec2::new(x4, y3)]; + }; + + if 13 < out_x - in_x && out_x - in_x <= 18 * grid_spacing { + return wire3; + }; + + return wire4; + } + + // 0 to 4 units below the `outConnector` Point + if out_y - in_y <= 4 * grid_spacing { + return wire1; + }; + + return wire2; + } + + // Start horizontal, then vertical + if vertical_in { + // when `outConnector` lies below `inConnector` + if out_y > in_y { + // `out_x` lies to the left of `in_x` + if out_x < in_x { + return vec![IVec2::new(x1, y1), IVec2::new(x4, y1), IVec2::new(x4, y3)]; + }; + + // `out_x` lies to the right of `in_x` + if out_y - in_y <= grid_spacing { + // `outConnector` point directly below `inConnector` point + if 0 <= out_x - in_x && out_x - in_x <= grid_spacing { + return vec![IVec2::new(x1, y1), IVec2::new(x14, y1), IVec2::new(x14, y2), IVec2::new(x4, y2), IVec2::new(x4, y3)]; + }; + + // `outConnector` point lies below `inConnector` point and strictly to the right of `inConnector` point + return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y111), IVec2::new(x4, y111), IVec2::new(x4, y3)]; + } + + return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x4, y2), IVec2::new(x4, y3)]; + } + + // `out_y` lies on or above the `in_y` point + if -6 * grid_spacing < in_x - out_x && in_x - out_x < 4 * grid_spacing { + // edge case: `outConnector` point lying on vertical grid lines ranging from 4 units to left to 5 units to right of `inConnector` point + if -grid_spacing < in_x - out_x && in_x - out_x < 4 * grid_spacing { + return vec![ + IVec2::new(x1, y1), + IVec2::new(x2, y1), + IVec2::new(x2, y2), + IVec2::new(x15, y2), + IVec2::new(x15, y12), + IVec2::new(x4, y12), + IVec2::new(x4, y3), + ]; + } + + return vec![IVec2::new(x1, y1), IVec2::new(x16, y1), IVec2::new(x16, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)]; + } + + // left of edge case: `outConnector` point lying on vertical grid lines more than 4 units to left of `inConnector` point + if 4 * grid_spacing < in_x - out_x { + return vec![IVec2::new(x1, y1), IVec2::new(x17, y1), IVec2::new(x17, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)]; + }; + + // right of edge case: `outConnector` point lying on the vertical grid lines more than 5 units to right of `inConnector` point + if 6 * grid_spacing > in_x - out_x { + return vec![IVec2::new(x1, y1), IVec2::new(x18, y1), IVec2::new(x18, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)]; + }; + } + + // Both horizontal - use horizontal middle point + // When `inConnector` point is one of the two closest diagonally opposite points + if 0 <= in_x - out_x && in_x - out_x <= grid_spacing && in_y - out_y >= -grid_spacing && in_y - out_y <= grid_spacing { + return vec![IVec2::new(x19, y1), IVec2::new(x19, y3), IVec2::new(x4, y3)]; + } + + // When `inConnector` point lies on the horizontal line 1 unit above and below the `outConnector` point + if -grid_spacing <= out_y - in_y && out_y - in_y <= grid_spacing && out_x > in_x { + // Horizontal line above `out_y` + if in_y < out_y { + return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y13), IVec2::new(x3, y13), IVec2::new(x3, y3), IVec2::new(x4, y3)]; + }; + + // Horizontal line below `out_y` + return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y14), IVec2::new(x3, y14), IVec2::new(x3, y3), IVec2::new(x4, y3)]; + } + + // `outConnector` point to the right of `inConnector` point + if out_x > in_x - grid_spacing { + return vec![ + IVec2::new(x1, y1), + IVec2::new(x18, y1), + IVec2::new(x18, y15), + IVec2::new(x5, y15), + IVec2::new(x5, y3), + IVec2::new(x4, y3), + ]; + }; + + // When `inConnector` point lies on the vertical grid line two units to the right of `outConnector` point + if grid_spacing <= in_x - out_x && in_x - out_x <= 2 * grid_spacing { + return vec![IVec2::new(x1, y1), IVec2::new(x18, y1), IVec2::new(x18, y3), IVec2::new(x4, y3)]; + }; + + vec![IVec2::new(x1, y1), IVec2::new(x20, y1), IVec2::new(x20, y3), IVec2::new(x4, y3)] +} + +fn straight_wire_subpath(locations: Vec) -> Subpath { + if locations.is_empty() { + return Subpath::new(Vec::new(), false); + } + + if locations.len() == 2 { + return Subpath::new( + vec![ + ManipulatorGroup { + anchor: locations[0].into(), + in_handle: None, + out_handle: None, + id: PointId::generate(), + }, + ManipulatorGroup { + anchor: locations[1].into(), + in_handle: None, + out_handle: None, + id: PointId::generate(), + }, + ], + false, + ); + } + + let corner_radius = 10; + + // Create path with rounded corners + let mut path = vec![ManipulatorGroup { + anchor: locations[0].into(), + in_handle: None, + out_handle: None, + id: PointId::generate(), + }]; + + for i in 1..(locations.len() - 1) { + let prev = locations[i - 1]; + let curr = locations[i]; + let next = locations[i + 1]; + + let corner_start = IVec2::new( + curr.x + + if curr.x == prev.x { + 0 + } else if prev.x > curr.x { + corner_radius + } else { + -corner_radius + }, + curr.y + + if curr.y == prev.y { + 0 + } else if prev.y > curr.y { + corner_radius + } else { + -corner_radius + }, + ); + + let corner_start_mid = IVec2::new( + curr.x + + if curr.x == prev.x { + 0 + } else if prev.x > curr.x { + corner_radius / 2 + } else { + -corner_radius / 2 + }, + curr.y + + if curr.y == prev.y { + 0 + } else { + match prev.y > curr.y { + true => corner_radius / 2, + false => -corner_radius / 2, + } + }, + ); + + let corner_end = IVec2::new( + curr.x + + if curr.x == next.x { + 0 + } else if next.x > curr.x { + corner_radius + } else { + -corner_radius + }, + curr.y + + if curr.y == next.y { + 0 + } else if next.y > curr.y { + corner_radius + } else { + -corner_radius + }, + ); + + let corner_end_mid = IVec2::new( + curr.x + + if curr.x == next.x { + 0 + } else if next.x > curr.x { + corner_radius / 2 + } else { + -corner_radius / 2 + }, + curr.y + + if curr.y == next.y { + 0 + } else if next.y > curr.y { + 10 / 2 + } else { + -corner_radius / 2 + }, + ); + + path.extend(vec![ + ManipulatorGroup { + anchor: corner_start.into(), + in_handle: None, + out_handle: Some(corner_start_mid.into()), + id: PointId::generate(), + }, + ManipulatorGroup { + anchor: corner_end.into(), + in_handle: Some(corner_end_mid.into()), + out_handle: None, + id: PointId::generate(), + }, + ]) + } + + path.push(ManipulatorGroup { + anchor: (*locations.last().unwrap()).into(), + in_handle: None, + out_handle: None, + id: PointId::generate(), + }); + Subpath::new(path, false) +} diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs index 920d5fa68e..e4db26d2ec 100644 --- a/editor/src/messages/portfolio/document_migration.rs +++ b/editor/src/messages/portfolio/document_migration.rs @@ -1,17 +1,17 @@ // TODO: Eventually remove this document upgrade code // This file contains lots of hacky code for upgrading old documents to the new format -use super::document::utility_types::network_interface::{NumberInputSettings, PropertiesRow, WidgetOverride}; use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; -use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, OutputConnector}; +use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector}; use crate::messages::prelude::DocumentMessageHandler; use bezier_rs::Subpath; use glam::IVec2; +use graph_craft::document::DocumentNode; use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue}; use graphene_std::text::TypesettingConfig; use graphene_std::uuid::NodeId; -use graphene_std::vector::style::{Fill, FillType, Gradient, PaintOrder, StrokeAlign}; +use graphene_std::vector::style::{PaintOrder, StrokeAlign}; use graphene_std::vector::{VectorData, VectorDataTable}; use std::collections::HashMap; @@ -66,6 +66,7 @@ const REPLACEMENTS: &[(&str, &str)] = &[ ("graphene_core::ops::Vector2ValueNode", "graphene_math_nodes::CoordinateValueNode"), ("graphene_core::ops::ColorValueNode", "graphene_math_nodes::ColorValueNode"), ("graphene_core::ops::GradientValueNode", "graphene_math_nodes::GradientValueNode"), + ("graphene_core::ops::SampleGradientNode", "graphene_math_nodes::SampleGradientNode"), ("graphene_core::ops::StringValueNode", "graphene_math_nodes::StringValueNode"), ("graphene_core::ops::DotProductNode", "graphene_math_nodes::DotProductNode"), // debug @@ -190,665 +191,478 @@ pub fn document_migration_reset_node_definition(document_serialized_content: &st } pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) { - let mut network = document.network_interface.document_network().clone(); - network.generate_node_paths(&[]); + document.network_interface.migrate_path_modify_node(); + + let network = document.network_interface.document_network().clone(); // Apply string replacements to each node - let node_ids: Vec<_> = network.recursive_nodes().map(|(&id, node)| (id, node.original_location.path.clone().unwrap())).collect(); - for (node_id, path) in &node_ids { - let network_path: Vec<_> = path.iter().copied().take(path.len() - 1).collect(); - - if let Some(DocumentNodeImplementation::ProtoNode(protonode_id)) = document - .network_interface - .nested_network(&network_path) - .unwrap() - .nodes - .get(node_id) - .map(|node| node.implementation.clone()) - { + for (node_id, node, network_path) in network.recursive_nodes() { + if let DocumentNodeImplementation::ProtoNode(protonode_id) = &node.implementation { for (old, new) in REPLACEMENTS { let node_path_without_type_args = protonode_id.name.split('<').next(); + let mut default_template = NodeTemplate::default(); + default_template.document_node.implementation = DocumentNodeImplementation::ProtoNode(new.to_string().into()); if node_path_without_type_args == Some(old) { - document - .network_interface - .replace_implementation(node_id, &network_path, DocumentNodeImplementation::ProtoNode(new.to_string().into())); + document.network_interface.replace_implementation(node_id, &network_path, &mut default_template); document.network_interface.set_manual_compostion(node_id, &network_path, Some(graph_craft::Type::Generic("T".into()))); } } } } - if reset_node_definitions_on_open { - // This can be used, if uncommented, 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 - for node_id in &document - .network_interface - .document_network_metadata() - .persistent_metadata - .node_metadata - .keys() - .cloned() - .collect::>() - { - if let Some(reference) = document - .network_interface - .document_network_metadata() - .persistent_metadata - .node_metadata - .get(node_id) - .and_then(|node| node.persistent_metadata.reference.as_ref()) - { - let Some(node_definition) = resolve_document_node_type(reference) else { continue }; - let default_definition_node = node_definition.default_node_template(); - document.network_interface.replace_implementation(node_id, &[], default_definition_node.document_node.implementation); - document - .network_interface - .replace_implementation_metadata(node_id, &[], default_definition_node.persistent_node_metadata); - document.network_interface.set_manual_compostion(node_id, &[], default_definition_node.document_node.manual_composition); - } - } - } - - if document + // Apply upgrades to each unmodified node. + let nodes = document .network_interface - .document_network_metadata() - .persistent_metadata - .node_metadata - .iter() - .any(|(node_id, node)| node.persistent_metadata.reference.as_ref().is_some_and(|reference| reference == "Output") && *node_id == NodeId(0)) - { - document.network_interface.delete_nodes(vec![NodeId(0)], true, &[]); + .document_network() + .recursive_nodes() + .map(|(node_id, node, path)| (*node_id, node.clone(), path)) + .collect::)>>(); + for (node_id, node, network_path) in &nodes { + migrate_node(node_id, node, network_path, document, reset_node_definitions_on_open); + } +} + +fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) -> Option<()> { + if reset_node_definitions_on_open { + if let Some(Some(reference)) = document.network_interface.reference(node_id, network_path) { + let node_definition = resolve_document_node_type(reference)?; + document.network_interface.replace_implementation(node_id, network_path, &mut node_definition.default_node_template()); + } } - let mut network = document.network_interface.document_network().clone(); - network.generate_node_paths(&[]); + // Upgrade old nodes to use `Context` instead of `()` or `Footprint` for manual composition + if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) { + document + .network_interface + .set_manual_compostion(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into()); + } - let node_ids: Vec<_> = network.recursive_nodes().map(|(&id, node)| (id, node.original_location.path.clone().unwrap())).collect(); + // Only nodes that have not been modified and still refer to a definition can be updated + let reference = document.network_interface.reference(node_id, network_path).cloned().flatten()?; + let reference = &reference; - // Apply upgrades to each node - for (node_id, path) in &node_ids { - let network_path: Vec<_> = path.iter().copied().take(path.len() - 1).collect(); - let network_path = &network_path; + let inputs_count = node.inputs.len(); - let Some(node) = document.network_interface.nested_network(network_path).unwrap().nodes.get(node_id).cloned() else { - log::error!("could not get node in deserialize_document"); - continue; + // Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644) + if reference == "Stroke" && inputs_count == 8 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false); + let paint_order_input = NodeInput::value(TaggedValue::PaintOrder(PaintOrder::StrokeAbove), false); + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 3), align_input, network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[6].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[7].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 7), paint_order_input, network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 8), old_inputs[3].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 9), old_inputs[4].clone(), network_path); + } + + // Rename the old "Splines from Points" node to "Spline" and upgrade it to the new "Spline" node + if reference == "Splines from Points" { + document.network_interface.set_reference(node_id, network_path, Some("Spline".to_string())); + } + + // Upgrade the old "Spline" node to the new "Spline" node + if reference == "Spline" { + // Retrieve the proto node identifier and verify it is the old "Spline" node, otherwise skip it if this is the new "Spline" node + let identifier = document + .network_interface + .implementation(node_id, network_path) + .and_then(|implementation| implementation.get_proto_node()); + if identifier.map(|identifier| &identifier.name) != Some(&"graphene_core::vector::generator_nodes::SplineNode".into()) { + return None; + } + + // Obtain the document node for the given node ID, extract the vector points, and create vector data from the list of points + let node = document.network_interface.document_node(node_id, network_path)?; + let Some(TaggedValue::VecDVec2(points)) = node.inputs.get(1).and_then(|tagged_value| tagged_value.as_value()) else { + log::error!("The old Spline node's input at index 1 is not a TaggedValue::VecDVec2"); + return None; + }; + let vector_data = VectorData::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false)); + + // Retrieve the output connectors linked to the "Spline" node's output port + let Some(spline_outputs) = document.network_interface.outward_wires(network_path)?.get(&OutputConnector::node(*node_id, 0)).cloned() else { + log::error!("Vec of InputConnector Spline node is connected to its output port 0."); + return None; }; - // Upgrade old nodes to use `Context` instead of `()` or `Footprint` for manual composition - if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) { - document - .network_interface - .set_manual_compostion(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into()); - } - - let Some(node_metadata) = document.network_interface.network_metadata(network_path).unwrap().persistent_metadata.node_metadata.get(node_id) else { - log::error!("could not get node metadata for node {node_id} in deserialize_document"); - continue; + // Get the node's current position in the graph + let Some(node_position) = document.network_interface.position(node_id, network_path) else { + log::error!("Could not get position of spline node."); + return None; }; - let Some(ref reference) = node_metadata.persistent_metadata.reference.clone() else { - // TODO: Investigate if this should be an expected case, because currently it runs hundreds of times normally. - // TODO: Either delete the commented out error below if this is normal, or fix the underlying issue if this is not expected. - // log::error!("could not get reference in deserialize_document"); - continue; + // Get the "Path" node definition and fill it in with the vector data and default vector modification + let Some(path_node_type) = resolve_document_node_type("Path") else { + log::error!("Path node does not exist."); + return None; }; + let path_node = path_node_type.node_template_input_override([ + Some(NodeInput::value(TaggedValue::VectorData(VectorDataTable::new(vector_data)), true)), + Some(NodeInput::value(TaggedValue::VectorModification(Default::default()), false)), + ]); - let inputs_count = node.inputs.len(); + // Get the "Spline" node definition and wire it up with the "Path" node as input + let Some(spline_node_type) = resolve_document_node_type("Spline") else { + log::error!("Spline node does not exist."); + return None; + }; + let spline_node = spline_node_type.node_template_input_override([Some(NodeInput::node(NodeId(1), 0))]); - // Upgrade Fill nodes to the format change in #1778 - if reference == "Fill" && inputs_count == 8 { - let node_definition = resolve_document_node_type(reference).unwrap(); - let document_node = node_definition.default_node_template().document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); + // Create a new node group with the "Path" and "Spline" nodes and generate new node IDs for them + let nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)]; + let new_ids = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect::>(); + let new_spline_id = *new_ids.get(&NodeId(0))?; + let new_path_id = *new_ids.get(&NodeId(1))?; - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); + // Remove the old "Spline" node from the document + document.network_interface.delete_nodes(vec![*node_id], false, network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + // Insert the new "Path" and "Spline" nodes into the network interface with generated IDs + document.network_interface.insert_node_group(nodes.clone(), new_ids, network_path); - let Some(fill_type) = old_inputs[1].as_value().cloned() else { continue }; - let TaggedValue::FillType(fill_type) = fill_type else { continue }; - let Some(solid_color) = old_inputs[2].as_value().cloned() else { continue }; - let TaggedValue::OptionalColor(solid_color) = solid_color else { continue }; - let Some(gradient_type) = old_inputs[3].as_value().cloned() else { continue }; - let TaggedValue::GradientType(gradient_type) = gradient_type else { continue }; - let Some(start) = old_inputs[4].as_value().cloned() else { continue }; - let TaggedValue::DVec2(start) = start else { continue }; - let Some(end) = old_inputs[5].as_value().cloned() else { continue }; - let TaggedValue::DVec2(end) = end else { continue }; - let Some(transform) = old_inputs[6].as_value().cloned() else { continue }; - let TaggedValue::DAffine2(transform) = transform else { continue }; - let Some(positions) = old_inputs[7].as_value().cloned() else { continue }; - let TaggedValue::GradientStops(positions) = positions else { continue }; + // Reposition the new "Spline" node to match the original "Spline" node's position + document.network_interface.shift_node(&new_spline_id, node_position, network_path); - let fill = match (fill_type, solid_color) { - (FillType::Solid, None) => Fill::None, - (FillType::Solid, Some(color)) => Fill::Solid(color), - (FillType::Gradient, _) => Fill::Gradient(Gradient { - stops: positions, - gradient_type, - start, - end, - transform, - }), - }; - document - .network_interface - .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Fill(fill.clone()), false), network_path); - match fill { - Fill::None => { - document - .network_interface - .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::OptionalColor(None), false), network_path); - } - Fill::Solid(color) => { - document - .network_interface - .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::OptionalColor(Some(color)), false), network_path); - } - Fill::Gradient(gradient) => { - document - .network_interface - .set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Gradient(gradient), false), network_path); - } + // Reposition the new "Path" node with an offset relative to the original "Spline" node's position + document.network_interface.shift_node(&new_path_id, node_position + IVec2::new(-7, 0), network_path); + + // Redirect each output connection from the old node to the new "Spline" node's output port + for input_connector in spline_outputs { + document.network_interface.set_input(&input_connector, NodeInput::node(new_spline_id, 0), network_path); + } + } + + // Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016 + if reference == "Text" && inputs_count != 9 { + let mut template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut template); + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[3].clone(), network_path); + document.network_interface.set_input( + &InputConnector::node(*node_id, 4), + if inputs_count == 6 { + old_inputs[4].clone() + } else { + NodeInput::value(TaggedValue::F64(TypesettingConfig::default().line_height_ratio), false) + }, + network_path, + ); + document.network_interface.set_input( + &InputConnector::node(*node_id, 5), + if inputs_count == 6 { + old_inputs[5].clone() + } else { + NodeInput::value(TaggedValue::F64(TypesettingConfig::default().character_spacing), false) + }, + network_path, + ); + document.network_interface.set_input( + &InputConnector::node(*node_id, 6), + if inputs_count >= 7 { + old_inputs[6].clone() + } else { + NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false) + }, + network_path, + ); + document.network_interface.set_input( + &InputConnector::node(*node_id, 7), + if inputs_count >= 8 { + old_inputs[7].clone() + } else { + NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false) + }, + network_path, + ); + document.network_interface.set_input( + &InputConnector::node(*node_id, 8), + if inputs_count >= 9 { + old_inputs[8].clone() + } else { + NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false) + }, + network_path, + ); + } + + // Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default + if (reference == "Sine" || reference == "Cosine" || reference == "Tangent") && inputs_count == 1 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document + .network_interface + .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Bool(true), false), network_path); + } + + // Upgrade the Modulo node to include a boolean input for whether the output should be always positive, which was previously not an option + if reference == "Modulo" && inputs_count == 2 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document + .network_interface + .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path); + } + + // Upgrade the Mirror node to add the `keep_original` boolean input + if reference == "Mirror" && inputs_count == 3 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); + document + .network_interface + .set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Bool(true), false), network_path); + } + + // Upgrade the Mirror node to add the `reference_point` input and change `offset` from `DVec2` to `f64` + if reference == "Mirror" && inputs_count == 4 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + let Some(&TaggedValue::DVec2(old_offset)) = old_inputs[1].as_value() else { return None }; + let old_offset = if old_offset.x.abs() > old_offset.y.abs() { old_offset.x } else { old_offset.y }; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input( + &InputConnector::node(*node_id, 1), + NodeInput::value(TaggedValue::ReferencePoint(graphene_std::transform::ReferencePoint::Center), false), + network_path, + ); + document + .network_interface + .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::F64(old_offset), false), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[3].clone(), network_path); + } + + // Upgrade artboard name being passed as hidden value input to "To Artboard" + if reference == "Artboard" && reset_node_definitions_on_open { + let label = document.network_interface.display_name(node_id, network_path); + document + .network_interface + .set_input(&InputConnector::node(NodeId(0), 1), NodeInput::value(TaggedValue::String(label), false), &[*node_id]); + } + + if reference == "Image" && inputs_count == 1 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + // Insert a new empty input for the image + document.network_interface.add_import(TaggedValue::None, false, 0, "Empty", "", &[*node_id]); + document.network_interface.set_reference(node_id, network_path, Some("Image".to_string())); + } + + if reference == "Noise Pattern" && inputs_count == 15 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document + .network_interface + .set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path); + for (i, input) in old_inputs.iter().enumerate() { + document.network_interface.set_input(&InputConnector::node(*node_id, i + 1), input.clone(), network_path); + } + } + + if reference == "Instance on Points" && inputs_count == 2 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + } + + if reference == "Morph" && inputs_count == 4 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); + // We have removed the last input, so we don't add index 3 + } + + if reference == "Brush" && inputs_count == 4 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + // We have removed the second input ("bounds"), so we don't add index 1 and we shift the rest of the inputs down by one + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[3].clone(), network_path); + } + + if reference == "Flatten Vector Elements" { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + + document.network_interface.replace_reference_name(node_id, network_path, "Flatten Path".to_string()); + } + + if reference == "Remove Handles" { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document + .network_interface + .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::F64(0.), false), network_path); + document + .network_interface + .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path); + + document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string()); + } + + if reference == "Generate Handles" { + let mut node_template = resolve_document_node_type("Auto-Tangents")?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document + .network_interface + .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(true), false), network_path); + + document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string()); + } + + if reference == "Merge by Distance" && inputs_count == 2 { + let mut node_template = resolve_document_node_type(reference)?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input( + &InputConnector::node(*node_id, 2), + NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Topological), false), + network_path, + ); + } + + if reference == "Spatial Merge by Distance" { + let mut node_template = resolve_document_node_type("Merge by Distance")?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input( + &InputConnector::node(*node_id, 2), + NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Spatial), false), + network_path, + ); + + document.network_interface.replace_reference_name(node_id, network_path, "Merge by Distance".to_string()); + } + + if reference == "Sample Points" && inputs_count == 5 { + let mut node_template = resolve_document_node_type("Sample Polyline")?.default_node_template(); + document.network_interface.replace_implementation(node_id, network_path, &mut node_template); + + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?; + let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false); + let new_quantity_value = NodeInput::value(TaggedValue::U32(100), false); + + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), new_spacing_value, network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[4].clone(), network_path); + + document.network_interface.replace_reference_name(node_id, network_path, "Sample Polyline".to_string()); + } + + // Make the "Quantity" parameter a u32 instead of f64 + if reference == "Sample Polyline" { + // Get the inputs, obtain the quantity value, and put the inputs back + let quantity_value = document + .network_interface + .input_from_connector(&InputConnector::Node { node_id: *node_id, input_index: 3 }, network_path)?; + + if let NodeInput::Value { tagged_value, exposed } = quantity_value { + if let TaggedValue::F64(value) = **tagged_value { + let new_quantity_value = NodeInput::value(TaggedValue::U32(value as u32), *exposed); + document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path); + } + } + } + + // Make the "Grid" node, if its input of index 3 is a DVec2 for "angles" instead of a u32 for the "columns" input that now succeeds "angles", move the angle to index 5 (after "columns" and "rows") + if reference == "Grid" && inputs_count == 6 { + let node_definition = resolve_document_node_type(reference)?; + let mut new_node_template = node_definition.default_node_template(); + + let mut current_node_template = document.network_interface.create_node_template(node_id, network_path)?; + let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template)?; + let index_3_value = old_inputs.get(3).cloned(); + + let mut upgraded = false; + + if let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value { + if matches!(*tagged_value, TaggedValue::DVec2(_)) { + // Move index 3 to the end + document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path); + document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path); + + upgraded = true; } } - // Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644) - if reference == "Stroke" && inputs_count == 8 { - let node_definition = resolve_document_node_type(reference).unwrap(); - let document_node = node_definition.default_node_template().document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document.network_interface.insert_input_properties_row(node_id, 8, network_path, ("", "TODO").into()); - document.network_interface.insert_input_properties_row(node_id, 9, network_path, ("", "TODO").into()); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false); - let paint_order_input = NodeInput::value(TaggedValue::PaintOrder(PaintOrder::StrokeAbove), false); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), align_input, network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[6].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[7].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 7), paint_order_input, network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 8), old_inputs[3].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 9), old_inputs[4].clone(), network_path); - } - - // Rename the old "Splines from Points" node to "Spline" and upgrade it to the new "Spline" node - if reference == "Splines from Points" { - document.network_interface.set_reference(node_id, network_path, Some("Spline".to_string())); - } - - // Upgrade the old "Spline" node to the new "Spline" node - if reference == "Spline" { - // Retrieve the proto node identifier and verify it is the old "Spline" node, otherwise skip it if this is the new "Spline" node - let identifier = document - .network_interface - .implementation(node_id, network_path) - .and_then(|implementation| implementation.get_proto_node()); - if identifier.map(|identifier| &identifier.name) != Some(&"graphene_core::vector::generator_nodes::SplineNode".into()) { - continue; - } - - // Obtain the document node for the given node ID, extract the vector points, and create vector data from the list of points - let node = document.network_interface.document_node(node_id, network_path).unwrap(); - let Some(TaggedValue::VecDVec2(points)) = node.inputs.get(1).and_then(|tagged_value| tagged_value.as_value()) else { - log::error!("The old Spline node's input at index 1 is not a TaggedValue::VecDVec2"); - continue; - }; - let vector_data = VectorData::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false)); - - // Retrieve the output connectors linked to the "Spline" node's output port - let spline_outputs = document - .network_interface - .outward_wires(network_path) - .unwrap() - .get(&OutputConnector::node(*node_id, 0)) - .expect("Vec of InputConnector Spline node is connected to its output port 0.") - .clone(); - - // Get the node's current position in the graph - let Some(node_position) = document.network_interface.position(node_id, network_path) else { - log::error!("Could not get position of spline node."); - continue; - }; - - // Get the "Path" node definition and fill it in with the vector data and default vector modification - let path_node_type = resolve_document_node_type("Path").expect("Path node does not exist."); - let path_node = path_node_type.node_template_input_override([ - Some(NodeInput::value(TaggedValue::VectorData(VectorDataTable::new(vector_data)), true)), - Some(NodeInput::value(TaggedValue::VectorModification(Default::default()), false)), - ]); - - // Get the "Spline" node definition and wire it up with the "Path" node as input - let spline_node_type = resolve_document_node_type("Spline").expect("Spline node does not exist."); - let spline_node = spline_node_type.node_template_input_override([Some(NodeInput::node(NodeId(1), 0))]); - - // Create a new node group with the "Path" and "Spline" nodes and generate new node IDs for them - let nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)]; - let new_ids = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect::>(); - let new_spline_id = *new_ids.get(&NodeId(0)).unwrap(); - let new_path_id = *new_ids.get(&NodeId(1)).unwrap(); - - // Remove the old "Spline" node from the document - document.network_interface.delete_nodes(vec![*node_id], false, network_path); - - // Insert the new "Path" and "Spline" nodes into the network interface with generated IDs - document.network_interface.insert_node_group(nodes.clone(), new_ids, network_path); - - // Reposition the new "Spline" node to match the original "Spline" node's position - document.network_interface.shift_node(&new_spline_id, node_position, network_path); - - // Reposition the new "Path" node with an offset relative to the original "Spline" node's position - document.network_interface.shift_node(&new_path_id, node_position + IVec2::new(-7, 0), network_path); - - // Redirect each output connection from the old node to the new "Spline" node's output port - for input_connector in spline_outputs { - document.network_interface.set_input(&input_connector, NodeInput::node(new_spline_id, 0), network_path); - } - } - - // Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016 - if reference == "Text" && inputs_count != 9 { - let node_definition = resolve_document_node_type(reference).unwrap(); - let document_node = node_definition.default_node_template().document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[3].clone(), network_path); - document.network_interface.set_input( - &InputConnector::node(*node_id, 4), - if inputs_count == 6 { - old_inputs[4].clone() - } else { - NodeInput::value(TaggedValue::F64(TypesettingConfig::default().line_height_ratio), false) - }, - network_path, - ); - document.network_interface.set_input( - &InputConnector::node(*node_id, 5), - if inputs_count == 6 { - old_inputs[5].clone() - } else { - NodeInput::value(TaggedValue::F64(TypesettingConfig::default().character_spacing), false) - }, - network_path, - ); - document.network_interface.set_input( - &InputConnector::node(*node_id, 6), - if inputs_count >= 7 { - old_inputs[6].clone() - } else { - NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false) - }, - network_path, - ); - document.network_interface.set_input( - &InputConnector::node(*node_id, 7), - if inputs_count >= 8 { - old_inputs[7].clone() - } else { - NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false) - }, - network_path, - ); - document.network_interface.insert_input_properties_row( - node_id, - 9, - network_path, - PropertiesRow::with_override( - "Tilt", - "Faux italic", - WidgetOverride::Number(NumberInputSettings { - min: Some(-85.), - max: Some(85.), - unit: Some("°".to_string()), - ..Default::default() - }), - ), - ); - document.network_interface.set_input( - &InputConnector::node(*node_id, 8), - if inputs_count >= 9 { - old_inputs[8].clone() - } else { - NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false) - }, - network_path, - ); - } - - // Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default - if (reference == "Sine" || reference == "Cosine" || reference == "Tangent") && inputs_count == 1 { - let node_definition = resolve_document_node_type(reference).unwrap(); - let document_node = node_definition.default_node_template().document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document - .network_interface - .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Bool(true), false), network_path); - } - - // Upgrade the Modulo node to include a boolean input for whether the output should be always positive, which was previously not an option - if reference == "Modulo" && inputs_count == 2 { - let node_definition = resolve_document_node_type(reference).unwrap(); - let document_node = node_definition.default_node_template().document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document - .network_interface - .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path); - } - - // Upgrade the Mirror node to add the `keep_original` boolean input - if reference == "Mirror" && inputs_count == 3 { - let node_definition = resolve_document_node_type(reference).unwrap(); - let document_node = node_definition.default_node_template().document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); - document - .network_interface - .set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Bool(true), false), network_path); - } - - // Upgrade the Mirror node to add the `reference_point` input and change `offset` from `DVec2` to `f64` - if reference == "Mirror" && inputs_count == 4 { - let node_definition = resolve_document_node_type(reference).unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - let Some(&TaggedValue::DVec2(old_offset)) = old_inputs[1].as_value() else { return }; - let old_offset = if old_offset.x.abs() > old_offset.y.abs() { old_offset.x } else { old_offset.y }; - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input( - &InputConnector::node(*node_id, 1), - NodeInput::value(TaggedValue::ReferencePoint(graphene_std::transform::ReferencePoint::Center), false), - network_path, - ); - document - .network_interface - .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::F64(old_offset), false), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[2].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[3].clone(), network_path); - } - - // Upgrade artboard name being passed as hidden value input to "To Artboard" - if reference == "Artboard" && reset_node_definitions_on_open { - let label = document.network_interface.display_name(node_id, network_path); - document - .network_interface - .set_input(&InputConnector::node(NodeId(0), 1), NodeInput::value(TaggedValue::String(label), false), &[*node_id]); - } - - if reference == "Image" && inputs_count == 1 { - let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap(); - let new_image_node = node_definition.default_node_template(); - document.network_interface.replace_implementation(node_id, network_path, new_image_node.document_node.implementation); - - // Insert a new empty input for the image - document.network_interface.add_import(TaggedValue::None, false, 0, "Empty", "", &[*node_id]); - document.network_interface.set_reference(node_id, network_path, Some("Image".to_string())); - } - - if reference == "Noise Pattern" && inputs_count == 15 { - let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap(); - let new_noise_pattern_node = node_definition.default_node_template(); - document - .network_interface - .replace_implementation(node_id, network_path, new_noise_pattern_node.document_node.implementation); - - let old_inputs = document.network_interface.replace_inputs(node_id, new_noise_pattern_node.document_node.inputs.clone(), network_path); - - document - .network_interface - .set_input(&InputConnector::node(*node_id, 0), NodeInput::value(TaggedValue::None, false), network_path); - for (i, input) in old_inputs.iter().enumerate() { - document.network_interface.set_input(&InputConnector::node(*node_id, i + 1), input.clone(), network_path); - } - } - - if reference == "Instance on Points" && inputs_count == 2 { - let node_definition = resolve_document_node_type(reference).unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - } - - if reference == "Morph" && inputs_count == 4 { - let node_definition = resolve_document_node_type(reference).unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); - // We have removed the last input, so we don't add index 3 - } - - if reference == "Brush" && inputs_count == 4 { - let node_definition = resolve_document_node_type(reference).unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - // We have removed the second input ("bounds"), so we don't add index 1 and we shift the rest of the inputs down by one - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[2].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[3].clone(), network_path); - } - - if reference == "Flatten Vector Elements" { - let node_definition = resolve_document_node_type("Flatten Path").unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - - document.network_interface.replace_reference_name(node_id, network_path, "Flatten Path".to_string()); - } - - if reference == "Remove Handles" { - let node_definition = resolve_document_node_type("Auto-Tangents").unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document - .network_interface - .set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::F64(0.), false), network_path); - document - .network_interface - .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(false), false), network_path); - - document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string()); - } - - if reference == "Generate Handles" { - let node_definition = resolve_document_node_type("Auto-Tangents").unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document - .network_interface - .set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::Bool(true), false), network_path); - - document.network_interface.replace_reference_name(node_id, network_path, "Auto-Tangents".to_string()); - } - - if reference == "Merge by Distance" && inputs_count == 2 { - let node_definition = resolve_document_node_type("Merge by Distance").unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input( - &InputConnector::node(*node_id, 2), - NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Topological), false), - network_path, - ); - } - - if reference == "Spatial Merge by Distance" { - let node_definition = resolve_document_node_type("Merge by Distance").unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input( - &InputConnector::node(*node_id, 2), - NodeInput::value(TaggedValue::MergeByDistanceAlgorithm(graphene_std::vector::misc::MergeByDistanceAlgorithm::Spatial), false), - network_path, - ); - - document.network_interface.replace_reference_name(node_id, network_path, "Merge by Distance".to_string()); - } - - if reference == "Sample Points" && inputs_count == 5 { - let node_definition = resolve_document_node_type("Sample Polyline").unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone()); - document - .network_interface - .replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata); - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false); - let new_quantity_value = NodeInput::value(TaggedValue::U32(100), false); - - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), new_spacing_value, network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[2].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[4].clone(), network_path); - - document.network_interface.replace_reference_name(node_id, network_path, "Sample Polyline".to_string()); - } - - // Make the "Quantity" parameter a u32 instead of f64 - if reference == "Sample Polyline" { - let node_definition = resolve_document_node_type("Sample Polyline").unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - - // Get the inputs, obtain the quantity value, and put the inputs back - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - let quantity_value = old_inputs.get(3).cloned(); - let _ = document.network_interface.replace_inputs(node_id, old_inputs, network_path); - - if let Some(NodeInput::Value { tagged_value, exposed }) = quantity_value { - if let TaggedValue::F64(value) = *tagged_value { - let new_quantity_value = NodeInput::value(TaggedValue::U32(value as u32), exposed); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path); - } - } - } - - // Make the "Grid" node, if its input of index 3 is a DVec2 for "angles" instead of a u32 for the "columns" input that now succeeds "angles", move the angle to index 5 (after "columns" and "rows") - if reference == "Grid" && inputs_count == 6 { - let node_definition = resolve_document_node_type(reference).unwrap(); - let new_node_template = node_definition.default_node_template(); - let document_node = new_node_template.document_node; - - let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path); - let index_3_value = old_inputs.get(3).cloned(); - - if let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value { - if matches!(*tagged_value, TaggedValue::DVec2(_)) { - // Move index 3 to the end - document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path); - document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path); - } else { - // Swap it back if we're not changing anything - let _ = document.network_interface.replace_inputs(node_id, old_inputs, network_path); - } - } + if !upgraded { + let _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template); } } @@ -856,25 +670,24 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_ document.network_interface.load_structure(); let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::>(); for layer in all_layers { - let Some((downstream_node, input_index)) = document + let (downstream_node, input_index) = document .network_interface .outward_wires(&[]) .and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0))) .and_then(|outward_wires| outward_wires.first()) - .and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index()))) - else { - continue; - }; + .and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))?; // If the downstream node is a layer and the input is the first input and the current layer is not in a stack if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) { // Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files let (Some(layer_position), Some(downstream_position)) = (document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[])) else { log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node); - continue; + return None; }; if layer_position.x == downstream_position.x { document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]); } } } + + Some(()) } diff --git a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs index 50cfa632ac..26a9a739f3 100644 --- a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs +++ b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs @@ -6,7 +6,7 @@ use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, use crate::messages::prelude::*; use graphene_std::path_bool::BooleanOperation; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct MenuBarMessageHandler { pub has_active_document: bool, pub canvas_tilted: bool, @@ -21,6 +21,7 @@ pub struct MenuBarMessageHandler { pub reset_node_definitions_on_open: bool, } +#[message_handler_data] impl MessageHandler for MenuBarMessageHandler { fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 7dac84d342..195768bd4b 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -12,6 +12,7 @@ use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::portfolio::document::DocumentMessageData; use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn; use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT}; +use crate::messages::portfolio::document::utility_types::network_interface::OutputConnector; use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes; use crate::messages::portfolio::document_migration::*; use crate::messages::preferences::SelectionMode; @@ -24,6 +25,7 @@ use graphene_std::renderer::Quad; use graphene_std::text::Font; use std::vec; +#[derive(ExtractField)] pub struct PortfolioMessageData<'a> { pub ipp: &'a InputPreprocessorMessageHandler, pub preferences: &'a PreferencesMessageHandler, @@ -34,7 +36,7 @@ pub struct PortfolioMessageData<'a> { pub animation: &'a AnimationMessageHandler, } -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct PortfolioMessageHandler { menu_bar_message_handler: MenuBarMessageHandler, pub documents: HashMap, @@ -51,6 +53,7 @@ pub struct PortfolioMessageHandler { pub reset_node_definitions_on_open: bool, } +#[message_handler_data] impl MessageHandler> for PortfolioMessageHandler { fn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque, data: PortfolioMessageData) { let PortfolioMessageData { @@ -426,6 +429,43 @@ impl MessageHandler> for PortfolioMes // Upgrade the document's nodes to be compatible with the latest version document_migration_upgrades(&mut document, reset_node_definitions_on_open); + // Ensure each node has the metadata for its inputs + for (node_id, node, path) in document.network_interface.document_network().clone().recursive_nodes() { + document.network_interface.validate_input_metadata(node_id, node, &path); + document.network_interface.validate_display_name_metadata(node_id, &path); + document.network_interface.validate_output_names(node_id, node, &path); + } + + // Ensure layers are positioned as stacks if they are upstream siblings of another layer + document.network_interface.load_structure(); + let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::>(); + for layer in all_layers { + let Some((downstream_node, input_index)) = document + .network_interface + .outward_wires(&[]) + .and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0))) + .and_then(|outward_wires| outward_wires.first()) + .and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index()))) + else { + continue; + }; + + // If the downstream node is a layer and the input is the first input and the current layer is not in a stack + if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) { + // Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files + let (Some(layer_position), Some(downstream_position)) = + (document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[])) + else { + log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node); + continue; + }; + + if layer_position.x == downstream_position.x { + document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]); + } + } + } + // Set the save state of the document based on what's given to us by the caller to this message document.set_auto_save_state(document_is_auto_saved); document.set_save_state(document_is_saved); @@ -709,7 +749,7 @@ impl MessageHandler> for PortfolioMes } let Some(document) = self.documents.get_mut(&document_id) else { - warn!("Tried to read non existant document"); + warn!("Tried to read non existent document"); return; }; if !document.is_loaded { diff --git a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs index 378c8b1b52..72c44c5975 100644 --- a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs +++ b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs @@ -15,7 +15,7 @@ use std::any::Any; use std::sync::Arc; /// The spreadsheet UI allows for instance data to be previewed. -#[derive(Default, Debug, Clone)] +#[derive(Default, Debug, Clone, ExtractField)] pub struct SpreadsheetMessageHandler { /// Sets whether or not the spreadsheet is drawn. pub spreadsheet_view_open: bool, @@ -25,6 +25,7 @@ pub struct SpreadsheetMessageHandler { viewing_vector_data_domain: VectorDataDomain, } +#[message_handler_data] impl MessageHandler for SpreadsheetMessageHandler { fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/messages/preferences/preferences_message.rs b/editor/src/messages/preferences/preferences_message.rs index 11b32deadd..b8988fa3e5 100644 --- a/editor/src/messages/preferences/preferences_message.rs +++ b/editor/src/messages/preferences/preferences_message.rs @@ -1,4 +1,4 @@ -use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle; +use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle; use crate::messages::preferences::SelectionMode; use crate::messages::prelude::*; diff --git a/editor/src/messages/preferences/preferences_message_handler.rs b/editor/src/messages/preferences/preferences_message_handler.rs index d6ec4d9376..1e52233fe3 100644 --- a/editor/src/messages/preferences/preferences_message_handler.rs +++ b/editor/src/messages/preferences/preferences_message_handler.rs @@ -1,11 +1,11 @@ use crate::consts::VIEWPORT_ZOOM_WHEEL_RATE; use crate::messages::input_mapper::key_mapping::MappingVariant; -use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle; +use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle; use crate::messages::preferences::SelectionMode; use crate::messages::prelude::*; use graph_craft::wasm_application_io::EditorPreferences; -#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type)] +#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type, ExtractField)] pub struct PreferencesMessageHandler { pub selection_mode: SelectionMode, pub zoom_with_scroll: bool, @@ -44,6 +44,7 @@ impl Default for PreferencesMessageHandler { } } +#[message_handler_data] impl MessageHandler for PreferencesMessageHandler { fn process_message(&mut self, message: PreferencesMessage, responses: &mut VecDeque, _data: ()) { match message { @@ -86,7 +87,8 @@ impl MessageHandler for PreferencesMessageHandler { } PreferencesMessage::GraphWireStyle { style } => { self.graph_wire_style = style; - responses.add(NodeGraphMessage::SendGraph); + responses.add(NodeGraphMessage::UnloadWires); + responses.add(NodeGraphMessage::SendWires); } PreferencesMessage::ViewportZoomWheelRate { rate } => { self.viewport_zoom_wheel_rate = rate; diff --git a/editor/src/messages/prelude.rs b/editor/src/messages/prelude.rs index d33b7c235c..7517c80f48 100644 --- a/editor/src/messages/prelude.rs +++ b/editor/src/messages/prelude.rs @@ -1,6 +1,6 @@ // Root -pub use crate::utility_traits::{ActionList, AsMessage, MessageHandler, ToDiscriminant, TransitiveChild}; - +pub use crate::utility_traits::{ActionList, AsMessage, HierarchicalTree, MessageHandler, ToDiscriminant, TransitiveChild}; +pub use crate::utility_types::{DebugMessageTree, MessageData}; // Message, MessageData, MessageDiscriminant, MessageHandler pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler}; pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler}; diff --git a/editor/src/messages/tool/common_functionality/compass_rose.rs b/editor/src/messages/tool/common_functionality/compass_rose.rs index 8a7a2f6b2a..bedc1ca230 100644 --- a/editor/src/messages/tool/common_functionality/compass_rose.rs +++ b/editor/src/messages/tool/common_functionality/compass_rose.rs @@ -1,5 +1,4 @@ use crate::consts::{COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER}; -use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::prelude::DocumentMessageHandler; use glam::{DAffine2, DVec2}; use std::f64::consts::FRAC_PI_2; @@ -10,25 +9,32 @@ pub struct CompassRose { } impl CompassRose { - fn get_layer_pivot_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 { - let [min, max] = document.metadata().nonzero_bounding_box(layer); - - let bounds_transform = DAffine2::from_translation(min) * DAffine2::from_scale(max - min); - let layer_transform = document.metadata().transform_to_viewport(layer); - layer_transform * bounds_transform - } pub fn refresh_position(&mut self, document: &DocumentMessageHandler) { - let selected_nodes = document.network_interface.selected_nodes(); - let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface); + let selected = document.network_interface.selected_nodes(); - let Some(first) = layers.next() else { return }; - let count = layers.count() + 1; - let transform = if count == 1 { - Self::get_layer_pivot_transform(first, document) - } else { - let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]); - DAffine2::from_translation(min) * DAffine2::from_scale(max - min) - }; + if !selected.has_selected_nodes() { + return; + } + + let transform = selected + .selected_visible_and_unlocked_layers(&document.network_interface) + .find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[])) + .map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface)) + .unwrap_or_default(); + + let bounds = document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter_map(|layer| { + document + .metadata() + .bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer)) + }) + .reduce(graphene_std::renderer::Quad::combine_bounds); + + let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]); + let transform = transform * DAffine2::from_translation(min) * DAffine2::from_scale(max - min); self.compass_center = transform.transform_point2(DVec2::splat(0.5)); } diff --git a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs index 81b26b988a..da7bdb987f 100644 --- a/editor/src/messages/tool/common_functionality/graph_modification_utils.rs +++ b/editor/src/messages/tool/common_functionality/graph_modification_utils.rs @@ -5,9 +5,9 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Flo use crate::messages::prelude::*; use bezier_rs::Subpath; use glam::DVec2; -use graph_craft::concrete; use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeInput}; +use graph_craft::{ProtoNodeIdentifier, concrete}; use graphene_std::Color; use graphene_std::NodeInputDecleration; use graphene_std::raster::BlendMode; @@ -243,20 +243,26 @@ pub fn new_custom(id: NodeId, nodes: Vec<(NodeId, NodeTemplate)>, parent: LayerN LayerNodeIdentifier::new_unchecked(id) } -/// Locate the final pivot from the transform (TODO: decide how the pivot should actually work) -pub fn get_pivot(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { - let pivot_node_input_index = 5; - if let TaggedValue::DVec2(pivot) = NodeGraphLayer::new(layer, network_interface).find_input("Transform", pivot_node_input_index)? { - Some(*pivot) +/// Locate the origin of the transform node +pub fn get_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option { + use graphene_std::transform_nodes::transform::TranslateInput; + + if let TaggedValue::DVec2(origin) = NodeGraphLayer::new(layer, network_interface).find_input("Transform", TranslateInput::INDEX)? { + Some(*origin) } else { None } } -pub fn get_viewport_pivot(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 { +pub fn get_viewport_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 { + let origin = get_origin(layer, network_interface).unwrap_or_default(); + network_interface.document_metadata().document_to_viewport.transform_point2(origin) +} + +pub fn get_viewport_center(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 { let [min, max] = network_interface.document_metadata().nonzero_bounding_box(layer); - let pivot = get_pivot(layer, network_interface).unwrap_or(DVec2::splat(0.5)); - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(min + (max - min) * pivot) + let center = DVec2::splat(0.5); + network_interface.document_metadata().transform_to_viewport(layer).transform_point2(min + (max - min) * center) } /// Get the current gradient of a layer from the closest "Fill" node. @@ -420,14 +426,14 @@ impl<'a> NodeGraphLayer<'a> { } /// Node id of a protonode if it exists in the layer's primary flow - pub fn upstream_node_id_from_protonode(&self, protonode_identifier: &'static str) -> Option { + pub fn upstream_node_id_from_protonode(&self, protonode_identifier: ProtoNodeIdentifier) -> Option { self.horizontal_layer_flow() // Take until a different layer is reached .take_while(|&node_id| node_id == self.layer_node || !self.network_interface.is_layer(&node_id, &[])) - .find(move |node_id| { + .find(|node_id| { self.network_interface .implementation(node_id, &[]) - .is_some_and(move |implementation| *implementation == graph_craft::document::DocumentNodeImplementation::proto(protonode_identifier)) + .is_some_and(|implementation| *implementation == graph_craft::document::DocumentNodeImplementation::ProtoNode(protonode_identifier.clone())) }) } diff --git a/editor/src/messages/tool/common_functionality/pivot.rs b/editor/src/messages/tool/common_functionality/pivot.rs index 67ce7f5dc5..10ea38371c 100644 --- a/editor/src/messages/tool/common_functionality/pivot.rs +++ b/editor/src/messages/tool/common_functionality/pivot.rs @@ -1,26 +1,184 @@ -//! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale and origin of the layer. +//! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale. -use super::graph_modification_utils; use crate::consts::PIVOT_DIAMETER; -use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::graph_modification_utils; +use crate::messages::tool::tool_messages::path_tool::PathOptionsUpdate; +use crate::messages::tool::tool_messages::select_tool::SelectOptionsUpdate; +use crate::messages::tool::tool_messages::tool_prelude::*; use glam::{DAffine2, DVec2}; -use graphene_std::transform::ReferencePoint; -use std::collections::VecDeque; +use graphene_std::{transform::ReferencePoint, vector::ManipulatorPointId}; +use std::fmt; -#[derive(Clone, Debug)] +pub fn pin_pivot_widget(active: bool, enabled: bool, source: PivotToolSource) -> WidgetHolder { + IconButton::new(if active { "PinActive" } else { "PinInactive" }, 24) + .tooltip(String::from(if active { "Unpin Custom Pivot" } else { "Pin Custom Pivot" }) + "\n\nUnless pinned, the pivot will return to its prior reference point when a new selection is made.") + .disabled(!enabled) + .on_update(move |_| match source { + PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotPinned).into(), + PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotPinned).into(), + }) + .widget_holder() +} + +pub fn pivot_reference_point_widget(disabled: bool, reference_point: ReferencePoint, source: PivotToolSource) -> WidgetHolder { + ReferencePointInput::new(reference_point) + .tooltip("Custom Pivot Reference Point\n\nPlaces the pivot at a corner, edge, or center of the selection bounds, unless it is dragged elsewhere.") + .disabled(disabled) + .on_update(move |pivot_input: &ReferencePointInput| match source { + PivotToolSource::Select => SelectToolMessage::SetPivot { position: pivot_input.value }.into(), + PivotToolSource::Path => PathToolMessage::SetPivot { position: pivot_input.value }.into(), + }) + .widget_holder() +} + +pub fn pivot_gizmo_type_widget(state: PivotGizmoState, source: PivotToolSource) -> Vec { + let gizmo_type_entries = [PivotGizmoType::Pivot, PivotGizmoType::Average, PivotGizmoType::Active] + .iter() + .map(|gizmo_type| { + MenuListEntry::new(format!("{gizmo_type:?}")).label(gizmo_type.to_string()).on_commit({ + let value = source.clone(); + move |_| match value { + PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::PivotGizmoType(*gizmo_type)).into(), + PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::PivotGizmoType(*gizmo_type)).into(), + } + }) + }) + .collect(); + + vec![ + CheckboxInput::new(!state.disabled) + .tooltip( + "Pivot Gizmo\n\ + \n\ + Enabled: the chosen gizmo type is shown and used to control rotation and scaling.\n\ + Disabled: rotation and scaling occurs about the center of the selection bounds.", + ) + .on_update(move |optional_input: &CheckboxInput| match source { + PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(), + PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(), + }) + .widget_holder(), + Separator::new(SeparatorType::Related).widget_holder(), + DropdownInput::new(vec![gizmo_type_entries]) + .selected_index(Some(match state.gizmo_type { + PivotGizmoType::Pivot => 0, + PivotGizmoType::Average => 1, + PivotGizmoType::Active => 2, + })) + .tooltip( + "Pivot Gizmo Type\n\ + \n\ + Selects which gizmo type is shown and used as the center of rotation/scaling transformations.\n\ + \n\ + Custom Pivot: rotates and scales relative to the selection bounds, or elsewhere if dragged.\n\ + Origin (Average Point): rotates and scales about the average point of all selected layer origins.\n\ + Origin (Active Object): rotates and scales about the origin of the most recently selected layer.", + ) + .disabled(state.disabled) + .widget_holder(), + ] +} + +#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)] +pub enum PivotToolSource { + Path, + #[default] + Select, +} + +#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)] +pub struct PivotGizmo { + pub pivot: Pivot, + pub state: PivotGizmoState, + pub layer: Option, + pub point: Option, +} + +impl PivotGizmo { + pub fn position(&self, document: &DocumentMessageHandler) -> DVec2 { + let network = &document.network_interface; + (!self.state.disabled) + .then_some({ + match self.state.gizmo_type { + PivotGizmoType::Average => Some(network.selected_nodes().selected_visible_and_unlocked_layers_mean_average_origin(network)), + PivotGizmoType::Pivot => self.pivot.pivot, + PivotGizmoType::Active => self.layer.map(|layer| graph_modification_utils::get_viewport_origin(layer, network)), + } + }) + .flatten() + .unwrap_or_else(|| self.pivot.transform_from_normalized.transform_point2(DVec2::splat(0.5))) + } + + pub fn recalculate_transform(&mut self, document: &DocumentMessageHandler) -> DAffine2 { + self.pivot.recalculate_pivot(document); + self.pivot.transform_from_normalized + } + + pub fn pin_active(&self) -> bool { + self.pivot.pinned && self.state.is_pivot_type() + } + + pub fn pivot_disconnected(&self) -> bool { + self.pivot.old_pivot_position == ReferencePoint::None + } +} + +#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +pub enum PivotGizmoType { + // Pivot + #[default] + Pivot, + // Origin + Average, + Active, + // TODO: Add "Individual" +} + +#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] +pub struct PivotGizmoState { + pub disabled: bool, + pub gizmo_type: PivotGizmoType, +} + +impl PivotGizmoState { + pub fn is_pivot_type(&self) -> bool { + self.gizmo_type == PivotGizmoType::Pivot || self.disabled + } + + pub fn is_pivot(&self) -> bool { + self.gizmo_type == PivotGizmoType::Pivot && !self.disabled + } +} + +impl fmt::Display for PivotGizmoType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PivotGizmoType::Pivot => write!(f, "Custom Pivot"), + PivotGizmoType::Average => write!(f, "Origin (Average Point)"), + PivotGizmoType::Active => write!(f, "Origin (Active Object)"), + // TODO: Add "Origin (Individual)" + } + } +} + +#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct Pivot { /// Pivot between (0,0) and (1,1) normalized_pivot: DVec2, /// Transform to get from normalized pivot to viewspace - transform_from_normalized: DAffine2, - /// The viewspace pivot position (if applicable) - pivot: Option, + pub transform_from_normalized: DAffine2, + /// The viewspace pivot position + pub pivot: Option, /// The old pivot position in the GUI, used to reduce refreshes of the document bar - old_pivot_position: ReferencePoint, + pub old_pivot_position: ReferencePoint, + /// The last ReferencePoint which wasn't none + pub last_non_none_reference_point: ReferencePoint, /// Used to enable and disable the pivot - active: bool, + pub pinned: bool, + /// Had selected_visible_and_unlocked_layers + pub empty: bool, } impl Default for Pivot { @@ -30,84 +188,62 @@ impl Default for Pivot { transform_from_normalized: Default::default(), pivot: Default::default(), old_pivot_position: ReferencePoint::Center, - active: true, + last_non_none_reference_point: ReferencePoint::Center, + pinned: false, + empty: true, } } } impl Pivot { - /// Calculates the transform that gets from normalized pivot to viewspace. - fn get_layer_pivot_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 { - let [min, max] = document.metadata().nonzero_bounding_box(layer); - - let bounds_transform = DAffine2::from_translation(min) * DAffine2::from_scale(max - min); - let layer_transform = document.metadata().transform_to_viewport(layer); - layer_transform * bounds_transform - } - /// Recomputes the pivot position and transform. - fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) { - if !self.active { + pub fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) { + let selected = document.network_interface.selected_nodes(); + self.empty = !selected.has_selected_nodes(); + if !selected.has_selected_nodes() { return; } - let selected_nodes = document.network_interface.selected_nodes(); - let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface); - let Some(first) = layers.next() else { - // If no layers are selected then we revert things back to default + let transform = selected + .selected_visible_and_unlocked_layers(&document.network_interface) + .find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[])) + .map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface)) + .unwrap_or_default(); + + let bounds = document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .filter_map(|layer| { + document + .metadata() + .bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer)) + }) + .reduce(graphene_std::renderer::Quad::combine_bounds); + + let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]); + self.transform_from_normalized = transform * DAffine2::from_translation(min) * DAffine2::from_scale(max - min); + + if self.old_pivot_position != ReferencePoint::None { + self.pivot = Some(self.transform_from_normalized.transform_point2(self.normalized_pivot)); + } + } + + pub fn recalculate_pivot_for_layer(&mut self, document: &DocumentMessageHandler, bounds: Option<[DVec2; 2]>) { + let selected = document.network_interface.selected_nodes(); + if !selected.has_selected_nodes() { self.normalized_pivot = DVec2::splat(0.5); self.pivot = None; return; }; - // Add one because the first item is consumed above. - let selected_layers_count = layers.count() + 1; - - // If just one layer is selected we can use its inner transform (as it accounts for rotation) - if selected_layers_count == 1 { - let normalized_pivot = graph_modification_utils::get_pivot(first, &document.network_interface).unwrap_or(DVec2::splat(0.5)); - self.normalized_pivot = normalized_pivot; - self.transform_from_normalized = Self::get_layer_pivot_transform(first, document); - self.pivot = Some(self.transform_from_normalized.transform_point2(normalized_pivot)); - } else { - // If more than one layer is selected we use the AABB with the mean of the pivots - let xy_summation = document - .network_interface - .selected_nodes() - .selected_visible_and_unlocked_layers(&document.network_interface) - .map(|layer| graph_modification_utils::get_viewport_pivot(layer, &document.network_interface)) - .reduce(|a, b| a + b) - .unwrap_or_default(); - - let pivot = xy_summation / selected_layers_count as f64; - self.pivot = Some(pivot); - let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]); - self.normalized_pivot = (pivot - min) / (max - min); - - self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min); - } - } - - pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, draw_data: Option<(f64,)>) { - if !overlay_context.visibility_settings.pivot() { - self.active = false; - return; - } else { - self.active = true; - } - - self.recalculate_pivot(document); - if let (Some(pivot), Some(data)) = (self.pivot, draw_data) { - overlay_context.pivot(pivot, data.0); - } + let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]); + self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min); + self.pivot = Some(self.transform_from_normalized.transform_point2(self.normalized_pivot)); } /// Answers if the pivot widget has changed (so we should refresh the tool bar at the top of the canvas). pub fn should_refresh_pivot_position(&mut self) -> bool { - if !self.active { - return false; - } - let new = self.to_pivot_position(); let should_refresh = new != self.old_pivot_position; self.old_pivot_position = new; @@ -118,37 +254,24 @@ impl Pivot { self.normalized_pivot.into() } - /// Sets the viewport position of the pivot for all selected layers. - pub fn set_viewport_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { - if !self.active { + /// Sets the viewport position of the pivot. + pub fn set_viewport_position(&mut self, position: DVec2) { + if self.transform_from_normalized.matrix2.determinant().abs() <= f64::EPSILON { return; - } + }; - for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) { - let transform = Self::get_layer_pivot_transform(layer, document); - // Only update the pivot when computed position is finite. - if transform.matrix2.determinant().abs() <= f64::EPSILON { - return; - }; - let pivot = transform.inverse().transform_point2(position); - responses.add(GraphOperationMessage::TransformSetPivot { layer, pivot }); - } + self.normalized_pivot = self.transform_from_normalized.inverse().transform_point2(position); + self.pivot = Some(position); } - /// Set the pivot using the normalized transform that is set above. - pub fn set_normalized_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque) { - if !self.active { - return; - } - - self.set_viewport_position(self.transform_from_normalized.transform_point2(position), document, responses); + /// Set the pivot using a normalized position. + pub fn set_normalized_position(&mut self, position: DVec2) { + self.normalized_pivot = position; + self.pivot = Some(self.transform_from_normalized.transform_point2(position)); } /// Answers if the pointer is currently positioned over the pivot. pub fn is_over(&self, mouse: DVec2) -> bool { - if !self.active { - return false; - } self.pivot.filter(|&pivot| mouse.distance_squared(pivot) < (PIVOT_DIAMETER / 2.).powi(2)).is_some() } } diff --git a/editor/src/messages/tool/common_functionality/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs index b491e0943d..f98e50f079 100644 --- a/editor/src/messages/tool/common_functionality/shape_editor.rs +++ b/editor/src/messages/tool/common_functionality/shape_editor.rs @@ -96,6 +96,14 @@ impl SelectedLayerState { self.selected_segments.remove(&segment); } + pub fn deselect_all_points_in_layer(&mut self) { + self.selected_points.clear(); + } + + pub fn deselect_all_segments_in_layer(&mut self) { + self.selected_segments.clear(); + } + pub fn clear_points(&mut self) { self.selected_points.clear(); } @@ -204,15 +212,15 @@ impl ClosestSegment { self.bezier_point_to_viewport } - pub fn closest_point(&self, document_metadata: &DocumentMetadata) -> DVec2 { - let transform = document_metadata.transform_to_viewport(self.layer); + pub fn closest_point(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> DVec2 { + let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface); let bezier_point = self.bezier.evaluate(TValue::Parametric(self.t)); transform.transform_point2(bezier_point) } /// Updates this [`ClosestSegment`] with the viewport-space location of the closest point on the segment to the given mouse position. - pub fn update_closest_point(&mut self, document_metadata: &DocumentMetadata, mouse_position: DVec2) { - let transform = document_metadata.transform_to_viewport(self.layer); + pub fn update_closest_point(&mut self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface, mouse_position: DVec2) { + let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface); let layer_mouse_pos = transform.inverse().transform_point2(mouse_position); let t = self.bezier.project(layer_mouse_pos).clamp(0., 1.); @@ -231,9 +239,9 @@ impl ClosestSegment { tolerance.powi(2) < self.distance_squared(mouse_position) } - pub fn handle_positions(&self, document_metadata: &DocumentMetadata) -> (Option, Option) { + pub fn handle_positions(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> (Option, Option) { // Transform to viewport space - let transform = document_metadata.transform_to_viewport(self.layer); + let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface); // Split the Bezier at the parameter `t` let [first, second] = self.bezier.split(TValue::Parametric(self.t)); @@ -299,7 +307,7 @@ impl ClosestSegment { } pub fn calculate_perp(&self, document: &DocumentMessageHandler) -> DVec2 { - let tangent = if let (Some(handle1), Some(handle2)) = self.handle_positions(document.metadata()) { + let tangent = if let (Some(handle1), Some(handle2)) = self.handle_positions(document.metadata(), &document.network_interface) { (handle1 - handle2).try_normalize() } else { let [first_point, last_point] = self.points(); @@ -331,7 +339,7 @@ impl ClosestSegment { break_colinear_molding: bool, temporary_adjacent_handles_while_molding: Option<[Option; 2]>, ) -> Option<[Option; 2]> { - let transform = document.metadata().transform_to_viewport(self.layer); + let transform = document.metadata().transform_to_viewport_if_feeds(self.layer, &document.network_interface); let start = self.bezier.start; let end = self.bezier.end; @@ -388,6 +396,10 @@ impl ClosestSegment { // TODO Consider keeping a list of selected manipulators to minimize traversals of the layers impl ShapeState { + pub fn is_selected_layer(&self, layer: LayerNodeIdentifier) -> bool { + self.selected_shape_state.contains_key(&layer) + } + pub fn is_point_ignored(&self, point: &ManipulatorPointId) -> bool { (point.as_handle().is_some() && self.ignore_handles) || (point.as_anchor().is_some() && self.ignore_anchors) } @@ -495,7 +507,7 @@ impl ShapeState { continue; }; - let to_document = document.metadata().transform_to_document(*layer); + let to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface); for &selected in &state.selected_points { let source = match selected { @@ -552,7 +564,11 @@ impl ShapeState { let already_selected = selected_shape_state.is_point_selected(manipulator_point_id); // Offset to snap the selected point to the cursor - let offset = mouse_position - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(point_position); + let offset = mouse_position + - network_interface + .document_metadata() + .transform_to_viewport_if_feeds(layer, network_interface) + .transform_point2(point_position); // This is selecting the manipulator only for now, next to generalize to points @@ -609,7 +625,11 @@ impl ShapeState { let already_selected = selected_shape_state.is_point_selected(manipulator_point_id); // Offset to snap the selected point to the cursor - let offset = mouse_position - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(point_position); + let offset = mouse_position + - network_interface + .document_metadata() + .transform_to_viewport_if_feeds(layer, network_interface) + .transform_point2(point_position); // Gather current selection information let points = self @@ -637,11 +657,11 @@ impl ShapeState { } /// Selects all anchors connected to the selected subpath, and deselects all handles, for the given layer. - pub fn select_connected_anchors(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, mouse: DVec2) { + pub fn select_connected(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, mouse: DVec2, points: bool, segments: bool) { let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { return; }; - let to_viewport = document.metadata().transform_to_viewport(layer); + let to_viewport = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface); let layer_mouse = to_viewport.inverse().transform_point2(mouse); let state = self.selected_shape_state.entry(layer).or_default(); @@ -655,18 +675,39 @@ impl ShapeState { } } state.clear_points(); + if selected_stack.is_empty() { - // Fall back on just selecting all points in the layer - for &point in vector_data.point_domain.ids() { - state.select_point(ManipulatorPointId::Anchor(point)) + // Fall back on just selecting all points/segments in the layer + if points { + for &point in vector_data.point_domain.ids() { + state.select_point(ManipulatorPointId::Anchor(point)); + } } - } else { - // Select all connected points - while let Some(point) = selected_stack.pop() { - let anchor_point = ManipulatorPointId::Anchor(point); - if !state.is_point_selected(anchor_point) { - state.select_point(anchor_point); - selected_stack.extend(vector_data.connected_points(point)); + if segments { + for &segment in vector_data.segment_domain.ids() { + state.select_segment(segment); + } + } + return; + } + + let mut connected_points = HashSet::new(); + + while let Some(point) = selected_stack.pop() { + if !connected_points.contains(&point) { + connected_points.insert(point); + selected_stack.extend(vector_data.connected_points(point)); + } + } + + if points { + connected_points.iter().for_each(|point| state.select_point(ManipulatorPointId::Anchor(*point))); + } + + if segments { + for (id, _, start, end) in vector_data.segment_bezier_iter() { + if connected_points.contains(&start) || connected_points.contains(&end) { + state.select_segment(id); } } } @@ -842,7 +883,7 @@ impl ShapeState { } let vector_data = network_interface.compute_modified_vector(layer)?; - let transform = network_interface.document_metadata().transform_to_document(layer).inverse(); + let transform = network_interface.document_metadata().transform_to_document_if_feeds(layer, network_interface).inverse(); let position = transform.transform_point2(new_position); let current_position = point.get_position(&vector_data)?; let delta = position - current_position; @@ -993,7 +1034,7 @@ impl ShapeState { let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue; }; - let transform = document.metadata().transform_to_document(layer); + let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface); for &point in layer_state.selected_points.iter() { let Some(handles) = point.get_handle_pair(&vector_data) else { continue }; @@ -1038,7 +1079,7 @@ impl ShapeState { let mut normalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize())); - if normalized.is_none() { + if normalized.is_none() || handle_directions.iter().any(|&d| d.is_some_and(|d| d.length_squared() < f64::EPSILON * 1e5)) { handle_directions = anchor_positions.map(|relative_anchor| relative_anchor.map(|relative_anchor| (relative_anchor - anchor) / 3.)); normalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize())) } @@ -1083,8 +1124,8 @@ impl ShapeState { let opposing_handles = handle_lengths.as_ref().and_then(|handle_lengths| handle_lengths.get(&layer)); - let transform_to_viewport_space = document.metadata().transform_to_viewport(layer); - let transform_to_document_space = document.metadata().transform_to_document(layer); + let transform_to_viewport_space = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface); + let transform_to_document_space = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface); let delta_transform = if in_viewport_space { transform_to_viewport_space } else { @@ -1177,7 +1218,7 @@ impl ShapeState { .iter() .filter_map(|(&layer, state)| { let vector_data = document.network_interface.compute_modified_vector(layer)?; - let transform = document.metadata().transform_to_document(layer); + let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface); let opposing_handle_lengths = vector_data .colinear_manipulators .iter() @@ -1542,7 +1583,7 @@ impl ShapeState { let mut manipulator_point = None; let vector_data = network_interface.compute_modified_vector(layer)?; - let viewspace = network_interface.document_metadata().transform_to_viewport(layer); + let viewspace = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface); // Handles for (segment_id, bezier, _, _) in vector_data.segment_bezier_iter() { @@ -1578,7 +1619,7 @@ impl ShapeState { /// Find the `t` value along the path segment we have clicked upon, together with that segment ID. fn closest_segment(&self, network_interface: &NodeNetworkInterface, layer: LayerNodeIdentifier, position: glam::DVec2, tolerance: f64) -> Option { - let transform = network_interface.document_metadata().transform_to_viewport(layer); + let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface); let layer_pos = transform.inverse().transform_point2(position); let tolerance = tolerance + 0.5; @@ -1752,7 +1793,7 @@ impl ShapeState { pub fn flip_smooth_sharp(&self, network_interface: &NodeNetworkInterface, target: glam::DVec2, tolerance: f64, responses: &mut VecDeque) -> bool { let mut process_layer = |layer| { let vector_data = network_interface.compute_modified_vector(layer)?; - let transform_to_screenspace = network_interface.document_metadata().transform_to_viewport(layer); + let transform_to_screenspace = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface); let mut result = None; let mut closest_distance_squared = tolerance * tolerance; @@ -1856,7 +1897,7 @@ impl ShapeState { let vector_data = network_interface.compute_modified_vector(layer); let Some(vector_data) = vector_data else { continue }; - let transform = network_interface.document_metadata().transform_to_viewport(layer); + let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface); assert_eq!(vector_data.segment_domain.ids().len(), vector_data.start_point().count()); assert_eq!(vector_data.segment_domain.ids().len(), vector_data.end_point().count()); diff --git a/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs b/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs index fe97c22318..700f3c2779 100644 --- a/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs +++ b/editor/src/messages/tool/common_functionality/shapes/ellipse_shape.rs @@ -77,7 +77,7 @@ mod test_ellipse { layers .filter_map(|layer| { let node_graph_layer = NodeGraphLayer::new(layer, &document.network_interface); - let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::protonode_identifier())?; + let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::IDENTIFIER)?; Some(ResolvedEllipse { radius_x: instrumented.grab_protonode_input::(&vec![ellipse_node], &editor.runtime).unwrap(), radius_y: instrumented.grab_protonode_input::(&vec![ellipse_node], &editor.runtime).unwrap(), diff --git a/editor/src/messages/tool/common_functionality/utility_functions.rs b/editor/src/messages/tool/common_functionality/utility_functions.rs index 23829271b0..6ae3f130b5 100644 --- a/editor/src/messages/tool/common_functionality/utility_functions.rs +++ b/editor/src/messages/tool/common_functionality/utility_functions.rs @@ -393,6 +393,7 @@ pub fn transforming_transform_cage( input: &InputPreprocessorMessageHandler, responses: &mut VecDeque, layers_dragging: &mut Vec, + center_of_transformation: Option, ) -> (bool, bool, bool) { let dragging_bounds = bounding_box_manager.as_mut().and_then(|bounding_box| { let edges = bounding_box.check_selected_edges(input.mouse.position); @@ -429,17 +430,12 @@ pub fn transforming_transform_cage( } }); - let mut selected = Selected::new( - &mut bounds.original_transforms, - &mut bounds.center_of_transformation, - layers_dragging, - responses, - &document.network_interface, - None, - &ToolType::Select, - None, - ); - bounds.center_of_transformation = selected.mean_average_of_pivots(); + bounds.center_of_transformation = center_of_transformation.unwrap_or_else(|| { + document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers_mean_average_origin(&document.network_interface) + }); // Check if we're hovering over a skew triangle let edges = bounds.check_selected_edges(input.mouse.position); @@ -469,18 +465,12 @@ pub fn transforming_transform_cage( } }); - let mut selected = Selected::new( - &mut bounds.original_transforms, - &mut bounds.center_of_transformation, - &selected, - responses, - &document.network_interface, - None, - &ToolType::Select, - None, - ); - - bounds.center_of_transformation = selected.mean_average_of_pivots(); + bounds.center_of_transformation = center_of_transformation.unwrap_or_else(|| { + document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers_mean_average_origin(&document.network_interface) + }); } *layers_dragging = selected; diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs index 9413495488..6000301a18 100644 --- a/editor/src/messages/tool/tool_message_handler.rs +++ b/editor/src/messages/tool/tool_message_handler.rs @@ -12,6 +12,7 @@ use graphene_std::raster::color::Color; const ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |context| DocumentMessage::DrawArtboardOverlays(context).into(); +#[derive(ExtractField)] pub struct ToolMessageData<'a> { pub document_id: DocumentId, pub document: &'a mut DocumentMessageHandler, @@ -21,7 +22,7 @@ pub struct ToolMessageData<'a> { pub preferences: &'a PreferencesMessageHandler, } -#[derive(Debug, Default)] +#[derive(Debug, Default, ExtractField)] pub struct ToolMessageHandler { pub tool_state: ToolFsmState, pub transform_layer_handler: TransformLayerMessageHandler, @@ -29,6 +30,7 @@ pub struct ToolMessageHandler { pub tool_is_active: bool, } +#[message_handler_data] impl MessageHandler> for ToolMessageHandler { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, data: ToolMessageData) { let ToolMessageData { @@ -181,6 +183,11 @@ impl MessageHandler> for ToolMessageHandler { send: Box::new(TransformLayerMessage::SelectionChanged.into()), }); + responses.add(BroadcastMessage::SubscribeEvent { + on: BroadcastEvent::SelectionChanged, + send: Box::new(SelectToolMessage::SyncHistory.into()), + }); + self.tool_is_active = true; let tool_data = &mut self.tool_state.tool_data; diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs index d72e28df51..e14058c32c 100644 --- a/editor/src/messages/tool/tool_messages/artboard_tool.rs +++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs @@ -13,7 +13,7 @@ use crate::messages::tool::common_functionality::transformation_cage::*; use graph_craft::document::NodeId; use graphene_std::renderer::Quad; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct ArtboardTool { fsm_state: ArtboardToolFsmState, data: ArtboardToolData, @@ -48,6 +48,7 @@ impl ToolMetadata for ArtboardTool { } } +#[message_handler_data] impl<'a> MessageHandler> for ArtboardTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, false); @@ -567,7 +568,7 @@ mod test_artboard { Ok(instrumented) => instrumented, Err(e) => panic!("Failed to evaluate graph: {}", e), }; - instrumented.grab_all_input::(&editor.runtime).collect() + instrumented.grab_all_input::(&editor.runtime).collect() } #[tokio::test] diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs index b468603989..a625bdb9e6 100644 --- a/editor/src/messages/tool/tool_messages/brush_tool.rs +++ b/editor/src/messages/tool/tool_messages/brush_tool.rs @@ -1,6 +1,6 @@ use super::tool_prelude::*; use crate::consts::DEFAULT_BRUSH_SIZE; -use crate::messages::portfolio::document::graph_operation::transform_utils::{get_current_normalized_pivot, get_current_transform}; +use crate::messages::portfolio::document::graph_operation::transform_utils::get_current_transform; use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::network_interface::FlowType; @@ -20,7 +20,7 @@ pub enum DrawMode { Restore, } -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct BrushTool { fsm_state: BrushToolFsmState, data: BrushToolData, @@ -185,6 +185,7 @@ impl LayoutHolder for BrushTool { } } +#[message_handler_data] impl<'a> MessageHandler> for BrushTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message else { @@ -286,9 +287,7 @@ impl BrushToolData { } if *reference == Some("Transform".to_string()) { - let upstream = document.metadata().upstream_transform(node_id); - let pivot = DAffine2::from_translation(upstream.transform_point2(get_current_normalized_pivot(&node.inputs))); - self.transform = pivot * get_current_transform(&node.inputs) * pivot.inverse() * self.transform; + self.transform = get_current_transform(&node.inputs) * self.transform; } } diff --git a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs index 3b3f324972..ce4b09d8e9 100644 --- a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs +++ b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs @@ -1,7 +1,7 @@ use super::tool_prelude::*; use crate::messages::tool::utility_types::DocumentToolData; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct EyedropperTool { fsm_state: EyedropperToolFsmState, data: EyedropperToolData, @@ -39,6 +39,7 @@ impl LayoutHolder for EyedropperTool { } } +#[message_handler_data] impl<'a> MessageHandler> for EyedropperTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true); diff --git a/editor/src/messages/tool/tool_messages/fill_tool.rs b/editor/src/messages/tool/tool_messages/fill_tool.rs index 5aeb063d87..94ad2b8c21 100644 --- a/editor/src/messages/tool/tool_messages/fill_tool.rs +++ b/editor/src/messages/tool/tool_messages/fill_tool.rs @@ -3,7 +3,7 @@ use crate::messages::portfolio::document::overlays::utility_types::OverlayContex use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; use graphene_std::vector::style::Fill; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct FillTool { fsm_state: FillToolFsmState, } @@ -41,6 +41,7 @@ impl LayoutHolder for FillTool { } } +#[message_handler_data] impl<'a> MessageHandler> for FillTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut (), tool_data, &(), responses, true); diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs index e2abddef2a..fb1539a818 100644 --- a/editor/src/messages/tool/tool_messages/freehand_tool.rs +++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs @@ -13,7 +13,7 @@ use graphene_std::Color; use graphene_std::vector::VectorModificationType; use graphene_std::vector::{PointId, SegmentId}; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct FreehandTool { fsm_state: FreehandToolFsmState, data: FreehandToolData, @@ -116,6 +116,7 @@ impl LayoutHolder for FreehandTool { } } +#[message_handler_data] impl<'a> MessageHandler> for FreehandTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message else { diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs index 030da8ec1d..0e84f9585f 100644 --- a/editor/src/messages/tool/tool_messages/gradient_tool.rs +++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs @@ -7,7 +7,7 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{Node use crate::messages::tool::common_functionality::snapping::SnapManager; use graphene_std::vector::style::{Fill, Gradient, GradientType}; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct GradientTool { fsm_state: GradientToolFsmState, data: GradientToolData, @@ -53,6 +53,7 @@ impl ToolMetadata for GradientTool { } } +#[message_handler_data] impl<'a> MessageHandler> for GradientTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message else { diff --git a/editor/src/messages/tool/tool_messages/navigate_tool.rs b/editor/src/messages/tool/tool_messages/navigate_tool.rs index 0a76a331dc..9f9ef0307b 100644 --- a/editor/src/messages/tool/tool_messages/navigate_tool.rs +++ b/editor/src/messages/tool/tool_messages/navigate_tool.rs @@ -1,6 +1,6 @@ use super::tool_prelude::*; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct NavigateTool { fsm_state: NavigateToolFsmState, tool_data: NavigateToolData, @@ -38,6 +38,7 @@ impl LayoutHolder for NavigateTool { } } +#[message_handler_data] impl<'a> MessageHandler> for NavigateTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true); diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs index ff929ce9bd..d54ce54274 100644 --- a/editor/src/messages/tool/tool_messages/path_tool.rs +++ b/editor/src/messages/tool/tool_messages/path_tool.rs @@ -1,8 +1,8 @@ use super::select_tool::extend_lasso; use super::tool_prelude::*; use crate::consts::{ - COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, SEGMENT_INSERTION_DISTANCE, - SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE, + COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DOUBLE_CLICK_MILLISECONDS, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, + SEGMENT_INSERTION_DISTANCE, SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE, }; use crate::messages::portfolio::document::overlays::utility_functions::{path_overlays, selected_segments}; use crate::messages::portfolio::document::overlays::utility_types::{DrawHandles, OverlayContext}; @@ -11,18 +11,20 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node use crate::messages::portfolio::document::utility_types::transformation::Axis; use crate::messages::preferences::SelectionMode; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; +use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget}; use crate::messages::tool::common_functionality::shape_editor::{ - ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState, + ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedLayerState, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState, }; use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager}; use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate}; -use bezier_rs::{Bezier, TValue}; +use bezier_rs::{Bezier, BezierHandles, TValue}; use graphene_std::renderer::Quad; +use graphene_std::transform::ReferencePoint; use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, VectorData}; use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType}; use std::vec; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct PathTool { fsm_state: PathToolFsmState, tool_data: PathToolData, @@ -58,7 +60,10 @@ pub enum PathToolMessage { }, Escape, ClosePath, - FlipSmoothSharp, + DoubleClick { + extend_selection: Key, + shrink_selection: Key, + }, GRS { // Should be `Key::KeyG` (Grab), `Key::KeyR` (Rotate), or `Key::KeyS` (Scale) key: Key, @@ -103,6 +108,9 @@ pub enum PathToolMessage { SelectedPointYChanged { new_y: f64, }, + SetPivot { + position: ReferencePoint, + }, SwapSelectedHandles, UpdateOptions(PathOptionsUpdate), UpdateSelectedPointsStatus { @@ -138,6 +146,9 @@ pub enum PathOptionsUpdate { OverlayModeType(PathOverlayMode), PointEditingMode { enabled: bool }, SegmentEditingMode { enabled: bool }, + PivotGizmoType(PivotGizmoType), + TogglePivotGizmoType(bool), + TogglePivotPinned, } impl ToolMetadata for PathTool { @@ -252,6 +263,20 @@ impl LayoutHolder for PathTool { .selected_index(Some(self.options.path_overlay_mode as u32)) .widget_holder(); + let [_checkbox, _dropdown] = { + let pivot_gizmo_type_widget = pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Path); + [pivot_gizmo_type_widget[0].clone(), pivot_gizmo_type_widget[2].clone()] + }; + + let has_something = !self.tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty(); + let _pivot_reference = pivot_reference_point_widget( + has_something || !self.tool_data.pivot_gizmo.state.is_pivot(), + self.tool_data.pivot_gizmo.pivot.to_pivot_position(), + PivotToolSource::Path, + ); + + let _pin_pivot = pin_pivot_widget(self.tool_data.pivot_gizmo.pin_active(), false, PivotToolSource::Path); + Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets: vec![ x_location, @@ -265,13 +290,22 @@ impl LayoutHolder for PathTool { point_editing_mode, related_seperator.clone(), segment_editing_mode, - unrelated_seperator, + unrelated_seperator.clone(), path_overlay_mode_widget, + unrelated_seperator.clone(), + // checkbox.clone(), + // related_seperator.clone(), + // dropdown.clone(), + // unrelated_seperator, + // pivot_reference, + // related_seperator.clone(), + // pin_pivot, ], }])) } } +#[message_handler_data] impl<'a> MessageHandler> for PathTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated); @@ -290,6 +324,29 @@ impl<'a> MessageHandler> for PathToo self.options.path_editing_mode.segment_editing_mode = enabled; responses.add(OverlaysMessage::Draw); } + PathOptionsUpdate::PivotGizmoType(gizmo_type) => { + if !self.tool_data.pivot_gizmo.state.disabled { + self.tool_data.pivot_gizmo.state.gizmo_type = gizmo_type; + responses.add(ToolMessage::UpdateHints); + let pivot_gizmo = self.tool_data.pivot_gizmo(); + responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); + responses.add(NodeGraphMessage::RunDocumentGraph); + self.send_layout(responses, LayoutTarget::ToolOptions); + } + } + PathOptionsUpdate::TogglePivotGizmoType(state) => { + self.tool_data.pivot_gizmo.state.disabled = !state; + responses.add(ToolMessage::UpdateHints); + responses.add(NodeGraphMessage::RunDocumentGraph); + self.send_layout(responses, LayoutTarget::ToolOptions); + } + + PathOptionsUpdate::TogglePivotPinned => { + self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned; + responses.add(ToolMessage::UpdateHints); + responses.add(NodeGraphMessage::RunDocumentGraph); + self.send_layout(responses, LayoutTarget::ToolOptions); + } }, ToolMessage::Path(PathToolMessage::ClosePath) => { responses.add(DocumentMessage::AddTransaction); @@ -319,7 +376,7 @@ impl<'a> MessageHandler> for PathToo fn actions(&self) -> ActionList { match self.fsm_state { PathToolFsmState::Ready => actions!(PathToolMessageDiscriminant; - FlipSmoothSharp, + DoubleClick, MouseDown, Delete, NudgeSelectedPoints, @@ -334,7 +391,7 @@ impl<'a> MessageHandler> for PathToo PathToolFsmState::Dragging(_) => actions!(PathToolMessageDiscriminant; Escape, RightClick, - FlipSmoothSharp, + DoubleClick, DragStop, PointerMove, Delete, @@ -343,7 +400,7 @@ impl<'a> MessageHandler> for PathToo SwapSelectedHandles, ), PathToolFsmState::Drawing { .. } => actions!(PathToolMessageDiscriminant; - FlipSmoothSharp, + DoubleClick, DragStop, PointerMove, Delete, @@ -359,12 +416,6 @@ impl<'a> MessageHandler> for PathToo Escape, RightClick ), - PathToolFsmState::MoldingSegment => actions!(PathToolMessageDiscriminant; - PointerMove, - DragStop, - RightClick, - Escape, - ), } } } @@ -416,7 +467,6 @@ enum PathToolFsmState { selection_shape: SelectionShapeType, }, SlidingPoint, - MoldingSegment, } #[derive(Default)] @@ -447,6 +497,8 @@ struct PathToolData { last_click_time: u64, dragging_state: DraggingState, angle: f64, + pivot_gizmo: PivotGizmo, + ordered_points: Vec, opposite_handle_position: Option, last_clicked_point_was_selected: bool, last_clicked_segment_was_selected: bool, @@ -462,6 +514,8 @@ struct PathToolData { adjacent_anchor_offset: Option, sliding_point_info: Option, started_drawing_from_inside: bool, + first_selected_with_single_click: bool, + stored_selection: Option>, } impl PathToolData { @@ -544,8 +598,9 @@ impl PathToolData { self.drag_start_pos = input.mouse.position; - if !self.saved_points_before_anchor_convert_smooth_sharp.is_empty() && (input.time - self.last_click_time > 500) { + if input.time - self.last_click_time > DOUBLE_CLICK_MILLISECONDS { self.saved_points_before_anchor_convert_smooth_sharp.clear(); + self.stored_selection = None; } self.last_click_time = input.time; @@ -675,30 +730,30 @@ impl PathToolData { responses.add(OverlaysMessage::Draw); PathToolFsmState::Dragging(self.dragging_state) } else { - let handle1 = ManipulatorPointId::PrimaryHandle(segment.segment()); - let handle2 = ManipulatorPointId::EndHandle(segment.segment()); - if let Some(vector_data) = document.network_interface.compute_modified_vector(segment.layer()) { - if let (Some(pos1), Some(pos2)) = (handle1.get_position(&vector_data), handle2.get_position(&vector_data)) { - self.molding_info = Some((pos1, pos2)) - } - } - PathToolFsmState::MoldingSegment + let start_pos = segment.bezier().start; + let end_pos = segment.bezier().end; + + let [pos1, pos2] = match segment.bezier().handles { + BezierHandles::Cubic { handle_start, handle_end } => [handle_start, handle_end], + BezierHandles::Quadratic { handle } => [handle, end_pos], + BezierHandles::Linear => [start_pos + (end_pos - start_pos) / 3., end_pos + (start_pos - end_pos) / 3.], + }; + self.molding_info = Some((pos1, pos2)); + PathToolFsmState::Dragging(self.dragging_state) } } - // We didn't find a segment, so consider selecting the nearest shape instead and start drawing + // If no other layers are selected and this is a single-click, then also select the layer (exception) else if let Some(layer) = document.click(input) { - shape_editor.deselect_all_points(); - shape_editor.deselect_all_segments(); - if extend_selection { - responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] }); - } else { + if shape_editor.selected_shape_state.is_empty() { + self.first_selected_with_single_click = true; responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }); } - self.drag_start_pos = input.mouse.position; - self.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); self.started_drawing_from_inside = true; + self.drag_start_pos = input.mouse.position; + self.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); + let selection_shape = if lasso_select { SelectionShapeType::Lasso } else { SelectionShapeType::Box }; PathToolFsmState::Drawing { selection_shape } } @@ -719,7 +774,7 @@ impl PathToolData { let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue; }; - let transform = document.metadata().transform_to_document(layer); + let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface); let mut layer_manipulators = HashSet::with_hasher(NoHashBuilder); for point in state.selected_points() { @@ -829,7 +884,7 @@ impl PathToolData { let selected_handle = selection.selected_points().next()?.as_handle()?; let handle_id = selected_handle.to_manipulator_point(); - let layer_to_document = document.metadata().transform_to_document(*layer); + let layer_to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface); let vector_data = document.network_interface.compute_modified_vector(*layer)?; let handle_position_local = selected_handle.to_manipulator_point().get_position(&vector_data)?; @@ -871,7 +926,7 @@ impl PathToolData { let anchor = handle_id.get_anchor(&vector_data); let (angle, anchor_position) = calculate_adjacent_anchor_tangent(handle_id, anchor, adjacent_anchor, &vector_data); - let layer_to_document = document.metadata().transform_to_document(*layer); + let layer_to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface); self.adjacent_anchor_offset = handle_id .get_anchor_position(&vector_data) @@ -1018,7 +1073,7 @@ impl PathToolData { } // If already hovering on a segment, then recalculate its closest point else if let Some(closest_segment) = &mut self.segment { - closest_segment.update_closest_point(document.metadata(), position); + closest_segment.update_closest_point(document.metadata(), &document.network_interface, position); if closest_segment.too_far(position, SEGMENT_INSERTION_DISTANCE) { self.segment = None; @@ -1085,7 +1140,7 @@ impl PathToolData { let layer = sliding_point_info.layer; let Some(vector_data) = network_interface.compute_modified_vector(layer) else { return }; - let transform = network_interface.document_metadata().transform_to_viewport(layer); + let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface); let layer_pos = transform.inverse().transform_point2(target_position); let segments = sliding_point_info.connected_segments; @@ -1316,6 +1371,16 @@ impl PathToolData { } } } + + fn pivot_gizmo(&self) -> PivotGizmo { + self.pivot_gizmo.clone() + } + + fn sync_history(&mut self, points: &[ManipulatorPointId]) { + self.ordered_points.retain(|layer| points.contains(layer)); + self.ordered_points.extend(points.iter().find(|&layer| !self.ordered_points.contains(layer))); + self.pivot_gizmo.point = self.ordered_points.last().copied() + } } impl Fsm for PathToolFsmState { @@ -1328,6 +1393,10 @@ impl Fsm for PathToolFsmState { update_dynamic_hints(self, responses, shape_editor, document, tool_data, tool_options); let ToolMessage::Path(event) = event else { return self }; + + // TODO(mTvare6): Remove once gizmos are implemented for path_tool + tool_data.pivot_gizmo.state.disabled = true; + match (self, event) { (_, PathToolMessage::SelectionChanged) => { // Set the newly targeted layers to visible @@ -1344,6 +1413,9 @@ impl Fsm for PathToolFsmState { shape_editor.update_selected_anchors_status(display_anchors); shape_editor.update_selected_handles_status(display_handles); + let new_points = shape_editor.selected_points().copied().collect::>(); + tool_data.sync_history(&new_points); + self } (_, PathToolMessage::Overlays(mut overlay_context)) => { @@ -1413,7 +1485,7 @@ impl Fsm for PathToolFsmState { if let Some(closest_segment) = &tool_data.segment { if tool_options.path_editing_mode.segment_editing_mode { - let transform = document.metadata().transform_to_viewport(closest_segment.layer()); + let transform = document.metadata().transform_to_viewport_if_feeds(closest_segment.layer(), &document.network_interface); overlay_context.outline_overlay_bezier(closest_segment.bezier(), transform); @@ -1431,7 +1503,7 @@ impl Fsm for PathToolFsmState { } } else { let perp = closest_segment.calculate_perp(document); - let point = closest_segment.closest_point(document.metadata()); + let point = closest_segment.closest_point(document.metadata(), &document.network_interface); // Draw an X on the segment if tool_data.delete_segment_pressed { @@ -1501,7 +1573,6 @@ impl Fsm for PathToolFsmState { } } Self::SlidingPoint => {} - Self::MoldingSegment => {} } responses.add(PathToolMessage::SelectedPointUpdated); @@ -1557,7 +1628,9 @@ impl Fsm for PathToolFsmState { }, ) => { tool_data.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position); + tool_data.started_drawing_from_inside = false; + tool_data.stored_selection = None; if selection_shape == SelectionShapeType::Lasso { extend_lasso(&mut tool_data.lasso_polygon, input.mouse.position); @@ -1604,21 +1677,35 @@ impl Fsm for PathToolFsmState { break_colinear_molding, }, ) => { - let mut selected_only_handles = true; - - let selected_points = shape_editor.selected_points(); - - for point in selected_points { - if matches!(point, ManipulatorPointId::Anchor(_)) { - selected_only_handles = false; - break; - } - } + let selected_only_handles = !shape_editor.selected_points().any(|point| matches!(point, ManipulatorPointId::Anchor(_))); + tool_data.stored_selection = None; if !tool_data.saved_points_before_handle_drag.is_empty() && (tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD) && (selected_only_handles) { tool_data.handle_drag_toggle = true; } + if tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD { + tool_data.molding_segment = true; + } + + let break_molding = input.keyboard.get(break_colinear_molding as usize); + + // Logic for molding segment + if let Some(segment) = &mut tool_data.segment { + if let Some(molding_segment_handles) = tool_data.molding_info { + tool_data.temporary_adjacent_handles_while_molding = segment.mold_handle_positions( + document, + responses, + molding_segment_handles, + input.mouse.position, + break_molding, + tool_data.temporary_adjacent_handles_while_molding, + ); + } + + return PathToolFsmState::Dragging(tool_data.dragging_state); + } + let anchor_and_handle_toggled = input.keyboard.get(move_anchor_with_handles as usize); let initial_press = anchor_and_handle_toggled && !tool_data.select_anchor_toggled; let released_from_toggle = tool_data.select_anchor_toggled && !anchor_and_handle_toggled; @@ -1694,39 +1781,11 @@ impl Fsm for PathToolFsmState { tool_data.slide_point(input.mouse.position, responses, &document.network_interface, shape_editor); PathToolFsmState::SlidingPoint } - (PathToolFsmState::MoldingSegment, PathToolMessage::PointerMove { break_colinear_molding, .. }) => { - if tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD { - tool_data.molding_segment = true; - } - - let break_colinear_molding = input.keyboard.get(break_colinear_molding as usize); - - // Logic for molding segment - if let Some(segment) = &mut tool_data.segment { - if let Some(molding_segment_handles) = tool_data.molding_info { - tool_data.temporary_adjacent_handles_while_molding = segment.mold_handle_positions( - document, - responses, - molding_segment_handles, - input.mouse.position, - break_colinear_molding, - tool_data.temporary_adjacent_handles_while_molding, - ); - } - } - - PathToolFsmState::MoldingSegment - } (PathToolFsmState::Ready, PathToolMessage::PointerMove { delete_segment, .. }) => { tool_data.delete_segment_pressed = input.keyboard.get(delete_segment as usize); - - if !tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty() { - tool_data.saved_points_before_anchor_convert_smooth_sharp.clear(); - } - - if tool_data.adjacent_anchor_offset.is_some() { - tool_data.adjacent_anchor_offset = None; - } + tool_data.saved_points_before_anchor_convert_smooth_sharp.clear(); + tool_data.adjacent_anchor_offset = None; + tool_data.stored_selection = None; responses.add(OverlaysMessage::Draw); @@ -1847,6 +1906,9 @@ impl Fsm for PathToolFsmState { tool_data.saved_points_before_handle_drag.clear(); tool_data.handle_drag_toggle = false; } + tool_data.molding_info = None; + tool_data.molding_segment = false; + tool_data.temporary_adjacent_handles_while_molding = None; tool_data.angle_locked = false; responses.add(DocumentMessage::AbortTransaction); tool_data.snap_manager.cleanup(responses); @@ -1864,17 +1926,6 @@ impl Fsm for PathToolFsmState { PathToolFsmState::Ready } - (PathToolFsmState::MoldingSegment, PathToolMessage::Escape | PathToolMessage::RightClick) => { - // Undo the molding and go back to the state before - tool_data.molding_info = None; - tool_data.molding_segment = false; - tool_data.temporary_adjacent_handles_while_molding = None; - - responses.add(DocumentMessage::AbortTransaction); - tool_data.snap_manager.cleanup(responses); - - PathToolFsmState::Ready - } // Mouse up (PathToolFsmState::Drawing { selection_shape }, PathToolMessage::DragStop { extend_selection, shrink_selection }) => { let extend_selection = input.keyboard.get(extend_selection as usize); @@ -1895,12 +1946,16 @@ impl Fsm for PathToolFsmState { SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(document.metadata()), selection_mode => selection_mode, }; + tool_data.started_drawing_from_inside = false; if tool_data.drag_start_pos.distance(previous_mouse) < 1e-8 { - // If click happens inside of a shape then don't set selected nodes to empty - if document.click(input).is_none() { - responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] }); + // Clicked inside or outside the shape then deselect all of the points/segments + if document.click(input).is_some() && tool_data.stored_selection.is_none() { + tool_data.stored_selection = Some(shape_editor.selected_shape_state.clone()); } + + shape_editor.deselect_all_points(); + shape_editor.deselect_all_segments(); } else { match selection_shape { SelectionShapeType::Box => { @@ -2072,8 +2127,8 @@ impl Fsm for PathToolFsmState { shape_editor.delete_point_and_break_path(document, responses); PathToolFsmState::Ready } - (_, PathToolMessage::FlipSmoothSharp) => { - // Double-clicked on a point + (_, PathToolMessage::DoubleClick { extend_selection, shrink_selection }) => { + // Double-clicked on a point (flip smooth/sharp behavior) let nearest_point = shape_editor.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD); if nearest_point.is_some() { // Flip the selected point between smooth and sharp @@ -2090,13 +2145,70 @@ impl Fsm for PathToolFsmState { return PathToolFsmState::Ready; } - // Double-clicked on a filled region - if let Some(layer) = document.click(input) { - // Select all points in the layer - shape_editor.select_connected_anchors(document, layer, input.mouse.position); + else if let Some(layer) = document.click(input) { + let extend_selection = input.keyboard.get(extend_selection as usize); + let shrink_selection = input.keyboard.get(shrink_selection as usize); + + if shape_editor.is_selected_layer(layer) { + if extend_selection && !tool_data.first_selected_with_single_click { + responses.add(NodeGraphMessage::SelectedNodesRemove { nodes: vec![layer.to_node()] }); + + if let Some(selection) = &tool_data.stored_selection { + let mut selection = selection.clone(); + selection.remove(&layer); + shape_editor.selected_shape_state = selection; + tool_data.stored_selection = None; + } + } else if shrink_selection && !tool_data.first_selected_with_single_click { + // Only deselect all the points of the double clicked layer + if let Some(selection) = &tool_data.stored_selection { + let selection = selection.clone(); + shape_editor.selected_shape_state = selection; + tool_data.stored_selection = None; + } + + let state = shape_editor.selected_shape_state.get_mut(&layer).expect("No state for selected layer"); + state.deselect_all_points_in_layer(); + state.deselect_all_segments_in_layer(); + } else if !tool_data.first_selected_with_single_click { + // Select according to the selected editing mode + let point_editing_mode = tool_options.path_editing_mode.point_editing_mode; + let segment_editing_mode = tool_options.path_editing_mode.segment_editing_mode; + shape_editor.select_connected(document, layer, input.mouse.position, point_editing_mode, segment_editing_mode); + + // Select all the other layers back again + if let Some(selection) = &tool_data.stored_selection { + let mut selection = selection.clone(); + selection.remove(&layer); + + for (layer, state) in selection { + shape_editor.selected_shape_state.insert(layer, state); + } + tool_data.stored_selection = None; + } + } + + // If it was the very first click without there being an existing selection, + // then the single-click behavior and double-click behavior should not collide + tool_data.first_selected_with_single_click = false; + } else if extend_selection { + responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] }); + + if let Some(selection) = &tool_data.stored_selection { + shape_editor.selected_shape_state = selection.clone(); + tool_data.stored_selection = None; + } + } else { + responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] }); + } + responses.add(OverlaysMessage::Draw); } + // Double clicked on the background + else { + responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] }); + } PathToolFsmState::Ready } @@ -2163,6 +2275,18 @@ impl Fsm for PathToolFsmState { responses.add(DocumentMessage::EndTransaction); PathToolFsmState::Ready } + (_, PathToolMessage::SetPivot { position }) => { + responses.add(DocumentMessage::StartTransaction); + + tool_data.pivot_gizmo.pivot.last_non_none_reference_point = position; + let position: Option = position.into(); + tool_data.pivot_gizmo.pivot.set_normalized_position(position.unwrap()); + let pivot_gizmo = tool_data.pivot_gizmo(); + responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); + responses.add(NodeGraphMessage::RunDocumentGraph); + + self + } (_, _) => PathToolFsmState::Ready, } } @@ -2235,7 +2359,10 @@ fn get_selection_status(network_interface: &NodeNetworkInterface, shape_state: & return SelectionStatus::None; }; - let coordinates = network_interface.document_metadata().transform_to_document(layer).transform_point2(local_position); + let coordinates = network_interface + .document_metadata() + .transform_to_document_if_feeds(layer, network_interface) + .transform_point2(local_position); let manipulator_angle = if vector_data.colinear(point) { ManipulatorAngle::Colinear } else { ManipulatorAngle::Free }; return SelectionStatus::One(SingleSelectedPoint { @@ -2529,7 +2656,40 @@ fn update_dynamic_hints( dragging_hint_data.0.push(HintGroup(hold_group)); } - dragging_hint_data + if tool_data.molding_segment { + let mut has_colinear_anchors = false; + + if let Some(segment) = &tool_data.segment { + let handle1 = HandleId::primary(segment.segment()); + let handle2 = HandleId::end(segment.segment()); + + if let Some(vector_data) = document.network_interface.compute_modified_vector(segment.layer()) { + let other_handle1 = vector_data.other_colinear_handle(handle1); + let other_handle2 = vector_data.other_colinear_handle(handle2); + if other_handle1.is_some() || other_handle2.is_some() { + has_colinear_anchors = true; + } + }; + } + + let handles_stored = if let Some(other_handles) = tool_data.temporary_adjacent_handles_while_molding { + other_handles[0].is_some() || other_handles[1].is_some() + } else { + false + }; + + let molding_disable_possible = has_colinear_anchors || handles_stored; + + let mut molding_hints = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]; + + if molding_disable_possible { + molding_hints.push(HintGroup(vec![HintInfo::keys([Key::Alt], "Break Colinear Handles")])); + } + + HintData(molding_hints) + } else { + dragging_hint_data + } } PathToolFsmState::Drawing { .. } => HintData(vec![ HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]), @@ -2539,38 +2699,6 @@ fn update_dynamic_hints( HintInfo::keys([Key::Alt], "Subtract").prepend_plus(), ]), ]), - PathToolFsmState::MoldingSegment => { - let mut has_colinear_anchors = false; - - if let Some(segment) = &tool_data.segment { - let handle1 = HandleId::primary(segment.segment()); - let handle2 = HandleId::end(segment.segment()); - - if let Some(vector_data) = document.network_interface.compute_modified_vector(segment.layer()) { - let other_handle1 = vector_data.other_colinear_handle(handle1); - let other_handle2 = vector_data.other_colinear_handle(handle2); - if other_handle1.is_some() || other_handle2.is_some() { - has_colinear_anchors = true; - } - }; - } - - let handles_stored = if let Some(other_handles) = tool_data.temporary_adjacent_handles_while_molding { - other_handles[0].is_some() || other_handles[1].is_some() - } else { - false - }; - - let molding_disable_possible = has_colinear_anchors || handles_stored; - - let mut molding_hints = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]; - - if molding_disable_possible { - molding_hints.push(HintGroup(vec![HintInfo::keys([Key::Alt], "Break Colinear Handles")])); - } - - HintData(molding_hints) - } PathToolFsmState::SlidingPoint => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]), }; responses.add(FrontendMessage::UpdateInputHints { hint_data }); diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs index 14f95a48f7..53a9e4d17c 100644 --- a/editor/src/messages/tool/tool_messages/pen_tool.rs +++ b/editor/src/messages/tool/tool_messages/pen_tool.rs @@ -17,7 +17,7 @@ use graphene_std::Color; use graphene_std::vector::{HandleId, ManipulatorPointId, NoHashBuilder, SegmentId, StrokeId, VectorData}; use graphene_std::vector::{PointId, VectorModificationType}; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct PenTool { fsm_state: PenToolFsmState, tool_data: PenToolData, @@ -186,6 +186,7 @@ impl LayoutHolder for PenTool { } } +#[message_handler_data] impl<'a> MessageHandler> for PenTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message else { diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs index 76158d08e5..e6f4a5d7a3 100644 --- a/editor/src/messages/tool/tool_messages/select_tool.rs +++ b/editor/src/messages/tool/tool_messages/select_tool.rs @@ -12,9 +12,10 @@ use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes; use crate::messages::preferences::SelectionMode; use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose}; +use crate::messages::tool::common_functionality::graph_modification_utils; use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name; use crate::messages::tool::common_functionality::measure; -use crate::messages::tool::common_functionality::pivot::Pivot; +use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget}; use crate::messages::tool::common_functionality::shape_editor::SelectionShapeType; use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager}; use crate::messages::tool::common_functionality::transformation_cage::*; @@ -28,7 +29,7 @@ use graphene_std::renderer::Rect; use graphene_std::transform::ReferencePoint; use std::fmt; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct SelectTool { fsm_state: SelectToolFsmState, tool_data: SelectToolData, @@ -43,6 +44,9 @@ pub struct SelectOptions { #[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] pub enum SelectOptionsUpdate { NestedSelectionBehavior(NestedSelectionBehavior), + PivotGizmoType(PivotGizmoType), + TogglePivotGizmoType(bool), + TogglePivotPinned, } #[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] @@ -95,6 +99,14 @@ pub enum SelectToolMessage { SetPivot { position: ReferencePoint, }, + SyncHistory, + ShiftSelectedNodes { + offset: DVec2, + }, + PivotShift { + offset: Option, + flush: bool, + }, } impl ToolMetadata for SelectTool { @@ -122,14 +134,12 @@ impl SelectTool { DropdownInput::new(vec![layer_selection_behavior_entries]) .selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest) as u32)) - .tooltip("Choose if clicking nested layers directly selects the deepest, or selects the shallowest and deepens by double clicking") - .widget_holder() - } - - fn pivot_reference_point_widget(&self, disabled: bool) -> WidgetHolder { - ReferencePointInput::new(self.tool_data.pivot.to_pivot_position()) - .on_update(|pivot_input: &ReferencePointInput| SelectToolMessage::SetPivot { position: pivot_input.value }.into()) - .disabled(disabled) + .tooltip( + "Selection Mode\n\ + \n\ + Shallow Select: clicks initially select the least-nested layers and double clicks drill deeper into the folder hierarchy.\n\ + Deep Select: clicks directly select the most-nested layers in the folder hierarchy.", + ) .widget_holder() } @@ -178,7 +188,7 @@ impl SelectTool { fn boolean_widgets(&self, selected_count: usize) -> impl Iterator + use<> { let list = ::list(); - list.into_iter().map(|i| i.into_iter()).flatten().map(move |(operation, info)| { + list.iter().flat_map(|i| i.iter()).map(move |(operation, info)| { let mut tooltip = info.label.to_string(); if let Some(doc) = info.docstring.as_deref() { tooltip.push_str("\n\n"); @@ -203,9 +213,29 @@ impl LayoutHolder for SelectTool { // Select mode (Deep/Shallow) widgets.push(self.deep_selection_widget()); - // Pivot + // Pivot gizmo type (checkbox + dropdown for pivot/origin) widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); - widgets.push(self.pivot_reference_point_widget(self.tool_data.selected_layers_count == 0)); + widgets.extend(pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Select)); + + if self.tool_data.pivot_gizmo.state.is_pivot_type() { + // Nine-position reference point widget + widgets.push(Separator::new(SeparatorType::Related).widget_holder()); + widgets.push(pivot_reference_point_widget( + self.tool_data.selected_layers_count == 0 || !self.tool_data.pivot_gizmo.state.is_pivot(), + self.tool_data.pivot_gizmo.pivot.to_pivot_position(), + PivotToolSource::Select, + )); + + // Pivot pin button + widgets.push(Separator::new(SeparatorType::Related).widget_holder()); + + let pin_active = self.tool_data.pivot_gizmo.pin_active(); + let pin_enabled = self.tool_data.pivot_gizmo.pivot.old_pivot_position == ReferencePoint::None && !self.tool_data.pivot_gizmo.state.disabled; + + if pin_active || pin_enabled { + widgets.push(pin_pivot_widget(pin_active, pin_enabled, PivotToolSource::Select)); + } + } // Align let disabled = self.tool_data.selected_layers_count < 2; @@ -242,16 +272,46 @@ impl LayoutHolder for SelectTool { } } +#[message_handler_data] impl<'a> MessageHandler> for SelectTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { - if let ToolMessage::Select(SelectToolMessage::SelectOptions(SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior))) = message { - self.tool_data.nested_selection_behavior = nested_selection_behavior; - responses.add(ToolMessage::UpdateHints); + let mut redraw_reference_pivot = false; + + if let ToolMessage::Select(SelectToolMessage::SelectOptions(ref option_update)) = message { + match option_update { + SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior) => { + self.tool_data.nested_selection_behavior = *nested_selection_behavior; + responses.add(ToolMessage::UpdateHints); + } + SelectOptionsUpdate::PivotGizmoType(gizmo_type) => { + if !self.tool_data.pivot_gizmo.state.disabled { + self.tool_data.pivot_gizmo.state.gizmo_type = *gizmo_type; + responses.add(ToolMessage::UpdateHints); + let pivot_gizmo = self.tool_data.pivot_gizmo(); + responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); + responses.add(NodeGraphMessage::RunDocumentGraph); + redraw_reference_pivot = true; + } + } + SelectOptionsUpdate::TogglePivotGizmoType(state) => { + self.tool_data.pivot_gizmo.state.disabled = !state; + responses.add(ToolMessage::UpdateHints); + responses.add(NodeGraphMessage::RunDocumentGraph); + redraw_reference_pivot = true; + } + + SelectOptionsUpdate::TogglePivotPinned => { + self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned; + responses.add(ToolMessage::UpdateHints); + responses.add(NodeGraphMessage::RunDocumentGraph); + redraw_reference_pivot = true; + } + } } self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, false); - if self.tool_data.pivot.should_refresh_pivot_position() || self.tool_data.selected_layers_changed { + if self.tool_data.pivot_gizmo.pivot.should_refresh_pivot_position() || self.tool_data.selected_layers_changed || redraw_reference_pivot { // Send the layout containing the updated pivot position (a bit ugly to do it here not in the fsm but that doesn't have SelectTool) self.send_layout(responses, LayoutTarget::ToolOptions); self.tool_data.selected_layers_changed = false; @@ -323,7 +383,8 @@ struct SelectToolData { drag_current: ViewportPosition, lasso_polygon: Vec, selection_mode: Option, - layers_dragging: Vec, + layers_dragging: Vec, // Unordered, often used as temporary buffer + ordered_layers: Vec, // Ordered list of layers layer_selected_on_start: Option, select_single_layer: Option, axis_align: bool, @@ -331,7 +392,9 @@ struct SelectToolData { bounding_box_manager: Option, snap_manager: SnapManager, cursor: MouseCursorIcon, - pivot: Pivot, + pivot_gizmo: PivotGizmo, + pivot_gizmo_start: Option, + pivot_gizmo_shift: Option, compass_rose: CompassRose, line_center: DVec2, skew_edge: EdgeBool, @@ -497,6 +560,24 @@ impl SelectToolData { responses.add(NodeGraphMessage::SendGraph); self.layers_dragging = original; } + + fn state_from_pivot_gizmo(&self, mouse: DVec2) -> Option { + match self.pivot_gizmo.state.gizmo_type { + PivotGizmoType::Pivot if self.pivot_gizmo.state.is_pivot() => self.pivot_gizmo.pivot.is_over(mouse).then_some(SelectToolFsmState::DraggingPivot), + _ => None, + } + } + + fn pivot_gizmo(&self) -> PivotGizmo { + self.pivot_gizmo.clone() + } + + fn sync_history(&mut self, document: &DocumentMessageHandler) { + let layers: Vec<_> = document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface).collect(); + self.ordered_layers.retain(|layer| layers.contains(layer)); + self.ordered_layers.extend(layers.iter().find(|&layer| !self.ordered_layers.contains(layer))); + self.pivot_gizmo.layer = self.ordered_layers.last().copied() + } } impl Fsm for SelectToolFsmState { @@ -710,8 +791,63 @@ impl Fsm for SelectToolFsmState { .flatten() }); - // Update pivot - tool_data.pivot.update_pivot(document, &mut overlay_context, Some((angle,))); + let mut active_origin = None; + let mut origin_angle = 0.; + if overlay_context.visibility_settings.origin() && !tool_data.pivot_gizmo.state.is_pivot_type() { + let get_angle = |layer: LayerNodeIdentifier| -> f64 { + let quad = Quad::from_box([DVec2::ZERO, DVec2::ONE]); + let bounds = document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface) * quad; + (bounds.top_left() - bounds.top_right()).to_angle() + }; + if tool_data.pivot_gizmo.state.gizmo_type == PivotGizmoType::Average { + let mut count = 0_usize; + + let sum: f64 = document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .map(get_angle) + .inspect(|_| count += 1) + .sum(); + if count > 0 { + origin_angle = sum / count as f64; + } + } else if tool_data.pivot_gizmo.state.gizmo_type == PivotGizmoType::Active { + origin_angle = document + .network_interface + .selected_nodes() + .selected_visible_and_unlocked_layers(&document.network_interface) + .find(|&layer| Some(layer) == tool_data.pivot_gizmo.layer) + .iter() + .map(|&layer| get_angle(layer)) + .sum(); + } + + for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) { + let origin = graph_modification_utils::get_viewport_origin(layer, &document.network_interface); + if Some(layer) == tool_data.pivot_gizmo.layer { + active_origin = Some(origin); + continue; + } + overlay_context.dowel_pin(origin, origin_angle, None); + } + } + if let Some(origin) = active_origin { + overlay_context.dowel_pin(origin, origin_angle, Some(COLOR_OVERLAY_YELLOW)); + } + + let has_layers = document.network_interface.selected_nodes().has_selected_nodes(); + let draw_pivot = tool_data.pivot_gizmo.state.is_pivot() && overlay_context.visibility_settings.pivot() && has_layers; + tool_data.pivot_gizmo.pivot.recalculate_pivot(document); + let pivot = draw_pivot.then_some(tool_data.pivot_gizmo.pivot.pivot).flatten(); + if let Some(pivot) = pivot { + let offset = tool_data + .pivot_gizmo_start + .map(|offset| tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - offset).unwrap_or_default()) + .unwrap_or_default(); + let shift = tool_data.pivot_gizmo_shift.unwrap_or_default(); + overlay_context.pivot(pivot + offset + shift, angle); + } // Update compass rose if overlay_context.visibility_settings.compass_rose() { @@ -837,6 +973,15 @@ impl Fsm for SelectToolFsmState { (SelectionShapeType::Lasso, _) => overlay_context.polygon(polygon, None, fill_color), } } + + if let Self::Dragging { .. } = self { + let quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]); + let document_start = document.metadata().document_to_viewport.inverse().transform_point2(quad.top_left()); + let document_current = document.metadata().document_to_viewport.inverse().transform_point2(quad.bottom_right()); + + overlay_context.translation_box(document_current - document_start, quad, None); + } + self } (_, SelectToolMessage::EditLayer) => { @@ -868,7 +1013,8 @@ impl Fsm for SelectToolFsmState { let intersection_list = document.click_list(input).collect::>(); let intersection = document.find_deepest(&intersection_list); - let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging); + let position = tool_data.pivot_gizmo().position(document); + let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, Some(position)); // If the user is dragging the bounding box bounds, go into ResizingBounds mode. // If the user is dragging the rotate trigger, go into RotatingBounds mode. @@ -883,20 +1029,17 @@ impl Fsm for SelectToolFsmState { let angle = bounds.map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle()); let mouse_position = input.mouse.position; let compass_rose_state = tool_data.compass_rose.compass_rose_state(mouse_position, angle); - let is_over_pivot = tool_data.pivot.is_over(mouse_position); let show_compass = bounds.is_some_and(|quad| quad.all_sides_at_least_width(COMPASS_ROSE_HOVER_RING_DIAMETER) && quad.contains(mouse_position)); let can_grab_compass_rose = compass_rose_state.can_grab() && (show_compass || bounds.is_none()); - let state = if is_over_pivot - // Dragging the pivot - { + let state = if let Some(state) = tool_data.state_from_pivot_gizmo(input.mouse.position) { responses.add(DocumentMessage::StartTransaction); // tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); // tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); - SelectToolFsmState::DraggingPivot + state } // Dragging one (or two, forming a corner) of the transform cage bounding box edges else if resize { @@ -917,12 +1060,14 @@ impl Fsm for SelectToolFsmState { } tool_data.layers_dragging = selected; - tool_data.get_snap_candidates(document, input); let (axis, using_compass) = { let axis_state = compass_rose_state.axis_type().filter(|_| can_grab_compass_rose); (axis_state.unwrap_or_default(), axis_state.is_some()) }; + + tool_data.pivot_gizmo_start = Some(tool_data.drag_current); + SelectToolFsmState::Dragging { axis, using_compass, @@ -941,6 +1086,12 @@ impl Fsm for SelectToolFsmState { let extend = input.keyboard.key(extend_selection); if !extend && !input.keyboard.key(remove_from_selection) { responses.add(DocumentMessage::DeselectAllLayers); + + if !tool_data.pivot_gizmo.pivot.pinned { + let position = tool_data.pivot_gizmo.pivot.last_non_none_reference_point; + responses.add(SelectToolMessage::SetPivot { position }); + } + tool_data.layers_dragging.clear(); } @@ -955,6 +1106,9 @@ impl Fsm for SelectToolFsmState { tool_data.get_snap_candidates(document, input); responses.add(DocumentMessage::StartTransaction); + + tool_data.pivot_gizmo_start = Some(tool_data.drag_current); + SelectToolFsmState::Dragging { axis: Axis::None, using_compass: false, @@ -1098,7 +1252,10 @@ impl Fsm for SelectToolFsmState { (SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove(modifier_keys)) => { let mouse_position = input.mouse.position; let snapped_mouse_position = mouse_position; - tool_data.pivot.set_viewport_position(snapped_mouse_position, document, responses); + + tool_data.pivot_gizmo.pivot.set_viewport_position(snapped_mouse_position); + + responses.add(NodeGraphMessage::RunDocumentGraph); // Auto-panning let messages = [ @@ -1143,7 +1300,7 @@ impl Fsm for SelectToolFsmState { .map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge))); // Dragging the pivot overrules the other operations - if tool_data.pivot.is_over(input.mouse.position) { + if tool_data.state_from_pivot_gizmo(input.mouse.position).is_some() { cursor = MouseCursorIcon::Move; } @@ -1283,20 +1440,32 @@ impl Fsm for SelectToolFsmState { tool_data.snap_manager.cleanup(responses); tool_data.select_single_layer = None; + if let Some(start) = tool_data.pivot_gizmo_start { + let offset = tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - start).unwrap_or_default(); + if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() { + *v += offset; + } + } + tool_data.pivot_gizmo_start = None; + + let pivot_gizmo = tool_data.pivot_gizmo(); + responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); + let selection = tool_data.nested_selection_behavior; SelectToolFsmState::Ready { selection } } ( - SelectToolFsmState::ResizingBounds - | SelectToolFsmState::SkewingBounds { .. } - | SelectToolFsmState::RotatingBounds - | SelectToolFsmState::Dragging { .. } - | SelectToolFsmState::DraggingPivot, + SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds | SelectToolFsmState::DraggingPivot, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter, ) => { let drag_too_small = input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON; let response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction }; + + let pivot_gizmo = tool_data.pivot_gizmo(); + responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); + responses.add(response); + tool_data.axis_align = false; tool_data.snap_manager.cleanup(responses); @@ -1432,8 +1601,48 @@ impl Fsm for SelectToolFsmState { (_, SelectToolMessage::SetPivot { position }) => { responses.add(DocumentMessage::StartTransaction); + tool_data.pivot_gizmo.pivot.last_non_none_reference_point = position; + tool_data.pivot_gizmo.pivot.pinned = false; + let pos: Option = position.into(); - tool_data.pivot.set_normalized_position(pos.unwrap(), document, responses); + + tool_data.pivot_gizmo.pivot.set_normalized_position(pos.unwrap()); + + let pivot_gizmo = tool_data.pivot_gizmo(); + responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); + + responses.add(NodeGraphMessage::RunDocumentGraph); + + self + } + (_, SelectToolMessage::SyncHistory) => { + tool_data.sync_history(document); + + self + } + (_, SelectToolMessage::ShiftSelectedNodes { offset }) => { + let offset = document.metadata().document_to_viewport.transform_vector2(offset); + if tool_data.pivot_gizmo.pivot_disconnected() { + if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() { + *v += offset; + } + + let pivot_gizmo = tool_data.pivot_gizmo(); + responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); + } + + self + } + (_, SelectToolMessage::PivotShift { offset, flush }) => { + if flush { + tool_data.pivot_gizmo.pivot.pivot.as_mut().map(|v| *v += tool_data.pivot_gizmo_shift.take().unwrap_or_default()); + let pivot_gizmo = tool_data.pivot_gizmo(); + responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo }); + return self; + } + if tool_data.pivot_gizmo.pivot_disconnected() { + tool_data.pivot_gizmo_shift = offset; + } self } @@ -1658,6 +1867,7 @@ fn drag_deepest_manipulation(responses: &mut VecDeque, selected: Vec MessageHandler> for SplineTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message else { diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs index e56e53d477..52430f09ca 100644 --- a/editor/src/messages/tool/tool_messages/text_tool.rs +++ b/editor/src/messages/tool/tool_messages/text_tool.rs @@ -9,7 +9,6 @@ use crate::messages::portfolio::document::utility_types::network_interface::Inpu use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::graph_modification_utils::{self, is_layer_fed_by_node_of_name}; -use crate::messages::tool::common_functionality::pivot::Pivot; use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData}; use crate::messages::tool::common_functionality::transformation_cage::*; @@ -21,7 +20,7 @@ use graphene_std::renderer::Quad; use graphene_std::text::{Font, FontCache, TypesettingConfig, lines_clipping, load_font}; use graphene_std::vector::style::Fill; -#[derive(Default)] +#[derive(Default, ExtractField)] pub struct TextTool { fsm_state: TextToolFsmState, tool_data: TextToolData, @@ -171,6 +170,7 @@ impl LayoutHolder for TextTool { } } +#[message_handler_data] impl<'a> MessageHandler> for TextTool { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) { let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message else { @@ -283,7 +283,6 @@ struct TextToolData { // Since the overlays must be drawn without knowledge of the inputs cached_resize_bounds: [DVec2; 2], bounding_box_manager: Option, - pivot: Pivot, snap_candidates: Vec, // TODO: Handle multiple layers in the future layer_dragging: Option, @@ -526,7 +525,6 @@ impl Fsm for TextToolFsmState { } bounding_box_manager.render_overlays(&mut overlay_context, false); - tool_data.pivot.update_pivot(document, &mut overlay_context, None); } } else { tool_data.bounding_box_manager.take(); diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message.rs b/editor/src/messages/tool/transform_layer/transform_layer_message.rs index e68d2702c4..c819260e58 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message.rs @@ -2,6 +2,7 @@ use crate::messages::input_mapper::utility_types::input_keyboard::Key; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::utility_types::transformation::TransformType; use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::pivot::PivotGizmo; use glam::DVec2; #[impl_message(Message, ToolMessage, TransformLayer)] @@ -29,4 +30,5 @@ pub enum TransformLayerMessage { TypeDecimalPoint, TypeDigit { digit: u8 }, TypeNegate, + SetPivotGizmo { pivot_gizmo: PivotGizmo }, } diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index 79c419c688..acc0ed83a2 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -5,6 +5,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye use crate::messages::portfolio::document::utility_types::misc::PTZ; use crate::messages::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, TransformType, Typing}; use crate::messages::prelude::*; +use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType}; use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::tool_messages::tool_prelude::Key; use crate::messages::tool::utility_types::{ToolData, ToolType}; @@ -20,7 +21,7 @@ const TRANSFORM_GRS_OVERLAY_PROVIDER: OverlayProvider = |context| TransformLayer const SLOW_KEY: Key = Key::Shift; const INCREMENTS_KEY: Key = Key::Control; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct TransformLayerMessageHandler { pub transform_operation: TransformOperation, @@ -34,8 +35,11 @@ pub struct TransformLayerMessageHandler { start_mouse: ViewportPosition, original_transforms: OriginalTransforms, + pivot_gizmo: PivotGizmo, pivot: ViewportPosition, + path_bounds: Option<[DVec2; 2]>, + local_pivot: DocumentPosition, local_mouse_start: DocumentPosition, grab_target: DocumentPosition, @@ -61,27 +65,64 @@ impl TransformLayerMessageHandler { } } -fn calculate_pivot(selected_points: &Vec<&ManipulatorPointId>, vector_data: &VectorData, viewspace: DAffine2, get_location: impl Fn(&ManipulatorPointId) -> Option) -> Option<(DVec2, DVec2)> { +fn calculate_pivot( + document: &DocumentMessageHandler, + selected_points: &Vec<&ManipulatorPointId>, + vector_data: &VectorData, + viewspace: DAffine2, + get_location: impl Fn(&ManipulatorPointId) -> Option, + gizmo: &mut PivotGizmo, +) -> (Option<(DVec2, DVec2)>, Option<[DVec2; 2]>) { + let average_position = || { + let mut point_count = 0_usize; + selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::() / point_count as f64 + }; + let bounds = selected_points.iter().filter_map(|p| get_location(p)).fold(None, |acc: Option<[DVec2; 2]>, point| { + if let Some([mut min, mut max]) = acc { + min.x = min.x.min(point.x); + min.y = min.y.min(point.y); + max.x = max.x.max(point.x); + max.y = max.y.max(point.y); + Some([min, max]) + } else { + Some([point, point]) + } + }); + gizmo.pivot.recalculate_pivot_for_layer(document, bounds); + let position = || { + (if !gizmo.state.disabled { + match gizmo.state.gizmo_type { + PivotGizmoType::Average => None, + PivotGizmoType::Active => gizmo.point.and_then(|p| get_location(&p)), + PivotGizmoType::Pivot => gizmo.pivot.pivot, + } + } else { + None + }) + .unwrap_or_else(average_position) + }; let [point] = selected_points.as_slice() else { // Handle the case where there are multiple points - let mut point_count = 0; - let average_position = selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::() / point_count as f64; - - return Some((average_position, average_position)); + let position = position(); + return (Some((position, position)), bounds); }; match point { ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => { // Get the anchor position and transform it to the pivot - let pivot_pos = point.get_anchor_position(vector_data).map(|anchor_position| viewspace.transform_point2(anchor_position))?; - let target = viewspace.transform_point2(point.get_position(vector_data)?); - Some((pivot_pos, target)) + let (Some(pivot_position), Some(position)) = ( + point.get_anchor_position(vector_data).map(|anchor_position| viewspace.transform_point2(anchor_position)), + point.get_position(vector_data), + ) else { + return (None, None); + }; + let target = viewspace.transform_point2(position); + (Some((pivot_position, target)), None) } _ => { // Calculate the average position of all selected points - let mut point_count = 0; - let average_position = selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::() / point_count as f64; - Some((average_position, average_position)) + let position = position(); + (Some((position, position)), bounds) } } } @@ -134,6 +175,26 @@ fn update_colinear_handles(selected_layers: &[LayerNodeIdentifier], document: &D } type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessageHandler, &'a ToolData, &'a mut ShapeState); + +pub fn custom_data() -> MessageData { + MessageData::new( + String::from("TransformData<'a>"), + // TODO: When is resolved and released, + // TODO: use to get + // TODO: the line number instead of hardcoding it to the magic number on the following lines + // TODO: which points to the line of the `type TransformData<'a> = ...` definition above. + // TODO: Also, utilize the line number in the actual output, since it is currently unused. + vec![ + (String::from("&'a DocumentMessageHandler"), 177), + (String::from("&'a InputPreprocessorMessageHandler"), 177), + (String::from("&'a ToolData"), 177), + (String::from("&'a mut ShapeState"), 177), + ], + file!(), + ) +} + +#[message_handler_data(CustomData)] impl MessageHandler> for TransformLayerMessageHandler { fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque, (document, input, tool_data, shape_editor): TransformData) { let using_path_tool = tool_data.active_tool_type == ToolType::Path; @@ -177,18 +238,17 @@ impl MessageHandler> for TransformLayer } if !using_path_tool || !using_shape_tool { - *selected.pivot = selected.mean_average_of_pivots(); + self.pivot_gizmo.recalculate_transform(document); + *selected.pivot = self.pivot_gizmo.position(document); self.local_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot); - self.grab_target = document.metadata().document_to_viewport.inverse().transform_point2(selected.mean_average_of_pivots()); + self.grab_target = self.local_pivot; } // Here vector data from all layers is not considered which can be a problem in pivot calculation else if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) { *selected.original_transforms = OriginalTransforms::default(); let viewspace = document.metadata().transform_to_viewport(selected_layers[0]); - let selected_segments = shape_editor.selected_segments().collect::>(); - let mut affected_points = shape_editor.selected_points().copied().collect::>(); for (segment_id, _, start, end) in vector_data.segment_bezier_iter() { @@ -201,8 +261,16 @@ impl MessageHandler> for TransformLayer let affected_point_refs = affected_points.iter().collect(); let get_location = |point: &&ManipulatorPointId| point.get_position(&vector_data).map(|position| viewspace.transform_point2(position)); - if let Some((new_pivot, grab_target)) = calculate_pivot(&affected_point_refs, &vector_data, viewspace, |point: &ManipulatorPointId| get_location(&point)) { + if let (Some((new_pivot, grab_target)), bounds) = calculate_pivot( + document, + &affected_point_refs, + &vector_data, + viewspace, + |point: &ManipulatorPointId| get_location(&point), + &mut self.pivot_gizmo, + ) { *selected.pivot = new_pivot; + self.path_bounds = bounds; self.local_pivot = document_to_viewport.inverse().transform_point2(*selected.pivot); self.grab_target = document_to_viewport.inverse().transform_point2(grab_target); @@ -228,116 +296,93 @@ impl MessageHandler> for TransformLayer return; } - for layer in document.metadata().all_layers() { - if !document.network_interface.is_artboard(&layer.to_node(), &[]) { - continue; - }; + let viewport_box = input.viewport_bounds.size(); + let axis_constraint = self.transform_operation.axis_constraint(); - let viewport_box = input.viewport_bounds.size(); - let axis_constraint = self.transform_operation.axis_constraint(); + let format_rounded = |value: f64, precision: usize| { + if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() { + format!("{:.*}", precision, value).trim_end_matches('0').trim_end_matches('.').to_string() + } else { + self.typing.string.clone() + } + }; - let format_rounded = |value: f64, precision: usize| { - if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() { - format!("{:.*}", precision, value).trim_end_matches('0').trim_end_matches('.').to_string() + // TODO: Ensure removing this and adding this doesn't change the position of layers under PTZ ops + // responses.add(TransformLayerMessage::PointerMove { + // slow_key: SLOW_KEY, + // increments_key: INCREMENTS_KEY, + // }); + + match self.transform_operation { + TransformOperation::None => (), + TransformOperation::Grabbing(translation) => { + let translation = translation.to_dvec(self.initial_transform, self.increments); + let viewport_translate = document_to_viewport.transform_vector2(translation); + let pivot = document_to_viewport.transform_point2(self.grab_target); + let quad = Quad::from_box([pivot, pivot + viewport_translate]); + + responses.add(SelectToolMessage::PivotShift { + offset: Some(viewport_translate), + flush: false, + }); + + let typed_string = (!self.typing.digits.is_empty() && self.transform_operation.can_begin_typing()).then(|| self.typing.string.clone()); + overlay_context.translation_box(translation, quad, typed_string); + } + TransformOperation::Scaling(scale) => { + let scale = scale.to_f64(self.increments); + let text = format!("{}x", format_rounded(scale, 3)); + let pivot = document_to_viewport.transform_point2(self.local_pivot); + let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start); + let local_edge = start_mouse - pivot; + let local_edge = project_edge_to_quad(local_edge, &self.layer_bounding_box, self.local, axis_constraint); + let boundary_point = pivot + local_edge * scale.min(1.); + let end_point = pivot + local_edge * scale.max(1.); + + if scale > 0. { + overlay_context.dashed_line(pivot, boundary_point, None, None, Some(2.), Some(2.), Some(0.5)); + } + overlay_context.line(boundary_point, end_point, None, None); + + let transform = DAffine2::from_translation(boundary_point.midpoint(pivot) + local_edge.perp().normalize_or(DVec2::X) * local_edge.element_product().signum() * 24.); + overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]); + } + TransformOperation::Rotating(rotation) => { + let angle = rotation.to_f64(self.increments); + let pivot = document_to_viewport.transform_point2(self.local_pivot); + let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start); + let offset_angle = if self.grs_pen_handle { + self.handle - self.last_point + } else if using_path_tool { + start_mouse - pivot } else { - self.typing.string.clone() - } - }; - - // TODO: Ensure removing this and adding this doesn't change the position of layers under PTZ ops - // responses.add(TransformLayerMessage::PointerMove { - // slow_key: SLOW_KEY, - // increments_key: INCREMENTS_KEY, - // }); - - match self.transform_operation { - TransformOperation::None => (), - TransformOperation::Grabbing(translation) => { - let translation = translation.to_dvec(self.initial_transform, self.increments); - let viewport_translate = document_to_viewport.transform_vector2(translation); - let pivot = document_to_viewport.transform_point2(self.grab_target); - let quad = Quad::from_box([pivot, pivot + viewport_translate]).0; - let e1 = (self.layer_bounding_box.0[1] - self.layer_bounding_box.0[0]).normalize_or(DVec2::X); - - if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. { - let end = if self.local { (quad[1] - quad[0]).rotate(e1) + quad[0] } else { quad[1] }; - overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5)); - - let x_transform = DAffine2::from_translation((quad[0] + end) / 2.); - overlay_context.text(&format_rounded(translation.x, 3), COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]); - } - - if matches!(axis_constraint, Axis::Both | Axis::Y) && translation.y != 0. { - let end = if self.local { (quad[3] - quad[0]).rotate(e1) + quad[0] } else { quad[3] }; - overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5)); - let x_parameter = viewport_translate.x.clamp(-1., 1.); - let y_transform = DAffine2::from_translation((quad[0] + end) / 2. + x_parameter * DVec2::X * 0.); - let pivot_selection = if x_parameter >= -1e-3 { Pivot::Start } else { Pivot::End }; - if axis_constraint != Axis::Both || self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() { - overlay_context.text(&format_rounded(translation.y, 2), COLOR_OVERLAY_BLUE, None, y_transform, 3., [pivot_selection, Pivot::Middle]); - } - } - - if matches!(axis_constraint, Axis::Both) && translation.x != 0. && translation.y != 0. { - overlay_context.line(quad[1], quad[2], None, None); - overlay_context.line(quad[3], quad[2], None, None); - } - } - TransformOperation::Scaling(scale) => { - let scale = scale.to_f64(self.increments); - let text = format!("{}x", format_rounded(scale, 3)); - let pivot = document_to_viewport.transform_point2(self.local_pivot); - let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start); - let local_edge = start_mouse - pivot; - let local_edge = project_edge_to_quad(local_edge, &self.layer_bounding_box, self.local, axis_constraint); - let boundary_point = pivot + local_edge * scale.min(1.); - let end_point = pivot + local_edge * scale.max(1.); - - if scale > 0. { - overlay_context.dashed_line(pivot, boundary_point, None, None, Some(2.), Some(2.), Some(0.5)); - } - overlay_context.line(boundary_point, end_point, None, None); - - let transform = DAffine2::from_translation(boundary_point.midpoint(pivot) + local_edge.perp().normalize_or(DVec2::X) * local_edge.element_product().signum() * 24.); - overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]); - } - TransformOperation::Rotating(rotation) => { - let angle = rotation.to_f64(self.increments); - let pivot = document_to_viewport.transform_point2(self.local_pivot); - let start_mouse = document_to_viewport.transform_point2(self.local_mouse_start); - let offset_angle = if self.grs_pen_handle { - self.handle - self.last_point - } else if using_path_tool { - start_mouse - pivot - } else { - self.layer_bounding_box.top_right() - self.layer_bounding_box.top_right() - }; - let tilt_offset = document.document_ptz.unmodified_tilt(); - let offset_angle = offset_angle.to_angle() + tilt_offset; - let width = viewport_box.max_element(); - let radius = start_mouse.distance(pivot); - let arc_radius = ANGLE_MEASURE_RADIUS_FACTOR * width; - let radius = radius.clamp(ARC_MEASURE_RADIUS_FACTOR_RANGE.0 * width, ARC_MEASURE_RADIUS_FACTOR_RANGE.1 * width); - let angle_in_degrees = angle.to_degrees(); - let display_angle = if angle_in_degrees.is_sign_positive() { - angle_in_degrees - (angle_in_degrees / 360.).floor() * 360. - } else if angle_in_degrees.is_sign_negative() { - angle_in_degrees - ((angle_in_degrees / 360.).floor() + 1.) * 360. - } else { - angle_in_degrees - }; - let text = format!("{}°", format_rounded(display_angle, 2)); - let text_texture_width = overlay_context.get_width(&text) / 2.; - let text_texture_height = 12.; - let text_angle_on_unit_circle = DVec2::from_angle((angle % TAU) / 2. + offset_angle); - let text_texture_position = DVec2::new( - (arc_radius + 4. + text_texture_width) * text_angle_on_unit_circle.x, - (arc_radius + text_texture_height) * text_angle_on_unit_circle.y, - ); - let transform = DAffine2::from_translation(text_texture_position + pivot); - overlay_context.draw_angle(pivot, radius, arc_radius, offset_angle, angle); - overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]); - } + self.layer_bounding_box.top_right() - self.layer_bounding_box.top_right() + }; + let tilt_offset = document.document_ptz.unmodified_tilt(); + let offset_angle = offset_angle.to_angle() + tilt_offset; + let width = viewport_box.max_element(); + let radius = start_mouse.distance(pivot); + let arc_radius = ANGLE_MEASURE_RADIUS_FACTOR * width; + let radius = radius.clamp(ARC_MEASURE_RADIUS_FACTOR_RANGE.0 * width, ARC_MEASURE_RADIUS_FACTOR_RANGE.1 * width); + let angle_in_degrees = angle.to_degrees(); + let display_angle = if angle_in_degrees.is_sign_positive() { + angle_in_degrees - (angle_in_degrees / 360.).floor() * 360. + } else if angle_in_degrees.is_sign_negative() { + angle_in_degrees - ((angle_in_degrees / 360.).floor() + 1.) * 360. + } else { + angle_in_degrees + }; + let text = format!("{}°", format_rounded(display_angle, 2)); + let text_texture_width = overlay_context.get_width(&text) / 2.; + let text_texture_height = 12.; + let text_angle_on_unit_circle = DVec2::from_angle((angle % TAU) / 2. + offset_angle); + let text_texture_position = DVec2::new( + (arc_radius + 4. + text_texture_width) * text_angle_on_unit_circle.x, + (arc_radius + text_texture_height) * text_angle_on_unit_circle.y, + ); + let transform = DAffine2::from_translation(text_texture_position + pivot); + overlay_context.draw_angle(pivot, radius, arc_radius, offset_angle, angle); + overlay_context.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]); } } } @@ -364,6 +409,8 @@ impl MessageHandler> for TransformLayer responses.add(NodeGraphMessage::RunDocumentGraph); } + responses.add(SelectToolMessage::PivotShift { offset: None, flush: true }); + if final_transform { responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER)); } @@ -487,6 +534,7 @@ impl MessageHandler> for TransformLayer responses.add(ToolMessage::UpdateHints); } + responses.add(SelectToolMessage::PivotShift { offset: None, flush: false }); responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER)); } TransformLayerMessage::ConstrainX => { @@ -694,6 +742,9 @@ impl MessageHandler> for TransformLayer self.initial_transform, ) } + TransformLayerMessage::SetPivotGizmo { pivot_gizmo } => { + self.pivot_gizmo = pivot_gizmo; + } } } diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs index fbbec768e4..fc06385f43 100644 --- a/editor/src/messages/tool/utility_types.rs +++ b/editor/src/messages/tool/utility_types.rs @@ -18,6 +18,7 @@ use graphene_std::text::FontCache; use std::borrow::Cow; use std::fmt::{self, Debug}; +#[derive(ExtractField)] pub struct ToolActionHandlerData<'a> { pub document: &'a mut DocumentMessageHandler, pub document_id: DocumentId, diff --git a/editor/src/messages/workspace/workspace_message_handler.rs b/editor/src/messages/workspace/workspace_message_handler.rs index 397e7cf00b..47c81aab0e 100644 --- a/editor/src/messages/workspace/workspace_message_handler.rs +++ b/editor/src/messages/workspace/workspace_message_handler.rs @@ -1,10 +1,11 @@ use crate::messages::prelude::*; -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, ExtractField)] pub struct WorkspaceMessageHandler { node_graph_visible: bool, } +#[message_handler_data] impl MessageHandler for WorkspaceMessageHandler { fn process_message(&mut self, message: WorkspaceMessage, _responses: &mut VecDeque, _data: ()) { match message { diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index dbb87c07f0..ba2f52d7b4 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -413,6 +413,7 @@ mod test { use super::*; use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; use crate::test_utils::test_prelude::{self, NodeGraphLayer}; + use graph_craft::ProtoNodeIdentifier; use graph_craft::document::NodeNetwork; use graphene_std::Context; use graphene_std::NodeInputDecleration; @@ -422,7 +423,7 @@ mod test { /// Stores all of the monitor nodes that have been attached to a graph #[derive(Default)] pub struct Instrumented { - protonodes_by_name: HashMap>>>, + protonodes_by_name: HashMap>>>, protonodes_by_path: HashMap, Vec>>, } @@ -449,7 +450,7 @@ mod test { } if let DocumentNodeImplementation::ProtoNode(identifier) = &mut node.implementation { path.push(*id); - self.protonodes_by_name.entry(identifier.name.to_string()).or_default().push(monitor_node_ids.clone()); + self.protonodes_by_name.entry(identifier.clone()).or_default().push(monitor_node_ids.clone()); self.protonodes_by_path.insert(path.clone(), monitor_node_ids); path.pop(); } @@ -457,7 +458,7 @@ mod test { for (input, monitor_id) in monitor_nodes { let monitor_node = DocumentNode { inputs: vec![input], - implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), + implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER), manual_composition: Some(graph_craft::generic!(T)), skip_deduplication: true, ..Default::default() @@ -495,7 +496,7 @@ mod test { Input::Result: Send + Sync + Clone + 'static, { self.protonodes_by_name - .get(Input::identifier()) + .get(&Input::identifier()) .map_or([].as_slice(), |x| x.as_slice()) .iter() .filter_map(|inputs| inputs.get(Input::INDEX)) diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs index a93f73546c..227999143c 100644 --- a/editor/src/node_graph_executor/runtime.rs +++ b/editor/src/node_graph_executor/runtime.rs @@ -1,12 +1,12 @@ use super::*; use crate::messages::frontend::utility_types::{ExportBounds, FileType}; use glam::{DAffine2, DVec2}; -use graph_craft::concrete; use graph_craft::document::value::TaggedValue; use graph_craft::document::{NodeId, NodeNetwork}; use graph_craft::graphene_compiler::Compiler; use graph_craft::proto::GraphErrors; use graph_craft::wasm_application_io::EditorPreferences; +use graph_craft::{ProtoNodeIdentifier, concrete}; use graphene_std::Context; use graphene_std::application_io::{NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig}; use graphene_std::instances::Instance; @@ -46,7 +46,7 @@ pub struct NodeRuntime { inspect_state: Option, /// Mapping of the fully-qualified node paths to their preprocessor substitutions. - substitutions: HashMap, + substitutions: HashMap, // TODO: Remove, it doesn't need to be persisted anymore /// The current renders of the thumbnails for layer nodes. @@ -435,7 +435,7 @@ impl InspectState { let monitor_node = DocumentNode { inputs: vec![NodeInput::node(inspect_node, 0)], // Connect to the primary output of the inspect node - implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), + implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER), manual_composition: Some(graph_craft::generic!(T)), skip_deduplication: true, ..Default::default() diff --git a/editor/src/test_utils.rs b/editor/src/test_utils.rs index a8c9691c86..04ef698da0 100644 --- a/editor/src/test_utils.rs +++ b/editor/src/test_utils.rs @@ -172,9 +172,10 @@ impl EditorTestUtils { pub fn get_node<'a, T: InputAccessor<'a, DocumentNode>>(&'a self) -> impl Iterator + 'a { self.active_document() .network_interface - .iter_recursive() - .inspect(|node| println!("{:#?}", node.1.implementation)) - .filter_map(move |(_, document)| T::new_with_source(document)) + .document_network() + .recursive_nodes() + .inspect(|(_, node, _)| println!("{:#?}", node.implementation)) + .filter_map(move |(_, document, _)| T::new_with_source(document)) } pub async fn move_mouse(&mut self, x: f64, y: f64, modifier_keys: ModifierKeys, mouse_keys: MouseKeys) { @@ -300,7 +301,7 @@ pub trait FrontendMessageTestUtils { impl FrontendMessageTestUtils for FrontendMessage { fn check_node_graph_error(&self) { - let FrontendMessage::UpdateNodeGraph { nodes, .. } = self else { return }; + let FrontendMessage::UpdateNodeGraphNodes { nodes, .. } = self else { return }; for node in nodes { if let Some(error) = &node.errors { diff --git a/editor/src/utility_traits.rs b/editor/src/utility_traits.rs index 711a8cc34b..850d730b13 100644 --- a/editor/src/utility_traits.rs +++ b/editor/src/utility_traits.rs @@ -45,3 +45,19 @@ pub trait TransitiveChild: Into + Into { pub trait Hint { fn hints(&self) -> HashMap; } + +pub trait HierarchicalTree { + fn build_message_tree() -> DebugMessageTree; + + fn message_handler_data_str() -> MessageData { + MessageData::new(String::new(), Vec::new(), "") + } + + fn message_handler_str() -> MessageData { + MessageData::new(String::new(), Vec::new(), "") + } + + fn path() -> &'static str { + "" + } +} diff --git a/editor/src/utility_types.rs b/editor/src/utility_types.rs new file mode 100644 index 0000000000..6b5dc6de6b --- /dev/null +++ b/editor/src/utility_types.rs @@ -0,0 +1,99 @@ +#[derive(Debug)] +pub struct MessageData { + name: String, + fields: Vec<(String, usize)>, + path: &'static str, +} + +impl MessageData { + pub fn new(name: String, fields: Vec<(String, usize)>, path: &'static str) -> MessageData { + MessageData { name, fields, path } + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn fields(&self) -> &Vec<(String, usize)> { + &self.fields + } + + pub fn path(&self) -> &'static str { + self.path + } +} + +#[derive(Debug)] +pub struct DebugMessageTree { + name: String, + variants: Option>, + message_handler: Option, + message_handler_data: Option, + path: &'static str, +} + +impl DebugMessageTree { + pub fn new(name: &str) -> DebugMessageTree { + DebugMessageTree { + name: name.to_string(), + variants: None, + message_handler: None, + message_handler_data: None, + path: "", + } + } + + pub fn set_path(&mut self, path: &'static str) { + self.path = path; + } + + pub fn add_variant(&mut self, variant: DebugMessageTree) { + if let Some(variants) = &mut self.variants { + variants.push(variant); + } else { + self.variants = Some(vec![variant]); + } + } + + pub fn add_message_handler_data_field(&mut self, message_handler_data: MessageData) { + self.message_handler_data = Some(message_handler_data); + } + + pub fn add_message_handler_field(&mut self, message_handler: MessageData) { + self.message_handler = Some(message_handler); + } + + pub fn name(&self) -> &str { + &self.name + } + + pub fn path(&self) -> &'static str { + self.path + } + + pub fn variants(&self) -> Option<&Vec> { + self.variants.as_ref() + } + + pub fn message_handler_data_fields(&self) -> Option<&MessageData> { + self.message_handler_data.as_ref() + } + + pub fn message_handler_fields(&self) -> Option<&MessageData> { + self.message_handler.as_ref() + } + + pub fn has_message_handler_data_fields(&self) -> bool { + match self.message_handler_data_fields() { + Some(_) => true, + None => false, + } + } + + pub fn has_message_handler_fields(&self) -> bool { + match self.message_handler_fields() { + Some(_) => true, + None => false, + } + } +} diff --git a/frontend/README.md b/frontend/README.md index 8e451aec34..657f90ac42 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -2,7 +2,7 @@ The Graphite frontend is a web app that provides the presentation for the editor. It displays the GUI based on state from the backend and provides users with interactive widgets that send updates to the backend, which is the source of truth for state information. The frontend is built out of reactive components using the [Svelte](https://svelte.dev/) framework. The backend is written in Rust and compiled to WebAssembly (WASM) to be run in the browser alongside the JS code. -For lack of other options, the frontend is currently written as a web app. Maintaining web compatibility will always be a requirement, but the long-term plan is to port this code to a Rust-based native GUI framework, either written by the Rust community or created by our project if necessary. As a medium-term compromise, we may wrap the web-based frontend in a desktop webview windowing solution like Electron (probably not) or [Tauri](https://tauri.studio/) (probably). +For lack of other options, the frontend is currently written as a web app. Maintaining web compatibility will always be a requirement, but the long-term plan is to port this code to a Rust-based native GUI framework, either written by the Rust community or created by our project if necessary. As a medium-term compromise, we may wrap the web-based frontend in a desktop webview windowing solution like Electron (probably not) or [Tauri](https://tauri.app/) (probably). ## Bundled assets: `assets/` diff --git a/frontend/src-tauri/src/main.rs b/frontend/src-tauri/src/main.rs index edc544daef..64c78a79be 100644 --- a/frontend/src-tauri/src/main.rs +++ b/frontend/src-tauri/src/main.rs @@ -3,10 +3,6 @@ use axum::routing::get; use axum::Router; use fern::colors::{Color, ColoredLevelConfig}; -use graphite_editor::application::Editor; -use graphite_editor::messages::prelude::*; -use graphite_editor::node_graph_executor::GraphRuntimeRequest; -use graphite_editor::node_graph_executor::NODE_RUNTIME; use graphite_editor::node_graph_executor::*; use std::sync::Mutex; diff --git a/frontend/src/components/views/Graph.svelte b/frontend/src/components/views/Graph.svelte index 59575d3141..eaedc3a6df 100644 --- a/frontend/src/components/views/Graph.svelte +++ b/frontend/src/components/views/Graph.svelte @@ -1,11 +1,11 @@ -
+
diff --git a/frontend/src/messages.ts b/frontend/src/messages.ts index 399beb50c5..28b777f806 100644 --- a/frontend/src/messages.ts +++ b/frontend/src/messages.ts @@ -96,16 +96,21 @@ export class UpdateLayerWidths extends JsMessage { readonly hasLeftInputWire!: Map; } -export class UpdateNodeGraph extends JsMessage { +export class UpdateNodeGraphNodes extends JsMessage { @Type(() => FrontendNode) readonly nodes!: FrontendNode[]; - - @Type(() => FrontendNodeWire) - readonly wires!: FrontendNodeWire[]; - - readonly wiresDirectNotGridAligned!: boolean; } +export class UpdateVisibleNodes extends JsMessage { + readonly nodes!: bigint[]; +} + +export class UpdateNodeGraphWires extends JsMessage { + readonly wires!: WireUpdate[]; +} + +export class ClearAllNodeGraphWires extends JsMessage {} + export class UpdateNodeGraphTransform extends JsMessage { readonly transform!: NodeGraphTransform; } @@ -219,7 +224,7 @@ export class FrontendGraphInput { readonly description!: string; - readonly resolvedType!: string | undefined; + readonly resolvedType!: string; readonly validTypes!: string[]; @@ -252,7 +257,7 @@ export class FrontendGraphOutput { readonly description!: string; - readonly resolvedType!: string | undefined; + readonly resolvedType!: string; @CreateInputConnectorArray connectedTo!: Node[]; @@ -297,44 +302,6 @@ export class FrontendNode { readonly uiOnly!: boolean; } -const CreateOutputConnector = Transform(({ obj }) => { - if (obj.wireStart.export !== undefined) { - return { index: obj.wireStart.export }; - } else if (obj.wireStart.import !== undefined) { - return { index: obj.wireStart.import }; - } else { - if (obj.wireStart.node.inputIndex !== undefined) { - return { nodeId: obj.wireStart.node.nodeId, index: obj.wireStart.node.inputIndex }; - } else { - return { nodeId: obj.wireStart.node.nodeId, index: obj.wireStart.node.outputIndex }; - } - } -}); - -const CreateInputConnector = Transform(({ obj }) => { - if (obj.wireEnd.export !== undefined) { - return { index: obj.wireEnd.export }; - } else if (obj.wireEnd.import !== undefined) { - return { index: obj.wireEnd.import }; - } else { - if (obj.wireEnd.node.inputIndex !== undefined) { - return { nodeId: obj.wireEnd.node.nodeId, index: obj.wireEnd.node.inputIndex }; - } else { - return { nodeId: obj.wireEnd.node.nodeId, index: obj.wireEnd.node.outputIndex }; - } - } -}); - -export class FrontendNodeWire { - @CreateOutputConnector - readonly wireStart!: Node; - - @CreateInputConnector - readonly wireEnd!: Node; - - readonly dashed!: boolean; -} - export class FrontendNodeType { readonly name!: string; @@ -356,6 +323,12 @@ export class WirePath { readonly dashed!: boolean; } +export class WireUpdate { + readonly id!: bigint; + readonly inputIndex!: number; + readonly wirePathUpdate!: WirePath | undefined; +} + export class IndexedDbDocumentDetails extends DocumentDetails { @Transform(({ value }: { value: bigint }) => value.toString()) id!: string; @@ -1367,6 +1340,9 @@ export class ReferencePointInput extends WidgetProps { value!: ReferencePoint; disabled!: boolean; + + @Transform(({ value }: { value: string }) => value || undefined) + tooltip!: string | undefined; } // WIDGET @@ -1645,6 +1621,7 @@ type JSMessageFactory = (data: any, wasm: WebAssembly.Memory, handle: EditorHand type MessageMaker = typeof JsMessage | JSMessageFactory; export const messageMakers: Record = { + ClearAllNodeGraphWires, DisplayDialog, DisplayDialogDismiss, DisplayDialogPanic, @@ -1700,10 +1677,12 @@ export const messageMakers: Record = { UpdateLayerWidths, UpdateMenuBarLayout, UpdateMouseCursor, - UpdateNodeGraph, + UpdateNodeGraphNodes, + UpdateVisibleNodes, + UpdateNodeGraphWires, + UpdateNodeGraphTransform, UpdateNodeGraphControlBarLayout, UpdateNodeGraphSelection, - UpdateNodeGraphTransform, UpdateNodeThumbnail, UpdateOpenDocumentsList, UpdatePropertyPanelSectionsLayout, diff --git a/frontend/src/state-providers/node-graph.ts b/frontend/src/state-providers/node-graph.ts index 05b3101b50..9884659340 100644 --- a/frontend/src/state-providers/node-graph.ts +++ b/frontend/src/state-providers/node-graph.ts @@ -7,9 +7,9 @@ import { type FrontendClickTargets, type ContextMenuInformation, type FrontendNode, - type FrontendNodeWire as FrontendNodeWire, type FrontendNodeType, type WirePath, + ClearAllNodeGraphWires, SendUIMetadata, UpdateBox, UpdateClickTargets, @@ -19,7 +19,9 @@ import { UpdateExportReorderIndex, UpdateImportsExports, UpdateLayerWidths, - UpdateNodeGraph, + UpdateNodeGraphNodes, + UpdateVisibleNodes, + UpdateNodeGraphWires, UpdateNodeGraphSelection, UpdateNodeGraphTransform, UpdateNodeThumbnail, @@ -40,8 +42,9 @@ export function createNodeGraphState(editor: Editor) { addImport: undefined as { x: number; y: number } | undefined, addExport: undefined as { x: number; y: number } | undefined, nodes: new Map(), - wires: [] as FrontendNodeWire[], - wiresDirectNotGridAligned: false, + visibleNodes: new Set(), + /// The index is the exposed input index. The exports have a first key value of u32::MAX. + wires: new Map>(), wirePathInProgress: undefined as WirePath | undefined, nodeDescriptions: new Map(), nodeTypes: [] as FrontendNodeType[], @@ -114,15 +117,42 @@ export function createNodeGraphState(editor: Editor) { return state; }); }); - // TODO: Add a way to only update the nodes that have changed - editor.subscriptions.subscribeJsMessage(UpdateNodeGraph, (updateNodeGraph) => { + editor.subscriptions.subscribeJsMessage(UpdateNodeGraphNodes, (updateNodeGraphNodes) => { update((state) => { state.nodes.clear(); - updateNodeGraph.nodes.forEach((node) => { + updateNodeGraphNodes.nodes.forEach((node) => { state.nodes.set(node.id, node); }); - state.wires = updateNodeGraph.wires; - state.wiresDirectNotGridAligned = updateNodeGraph.wiresDirectNotGridAligned; + return state; + }); + }); + editor.subscriptions.subscribeJsMessage(UpdateVisibleNodes, (updateVisibleNodes) => { + update((state) => { + state.visibleNodes = new Set(updateVisibleNodes.nodes); + return state; + }); + }); + editor.subscriptions.subscribeJsMessage(UpdateNodeGraphWires, (updateNodeWires) => { + update((state) => { + updateNodeWires.wires.forEach((wireUpdate) => { + let inputMap = state.wires.get(wireUpdate.id); + // If it doesn't exist, create it and set it in the outer map + if (!inputMap) { + inputMap = new Map(); + state.wires.set(wireUpdate.id, inputMap); + } + if (wireUpdate.wirePathUpdate !== undefined) { + inputMap.set(wireUpdate.inputIndex, wireUpdate.wirePathUpdate); + } else { + inputMap.delete(wireUpdate.inputIndex); + } + }); + return state; + }); + }); + editor.subscriptions.subscribeJsMessage(ClearAllNodeGraphWires, (_) => { + update((state) => { + state.wires.clear(); return state; }); }); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 13c8cb2d63..04b0fc6b73 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -21,6 +21,7 @@ const ALLOWED_LICENSES = [ "BSD-3-Clause", "BSL-1.0", "CC0-1.0", + "CDLA-Permissive-2.0", "ISC", "MIT-0", "MIT", @@ -29,6 +30,7 @@ const ALLOWED_LICENSES = [ "Unicode-3.0", "Unicode-DFS-2016", "Zlib", + "NCSA", ]; // https://vitejs.dev/config/ diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 1b7d3a0b25..20f2f1ae96 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -604,6 +604,7 @@ impl EditorHandle { node_id: Some(id), node_type, xy: Some((x / 24, y / 24)), + add_transaction: true, }; self.dispatch(message); } diff --git a/node-graph/gbrush/src/brush.rs b/node-graph/gbrush/src/brush.rs index b2782adc8a..3a085c0abe 100644 --- a/node-graph/gbrush/src/brush.rs +++ b/node-graph/gbrush/src/brush.rs @@ -403,7 +403,7 @@ mod test { blend_mode: BlendMode::Normal, }, }], - BrushCache::new_proto(), + BrushCache::default(), ) .await; assert_eq!(image.instance_ref_iter().next().unwrap().instance.width, 20); diff --git a/node-graph/gbrush/src/brush_cache.rs b/node-graph/gbrush/src/brush_cache.rs index 853a13ca34..c5495534c1 100644 --- a/node-graph/gbrush/src/brush_cache.rs +++ b/node-graph/gbrush/src/brush_cache.rs @@ -6,11 +6,16 @@ use graphene_core::raster_types::CPU; use graphene_core::raster_types::Raster; use std::collections::HashMap; use std::hash::Hash; -use std::sync::Arc; -use std::sync::Mutex; +use std::hash::Hasher; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::sync::{Arc, Mutex}; -#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)] +// TODO: This is a temporary hack, be sure to not reuse this when the brush is being rewritten. +static NEXT_BRUSH_CACHE_IMPL_ID: AtomicU64 = AtomicU64::new(0); + +#[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)] struct BrushCacheImpl { + unique_id: u64, // The full previous input that was cached. prev_input: Vec, @@ -90,9 +95,29 @@ impl BrushCacheImpl { } } +impl Default for BrushCacheImpl { + fn default() -> Self { + Self { + unique_id: NEXT_BRUSH_CACHE_IMPL_ID.fetch_add(1, Ordering::SeqCst), + prev_input: Vec::new(), + background: Default::default(), + blended_image: Default::default(), + last_stroke_texture: Default::default(), + brush_texture_cache: HashMap::new(), + } + } +} + +impl PartialEq for BrushCacheImpl { + fn eq(&self, other: &Self) -> bool { + self.unique_id == other.unique_id + } +} + impl Hash for BrushCacheImpl { - // Zero hash. - fn hash(&self, _state: &mut H) {} + fn hash(&self, state: &mut H) { + self.unique_id.hash(state); + } } #[derive(Clone, Debug, Default)] @@ -103,46 +128,26 @@ pub struct BrushPlan { pub first_stroke_point_skip: usize, } -#[derive(Debug, DynAny, serde::Serialize, serde::Deserialize)] -pub struct BrushCache { - inner: Arc>, - proto: bool, -} - -impl Default for BrushCache { - fn default() -> Self { - Self::new_proto() - } -} +#[derive(Debug, Default, DynAny, serde::Serialize, serde::Deserialize)] +pub struct BrushCache(Arc>); // A bit of a cursed implementation to work around the current node system. // The original object is a 'prototype' that when cloned gives you a independent // new object. Any further clones however are all the same underlying cache object. impl Clone for BrushCache { fn clone(&self) -> Self { - if self.proto { - let inner_val = self.inner.lock().unwrap(); - Self { - inner: Arc::new(Mutex::new(inner_val.clone())), - proto: false, - } - } else { - Self { - inner: Arc::clone(&self.inner), - proto: false, - } - } + Self(Arc::new(Mutex::new(self.0.lock().unwrap().clone()))) } } impl PartialEq for BrushCache { fn eq(&self, other: &Self) -> bool { - if Arc::ptr_eq(&self.inner, &other.inner) { + if Arc::ptr_eq(&self.0, &other.0) { return true; } - let s = self.inner.lock().unwrap(); - let o = other.inner.lock().unwrap(); + let s = self.0.lock().unwrap(); + let o = other.0.lock().unwrap(); *s == *o } @@ -150,35 +155,28 @@ impl PartialEq for BrushCache { impl Hash for BrushCache { fn hash(&self, state: &mut H) { - self.inner.lock().unwrap().hash(state); + self.0.lock().unwrap().hash(state); } } impl BrushCache { - pub fn new_proto() -> Self { - Self { - inner: Default::default(), - proto: true, - } - } - pub fn compute_brush_plan(&self, background: Instance>, input: &[BrushStroke]) -> BrushPlan { - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.0.lock().unwrap(); inner.compute_brush_plan(background, input) } pub fn cache_results(&self, input: Vec, blended_image: Instance>, last_stroke_texture: Instance>) { - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.0.lock().unwrap(); inner.cache_results(input, blended_image, last_stroke_texture) } pub fn get_cached_brush(&self, style: &BrushStyle) -> Option> { - let inner = self.inner.lock().unwrap(); + let inner = self.0.lock().unwrap(); inner.brush_texture_cache.get(style).cloned() } pub fn store_brush(&self, style: BrushStyle, brush: Raster) { - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.0.lock().unwrap(); inner.brush_texture_cache.insert(style, brush); } } diff --git a/node-graph/gcore/src/context.rs b/node-graph/gcore/src/context.rs index 3adb839b0b..4e8854c901 100644 --- a/node-graph/gcore/src/context.rs +++ b/node-graph/gcore/src/context.rs @@ -356,7 +356,7 @@ pub struct ContextImpl<'a> { } impl<'a> ContextImpl<'a> { - pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl (Borrow<[DynRef<'f>]>)>) -> ContextImpl<'f> + pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl Borrow<[DynRef<'f>]>>) -> ContextImpl<'f> where 'a: 'f, { diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs index 973b2f4d24..1333ec7433 100644 --- a/node-graph/gcore/src/lib.rs +++ b/node-graph/gcore/src/lib.rs @@ -12,7 +12,7 @@ pub mod debug; pub mod extract_xy; pub mod generic; pub mod gradient; -mod graphic_element; +pub mod graphic_element; pub mod instances; pub mod logic; pub mod math; @@ -35,7 +35,7 @@ pub use blending::*; pub use context::*; pub use ctor; pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync}; -pub use graphic_element::*; +pub use graphic_element::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable}; pub use memo::MemoHash; pub use num_traits; pub use raster::Color; @@ -161,7 +161,7 @@ where pub trait NodeInputDecleration { const INDEX: usize; - fn identifier() -> &'static str; + fn identifier() -> ProtoNodeIdentifier; type Result; } diff --git a/node-graph/gcore/src/logic.rs b/node-graph/gcore/src/logic.rs index fc122beb8e..8ccd9c5069 100644 --- a/node-graph/gcore/src/logic.rs +++ b/node-graph/gcore/src/logic.rs @@ -3,6 +3,7 @@ use crate::Color; use crate::GraphicElement; use crate::GraphicGroupTable; use crate::gradient::GradientStops; +use crate::graphene_core::registry::types::TextArea; use crate::raster_types::{CPU, GPU, RasterDataTable}; use crate::vector::VectorDataTable; use crate::{Context, Ctx}; @@ -14,12 +15,12 @@ fn to_string(_: impl Ctx, #[implementations(String, bool, f6 } #[node_macro::node(category("Text"))] -fn string_concatenate(_: impl Ctx, #[implementations(String)] first: String, #[implementations(String)] second: String) -> String { +fn string_concatenate(_: impl Ctx, #[implementations(String)] first: String, second: TextArea) -> String { first.clone() + &second } #[node_macro::node(category("Text"))] -fn string_replace(_: impl Ctx, #[implementations(String)] string: String, from: String, to: String) -> String { +fn string_replace(_: impl Ctx, #[implementations(String)] string: String, from: TextArea, to: TextArea) -> String { string.replace(&from, &to) } diff --git a/node-graph/gcore/src/memo.rs b/node-graph/gcore/src/memo.rs index 66464eef4f..1a124d2068 100644 --- a/node-graph/gcore/src/memo.rs +++ b/node-graph/gcore/src/memo.rs @@ -2,6 +2,7 @@ use crate::{Node, WasmNotSend}; use dyn_any::DynFuture; use std::future::Future; use std::hash::DefaultHasher; +use std::hash::{Hash, Hasher}; use std::ops::Deref; use std::sync::Arc; use std::sync::Mutex; @@ -49,6 +50,10 @@ impl MemoNode { } } +pub mod memo { + pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::MemoNode"); +} + /// Caches the output of a given Node and acts as a proxy. /// In contrast to the regular `MemoNode`. This node ignores all input. /// Using this node might result in the document not updating properly, @@ -98,6 +103,10 @@ impl ImpureMemoNode { } } +pub mod impure_memo { + pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode"); +} + /// Stores both what a node was called with and what it returned. #[derive(Clone, Debug)] pub struct IORecord { @@ -142,7 +151,10 @@ impl MonitorNode { } } -use std::hash::{Hash, Hasher}; +pub mod monitor { + pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::MonitorNode"); +} + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] pub struct MemoHash { hash: u64, diff --git a/node-graph/gcore/src/registry.rs b/node-graph/gcore/src/registry.rs index 2727a95759..5d405df093 100644 --- a/node-graph/gcore/src/registry.rs +++ b/node-graph/gcore/src/registry.rs @@ -1,4 +1,4 @@ -use crate::{Node, NodeIO, NodeIOTypes, Type, WasmNotSend}; +use crate::{Node, NodeIO, NodeIOTypes, ProtoNodeIdentifier, Type, WasmNotSend}; use dyn_any::{DynAny, StaticType}; use std::borrow::Cow; use std::collections::HashMap; @@ -30,6 +30,8 @@ pub mod types { pub type Resolution = glam::UVec2; /// DVec2 with px unit pub type PixelSize = glam::DVec2; + /// String with one or more than one line + pub type TextArea = String; } // Translation struct between macro and definition @@ -101,11 +103,11 @@ pub enum RegistryValueSource { Scope(&'static str), } -type NodeRegistry = LazyLock>>>; +type NodeRegistry = LazyLock>>>; pub static NODE_REGISTRY: NodeRegistry = LazyLock::new(|| Mutex::new(HashMap::new())); -pub static NODE_METADATA: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); +pub static NODE_METADATA: LazyLock>> = LazyLock::new(|| Mutex::new(HashMap::new())); #[cfg(not(target_arch = "wasm32"))] pub type DynFuture<'n, T> = Pin + 'n + Send>>; diff --git a/node-graph/gcore/src/transform_nodes.rs b/node-graph/gcore/src/transform_nodes.rs index 1c28a3087d..4cde6a7457 100644 --- a/node-graph/gcore/src/transform_nodes.rs +++ b/node-graph/gcore/src/transform_nodes.rs @@ -20,7 +20,6 @@ async fn transform( rotate: f64, scale: DVec2, skew: DVec2, - _pivot: DVec2, ) -> Instances { let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., skew.y, skew.x, 1., 0., 0.]); diff --git a/node-graph/gcore/src/types.rs b/node-graph/gcore/src/types.rs index 156d9a85d2..48ee2d804c 100644 --- a/node-graph/gcore/src/types.rs +++ b/node-graph/gcore/src/types.rs @@ -1,6 +1,7 @@ use std::any::TypeId; pub use std::borrow::Cow; +use std::ops::Deref; #[macro_export] macro_rules! concrete { @@ -128,12 +129,37 @@ impl std::fmt::Debug for NodeIOTypes { pub struct ProtoNodeIdentifier { pub name: Cow<'static, str>, } + impl From for ProtoNodeIdentifier { fn from(value: String) -> Self { Self { name: Cow::Owned(value) } } } +impl From<&'static str> for ProtoNodeIdentifier { + fn from(s: &'static str) -> Self { + ProtoNodeIdentifier { name: Cow::Borrowed(s) } + } +} + +impl ProtoNodeIdentifier { + pub const fn new(name: &'static str) -> Self { + ProtoNodeIdentifier { name: Cow::Borrowed(name) } + } + + pub const fn with_owned_string(name: String) -> Self { + ProtoNodeIdentifier { name: Cow::Owned(name) } + } +} + +impl Deref for ProtoNodeIdentifier { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.name.as_ref() + } +} + fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result, D::Error> { use serde::Deserialize; @@ -306,6 +332,13 @@ impl Type { Self::Future(output) => output.replace_nested(f), } } + + pub fn to_cow_string(&self) -> Cow<'static, str> { + match self { + Type::Generic(name) => name.clone(), + _ => Cow::Owned(self.to_string()), + } + } } fn format_type(ty: &str) -> String { @@ -343,19 +376,3 @@ impl std::fmt::Display for Type { write!(f, "{}", result) } } - -impl From<&'static str> for ProtoNodeIdentifier { - fn from(s: &'static str) -> Self { - ProtoNodeIdentifier { name: Cow::Borrowed(s) } - } -} - -impl ProtoNodeIdentifier { - pub const fn new(name: &'static str) -> Self { - ProtoNodeIdentifier { name: Cow::Borrowed(name) } - } - - pub const fn with_owned_string(name: String) -> Self { - ProtoNodeIdentifier { name: Cow::Owned(name) } - } -} diff --git a/node-graph/gcore/src/vector/click_target.rs b/node-graph/gcore/src/vector/click_target.rs index 365c2c7ee0..4ea81c3cd2 100644 --- a/node-graph/gcore/src/vector/click_target.rs +++ b/node-graph/gcore/src/vector/click_target.rs @@ -67,6 +67,10 @@ impl ClickTarget { self.bounding_box } + pub fn bounding_box_center(&self) -> Option { + self.bounding_box.map(|bbox| bbox[0] + (bbox[1] - bbox[0]) / 2.) + } + pub fn bounding_box_with_transform(&self, transform: DAffine2) -> Option<[DVec2; 2]> { self.bounding_box.map(|[a, b]| [transform.transform_point2(a), transform.transform_point2(b)]) } diff --git a/node-graph/gcore/src/vector/vector_data/modification.rs b/node-graph/gcore/src/vector/vector_data/modification.rs index 0f06c643a2..fd7162a6a8 100644 --- a/node-graph/gcore/src/vector/vector_data/modification.rs +++ b/node-graph/gcore/src/vector/vector_data/modification.rs @@ -1,7 +1,7 @@ use super::*; use crate::Ctx; use crate::instances::Instance; -use crate::uuid::generate_uuid; +use crate::uuid::{NodeId, generate_uuid}; use bezier_rs::BezierHandles; use dyn_any::DynAny; use kurbo::{BezPath, PathEl, Point}; @@ -420,12 +420,17 @@ impl Hash for VectorModification { /// A node that applies a procedural modification to some [`VectorData`]. #[node_macro::node(category(""))] -async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box) -> VectorDataTable { +async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box, node_path: Vec) -> VectorDataTable { if vector_data.is_empty() { vector_data.push(Instance::default()); } let vector_data_instance = vector_data.get_mut(0).expect("push should give one item"); modification.apply(vector_data_instance.instance); + + // Update the source node id + let this_node_path = node_path.iter().rev().nth(1).copied(); + *vector_data_instance.source_node_id = vector_data_instance.source_node_id.or(this_node_path); + if vector_data.len() > 1 { warn!("The path modify ran on {} instances of vector data. Only the first can be modified.", vector_data.len()); } diff --git a/node-graph/gcore/src/vector/vector_nodes.rs b/node-graph/gcore/src/vector/vector_nodes.rs index 118c2fbfb6..b3b47e48e7 100644 --- a/node-graph/gcore/src/vector/vector_nodes.rs +++ b/node-graph/gcore/src/vector/vector_nodes.rs @@ -352,8 +352,7 @@ async fn copy_to_points( let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation); for mut instance in instance.instance_ref_iter().map(|instance| instance.to_instance_cloned()) { - let local_matrix = DAffine2::from_mat2(instance.transform.matrix2); - instance.transform = transform * local_matrix; + instance.transform = transform * instance.transform; result_table.push(instance); } diff --git a/node-graph/gmath-nodes/src/lib.rs b/node-graph/gmath-nodes/src/lib.rs index b47b9c0eee..1019506bdc 100644 --- a/node-graph/gmath-nodes/src/lib.rs +++ b/node-graph/gmath-nodes/src/lib.rs @@ -1,6 +1,6 @@ use glam::DVec2; use graphene_core::gradient::GradientStops; -use graphene_core::registry::types::{Fraction, Percentage}; +use graphene_core::registry::types::{Fraction, Percentage, TextArea}; use graphene_core::{Color, Ctx, num_traits}; use log::warn; use math_parser::ast; @@ -603,7 +603,7 @@ fn gradient_value(_: impl Ctx, _primary: (), gradient: GradientStops) -> Gradien /// Constructs a string value which may be set to any plain text. #[node_macro::node(category("Value"))] -fn string_value(_: impl Ctx, _primary: (), string: String) -> String { +fn string_value(_: impl Ctx, _primary: (), string: TextArea) -> String { string } @@ -612,6 +612,20 @@ fn dot_product(_: impl Ctx, vector_a: DVec2, vector_b: DVec2) -> f64 { vector_a.dot(vector_b) } +/// Gets the length or magnitude of a vector. +#[node_macro::node(category("Math: Vector"))] +fn length(_: impl Ctx, vector: DVec2) -> f64 { + vector.length() +} + +/// Scales the input vector to unit length while preserving it's direction. This is equivalent to dividing the input vector by it's own magnitude. +/// +/// Returns zero when the input vector is zero. +#[node_macro::node(category("Math: Vector"))] +fn normalize(_: impl Ctx, vector: DVec2) -> DVec2 { + vector.normalize_or_zero() +} + #[cfg(test)] mod test { use super::*; @@ -625,6 +639,12 @@ mod test { assert_eq!(dot_product((), vector_a, vector_b), 11.); } + #[test] + pub fn length_function() { + let vector = DVec2::new(3., 4.); + assert_eq!(length((), vector), 5.); + } + #[test] fn test_basic_expression() { let result = math((), 0., "2 + 2".to_string(), 0.); diff --git a/node-graph/graph-craft/src/document.rs b/node-graph/graph-craft/src/document.rs index 85c15343df..08679ce417 100644 --- a/node-graph/graph-craft/src/document.rs +++ b/node-graph/graph-craft/src/document.rs @@ -363,17 +363,6 @@ impl NodeInput { NodeInput::Reflection(_) => false, } } - /// Network node inputs in the document network are not displayed, but still exist in the compiled network - pub fn is_exposed_to_frontend(&self, is_document_network: bool) -> bool { - match self { - NodeInput::Node { .. } => true, - NodeInput::Value { exposed, .. } => *exposed, - NodeInput::Network { .. } => !is_document_network, - NodeInput::Inline(_) => false, - NodeInput::Scope(_) => false, - NodeInput::Reflection(_) => false, - } - } pub fn ty(&self) -> Type { match self { @@ -497,10 +486,6 @@ impl DocumentNodeImplementation { } } - pub const fn proto(name: &'static str) -> Self { - Self::ProtoNode(ProtoNodeIdentifier::new(name)) - } - pub fn output_count(&self) -> usize { match self { DocumentNodeImplementation::Network(network) => network.exports.len(), @@ -1250,24 +1235,28 @@ impl NodeNetwork { /// Create a [`RecursiveNodeIter`] that iterates over all [`DocumentNode`]s, including ones that are deeply nested. pub fn recursive_nodes(&self) -> RecursiveNodeIter<'_> { - let nodes = self.nodes.iter().collect(); + let nodes = self.nodes.iter().map(|(id, node)| (id, node, Vec::new())).collect(); RecursiveNodeIter { nodes } } } /// An iterator over all [`DocumentNode`]s, including ones that are deeply nested. pub struct RecursiveNodeIter<'a> { - nodes: Vec<(&'a NodeId, &'a DocumentNode)>, + nodes: Vec<(&'a NodeId, &'a DocumentNode, Vec)>, } impl<'a> Iterator for RecursiveNodeIter<'a> { - type Item = (&'a NodeId, &'a DocumentNode); + type Item = (&'a NodeId, &'a DocumentNode, Vec); fn next(&mut self) -> Option { - let node = self.nodes.pop()?; - if let DocumentNodeImplementation::Network(network) = &node.1.implementation { - self.nodes.extend(network.nodes.iter()); + let (current_id, node, path) = self.nodes.pop()?; + if let DocumentNodeImplementation::Network(network) = &node.implementation { + self.nodes.extend(network.nodes.iter().map(|(id, node)| { + let mut nested_path = path.clone(); + nested_path.push(*current_id); + (id, node, nested_path) + })); } - Some(node) + Some((current_id, node, path)) } } @@ -1275,7 +1264,6 @@ impl<'a> Iterator for RecursiveNodeIter<'a> { mod test { use super::*; use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; - use graphene_core::ProtoNodeIdentifier; use std::sync::atomic::AtomicU64; fn gen_node_id() -> NodeId { @@ -1547,7 +1535,7 @@ mod test { NodeId(1), DocumentNode { inputs: vec![NodeInput::network(concrete!(u32), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), + implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), ..Default::default() }, ), @@ -1555,7 +1543,7 @@ mod test { NodeId(2), DocumentNode { inputs: vec![NodeInput::network(concrete!(u32), 1)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), + implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), ..Default::default() }, ), @@ -1582,7 +1570,7 @@ mod test { NodeId(2), DocumentNode { inputs: vec![result_node_input], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), + implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER), ..Default::default() }, ), diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index ef713a8c8d..67018f4395 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -97,7 +97,7 @@ macro_rules! tagged_value { } } /// Attempts to downcast the dynamic type to a tagged value - pub fn try_from_std_any_ref(input: &(dyn std::any::Any)) -> Result { + pub fn try_from_std_any_ref(input: &dyn std::any::Any) -> Result { use std::any::TypeId; match input.type_id() { @@ -190,9 +190,9 @@ tagged_value! { VectorData(graphene_core::vector::VectorDataTable), #[cfg_attr(target_arch = "wasm32", serde(alias = "ImageFrame", deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] // TODO: Eventually remove this migration document upgrade code RasterData(graphene_core::raster_types::RasterDataTable), - #[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code + #[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::graphic_element::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code GraphicGroup(graphene_core::GraphicGroupTable), - #[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::migrate_artboard_group"))] // TODO: Eventually remove this migration document upgrade code + #[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::graphic_element::migrate_artboard_group"))] // TODO: Eventually remove this migration document upgrade code ArtboardGroup(graphene_core::ArtboardGroupTable), // ============ // STRUCT TYPES diff --git a/node-graph/gstd/src/wasm_application_io.rs b/node-graph/gstd/src/wasm_application_io.rs index 82b526f18e..ae03edd425 100644 --- a/node-graph/gstd/src/wasm_application_io.rs +++ b/node-graph/gstd/src/wasm_application_io.rs @@ -18,7 +18,6 @@ use graphene_svg_renderer::{GraphicElementRendered, RenderParams, RenderSvgSegme use base64::Engine; #[cfg(target_arch = "wasm32")] use glam::DAffine2; -use std::collections::{HashMap, HashSet}; use std::sync::Arc; #[cfg(target_arch = "wasm32")] use wasm_bindgen::JsCast; @@ -278,12 +277,7 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>( #[cfg(all(feature = "vello", not(test)))] let use_vello = use_vello && surface_handle.is_some(); - let mut metadata = RenderMetadata { - upstream_footprints: HashMap::new(), - local_transforms: HashMap::new(), - click_targets: HashMap::new(), - clip_targets: HashSet::new(), - }; + let mut metadata = RenderMetadata::default(); data.collect_metadata(&mut metadata, footprint, None); let output_format = render_config.export_format; diff --git a/node-graph/gsvg-renderer/src/renderer.rs b/node-graph/gsvg-renderer/src/renderer.rs index 95e9890a86..70f7c18206 100644 --- a/node-graph/gsvg-renderer/src/renderer.rs +++ b/node-graph/gsvg-renderer/src/renderer.rs @@ -198,6 +198,7 @@ pub fn to_transform(transform: DAffine2) -> usvg::Transform { pub struct RenderMetadata { pub upstream_footprints: HashMap, pub local_transforms: HashMap, + pub first_instance_source_id: HashMap>, pub click_targets: HashMap>, pub clip_targets: HashSet, } @@ -1090,6 +1091,7 @@ impl GraphicElementRendered for GraphicElement { metadata.upstream_footprints.insert(element_id, footprint); // TODO: Find a way to handle more than one row of the graphical data table if let Some(vector_data) = vector_data.instance_ref_iter().next() { + metadata.first_instance_source_id.insert(element_id, *vector_data.source_node_id); metadata.local_transforms.insert(element_id, *vector_data.transform); } } diff --git a/node-graph/interpreted-executor/src/lib.rs b/node-graph/interpreted-executor/src/lib.rs index 097653c2a9..5c05ef62ba 100644 --- a/node-graph/interpreted-executor/src/lib.rs +++ b/node-graph/interpreted-executor/src/lib.rs @@ -20,7 +20,7 @@ mod tests { NodeId(0), DocumentNode { inputs: vec![NodeInput::network(concrete!(u32), 0)], - implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), + implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER), ..Default::default() }, ), diff --git a/node-graph/interpreted-executor/src/util.rs b/node-graph/interpreted-executor/src/util.rs index ab4c744e36..e0f52dae20 100644 --- a/node-graph/interpreted-executor/src/util.rs +++ b/node-graph/interpreted-executor/src/util.rs @@ -39,7 +39,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc syn::Result quote!(stringify!(#path).replace(' ', "")), - None => quote!(std::module_path!().rsplit_once("::").unwrap().0), + + let identifier = format_ident!("{}_proto_ident", fn_name); + let identifier_path = match parsed.attributes.path.as_ref() { + Some(path) => { + let path = path.to_token_stream().to_string().replace(' ', ""); + quote!(#path) + } + None => quote!(std::module_path!()), }; - let identifier = quote!(format!("{}::{}", #path, stringify!(#struct_name))); let register_node_impl = generate_register_node_impl(parsed, &field_names, &struct_name, &identifier)?; let import_name = format_ident!("_IMPORT_STUB_{}", mod_name.to_string().to_case(Case::UpperSnake)); @@ -354,6 +358,11 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result #graphene_core::ProtoNodeIdentifier { + #graphene_core::ProtoNodeIdentifier::new(std::concat!(#identifier_path, "::", std::stringify!(#struct_name))) + } + #[doc(inline)] pub use #mod_name::#struct_name; @@ -418,67 +427,63 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result TokenStream2 { - if parsed.attributes.skip_impl { - return quote! {}; - } +fn generate_node_input_references(parsed: &ParsedNodeFn, fn_generics: &[crate::GenericParam], field_idents: &[&PatIdent], graphene_core: &TokenStream2, identifier: &Ident) -> TokenStream2 { let inputs_module_name = format_ident!("{}", parsed.struct_name.to_string().to_case(Case::Snake)); - let (mut modified, mut generic_collector) = FilterUsedGenerics::new(fn_generics); - let mut generated_input_accessor = Vec::new(); - for (input_index, (parsed_input, input_ident)) in parsed.fields.iter().zip(field_idents).enumerate() { - let mut ty = match parsed_input { - ParsedField::Regular { ty, .. } => ty, - ParsedField::Node { output_type, .. } => output_type, + if !parsed.attributes.skip_impl { + let (mut modified, mut generic_collector) = FilterUsedGenerics::new(fn_generics); + + for (input_index, (parsed_input, input_ident)) in parsed.fields.iter().zip(field_idents).enumerate() { + let mut ty = match parsed_input { + ParsedField::Regular { ty, .. } => ty, + ParsedField::Node { output_type, .. } => output_type, + } + .clone(); + + // We only want the necessary generics. + let used = generic_collector.filter_unnecessary_generics(&mut modified, &mut ty); + // TODO: figure out a better name that doesn't conflict with so many types + let struct_name = format_ident!("{}Input", input_ident.ident.to_string().to_case(Case::Pascal)); + let (fn_generic_params, phantom_data_declerations) = generate_phantom_data(used.iter()); + + // Only create structs with phantom data where necessary. + generated_input_accessor.push(if phantom_data_declerations.is_empty() { + quote! { + pub struct #struct_name; + } + } else { + quote! { + pub struct #struct_name <#(#used),*>{ + #(#phantom_data_declerations,)* + } + } + }); + generated_input_accessor.push(quote! { + impl <#(#used),*> #graphene_core::NodeInputDecleration for #struct_name <#(#fn_generic_params),*> { + const INDEX: usize = #input_index; + fn identifier() -> #graphene_core::ProtoNodeIdentifier { + #inputs_module_name::IDENTIFIER.clone() + } + type Result = #ty; + } + }) } - .clone(); - - // We only want the necessary generics. - let used = generic_collector.filter_unnecessary_generics(&mut modified, &mut ty); - // TODO: figure out a better name that doesn't conflict with so many types - let struct_name = format_ident!("{}Input", input_ident.ident.to_string().to_case(Case::Pascal)); - let (fn_generic_params, phantom_data_declerations) = generate_phantom_data(used.iter()); - - // Only create structs with phantom data where necessary. - generated_input_accessor.push(if phantom_data_declerations.is_empty() { - quote! { - pub struct #struct_name; - } - } else { - quote! { - pub struct #struct_name <#(#used),*>{ - #(#phantom_data_declerations,)* - } - } - }); - generated_input_accessor.push(quote! { - impl <#(#used),*> #graphene_core::NodeInputDecleration for #struct_name <#(#fn_generic_params),*> { - const INDEX: usize = #input_index; - fn identifier() -> &'static str { - protonode_identifier() - } - type Result = #ty; - } - }) } quote! { pub mod #inputs_module_name { use super::*; - pub fn protonode_identifier() -> &'static str { - // Storing the string in a once lock should reduce allocations (since we call this in a loop)? - static NODE_NAME: std::sync::OnceLock = std::sync::OnceLock::new(); - NODE_NAME.get_or_init(|| #identifier ) - } + /// The `ProtoNodeIdentifier` of this node without any generics attached to it + pub const IDENTIFIER: #graphene_core::ProtoNodeIdentifier = #identifier(); #(#generated_input_accessor)* } } @@ -511,7 +516,7 @@ fn generate_phantom_data<'a>(fn_generics: impl Iterator Result { +fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], struct_name: &Ident, identifier: &Ident) -> Result { if parsed.attributes.skip_impl { return Ok(quote!()); } @@ -604,7 +609,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st fn register_node() { let mut registry = NODE_REGISTRY.lock().unwrap(); registry.insert( - #identifier, + #identifier(), vec![ #(#constructors,)* ] diff --git a/node-graph/preprocessor/src/lib.rs b/node-graph/preprocessor/src/lib.rs index 50c265f92f..e0b4a01685 100644 --- a/node-graph/preprocessor/src/lib.rs +++ b/node-graph/preprocessor/src/lib.rs @@ -6,7 +6,7 @@ use graphene_std::registry::*; use graphene_std::*; use std::collections::{HashMap, HashSet}; -pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap) { +pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap) { if network.generated { return; } @@ -15,7 +15,7 @@ pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap expand_network(node_network, substitutions), DocumentNodeImplementation::ProtoNode(proto_node_identifier) => { - if let Some(new_node) = substitutions.get(proto_node_identifier.name.as_ref()) { + if let Some(new_node) = substitutions.get(proto_node_identifier) { node.implementation = new_node.implementation.clone(); } } @@ -24,7 +24,7 @@ pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap HashMap { +pub fn generate_node_substitutions() -> HashMap { let mut custom = HashMap::new(); let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap(); for (id, metadata) in graphene_core::registry::NODE_METADATA.lock().unwrap().iter() { @@ -49,7 +49,7 @@ pub fn generate_node_substitutions() -> HashMap { let input_count = inputs.len(); let network_inputs = (0..input_count).map(|i| NodeInput::node(NodeId(i as u64), 0)).collect(); - let identity_node = ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"); + let identity_node = ops::identity::IDENTIFIER; let into_node_registry = &interpreted_executor::node_registry::NODE_REGISTRY; diff --git a/proc-macros/src/combined_message_attrs.rs b/proc-macros/src/combined_message_attrs.rs index aadca6384e..943457cd1c 100644 --- a/proc-macros/src/combined_message_attrs.rs +++ b/proc-macros/src/combined_message_attrs.rs @@ -61,7 +61,7 @@ pub fn combined_message_attrs_impl(attr: TokenStream, input_item: TokenStream) - <#parent as ToDiscriminant>::Discriminant }; - input.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant, TransitiveChild)] }); + input.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant, TransitiveChild, HierarchicalTree)] }); input.attrs.push(syn::parse_quote! { #[parent(#parent, #parent::#variant)] }); if parent_is_top { input.attrs.push(syn::parse_quote! { #[parent_is_top] }); @@ -97,7 +97,7 @@ pub fn combined_message_attrs_impl(attr: TokenStream, input_item: TokenStream) - fn top_level_impl(input_item: TokenStream) -> syn::Result { let mut input = syn::parse2::(input_item)?; - input.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant)] }); + input.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant, HierarchicalTree)] }); input.attrs.push(syn::parse_quote! { #[discriminant_attr(derive(Debug, Copy, Clone, PartialEq, Eq, Hash, AsMessage))] }); for var in &mut input.variants { diff --git a/proc-macros/src/extract_fields.rs b/proc-macros/src/extract_fields.rs new file mode 100644 index 0000000000..606b3b8a37 --- /dev/null +++ b/proc-macros/src/extract_fields.rs @@ -0,0 +1,57 @@ +use crate::helpers::clean_rust_type_syntax; +use proc_macro2::{Span, TokenStream}; +use quote::{ToTokens, format_ident, quote}; +use syn::{Data, DeriveInput, Fields, Type, parse2}; + +pub fn derive_extract_field_impl(input: TokenStream) -> syn::Result { + let input = parse2::(input)?; + let struct_name = &input.ident; + let generics = &input.generics; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let fields = match &input.data { + Data::Struct(data) => match &data.fields { + Fields::Named(fields) => &fields.named, + _ => return Err(syn::Error::new(Span::call_site(), "ExtractField only works on structs with named fields")), + }, + _ => return Err(syn::Error::new(Span::call_site(), "ExtractField only works on structs")), + }; + + let mut field_line = Vec::new(); + // Extract field names and types as strings at compile time + let field_info = fields + .iter() + .map(|field| { + let ident = field.ident.as_ref().unwrap(); + let name = ident.to_string(); + let ty = clean_rust_type_syntax(field.ty.to_token_stream().to_string()); + let line = ident.span().start().line; + field_line.push(line); + (name, ty) + }) + .collect::>(); + + let field_str = field_info.into_iter().map(|(name, ty)| (format!("{}: {}", name, ty))); + + let res = quote! { + impl #impl_generics #struct_name #ty_generics #where_clause { + pub fn field_types() -> Vec<(String, usize)> { + vec![ + #((String::from(#field_str), #field_line)),* + ] + } + + pub fn print_field_types() { + for (field, line) in Self::field_types() { + println!("{} at line {}", field, line); + } + } + + pub fn path() -> &'static str { + file!() + } + } + }; + + Ok(res) +} diff --git a/proc-macros/src/helpers.rs b/proc-macros/src/helpers.rs index 11c18c8b41..230f792c0f 100644 --- a/proc-macros/src/helpers.rs +++ b/proc-macros/src/helpers.rs @@ -42,6 +42,58 @@ pub fn two_segment_path(left_ident: Ident, right_ident: Ident) -> Path { Path { leading_colon: None, segments } } +pub fn clean_rust_type_syntax(input: String) -> String { + let mut result = String::new(); + let mut chars = input.chars().peekable(); + + while let Some(c) = chars.next() { + match c { + '&' => { + result.push('&'); + while let Some(' ') = chars.peek() { + chars.next(); + } + } + '<' => { + while let Some(' ') = result.chars().rev().next() { + result.pop(); + } + result.push('<'); + while let Some(' ') = chars.peek() { + chars.next(); + } + } + '>' => { + while let Some(' ') = result.chars().rev().next() { + result.pop(); + } + result.push('>'); + while let Some(' ') = chars.peek() { + chars.next(); + } + } + ':' => { + if let Some(':') = chars.peek() { + while let Some(' ') = result.chars().rev().next() { + result.pop(); + } + } + result.push(':'); + chars.next(); + result.push(':'); + while let Some(' ') = chars.peek() { + chars.next(); + } + } + _ => { + result.push(c); + } + } + } + + result +} + #[cfg(test)] mod tests { use super::*; diff --git a/proc-macros/src/hierarchical_tree.rs b/proc-macros/src/hierarchical_tree.rs new file mode 100644 index 0000000000..e0d6fb6a71 --- /dev/null +++ b/proc-macros/src/hierarchical_tree.rs @@ -0,0 +1,73 @@ +use proc_macro2::{Span, TokenStream}; +use quote::{ToTokens, quote}; +use syn::{Data, DeriveInput, Fields, Type, parse2}; + +pub fn generate_hierarchical_tree(input: TokenStream) -> syn::Result { + let input = parse2::(input)?; + let input_type = &input.ident; + + let data = match &input.data { + Data::Enum(data) => data, + _ => return Err(syn::Error::new(Span::call_site(), "Tried to derive HierarchicalTree for non-enum")), + }; + + let build_message_tree = data.variants.iter().map(|variant| { + let variant_type = &variant.ident; + + let has_child = variant + .attrs + .iter() + .any(|attr| attr.path().get_ident().is_some_and(|ident| ident == "sub_discriminant" || ident == "child")); + + if has_child { + if let Fields::Unnamed(fields) = &variant.fields { + let field_type = &fields.unnamed.first().unwrap().ty; + quote! { + { + let mut variant_tree = DebugMessageTree::new(stringify!(#variant_type)); + let field_name = stringify!(#field_type); + const message_string: &str = "Message"; + if message_string == &field_name[field_name.len().saturating_sub(message_string.len())..] { + // The field is a Message type, recursively build its tree + let sub_tree = #field_type::build_message_tree(); + variant_tree.add_variant(sub_tree); + } + message_tree.add_variant(variant_tree); + } + } + } else { + quote! { + message_tree.add_variant(DebugMessageTree::new(stringify!(#variant_type))); + } + } + } else { + quote! { + message_tree.add_variant(DebugMessageTree::new(stringify!(#variant_type))); + } + } + }); + + let res = quote! { + impl HierarchicalTree for #input_type { + fn build_message_tree() -> DebugMessageTree { + let mut message_tree = DebugMessageTree::new(stringify!(#input_type)); + #(#build_message_tree)* + let message_handler_str = #input_type::message_handler_str(); + if message_handler_str.fields().len() > 0 { + message_tree.add_message_handler_field(message_handler_str); + } + + let message_handler_data_str = #input_type::message_handler_data_str(); + if message_handler_data_str.fields().len() > 0 { + message_tree.add_message_handler_data_field(message_handler_data_str); + } + + message_tree.set_path(file!()); + + message_tree + } + } + }; + + Ok(res) +} diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs index 02cd1caaf4..8d68df75c8 100644 --- a/proc-macros/src/lib.rs +++ b/proc-macros/src/lib.rs @@ -3,17 +3,23 @@ mod as_message; mod combined_message_attrs; mod discriminant; +mod extract_fields; mod helper_structs; mod helpers; +mod hierarchical_tree; mod hint; +mod message_handler_data_attr; mod transitive_child; mod widget_builder; use crate::as_message::derive_as_message_impl; use crate::combined_message_attrs::combined_message_attrs_impl; use crate::discriminant::derive_discriminant_impl; +use crate::extract_fields::derive_extract_field_impl; use crate::helper_structs::AttrInnerSingleString; +use crate::hierarchical_tree::generate_hierarchical_tree; use crate::hint::derive_hint_impl; +use crate::message_handler_data_attr::message_handler_data_attr_impl; use crate::transitive_child::derive_transitive_child_impl; use crate::widget_builder::derive_widget_builder_impl; use proc_macro::TokenStream; @@ -281,6 +287,21 @@ pub fn derive_widget_builder(input_item: TokenStream) -> TokenStream { TokenStream::from(derive_widget_builder_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error())) } +#[proc_macro_derive(HierarchicalTree)] +pub fn derive_hierarchical_tree(input_item: TokenStream) -> TokenStream { + TokenStream::from(generate_hierarchical_tree(input_item.into()).unwrap_or_else(|err| err.to_compile_error())) +} + +#[proc_macro_derive(ExtractField)] +pub fn derive_extract_field(input_item: TokenStream) -> TokenStream { + TokenStream::from(derive_extract_field_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error())) +} + +#[proc_macro_attribute] +pub fn message_handler_data(attr: TokenStream, input_item: TokenStream) -> TokenStream { + TokenStream::from(message_handler_data_attr_impl(attr.into(), input_item.into()).unwrap_or_else(|err| err.to_compile_error())) +} + #[cfg(test)] mod tests { use super::*; diff --git a/proc-macros/src/message_handler_data_attr.rs b/proc-macros/src/message_handler_data_attr.rs new file mode 100644 index 0000000000..b2170beac4 --- /dev/null +++ b/proc-macros/src/message_handler_data_attr.rs @@ -0,0 +1,118 @@ +use crate::helpers::{call_site_ident, clean_rust_type_syntax}; +use proc_macro2::{Span, TokenStream}; +use quote::{ToTokens, quote}; +use syn::{ItemImpl, Type, parse2, spanned::Spanned}; + +pub fn message_handler_data_attr_impl(attr: TokenStream, input_item: TokenStream) -> syn::Result { + // Parse the input as an impl block + let impl_block = parse2::(input_item.clone())?; + + let self_ty = &impl_block.self_ty; + + let path = match &**self_ty { + Type::Path(path) => &path.path, + _ => return Err(syn::Error::new(Span::call_site(), "Expected impl implementation")), + }; + + let input_type = path.segments.last().map(|s| &s.ident).unwrap(); + + // Extract the message type from the trait path + let trait_path = match &impl_block.trait_ { + Some((_, path, _)) => path, + None => return Err(syn::Error::new(Span::call_site(), "Expected trait implementation")), + }; + + // Get the trait generics (should be MessageHandler) + if let Some(segment) = trait_path.segments.last() { + if segment.ident != "MessageHandler" { + return Err(syn::Error::new(segment.ident.span(), "Expected MessageHandler trait")); + } + if let syn::PathArguments::AngleBracketed(args) = &segment.arguments { + if args.args.len() >= 2 { + // Extract the message type (M) and data type (D) from the trait params + let message_type = &args.args[0]; + let data_type = &args.args[1]; + + // Check if the attribute is "CustomData" + let is_custom_data = attr.to_string().contains("CustomData"); + + let impl_item = match data_type { + syn::GenericArgument::Type(t) => { + match t { + syn::Type::Path(type_path) if !type_path.path.segments.is_empty() => { + // Get just the base identifier (ToolMessageData) without generics + let type_name = &type_path.path.segments.first().unwrap().ident; + + if is_custom_data { + quote! { + #input_item + impl #message_type { + pub fn message_handler_data_str() -> MessageData { + custom_data() + } + pub fn message_handler_str() -> MessageData { + MessageData::new(format!("{}",stringify!(#input_type)), #input_type::field_types(), #input_type::path()) + + } + } + } + } else { + quote! { + #input_item + impl #message_type { + pub fn message_handler_data_str() -> MessageData + { + MessageData::new(format!("{}",stringify!(#type_name)), #type_name::field_types(), #type_name::path()) + + } + pub fn message_handler_str() -> MessageData { + MessageData::new(format!("{}",stringify!(#input_type)), #input_type::field_types(), #input_type::path()) + + } + } + } + } + } + syn::Type::Tuple(_) => quote! { + #input_item + impl #message_type { + pub fn message_handler_str() -> MessageData { + MessageData::new(format!("{}",stringify!(#input_type)), #input_type::field_types(), #input_type::path()) + } + } + }, + syn::Type::Reference(type_reference) => { + let message_type = call_site_ident(format!("{input_type}Message")); + let type_ident = match &*type_reference.elem { + syn::Type::Path(type_path) => &type_path.path.segments.first().unwrap().ident, + _ => return Err(syn::Error::new(type_reference.elem.span(), "Expected type path")), + }; + let tr = clean_rust_type_syntax(type_reference.to_token_stream().to_string()); + quote! { + #input_item + impl #message_type { + pub fn message_handler_data_str() -> MessageData { + MessageData::new(format!("{}", #tr),#type_ident::field_types(), #type_ident::path()) + } + + pub fn message_handler_str() -> MessageData { + MessageData::new(format!("{}",stringify!(#input_type)), #input_type::field_types(), #input_type::path()) + + } + } + } + } + _ => return Err(syn::Error::new(t.span(), "Unsupported type format")), + } + } + + _ => quote! { + #input_item + }, + }; + return Ok(impl_item); + } + } + } + Ok(input_item) +}