This commit is contained in:
mtvare6
2025-07-23 14:29:59 +05:30
159 changed files with 4838 additions and 2997 deletions
+64 -3
View File
@@ -4,7 +4,7 @@ on:
push:
branches:
- master
pull_request:
pull_request: {}
env:
CARGO_TERM_COLOR: always
INDEX_HTML_HEAD_REPLACEMENT: <script defer data-domain="dev.graphite.rs" data-api="https://graphite.rs/visit/event" src="https://graphite.rs/visit/script.hash.js"></script>
@@ -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 <head> 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 -->|$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
@@ -73,9 +73,10 @@ jobs:
rustc --version
- name: ✂ Replace template in <head> 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 -->|$INDEX_HTML_HEAD_REPLACEMENT|" frontend/index.html
- name: ⌨ Set build command based on comment
+37 -9
View File
@@ -31,11 +31,47 @@ 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 <head> of index.html
run: |
# Remove the INDEX_HTML_HEAD_INCLUSION environment variable for build links (not master deploys)
git rev-parse --abbrev-ref HEAD | grep master > /dev/null || export INDEX_HTML_HEAD_INCLUSION=""
- 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/other/editor-structure`
run: |
mv artifacts/* website/other/editor-structure
- name: 🔧 Build auto-generated code docs artifacts into HTML
run: |
cd website/other/editor-structure
node generate.js hierarchical_message_system_tree.txt replacement.html
- name: 🌐 Build Graphite website with Zola
env:
MODE: prod
@@ -44,16 +80,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:
Generated
+202 -169
View File
@@ -10,9 +10,9 @@ checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
[[package]]
name = "ab_glyph"
version = "0.2.30"
version = "0.2.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0f4f6fbdc5ee39f2ede9f5f3ec79477271a6d6a2baff22310d51736bda6cea"
checksum = "e074464580a518d16a7126262fffaaa47af89d4099d4cb403f8ed938ba12ee7d"
dependencies = [
"ab_glyph_rasterizer",
"owned_ttf_parser",
@@ -546,9 +546,9 @@ dependencies = [
[[package]]
name = "bytemuck_derive"
version = "1.9.3"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ecc273b49b3205b83d648f0690daa588925572cc5063745bfe547fe7ec8e1a1"
checksum = "441473f2b4b0459a68628c744bc61d23e730fb00128b841d30fa4bb3972257e4"
dependencies = [
"proc-macro2",
"quote",
@@ -677,9 +677,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.2.29"
version = "1.2.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c1599538de2394445747c8cf7935946e3cc27e9625f889d979bfb2aaf569362"
checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7"
dependencies = [
"jobserver",
"libc",
@@ -815,20 +815,13 @@ checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
[[package]]
name = "codespan-reporting"
version = "0.11.1"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "color"
version = "0.1.0"
source = "git+https://github.com/linebender/color.git?rev=a4fa61aff6c3f292b729dc409e7832e5f0166e4a#a4fa61aff6c3f292b729dc409e7832e5f0166e4a"
checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81"
dependencies = [
"serde",
"termcolor",
"unicode-width",
]
[[package]]
@@ -1565,12 +1558,6 @@ dependencies = [
"rustc_version",
]
[[package]]
name = "fixedbitset"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "fixedbitset"
version = "0.5.7"
@@ -1605,15 +1592,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "font-types"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fa6a5e5a77b5f3f7f9e32879f484aa5b3632ddfbe568a16266c904a6f32cdaf"
dependencies = [
"bytemuck",
]
[[package]]
name = "font-types"
version = "0.9.0"
@@ -1671,7 +1649,7 @@ dependencies = [
"objc2-core-foundation",
"objc2-core-text",
"objc2-foundation 0.3.1",
"peniko 0.4.0",
"peniko",
"read-fonts 0.29.3",
"roxmltree",
"smallvec",
@@ -2129,9 +2107,9 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "glow"
version = "0.14.2"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51fa363f025f5c111e03f13eda21162faeacb6911fe8caa0c0349f9cf0c4483"
checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08"
dependencies = [
"js-sys",
"slotmap",
@@ -2306,7 +2284,7 @@ dependencies = [
"num-traits",
"parley",
"petgraph 0.7.1",
"rand 0.9.1",
"rand 0.9.2",
"rand_chacha 0.9.0",
"rustc-hash 2.1.1",
"serde",
@@ -2327,7 +2305,7 @@ dependencies = [
"log",
"math-parser",
"node-macro",
"rand 0.9.1",
"rand 0.9.2",
]
[[package]]
@@ -2359,7 +2337,7 @@ dependencies = [
"image",
"ndarray",
"node-macro",
"rand 0.9.1",
"rand 0.9.2",
"rand_chacha 0.9.0",
"serde",
"specta",
@@ -2388,7 +2366,7 @@ dependencies = [
"log",
"ndarray",
"node-macro",
"rand 0.9.1",
"rand 0.9.2",
"rand_chacha 0.9.0",
"reqwest",
"tokio",
@@ -2587,6 +2565,7 @@ dependencies = [
"bytemuck",
"cfg-if",
"crunchy",
"num-traits",
"serde",
]
@@ -2751,9 +2730,9 @@ dependencies = [
[[package]]
name = "hyper-util"
version = "0.1.15"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df"
checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
dependencies = [
"base64 0.22.1",
"bytes",
@@ -2767,7 +2746,7 @@ dependencies = [
"libc",
"percent-encoding",
"pin-project-lite",
"socket2",
"socket2 0.6.0",
"system-configuration",
"tokio",
"tower-service",
@@ -3102,9 +3081,9 @@ dependencies = [
[[package]]
name = "io-uring"
version = "0.7.8"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013"
checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
dependencies = [
"bitflags 2.9.1",
"cfg-if",
@@ -3346,11 +3325,12 @@ dependencies = [
[[package]]
name = "kurbo"
version = "0.11.2"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1077d333efea6170d9ccb96d3c3026f300ca0773da4938cc4c811daa6df68b0c"
checksum = "c62026ae44756f8a599ba21140f350303d4f08dcdcc71b5ad9c9bb8128c13c62"
dependencies = [
"arrayvec",
"euclid",
"serde",
"smallvec",
]
@@ -3435,13 +3415,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "libredox"
version = "0.1.4"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638"
checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0"
dependencies = [
"bitflags 2.9.1",
"libc",
"redox_syscall 0.5.13",
"redox_syscall 0.5.15",
]
[[package]]
@@ -3615,9 +3595,9 @@ dependencies = [
[[package]]
name = "metal"
version = "0.29.0"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ecfd3296f8c56b7c1f6fbac3c71cefa9d78ce009850c45000015f206dc7fa21"
checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e"
dependencies = [
"bitflags 2.9.1",
"block",
@@ -3684,24 +3664,28 @@ dependencies = [
[[package]]
name = "naga"
version = "23.1.0"
version = "25.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "364f94bc34f61332abebe8cad6f6cd82a5b65cff22c828d05d0968911462ca4f"
checksum = "2b977c445f26e49757f9aca3631c3b8b836942cb278d69a92e7b80d3b24da632"
dependencies = [
"arrayvec",
"bit-set",
"bitflags 2.9.1",
"cfg_aliases 0.1.1",
"cfg_aliases 0.2.1",
"codespan-reporting",
"half",
"hashbrown 0.15.4",
"hexf-parse",
"indexmap 2.10.0",
"log",
"petgraph 0.6.5",
"num-traits",
"once_cell",
"petgraph 0.8.2",
"rustc-hash 1.1.0",
"spirv",
"termcolor",
"thiserror 1.0.69",
"unicode-xid",
"strum",
"thiserror 2.0.12",
"unicode-ident",
]
[[package]]
@@ -3895,6 +3879,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
"libm",
]
[[package]]
@@ -4266,6 +4251,15 @@ dependencies = [
"libredox",
]
[[package]]
name = "ordered-float"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bb71e1b3fa6ca1c61f383464aaf2bb0e2f8e772a1f01d486832464de363b951"
dependencies = [
"num-traits",
]
[[package]]
name = "os_pipe"
version = "1.2.2"
@@ -4328,7 +4322,7 @@ checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.5.13",
"redox_syscall 0.5.15",
"smallvec",
"windows-targets 0.52.6",
]
@@ -4341,7 +4335,7 @@ checksum = "13e57638545cf2ba4c3e72cc5715e53b1880b829cc3dbefda3d1700c58efe723"
dependencies = [
"fontique",
"hashbrown 0.15.4",
"peniko 0.4.0",
"peniko",
"skrifa 0.31.3",
"swash",
]
@@ -4372,23 +4366,13 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]]
name = "peniko"
version = "0.2.0"
source = "git+https://github.com/mTvare6/peniko.git?branch=luminance-clip#cd9aa45fe5a5c3070f18311cf33aad9cc83e5863"
dependencies = [
"color 0.1.0",
"kurbo",
"smallvec",
]
[[package]]
name = "peniko"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f9529efd019889b2a205193c14ffb6e2839b54ed9d2720674f10f4b04d87ac9"
dependencies = [
"color 0.3.1",
"color",
"kurbo",
"smallvec",
]
@@ -4445,22 +4429,24 @@ dependencies = [
[[package]]
name = "petgraph"
version = "0.6.5"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
dependencies = [
"fixedbitset 0.4.2",
"fixedbitset",
"indexmap 2.10.0",
]
[[package]]
name = "petgraph"
version = "0.7.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772"
checksum = "54acf3a685220b533e437e264e4d932cfbdc4cc7ec0cd232ed73c08d03b8a7ca"
dependencies = [
"fixedbitset 0.5.7",
"fixedbitset",
"hashbrown 0.15.4",
"indexmap 2.10.0",
"serde",
]
[[package]]
@@ -4677,17 +4663,16 @@ dependencies = [
[[package]]
name = "polling"
version = "3.8.0"
version = "3.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b53a684391ad002dd6a596ceb6c74fd004fdce75f4be2e3f615068abbea5fd50"
checksum = "8ee9b2fa7a4517d2c91ff5bc6c297a427a96749d15f98fcdbb22c05571a4d4b7"
dependencies = [
"cfg-if",
"concurrent-queue",
"hermit-abi",
"pin-project-lite",
"rustix 1.0.8",
"tracing",
"windows-sys 0.59.0",
"windows-sys 0.60.2",
]
[[package]]
@@ -4930,7 +4915,7 @@ dependencies = [
"quinn-udp",
"rustc-hash 2.1.1",
"rustls",
"socket2",
"socket2 0.5.10",
"thiserror 2.0.12",
"tokio",
"tracing",
@@ -4946,7 +4931,7 @@ dependencies = [
"bytes",
"getrandom 0.3.3",
"lru-slab",
"rand 0.9.1",
"rand 0.9.2",
"ring",
"rustc-hash 2.1.1",
"rustls",
@@ -4967,7 +4952,7 @@ dependencies = [
"cfg_aliases 0.2.1",
"libc",
"once_cell",
"socket2",
"socket2 0.5.10",
"tracing",
"windows-sys 0.59.0",
]
@@ -5014,9 +4999,9 @@ dependencies = [
[[package]]
name = "rand"
version = "0.9.1"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.3",
@@ -5185,16 +5170,6 @@ dependencies = [
"crossbeam-utils",
]
[[package]]
name = "read-fonts"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f9e8a4f503e5c8750e4cd3b32a4e090035c46374b305a15c70bad833dca05f"
dependencies = [
"bytemuck",
"font-types 0.8.4",
]
[[package]]
name = "read-fonts"
version = "0.29.3"
@@ -5202,7 +5177,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04ca636dac446b5664bd16c069c00a9621806895b8bb02c2dc68542b23b8f25d"
dependencies = [
"bytemuck",
"font-types 0.9.0",
"font-types",
]
[[package]]
@@ -5212,7 +5187,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "192735ef611aac958468e670cb98432c925426f3cb71521fda202130f7388d91"
dependencies = [
"bytemuck",
"font-types 0.9.0",
"font-types",
]
[[package]]
@@ -5226,9 +5201,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
version = "0.5.13"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6"
checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec"
dependencies = [
"bitflags 2.9.1",
]
@@ -5724,9 +5699,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.140"
version = "1.0.141"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
dependencies = [
"itoa",
"memchr",
@@ -5943,16 +5918,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "skrifa"
version = "0.26.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cc1aa86c26dbb1b63875a7180aa0819709b33348eb5b1491e4321fae388179d"
dependencies = [
"bytemuck",
"read-fonts 0.25.3",
]
[[package]]
name = "skrifa"
version = "0.31.3"
@@ -6041,6 +6006,16 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "socket2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
dependencies = [
"libc",
"windows-sys 0.59.0",
]
[[package]]
name = "softbuffer"
version = "0.4.6"
@@ -6057,7 +6032,7 @@ dependencies = [
"objc2-foundation 0.2.2",
"objc2-quartz-core 0.2.2",
"raw-window-handle",
"redox_syscall 0.5.13",
"redox_syscall 0.5.15",
"wasm-bindgen",
"web-sys",
"windows-sys 0.59.0",
@@ -6182,6 +6157,28 @@ version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
dependencies = [
"strum_macros",
]
[[package]]
name = "strum_macros"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.104",
]
[[package]]
name = "subtle"
version = "2.6.1"
@@ -6366,9 +6363,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
version = "2.6.2"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "124e129c9c0faa6bec792c5948c89e86c90094133b0b9044df0ce5f0a8efaa0d"
checksum = "352a4bc7bf6c25f5624227e3641adf475a6535707451b09bb83271df8b7a6ac7"
dependencies = [
"anyhow",
"bytes",
@@ -6416,9 +6413,9 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f025c389d3adb83114bec704da973142e82fc6ec799c7c750c5e21cefaec83"
checksum = "182d688496c06bf08ea896459bf483eb29cdff35c1c4c115fb14053514303064"
dependencies = [
"anyhow",
"cargo_toml",
@@ -6438,9 +6435,9 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5df493a1075a241065bc865ed5ef8d0fbc1e76c7afdc0bf0eccfaa7d4f0e406"
checksum = "b54a99a6cd8e01abcfa61508177e6096a4fe2681efecee9214e962f2f073ae4a"
dependencies = [
"base64 0.22.1",
"brotli",
@@ -6465,9 +6462,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "2.3.1"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f237fbea5866fa5f2a60a21bea807a2d6e0379db070d89c3a10ac0f2d4649bbc"
checksum = "7945b14dc45e23532f2ded6e120170bbdd4af5ceaa45784a6b33d250fbce3f9e"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -6479,9 +6476,9 @@ dependencies = [
[[package]]
name = "tauri-plugin"
version = "2.3.0"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d9a0bd00bf1930ad1a604d08b0eb6b2a9c1822686d65d7f4731a7723b8901d3"
checksum = "5bd5c1e56990c70a906ef67a9851bbdba9136d26075ee9a2b19c8b46986b3e02"
dependencies = [
"anyhow",
"glob",
@@ -6496,9 +6493,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-fs"
version = "2.4.0"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c341290d31991dbca38b31d412c73dfbdb070bb11536784f19dd2211d13b778f"
checksum = "8c6ef84ee2f2094ce093e55106d90d763ba343fad57566992962e8f76d113f99"
dependencies = [
"anyhow",
"dunce",
@@ -6518,9 +6515,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-http"
version = "2.5.0"
version = "2.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c1a38da944b357ffa23bafd563b1579f18e6fbd118fcd84769406d35dcc5c7"
checksum = "fcde333d97e565a7765aad82f32d8672458f7bd77b6ee653830d5dded9d7b5c2"
dependencies = [
"bytes",
"cookie_store",
@@ -6563,9 +6560,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "2.7.0"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e7bb73d1bceac06c20b3f755b2c8a2cb13b20b50083084a8cf3700daf397ba4"
checksum = "2b1cc885be806ea15ff7b0eb47098a7b16323d9228876afda329e34e2d6c4676"
dependencies = [
"cookie",
"dpi",
@@ -6585,9 +6582,9 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "2.7.1"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "902b5aa9035e16f342eb64f8bf06ccdc2808e411a2525ed1d07672fa4e780bad"
checksum = "fe653a2fbbef19fe898efc774bc52c8742576342a33d3d028c189b57eb1d2439"
dependencies = [
"gtk",
"http",
@@ -6612,9 +6609,9 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "2.5.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41743bbbeb96c3a100d234e5a0b60a46d5aa068f266160862c7afdbf828ca02e"
checksum = "9330c15cabfe1d9f213478c9e8ec2b0c76dab26bb6f314b8ad1c8a568c1d186e"
dependencies = [
"anyhow",
"brotli",
@@ -6857,7 +6854,7 @@ dependencies = [
"mio",
"pin-project-lite",
"slab",
"socket2",
"socket2 0.5.10",
"tokio-macros",
"windows-sys 0.52.0",
]
@@ -7226,15 +7223,9 @@ checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
[[package]]
name = "unicode-width"
version = "0.1.14"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
[[package]]
name = "untrusted"
@@ -7342,15 +7333,15 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vello"
version = "0.3.0"
source = "git+https://github.com/mTvare6/vello.git?branch=luminance-clip#cbe3d204cdbe3dd45716ffac6109da1390ec9536"
version = "0.5.0"
source = "git+https://github.com/linebender/vello.git#daf940230a24cbb123a458b6de95721af47aef98"
dependencies = [
"bytemuck",
"futures-intrusive",
"log",
"peniko 0.2.0",
"peniko",
"png",
"skrifa 0.26.6",
"skrifa 0.31.3",
"static_assertions",
"thiserror 2.0.12",
"vello_encoding",
@@ -7360,22 +7351,23 @@ dependencies = [
[[package]]
name = "vello_encoding"
version = "0.3.0"
source = "git+https://github.com/mTvare6/vello.git?branch=luminance-clip#cbe3d204cdbe3dd45716ffac6109da1390ec9536"
version = "0.5.0"
source = "git+https://github.com/linebender/vello.git#daf940230a24cbb123a458b6de95721af47aef98"
dependencies = [
"bytemuck",
"guillotiere",
"peniko 0.2.0",
"skrifa 0.26.6",
"peniko",
"skrifa 0.31.3",
"smallvec",
]
[[package]]
name = "vello_shaders"
version = "0.3.0"
source = "git+https://github.com/mTvare6/vello.git?branch=luminance-clip#cbe3d204cdbe3dd45716ffac6109da1390ec9536"
version = "0.5.0"
source = "git+https://github.com/linebender/vello.git#daf940230a24cbb123a458b6de95721af47aef98"
dependencies = [
"bytemuck",
"log",
"naga",
"thiserror 2.0.12",
"vello_encoding",
@@ -7722,9 +7714,9 @@ dependencies = [
[[package]]
name = "webpki-roots"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8782dd5a41a24eed3a4f40b606249b3e236ca61adf1f25ea4d45c73de122b502"
checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2"
dependencies = [
"rustls-pki-types",
]
@@ -7773,17 +7765,20 @@ checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3"
[[package]]
name = "wgpu"
version = "23.0.1"
version = "25.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80f70000db37c469ea9d67defdc13024ddf9a5f1b89cb2941b812ad7cde1735a"
checksum = "ec8fb398f119472be4d80bc3647339f56eb63b2a331f6a3d16e25d8144197dd9"
dependencies = [
"arrayvec",
"cfg_aliases 0.1.1",
"bitflags 2.9.1",
"cfg_aliases 0.2.1",
"document-features",
"hashbrown 0.15.4",
"js-sys",
"log",
"naga",
"parking_lot",
"portable-atomic",
"profiling",
"raw-window-handle",
"smallvec",
@@ -7798,30 +7793,63 @@ dependencies = [
[[package]]
name = "wgpu-core"
version = "23.0.1"
version = "25.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d63c3c478de8e7e01786479919c8769f62a22eec16788d8c2ac77ce2c132778a"
checksum = "f7b882196f8368511d613c6aeec80655160db6646aebddf8328879a88d54e500"
dependencies = [
"arrayvec",
"bit-set",
"bit-vec",
"bitflags 2.9.1",
"bytemuck",
"cfg_aliases 0.1.1",
"cfg_aliases 0.2.1",
"document-features",
"hashbrown 0.15.4",
"indexmap 2.10.0",
"log",
"naga",
"once_cell",
"parking_lot",
"portable-atomic",
"profiling",
"raw-window-handle",
"rustc-hash 1.1.0",
"smallvec",
"thiserror 1.0.69",
"thiserror 2.0.12",
"wgpu-core-deps-apple",
"wgpu-core-deps-emscripten",
"wgpu-core-deps-windows-linux-android",
"wgpu-hal",
"wgpu-types",
]
[[package]]
name = "wgpu-core-deps-apple"
version = "25.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfd488b3239b6b7b185c3b045c39ca6bf8af34467a4c5de4e0b1a564135d093d"
dependencies = [
"wgpu-hal",
]
[[package]]
name = "wgpu-core-deps-emscripten"
version = "25.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09ad7aceb3818e52539acc679f049d3475775586f3f4e311c30165cf2c00445"
dependencies = [
"wgpu-hal",
]
[[package]]
name = "wgpu-core-deps-windows-linux-android"
version = "25.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cba5fb5f7f9c98baa7c889d444f63ace25574833df56f5b817985f641af58e46"
dependencies = [
"wgpu-hal",
]
[[package]]
name = "wgpu-executor"
version = "0.1.0"
@@ -7842,9 +7870,9 @@ dependencies = [
[[package]]
name = "wgpu-hal"
version = "23.0.1"
version = "25.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89364b8a0b211adc7b16aeaf1bd5ad4a919c1154b44c9ce27838213ba05fd821"
checksum = "f968767fe4d3d33747bbd1473ccd55bf0f6451f55d733b5597e67b5deab4ad17"
dependencies = [
"android_system_properties",
"arrayvec",
@@ -7853,13 +7881,15 @@ dependencies = [
"bitflags 2.9.1",
"block",
"bytemuck",
"cfg_aliases 0.1.1",
"cfg-if",
"cfg_aliases 0.2.1",
"core-graphics-types 0.1.3",
"glow",
"glutin_wgl_sys",
"gpu-alloc",
"gpu-allocator",
"gpu-descriptor",
"hashbrown 0.15.4",
"js-sys",
"khronos-egl",
"libc",
@@ -7869,15 +7899,15 @@ dependencies = [
"naga",
"ndk-sys 0.5.0+25.2.9519653",
"objc",
"once_cell",
"ordered-float",
"parking_lot",
"portable-atomic",
"profiling",
"range-alloc",
"raw-window-handle",
"renderdoc-sys",
"rustc-hash 1.1.0",
"smallvec",
"thiserror 1.0.69",
"thiserror 2.0.12",
"wasm-bindgen",
"web-sys",
"wgpu-types",
@@ -7887,12 +7917,15 @@ dependencies = [
[[package]]
name = "wgpu-types"
version = "23.0.0"
version = "25.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "610f6ff27778148c31093f3b03abc4840f9636d58d597ca2f5977433acfe0068"
checksum = "2aa49460c2a8ee8edba3fca54325540d904dd85b2e086ada762767e17d06e8bc"
dependencies = [
"bitflags 2.9.1",
"bytemuck",
"js-sys",
"log",
"thiserror 2.0.12",
"web-sys",
]
+2 -2
View File
@@ -83,7 +83,7 @@ axum = "0.8"
chrono = "0.4"
ron = "0.8"
fastnoise-lite = "1.1"
wgpu = { version = "23", features = [
wgpu = { version = "25.0.2", features = [
# We don't have wgpu on multiple threads (yet) https://github.com/gfx-rs/wgpu/blob/trunk/CHANGELOG.md#wgpu-types-now-send-sync-on-wasm
"fragile-send-sync-non-atomic-wasm",
"spirv",
@@ -114,7 +114,7 @@ web-sys = { version = "=0.3.77", features = [
winit = "0.29"
url = "2.5"
tokio = { version = "1.29", features = ["fs", "macros", "io-std", "rt"] }
vello = { git = "https://github.com/mTvare6/vello.git", branch = "luminance-clip" } # TODO switch back to stable when a release is made
vello = { git = "https://github.com/linebender/vello.git" } # TODO switch back to stable when a release is made
resvg = "0.44"
usvg = "0.44"
rand = { version = "0.9", default-features = false, features = ["std_rng"] }
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -2,7 +2,7 @@
name = "graphite-editor"
publish = false
version = "0.0.0"
rust-version = "1.85"
rust-version = "1.88"
authors = ["Graphite Authors <contact@graphite.rs>"]
edition = "2024"
readme = "../README.md"
+4 -1
View File
@@ -101,6 +101,7 @@ pub const MIN_LENGTH_FOR_SKEW_TRIANGLE_VISIBILITY: f64 = 48.;
// PATH TOOL
pub const MANIPULATOR_GROUP_MARKER_SIZE: f64 = 6.;
pub const SELECTION_THRESHOLD: f64 = 10.;
pub const DRILL_THROUGH_THRESHOLD: f64 = 10.;
pub const HIDE_HANDLE_DISTANCE: f64 = 3.;
pub const HANDLE_ROTATE_SNAP_ANGLE: f64 = 15.;
pub const SEGMENT_INSERTION_DISTANCE: f64 = 5.;
@@ -134,13 +135,15 @@ pub const SCALE_EFFECT: f64 = 0.5;
// COLORS
pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff";
pub const COLOR_OVERLAY_BLUE_50: &str = "#00a8ff80";
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";
pub const COLOR_OVERLAY_GRAY_25: &str = "#cccccc40";
pub const COLOR_OVERLAY_WHITE: &str = "#ffffff";
pub const COLOR_OVERLAY_LABEL_BACKGROUND: &str = "#000000cc";
pub const COLOR_OVERLAY_BLACK_75: &str = "#000000bf";
// DOCUMENT
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
+106 -122
View File
@@ -1,6 +1,6 @@
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
use crate::messages::dialog::DialogMessageData;
use crate::messages::portfolio::document::node_graph::document_node_definitions;
use crate::messages::dialog::DialogMessageContext;
use crate::messages::layout::layout_message_handler::LayoutMessageContext;
use crate::messages::prelude::*;
#[derive(Debug, Default)]
@@ -91,7 +91,7 @@ impl Dispatcher {
pub fn handle_message<T: Into<Message>>(&mut self, message: T, process_after_all_current: bool) {
let message = message.into();
// Add all additional messages to the buffer if it exists (except from the end buffer message)
if !matches!(message, Message::EndBuffer(_)) {
if !matches!(message, Message::EndBuffer { .. }) {
if let Some(buffered_queue) = &mut self.buffered_queue {
Self::schedule_execution(buffered_queue, true, [message]);
@@ -126,10 +126,112 @@ impl Dispatcher {
// Process the action by forwarding it to the relevant message handler, or saving the FrontendMessage to be sent to the frontend
match message {
Message::Animation(message) => {
self.message_handlers.animation_message_handler.process_message(message, &mut queue, ());
}
Message::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()),
Message::Debug(message) => {
self.message_handlers.debug_message_handler.process_message(message, &mut queue, ());
}
Message::Dialog(message) => {
let context = DialogMessageContext {
portfolio: &self.message_handlers.portfolio_message_handler,
preferences: &self.message_handlers.preferences_message_handler,
};
self.message_handlers.dialog_message_handler.process_message(message, &mut queue, context);
}
Message::Frontend(message) => {
// Handle these messages immediately by returning early
if let FrontendMessage::TriggerFontLoad { .. } = message {
self.responses.push(message);
self.cleanup_queues(false);
// Return early to avoid running the code after the match block
return;
} else {
// `FrontendMessage`s are saved and will be sent to the frontend after the message queue is done being processed
self.responses.push(message);
}
}
Message::Globals(message) => {
self.message_handlers.globals_message_handler.process_message(message, &mut queue, ());
}
Message::InputPreprocessor(message) => {
let keyboard_platform = GLOBAL_PLATFORM.get().copied().unwrap_or_default().as_keyboard_platform_layout();
self.message_handlers
.input_preprocessor_message_handler
.process_message(message, &mut queue, InputPreprocessorMessageContext { keyboard_platform });
}
Message::KeyMapping(message) => {
let input = &self.message_handlers.input_preprocessor_message_handler;
let actions = self.collect_actions();
self.message_handlers
.key_mapping_message_handler
.process_message(message, &mut queue, KeyMappingMessageContext { input, actions });
}
Message::Layout(message) => {
let action_input_mapping = &|action_to_find: &MessageDiscriminant| self.message_handlers.key_mapping_message_handler.action_input_mapping(action_to_find);
let context = LayoutMessageContext { action_input_mapping };
self.message_handlers.layout_message_handler.process_message(message, &mut queue, context);
}
Message::Portfolio(message) => {
let ipp = &self.message_handlers.input_preprocessor_message_handler;
let preferences = &self.message_handlers.preferences_message_handler;
let current_tool = &self.message_handlers.tool_message_handler.tool_state.tool_data.active_tool_type;
let message_logging_verbosity = self.message_handlers.debug_message_handler.message_logging_verbosity;
let reset_node_definitions_on_open = self.message_handlers.portfolio_message_handler.reset_node_definitions_on_open;
let timing_information = self.message_handlers.animation_message_handler.timing_information();
let animation = &self.message_handlers.animation_message_handler;
self.message_handlers.portfolio_message_handler.process_message(
message,
&mut queue,
PortfolioMessageContext {
ipp,
preferences,
current_tool,
message_logging_verbosity,
reset_node_definitions_on_open,
timing_information,
animation,
},
);
}
Message::Preferences(message) => {
self.message_handlers.preferences_message_handler.process_message(message, &mut queue, ());
}
Message::Tool(message) => {
let document_id = self.message_handlers.portfolio_message_handler.active_document_id().unwrap();
let Some(document) = self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id) else {
warn!("Called ToolMessage without an active document.\nGot {message:?}");
return;
};
let context = ToolMessageContext {
document_id,
document,
input: &self.message_handlers.input_preprocessor_message_handler,
persistent_data: &self.message_handlers.portfolio_message_handler.persistent_data,
node_graph: &self.message_handlers.portfolio_message_handler.executor,
preferences: &self.message_handlers.preferences_message_handler,
};
self.message_handlers.tool_message_handler.process_message(message, &mut queue, context);
}
Message::Workspace(message) => {
self.message_handlers.workspace_message_handler.process_message(message, &mut queue, ());
}
Message::NoOp => {}
Message::Batched { messages } => {
messages.iter().for_each(|message| self.handle_message(message.to_owned(), false));
}
Message::StartBuffer => {
self.buffered_queue = Some(std::mem::take(&mut self.message_queues));
}
Message::EndBuffer(render_metadata) => {
Message::EndBuffer { render_metadata } => {
// Assign the message queue to the currently buffered queue
if let Some(buffered_queue) = self.buffered_queue.take() {
self.cleanup_queues(false);
@@ -157,124 +259,6 @@ impl Dispatcher {
];
Self::schedule_execution(&mut self.message_queues, false, messages.map(Message::from));
}
Message::NoOp => {}
Message::Init => {
// Load persistent data from the browser database
queue.add(FrontendMessage::TriggerLoadFirstAutoSaveDocument);
queue.add(FrontendMessage::TriggerLoadPreferences);
// Display the menu bar at the top of the window
queue.add(MenuBarMessage::SendLayout);
// Send the information for tooltips and categories for each node/input.
queue.add(FrontendMessage::SendUIMetadata {
node_descriptions: document_node_definitions::collect_node_descriptions(),
node_types: document_node_definitions::collect_node_types(),
});
// Finish loading persistent data from the browser database
queue.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments);
}
Message::Animation(message) => {
self.message_handlers.animation_message_handler.process_message(message, &mut queue, ());
}
Message::Batched(messages) => {
messages.iter().for_each(|message| self.handle_message(message.to_owned(), false));
}
Message::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()),
Message::Debug(message) => {
self.message_handlers.debug_message_handler.process_message(message, &mut queue, ());
}
Message::Dialog(message) => {
let data = DialogMessageData {
portfolio: &self.message_handlers.portfolio_message_handler,
preferences: &self.message_handlers.preferences_message_handler,
};
self.message_handlers.dialog_message_handler.process_message(message, &mut queue, data);
}
Message::Frontend(message) => {
// Handle these messages immediately by returning early
if let FrontendMessage::TriggerFontLoad { .. } = message {
self.responses.push(message);
self.cleanup_queues(false);
// Return early to avoid running the code after the match block
return;
} else {
// `FrontendMessage`s are saved and will be sent to the frontend after the message queue is done being processed
self.responses.push(message);
}
}
Message::Globals(message) => {
self.message_handlers.globals_message_handler.process_message(message, &mut queue, ());
}
Message::InputPreprocessor(message) => {
let keyboard_platform = GLOBAL_PLATFORM.get().copied().unwrap_or_default().as_keyboard_platform_layout();
self.message_handlers
.input_preprocessor_message_handler
.process_message(message, &mut queue, InputPreprocessorMessageData { keyboard_platform });
}
Message::KeyMapping(message) => {
let input = &self.message_handlers.input_preprocessor_message_handler;
let actions = self.collect_actions();
self.message_handlers
.key_mapping_message_handler
.process_message(message, &mut queue, KeyMappingMessageData { input, actions });
}
Message::Layout(message) => {
let action_input_mapping = &|action_to_find: &MessageDiscriminant| self.message_handlers.key_mapping_message_handler.action_input_mapping(action_to_find);
self.message_handlers.layout_message_handler.process_message(message, &mut queue, action_input_mapping);
}
Message::Portfolio(message) => {
let ipp = &self.message_handlers.input_preprocessor_message_handler;
let preferences = &self.message_handlers.preferences_message_handler;
let current_tool = &self.message_handlers.tool_message_handler.tool_state.tool_data.active_tool_type;
let message_logging_verbosity = self.message_handlers.debug_message_handler.message_logging_verbosity;
let reset_node_definitions_on_open = self.message_handlers.portfolio_message_handler.reset_node_definitions_on_open;
let timing_information = self.message_handlers.animation_message_handler.timing_information();
let animation = &self.message_handlers.animation_message_handler;
self.message_handlers.portfolio_message_handler.process_message(
message,
&mut queue,
PortfolioMessageData {
ipp,
preferences,
current_tool,
message_logging_verbosity,
reset_node_definitions_on_open,
timing_information,
animation,
},
);
}
Message::Preferences(message) => {
self.message_handlers.preferences_message_handler.process_message(message, &mut queue, ());
}
Message::Tool(message) => {
let document_id = self.message_handlers.portfolio_message_handler.active_document_id().unwrap();
let Some(document) = self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id) else {
warn!("Called ToolMessage without an active document.\nGot {message:?}");
return;
};
let data = ToolMessageData {
document_id,
document,
input: &self.message_handlers.input_preprocessor_message_handler,
persistent_data: &self.message_handlers.portfolio_message_handler.persistent_data,
node_graph: &self.message_handlers.portfolio_message_handler.executor,
preferences: &self.message_handlers.preferences_message_handler,
};
self.message_handlers.tool_message_handler.process_message(message, &mut queue, data);
}
Message::Workspace(message) => {
self.message_handlers.workspace_message_handler.process_message(message, &mut queue, ());
}
}
// If there are child messages, append the queue to the list of queues
@@ -9,9 +9,9 @@ pub enum AnimationMessage {
EnableLivePreview,
DisableLivePreview,
RestartAnimation,
SetFrameIndex(f64),
SetTime(f64),
SetFrameIndex { frame: f64 },
SetTime { time: f64 },
UpdateTime,
IncrementFrameCounter,
SetAnimationTimeMode(AnimationTimeMode),
SetAnimationTimeMode { animation_time_mode: AnimationTimeMode },
}
@@ -59,7 +59,7 @@ impl AnimationMessageHandler {
#[message_handler_data]
impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque<Message>, _data: ()) {
fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque<Message>, _: ()) {
match message {
AnimationMessage::ToggleLivePreview => match self.animation_state {
AnimationState::Stopped => responses.add(AnimationMessage::EnableLivePreview),
@@ -82,13 +82,13 @@ impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
// Update the restart and pause/play buttons
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
AnimationMessage::SetFrameIndex(frame) => {
AnimationMessage::SetFrameIndex { frame } => {
self.frame_index = frame;
responses.add(PortfolioMessage::SubmitActiveGraphRender);
// Update the restart and pause/play buttons
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
AnimationMessage::SetTime(time) => {
AnimationMessage::SetTime { time } => {
self.timestamp = time;
responses.add(AnimationMessage::UpdateTime);
}
@@ -120,7 +120,7 @@ impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
// Update the restart and pause/play buttons
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
AnimationMessage::SetAnimationTimeMode(animation_time_mode) => {
AnimationMessage::SetAnimationTimeMode { animation_time_mode } => {
self.animation_time_mode = animation_time_mode;
}
}
@@ -7,7 +7,7 @@ pub struct BroadcastMessageHandler {
#[message_handler_data]
impl MessageHandler<BroadcastMessage, ()> for BroadcastMessageHandler {
fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque<Message>, _data: ()) {
fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque<Message>, _: ()) {
match message {
// Sub-messages
BroadcastMessage::TriggerEvent(event) => {
@@ -8,7 +8,7 @@ pub struct DebugMessageHandler {
#[message_handler_data]
impl MessageHandler<DebugMessage, ()> for DebugMessageHandler {
fn process_message(&mut self, message: DebugMessage, responses: &mut VecDeque<Message>, _data: ()) {
fn process_message(&mut self, message: DebugMessage, responses: &mut VecDeque<Message>, _: ()) {
match message {
DebugMessage::ToggleTraceLogs => {
if log::max_level() == log::LevelFilter::Debug {
@@ -3,7 +3,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
#[derive(ExtractField)]
pub struct DialogMessageData<'a> {
pub struct DialogMessageContext<'a> {
pub portfolio: &'a PortfolioMessageHandler,
pub preferences: &'a PreferencesMessageHandler,
}
@@ -17,14 +17,14 @@ pub struct DialogMessageHandler {
}
#[message_handler_data]
impl MessageHandler<DialogMessage, DialogMessageData<'_>> for DialogMessageHandler {
fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque<Message>, data: DialogMessageData) {
let DialogMessageData { portfolio, preferences } = data;
impl MessageHandler<DialogMessage, DialogMessageContext<'_>> for DialogMessageHandler {
fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque<Message>, context: DialogMessageContext) {
let DialogMessageContext { portfolio, preferences } = context;
match message {
DialogMessage::ExportDialog(message) => self.export_dialog.process_message(message, responses, ExportDialogMessageData { portfolio }),
DialogMessage::ExportDialog(message) => self.export_dialog.process_message(message, responses, ExportDialogMessageContext { portfolio }),
DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_message(message, responses, ()),
DialogMessage::PreferencesDialog(message) => self.preferences_dialog.process_message(message, responses, PreferencesDialogMessageData { preferences }),
DialogMessage::PreferencesDialog(message) => self.preferences_dialog.process_message(message, responses, PreferencesDialogMessageContext { preferences }),
DialogMessage::CloseAllDocumentsWithConfirmation => {
let dialog = simple_dialogs::CloseAllDocumentsDialog {
@@ -4,7 +4,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
use crate::messages::prelude::*;
#[derive(ExtractField)]
pub struct ExportDialogMessageData<'a> {
pub struct ExportDialogMessageContext<'a> {
pub portfolio: &'a PortfolioMessageHandler,
}
@@ -33,9 +33,9 @@ impl Default for ExportDialogMessageHandler {
}
#[message_handler_data]
impl MessageHandler<ExportDialogMessage, ExportDialogMessageData<'_>> for ExportDialogMessageHandler {
fn process_message(&mut self, message: ExportDialogMessage, responses: &mut VecDeque<Message>, data: ExportDialogMessageData) {
let ExportDialogMessageData { portfolio } = data;
impl MessageHandler<ExportDialogMessage, ExportDialogMessageContext<'_>> for ExportDialogMessageHandler {
fn process_message(&mut self, message: ExportDialogMessage, responses: &mut VecDeque<Message>, context: ExportDialogMessageContext) {
let ExportDialogMessageContext { portfolio } = context;
match message {
ExportDialogMessage::FileType(export_type) => self.file_type = export_type,
@@ -4,4 +4,4 @@ mod export_dialog_message_handler;
#[doc(inline)]
pub use export_dialog_message::{ExportDialogMessage, ExportDialogMessageDiscriminant};
#[doc(inline)]
pub use export_dialog_message_handler::{ExportDialogMessageData, ExportDialogMessageHandler};
pub use export_dialog_message_handler::{ExportDialogMessageContext, ExportDialogMessageHandler};
+1 -1
View File
@@ -16,4 +16,4 @@ pub mod simple_dialogs;
#[doc(inline)]
pub use dialog_message::{DialogMessage, DialogMessageDiscriminant};
#[doc(inline)]
pub use dialog_message_handler::{DialogMessageData, DialogMessageHandler};
pub use dialog_message_handler::{DialogMessageContext, DialogMessageHandler};
@@ -13,7 +13,7 @@ pub struct NewDocumentDialogMessageHandler {
#[message_handler_data]
impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHandler {
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, _data: ()) {
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, _: ()) {
match message {
NewDocumentDialogMessage::Name(name) => self.name = name,
NewDocumentDialogMessage::Infinite(infinite) => self.infinite = infinite,
@@ -4,4 +4,4 @@ mod preferences_dialog_message_handler;
#[doc(inline)]
pub use preferences_dialog_message::{PreferencesDialogMessage, PreferencesDialogMessageDiscriminant};
#[doc(inline)]
pub use preferences_dialog_message_handler::{PreferencesDialogMessageData, PreferencesDialogMessageHandler};
pub use preferences_dialog_message_handler::{PreferencesDialogMessageContext, PreferencesDialogMessageHandler};
@@ -5,7 +5,7 @@ use crate::messages::preferences::SelectionMode;
use crate::messages::prelude::*;
#[derive(ExtractField)]
pub struct PreferencesDialogMessageData<'a> {
pub struct PreferencesDialogMessageContext<'a> {
pub preferences: &'a PreferencesMessageHandler,
}
@@ -14,9 +14,9 @@ pub struct PreferencesDialogMessageData<'a> {
pub struct PreferencesDialogMessageHandler {}
#[message_handler_data]
impl MessageHandler<PreferencesDialogMessage, PreferencesDialogMessageData<'_>> for PreferencesDialogMessageHandler {
fn process_message(&mut self, message: PreferencesDialogMessage, responses: &mut VecDeque<Message>, data: PreferencesDialogMessageData) {
let PreferencesDialogMessageData { preferences } = data;
impl MessageHandler<PreferencesDialogMessage, PreferencesDialogMessageContext<'_>> for PreferencesDialogMessageHandler {
fn process_message(&mut self, message: PreferencesDialogMessage, responses: &mut VecDeque<Message>, context: PreferencesDialogMessageContext) {
let PreferencesDialogMessageContext { preferences } = context;
match message {
PreferencesDialogMessage::Confirm => {}
@@ -5,7 +5,7 @@ pub struct GlobalsMessageHandler {}
#[message_handler_data]
impl MessageHandler<GlobalsMessage, ()> for GlobalsMessageHandler {
fn process_message(&mut self, message: GlobalsMessage, _responses: &mut VecDeque<Message>, _data: ()) {
fn process_message(&mut self, message: GlobalsMessage, _responses: &mut VecDeque<Message>, _: ()) {
match message {
GlobalsMessage::SetPlatform { platform } => {
if GLOBAL_PLATFORM.get() != Some(&platform) {
@@ -19,5 +19,6 @@ pub enum InputMapperMessage {
// Messages
PointerMove,
PointerShake,
WheelScroll,
}
@@ -7,7 +7,7 @@ use crate::messages::prelude::*;
use std::fmt::Write;
#[derive(ExtractField)]
pub struct InputMapperMessageData<'a> {
pub struct InputMapperMessageContext<'a> {
pub input: &'a InputPreprocessorMessageHandler,
pub actions: ActionList,
}
@@ -18,9 +18,9 @@ pub struct InputMapperMessageHandler {
}
#[message_handler_data]
impl MessageHandler<InputMapperMessage, InputMapperMessageData<'_>> for InputMapperMessageHandler {
fn process_message(&mut self, message: InputMapperMessage, responses: &mut VecDeque<Message>, data: InputMapperMessageData) {
let InputMapperMessageData { input, actions } = data;
impl MessageHandler<InputMapperMessage, InputMapperMessageContext<'_>> for InputMapperMessageHandler {
fn process_message(&mut self, message: InputMapperMessage, responses: &mut VecDeque<Message>, context: InputMapperMessageContext) {
let InputMapperMessageContext { input, actions } = context;
if let Some(message) = self.mapping.match_input_message(message, &input.keyboard, actions) {
responses.add(message);
@@ -54,14 +54,15 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(KeyZ); modifiers=[Accel, MouseLeft], action_dispatch=DocumentMessage::Noop),
//
// NodeGraphMessage
entry!(KeyDown(MouseLeft); action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: false, right_click: false}),
entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=NodeGraphMessage::PointerDown {shift_click: true, control_click: false, alt_click: false, right_click: false}),
entry!(KeyDown(MouseLeft); modifiers=[Accel], action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: true, alt_click: false, right_click: false}),
entry!(KeyDown(MouseLeft); modifiers=[Shift, Accel], action_dispatch=NodeGraphMessage::PointerDown {shift_click: true, control_click: true, alt_click: false, right_click: false}),
entry!(KeyDown(MouseLeft); modifiers=[Alt], action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: true, right_click: false}),
entry!(KeyDown(MouseRight); action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: false, right_click: true}),
entry!(KeyDown(MouseLeft); action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: false, right_click: false }),
entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=NodeGraphMessage::PointerDown { shift_click: true, control_click: false, alt_click: false, right_click: false }),
entry!(KeyDown(MouseLeft); modifiers=[Accel], action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: true, alt_click: false, right_click: false }),
entry!(KeyDown(MouseLeft); modifiers=[Shift, Accel], action_dispatch=NodeGraphMessage::PointerDown { shift_click: true, control_click: true, alt_click: false, right_click: false }),
entry!(KeyDown(MouseLeft); modifiers=[Alt], action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: true, right_click: false }),
entry!(KeyDown(MouseRight); action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: false, right_click: true }),
entry!(DoubleClick(MouseButton::Left); action_dispatch=NodeGraphMessage::EnterNestedNetwork),
entry!(PointerMove; refresh_keys=[Shift], action_dispatch=NodeGraphMessage::PointerMove {shift: Shift}),
entry!(PointerMove; refresh_keys=[Shift], action_dispatch=NodeGraphMessage::PointerMove { shift: Shift }),
entry!(PointerShake; action_dispatch=NodeGraphMessage::ShakeNode),
entry!(KeyUp(MouseLeft); action_dispatch=NodeGraphMessage::PointerUp),
entry!(KeyDown(Delete); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: false }),
entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: false }),
@@ -317,6 +318,7 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(KeyX); modifiers=[Shift], action_dispatch=ToolMessage::SwapColors),
entry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=ToolMessage::SelectRandomWorkingColor { primary: true }),
entry!(KeyDown(KeyC); modifiers=[Alt, Shift], action_dispatch=ToolMessage::SelectRandomWorkingColor { primary: false }),
entry!(KeyDownNoRepeat(Tab); action_dispatch=ToolMessage::ToggleSelectVsPath),
//
// DocumentMessage
entry!(KeyDown(Space); modifiers=[Control], action_dispatch=DocumentMessage::GraphViewOverlayToggle),
@@ -416,7 +418,7 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(Tab); modifiers=[Control], action_dispatch=PortfolioMessage::NextDocument),
entry!(KeyDown(Tab); modifiers=[Control, Shift], action_dispatch=PortfolioMessage::PrevDocument),
entry!(KeyDown(KeyW); modifiers=[Accel], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation),
entry!(KeyDown(KeyW); modifiers=[Accel,Alt], action_dispatch=PortfolioMessage::CloseAllDocumentsWithConfirmation),
entry!(KeyDown(KeyW); modifiers=[Accel, Alt], action_dispatch=PortfolioMessage::CloseAllDocumentsWithConfirmation),
entry!(KeyDown(KeyO); modifiers=[Accel], action_dispatch=PortfolioMessage::OpenDocument),
entry!(KeyDown(KeyI); modifiers=[Accel], action_dispatch=PortfolioMessage::Import),
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }),
@@ -439,7 +441,7 @@ pub fn input_mappings() -> Mapping {
entry!(KeyDown(Space); modifiers=[Shift], action_dispatch=AnimationMessage::ToggleLivePreview),
entry!(KeyDown(Home); modifiers=[Shift], action_dispatch=AnimationMessage::RestartAnimation),
];
let (mut key_up, mut key_down, mut key_up_no_repeat, mut key_down_no_repeat, mut double_click, mut wheel_scroll, mut pointer_move) = mappings;
let (mut key_up, mut key_down, mut key_up_no_repeat, mut key_down_no_repeat, mut double_click, mut wheel_scroll, mut pointer_move, mut pointer_shake) = mappings;
let sort = |list: &mut KeyMappingEntries| list.0.sort_by(|a, b| b.modifiers.count_ones().cmp(&a.modifiers.count_ones()));
// Sort the sublists of `key_up`, `key_down`, `key_up_no_repeat`, and `key_down_no_repeat`
@@ -456,6 +458,8 @@ pub fn input_mappings() -> Mapping {
sort(&mut wheel_scroll);
// Sort `pointer_move`
sort(&mut pointer_move);
// Sort `pointer_shake`
sort(&mut pointer_shake);
Mapping {
key_up,
@@ -465,6 +469,7 @@ pub fn input_mappings() -> Mapping {
double_click,
wheel_scroll,
pointer_move,
pointer_shake,
}
}
@@ -1,9 +1,9 @@
use crate::messages::input_mapper::input_mapper_message_handler::InputMapperMessageData;
use crate::messages::input_mapper::input_mapper_message_handler::InputMapperMessageContext;
use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
use crate::messages::prelude::*;
#[derive(ExtractField)]
pub struct KeyMappingMessageData<'a> {
pub struct KeyMappingMessageContext<'a> {
pub input: &'a InputPreprocessorMessageHandler,
pub actions: ActionList,
}
@@ -14,12 +14,12 @@ pub struct KeyMappingMessageHandler {
}
#[message_handler_data]
impl MessageHandler<KeyMappingMessage, KeyMappingMessageData<'_>> for KeyMappingMessageHandler {
fn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque<Message>, data: KeyMappingMessageData) {
let KeyMappingMessageData { input, actions } = data;
impl MessageHandler<KeyMappingMessage, KeyMappingMessageContext<'_>> for KeyMappingMessageHandler {
fn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque<Message>, context: KeyMappingMessageContext) {
let KeyMappingMessageContext { input, actions } = context;
match message {
KeyMappingMessage::Lookup(input_message) => self.mapping_handler.process_message(input_message, responses, InputMapperMessageData { input, actions }),
KeyMappingMessage::Lookup(input_message) => self.mapping_handler.process_message(input_message, responses, InputMapperMessageContext { input, actions }),
KeyMappingMessage::ModifyMapping(new_layout) => self.mapping_handler.set_mapping(new_layout.into()),
}
}
@@ -4,4 +4,4 @@ mod key_mapping_message_handler;
#[doc(inline)]
pub use key_mapping_message::{KeyMappingMessage, KeyMappingMessageDiscriminant, MappingVariant, MappingVariantDiscriminant};
#[doc(inline)]
pub use key_mapping_message_handler::{KeyMappingMessageData, KeyMappingMessageHandler};
pub use key_mapping_message_handler::{KeyMappingMessageContext, KeyMappingMessageHandler};
+1 -1
View File
@@ -8,4 +8,4 @@ pub mod utility_types;
#[doc(inline)]
pub use input_mapper_message::{InputMapperMessage, InputMapperMessageDiscriminant};
#[doc(inline)]
pub use input_mapper_message_handler::{InputMapperMessageData, InputMapperMessageHandler};
pub use input_mapper_message_handler::{InputMapperMessageContext, InputMapperMessageHandler};
@@ -90,6 +90,7 @@ macro_rules! mapping {
let mut double_click = KeyMappingEntries::mouse_buttons_arrays();
let mut wheel_scroll = KeyMappingEntries::new();
let mut pointer_move = KeyMappingEntries::new();
let mut pointer_shake = KeyMappingEntries::new();
$(
// Each of the many entry slices, one specified per action
@@ -104,6 +105,7 @@ macro_rules! mapping {
InputMapperMessage::DoubleClick(key) => &mut double_click[key as usize],
InputMapperMessage::WheelScroll => &mut wheel_scroll,
InputMapperMessage::PointerMove => &mut pointer_move,
InputMapperMessage::PointerShake => &mut pointer_shake,
};
// Push each entry to the corresponding `KeyMappingEntries` list for its input type
corresponding_list.push(entry.clone());
@@ -111,7 +113,7 @@ macro_rules! mapping {
}
)*
(key_up, key_down, key_up_no_repeat, key_down_no_repeat, double_click, wheel_scroll, pointer_move)
(key_up, key_down, key_up_no_repeat, key_down_no_repeat, double_click, wheel_scroll, pointer_move, pointer_shake)
}};
}
@@ -14,6 +14,7 @@ pub struct Mapping {
pub double_click: [KeyMappingEntries; NUMBER_OF_MOUSE_BUTTONS],
pub wheel_scroll: KeyMappingEntries,
pub pointer_move: KeyMappingEntries,
pub pointer_shake: KeyMappingEntries,
}
impl Default for Mapping {
@@ -47,6 +48,7 @@ impl Mapping {
InputMapperMessage::DoubleClick(key) => &self.double_click[*key as usize],
InputMapperMessage::WheelScroll => &self.wheel_scroll,
InputMapperMessage::PointerMove => &self.pointer_move,
InputMapperMessage::PointerShake => &self.pointer_shake,
}
}
@@ -59,6 +61,7 @@ impl Mapping {
InputMapperMessage::DoubleClick(key) => &mut self.double_click[*key as usize],
InputMapperMessage::WheelScroll => &mut self.wheel_scroll,
InputMapperMessage::PointerMove => &mut self.pointer_move,
InputMapperMessage::PointerShake => &mut self.pointer_shake,
}
}
}
@@ -12,6 +12,7 @@ pub enum InputPreprocessorMessage {
PointerDown { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
PointerMove { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
PointerUp { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
PointerShake { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
CurrentTime { timestamp: u64 },
WheelScroll { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
}
@@ -7,7 +7,7 @@ use glam::DVec2;
use std::time::Duration;
#[derive(ExtractField)]
pub struct InputPreprocessorMessageData {
pub struct InputPreprocessorMessageContext {
pub keyboard_platform: KeyboardPlatformLayout,
}
@@ -21,9 +21,9 @@ pub struct InputPreprocessorMessageHandler {
}
#[message_handler_data]
impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageData> for InputPreprocessorMessageHandler {
fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque<Message>, data: InputPreprocessorMessageData) {
let InputPreprocessorMessageData { keyboard_platform } = data;
impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageContext> for InputPreprocessorMessageHandler {
fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque<Message>, context: InputPreprocessorMessageContext) {
let InputPreprocessorMessageContext { keyboard_platform } = context;
match message {
InputPreprocessorMessage::BoundsOfViewports { bounds_of_viewports } => {
@@ -97,8 +97,16 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageData> for
self.translate_mouse_event(mouse_state, false, responses);
}
InputPreprocessorMessage::PointerShake { editor_mouse_state, modifier_keys } => {
self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses);
let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds);
self.mouse.position = mouse_state.position;
responses.add(InputMapperMessage::PointerShake);
}
InputPreprocessorMessage::CurrentTime { timestamp } => {
responses.add(AnimationMessage::SetTime(timestamp as f64));
responses.add(AnimationMessage::SetTime { time: timestamp as f64 });
self.time = timestamp;
self.frame_time.advance_timestamp(Duration::from_millis(timestamp));
}
@@ -214,10 +222,10 @@ mod test {
let mut responses = VecDeque::new();
let data = InputPreprocessorMessageData {
let context = InputPreprocessorMessageContext {
keyboard_platform: KeyboardPlatformLayout::Standard,
};
input_preprocessor.process_message(message, &mut responses, data);
input_preprocessor.process_message(message, &mut responses, context);
assert!(input_preprocessor.keyboard.get(Key::Alt as usize));
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Alt).into()));
@@ -233,10 +241,10 @@ mod test {
let mut responses = VecDeque::new();
let data = InputPreprocessorMessageData {
let context = InputPreprocessorMessageContext {
keyboard_platform: KeyboardPlatformLayout::Standard,
};
input_preprocessor.process_message(message, &mut responses, data);
input_preprocessor.process_message(message, &mut responses, context);
assert!(input_preprocessor.keyboard.get(Key::Control as usize));
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Control).into()));
@@ -252,10 +260,10 @@ mod test {
let mut responses = VecDeque::new();
let data = InputPreprocessorMessageData {
let context = InputPreprocessorMessageContext {
keyboard_platform: KeyboardPlatformLayout::Standard,
};
input_preprocessor.process_message(message, &mut responses, data);
input_preprocessor.process_message(message, &mut responses, context);
assert!(input_preprocessor.keyboard.get(Key::Shift as usize));
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Shift).into()));
@@ -273,10 +281,10 @@ mod test {
let mut responses = VecDeque::new();
let data = InputPreprocessorMessageData {
let context = InputPreprocessorMessageContext {
keyboard_platform: KeyboardPlatformLayout::Standard,
};
input_preprocessor.process_message(message, &mut responses, data);
input_preprocessor.process_message(message, &mut responses, context);
assert!(!input_preprocessor.keyboard.get(Key::Control as usize));
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyUp(Key::Control).into()));
@@ -293,10 +301,10 @@ mod test {
let mut responses = VecDeque::new();
let data = InputPreprocessorMessageData {
let context = InputPreprocessorMessageContext {
keyboard_platform: KeyboardPlatformLayout::Standard,
};
input_preprocessor.process_message(message, &mut responses, data);
input_preprocessor.process_message(message, &mut responses, context);
assert!(input_preprocessor.keyboard.get(Key::Control as usize));
assert!(input_preprocessor.keyboard.get(Key::Shift as usize));
@@ -4,4 +4,4 @@ mod input_preprocessor_message_handler;
#[doc(inline)]
pub use input_preprocessor_message::{InputPreprocessorMessage, InputPreprocessorMessageDiscriminant};
#[doc(inline)]
pub use input_preprocessor_message_handler::{InputPreprocessorMessageData, InputPreprocessorMessageHandler};
pub use input_preprocessor_message_handler::{InputPreprocessorMessageContext, InputPreprocessorMessageHandler};
@@ -6,14 +6,53 @@ use graphene_std::text::Font;
use graphene_std::vector::style::{FillChoice, GradientStops};
use serde_json::Value;
#[derive(ExtractField)]
pub struct LayoutMessageContext<'a> {
pub action_input_mapping: &'a dyn Fn(&MessageDiscriminant) -> Option<KeysGroup>,
}
#[derive(Debug, Clone, Default, ExtractField)]
pub struct LayoutMessageHandler {
layouts: [Layout; LayoutTarget::LayoutTargetLength as usize],
}
enum WidgetValueAction {
Commit,
Update,
#[message_handler_data]
impl MessageHandler<LayoutMessage, LayoutMessageContext<'_>> for LayoutMessageHandler {
fn process_message(&mut self, message: LayoutMessage, responses: &mut std::collections::VecDeque<Message>, context: LayoutMessageContext) {
let action_input_mapping = &context.action_input_mapping;
match message {
LayoutMessage::ResendActiveWidget { layout_target, widget_id } => {
// Find the updated diff based on the specified layout target
let Some(diff) = (match &self.layouts[layout_target as usize] {
Layout::MenuLayout(_) => return,
Layout::WidgetLayout(layout) => Self::get_widget_path(layout, widget_id).map(|(widget, widget_path)| {
// Create a widget update diff for the relevant id
let new_value = DiffUpdate::Widget(widget.clone());
WidgetDiff { widget_path, new_value }
}),
}) else {
return;
};
// Resend that diff
self.send_diff(vec![diff], layout_target, responses, action_input_mapping);
}
LayoutMessage::SendLayout { layout, layout_target } => {
self.diff_and_send_layout_to_frontend(layout_target, layout, responses, action_input_mapping);
}
LayoutMessage::WidgetValueCommit { layout_target, widget_id, value } => {
self.handle_widget_callback(layout_target, widget_id, value, WidgetValueAction::Commit, responses);
}
LayoutMessage::WidgetValueUpdate { layout_target, widget_id, value } => {
self.handle_widget_callback(layout_target, widget_id, value, WidgetValueAction::Update, responses);
responses.add(LayoutMessage::ResendActiveWidget { layout_target, widget_id });
}
}
}
fn actions(&self) -> ActionList {
actions!(LayoutMessageDiscriminant;)
}
}
impl LayoutMessageHandler {
@@ -84,7 +123,10 @@ impl LayoutMessageHandler {
let callback_message = match action {
WidgetValueAction::Commit => (breadcrumb_trail_buttons.on_commit.callback)(&()),
WidgetValueAction::Update => {
let update_value = value.as_u64().expect("BreadcrumbTrailButtons update was not of type: u64");
let Some(update_value) = value.as_u64() else {
error!("BreadcrumbTrailButtons update was not of type: u64");
return;
};
(breadcrumb_trail_buttons.on_update.callback)(&update_value)
}
};
@@ -94,7 +136,10 @@ impl LayoutMessageHandler {
let callback_message = match action {
WidgetValueAction::Commit => (checkbox_input.on_commit.callback)(&()),
WidgetValueAction::Update => {
let update_value = value.as_bool().expect("CheckboxInput update was not of type: bool");
let Some(update_value) = value.as_bool() else {
error!("CheckboxInput update was not of type: bool");
return;
};
checkbox_input.checked = update_value;
(checkbox_input.on_update.callback)(checkbox_input)
}
@@ -169,7 +214,10 @@ impl LayoutMessageHandler {
let callback_message = match action {
WidgetValueAction::Commit => (curve_input.on_commit.callback)(&()),
WidgetValueAction::Update => {
let curve = serde_json::from_value(value).expect("CurveInput event data could not be deserialized");
let Some(curve) = serde_json::from_value(value).ok() else {
error!("CurveInput event data could not be deserialized");
return;
};
curve_input.value = curve;
(curve_input.on_update.callback)(curve_input)
}
@@ -180,13 +228,27 @@ impl LayoutMessageHandler {
Widget::DropdownInput(dropdown_input) => {
let callback_message = match action {
WidgetValueAction::Commit => {
let update_value = value.as_u64().unwrap_or_else(|| panic!("DropdownInput commit was not of type `u64`, found {value:?}"));
(dropdown_input.entries.iter().flatten().nth(update_value as usize).unwrap().on_commit.callback)(&())
let Some(update_value) = value.as_u64() else {
error!("DropdownInput commit was not of type `u64`, found {value:?}");
return;
};
let Some(entry) = dropdown_input.entries.iter().flatten().nth(update_value as usize) else {
error!("DropdownInput commit was not able to find entry for index {update_value}");
return;
};
(entry.on_commit.callback)(&())
}
WidgetValueAction::Update => {
let update_value = value.as_u64().unwrap_or_else(|| panic!("DropdownInput update was not of type `u64`, found {value:?}"));
let Some(update_value) = value.as_u64() else {
error!("DropdownInput update was not of type `u64`, found {value:?}");
return;
};
dropdown_input.selected_index = Some(update_value as u32);
(dropdown_input.entries.iter().flatten().nth(update_value as usize).unwrap().on_update.callback)(&())
let Some(entry) = dropdown_input.entries.iter().flatten().nth(update_value as usize) else {
error!("DropdownInput update was not able to find entry for index {update_value}");
return;
};
(entry.on_update.callback)(&())
}
};
@@ -196,12 +258,27 @@ impl LayoutMessageHandler {
let callback_message = match action {
WidgetValueAction::Commit => (font_input.on_commit.callback)(&()),
WidgetValueAction::Update => {
let update_value = value.as_object().expect("FontInput update was not of type: object");
let font_family_value = update_value.get("fontFamily").expect("FontInput update does not have a fontFamily");
let font_style_value = update_value.get("fontStyle").expect("FontInput update does not have a fontStyle");
let Some(update_value) = value.as_object() else {
error!("FontInput update was not of type: object");
return;
};
let Some(font_family_value) = update_value.get("fontFamily") else {
error!("FontInput update does not have a fontFamily");
return;
};
let Some(font_style_value) = update_value.get("fontStyle") else {
error!("FontInput update does not have a fontStyle");
return;
};
let font_family = font_family_value.as_str().expect("FontInput update fontFamily was not of type: string");
let font_style = font_style_value.as_str().expect("FontInput update fontStyle was not of type: string");
let Some(font_family) = font_family_value.as_str() else {
error!("FontInput update fontFamily was not of type: string");
return;
};
let Some(font_style) = font_style_value.as_str() else {
error!("FontInput update fontStyle was not of type: string");
return;
};
font_input.font_family = font_family.into();
font_input.font_style = font_style.into();
@@ -246,7 +323,10 @@ impl LayoutMessageHandler {
responses.add(callback_message);
}
WidgetValueAction::Update => {
let value = value.as_str().expect("NodeCatalog update was not of type String").to_string();
let Some(value) = value.as_str().map(|s| s.to_string()) else {
error!("NodeCatalog update was not of type String");
return;
};
let callback_message = (node_type_input.on_update.callback)(&value);
responses.add(callback_message);
}
@@ -257,8 +337,11 @@ impl LayoutMessageHandler {
responses.add(callback_message);
}
WidgetValueAction::Update => match value {
Value::Number(num) => {
let update_value = num.as_f64().unwrap();
Value::Number(ref num) => {
let Some(update_value) = num.as_f64() else {
error!("NumberInput update was not of type: f64, found {value:?}");
return;
};
number_input.value = Some(update_value);
let callback_message = (number_input.on_update.callback)(number_input);
responses.add(callback_message);
@@ -284,7 +367,10 @@ impl LayoutMessageHandler {
let callback_message = match action {
WidgetValueAction::Commit => (reference_point_input.on_commit.callback)(&()),
WidgetValueAction::Update => {
let update_value = value.as_str().expect("ReferencePointInput update was not of type: u64");
let Some(update_value) = value.as_str() else {
error!("ReferencePointInput update was not of type: u64");
return;
};
reference_point_input.value = update_value.into();
(reference_point_input.on_update.callback)(reference_point_input)
}
@@ -294,7 +380,10 @@ impl LayoutMessageHandler {
}
Widget::PopoverButton(_) => {}
Widget::RadioInput(radio_input) => {
let update_value = value.as_u64().expect("RadioInput update was not of type: u64");
let Some(update_value) = value.as_u64() else {
error!("RadioInput update was not of type: u64");
return;
};
radio_input.selected_index = Some(update_value as u32);
let callback_message = match action {
WidgetValueAction::Commit => (radio_input.entries[update_value as usize].on_commit.callback)(&()),
@@ -308,7 +397,10 @@ impl LayoutMessageHandler {
let callback_message = match action {
WidgetValueAction::Commit => (text_area_input.on_commit.callback)(&()),
WidgetValueAction::Update => {
let update_value = value.as_str().expect("TextAreaInput update was not of type: string");
let Some(update_value) = value.as_str() else {
error!("TextAreaInput update was not of type: string");
return;
};
text_area_input.value = update_value.into();
(text_area_input.on_update.callback)(text_area_input)
}
@@ -328,7 +420,10 @@ impl LayoutMessageHandler {
let callback_message = match action {
WidgetValueAction::Commit => (text_input.on_commit.callback)(&()),
WidgetValueAction::Update => {
let update_value = value.as_str().expect("TextInput update was not of type: string");
let Some(update_value) = value.as_str() else {
error!("TextInput update was not of type: string");
return;
};
text_input.value = update_value.into();
(text_input.on_update.callback)(text_input)
}
@@ -340,54 +435,7 @@ impl LayoutMessageHandler {
Widget::WorkingColorsInput(_) => {}
};
}
}
pub fn custom_data() -> MessageData {
// TODO: When <https://github.com/dtolnay/proc-macro2/issues/503> is resolved and released,
// TODO: use <https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.line> 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<KeysGroup>"), 350)], file!())
}
#[message_handler_data(CustomData)]
impl<F: Fn(&MessageDiscriminant) -> Option<KeysGroup>> MessageHandler<LayoutMessage, F> for LayoutMessageHandler {
fn process_message(&mut self, message: LayoutMessage, responses: &mut std::collections::VecDeque<Message>, action_input_mapping: F) {
match message {
LayoutMessage::ResendActiveWidget { layout_target, widget_id } => {
// Find the updated diff based on the specified layout target
let Some(diff) = (match &self.layouts[layout_target as usize] {
Layout::MenuLayout(_) => return,
Layout::WidgetLayout(layout) => Self::get_widget_path(layout, widget_id).map(|(widget, widget_path)| {
// Create a widget update diff for the relevant id
let new_value = DiffUpdate::Widget(widget.clone());
WidgetDiff { widget_path, new_value }
}),
}) else {
return;
};
// Resend that diff
self.send_diff(vec![diff], layout_target, responses, &action_input_mapping);
}
LayoutMessage::SendLayout { layout, layout_target } => {
self.diff_and_send_layout_to_frontend(layout_target, layout, responses, &action_input_mapping);
}
LayoutMessage::WidgetValueCommit { layout_target, widget_id, value } => {
self.handle_widget_callback(layout_target, widget_id, value, WidgetValueAction::Commit, responses);
}
LayoutMessage::WidgetValueUpdate { layout_target, widget_id, value } => {
self.handle_widget_callback(layout_target, widget_id, value, WidgetValueAction::Update, responses);
responses.add(LayoutMessage::ResendActiveWidget { layout_target, widget_id });
}
}
}
fn actions(&self) -> ActionList {
actions!(LayoutMessageDiscriminant;)
}
}
impl LayoutMessageHandler {
/// Diff the update and send to the frontend where necessary
fn diff_and_send_layout_to_frontend(
&mut self,
@@ -419,10 +467,11 @@ impl LayoutMessageHandler {
self.layouts[layout_target as usize] = new_layout;
// Update the UI
responses.add(FrontendMessage::UpdateMenuBarLayout {
layout_target,
layout: self.layouts[layout_target as usize].clone().unwrap_menu_layout(action_input_mapping).layout,
});
let Some(layout) = self.layouts[layout_target as usize].clone().as_menu_layout(action_input_mapping).map(|x| x.layout) else {
error!("Called unwrap_menu_layout on a widget layout");
return;
};
responses.add(FrontendMessage::UpdateMenuBarLayout { layout_target, layout });
}
}
}
@@ -453,3 +502,8 @@ impl LayoutMessageHandler {
responses.add(message);
}
}
enum WidgetValueAction {
Commit,
Update,
}
+1 -1
View File
@@ -1,5 +1,5 @@
mod layout_message;
mod layout_message_handler;
pub mod layout_message_handler;
pub mod utility_types;
@@ -109,14 +109,14 @@ pub enum Layout {
}
impl Layout {
pub fn unwrap_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) -> MenuLayout {
pub fn as_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) -> Option<MenuLayout> {
if let Self::MenuLayout(mut menu) = self {
menu.layout
.iter_mut()
.for_each(|menu_column| menu_column.children.fill_in_shortcut_actions_with_keys(action_input_mapping));
menu
Some(menu)
} else {
panic!("Called unwrap_menu_layout on a widget layout");
None
}
}
@@ -332,6 +332,9 @@ pub enum NumberInputMode {
pub struct NodeCatalog {
pub disabled: bool,
#[serde(rename = "initialSearchTerm")]
pub intial_search: String,
// Callbacks
#[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")]
+12 -6
View File
@@ -1,15 +1,11 @@
use crate::messages::prelude::*;
use graphene_std::renderer::RenderMetadata;
use graphite_proc_macros::*;
#[impl_message]
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Message {
NoOp,
Init,
Batched(Box<[Message]>),
StartBuffer,
EndBuffer(graphene_std::renderer::RenderMetadata),
// Sub-messages
#[child]
Animation(AnimationMessage),
#[child]
@@ -36,6 +32,16 @@ pub enum Message {
Tool(ToolMessage),
#[child]
Workspace(WorkspaceMessage),
// Messages
NoOp,
Batched {
messages: Box<[Message]>,
},
StartBuffer,
EndBuffer {
render_metadata: RenderMetadata,
},
}
/// Provides an impl of `specta::Type` for `MessageDiscriminant`, the struct created by `impl_message`.
@@ -10,10 +10,10 @@ use crate::consts::{ASYMPTOTIC_EFFECT, COLOR_OVERLAY_GRAY, DEFAULT_DOCUMENT_NAME
use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::node_graph::NodeGraphHandlerData;
use crate::messages::portfolio::document::node_graph::NodeGraphMessageContext;
use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options};
use crate::messages::portfolio::document::overlays::utility_types::{OverlaysType, OverlaysVisibilitySettings};
use crate::messages::portfolio::document::properties_panel::utility_types::PropertiesPanelMessageHandlerData;
use crate::messages::portfolio::document::properties_panel::properties_panel_message_handler::PropertiesPanelMessageContext;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ};
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate};
@@ -39,7 +39,7 @@ use graphene_std::vector::style::ViewMode;
use std::time::Duration;
#[derive(ExtractField)]
pub struct DocumentMessageData<'a> {
pub struct DocumentMessageContext<'a> {
pub document_id: DocumentId,
pub ipp: &'a InputPreprocessorMessageHandler,
pub persistent_data: &'a PersistentData,
@@ -170,9 +170,9 @@ impl Default for DocumentMessageHandler {
}
#[message_handler_data]
impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessageHandler {
fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque<Message>, data: DocumentMessageData) {
let DocumentMessageData {
impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMessageHandler {
fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque<Message>, context: DocumentMessageContext) {
let DocumentMessageContext {
document_id,
ipp,
persistent_data,
@@ -180,28 +180,21 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
current_tool,
preferences,
device_pixel_ratio,
} = data;
} = context;
let selected_nodes_bounding_box_viewport = self.network_interface.selected_nodes_bounding_box_viewport(&self.breadcrumb_network_path);
let selected_visible_layers_bounding_box_viewport = self.selected_visible_layers_bounding_box_viewport();
match message {
// Sub-messages
DocumentMessage::Navigation(message) => {
let data = NavigationMessageData {
let context = NavigationMessageContext {
network_interface: &mut self.network_interface,
breadcrumb_network_path: &self.breadcrumb_network_path,
ipp,
selection_bounds: if self.graph_view_overlay_open {
selected_nodes_bounding_box_viewport
} else {
selected_visible_layers_bounding_box_viewport
},
document_ptz: &mut self.document_ptz,
graph_view_overlay_open: self.graph_view_overlay_open,
preferences,
};
self.navigation_handler.process_message(message, responses, data);
self.navigation_handler.process_message(message, responses, context);
}
DocumentMessage::Overlays(message) => {
let visibility_settings = self.overlays_visibility_settings;
@@ -210,7 +203,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
self.overlays_message_handler.process_message(
message,
responses,
OverlaysMessageData {
OverlaysMessageContext {
visibility_settings,
ipp,
device_pixel_ratio,
@@ -218,20 +211,20 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
);
}
DocumentMessage::PropertiesPanel(message) => {
let properties_panel_message_handler_data = PropertiesPanelMessageHandlerData {
let context = PropertiesPanelMessageContext {
network_interface: &mut self.network_interface,
selection_network_path: &self.selection_network_path,
document_name: self.name.as_str(),
executor,
persistent_data,
};
self.properties_panel_message_handler
.process_message(message, responses, (persistent_data, properties_panel_message_handler_data));
self.properties_panel_message_handler.process_message(message, responses, context);
}
DocumentMessage::NodeGraph(message) => {
self.node_graph_handler.process_message(
message,
responses,
NodeGraphHandlerData {
NodeGraphMessageContext {
network_interface: &mut self.network_interface,
selection_network_path: &self.selection_network_path,
breadcrumb_network_path: &self.breadcrumb_network_path,
@@ -246,20 +239,20 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
);
}
DocumentMessage::GraphOperation(message) => {
let data = GraphOperationMessageData {
let context = GraphOperationMessageContext {
network_interface: &mut self.network_interface,
collapsed: &mut self.collapsed,
node_graph: &mut self.node_graph_handler,
};
let mut graph_operation_message_handler = GraphOperationMessageHandler {};
graph_operation_message_handler.process_message(message, responses, data);
graph_operation_message_handler.process_message(message, responses, context);
}
DocumentMessage::AlignSelectedLayers { axis, aggregate } => {
let axis = match axis {
AlignAxis::X => DVec2::X,
AlignAxis::Y => DVec2::Y,
};
let Some(combined_box) = self.selected_visible_layers_bounding_box_viewport() else {
let Some(combined_box) = self.network_interface.selected_layers_artwork_bounding_box_viewport() else {
return;
};
@@ -486,7 +479,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
FlipAxis::X => DVec2::new(-1., 1.),
FlipAxis::Y => DVec2::new(1., -1.),
};
if let Some([min, max]) = self.selected_visible_and_unlock_layers_bounding_box_viewport() {
if let Some([min, max]) = self.network_interface.selected_unlocked_layers_bounding_box_viewport() {
let center = (max + min) / 2.;
let bbox_trans = DAffine2::from_translation(-center);
let mut added_transaction = false;
@@ -506,7 +499,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
}
DocumentMessage::RotateSelectedLayers { degrees } => {
// Get the bounding box of selected layers in viewport space
if let Some([min, max]) = self.selected_visible_and_unlock_layers_bounding_box_viewport() {
if let Some([min, max]) = self.network_interface.selected_unlocked_layers_bounding_box_viewport() {
// Calculate the center of the bounding box to use as rotation pivot
let center = (max + min) / 2.;
// Transform that moves pivot point to origin
@@ -1063,13 +1056,13 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
self.selected_layers_reorder(relative_index_offset, responses);
}
DocumentMessage::ClipLayer { id } => {
let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]);
let layer = LayerNodeIdentifier::new(id, &self.network_interface);
responses.add(DocumentMessage::AddTransaction);
responses.add(GraphOperationMessage::ClipModeToggle { layer });
}
DocumentMessage::SelectLayer { id, ctrl, shift } => {
let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]);
let layer = LayerNodeIdentifier::new(id, &self.network_interface);
let mut nodes = vec![];
@@ -1266,7 +1259,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
responses.add(OverlaysMessage::Draw);
}
DocumentMessage::ToggleLayerExpansion { id, recursive } => {
let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]);
let layer = LayerNodeIdentifier::new(id, &self.network_interface);
let metadata = self.metadata();
let is_collapsed = self.collapsed.0.contains(&layer);
@@ -1323,7 +1316,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
self.network_interface.document_network().nodes.contains_key(node_id))
.filter_map(|(node_id, click_targets)| {
self.network_interface.is_layer(&node_id, &[]).then(|| {
let layer = LayerNodeIdentifier::new(node_id, &self.network_interface, &[]);
let layer = LayerNodeIdentifier::new(node_id, &self.network_interface);
(layer, click_targets)
})
})
@@ -1683,6 +1676,11 @@ impl DocumentMessageHandler {
})
}
pub fn click_list_no_parents<'a>(&'a self, ipp: &InputPreprocessorMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
self.click_xray(ipp)
.filter(move |&layer| !self.network_interface.is_artboard(&layer.to_node(), &[]) && !layer.has_children(self.network_interface.document_metadata()))
}
/// Find the deepest layer that has been clicked on from a location in viewport space.
pub fn click(&self, ipp: &InputPreprocessorMessageHandler) -> Option<LayerNodeIdentifier> {
self.click_list(ipp).last()
@@ -1703,31 +1701,6 @@ impl DocumentMessageHandler {
.last()
}
/// Get the combined bounding box of the click targets of the selected visible layers in viewport space
pub fn selected_visible_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
self.network_interface
.selected_nodes()
.selected_visible_layers(&self.network_interface)
.filter_map(|layer| self.metadata().bounding_box_viewport(layer))
.reduce(graphene_std::renderer::Quad::combine_bounds)
}
pub fn selected_visible_and_unlock_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
self.network_interface
.selected_nodes()
.selected_visible_and_unlocked_layers(&self.network_interface)
.filter_map(|layer| self.metadata().bounding_box_viewport(layer))
.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()
}
@@ -2736,7 +2709,22 @@ impl DocumentMessageHandler {
.tooltip("Add an operation to the end of this layer's chain of nodes")
.disabled(!has_selection || has_multiple_selection)
.popover_layout({
let node_chooser = NodeCatalog::new()
// Showing only compatible types
let compatible_type = selected_layer.and_then(|layer| {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &self.network_interface);
let node_type = graph_layer.horizontal_layer_flow().nth(1);
if let Some(node_id) = node_type {
let (output_type, _) = self.network_interface.output_type(&node_id, 0, &self.selection_network_path);
Some(format!("type:{}", output_type.nested_type()))
} else {
None
}
});
let mut node_chooser = NodeCatalog::new();
node_chooser.intial_search = compatible_type.unwrap_or("".to_string());
let node_chooser = node_chooser
.on_update(move |node_type| {
if let Some(layer) = selected_layer {
NodeGraphMessage::CreateNodeInLayerWithTransaction {
@@ -14,15 +14,8 @@ use graphene_std::renderer::convert_usvg_path::convert_usvg_path;
use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::style::{Fill, Gradient, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
#[derive(Debug, Clone)]
struct ArtboardInfo {
input_node: NodeInput,
output_nodes: Vec<InputConnector>,
merge_node: NodeId,
}
#[derive(ExtractField)]
pub struct GraphOperationMessageData<'a> {
pub struct GraphOperationMessageContext<'a> {
pub network_interface: &'a mut NodeNetworkInterface,
pub collapsed: &'a mut CollapsedLayers,
pub node_graph: &'a mut NodeGraphMessageHandler,
@@ -34,9 +27,9 @@ 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<GraphOperationMessage, GraphOperationMessageData<'_>> for GraphOperationMessageHandler {
fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, data: GraphOperationMessageData) {
let network_interface = data.network_interface;
impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for GraphOperationMessageHandler {
fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, context: GraphOperationMessageContext) {
let network_interface = context.network_interface;
match message {
GraphOperationMessage::FillSet { layer, fill } => {
@@ -126,7 +119,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
let primary_input = artboard.inputs.first().expect("Artboard should have a primary input").clone();
if let NodeInput::Node { node_id, .. } = &primary_input {
if network_interface.is_layer(node_id, &[]) && !network_interface.is_artboard(node_id, &[]) {
network_interface.move_layer_to_stack(LayerNodeIdentifier::new(*node_id, network_interface, &[]), artboard_layer, 0, &[]);
network_interface.move_layer_to_stack(LayerNodeIdentifier::new(*node_id, network_interface), artboard_layer, 0, &[]);
} else {
network_interface.disconnect_input(&InputConnector::node(artboard_layer.to_node(), 0), &[]);
network_interface.set_input(&InputConnector::node(id, 0), primary_input, &[]);
@@ -323,6 +316,13 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
}
}
#[derive(Debug, Clone)]
struct ArtboardInfo {
input_node: NodeInput,
output_nodes: Vec<InputConnector>,
merge_node: NodeId,
}
fn usvg_color(c: usvg::Color, a: f32) -> Color {
Color::from_rgbaf32_unchecked(c.red as f32 / 255., c.green as f32 / 255., c.blue as f32 / 255., a)
}
@@ -97,7 +97,7 @@ impl<'a> ModifyInputsContext<'a> {
};
}
let layer_input_connector = post_node_input_connector.clone();
let layer_input_connector = post_node_input_connector;
// 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 {
@@ -124,7 +124,7 @@ impl<'a> ModifyInputsContext<'a> {
pub fn create_layer(&mut self, new_id: NodeId) -> LayerNodeIdentifier {
let new_merge_node = resolve_document_node_type("Merge").expect("Merge node").default_node_template();
self.network_interface.insert_node(new_id, new_merge_node, &[]);
LayerNodeIdentifier::new(new_id, self.network_interface, &[])
LayerNodeIdentifier::new(new_id, self.network_interface)
}
/// Creates an artboard as the primary export for the document network
@@ -132,13 +132,13 @@ impl<'a> ModifyInputsContext<'a> {
let artboard_node_template = resolve_document_node_type("Artboard").expect("Node").node_template_input_override([
Some(NodeInput::value(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroupTable::default()), true)),
Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_std::GraphicGroupTable::default()), true)),
Some(NodeInput::value(TaggedValue::IVec2(artboard.location), false)),
Some(NodeInput::value(TaggedValue::IVec2(artboard.dimensions), false)),
Some(NodeInput::value(TaggedValue::DVec2(artboard.location.into()), false)),
Some(NodeInput::value(TaggedValue::DVec2(artboard.dimensions.into()), false)),
Some(NodeInput::value(TaggedValue::Color(artboard.background), false)),
Some(NodeInput::value(TaggedValue::Bool(artboard.clip), false)),
]);
self.network_interface.insert_node(new_id, artboard_node_template, &[]);
LayerNodeIdentifier::new(new_id, self.network_interface, &[])
LayerNodeIdentifier::new(new_id, self.network_interface)
}
pub fn insert_boolean_data(&mut self, operation: graphene_std::path_bool::BooleanOperation, layer: LayerNodeIdentifier) {
@@ -236,7 +236,7 @@ impl<'a> ModifyInputsContext<'a> {
self.layer_node.or_else(|| {
let export_node = self.network_interface.document_network().exports.first().and_then(|export| export.as_node())?;
if self.network_interface.is_layer(&export_node, &[]) {
Some(LayerNodeIdentifier::new(export_node, self.network_interface, &[]))
Some(LayerNodeIdentifier::new(export_node, self.network_interface))
} else {
None
}
@@ -486,8 +486,8 @@ impl<'a> ModifyInputsContext<'a> {
dimensions.y *= -1;
location.y -= dimensions.y;
}
self.set_input_with_refresh(InputConnector::node(artboard_node_id, 2), NodeInput::value(TaggedValue::IVec2(location), false), false);
self.set_input_with_refresh(InputConnector::node(artboard_node_id, 3), NodeInput::value(TaggedValue::IVec2(dimensions), false), false);
self.set_input_with_refresh(InputConnector::node(artboard_node_id, 2), NodeInput::value(TaggedValue::DVec2(location.into()), false), false);
self.set_input_with_refresh(InputConnector::node(artboard_node_id, 3), NodeInput::value(TaggedValue::DVec2(dimensions.into()), false), false);
}
/// Set the input, refresh the properties panel, and run the document graph if skip_rerender is false
@@ -11,4 +11,4 @@ pub mod utility_types;
#[doc(inline)]
pub use document_message::{DocumentMessage, DocumentMessageDiscriminant};
#[doc(inline)]
pub use document_message_handler::{DocumentMessageData, DocumentMessageHandler};
pub use document_message_handler::{DocumentMessageContext, DocumentMessageHandler};
@@ -5,4 +5,4 @@ pub mod utility_types;
#[doc(inline)]
pub use navigation_message::{NavigationMessage, NavigationMessageDiscriminant};
#[doc(inline)]
pub use navigation_message_handler::{NavigationMessageData, NavigationMessageHandler};
pub use navigation_message_handler::{NavigationMessageContext, NavigationMessageHandler};
@@ -14,11 +14,10 @@ use glam::{DAffine2, DVec2};
use graph_craft::document::NodeId;
#[derive(ExtractField)]
pub struct NavigationMessageData<'a> {
pub struct NavigationMessageContext<'a> {
pub network_interface: &'a mut NodeNetworkInterface,
pub breadcrumb_network_path: &'a [NodeId],
pub ipp: &'a InputPreprocessorMessageHandler,
pub selection_bounds: Option<[DVec2; 2]>,
pub document_ptz: &'a mut PTZ,
pub graph_view_overlay_open: bool,
pub preferences: &'a PreferencesMessageHandler,
@@ -33,17 +32,16 @@ pub struct NavigationMessageHandler {
}
#[message_handler_data]
impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for NavigationMessageHandler {
fn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque<Message>, data: NavigationMessageData) {
let NavigationMessageData {
impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for NavigationMessageHandler {
fn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque<Message>, context: NavigationMessageContext) {
let NavigationMessageContext {
network_interface,
breadcrumb_network_path,
ipp,
selection_bounds,
document_ptz,
graph_view_overlay_open,
preferences,
} = data;
} = context;
fn get_ptz<'a>(document_ptz: &'a PTZ, network_interface: &'a NodeNetworkInterface, graph_view_overlay_open: bool, breadcrumb_network_path: &[NodeId]) -> Option<&'a PTZ> {
if !graph_view_overlay_open {
@@ -386,9 +384,16 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
responses.add(DocumentMessage::PTZUpdate);
responses.add(NodeGraphMessage::SetGridAlignedEdges);
}
// Fully zooms in on the selected
NavigationMessage::FitViewportToSelection => {
let selection_bounds = if graph_view_overlay_open {
network_interface.selected_nodes_bounding_box_viewport(breadcrumb_network_path)
} else {
network_interface.selected_layers_artwork_bounding_box_viewport()
};
if let Some(bounds) = selection_bounds {
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
log::error!("Could not get node graph PTZ in FitViewportToSelection");
return;
};
@@ -378,8 +378,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
inputs: vec![
NodeInput::value(TaggedValue::ArtboardGroup(ArtboardGroupTable::default()), true),
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
NodeInput::value(TaggedValue::IVec2(glam::IVec2::ZERO), false),
NodeInput::value(TaggedValue::IVec2(glam::IVec2::new(1920, 1080)), false),
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
NodeInput::value(TaggedValue::DVec2(DVec2::new(1920., 1080.)), false),
NodeInput::value(TaggedValue::Color(Color::WHITE), false),
NodeInput::value(TaggedValue::Bool(false), false),
],
@@ -396,6 +396,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
x: "X".to_string(),
y: "Y".to_string(),
unit: " px".to_string(),
is_integer: true,
..Default::default()
}),
),
@@ -406,6 +407,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
x: "W".to_string(),
y: "H".to_string(),
unit: " px".to_string(),
is_integer: true,
..Default::default()
}),
),
@@ -1222,6 +1224,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false),
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false),
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false),
NodeInput::value(TaggedValue::Bool(false), false),
],
..Default::default()
},
@@ -1281,7 +1284,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
),
InputMetadata::with_name_description_override(
"Tilt",
"Faux italic",
"Faux italic.",
WidgetOverride::Number(NumberInputSettings {
min: Some(-85.),
max: Some(85.),
@@ -1289,6 +1292,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
),
("Per-Glyph Instances", "Splits each text glyph into its own instance, i.e. row in the table of vector data.").into(),
],
output_names: vec!["Vector".to_string()],
..Default::default()
@@ -1299,11 +1303,11 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
},
DocumentNodeDefinition {
identifier: "Transform",
category: "General",
category: "Math: Transform",
node_template: NodeTemplate {
document_node: DocumentNode {
inputs: vec![
NodeInput::value(TaggedValue::VectorData(VectorDataTable::default()), true),
NodeInput::value(TaggedValue::DAffine2(DAffine2::default()), true),
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),
@@ -1313,7 +1317,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)],
inputs: vec![NodeInput::network(generic!(T), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
manual_composition: Some(generic!(T)),
skip_deduplication: true,
@@ -1370,7 +1374,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
input_metadata: vec![
("Vector Data", "TODO").into(),
("Value", "TODO").into(),
InputMetadata::with_name_description_override(
"Translation",
"TODO",
@@ -1951,8 +1955,20 @@ fn static_input_properties() -> InputProperties {
.network_interface
.input_data(&node_id, index, "min", context.selection_network_path)
.and_then(|value| value.as_f64());
let is_integer = context
.network_interface
.input_data(&node_id, index, "is_integer", context.selection_network_path)
.and_then(|value| value.as_bool())
.unwrap_or_default();
Ok(vec![node_properties::coordinate_widget(ParameterWidgetsInfo::new(node_id, index, true, context), &x, &y, &unit, min)])
Ok(vec![node_properties::coordinate_widget(
ParameterWidgetsInfo::new(node_id, index, true, context),
&x,
&y,
&unit,
min,
is_integer,
)])
}),
);
map.insert(
@@ -16,6 +16,7 @@ pub enum NodeGraphMessage {
nodes: Vec<(NodeId, NodeTemplate)>,
new_ids: HashMap<NodeId, NodeId>,
},
AddPathNode,
AddImport,
AddExport,
Init,
@@ -81,6 +82,9 @@ pub enum NodeGraphMessage {
node_id: NodeId,
parent: LayerNodeIdentifier,
},
SetChainPosition {
node_id: NodeId,
},
PasteNodes {
serialized_nodes: String,
},
@@ -97,6 +101,7 @@ pub enum NodeGraphMessage {
PointerOutsideViewport {
shift: Key,
},
ShakeNode,
RemoveImport {
import_index: usize,
},
@@ -10,25 +10,29 @@ use crate::messages::portfolio::document::node_graph::utility_types::{ContextMen
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::misc::GroupFolderType;
use crate::messages::portfolio::document::utility_types::network_interface::{
self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource,
self, FlowType, 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;
use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_clip_mode};
use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion};
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use bezier_rs::Subpath;
use glam::{DAffine2, DVec2, IVec2};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
use graph_craft::proto::GraphErrors;
use graphene_std::math::math_ext::QuadExt;
use graphene_std::vector::misc::subpath_to_kurbo_bezpath;
use graphene_std::*;
use kurbo::{Line, Point};
use renderer::Quad;
use std::cmp::Ordering;
#[derive(Debug, ExtractField)]
pub struct NodeGraphHandlerData<'a> {
pub struct NodeGraphMessageContext<'a> {
pub network_interface: &'a mut NodeNetworkInterface,
pub selection_network_path: &'a [NodeId],
pub breadcrumb_network_path: &'a [NodeId],
@@ -55,6 +59,8 @@ pub struct NodeGraphMessageHandler {
/// If dragging the selected nodes, this stores the starting position both in viewport and node graph coordinates,
/// plus a flag indicating if it has been dragged since the mousedown began.
pub drag_start: Option<(DragStart, bool)>,
// Store the selected chain nodes on drag start so they can be reconnected if shaken
pub drag_start_chain_nodes: Vec<NodeId>,
/// If dragging the background to create a box selection, this stores its starting point in node graph coordinates,
/// plus a flag indicating if it has been dragged since the mousedown began.
box_selection_start: Option<(DVec2, bool)>,
@@ -93,9 +99,9 @@ pub struct NodeGraphMessageHandler {
/// 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<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGraphMessageHandler {
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, data: NodeGraphHandlerData<'a>) {
let NodeGraphHandlerData {
impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeGraphMessageHandler {
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, context: NodeGraphMessageContext<'a>) {
let NodeGraphMessageContext {
network_interface,
selection_network_path,
breadcrumb_network_path,
@@ -106,7 +112,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
graph_fade_artwork_percentage,
navigation_handler,
preferences,
} = data;
} = context;
match message {
// TODO: automatically remove broadcast messages.
@@ -119,6 +125,38 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_layer_id] });
}
NodeGraphMessage::AddPathNode => {
let selected_nodes = network_interface.selected_nodes();
let mut selected_layers = selected_nodes.selected_layers(network_interface.document_metadata());
let first_layer = selected_layers.next();
let second_layer = selected_layers.next();
let has_single_selection = first_layer.is_some() && second_layer.is_none();
let compatible_type = first_layer.and_then(|layer| {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &network_interface);
graph_layer.horizontal_layer_flow().nth(1).and_then(|node_id| {
let (output_type, _) = network_interface.output_type(&node_id, 0, &[]);
Some(format!("type:{}", output_type.nested_type()))
})
});
let is_compatible = compatible_type.as_deref() == Some("type:Instances<VectorData>");
if first_layer.is_some() && has_single_selection && is_compatible {
if let Some(layer) = first_layer {
let node_type = "Path".to_string();
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &network_interface);
let is_modifiable = matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_)));
if !is_modifiable {
responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction {
node_type: node_type.clone(),
layer: LayerNodeIdentifier::new_unchecked(layer.to_node()),
});
responses.add(BroadcastEvent::SelectionChanged);
}
}
}
}
NodeGraphMessage::AddImport => {
network_interface.add_import(graph_craft::document::value::TaggedValue::None, true, -1, "", "", breadcrumb_network_path);
responses.add(NodeGraphMessage::SendGraph);
@@ -568,6 +606,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
NodeGraphMessage::MoveNodeToChainStart { node_id, parent } => {
network_interface.move_node_to_chain_start(&node_id, parent, selection_network_path);
}
NodeGraphMessage::SetChainPosition { node_id } => {
network_interface.set_chain_position(&node_id, selection_network_path);
}
NodeGraphMessage::PasteNodes { serialized_nodes } => {
let data = match serde_json::from_str::<Vec<(NodeId, NodeTemplate)>>(&serialized_nodes) {
Ok(d) => d,
@@ -821,6 +862,20 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
};
self.drag_start = Some((drag_start, false));
let selected_chain_nodes = updated_selected
.iter()
.filter(|node_id| network_interface.is_chain(node_id, selection_network_path))
.copied()
.collect::<Vec<_>>();
self.drag_start_chain_nodes = selected_chain_nodes
.iter()
.flat_map(|selected| {
network_interface
.upstream_flow_back_from_nodes(vec![*selected], selection_network_path, FlowType::PrimaryFlow)
.skip(1)
.filter(|node_id| network_interface.is_chain(node_id, selection_network_path))
})
.collect::<Vec<_>>();
self.begin_dragging = true;
self.node_has_moved_in_drag = false;
self.update_node_graph_hints(responses);
@@ -1188,10 +1243,39 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
{
return None;
}
log::debug!("preferences.graph_wire_style: {:?}", preferences.graph_wire_style);
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))
let bbox_rect = kurbo::Rect::new(bounding_box[0].x, bounding_box[0].y, bounding_box[1].x, bounding_box[1].y);
let p1 = DVec2::new(bbox_rect.x0, bbox_rect.y0);
let p2 = DVec2::new(bbox_rect.x1, bbox_rect.y0);
let p3 = DVec2::new(bbox_rect.x1, bbox_rect.y1);
let p4 = DVec2::new(bbox_rect.x0, bbox_rect.y1);
let ps = [p1, p2, p3, p4];
let inside = wire.is_inside_subpath(&Subpath::from_anchors_linear(ps, true), None, None);
let wire = subpath_to_kurbo_bezpath(wire);
let intersect = wire.segments().any(|segment| {
let rect = kurbo::Rect::new(bounding_box[0].x, bounding_box[0].y, bounding_box[1].x, bounding_box[1].y);
let top_line = Line::new(Point::new(rect.x0, rect.y0), Point::new(rect.x1, rect.y0));
let bottom_line = Line::new(Point::new(rect.x0, rect.y1), Point::new(rect.x1, rect.y1));
let left_line = Line::new(Point::new(rect.x0, rect.y0), Point::new(rect.x0, rect.y1));
let right_line = Line::new(Point::new(rect.x1, rect.y0), Point::new(rect.x1, rect.y1));
!segment.intersect_line(top_line).is_empty()
|| !segment.intersect_line(bottom_line).is_empty()
|| !segment.intersect_line(left_line).is_empty()
|| !segment.intersect_line(right_line).is_empty()
});
(intersect || inside).then_some((input, is_stack))
})
.collect::<Vec<_>>();
// Prioritize vertical thick lines and cancel if there are multiple potential wires
let mut node_wires = Vec::new();
let mut stack_wires = Vec::new();
@@ -1270,6 +1354,135 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
self.auto_panning.stop(&messages, responses);
}
}
NodeGraphMessage::ShakeNode => {
let Some(drag_start) = &self.drag_start else {
log::error!("Drag start should be initialized when shaking a node");
return;
};
let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {
return;
};
let viewport_location = ipp.mouse.position;
let point = network_metadata
.persistent_metadata
.navigation_metadata
.node_graph_to_viewport
.inverse()
.transform_point2(viewport_location);
// Collect the distance to move the shaken nodes after the undo
let graph_delta = IVec2::new(((point.x - drag_start.0.start_x) / 24.).round() as i32, ((point.y - drag_start.0.start_y) / 24.).round() as i32);
// Undo to the state of the graph before shaking
responses.add(DocumentMessage::AbortTransaction);
// Add a history step to abort to the state before shaking if right clicked
responses.add(DocumentMessage::StartTransaction);
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
log::error!("Could not get selected nodes in ShakeNode");
return;
};
let mut all_selected_nodes = selected_nodes.0.iter().copied().collect::<HashSet<_>>();
for selected_layer in selected_nodes
.0
.iter()
.filter(|selected_node| network_interface.is_layer(selected_node, selection_network_path))
.copied()
.collect::<Vec<_>>()
{
for sole_dependent in network_interface.upstream_nodes_below_layer(&selected_layer, selection_network_path) {
all_selected_nodes.insert(sole_dependent);
}
}
for selected_node in &all_selected_nodes {
// Handle inputs of selected node
for input_index in 0..network_interface.number_of_inputs(selected_node, selection_network_path) {
let input_connector = InputConnector::node(*selected_node, input_index);
// Only disconnect inputs to non selected nodes
if network_interface
.upstream_output_connector(&input_connector, selection_network_path)
.and_then(|connector| connector.node_id())
.is_some_and(|node_id| !all_selected_nodes.contains(&node_id))
{
responses.add(NodeGraphMessage::DisconnectInput { input_connector });
}
}
let number_of_outputs = network_interface.number_of_outputs(selected_node, selection_network_path);
let first_deselected_upstream_node = network_interface
.upstream_flow_back_from_nodes(vec![*selected_node], selection_network_path, FlowType::PrimaryFlow)
.find(|upstream_node| !all_selected_nodes.contains(upstream_node));
let Some(outward_wires) = network_interface.outward_wires(selection_network_path) else {
log::error!("Could not get output wires in shake input");
continue;
};
// Disconnect output wires to non selected nodes
for output_index in 0..number_of_outputs {
let output_connector = OutputConnector::node(*selected_node, output_index);
if let Some(downstream_connections) = outward_wires.get(&output_connector) {
for &input_connector in downstream_connections {
if input_connector.node_id().is_some_and(|downstream_node| !all_selected_nodes.contains(&downstream_node)) {
responses.add(NodeGraphMessage::DisconnectInput { input_connector });
}
}
}
}
// Handle reconnection
// Find first non selected upstream node by primary flow
if let Some(first_deselected_upstream_node) = first_deselected_upstream_node {
let Some(downstream_connections_to_first_output) = outward_wires.get(&OutputConnector::node(*selected_node, 0)).cloned() else {
log::error!("Could not get downstream_connections_to_first_output in shake node");
return;
};
// Reconnect only if all downstream outputs are not selected
if !downstream_connections_to_first_output
.iter()
.any(|connector| connector.node_id().is_some_and(|node_id| all_selected_nodes.contains(&node_id)))
{
// Find what output on the deselected upstream node to reconnect to
for output_index in 0..network_interface.number_of_outputs(&first_deselected_upstream_node, selection_network_path) {
let output_connector = &OutputConnector::node(first_deselected_upstream_node, output_index);
let Some(outward_wires) = network_interface.outward_wires(selection_network_path) else {
log::error!("Could not get output wires in shake input");
continue;
};
if let Some(inputs) = outward_wires.get(output_connector) {
// This can only run once
if inputs.iter().any(|input_connector| {
input_connector
.node_id()
.is_some_and(|upstream_node| all_selected_nodes.contains(&upstream_node) && input_connector.input_index() == 0)
}) {
// Output index is the output of the deselected upstream node to reconnect to
for downstream_connections_to_first_output in &downstream_connections_to_first_output {
responses.add(NodeGraphMessage::CreateWire {
output_connector: OutputConnector::node(first_deselected_upstream_node, output_index),
input_connector: *downstream_connections_to_first_output,
});
}
}
}
// Set all chain nodes back to chain position
// TODO: Fix
// for chain_node_to_reset in std::mem::take(&mut self.drag_start_chain_nodes) {
// responses.add(NodeGraphMessage::SetChainPosition { node_id: chain_node_to_reset });
// }
}
}
}
}
responses.add(NodeGraphMessage::ShiftSelectedNodesByAmount { graph_delta, rubber_band: false });
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::SendGraph);
}
NodeGraphMessage::RemoveImport { import_index: usize } => {
network_interface.remove_import(usize, selection_network_path);
responses.add(NodeGraphMessage::SendGraph);
@@ -1354,6 +1567,11 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
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);
}
for error in &self.node_graph_errors {
if error.node_path.contains(node_id) {
nodes.push(*node_id);
}
}
}
responses.add(FrontendMessage::UpdateVisibleNodes { nodes });
@@ -1785,6 +2003,12 @@ impl NodeGraphMessageHandler {
));
}
if self.drag_start.is_some() {
common.extend(actions!(NodeGraphMessageDiscriminant;
ShakeNode,
));
}
common
}
@@ -1824,25 +2048,58 @@ impl NodeGraphMessageHandler {
let selection_all_locked = network_interface.selected_nodes().selected_unlocked_layers(network_interface).count() == 0;
let selection_all_visible = selected_nodes.selected_nodes().all(|node_id| network_interface.is_visible(node_id, breadcrumb_network_path));
let mut selected_layers = selected_nodes.selected_layers(network_interface.document_metadata());
let selected_layer = selected_layers.next();
let has_multiple_selection = selected_layers.next().is_some();
let mut widgets = vec![
PopoverButton::new()
.icon(Some("Node".to_string()))
.tooltip("New Node (Right Click)")
.popover_layout({
let node_chooser = NodeCatalog::new()
.on_update(move |node_type| {
let node_id = NodeId::new();
// Showing only compatible types
let compatible_type = match (selection_includes_layers, has_multiple_selection, selected_layer) {
(true, false, Some(layer)) => {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface);
let node_type = graph_layer.horizontal_layer_flow().nth(1);
if let Some(node_id) = node_type {
let (output_type, _) = network_interface.output_type(&node_id, 0, &[]);
Some(format!("type:{}", output_type.nested_type()))
} else {
None
}
}
_ => None,
};
Message::Batched(Box::new([
NodeGraphMessage::CreateNodeFromContextMenu {
node_id: Some(node_id),
let single_layer_selected = selection_includes_layers && !has_multiple_selection;
let mut node_chooser = NodeCatalog::new();
node_chooser.intial_search = compatible_type.unwrap_or("".to_string());
let node_chooser = node_chooser
.on_update(move |node_type| {
if let (true, Some(layer)) = (single_layer_selected, selected_layer) {
NodeGraphMessage::CreateNodeInLayerWithTransaction {
node_type: node_type.clone(),
xy: None,
add_transaction: true,
layer: LayerNodeIdentifier::new_unchecked(layer.to_node()),
}
.into(),
NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(),
]))
.into()
} else {
let node_id = NodeId::new();
Message::Batched {
messages: Box::new([
NodeGraphMessage::CreateNodeFromContextMenu {
node_id: Some(node_id),
node_type: node_type.clone(),
xy: None,
add_transaction: true,
}
.into(),
NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(),
]),
}
}
})
.widget_holder();
vec![LayoutGroup::Row { widgets: vec![node_chooser] }]
@@ -2113,7 +2370,22 @@ impl NodeGraphMessageHandler {
.icon(Some("Node".to_string()))
.tooltip("Add an operation to the end of this layer's chain of nodes")
.popover_layout({
let node_chooser = NodeCatalog::new()
let layer_identifier = LayerNodeIdentifier::new(layer, &context.network_interface);
let compatible_type = {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer_identifier, &context.network_interface);
let node_type = graph_layer.horizontal_layer_flow().nth(1);
if let Some(node_id) = node_type {
let (output_type, _) = context.network_interface.output_type(&node_id, 0, &[]);
Some(format!("type:{}", output_type.nested_type()))
} else {
None
}
};
let mut node_chooser = NodeCatalog::new();
node_chooser.intial_search = compatible_type.unwrap_or("".to_string());
let node_chooser = node_chooser
.on_update(move |node_type| {
NodeGraphMessage::CreateNodeInLayerWithTransaction {
node_type: node_type.clone(),
@@ -2364,19 +2636,19 @@ impl NodeGraphMessageHandler {
let mut ancestors_of_selected = HashSet::new();
let mut descendants_of_selected = HashSet::new();
for selected_layer in &selected_layers {
for ancestor in LayerNodeIdentifier::new(*selected_layer, network_interface, &[]).ancestors(network_interface.document_metadata()) {
for ancestor in LayerNodeIdentifier::new(*selected_layer, network_interface).ancestors(network_interface.document_metadata()) {
if ancestor != LayerNodeIdentifier::ROOT_PARENT && ancestor.to_node() != *selected_layer {
ancestors_of_selected.insert(ancestor.to_node());
}
}
for descendant in LayerNodeIdentifier::new(*selected_layer, network_interface, &[]).descendants(network_interface.document_metadata()) {
for descendant in LayerNodeIdentifier::new(*selected_layer, network_interface).descendants(network_interface.document_metadata()) {
descendants_of_selected.insert(descendant.to_node());
}
}
for (&node_id, node_metadata) in &network_interface.document_network_metadata().persistent_metadata.node_metadata {
if node_metadata.persistent_metadata.is_layer() {
let layer = LayerNodeIdentifier::new(node_id, network_interface, &[]);
let layer = LayerNodeIdentifier::new(node_id, network_interface);
let children_allowed =
// The layer has other layers as children along the secondary input's horizontal flow
@@ -2557,6 +2829,7 @@ impl Default for NodeGraphMessageHandler {
node_has_moved_in_drag: false,
shift_without_push: false,
box_selection_start: None,
drag_start_chain_nodes: Vec::new(),
selection_before_pointer_down: Vec::new(),
disconnecting: None,
initial_disconnecting: false,
@@ -7,7 +7,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::Inpu
use crate::messages::prelude::*;
use choice::enum_choice;
use dyn_any::DynAny;
use glam::{DAffine2, DVec2, IVec2, UVec2};
use glam::{DAffine2, DVec2};
use graph_craft::Type;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
@@ -21,7 +21,7 @@ use graphene_std::raster::{
};
use graphene_std::raster_types::{CPU, GPU, RasterDataTable};
use graphene_std::text::Font;
use graphene_std::transform::{Footprint, ReferencePoint};
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
use graphene_std::vector::VectorDataTable;
use graphene_std::vector::misc::GridType;
use graphene_std::vector::misc::{ArcType, MergeByDistanceAlgorithm};
@@ -59,13 +59,13 @@ pub fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphData
} else {
"Expose this parameter as a node input in the graph"
})
.on_update(move |_parameter| {
Message::Batched(Box::new([NodeGraphMessage::ExposeInput {
.on_update(move |_parameter| Message::Batched {
messages: Box::new([NodeGraphMessage::ExposeInput {
input_connector: InputConnector::node(node_id, index),
set_to_exposed: !exposed,
start_transaction: true,
}
.into()]))
.into()]),
})
.widget_holder()
}
@@ -160,8 +160,7 @@ pub(crate) fn property_from_type(
Some("Fraction") => number_widget(default_info, number_input.mode_range().min(min(0.)).max(max(1.))).into(),
Some("IntegerCount") => number_widget(default_info, number_input.int().min(min(1.))).into(),
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("PixelSize") => coordinate_widget(default_info, "X", "Y", unit.unwrap_or(" px"), None, false),
Some("TextArea") => text_area_widget(default_info).into(),
// For all other types, use TypeId-based matching
@@ -176,9 +175,8 @@ pub(crate) fn property_from_type(
Some(x) if x == TypeId::of::<u64>() => number_widget(default_info, number_input.int().min(min(0.))).into(),
Some(x) if x == TypeId::of::<bool>() => bool_widget(default_info, CheckboxInput::default()).into(),
Some(x) if x == TypeId::of::<String>() => text_widget(default_info).into(),
Some(x) if x == TypeId::of::<DVec2>() => coordinate_widget(default_info, "X", "Y", "", None),
Some(x) if x == TypeId::of::<UVec2>() => coordinate_widget(default_info, "X", "Y", "", Some(0.)),
Some(x) if x == TypeId::of::<IVec2>() => coordinate_widget(default_info, "X", "Y", "", None),
Some(x) if x == TypeId::of::<DVec2>() => coordinate_widget(default_info, "X", "Y", "", None, false),
Some(x) if x == TypeId::of::<DAffine2>() => transform_widget(default_info, &mut extra_widgets),
// ==========================
// PRIMITIVE COLLECTION TYPES
// ==========================
@@ -507,7 +505,127 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
last.clone()
}
pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>) -> LayoutGroup {
pub fn transform_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widgets: &mut Vec<LayoutGroup>) -> LayoutGroup {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
let mut location_widgets = start_widgets(parameter_widgets_info);
location_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
let mut rotation_widgets = vec![TextLabel::new("").widget_holder()];
add_blank_assist(&mut rotation_widgets);
rotation_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
let mut scale_widgets = vec![TextLabel::new("").widget_holder()];
add_blank_assist(&mut scale_widgets);
scale_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();
};
let widgets = if let Some(&TaggedValue::DAffine2(transform)) = input.as_non_exposed_value() {
let translation = transform.translation;
let rotation = transform.decompose_rotation();
let scale = transform.decompose_scale();
location_widgets.extend_from_slice(&[
NumberInput::new(Some(translation.x))
.label("X")
.unit(" px")
.on_update(update_value(
move |x: &NumberInput| {
let mut transform = transform;
transform.translation.x = x.value.unwrap_or(transform.translation.x);
TaggedValue::DAffine2(transform)
},
node_id,
index,
))
.on_commit(commit_value)
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(translation.y))
.label("Y")
.unit(" px")
.on_update(update_value(
move |y: &NumberInput| {
let mut transform = transform;
transform.translation.y = y.value.unwrap_or(transform.translation.y);
TaggedValue::DAffine2(transform)
},
node_id,
index,
))
.on_commit(commit_value)
.widget_holder(),
]);
rotation_widgets.extend_from_slice(&[NumberInput::new(Some(rotation.to_degrees()))
.unit("°")
.mode(NumberInputMode::Range)
.range_min(Some(-180.))
.range_max(Some(180.))
.on_update(update_value(
move |r: &NumberInput| {
let transform = DAffine2::from_scale_angle_translation(scale, r.value.map(|r| r.to_radians()).unwrap_or(rotation), translation);
TaggedValue::DAffine2(transform)
},
node_id,
index,
))
.on_commit(commit_value)
.widget_holder()]);
scale_widgets.extend_from_slice(&[
NumberInput::new(Some(scale.x))
.label("W")
.unit("x")
.on_update(update_value(
move |w: &NumberInput| {
let transform = DAffine2::from_scale_angle_translation(DVec2::new(w.value.unwrap_or(scale.x), scale.y), rotation, translation);
TaggedValue::DAffine2(transform)
},
node_id,
index,
))
.on_commit(commit_value)
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(scale.y))
.label("H")
.unit("x")
.on_update(update_value(
move |h: &NumberInput| {
let transform = DAffine2::from_scale_angle_translation(DVec2::new(scale.x, h.value.unwrap_or(scale.y)), rotation, translation);
TaggedValue::DAffine2(transform)
},
node_id,
index,
))
.on_commit(commit_value)
.widget_holder(),
]);
vec![
LayoutGroup::Row { widgets: location_widgets },
LayoutGroup::Row { widgets: rotation_widgets },
LayoutGroup::Row { widgets: scale_widgets },
]
} else {
vec![LayoutGroup::Row { widgets: location_widgets }]
};
if let Some((last, rest)) = widgets.split_last() {
*extra_widgets = rest.to_vec();
last.clone()
} else {
LayoutGroup::default()
}
}
pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>, is_integer: bool) -> LayoutGroup {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
let mut widgets = start_widgets(parameter_widgets_info);
@@ -526,6 +644,7 @@ pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str,
.unit(unit)
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
.is_integer(is_integer)
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), dvec2.y)), node_id, index))
.on_commit(commit_value)
.widget_holder(),
@@ -535,63 +654,12 @@ pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str,
.unit(unit)
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
.is_integer(is_integer)
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(dvec2.x, input.value.unwrap())), node_id, index))
.on_commit(commit_value)
.widget_holder(),
]);
}
Some(&TaggedValue::IVec2(ivec2)) => {
let update_x = move |input: &NumberInput| TaggedValue::IVec2(IVec2::new(input.value.unwrap() as i32, ivec2.y));
let update_y = move |input: &NumberInput| TaggedValue::IVec2(IVec2::new(ivec2.x, input.value.unwrap() as i32));
widgets.extend_from_slice(&[
Separator::new(SeparatorType::Unrelated).widget_holder(),
NumberInput::new(Some(ivec2.x as f64))
.int()
.label(x)
.unit(unit)
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
.on_update(update_value(update_x, node_id, index))
.on_commit(commit_value)
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(ivec2.y as f64))
.int()
.label(y)
.unit(unit)
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
.on_update(update_value(update_y, node_id, index))
.on_commit(commit_value)
.widget_holder(),
]);
}
Some(&TaggedValue::UVec2(uvec2)) => {
let update_x = move |input: &NumberInput| TaggedValue::UVec2(UVec2::new(input.value.unwrap() as u32, uvec2.y));
let update_y = move |input: &NumberInput| TaggedValue::UVec2(UVec2::new(uvec2.x, input.value.unwrap() as u32));
widgets.extend_from_slice(&[
Separator::new(SeparatorType::Unrelated).widget_holder(),
NumberInput::new(Some(uvec2.x as f64))
.int()
.label(x)
.unit(unit)
.min(min.unwrap_or(0.))
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
.on_update(update_value(update_x, node_id, index))
.on_commit(commit_value)
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
NumberInput::new(Some(uvec2.y as f64))
.int()
.label(y)
.unit(unit)
.min(min.unwrap_or(0.))
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
.on_update(update_value(update_y, node_id, index))
.on_commit(commit_value)
.widget_holder(),
]);
}
Some(&TaggedValue::F64(value)) => {
widgets.extend_from_slice(&[
Separator::new(SeparatorType::Unrelated).widget_holder(),
@@ -600,6 +668,7 @@ pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str,
.unit(unit)
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
.is_integer(is_integer)
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), value)), node_id, index))
.on_commit(commit_value)
.widget_holder(),
@@ -609,6 +678,7 @@ pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str,
.unit(unit)
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
.is_integer(is_integer)
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(value, input.value.unwrap())), node_id, index))
.on_commit(commit_value)
.widget_holder(),
@@ -1178,7 +1248,7 @@ 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::new(node_id, SpacingInput::<f64>::INDEX, true, context), "W", "H", " px", Some(0.));
let spacing = coordinate_widget(ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context), "W", "H", " px", Some(0.), false);
widgets.push(spacing);
}
GridType::Isometric => {
@@ -1188,7 +1258,7 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
NumberInput::default().label("H").min(0.).unit(" px"),
),
};
let angles = coordinate_widget(ParameterWidgetsInfo::new(node_id, AnglesInput::INDEX, true, context), "", "", "°", None);
let angles = coordinate_widget(ParameterWidgetsInfo::new(node_id, AnglesInput::INDEX, true, context), "", "", "°", None, false);
widgets.extend([spacing, angles]);
}
}
@@ -1307,8 +1377,8 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
// Uniform/individual radio input widget
let uniform = RadioEntryData::new("Uniform")
.label("Uniform")
.on_update(move |_| {
Message::Batched(Box::new([
.on_update(move |_| Message::Batched {
messages: Box::new([
NodeGraphMessage::SetInputValue {
node_id,
input_index: IndividualCornerRadiiInput::INDEX,
@@ -1321,13 +1391,13 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
value: TaggedValue::F64(uniform_val),
}
.into(),
]))
]),
})
.on_commit(commit_value);
let individual = RadioEntryData::new("Individual")
.label("Individual")
.on_update(move |_| {
Message::Batched(Box::new([
.on_update(move |_| Message::Batched {
messages: Box::new([
NodeGraphMessage::SetInputValue {
node_id,
input_index: IndividualCornerRadiiInput::INDEX,
@@ -1340,7 +1410,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
value: TaggedValue::F64Array4(individual_val),
}
.into(),
]))
]),
})
.on_commit(commit_value);
let radio_input = RadioInput::new(vec![uniform, individual]).selected_index(Some(is_individual as u32)).widget_holder();
@@ -1396,9 +1466,9 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
pub(crate) fn node_no_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
let text = if context.network_interface.is_layer(&node_id, context.selection_network_path) {
"Layer has no properties"
"Layer has no parameters"
} else {
"Node has no properties"
"Node has no parameters"
};
string_properties(text)
}
@@ -1539,8 +1609,8 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
widgets_first_row.push(
ColorInput::default()
.value(fill.clone().into())
.on_update(move |x: &ColorInput| {
Message::Batched(Box::new([
.on_update(move |x: &ColorInput| Message::Batched {
messages: Box::new([
match &fill2 {
Fill::None => NodeGraphMessage::SetInputValue {
node_id,
@@ -1567,7 +1637,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
value: TaggedValue::Fill(x.value.to_fill(fill2.as_gradient())),
}
.into(),
]))
]),
})
.on_commit(commit_value)
.widget_holder(),
@@ -23,13 +23,12 @@ impl FrontendGraphDataType {
TaggedValue::U32(_)
| TaggedValue::U64(_)
| TaggedValue::F64(_)
| TaggedValue::UVec2(_)
| TaggedValue::IVec2(_)
| TaggedValue::DVec2(_)
| TaggedValue::OptionalDVec2(_)
| TaggedValue::F64Array4(_)
| TaggedValue::VecF64(_)
| TaggedValue::VecDVec2(_) => Self::Number,
| TaggedValue::VecDVec2(_)
| TaggedValue::DAffine2(_) => Self::Number,
TaggedValue::GraphicGroup(_) | TaggedValue::GraphicElement(_) => Self::Group, // TODO: Is GraphicElement supposed to be included here?
TaggedValue::ArtboardGroup(_) => Self::Artboard,
_ => Self::General,
@@ -7,4 +7,4 @@ pub mod utility_types;
#[doc(inline)]
pub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant};
#[doc(inline)]
pub use overlays_message_handler::{OverlaysMessageData, OverlaysMessageHandler};
pub use overlays_message_handler::{OverlaysMessageContext, OverlaysMessageHandler};
@@ -2,7 +2,7 @@ use super::utility_types::{OverlayProvider, OverlaysVisibilitySettings};
use crate::messages::prelude::*;
#[derive(ExtractField)]
pub struct OverlaysMessageData<'a> {
pub struct OverlaysMessageContext<'a> {
pub visibility_settings: OverlaysVisibilitySettings,
pub ipp: &'a InputPreprocessorMessageHandler,
pub device_pixel_ratio: f64,
@@ -18,9 +18,11 @@ pub struct OverlaysMessageHandler {
}
#[message_handler_data]
impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessageHandler {
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, data: OverlaysMessageData) {
let OverlaysMessageData { visibility_settings, ipp, .. } = data;
impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMessageHandler {
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, context: OverlaysMessageContext) {
let OverlaysMessageContext { visibility_settings, ipp, .. } = context;
#[cfg(target_arch = "wasm32")]
let device_pixel_ratio = context.device_pixel_ratio;
match message {
#[cfg(target_arch = "wasm32")]
@@ -30,8 +32,6 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessag
use glam::{DAffine2, DVec2};
use wasm_bindgen::JsCast;
let device_pixel_ratio = data.device_pixel_ratio;
let canvas = match &self.canvas {
Some(canvas) => canvas,
None => {
@@ -40,28 +40,28 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessag
}
};
let context = self.context.get_or_insert_with(|| {
let context = canvas.get_context("2d").ok().flatten().expect("Failed to get canvas context");
context.dyn_into().expect("Context should be a canvas 2d context")
let canvas_context = self.context.get_or_insert_with(|| {
let canvas_context = canvas.get_context("2d").ok().flatten().expect("Failed to get canvas context");
canvas_context.dyn_into().expect("Context should be a canvas 2d context")
});
let size = ipp.viewport_bounds.size().as_uvec2();
let [a, b, c, d, e, f] = DAffine2::from_scale(DVec2::splat(device_pixel_ratio)).to_cols_array();
let _ = context.set_transform(a, b, c, d, e, f);
context.clear_rect(0., 0., ipp.viewport_bounds.size().x, ipp.viewport_bounds.size().y);
let _ = context.reset_transform();
let _ = canvas_context.set_transform(a, b, c, d, e, f);
canvas_context.clear_rect(0., 0., ipp.viewport_bounds.size().x, ipp.viewport_bounds.size().y);
let _ = canvas_context.reset_transform();
if visibility_settings.all() {
responses.add(DocumentMessage::GridOverlays(OverlayContext {
render_context: context.clone(),
render_context: canvas_context.clone(),
size: size.as_dvec2(),
device_pixel_ratio,
visibility_settings: visibility_settings.clone(),
}));
for provider in &self.overlay_providers {
responses.add(provider(OverlayContext {
render_context: context.clone(),
render_context: canvas_context.clone(),
size: size.as_dvec2(),
device_pixel_ratio,
visibility_settings: visibility_settings.clone(),
@@ -1,7 +1,8 @@
use super::utility_functions::overlay_canvas_context;
use crate::consts::{
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,
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, 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};
@@ -349,6 +350,7 @@ impl OverlayContext {
self.render_context.rect(corner.x, corner.y, size, size);
self.render_context.set_fill_style_str(color_fill);
self.render_context.set_stroke_style_str(color_stroke);
self.render_context.set_line_width(1.);
self.render_context.fill();
self.render_context.stroke();
@@ -631,11 +633,9 @@ 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);
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50);
self.render_context.set_line_width(4.);
self.render_context.stroke();
@@ -727,6 +727,7 @@ impl OverlayContext {
let color = color.unwrap_or(COLOR_OVERLAY_BLUE);
self.render_context.set_stroke_style_str(color);
self.render_context.set_line_width(1.);
self.render_context.stroke();
}
}
@@ -1,7 +1,5 @@
mod properties_panel_message;
mod properties_panel_message_handler;
pub mod utility_types;
pub mod properties_panel_message_handler;
#[doc(inline)]
pub use properties_panel_message::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant};
@@ -1,21 +1,34 @@
use super::utility_types::PropertiesPanelMessageHandlerData;
use graphene_std::uuid::NodeId;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::document_node_definitions::NodePropertiesContext;
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*;
use crate::node_graph_executor::NodeGraphExecutor;
#[derive(ExtractField)]
pub struct PropertiesPanelMessageContext<'a> {
pub network_interface: &'a mut NodeNetworkInterface,
pub selection_network_path: &'a [NodeId],
pub document_name: &'a str,
pub executor: &'a mut NodeGraphExecutor,
pub persistent_data: &'a PersistentData,
}
#[derive(Debug, Clone, Default, ExtractField)]
pub struct PropertiesPanelMessageHandler {}
#[message_handler_data]
impl MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPanelMessageHandlerData<'_>)> for PropertiesPanelMessageHandler {
fn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque<Message>, (persistent_data, data): (&PersistentData, PropertiesPanelMessageHandlerData)) {
let PropertiesPanelMessageHandlerData {
impl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> for PropertiesPanelMessageHandler {
fn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque<Message>, context: PropertiesPanelMessageContext) {
let PropertiesPanelMessageContext {
network_interface,
selection_network_path,
document_name,
executor,
} = data;
persistent_data,
} = context;
match message {
PropertiesPanelMessage::Clear => {
@@ -25,7 +38,7 @@ impl MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPanelMes
});
}
PropertiesPanelMessage::Refresh => {
let mut context = NodePropertiesContext {
let mut node_properties_context = NodePropertiesContext {
persistent_data,
responses,
network_interface,
@@ -33,9 +46,9 @@ impl MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPanelMes
document_name,
executor,
};
let properties_sections = NodeGraphMessageHandler::collate_properties(&mut context);
let properties_sections = NodeGraphMessageHandler::collate_properties(&mut node_properties_context);
context.responses.add(LayoutMessage::SendLayout {
node_properties_context.responses.add(LayoutMessage::SendLayout {
layout: Layout::WidgetLayout(WidgetLayout::new(properties_sections)),
layout_target: LayoutTarget::PropertiesSections,
});
@@ -1,10 +0,0 @@
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::node_graph_executor::NodeGraphExecutor;
use graph_craft::document::NodeId;
pub struct PropertiesPanelMessageHandlerData<'a> {
pub network_interface: &'a mut NodeNetworkInterface,
pub selection_network_path: &'a [NodeId],
pub document_name: &'a str,
pub executor: &'a mut NodeGraphExecutor,
}
@@ -250,12 +250,8 @@ impl LayerNodeIdentifier {
/// Construct a [`LayerNodeIdentifier`], debug asserting that it is a layer node. This should only be used in the document network since the structure is not loaded in nested networks.
#[track_caller]
pub fn new(node_id: NodeId, network_interface: &NodeNetworkInterface, network_path: &[NodeId]) -> Self {
debug_assert!(
network_interface.is_layer(&node_id, network_path),
"Layer identifier constructed from non-layer node {node_id}: {:#?}",
network_interface.nested_network(network_path).unwrap().nodes.get(&node_id)
);
pub fn new(node_id: NodeId, network_interface: &NodeNetworkInterface) -> Self {
debug_assert!(network_interface.is_layer(&node_id, &[]), "Layer identifier constructed from non-layer node {node_id}",);
Self::new_unchecked(node_id)
}
@@ -203,12 +203,12 @@ impl NodeNetworkInterface {
}
/// Returns the first downstream layer(inclusive) from a node. If the node is a layer, it will return itself.
pub fn downstream_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<LayerNodeIdentifier> {
pub fn downstream_layer_for_chain_node(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<NodeId> {
let mut id = *node_id;
while !self.is_layer(&id, network_path) {
id = self.outward_wires(network_path)?.get(&OutputConnector::node(id, 0))?.first()?.node_id()?;
}
Some(LayerNodeIdentifier::new(id, self, network_path))
Some(id)
}
/// Returns all downstream layers (inclusive) from a node. If the node is a layer, it will return itself.
@@ -388,8 +388,8 @@ impl NodeNetworkInterface {
}
// If a chain node does not have a selected downstream layer, then set the position to absolute
let downstream_layer = self.downstream_layer(node_id, network_path);
if downstream_layer.is_none_or(|downstream_layer| new_ids.keys().all(|key| *key != downstream_layer.to_node())) {
let downstream_layer = self.downstream_layer_for_chain_node(node_id, network_path);
if downstream_layer.is_none_or(|downstream_layer| new_ids.keys().all(|key| *key != downstream_layer)) {
let Some(position) = self.position(node_id, network_path) else {
log::error!("Could not get position in create_node_template");
return None;
@@ -1244,7 +1244,7 @@ impl NodeNetworkInterface {
.as_ref()
.is_some_and(|reference| reference == "Artboard" && self.connected_to_output(node_id, &[]) && self.is_layer(node_id, &[]))
{
Some(LayerNodeIdentifier::new(*node_id, self, &[]))
Some(LayerNodeIdentifier::new(*node_id, self))
} else {
None
}
@@ -3025,7 +3025,7 @@ impl NodeNetworkInterface {
// Helper functions for mutable getters
impl NodeNetworkInterface {
pub fn upstream_chain_nodes(&mut self, network_path: &[NodeId]) -> Vec<NodeId> {
pub fn upstream_chain_nodes(&self, network_path: &[NodeId]) -> Vec<NodeId> {
let Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else {
log::error!("Could not get selected nodes in upstream_chain_nodes");
return Vec::new();
@@ -3156,7 +3156,7 @@ impl NodeNetworkInterface {
self.document_metadata.document_to_viewport = transform;
}
pub fn is_eligible_to_be_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
pub fn is_eligible_to_be_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
let Some(node) = self.document_node(node_id, network_path) else {
log::error!("Could not get node {node_id} in is_eligible_to_be_layer");
return false;
@@ -3362,6 +3362,24 @@ impl NodeNetworkInterface {
.map(|[a, b]| [node_graph_to_viewport.transform_point2(a), node_graph_to_viewport.transform_point2(b)])
}
pub fn selected_layers_artwork_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
self.selected_nodes()
.0
.iter()
.filter(|node| self.is_layer(&node, &[]))
.filter_map(|layer| self.document_metadata.bounding_box_viewport(LayerNodeIdentifier::new(*layer, self)))
.reduce(Quad::combine_bounds)
}
pub fn selected_unlocked_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
self.selected_nodes()
.0
.iter()
.filter(|node| self.is_layer(&node, &[]) && !self.is_layer(&node, &[]))
.filter_map(|layer| self.document_metadata.bounding_box_viewport(LayerNodeIdentifier::new(*layer, self)))
.reduce(Quad::combine_bounds)
}
/// Get the combined bounding box of the click targets of the selected nodes in the node graph in layer space
pub fn selected_nodes_bounding_box(&mut self, network_path: &[NodeId]) -> Option<[DVec2; 2]> {
let Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else {
@@ -3451,7 +3469,7 @@ impl NodeNetworkInterface {
let Some(first_root_layer) = self
.upstream_flow_back_from_nodes(vec![root_node.node_id], &[], FlowType::PrimaryFlow)
.find_map(|node_id| if self.is_layer(&node_id, &[]) { Some(LayerNodeIdentifier::new(node_id, self, &[])) } else { None })
.find_map(|node_id| if self.is_layer(&node_id, &[]) { Some(LayerNodeIdentifier::new(node_id, self)) } else { None })
else {
return;
};
@@ -3467,7 +3485,7 @@ impl NodeNetworkInterface {
if horizontal_root_node_id == first_root_layer.to_node() {
for current_node_id in horizontal_flow_iter {
if self.is_layer(&current_node_id, &[]) {
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self, &[]);
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self);
if !self.document_metadata.structure.contains_key(&current_layer_node) {
if current_node_id == first_root_layer.to_node() {
awaiting_primary_flow.push((current_node_id, LayerNodeIdentifier::ROOT_PARENT));
@@ -3484,7 +3502,7 @@ impl NodeNetworkInterface {
// Skip the horizontal_root_node_id node
for current_node_id in horizontal_flow_iter.skip(1) {
if self.is_layer(&current_node_id, &[]) {
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self, &[]);
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self);
if !self.document_metadata.structure.contains_key(&current_layer_node) {
awaiting_primary_flow.push((current_node_id, parent_layer_node));
children.push((parent_layer_node, current_layer_node));
@@ -3505,7 +3523,7 @@ impl NodeNetworkInterface {
for current_node_id in primary_flow_iter.skip(1) {
if self.is_layer(&current_node_id, &[]) {
// Create a new layer for the top of each stack, and add it as a child to the previous parent
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self, &[]);
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self);
if !self.document_metadata.structure.contains_key(&current_layer_node) {
children.push(current_layer_node);
@@ -3568,7 +3586,7 @@ impl NodeNetworkInterface {
}
stack.extend(self_network_metadata.persistent_metadata.node_metadata.keys().map(|node_id| {
let mut current_path = path.clone();
let mut current_path: Vec<NodeId> = path.clone();
current_path.push(*node_id);
current_path
}));
@@ -5085,12 +5103,45 @@ impl NodeNetworkInterface {
else {
log::error!("Could not set chain position for layer node {node_id}");
}
// let previous_upstream_node = self.upstream_output_connector(&InputConnector::node(*node_id, 0), network_path).and_then(|output| output.node_id());
// let Some(previous_upstream_node_position) = previous_upstream_node.and_then(|upstream| self.position_from_downstream_node(&upstream, network_path)) else {
// log::error!("Could not get previous_upstream_node_position");
// return;
// };
self.unload_upstream_node_click_targets(vec![*node_id], network_path);
// Reload click target of the layer which encapsulate the chain
if let Some(downstream_layer) = self.downstream_layer(node_id, network_path) {
self.unload_node_click_targets(&downstream_layer.to_node(), network_path);
if let Some(downstream_layer) = self.downstream_layer_for_chain_node(node_id, network_path) {
self.unload_node_click_targets(&downstream_layer, network_path);
}
self.unload_all_nodes_bounding_box(network_path);
// let Some(new_upstream_node_position) = previous_upstream_node.and_then(|upstream| self.position_from_downstream_node(&upstream, network_path)) else {
// log::error!("Could not get new_upstream_node_position");
// return;
// };
// if let Some(previous_upstream_node) = {
// let x_delta = new_upstream_node_position.x - previous_upstream_node_position.x;
// // Upstream node got shifted to left, so shift all upstream absolute sole dependents
// if x_delta != 0 {
// let upstream_absolute_nodes = SelectedNodes(
// self.upstream_flow_back_from_nodes(vec![previous_upstream_node], network_path, FlowType::UpstreamFlow)
// .into_iter()
// .filter(|node_id| self.is_absolute(node_id, network_path))
// .collect::<Vec<_>>(),
// );
// let old_selected_nodes = std::mem::replace(self.selected_nodes_mut(network_path).unwrap(), upstream_absolute_nodes);
// if x_delta < 0 {
// for _ in 0..x_delta.abs() {
// self.shift_selected_nodes(Direction::Left, false, network_path);
// }
// } else {
// for _ in 0..x_delta.abs() {
// self.shift_selected_nodes(Direction::Right, false, network_path);
// }
// }
// let _ = std::mem::replace(self.selected_nodes_mut(network_path).unwrap(), old_selected_nodes);
// }
// }
}
fn valid_upstream_chain_nodes(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec<NodeId> {
@@ -5205,7 +5256,7 @@ impl NodeNetworkInterface {
/// node_id is the first chain node, not the layer
fn set_upstream_chain_to_absolute(&mut self, node_id: &NodeId, network_path: &[NodeId]) {
let Some(downstream_layer) = self.downstream_layer(node_id, network_path) else {
let Some(downstream_layer) = self.downstream_layer_for_chain_node(node_id, network_path) else {
log::error!("Could not get downstream layer in set_upstream_chain_to_absolute");
return;
};
@@ -5218,7 +5269,7 @@ impl NodeNetworkInterface {
if self.is_chain(upstream_id, network_path) {
self.set_absolute_position(upstream_id, previous_position, network_path);
// Reload click target of the layer which used to encapsulate the chain
self.unload_node_click_targets(&downstream_layer.to_node(), network_path);
self.unload_node_click_targets(&downstream_layer, network_path);
}
// If there is an upstream layer then stop breaking the chain
else {
@@ -5297,8 +5348,8 @@ impl NodeNetworkInterface {
// Deselect chain nodes upstream from a selected layer
if self.is_chain(selected_node, network_path)
&& self
.downstream_layer(selected_node, network_path)
.is_some_and(|downstream_layer| node_ids.contains(&downstream_layer.to_node()))
.downstream_layer_for_chain_node(selected_node, network_path)
.is_some_and(|downstream_layer| node_ids.contains(&downstream_layer))
{
node_ids.remove(selected_node);
}
@@ -5947,31 +5998,6 @@ impl NodeNetworkInterface {
self.create_wire(&OutputConnector::node(*node_id, 0), &InputConnector::node(parent.to_node(), 1), network_path);
self.set_chain_position(node_id, network_path);
} else {
// TODO: Implement a more robust horizontal shift system when inserting a node into a chain.
// This should be done by breaking the chain and shifting the sole dependents for each node upstream of the insertion.
// Before inserting the node, shift the layer right 7 units so that all sole dependents are also shifted
// let input_connector = InputConnector::node(parent.to_node(), 0);
// let old_upstream = self.upstream_output_connector(&input_connector, network_path);
// This also needs to disconnect from the downstream layer
// self.disconnect_input(&input_connector, network_path);
// let Some(selected_nodes) = self.selected_nodes_mut(network_path) else {
// log::error!("Could not get selected nodes in move_layer_to_stack");
// return;
// };
// let old_selected_nodes = selected_nodes.replace_with(vec![parent.to_node()]);
// for _ in 0..7 {
// self.shift_selected_nodes(Direction::Left, false, network_path);
// }
// // Grip drag it back to the right
// for _ in 0..7 {
// self.shift_selected_nodes(Direction::Right, true, network_path);
// }
// let _ = self.selected_nodes_mut(network_path).unwrap().replace_with(old_selected_nodes);
// if let Some(old_upstream) = old_upstream {
// self.create_wire(&old_upstream, &input_connector, network_path);
// }
// Insert the node in the gap and set the upstream to a chain
self.insert_node_between(node_id, &InputConnector::node(parent.to_node(), 1), 0, network_path);
self.force_set_upstream_to_chain(node_id, network_path);
@@ -6473,6 +6499,7 @@ pub struct Vec2InputSettings {
pub y: String,
pub unit: String,
pub min: Option<f64>,
pub is_integer: bool,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -6547,6 +6574,7 @@ impl InputPersistentMetadata {
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));
self.input_data.insert("is_integer".to_string(), Value::Bool(vec2_properties.is_integer));
if let Some(min) = vec2_properties.min {
self.input_data.insert("min".to_string(), json!(min));
}
@@ -6776,13 +6804,6 @@ impl From<DocumentNodePersistentMetadataPropertiesRow> for DocumentNodePersisten
}
}
#[derive(serde::Serialize, serde::Deserialize)]
enum NodePersistentMetadataVersions {
DocumentNodePersistentMetadataPropertiesRow(DocumentNodePersistentMetadataPropertiesRow),
NodePersistentMetadataInputNames(DocumentNodePersistentMetadataInputNames),
NodePersistentMetadata(DocumentNodePersistentMetadata),
}
fn deserialize_node_persistent_metadata<'de, D>(deserializer: D) -> Result<DocumentNodePersistentMetadata, D::Error>
where
D: serde::Deserializer<'de>,
@@ -9,6 +9,7 @@ use bezier_rs::Subpath;
use glam::IVec2;
use graph_craft::document::DocumentNode;
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
use graphene_std::ProtoNodeIdentifier;
use graphene_std::text::TypesettingConfig;
use graphene_std::uuid::NodeId;
use graphene_std::vector::style::{PaintOrder, StrokeAlign};
@@ -20,156 +21,440 @@ const TEXT_REPLACEMENTS: &[(&str, &str)] = &[
("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode", "graphene_core::vector::SubpathSegmentLengthsNode"),
];
const REPLACEMENTS: &[(&str, &str)] = &[
("graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"),
("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"),
("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"),
("graphene_core::ToGraphicGroupNode", "graphene_core::graphic_element::ToGroupNode"),
pub struct NodeReplacement<'a> {
node: ProtoNodeIdentifier,
aliases: &'a [&'a str],
}
const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
// graphic element
NodeReplacement {
node: graphene_std::graphic_element::append_artboard::IDENTIFIER,
aliases: &["graphene_core::AddArtboardNode"],
},
NodeReplacement {
node: graphene_std::graphic_element::to_artboard::IDENTIFIER,
aliases: &["graphene_core::ConstructArtboardNode"],
},
NodeReplacement {
node: graphene_std::graphic_element::to_element::IDENTIFIER,
aliases: &["graphene_core::ToGraphicElementNode"],
},
NodeReplacement {
node: graphene_std::graphic_element::to_group::IDENTIFIER,
aliases: &["graphene_core::ToGraphicGroupNode"],
},
// math_nodes
("graphene_core::ops::MathNode", "graphene_math_nodes::MathNode"),
("graphene_core::ops::AddNode", "graphene_math_nodes::AddNode"),
("graphene_core::ops::SubtractNode", "graphene_math_nodes::SubtractNode"),
("graphene_core::ops::MultiplyNode", "graphene_math_nodes::MultiplyNode"),
("graphene_core::ops::DivideNode", "graphene_math_nodes::DivideNode"),
("graphene_core::ops::ModuloNode", "graphene_math_nodes::ModuloNode"),
("graphene_core::ops::ExponentNode", "graphene_math_nodes::ExponentNode"),
("graphene_core::ops::RootNode", "graphene_math_nodes::RootNode"),
("graphene_core::ops::LogarithmNode", "graphene_math_nodes::LogarithmNode"),
("graphene_core::ops::SineNode", "graphene_math_nodes::SineNode"),
("graphene_core::ops::CosineNode", "graphene_math_nodes::CosineNode"),
("graphene_core::ops::TangentNode", "graphene_math_nodes::TangentNode"),
("graphene_core::ops::SineInverseNode", "graphene_math_nodes::SineInverseNode"),
("graphene_core::ops::CosineInverseNode", "graphene_math_nodes::CosineInverseNode"),
("graphene_core::ops::TangentInverseNode", "graphene_math_nodes::TangentInverseNode"),
("graphene_core::ops::RandomNode", "graphene_math_nodes::RandomNode"),
("graphene_core::ops::ToU32Node", "graphene_math_nodes::ToU32Node"),
("graphene_core::ops::ToU64Node", "graphene_math_nodes::ToU64Node"),
("graphene_core::ops::ToF64Node", "graphene_math_nodes::ToF64Node"),
("graphene_core::ops::RoundNode", "graphene_math_nodes::RoundNode"),
("graphene_core::ops::FloorNode", "graphene_math_nodes::FloorNode"),
("graphene_core::ops::CeilingNode", "graphene_math_nodes::CeilingNode"),
("graphene_core::ops::MinNode", "graphene_math_nodes::MinNode"),
("graphene_core::ops::MaxNode", "graphene_math_nodes::MaxNode"),
("graphene_core::ops::ClampNode", "graphene_math_nodes::ClampNode"),
("graphene_core::ops::EqualsNode", "graphene_math_nodes::EqualsNode"),
("graphene_core::ops::NotEqualsNode", "graphene_math_nodes::NotEqualsNode"),
("graphene_core::ops::LessThanNode", "graphene_math_nodes::LessThanNode"),
("graphene_core::ops::GreaterThanNode", "graphene_math_nodes::GreaterThanNode"),
("graphene_core::ops::LogicalOrNode", "graphene_math_nodes::LogicalOrNode"),
("graphene_core::ops::LogicalAndNode", "graphene_math_nodes::LogicalAndNode"),
("graphene_core::ops::LogicalNotNode", "graphene_math_nodes::LogicalNotNode"),
("graphene_core::ops::BoolValueNode", "graphene_math_nodes::BoolValueNode"),
("graphene_core::ops::NumberValueNode", "graphene_math_nodes::NumberValueNode"),
("graphene_core::ops::PercentageValueNode", "graphene_math_nodes::PercentageValueNode"),
("graphene_core::ops::CoordinateValueNode", "graphene_math_nodes::CoordinateValueNode"),
("graphene_core::ops::ConstructVector2", "graphene_math_nodes::CoordinateValueNode"),
("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"),
NodeReplacement {
node: graphene_std::math_nodes::math::IDENTIFIER,
aliases: &["graphene_core::ops::MathNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::add::IDENTIFIER,
aliases: &["graphene_core::ops::AddNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::subtract::IDENTIFIER,
aliases: &["graphene_core::ops::SubtractNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::multiply::IDENTIFIER,
aliases: &["graphene_core::ops::MultiplyNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::divide::IDENTIFIER,
aliases: &["graphene_core::ops::DivideNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::modulo::IDENTIFIER,
aliases: &["graphene_core::ops::ModuloNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::exponent::IDENTIFIER,
aliases: &["graphene_core::ops::ExponentNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::root::IDENTIFIER,
aliases: &["graphene_core::ops::RootNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::logarithm::IDENTIFIER,
aliases: &["graphene_core::ops::LogarithmNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::sine::IDENTIFIER,
aliases: &["graphene_core::ops::SineNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::cosine::IDENTIFIER,
aliases: &["graphene_core::ops::CosineNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::tangent::IDENTIFIER,
aliases: &["graphene_core::ops::TangentNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::sine_inverse::IDENTIFIER,
aliases: &["graphene_core::ops::SineInverseNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::cosine_inverse::IDENTIFIER,
aliases: &["graphene_core::ops::CosineInverseNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::tangent_inverse::IDENTIFIER,
aliases: &["graphene_core::ops::TangentInverseNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::random::IDENTIFIER,
aliases: &["graphene_core::ops::RandomNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::to_u_32::IDENTIFIER,
aliases: &["graphene_core::ops::ToU32Node"],
},
NodeReplacement {
node: graphene_std::math_nodes::to_u_64::IDENTIFIER,
aliases: &["graphene_core::ops::ToU64Node"],
},
NodeReplacement {
node: graphene_std::math_nodes::to_f_64::IDENTIFIER,
aliases: &["graphene_core::ops::ToF64Node"],
},
NodeReplacement {
node: graphene_std::math_nodes::round::IDENTIFIER,
aliases: &["graphene_core::ops::RoundNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::floor::IDENTIFIER,
aliases: &["graphene_core::ops::FloorNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::ceiling::IDENTIFIER,
aliases: &["graphene_core::ops::CeilingNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::min::IDENTIFIER,
aliases: &["graphene_core::ops::MinNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::max::IDENTIFIER,
aliases: &["graphene_core::ops::MaxNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::clamp::IDENTIFIER,
aliases: &["graphene_core::ops::ClampNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::equals::IDENTIFIER,
aliases: &["graphene_core::ops::EqualsNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::not_equals::IDENTIFIER,
aliases: &["graphene_core::ops::NotEqualsNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::less_than::IDENTIFIER,
aliases: &["graphene_core::ops::LessThanNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::greater_than::IDENTIFIER,
aliases: &["graphene_core::ops::GreaterThanNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::logical_or::IDENTIFIER,
aliases: &["graphene_core::ops::LogicalOrNode", "graphene_core::ops::LogicAndNode", "graphene_core::logic::LogicAndNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::logical_and::IDENTIFIER,
aliases: &["graphene_core::ops::LogicalAndNode", "graphene_core::ops::LogicNotNode", "graphene_core::logic::LogicNotNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::logical_not::IDENTIFIER,
aliases: &["graphene_core::ops::LogicalNotNode", "graphene_core::ops::LogicOrNode", "graphene_core::logic::LogicOrNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::bool_value::IDENTIFIER,
aliases: &["graphene_core::ops::BoolValueNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::number_value::IDENTIFIER,
aliases: &["graphene_core::ops::NumberValueNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::percentage_value::IDENTIFIER,
aliases: &["graphene_core::ops::PercentageValueNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::coordinate_value::IDENTIFIER,
aliases: &[
"graphene_core::ops::CoordinateValueNode",
"graphene_core::ops::ConstructVector2",
"graphene_core::ops::Vector2ValueNode",
],
},
NodeReplacement {
node: graphene_std::math_nodes::color_value::IDENTIFIER,
aliases: &["graphene_core::ops::ColorValueNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::gradient_value::IDENTIFIER,
aliases: &["graphene_core::ops::GradientValueNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::sample_gradient::IDENTIFIER,
aliases: &["graphene_core::ops::SampleGradientNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::string_value::IDENTIFIER,
aliases: &["graphene_core::ops::StringValueNode"],
},
NodeReplacement {
node: graphene_std::math_nodes::dot_product::IDENTIFIER,
aliases: &["graphene_core::ops::DotProductNode"],
},
// debug
("graphene_core::ops::SizeOfNode", "graphene_core::debug::SizeOfNode"),
("graphene_core::ops::SomeNode", "graphene_core::debug::SomeNode"),
("graphene_core::ops::UnwrapNode", "graphene_core::debug::UnwrapNode"),
("graphene_core::ops::CloneNode", "graphene_core::debug::CloneNode"),
NodeReplacement {
node: graphene_std::debug::size_of::IDENTIFIER,
aliases: &["graphene_core::ops::SizeOfNode"],
},
NodeReplacement {
node: graphene_std::debug::some::IDENTIFIER,
aliases: &["graphene_core::ops::SomeNode"],
},
NodeReplacement {
node: graphene_std::debug::unwrap::IDENTIFIER,
aliases: &["graphene_core::ops::UnwrapNode"],
},
NodeReplacement {
node: graphene_std::debug::clone::IDENTIFIER,
aliases: &["graphene_core::ops::CloneNode"],
},
// ???
("graphene_core::ops::ExtractXyNode", "graphene_core::extract_xy::ExtractXyNode"),
("graphene_core::logic::LogicAndNode", "graphene_core::ops::LogicAndNode"),
("graphene_core::logic::LogicNotNode", "graphene_core::ops::LogicNotNode"),
("graphene_core::logic::LogicOrNode", "graphene_core::ops::LogicOrNode"),
("graphene_core::raster::BlendModeNode", "graphene_core::blending_nodes::BlendModeNode"),
("graphene_core::raster::OpacityNode", "graphene_core::blending_nodes::OpacityNode"),
("graphene_core::raster::BlendingNode", "graphene_core::blending_nodes::BlendingNode"),
("graphene_core::vector::GenerateHandlesNode", "graphene_core::vector::AutoTangentsNode"),
("graphene_core::vector::RemoveHandlesNode", "graphene_core::vector::AutoTangentsNode"),
NodeReplacement {
node: graphene_std::extract_xy::extract_xy::IDENTIFIER,
aliases: &["graphene_core::ops::ExtractXyNode"],
},
NodeReplacement {
node: graphene_std::blending_nodes::blend_mode::IDENTIFIER,
aliases: &["graphene_core::raster::BlendModeNode"],
},
NodeReplacement {
node: graphene_std::blending_nodes::opacity::IDENTIFIER,
aliases: &["graphene_core::raster::OpacityNode"],
},
NodeReplacement {
node: graphene_std::blending_nodes::blending::IDENTIFIER,
aliases: &["graphene_core::raster::BlendingNode"],
},
NodeReplacement {
node: graphene_std::vector::auto_tangents::IDENTIFIER,
aliases: &["graphene_core::vector::GenerateHandlesNode", "graphene_core::vector::RemoveHandlesNode"],
},
// raster::adjustments
("graphene_core::raster::adjustments::LuminanceNode", "graphene_raster_nodes::adjustments::LuminanceNode"),
("graphene_core::raster::LuminanceNode", "graphene_raster_nodes::adjustments::LuminanceNode"),
("graphene_core::raster::adjustments::ExtractChannelNode", "graphene_raster_nodes::adjustments::ExtractChannelNode"),
("graphene_core::raster::ExtractChannelNode", "graphene_raster_nodes::adjustments::ExtractChannelNode"),
("graphene_core::raster::adjustments::MakeOpaqueNode", "graphene_raster_nodes::adjustments::MakeOpaqueNode"),
("graphene_core::raster::ExtractOpaqueNode", "graphene_raster_nodes::adjustments::MakeOpaqueNode"),
(
"graphene_core::raster::adjustments::BrightnessContrastNode",
"graphene_raster_nodes::adjustments::BrightnessContrastNode",
),
("graphene_core::raster::adjustments::LevelsNode", "graphene_raster_nodes::adjustments::LevelsNode"),
("graphene_core::raster::LevelsNode", "graphene_raster_nodes::adjustments::LevelsNode"),
("graphene_core::raster::adjustments::BlackAndWhiteNode", "graphene_raster_nodes::adjustments::BlackAndWhiteNode"),
("graphene_core::raster::BlackAndWhiteNode", "graphene_raster_nodes::adjustments::BlackAndWhiteNode"),
("graphene_core::raster::adjustments::HueSaturationNode", "graphene_raster_nodes::adjustments::HueSaturationNode"),
("graphene_core::raster::HueSaturationNode", "graphene_raster_nodes::adjustments::HueSaturationNode"),
("graphene_core::raster::adjustments::InvertNode", "graphene_raster_nodes::adjustments::InvertNode"),
("graphene_core::raster::InvertNode", "graphene_raster_nodes::adjustments::InvertNode"),
("graphene_core::raster::InvertRGBNode", "graphene_raster_nodes::adjustments::InvertNode"),
("graphene_core::raster::adjustments::ThresholdNode", "graphene_raster_nodes::adjustments::ThresholdNode"),
("graphene_core::raster::ThresholdNode", "graphene_raster_nodes::adjustments::ThresholdNode"),
("graphene_core::raster::adjustments::BlendNode", "graphene_raster_nodes::adjustments::BlendNode"),
("graphene_core::raster::BlendNode", "graphene_raster_nodes::adjustments::BlendNode"),
("graphene_core::raster::BlendColorPairNode", "graphene_raster_nodes::adjustments::BlendColorPairNode"),
("graphene_core::raster::adjustments::BlendColorsNode", "graphene_raster_nodes::adjustments::BlendColorsNode"),
("graphene_core::raster::BlendColorsNode", "graphene_raster_nodes::adjustments::BlendColorsNode"),
("graphene_core::raster::adjustments::GradientMapNode", "graphene_raster_nodes::adjustments::GradientMapNode"),
("graphene_core::raster::GradientMapNode", "graphene_raster_nodes::adjustments::GradientMapNode"),
("graphene_core::raster::adjustments::VibranceNode", "graphene_raster_nodes::adjustments::VibranceNode"),
("graphene_core::raster::VibranceNode", "graphene_raster_nodes::adjustments::VibranceNode"),
("graphene_core::raster::adjustments::ChannelMixerNode", "graphene_raster_nodes::adjustments::ChannelMixerNode"),
("graphene_core::raster::ChannelMixerNode", "graphene_raster_nodes::adjustments::ChannelMixerNode"),
("graphene_core::raster::adjustments::SelectiveColorNode", "graphene_raster_nodes::adjustments::SelectiveColorNode"),
("graphene_core::raster::adjustments::PosterizeNode", "graphene_raster_nodes::adjustments::PosterizeNode"),
("graphene_core::raster::PosterizeNode", "graphene_raster_nodes::adjustments::PosterizeNode"),
("graphene_core::raster::adjustments::ExposureNode", "graphene_raster_nodes::adjustments::ExposureNode"),
("graphene_core::raster::ExposureNode", "graphene_raster_nodes::adjustments::ExposureNode"),
("graphene_core::raster::adjustments::ColorOverlayNode", "graphene_raster_nodes::adjustments::ColorOverlayNode"),
("graphene_raster_nodes::generate_curves::ColorOverlayNode", "graphene_raster_nodes::adjustments::ColorOverlayNode"),
NodeReplacement {
node: graphene_std::raster_nodes::adjustments::luminance::IDENTIFIER,
aliases: &["graphene_core::raster::adjustments::LuminanceNode", "graphene_core::raster::LuminanceNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::adjustments::extract_channel::IDENTIFIER,
aliases: &["graphene_core::raster::adjustments::ExtractChannelNode", "graphene_core::raster::ExtractChannelNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::adjustments::make_opaque::IDENTIFIER,
aliases: &["graphene_core::raster::adjustments::MakeOpaqueNode", "graphene_core::raster::ExtractOpaqueNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::adjustments::brightness_contrast::IDENTIFIER,
aliases: &["graphene_core::raster::adjustments::BrightnessContrastNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::adjustments::levels::IDENTIFIER,
aliases: &["graphene_core::raster::adjustments::LevelsNode", "graphene_core::raster::LevelsNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::adjustments::black_and_white::IDENTIFIER,
aliases: &["graphene_core::raster::adjustments::BlackAndWhiteNode", "graphene_core::raster::BlackAndWhiteNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::adjustments::hue_saturation::IDENTIFIER,
aliases: &["graphene_core::raster::adjustments::HueSaturationNode", "graphene_core::raster::HueSaturationNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::adjustments::invert::IDENTIFIER,
aliases: &[
"graphene_core::raster::adjustments::InvertNode",
"graphene_core::raster::InvertNode",
"graphene_core::raster::InvertRGBNode",
],
},
NodeReplacement {
node: graphene_std::raster_nodes::adjustments::threshold::IDENTIFIER,
aliases: &["graphene_core::raster::adjustments::ThresholdNode", "graphene_core::raster::ThresholdNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::adjustments::blend::IDENTIFIER,
aliases: &["graphene_core::raster::adjustments::BlendNode", "graphene_core::raster::BlendNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::adjustments::blend_color_pair::IDENTIFIER,
aliases: &["graphene_core::raster::BlendColorPairNode"],
},
// this node doesn't seem to exist?
// (graphene_std::raster_nodes::adjustments::blend_color::IDENTIFIER, &["graphene_core::raster::adjustments::BlendColorsNode","graphene_core::raster::BlendColorsNode"]),
NodeReplacement {
node: graphene_std::raster_nodes::adjustments::gradient_map::IDENTIFIER,
aliases: &["graphene_core::raster::adjustments::GradientMapNode", "graphene_core::raster::GradientMapNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::adjustments::vibrance::IDENTIFIER,
aliases: &["graphene_core::raster::adjustments::VibranceNode", "graphene_core::raster::VibranceNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::adjustments::channel_mixer::IDENTIFIER,
aliases: &["graphene_core::raster::adjustments::ChannelMixerNode", "graphene_core::raster::ChannelMixerNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::adjustments::selective_color::IDENTIFIER,
aliases: &["graphene_core::raster::adjustments::SelectiveColorNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::adjustments::posterize::IDENTIFIER,
aliases: &["graphene_core::raster::adjustments::PosterizeNode", "graphene_core::raster::PosterizeNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::adjustments::exposure::IDENTIFIER,
aliases: &["graphene_core::raster::adjustments::ExposureNode", "graphene_core::raster::ExposureNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::adjustments::color_overlay::IDENTIFIER,
aliases: &["graphene_core::raster::adjustments::ColorOverlayNode", "graphene_raster_nodes::generate_curves::ColorOverlayNode"],
},
// raster
("graphene_core::raster::adjustments::GenerateCurvesNode", "graphene_raster_nodes::generate_curves::GenerateCurvesNode"),
("graphene_std::dehaze::DehazeNode", "graphene_raster_nodes::dehaze::DehazeNode"),
("graphene_std::filter::BlurNode", "graphene_raster_nodes::filter::BlurNode"),
(
"graphene_std::image_color_palette::ImageColorPaletteNode",
"graphene_raster_nodes::image_color_palette::ImageColorPaletteNode",
),
("graphene_std::raster::SampleImageNode", "graphene_raster_nodes::std_nodes::SampleImageNode"),
("graphene_std::raster::CombineChannelsNode", "graphene_raster_nodes::std_nodes::CombineChannelsNode"),
("graphene_std::raster::MaskNode", "graphene_raster_nodes::std_nodes::MaskNode"),
("graphene_std::raster::ExtendImageToBoundsNode", "graphene_raster_nodes::std_nodes::ExtendImageToBoundsNode"),
("graphene_std::raster::EmptyImageNode", "graphene_raster_nodes::std_nodes::EmptyImageNode"),
("graphene_std::raster::ImageValueNode", "graphene_raster_nodes::std_nodes::ImageValueNode"),
("graphene_std::raster::NoisePatternNode", "graphene_raster_nodes::std_nodes::NoisePatternNode"),
("graphene_std::raster::MandelbrotNode", "graphene_raster_nodes::std_nodes::MandelbrotNode"),
NodeReplacement {
node: graphene_std::raster_nodes::generate_curves::generate_curves::IDENTIFIER,
aliases: &["graphene_core::raster::adjustments::GenerateCurvesNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::dehaze::dehaze::IDENTIFIER,
aliases: &["graphene_std::dehaze::DehazeNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::filter::blur::IDENTIFIER,
aliases: &["graphene_std::filter::BlurNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::image_color_palette::image_color_palette::IDENTIFIER,
aliases: &["graphene_std::image_color_palette::ImageColorPaletteNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::std_nodes::sample_image::IDENTIFIER,
aliases: &["graphene_std::raster::SampleImageNode", "graphene_std::raster::SampleNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::std_nodes::combine_channels::IDENTIFIER,
aliases: &["graphene_std::raster::CombineChannelsNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::std_nodes::mask::IDENTIFIER,
aliases: &["graphene_std::raster::MaskNode", "graphene_std::raster::MaskImageNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::std_nodes::extend_image_to_bounds::IDENTIFIER,
aliases: &["graphene_std::raster::ExtendImageToBoundsNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::std_nodes::empty_image::IDENTIFIER,
aliases: &["graphene_std::raster::EmptyImageNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::std_nodes::image_value::IDENTIFIER,
aliases: &["graphene_std::raster::ImageValueNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::std_nodes::noise_pattern::IDENTIFIER,
aliases: &["graphene_std::raster::NoisePatternNode"],
},
NodeReplacement {
node: graphene_std::raster_nodes::std_nodes::mandelbrot::IDENTIFIER,
aliases: &["graphene_std::raster::MandelbrotNode"],
},
// text
("graphene_core::text::TextGeneratorNode", "graphene_core::text::TextNode"),
NodeReplacement {
node: graphene_std::text::text::IDENTIFIER,
aliases: &["graphene_core::text::TextGeneratorNode"],
},
// transform
("graphene_core::transform::SetTransformNode", "graphene_core::transform_nodes::ReplaceTransformNode"),
("graphene_core::transform::ReplaceTransformNode", "graphene_core::transform_nodes::ReplaceTransformNode"),
("graphene_core::transform::TransformNode", "graphene_core::transform_nodes::TransformNode"),
("graphene_core::transform::BoundlessFootprintNode", "graphene_core::transform_nodes::BoundlessFootprintNode"),
("graphene_core::transform::FreezeRealTimeNode", "graphene_core::transform_nodes::FreezeRealTimeNode"),
NodeReplacement {
node: graphene_std::transform_nodes::replace_transform::IDENTIFIER,
aliases: &["graphene_core::transform::SetTransformNode", "graphene_core::transform::ReplaceTransformNode"],
},
NodeReplacement {
node: graphene_std::transform_nodes::transform::IDENTIFIER,
aliases: &["graphene_core::transform::TransformNode"],
},
NodeReplacement {
node: graphene_std::transform_nodes::boundless_footprint::IDENTIFIER,
aliases: &["graphene_core::transform::BoundlessFootprintNode"],
},
NodeReplacement {
node: graphene_std::transform_nodes::freeze_real_time::IDENTIFIER,
aliases: &["graphene_core::transform::FreezeRealTimeNode"],
},
// ???
("graphene_core::vector::SplinesFromPointsNode", "graphene_core::vector::SplineNode"),
("graphene_core::vector::generator_nodes::EllipseGenerator", "graphene_core::vector::generator_nodes::EllipseNode"),
("graphene_core::vector::generator_nodes::LineGenerator", "graphene_core::vector::generator_nodes::LineNode"),
("graphene_core::vector::generator_nodes::RectangleGenerator", "graphene_core::vector::generator_nodes::RectangleNode"),
(
"graphene_core::vector::generator_nodes::RegularPolygonGenerator",
"graphene_core::vector::generator_nodes::RegularPolygonNode",
),
("graphene_core::vector::generator_nodes::StarGenerator", "graphene_core::vector::generator_nodes::StarNode"),
("graphene_std::executor::BlendGpuImageNode", "graphene_std::gpu_nodes::BlendGpuImageNode"),
("graphene_std::raster::SampleNode", "graphene_std::raster::SampleImageNode"),
("graphene_core::transform::CullNode", "graphene_core::ops::IdentityNode"),
("graphene_std::raster::MaskImageNode", "graphene_std::raster::MaskNode"),
("graphene_core::vector::FlattenVectorElementsNode", "graphene_core::vector::FlattenPathNode"),
("graphene_std::vector::BooleanOperationNode", "graphene_path_bool::BooleanOperationNode"),
NodeReplacement {
node: graphene_std::vector::spline::IDENTIFIER,
aliases: &["graphene_core::vector::SplinesFromPointsNode"],
},
NodeReplacement {
node: graphene_std::vector::generator_nodes::ellipse::IDENTIFIER,
aliases: &["graphene_core::vector::generator_nodes::EllipseGenerator"],
},
NodeReplacement {
node: graphene_std::vector::generator_nodes::line::IDENTIFIER,
aliases: &["graphene_core::vector::generator_nodes::LineGenerator"],
},
NodeReplacement {
node: graphene_std::vector::generator_nodes::rectangle::IDENTIFIER,
aliases: &["graphene_core::vector::generator_nodes::RectangleGenerator"],
},
NodeReplacement {
node: graphene_std::vector::generator_nodes::regular_polygon::IDENTIFIER,
aliases: &["graphene_core::vector::generator_nodes::RegularPolygonGenerator"],
},
NodeReplacement {
node: graphene_std::vector::generator_nodes::star::IDENTIFIER,
aliases: &["graphene_core::vector::generator_nodes::StarGenerator"],
},
NodeReplacement {
node: graphene_std::ops::identity::IDENTIFIER,
aliases: &["graphene_core::transform::CullNode"],
},
NodeReplacement {
node: graphene_std::vector::flatten_path::IDENTIFIER,
aliases: &["graphene_core::vector::FlattenVectorElementsNode"],
},
NodeReplacement {
node: graphene_std::path_bool::boolean_operation::IDENTIFIER,
aliases: &["graphene_std::vector::BooleanOperationNode"],
},
// brush
("graphene_std::brush::BrushStampGeneratorNode", "graphene_brush::brush::BrushStampGeneratorNode"),
("graphene_std::brush::BlitNode", "graphene_brush::brush::BlitNode"),
("graphene_std::brush::BrushNode", "graphene_brush::brush::BrushNode"),
NodeReplacement {
node: graphene_std::brush::brush::brush_stamp_generator::IDENTIFIER,
aliases: &["graphene_std::brush::BrushStampGeneratorNode"],
},
NodeReplacement {
node: graphene_std::brush::brush::blit::IDENTIFIER,
aliases: &["graphene_std::brush::BlitNode"],
},
NodeReplacement {
node: graphene_std::brush::brush::brush::IDENTIFIER,
aliases: &["graphene_std::brush::BrushNode"],
},
];
const REPLACEMENTS: &[(&str, &str)] = &[];
pub fn document_migration_string_preprocessing(document_serialized_content: String) -> String {
TEXT_REPLACEMENTS
.iter()
@@ -195,17 +480,26 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
let network = document.network_interface.document_network().clone();
// Apply string replacements to each node
// Apply string and node replacements to each node
let mut replacements = HashMap::<&str, ProtoNodeIdentifier>::new();
Iterator::chain(
NODE_REPLACEMENTS.iter().flat_map(|NodeReplacement { node, aliases }| aliases.iter().map(|old| (*old, node.clone()))),
REPLACEMENTS.iter().map(|(old, new)| (*old, ProtoNodeIdentifier::new(new))),
)
.for_each(|(old, new)| {
if replacements.insert(old, new).is_some() {
panic!("Duplicate old name `{old}`");
}
});
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 node_path_without_type_args = protonode_id.name.split('<').next();
if let Some(new) = node_path_without_type_args.and_then(|node_path| replacements.get(node_path)) {
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, &mut default_template);
document.network_interface.set_manual_compostion(node_id, &network_path, Some(graph_craft::Type::Generic("T".into())));
}
default_template.document_node.implementation = DocumentNodeImplementation::ProtoNode(new.clone());
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())));
}
}
}
@@ -341,7 +635,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
}
// 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 {
if reference == "Text" && inputs_count != 10 {
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)?;
@@ -395,6 +689,15 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
},
network_path,
);
document.network_interface.set_input(
&InputConnector::node(*node_id, 9),
if inputs_count >= 10 {
old_inputs[9].clone()
} else {
NodeInput::value(TaggedValue::Bool(false), 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
@@ -666,6 +969,22 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
}
}
// Add the "Depth" parameter to the "Instance Index" node
if reference == "Instance Index" && inputs_count == 0 {
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 mut node_path = network_path.to_vec();
node_path.push(*node_id);
document.network_interface.add_import(TaggedValue::None, false, 0, "Primary", "", &node_path);
document.network_interface.add_import(TaggedValue::U32(0), false, 1, "Loop Level", "TODO", &node_path);
}
// ==================================
// PUT ALL MIGRATIONS ABOVE THIS LINE
// ==================================
// 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::<Vec<_>>();
@@ -691,3 +1010,20 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
Some(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_no_duplicate_node_replacements() {
let mut hashmap = HashMap::<ProtoNodeIdentifier, u32>::new();
NODE_REPLACEMENTS.iter().for_each(|node| {
*hashmap.entry(node.node.clone()).or_default() += 1;
});
let duplicates = hashmap.iter().filter(|(_, count)| **count > 1).map(|(node, _)| &node.name).collect::<Vec<_>>();
if duplicates.len() > 0 {
panic!("Duplicate entries in `NODE_REPLACEMENTS`: {:?}", duplicates);
}
}
}
@@ -19,11 +19,12 @@ pub struct MenuBarMessageHandler {
pub spreadsheet_view_open: bool,
pub message_logging_verbosity: MessageLoggingVerbosity,
pub reset_node_definitions_on_open: bool,
pub single_path_node_compatible_layer_selected: bool,
}
#[message_handler_data]
impl MessageHandler<MenuBarMessage, ()> for MenuBarMessageHandler {
fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque<Message>, _data: ()) {
fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque<Message>, _: ()) {
match message {
MenuBarMessage::SendLayout => self.send_layout(responses, LayoutTarget::MenuBar),
}
@@ -45,6 +46,7 @@ impl LayoutHolder for MenuBarMessageHandler {
let message_logging_verbosity_names = self.message_logging_verbosity == MessageLoggingVerbosity::Names;
let message_logging_verbosity_contents = self.message_logging_verbosity == MessageLoggingVerbosity::Contents;
let reset_node_definitions_on_open = self.reset_node_definitions_on_open;
let single_path_node_compatible_layer_selected = self.single_path_node_compatible_layer_selected;
let menu_bar_entries = vec![
MenuBarEntry {
@@ -418,9 +420,8 @@ impl LayoutHolder for MenuBarMessageHandler {
disabled: no_active_document || !has_selected_layers,
children: MenuBarEntryChildren(vec![{
let list = <BooleanOperation as graphene_std::registry::ChoiceTypeStatic>::list();
list.into_iter()
.map(|i| i.into_iter())
.flatten()
list.iter()
.flat_map(|i| i.iter())
.map(move |(operation, info)| MenuBarEntry {
label: info.label.to_string(),
icon: info.icon.as_ref().map(|i| i.to_string()),
@@ -436,6 +437,14 @@ impl LayoutHolder for MenuBarMessageHandler {
..MenuBarEntry::default()
},
],
vec![MenuBarEntry {
label: "Make Path Editable".into(),
icon: Some("NodeShape".into()),
shortcut: None,
action: MenuBarEntry::create_action(|_| NodeGraphMessage::AddPathNode.into()),
disabled: !single_path_node_compatible_layer_selected,
..MenuBarEntry::default()
}],
]),
),
MenuBarEntry::new_root(
+1 -1
View File
@@ -10,4 +10,4 @@ pub mod utility_types;
#[doc(inline)]
pub use portfolio_message::{PortfolioMessage, PortfolioMessageDiscriminant};
#[doc(inline)]
pub use portfolio_message_handler::{PortfolioMessageData, PortfolioMessageHandler};
pub use portfolio_message_handler::{PortfolioMessageContext, PortfolioMessageHandler};
@@ -19,6 +19,7 @@ pub enum PortfolioMessage {
Spreadsheet(SpreadsheetMessage),
// Messages
Init,
DocumentPassMessage {
document_id: DocumentId,
message: DocumentMessage,
@@ -9,24 +9,27 @@ use crate::messages::debug::utility_types::MessageLoggingVerbosity;
use crate::messages::dialog::simple_dialogs;
use crate::messages::frontend::utility_types::FrontendDocumentDetails;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::DocumentMessageData;
use crate::messages::portfolio::document::DocumentMessageContext;
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
use crate::messages::portfolio::document::node_graph::document_node_definitions;
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;
use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
use glam::{DAffine2, DVec2};
use graph_craft::document::NodeId;
use graph_craft::document::value::TaggedValue;
use graphene_std::renderer::Quad;
use graphene_std::text::Font;
use std::vec;
#[derive(ExtractField)]
pub struct PortfolioMessageData<'a> {
pub struct PortfolioMessageContext<'a> {
pub ipp: &'a InputPreprocessorMessageHandler,
pub preferences: &'a PreferencesMessageHandler,
pub current_tool: &'a ToolType,
@@ -54,9 +57,9 @@ pub struct PortfolioMessageHandler {
}
#[message_handler_data]
impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMessageHandler {
fn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque<Message>, data: PortfolioMessageData) {
let PortfolioMessageData {
impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for PortfolioMessageHandler {
fn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque<Message>, context: PortfolioMessageContext) {
let PortfolioMessageContext {
ipp,
preferences,
current_tool,
@@ -64,7 +67,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
reset_node_definitions_on_open,
timing_information,
animation,
} = data;
} = context;
match message {
// Sub-messages
@@ -77,6 +80,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
self.menu_bar_message_handler.has_selected_nodes = false;
self.menu_bar_message_handler.has_selected_layers = false;
self.menu_bar_message_handler.has_selection_history = (false, false);
self.menu_bar_message_handler.single_path_node_compatible_layer_selected = false;
self.menu_bar_message_handler.spreadsheet_view_open = self.spreadsheet.spreadsheet_view_open;
self.menu_bar_message_handler.message_logging_verbosity = message_logging_verbosity;
self.menu_bar_message_handler.reset_node_definitions_on_open = reset_node_definitions_on_open;
@@ -94,6 +98,30 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
let metadata = &document.network_interface.document_network_metadata().persistent_metadata;
(!metadata.selection_undo_history.is_empty(), !metadata.selection_redo_history.is_empty())
};
self.menu_bar_message_handler.single_path_node_compatible_layer_selected = {
let selected_nodes = document.network_interface.selected_nodes();
let mut selected_layers = selected_nodes.selected_layers(document.metadata());
let first_layer = selected_layers.next();
let second_layer = selected_layers.next();
let has_single_selection = first_layer.is_some() && second_layer.is_none();
let compatible_type = first_layer.and_then(|layer| {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface);
graph_layer.horizontal_layer_flow().nth(1).and_then(|node_id| {
let (output_type, _) = document.network_interface.output_type(&node_id, 0, &[]);
Some(format!("type:{}", output_type.nested_type()))
})
});
let is_compatible = compatible_type.as_deref() == Some("type:Instances<VectorData>");
let is_modifiable = first_layer.map_or(false, |layer| {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface);
matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_)))
});
first_layer.is_some() && has_single_selection && is_compatible && !is_modifiable
}
}
self.menu_bar_message_handler.process_message(message, responses, ());
@@ -104,7 +132,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
PortfolioMessage::Document(message) => {
if let Some(document_id) = self.active_document_id {
if let Some(document) = self.documents.get_mut(&document_id) {
let document_inputs = DocumentMessageData {
let document_inputs = DocumentMessageContext {
document_id,
ipp,
persistent_data: &self.persistent_data,
@@ -119,9 +147,26 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
}
// Messages
PortfolioMessage::Init => {
// Load persistent data from the browser database
responses.add(FrontendMessage::TriggerLoadFirstAutoSaveDocument);
responses.add(FrontendMessage::TriggerLoadPreferences);
// Display the menu bar at the top of the window
responses.add(MenuBarMessage::SendLayout);
// Send the information for tooltips and categories for each node/input.
responses.add(FrontendMessage::SendUIMetadata {
node_descriptions: document_node_definitions::collect_node_descriptions(),
node_types: document_node_definitions::collect_node_types(),
});
// Finish loading persistent data from the browser database
responses.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments);
}
PortfolioMessage::DocumentPassMessage { document_id, message } => {
if let Some(document) = self.documents.get_mut(&document_id) {
let document_inputs = DocumentMessageData {
let document_inputs = DocumentMessageContext {
document_id,
ipp,
persistent_data: &self.persistent_data,
@@ -744,6 +789,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
responses.add(DocumentMessage::GraphViewOverlay { open: node_graph_open });
if node_graph_open {
responses.add(NodeGraphMessage::UpdateGraphBarRight);
responses.add(NodeGraphMessage::UnloadWires);
responses.add(NodeGraphMessage::SendWires)
} else {
responses.add(PortfolioMessage::UpdateDocumentWidgets);
}
@@ -972,7 +1019,9 @@ impl PortfolioMessageHandler {
/text>"#
// It's a mystery why the `/text>` tag above needs to be missing its `<`, but when it exists it prints the `<` character in the text. However this works with it removed.
.to_string();
responses.add(Message::EndBuffer(graphene_std::renderer::RenderMetadata::default()));
responses.add(Message::EndBuffer {
render_metadata: graphene_std::renderer::RenderMetadata::default(),
});
responses.add(FrontendMessage::UpdateDocumentArtwork { svg: error });
}
result
@@ -27,7 +27,7 @@ pub struct SpreadsheetMessageHandler {
#[message_handler_data]
impl MessageHandler<SpreadsheetMessage, ()> for SpreadsheetMessageHandler {
fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque<Message>, _data: ()) {
fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque<Message>, _: ()) {
match message {
SpreadsheetMessage::ToggleOpen => {
self.spreadsheet_view_open = !self.spreadsheet_view_open;
@@ -46,7 +46,7 @@ impl Default for PreferencesMessageHandler {
#[message_handler_data]
impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
fn process_message(&mut self, message: PreferencesMessage, responses: &mut VecDeque<Message>, _data: ()) {
fn process_message(&mut self, message: PreferencesMessage, responses: &mut VecDeque<Message>, _: ()) {
match message {
// Management messages
PreferencesMessage::Load { preferences } => {
+12 -12
View File
@@ -5,28 +5,28 @@ pub use crate::utility_types::{DebugMessageTree, MessageData};
pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler};
pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};
pub use crate::messages::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler};
pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageData, ExportDialogMessageDiscriminant, ExportDialogMessageHandler};
pub use crate::messages::dialog::export_dialog::{ExportDialogMessage, ExportDialogMessageContext, ExportDialogMessageDiscriminant, ExportDialogMessageHandler};
pub use crate::messages::dialog::new_document_dialog::{NewDocumentDialogMessage, NewDocumentDialogMessageDiscriminant, NewDocumentDialogMessageHandler};
pub use crate::messages::dialog::preferences_dialog::{PreferencesDialogMessage, PreferencesDialogMessageData, PreferencesDialogMessageDiscriminant, PreferencesDialogMessageHandler};
pub use crate::messages::dialog::{DialogMessage, DialogMessageData, DialogMessageDiscriminant, DialogMessageHandler};
pub use crate::messages::dialog::preferences_dialog::{PreferencesDialogMessage, PreferencesDialogMessageContext, PreferencesDialogMessageDiscriminant, PreferencesDialogMessageHandler};
pub use crate::messages::dialog::{DialogMessage, DialogMessageContext, DialogMessageDiscriminant, DialogMessageHandler};
pub use crate::messages::frontend::{FrontendMessage, FrontendMessageDiscriminant};
pub use crate::messages::globals::{GlobalsMessage, GlobalsMessageDiscriminant, GlobalsMessageHandler};
pub use crate::messages::input_mapper::key_mapping::{KeyMappingMessage, KeyMappingMessageData, KeyMappingMessageDiscriminant, KeyMappingMessageHandler};
pub use crate::messages::input_mapper::{InputMapperMessage, InputMapperMessageData, InputMapperMessageDiscriminant, InputMapperMessageHandler};
pub use crate::messages::input_preprocessor::{InputPreprocessorMessage, InputPreprocessorMessageData, InputPreprocessorMessageDiscriminant, InputPreprocessorMessageHandler};
pub use crate::messages::input_mapper::key_mapping::{KeyMappingMessage, KeyMappingMessageContext, KeyMappingMessageDiscriminant, KeyMappingMessageHandler};
pub use crate::messages::input_mapper::{InputMapperMessage, InputMapperMessageContext, InputMapperMessageDiscriminant, InputMapperMessageHandler};
pub use crate::messages::input_preprocessor::{InputPreprocessorMessage, InputPreprocessorMessageContext, InputPreprocessorMessageDiscriminant, InputPreprocessorMessageHandler};
pub use crate::messages::layout::{LayoutMessage, LayoutMessageDiscriminant, LayoutMessageHandler};
pub use crate::messages::portfolio::document::graph_operation::{GraphOperationMessage, GraphOperationMessageData, GraphOperationMessageDiscriminant, GraphOperationMessageHandler};
pub use crate::messages::portfolio::document::navigation::{NavigationMessage, NavigationMessageData, NavigationMessageDiscriminant, NavigationMessageHandler};
pub use crate::messages::portfolio::document::graph_operation::{GraphOperationMessage, GraphOperationMessageContext, GraphOperationMessageDiscriminant, GraphOperationMessageHandler};
pub use crate::messages::portfolio::document::navigation::{NavigationMessage, NavigationMessageContext, NavigationMessageDiscriminant, NavigationMessageHandler};
pub use crate::messages::portfolio::document::node_graph::{NodeGraphMessage, NodeGraphMessageDiscriminant, NodeGraphMessageHandler};
pub use crate::messages::portfolio::document::overlays::{OverlaysMessage, OverlaysMessageData, OverlaysMessageDiscriminant, OverlaysMessageHandler};
pub use crate::messages::portfolio::document::overlays::{OverlaysMessage, OverlaysMessageContext, OverlaysMessageDiscriminant, OverlaysMessageHandler};
pub use crate::messages::portfolio::document::properties_panel::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant, PropertiesPanelMessageHandler};
pub use crate::messages::portfolio::document::{DocumentMessage, DocumentMessageData, DocumentMessageDiscriminant, DocumentMessageHandler};
pub use crate::messages::portfolio::document::{DocumentMessage, DocumentMessageContext, DocumentMessageDiscriminant, DocumentMessageHandler};
pub use crate::messages::portfolio::menu_bar::{MenuBarMessage, MenuBarMessageDiscriminant, MenuBarMessageHandler};
pub use crate::messages::portfolio::spreadsheet::{SpreadsheetMessage, SpreadsheetMessageDiscriminant};
pub use crate::messages::portfolio::{PortfolioMessage, PortfolioMessageData, PortfolioMessageDiscriminant, PortfolioMessageHandler};
pub use crate::messages::portfolio::{PortfolioMessage, PortfolioMessageContext, PortfolioMessageDiscriminant, PortfolioMessageHandler};
pub use crate::messages::preferences::{PreferencesMessage, PreferencesMessageDiscriminant, PreferencesMessageHandler};
pub use crate::messages::tool::transform_layer::{TransformLayerMessage, TransformLayerMessageDiscriminant, TransformLayerMessageHandler};
pub use crate::messages::tool::{ToolMessage, ToolMessageData, ToolMessageDiscriminant, ToolMessageHandler};
pub use crate::messages::tool::{ToolMessage, ToolMessageContext, ToolMessageDiscriminant, ToolMessageHandler};
pub use crate::messages::workspace::{WorkspaceMessage, WorkspaceMessageDiscriminant, WorkspaceMessageHandler};
// Message, MessageDiscriminant
@@ -357,7 +357,7 @@ pub fn get_text_id(layer: LayerNodeIdentifier, network_interface: &NodeNetworkIn
}
/// Gets properties from the Text node
pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<(&String, &Font, TypesettingConfig)> {
pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<(&String, &Font, TypesettingConfig, bool)> {
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Text")?;
let Some(TaggedValue::String(text)) = &inputs[1].as_value() else { return None };
@@ -368,6 +368,9 @@ pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInter
let Some(&TaggedValue::OptionalF64(max_width)) = inputs[6].as_value() else { return None };
let Some(&TaggedValue::OptionalF64(max_height)) = inputs[7].as_value() else { return None };
let Some(&TaggedValue::F64(tilt)) = inputs[8].as_value() else { return None };
let Some(TaggedValue::Bool(per_glyph_instances)) = &inputs[9].as_value() else {
return None;
};
let typesetting = TypesettingConfig {
font_size,
@@ -377,7 +380,7 @@ pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInter
max_height,
tilt,
};
Some((text, font, typesetting))
Some((text, font, typesetting, *per_glyph_instances))
}
pub fn get_stroke_width(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<f64> {
@@ -1000,7 +1000,7 @@ impl ShapeState {
} else {
// Push both in and out handles into the correct position
for ((handle, sign), other_anchor) in handles.iter().zip([1., -1.]).zip(&anchor_positions) {
let Some(anchor_vector) = other_anchor.map(|position| (position - anchor_position)) else {
let Some(anchor_vector) = other_anchor.map(|position| position - anchor_position) else {
continue;
};
@@ -4,7 +4,7 @@ mod grid_snapper;
mod layer_snapper;
mod snap_results;
use crate::consts::{COLOR_OVERLAY_BLUE, COLOR_OVERLAY_LABEL_BACKGROUND, COLOR_OVERLAY_WHITE};
use crate::consts::{COLOR_OVERLAY_BLACK_75, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_WHITE};
use crate::messages::portfolio::document::overlays::utility_types::{OverlayContext, Pivot};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::misc::{GridSnapTarget, PathSnapTarget, SnapTarget};
@@ -482,7 +482,7 @@ impl SnapManager {
if !any_align && ind.distribution_equal_distance_horizontal.is_none() && ind.distribution_equal_distance_vertical.is_none() {
let text = format!("[{}] from [{}]", ind.target, ind.source);
let transform = DAffine2::from_translation(viewport - DVec2::new(0., 4.));
overlay_context.text(&text, COLOR_OVERLAY_WHITE, Some(COLOR_OVERLAY_LABEL_BACKGROUND), transform, 4., [Pivot::Start, Pivot::End]);
overlay_context.text(&text, COLOR_OVERLAY_WHITE, Some(COLOR_OVERLAY_BLACK_75), transform, 4., [Pivot::Start, Pivot::End]);
overlay_context.square(viewport, Some(4.), Some(COLOR_OVERLAY_BLUE), Some(COLOR_OVERLAY_BLUE));
}
}
@@ -66,14 +66,22 @@ where
/// Calculates the bounding box of the layer's text, based on the settings for max width and height specified in the typesetting config.
pub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageHandler, font_cache: &FontCache) -> Quad {
let Some((text, font, typesetting)) = get_text(layer, &document.network_interface) else {
let Some((text, font, typesetting, per_glyph_instances)) = get_text(layer, &document.network_interface) else {
return Quad::from_box([DVec2::ZERO, DVec2::ZERO]);
};
let font_data = font_cache.get(font).map(|data| load_font(data));
let far = graphene_std::text::bounding_box(text, font_data, typesetting, false);
Quad::from_box([DVec2::ZERO, far])
// TODO: Once the instances refactor is complete and per_glyph_instances can be removed (since it'll be the default),
// TODO: remove this because the top of the dashed bounding overlay should no longer be based on the first line's baseline.
let vertical_offset = if per_glyph_instances {
DVec2::NEG_Y * typesetting.font_size * (1. + (typesetting.line_height_ratio - 1.) / 2.)
} else {
DVec2::ZERO
};
Quad::from_box([DVec2::ZERO + vertical_offset, far + vertical_offset])
}
pub fn calculate_segment_angle(anchor: PointId, segment: SegmentId, vector_data: &VectorData, prefer_handle_direction: bool) -> Option<f64> {
+1 -1
View File
@@ -9,6 +9,6 @@ pub mod utility_types;
#[doc(inline)]
pub use tool_message::{ToolMessage, ToolMessageDiscriminant};
#[doc(inline)]
pub use tool_message_handler::{ToolMessageData, ToolMessageHandler};
pub use tool_message_handler::{ToolMessageContext, ToolMessageHandler};
#[doc(inline)]
pub use transform_layer::{TransformLayerMessage, TransformLayerMessageDiscriminant};
+1
View File
@@ -87,6 +87,7 @@ pub enum ToolMessage {
SelectRandomWorkingColor {
primary: bool,
},
ToggleSelectVsPath,
SwapColors,
Undo,
UpdateCursor,
@@ -1,19 +1,20 @@
use super::common_functionality::shape_editor::ShapeState;
use super::common_functionality::shapes::shape_utility::ShapeType::{self, Ellipse, Line, Rectangle};
use super::utility_types::{ToolActionHandlerData, ToolFsmState, tool_message_to_tool_type};
use super::utility_types::{ToolActionMessageContext, ToolFsmState, tool_message_to_tool_type};
use crate::application::generate_uuid;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::overlays::utility_types::OverlayProvider;
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*;
use crate::messages::tool::transform_layer::transform_layer_message_handler::TransformLayerMessageContext;
use crate::messages::tool::utility_types::ToolType;
use crate::node_graph_executor::NodeGraphExecutor;
use graphene_std::raster::color::Color;
const ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |context| DocumentMessage::DrawArtboardOverlays(context).into();
const ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |overlay_context| DocumentMessage::DrawArtboardOverlays(overlay_context).into();
#[derive(ExtractField)]
pub struct ToolMessageData<'a> {
pub struct ToolMessageContext<'a> {
pub document_id: DocumentId,
pub document: &'a mut DocumentMessageHandler,
pub input: &'a InputPreprocessorMessageHandler,
@@ -31,23 +32,30 @@ pub struct ToolMessageHandler {
}
#[message_handler_data]
impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, data: ToolMessageData) {
let ToolMessageData {
impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: ToolMessageContext) {
let ToolMessageContext {
document_id,
document,
input,
persistent_data,
node_graph,
preferences,
} = data;
} = context;
let font_cache = &persistent_data.font_cache;
match message {
// Messages
ToolMessage::TransformLayer(message) => self
.transform_layer_handler
.process_message(message, responses, (document, input, &self.tool_state.tool_data, &mut self.shape_editor)),
ToolMessage::TransformLayer(message) => self.transform_layer_handler.process_message(
message,
responses,
TransformLayerMessageContext {
document,
input,
tool_data: &self.tool_state.tool_data,
shape_editor: &mut self.shape_editor,
},
),
ToolMessage::ActivateToolSelect => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Select }),
ToolMessage::ActivateToolArtboard => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Artboard }),
@@ -106,7 +114,7 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
// Send the old and new tools a transition to their FSM Abort states
let mut send_abort_to_tool = |old_tool: ToolType, new_tool: ToolType, update_hints_and_cursor: bool| {
if let Some(tool) = tool_data.tools.get_mut(&new_tool) {
let mut data = ToolActionHandlerData {
let mut data = ToolActionMessageContext {
document,
document_id,
global_tool_data: &self.tool_state.document_tool_data,
@@ -206,7 +214,7 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
// Notify the frontend about the initial working colors
document_data.update_working_colors(responses);
let mut data = ToolActionHandlerData {
let mut data = ToolActionMessageContext {
document,
document_id,
global_tool_data: &self.tool_state.document_tool_data,
@@ -276,6 +284,16 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
document_data.update_working_colors(responses); // TODO: Make this an event
}
ToolMessage::ToggleSelectVsPath => {
// If we have the select tool active, toggle to the path tool and vice versa
let tool_data = &mut self.tool_state.tool_data;
let active_tool_type = tool_data.active_tool_type;
if active_tool_type == ToolType::Select {
responses.add(ToolMessage::ActivateTool { tool_type: ToolType::Path });
} else {
responses.add(ToolMessage::ActivateTool { tool_type: ToolType::Select });
}
}
ToolMessage::SwapColors => {
let document_data = &mut self.tool_state.document_tool_data;
@@ -302,7 +320,7 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
let graph_view_overlay_open = document.graph_view_overlay_open();
if tool_type == tool_data.active_tool_type {
let mut data = ToolActionHandlerData {
let mut data = ToolActionMessageContext {
document,
document_id,
global_tool_data: &self.tool_state.document_tool_data,
@@ -351,9 +369,12 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
ActivateToolBrush,
ToggleSelectVsPath,
SelectRandomWorkingColor,
ResetColors,
SwapColors,
Undo,
);
list.extend(self.tool_state.tool_data.active_tool().actions());
@@ -49,9 +49,9 @@ impl ToolMetadata for ArtboardTool {
}
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ArtboardTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, false);
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for ArtboardTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
self.fsm_state.process_event(message, &mut self.data, context, &(), responses, false);
}
fn actions(&self) -> ActionList {
@@ -218,8 +218,8 @@ impl Fsm for ArtboardToolFsmState {
type ToolData = ArtboardToolData;
type ToolOptions = ();
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
let ToolActionHandlerData { document, input, .. } = tool_action_data;
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionMessageContext, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
let ToolActionMessageContext { document, input, .. } = tool_action_data;
let hovered = ArtboardToolData::hovered_artboard(document, input).is_some();
@@ -186,10 +186,10 @@ impl LayoutHolder for BrushTool {
}
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for BrushTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for BrushTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message else {
self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true);
self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, true);
return;
};
match action {
@@ -306,8 +306,15 @@ impl Fsm for BrushToolFsmState {
type ToolData = BrushToolData;
type ToolOptions = BrushOptions;
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
let ToolActionHandlerData {
fn transition(
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
tool_action_data: &mut ToolActionMessageContext,
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
let ToolActionMessageContext {
document, global_tool_data, input, ..
} = tool_action_data;
@@ -40,9 +40,9 @@ impl LayoutHolder for EyedropperTool {
}
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for EyedropperTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true);
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for EyedropperTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
self.fsm_state.process_event(message, &mut self.data, context, &(), responses, true);
}
advertise_actions!(EyedropperToolMessageDiscriminant;
@@ -80,8 +80,8 @@ impl Fsm for EyedropperToolFsmState {
type ToolData = EyedropperToolData;
type ToolOptions = ();
fn transition(self, event: ToolMessage, _tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
let ToolActionHandlerData { global_tool_data, input, .. } = tool_action_data;
fn transition(self, event: ToolMessage, _tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionMessageContext, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
let ToolActionMessageContext { global_tool_data, input, .. } = tool_action_data;
let ToolMessage::Eyedropper(event) = event else { return self };
match (self, event) {
@@ -42,9 +42,9 @@ impl LayoutHolder for FillTool {
}
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FillTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut (), tool_data, &(), responses, true);
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for FillTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
self.fsm_state.process_event(message, &mut (), context, &(), responses, true);
}
fn actions(&self) -> ActionList {
match self.fsm_state {
@@ -85,8 +85,15 @@ impl Fsm for FillToolFsmState {
type ToolData = ();
type ToolOptions = ();
fn transition(self, event: ToolMessage, _tool_data: &mut Self::ToolData, handler_data: &mut ToolActionHandlerData, _tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
let ToolActionHandlerData {
fn transition(
self,
event: ToolMessage,
_tool_data: &mut Self::ToolData,
handler_data: &mut ToolActionMessageContext,
_tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
let ToolActionMessageContext {
document, global_tool_data, input, ..
} = handler_data;
@@ -117,10 +117,10 @@ impl LayoutHolder for FreehandTool {
}
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FreehandTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for FreehandTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message else {
self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, true);
self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, true);
return;
};
match action {
@@ -184,8 +184,15 @@ impl Fsm for FreehandToolFsmState {
type ToolData = FreehandToolData;
type ToolOptions = FreehandOptions;
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
let ToolActionHandlerData {
fn transition(
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
tool_action_data: &mut ToolActionMessageContext,
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
let ToolActionMessageContext {
document,
global_tool_data,
input,
@@ -54,10 +54,10 @@ impl ToolMetadata for GradientTool {
}
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for GradientTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for GradientTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message else {
self.fsm_state.process_event(message, &mut self.data, tool_data, &self.options, responses, false);
self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, false);
return;
};
match action {
@@ -67,7 +67,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for Gradien
if let Some(selected_gradient) = &mut self.data.selected_gradient {
// Check if the current layer is a raster layer
if let Some(layer) = selected_gradient.layer {
if NodeGraphLayer::is_raster_layer(layer, &mut tool_data.document.network_interface) {
if NodeGraphLayer::is_raster_layer(layer, &mut context.document.network_interface) {
return; // Don't proceed if it's a raster layer
}
selected_gradient.gradient.gradient_type = gradient_type;
@@ -243,8 +243,15 @@ impl Fsm for GradientToolFsmState {
type ToolData = GradientToolData;
type ToolOptions = GradientOptions;
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
let ToolActionHandlerData {
fn transition(
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
tool_action_data: &mut ToolActionMessageContext,
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
let ToolActionMessageContext {
document, global_tool_data, input, ..
} = tool_action_data;
@@ -17,7 +17,7 @@ pub mod tool_prelude {
pub use crate::messages::input_mapper::utility_types::input_keyboard::{Key, MouseMotion};
pub use crate::messages::layout::utility_types::widget_prelude::*;
pub use crate::messages::prelude::*;
pub use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionHandlerData, ToolMetadata, ToolTransition, ToolType};
pub use crate::messages::tool::utility_types::{EventToMessageMap, Fsm, ToolActionMessageContext, ToolMetadata, ToolTransition, ToolType};
pub use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
pub use glam::{DAffine2, DVec2};
}
@@ -39,9 +39,9 @@ impl LayoutHolder for NavigateTool {
}
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for NavigateTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for NavigateTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
self.fsm_state.process_event(message, &mut self.tool_data, context, &(), responses, true);
}
fn actions(&self) -> ActionList {
@@ -92,7 +92,7 @@ impl Fsm for NavigateToolFsmState {
self,
message: ToolMessage,
tool_data: &mut Self::ToolData,
ToolActionHandlerData { input, .. }: &mut ToolActionHandlerData,
ToolActionMessageContext { input, .. }: &mut ToolActionMessageContext,
_tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
@@ -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, DOUBLE_CLICK_MILLISECONDS, 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_GRAY, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DOUBLE_CLICK_MILLISECONDS, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, DRILL_THROUGH_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,6 +11,7 @@ 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::graph_modification_utils;
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, SelectedLayerState, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState,
@@ -18,8 +19,10 @@ use crate::messages::tool::common_functionality::shape_editor::{
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, BezierHandles, TValue};
use graph_craft::document::value::TaggedValue;
use graphene_std::renderer::Quad;
use graphene_std::transform::ReferencePoint;
use graphene_std::vector::click_target::ClickTargetType;
use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, VectorData};
use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType};
use std::vec;
@@ -263,6 +266,14 @@ impl LayoutHolder for PathTool {
.selected_index(Some(self.options.path_overlay_mode as u32))
.widget_holder();
// Works only if a single layer is selected and its type is vectordata
let path_node_button = TextButton::new("Make Path Editable")
.icon(Some("NodeShape".into()))
.tooltip("Make Path Editable")
.on_update(|_| NodeGraphMessage::AddPathNode.into())
.disabled(!self.tool_data.single_path_node_compatible_layer_selected)
.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()]
@@ -293,6 +304,7 @@ impl LayoutHolder for PathTool {
unrelated_seperator.clone(),
path_overlay_mode_widget,
unrelated_seperator.clone(),
path_node_button,
// checkbox.clone(),
// related_seperator.clone(),
// dropdown.clone(),
@@ -306,8 +318,8 @@ impl LayoutHolder for PathTool {
}
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for PathTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
let updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated);
match message {
@@ -350,20 +362,20 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
},
ToolMessage::Path(PathToolMessage::ClosePath) => {
responses.add(DocumentMessage::AddTransaction);
tool_data.shape_editor.close_selected_path(tool_data.document, responses);
context.shape_editor.close_selected_path(context.document, responses);
responses.add(DocumentMessage::EndTransaction);
responses.add(OverlaysMessage::Draw);
}
ToolMessage::Path(PathToolMessage::SwapSelectedHandles) => {
if tool_data.shape_editor.handle_with_pair_selected(&tool_data.document.network_interface) {
tool_data.shape_editor.alternate_selected_handles(&tool_data.document.network_interface);
if context.shape_editor.handle_with_pair_selected(&context.document.network_interface) {
context.shape_editor.alternate_selected_handles(&context.document.network_interface);
responses.add(PathToolMessage::SelectedPointUpdated);
responses.add(FrontendMessage::UpdateMouseCursor { cursor: MouseCursorIcon::None });
responses.add(OverlaysMessage::Draw);
}
}
_ => {
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true);
}
}
@@ -516,6 +528,12 @@ struct PathToolData {
started_drawing_from_inside: bool,
first_selected_with_single_click: bool,
stored_selection: Option<HashMap<LayerNodeIdentifier, SelectedLayerState>>,
last_drill_through_click_position: Option<DVec2>,
drill_through_cycle_index: usize,
drill_through_cycle_count: usize,
hovered_layers: Vec<LayerNodeIdentifier>,
ghost_outline: Vec<(Vec<ClickTargetType>, DAffine2)>,
single_path_node_compatible_layer_selected: bool,
}
impl PathToolData {
@@ -524,10 +542,6 @@ impl PathToolData {
PathToolFsmState::Dragging(self.dragging_state)
}
fn remove_saved_points(&mut self) {
self.saved_points_before_anchor_select_toggle.clear();
}
pub fn selection_quad(&self, metadata: &DocumentMetadata) -> Quad {
let bbox = self.selection_box(metadata);
Quad::from_box(bbox)
@@ -576,6 +590,49 @@ impl PathToolData {
self.selection_status = selection_status;
}
fn remove_saved_points(&mut self) {
self.saved_points_before_anchor_select_toggle.clear();
}
fn reset_drill_through_cycle(&mut self) {
self.last_drill_through_click_position = None;
self.drill_through_cycle_index = 0;
}
fn next_drill_through_cycle(&mut self, position: DVec2) -> usize {
if self.last_drill_through_click_position.map_or(true, |last_pos| last_pos.distance(position) > DRILL_THROUGH_THRESHOLD) {
// New position, reset cycle
self.drill_through_cycle_index = 0;
} else {
// Same position, advance cycle
self.drill_through_cycle_index = (self.drill_through_cycle_index + 1) % self.drill_through_cycle_count.max(1);
}
self.last_drill_through_click_position = Some(position);
self.drill_through_cycle_index
}
fn peek_drill_through_index(&self) -> usize {
if self.drill_through_cycle_count == 0 {
0
} else {
(self.drill_through_cycle_index + 1) % self.drill_through_cycle_count.max(1)
}
}
fn has_drill_through_mouse_moved(&self, position: DVec2) -> bool {
self.last_drill_through_click_position.map_or(true, |last_pos| last_pos.distance(position) > DRILL_THROUGH_THRESHOLD)
}
fn set_ghost_outline(&mut self, shape_editor: &ShapeState, document: &DocumentMessageHandler) {
self.ghost_outline.clear();
for &layer in shape_editor.selected_shape_state.keys() {
// We probably need to collect here
let outline: Vec<ClickTargetType> = document.metadata().layer_with_free_points_outline(layer).cloned().collect();
let transform = document.metadata().transform_to_viewport(layer);
self.ghost_outline.push((outline, transform));
}
}
// TODO: This function is for basic point select mode. We definitely need to make a new one for the segment select mode.
#[allow(clippy::too_many_arguments)]
fn mouse_down(
@@ -619,6 +676,8 @@ impl PathToolData {
) {
responses.add(DocumentMessage::StartTransaction);
self.set_ghost_outline(shape_editor, document);
self.last_clicked_point_was_selected = already_selected;
// If the point is already selected and shift (`extend_selection`) is used, keep the selection unchanged.
@@ -703,6 +762,8 @@ impl PathToolData {
else if let Some(segment) = shape_editor.upper_closest_segment(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD) {
responses.add(DocumentMessage::StartTransaction);
self.set_ghost_outline(shape_editor, document);
if segment_editing_mode && !molding_in_segment_edit {
let layer = segment.layer();
let segment_id = segment.segment();
@@ -723,7 +784,6 @@ impl PathToolData {
}
self.drag_start_pos = input.mouse.position;
let viewport_to_document = document.metadata().document_to_viewport.inverse();
self.previous_mouse_position = viewport_to_document.transform_point2(input.mouse.position);
@@ -746,6 +806,8 @@ impl PathToolData {
else if let Some(layer) = document.click(input) {
if shape_editor.selected_shape_state.is_empty() {
self.first_selected_with_single_click = true;
// This ensures we don't need to double click a second time to get the drill through to work
self.last_drill_through_click_position = Some(input.mouse.position);
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
}
@@ -1387,8 +1449,15 @@ impl Fsm for PathToolFsmState {
type ToolData = PathToolData;
type ToolOptions = PathToolOptions;
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
let ToolActionHandlerData { document, input, shape_editor, .. } = tool_action_data;
fn transition(
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
tool_action_data: &mut ToolActionMessageContext,
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
let ToolActionMessageContext { document, input, shape_editor, .. } = tool_action_data;
update_dynamic_hints(self, responses, shape_editor, document, tool_data, tool_options);
@@ -1401,6 +1470,7 @@ impl Fsm for PathToolFsmState {
(_, PathToolMessage::SelectionChanged) => {
// Set the newly targeted layers to visible
let target_layers = document.network_interface.selected_nodes().selected_layers(document.metadata()).collect();
shape_editor.set_selected_layers(target_layers);
responses.add(OverlaysMessage::Draw);
@@ -1419,6 +1489,12 @@ impl Fsm for PathToolFsmState {
self
}
(_, PathToolMessage::Overlays(mut overlay_context)) => {
if matches!(self, Self::Dragging(_)) {
for (outline, transform) in &tool_data.ghost_outline {
overlay_context.outline(outline.iter(), *transform, Some(COLOR_OVERLAY_GRAY));
}
}
// TODO: find the segment ids of which the selected points are a part of
match tool_options.path_overlay_mode {
@@ -1520,6 +1596,34 @@ impl Fsm for PathToolFsmState {
}
}
}
// Show outlines for hovered layers with appropriate highlighting
let currently_selected_layer = document.network_interface.selected_nodes().selected_layers(document.metadata()).next();
let next_selected_index = tool_data.peek_drill_through_index();
let mouse_has_moved = tool_data.has_drill_through_mouse_moved(input.mouse.position);
for (index, &hovered_layer) in tool_data.hovered_layers.iter().enumerate() {
// Skip already highlighted selected layer
if Some(hovered_layer) == currently_selected_layer {
continue;
}
let layer_to_viewport = document.metadata().transform_to_viewport(hovered_layer);
let outline = document.metadata().layer_with_free_points_outline(hovered_layer);
// Determine highlight color based on drill-through state
let color = match (index, mouse_has_moved) {
// If the layer is the next selected one and mouse has not moved, highlight it blue
(i, false) if i == next_selected_index => COLOR_OVERLAY_BLUE,
// If the layer is the first hovered one and mouse has moved, highlight it blue
(0, true) => COLOR_OVERLAY_BLUE,
// Otherwise, use gray
_ => COLOR_OVERLAY_GRAY,
};
// TODO: Make this draw underneath all other overlays
overlay_context.outline(outline, layer_to_viewport, Some(color));
}
}
Self::Drawing { selection_shape } => {
let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap())
@@ -1701,9 +1805,9 @@ impl Fsm for PathToolFsmState {
break_molding,
tool_data.temporary_adjacent_handles_while_molding,
);
}
return PathToolFsmState::Dragging(tool_data.dragging_state);
return PathToolFsmState::Dragging(tool_data.dragging_state);
}
}
let anchor_and_handle_toggled = input.keyboard.get(move_anchor_with_handles as usize);
@@ -1787,6 +1891,23 @@ impl Fsm for PathToolFsmState {
tool_data.adjacent_anchor_offset = None;
tool_data.stored_selection = None;
if tool_data.has_drill_through_mouse_moved(input.mouse.position) {
tool_data.reset_drill_through_cycle();
}
// When moving the cursor around we want to update the hovered layers
let new_hovered_layers: Vec<LayerNodeIdentifier> = document
.click_list_no_parents(input)
.filter(|&layer| {
// Filter out artboards and parent holders, and already selected layers
!document.network_interface.is_artboard(&layer.to_node(), &[])
})
.collect();
if tool_data.hovered_layers != new_hovered_layers {
tool_data.hovered_layers = new_hovered_layers;
}
responses.add(OverlaysMessage::Draw);
self
@@ -1987,6 +2108,7 @@ impl Fsm for PathToolFsmState {
PathToolFsmState::Ready
}
(_, PathToolMessage::DragStop { extend_selection, .. }) => {
tool_data.ghost_outline.clear();
let extend_selection = input.keyboard.get(extend_selection as usize);
let drag_occurred = tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD;
@@ -2130,6 +2252,20 @@ impl Fsm for PathToolFsmState {
(_, 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);
let mut get_drill_through_layer = || -> Option<LayerNodeIdentifier> {
let drill_through_layers = document.click_list_no_parents(input).collect::<Vec<LayerNodeIdentifier>>();
if drill_through_layers.is_empty() {
tool_data.reset_drill_through_cycle();
None
} else {
tool_data.drill_through_cycle_count = drill_through_layers.len();
let cycle_index = tool_data.next_drill_through_cycle(input.mouse.position);
let layer = drill_through_layers.get(cycle_index);
if cycle_index == 0 { drill_through_layers.first().copied() } else { layer.copied() }
}
};
if nearest_point.is_some() {
// Flip the selected point between smooth and sharp
if !tool_data.double_click_handled && tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD {
@@ -2146,17 +2282,17 @@ impl Fsm for PathToolFsmState {
return PathToolFsmState::Ready;
}
// Double-clicked on a filled region
else if let Some(layer) = document.click(input) {
else if let Some(layer) = &get_drill_through_layer() {
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 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);
selection.remove(layer);
shape_editor.selected_shape_state = selection;
tool_data.stored_selection = None;
}
@@ -2168,19 +2304,19 @@ impl Fsm for PathToolFsmState {
tool_data.stored_selection = None;
}
let state = shape_editor.selected_shape_state.get_mut(&layer).expect("No state for selected layer");
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);
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);
selection.remove(layer);
for (layer, state) in selection {
shape_editor.selected_shape_state.insert(layer, state);
@@ -2259,6 +2395,31 @@ impl Fsm for PathToolFsmState {
point_select_state: shape_editor.get_dragging_state(&document.network_interface),
colinear,
};
tool_data.single_path_node_compatible_layer_selected = {
let selected_nodes = document.network_interface.selected_nodes();
let mut selected_layers = selected_nodes.selected_layers(document.metadata());
let first_layer = selected_layers.next();
let second_layer = selected_layers.next();
let has_single_selection = first_layer.is_some() && second_layer.is_none();
let compatible_type = first_layer.and_then(|layer| {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface);
graph_layer.horizontal_layer_flow().nth(1).and_then(|node_id| {
let (output_type, _) = document.network_interface.output_type(&node_id, 0, &[]);
Some(format!("type:{}", output_type.nested_type()))
})
});
let is_compatible = compatible_type.as_deref() == Some("type:Instances<VectorData>");
let is_modifiable = first_layer.map_or(false, |layer| {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &document.network_interface);
matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_)))
});
first_layer.is_some() && has_single_selection && is_compatible && !is_modifiable
};
tool_data.update_selection_status(shape_editor, document);
self
}
@@ -187,10 +187,10 @@ impl LayoutHolder for PenTool {
}
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for PenTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message else {
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true);
return;
};
@@ -1403,8 +1403,15 @@ impl Fsm for PenToolFsmState {
type ToolData = PenToolData;
type ToolOptions = PenOptions;
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
let ToolActionHandlerData {
fn transition(
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
tool_action_data: &mut ToolActionMessageContext,
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
let ToolActionMessageContext {
document,
global_tool_data,
input,
@@ -273,8 +273,8 @@ impl LayoutHolder for SelectTool {
}
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SelectTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for SelectTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
let mut redraw_reference_pivot = false;
if let ToolMessage::Select(SelectToolMessage::SelectOptions(ref option_update)) = message {
@@ -309,7 +309,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SelectT
}
}
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, false);
self.fsm_state.process_event(message, &mut self.tool_data, context, &(), responses, false);
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)
@@ -584,8 +584,8 @@ impl Fsm for SelectToolFsmState {
type ToolData = SelectToolData;
type ToolOptions = ();
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
let ToolActionHandlerData { document, input, font_cache, .. } = tool_action_data;
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionMessageContext, _tool_options: &(), responses: &mut VecDeque<Message>) -> Self {
let ToolActionMessageContext { document, input, font_cache, .. } = tool_action_data;
let ToolMessage::Select(event) = event else { return self };
match (self, event) {
@@ -163,10 +163,10 @@ impl LayoutHolder for ShapeTool {
}
}
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ShapeTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for ShapeTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
let ToolMessage::Shape(ShapeToolMessage::UpdateOptions(action)) = message else {
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true);
return;
};
match action {
@@ -337,14 +337,14 @@ impl Fsm for ShapeToolFsmState {
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
ToolActionHandlerData {
ToolActionMessageContext {
document,
global_tool_data,
input,
preferences,
shape_editor,
..
}: &mut ToolActionHandlerData,
}: &mut ToolActionMessageContext,
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
@@ -124,10 +124,10 @@ impl LayoutHolder for SplineTool {
}
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SplineTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for SplineTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message else {
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true);
return;
};
match action {
@@ -242,8 +242,15 @@ impl Fsm for SplineToolFsmState {
type ToolData = SplineToolData;
type ToolOptions = SplineOptions;
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, tool_action_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
let ToolActionHandlerData {
fn transition(
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
tool_action_data: &mut ToolActionMessageContext,
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
let ToolActionMessageContext {
document,
global_tool_data,
input,
@@ -171,10 +171,10 @@ impl LayoutHolder for TextTool {
}
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for TextTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message else {
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &self.options, responses, true);
self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true);
return;
};
match action {
@@ -329,7 +329,7 @@ impl TextToolData {
fn load_layer_text_node(&mut self, document: &DocumentMessageHandler) -> Option<()> {
let transform = document.metadata().transform_to_viewport(self.layer);
let color = graph_modification_utils::get_fill_color(self.layer, &document.network_interface).unwrap_or(Color::BLACK);
let (text, font, typesetting) = graph_modification_utils::get_text(self.layer, &document.network_interface)?;
let (text, font, typesetting, _) = graph_modification_utils::get_text(self.layer, &document.network_interface)?;
self.editing_text = Some(EditingText {
text: text.clone(),
font: font.clone(),
@@ -449,8 +449,15 @@ impl Fsm for TextToolFsmState {
type ToolData = TextToolData;
type ToolOptions = TextOptions;
fn transition(self, event: ToolMessage, tool_data: &mut Self::ToolData, transition_data: &mut ToolActionHandlerData, tool_options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self {
let ToolActionHandlerData {
fn transition(
self,
event: ToolMessage,
tool_data: &mut Self::ToolData,
transition_data: &mut ToolActionMessageContext,
tool_options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
) -> Self {
let ToolActionMessageContext {
document,
global_tool_data,
input,
@@ -517,7 +524,7 @@ impl Fsm for TextToolFsmState {
bounding_box_manager.render_quad(&mut overlay_context);
// Draw red overlay if text is clipped
let transformed_quad = layer_transform * bounds;
if let Some((text, font, typesetting)) = graph_modification_utils::get_text(layer.unwrap(), &document.network_interface) {
if let Some((text, font, typesetting, _)) = graph_modification_utils::get_text(layer.unwrap(), &document.network_interface) {
let font_data = font_cache.get(font).map(|data| load_font(data));
if lines_clipping(text.as_str(), font_data, typesetting) {
overlay_context.line(transformed_quad.0[2], transformed_quad.0[3], Some(COLOR_OVERLAY_RED), Some(3.));
@@ -1,4 +1,4 @@
//! Handles Blender inspired layer transformation with the <kbd>G</kbd> <kbd>R</kbd> and <kbd>S</kbd> keys for grabbing, rotating and scaling.
//! Handles Blender inspired layer transformation with the <kbd>G</kbd>, <kbd>R</kbd>, and <kbd>S</kbd> keys for grabbing, rotating, and scaling.
//!
//! Other features include
//! - Typing a number for a precise transformation
@@ -7,7 +7,7 @@
//! - Escape or right click to cancel
mod transform_layer_message;
mod transform_layer_message_handler;
pub mod transform_layer_message_handler;
#[doc(inline)]
pub use transform_layer_message::{TransformLayerMessage, TransformLayerMessageDiscriminant};
@@ -1,4 +1,4 @@
use crate::consts::{ANGLE_MEASURE_RADIUS_FACTOR, ARC_MEASURE_RADIUS_FACTOR_RANGE, COLOR_OVERLAY_BLUE, SLOWING_DIVISOR};
use crate::consts::{ANGLE_MEASURE_RADIUS_FACTOR, ARC_MEASURE_RADIUS_FACTOR_RANGE, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GRAY, SLOWING_DIVISOR};
use crate::messages::input_mapper::utility_types::input_mouse::{DocumentPosition, ViewportPosition};
use crate::messages::portfolio::document::overlays::utility_types::{OverlayProvider, Pivot};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
@@ -12,6 +12,7 @@ use crate::messages::tool::utility_types::{ToolData, ToolType};
use glam::{DAffine2, DVec2};
use graphene_std::renderer::Quad;
use graphene_std::vector::ManipulatorPointId;
use graphene_std::vector::click_target::ClickTargetType;
use graphene_std::vector::{VectorData, VectorModificationType};
use std::f64::consts::{PI, TAU};
@@ -21,6 +22,14 @@ const TRANSFORM_GRS_OVERLAY_PROVIDER: OverlayProvider = |context| TransformLayer
const SLOW_KEY: Key = Key::Shift;
const INCREMENTS_KEY: Key = Key::Control;
#[derive(ExtractField)]
pub struct TransformLayerMessageContext<'a> {
pub document: &'a DocumentMessageHandler,
pub input: &'a InputPreprocessorMessageHandler,
pub tool_data: &'a ToolData,
pub shape_editor: &'a mut ShapeState,
}
#[derive(Debug, Clone, Default, ExtractField)]
pub struct TransformLayerMessageHandler {
pub transform_operation: TransformOperation,
@@ -53,150 +62,20 @@ pub struct TransformLayerMessageHandler {
handle: DVec2,
last_point: DVec2,
grs_pen_handle: bool,
// Ghost outlines for Path Tool
ghost_outline: Vec<(Vec<ClickTargetType>, DAffine2)>,
}
impl TransformLayerMessageHandler {
pub fn is_transforming(&self) -> bool {
self.transform_operation != TransformOperation::None
}
impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for TransformLayerMessageHandler {
fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, context: TransformLayerMessageContext) {
let TransformLayerMessageContext {
document,
input,
tool_data,
shape_editor,
} = context;
pub fn hints(&self, responses: &mut VecDeque<Message>) {
self.transform_operation.hints(responses, self.local);
}
}
fn calculate_pivot(
document: &DocumentMessageHandler,
selected_points: &Vec<&ManipulatorPointId>,
vector_data: &VectorData,
viewspace: DAffine2,
get_location: impl Fn(&ManipulatorPointId) -> Option<DVec2>,
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::<DVec2>() / 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 position = position();
return (Some((position, position)), bounds);
};
match point {
ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => {
// Get the anchor position and transform it to the pivot
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 position = position();
(Some((position, position)), bounds)
}
}
}
fn project_edge_to_quad(edge: DVec2, quad: &Quad, local: bool, axis_constraint: Axis) -> DVec2 {
match axis_constraint {
Axis::X => {
if local {
edge.project_onto(quad.top_right() - quad.top_left())
} else {
edge.with_y(0.)
}
}
Axis::Y => {
if local {
edge.project_onto(quad.bottom_left() - quad.top_left())
} else {
edge.with_x(0.)
}
}
_ => edge,
}
}
fn update_colinear_handles(selected_layers: &[LayerNodeIdentifier], document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
for &layer in selected_layers {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
for [handle1, handle2] in &vector_data.colinear_manipulators {
let manipulator1 = handle1.to_manipulator_point();
let manipulator2 = handle2.to_manipulator_point();
let Some(anchor) = manipulator1.get_anchor_position(&vector_data) else { continue };
let Some(pos1) = manipulator1.get_position(&vector_data).map(|pos| pos - anchor) else { continue };
let Some(pos2) = manipulator2.get_position(&vector_data).map(|pos| pos - anchor) else { continue };
let angle = pos1.angle_to(pos2);
// Check if handles are not colinear (not approximately equal to +/- PI)
if (angle - PI).abs() > 1e-6 && (angle + PI).abs() > 1e-6 {
let modification_type = VectorModificationType::SetG1Continuous {
handles: [*handle1, *handle2],
enabled: false,
};
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
}
}
}
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 <https://github.com/dtolnay/proc-macro2/issues/503> is resolved and released,
// TODO: use <https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.line> 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<TransformLayerMessage, TransformData<'_>> for TransformLayerMessageHandler {
fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, (document, input, tool_data, shape_editor): TransformData) {
let using_path_tool = tool_data.active_tool_type == ToolType::Path;
let using_select_tool = tool_data.active_tool_type == ToolType::Select;
let using_pen_tool = tool_data.active_tool_type == ToolType::Pen;
@@ -296,6 +175,12 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
return;
}
if using_path_tool {
for (outline, transform) in &self.ghost_outline {
overlay_context.outline(outline.iter(), *transform, Some(COLOR_OVERLAY_GRAY));
}
}
let viewport_box = input.viewport_bounds.size();
let axis_constraint = self.transform_operation.axis_constraint();
@@ -409,6 +294,10 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
responses.add(NodeGraphMessage::RunDocumentGraph);
}
if using_path_tool {
self.ghost_outline.clear();
}
responses.add(SelectToolMessage::PivotShift { offset: None, flush: true });
if final_transform {
@@ -462,12 +351,15 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
let selected_points: Vec<&ManipulatorPointId> = shape_editor.selected_points().collect();
let selected_segments = shape_editor.selected_segments().collect::<Vec<_>>();
if (using_path_tool && selected_points.is_empty() && selected_segments.is_empty())
|| (!using_path_tool && !using_select_tool && !using_pen_tool && !using_shape_tool)
|| selected_layers.is_empty()
|| transform_type.equivalent_to(self.transform_operation)
{
return;
if using_path_tool {
Self::set_ghost_outline(&mut self.ghost_outline, shape_editor, document);
if (selected_points.is_empty() && selected_segments.is_empty())
|| (!using_path_tool && !using_select_tool && !using_pen_tool && !using_shape_tool)
|| selected_layers.is_empty()
|| transform_type.equivalent_to(self.transform_operation)
{
return;
}
}
if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
@@ -515,6 +407,10 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
TransformLayerMessage::BeginRotate => responses.add_front(TransformLayerMessage::BeginGRS { operation: TransformType::Rotate }),
TransformLayerMessage::BeginScale => responses.add_front(TransformLayerMessage::BeginGRS { operation: TransformType::Scale }),
TransformLayerMessage::CancelTransformOperation => {
if using_path_tool {
self.ghost_outline.clear();
}
if using_pen_tool {
self.typing.clear();
@@ -774,6 +670,135 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
}
}
impl TransformLayerMessageHandler {
pub fn is_transforming(&self) -> bool {
self.transform_operation != TransformOperation::None
}
pub fn hints(&self, responses: &mut VecDeque<Message>) {
self.transform_operation.hints(responses, self.local);
}
fn set_ghost_outline(ghost_outline: &mut Vec<(Vec<ClickTargetType>, DAffine2)>, shape_editor: &ShapeState, document: &DocumentMessageHandler) {
ghost_outline.clear();
for &layer in shape_editor.selected_shape_state.keys() {
// We probably need to collect here
let outline = document.metadata().layer_with_free_points_outline(layer).cloned().collect();
let transform = document.metadata().transform_to_viewport(layer);
ghost_outline.push((outline, transform));
}
}
}
fn calculate_pivot(
document: &DocumentMessageHandler,
selected_points: &Vec<&ManipulatorPointId>,
vector_data: &VectorData,
viewspace: DAffine2,
get_location: impl Fn(&ManipulatorPointId) -> Option<DVec2>,
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::<DVec2>() / 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 position = position();
return (Some((position, position)), bounds);
};
match point {
ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => {
// Get the anchor position and transform it to the pivot
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 position = position();
(Some((position, position)), bounds)
}
}
}
fn project_edge_to_quad(edge: DVec2, quad: &Quad, local: bool, axis_constraint: Axis) -> DVec2 {
match axis_constraint {
Axis::X => {
if local {
edge.project_onto(quad.top_right() - quad.top_left())
} else {
edge.with_y(0.)
}
}
Axis::Y => {
if local {
edge.project_onto(quad.bottom_left() - quad.top_left())
} else {
edge.with_x(0.)
}
}
_ => edge,
}
}
fn update_colinear_handles(selected_layers: &[LayerNodeIdentifier], document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
for &layer in selected_layers {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
for [handle1, handle2] in &vector_data.colinear_manipulators {
let manipulator1 = handle1.to_manipulator_point();
let manipulator2 = handle2.to_manipulator_point();
let Some(anchor) = manipulator1.get_anchor_position(&vector_data) else { continue };
let Some(pos1) = manipulator1.get_position(&vector_data).map(|pos| pos - anchor) else { continue };
let Some(pos2) = manipulator2.get_position(&vector_data).map(|pos| pos - anchor) else { continue };
let angle = pos1.angle_to(pos2);
// Check if handles are not colinear (not approximately equal to +/- PI)
if (angle - PI).abs() > 1e-6 && (angle + PI).abs() > 1e-6 {
let modification_type = VectorModificationType::SetG1Continuous {
handles: [*handle1, *handle2],
enabled: false,
};
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
}
}
}
#[cfg(test)]
mod test_transform_layer {
use crate::messages::portfolio::document::graph_operation::transform_utils;
@@ -1298,7 +1323,7 @@ mod test_transform_layer {
let document = editor.active_document_mut();
let group_children = document.network_interface.downstream_layers(&group_layer.to_node(), &[]);
if !group_children.is_empty() {
Some(LayerNodeIdentifier::new(group_children[0], &document.network_interface, &[]))
Some(LayerNodeIdentifier::new(group_children[0], &document.network_interface))
} else {
None
}
+5 -5
View File
@@ -19,7 +19,7 @@ use std::borrow::Cow;
use std::fmt::{self, Debug};
#[derive(ExtractField)]
pub struct ToolActionHandlerData<'a> {
pub struct ToolActionMessageContext<'a> {
pub document: &'a mut DocumentMessageHandler,
pub document_id: DocumentId,
pub global_tool_data: &'a DocumentToolData,
@@ -30,8 +30,8 @@ pub struct ToolActionHandlerData<'a> {
pub preferences: &'a PreferencesMessageHandler,
}
pub trait ToolCommon: for<'a, 'b> MessageHandler<ToolMessage, &'b mut ToolActionHandlerData<'a>> + LayoutHolder + ToolTransition + ToolMetadata {}
impl<T> ToolCommon for T where T: for<'a, 'b> MessageHandler<ToolMessage, &'b mut ToolActionHandlerData<'a>> + LayoutHolder + ToolTransition + ToolMetadata {}
pub trait ToolCommon: for<'a, 'b> MessageHandler<ToolMessage, &'b mut ToolActionMessageContext<'a>> + LayoutHolder + ToolTransition + ToolMetadata {}
impl<T> ToolCommon for T where T: for<'a, 'b> MessageHandler<ToolMessage, &'b mut ToolActionMessageContext<'a>> + LayoutHolder + ToolTransition + ToolMetadata {}
type Tool = dyn ToolCommon + Send + Sync;
@@ -53,7 +53,7 @@ pub trait Fsm {
/// For example, if the tool's FSM is in a `Ready` state and receives a `DragStart` message as its event, it may decide to send some messages,
/// update some internal tool variables, and end by transitioning to a `Drawing` state.
#[must_use]
fn transition(self, message: ToolMessage, tool_data: &mut Self::ToolData, transition_data: &mut ToolActionHandlerData, options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self;
fn transition(self, message: ToolMessage, tool_data: &mut Self::ToolData, transition_data: &mut ToolActionMessageContext, options: &Self::ToolOptions, responses: &mut VecDeque<Message>) -> Self;
/// Implementing this trait function lets a specific tool provide a list of hints (user input actions presently available) to draw in the footer bar.
fn update_hints(&self, responses: &mut VecDeque<Message>);
@@ -82,7 +82,7 @@ pub trait Fsm {
&mut self,
message: ToolMessage,
tool_data: &mut Self::ToolData,
transition_data: &mut ToolActionHandlerData,
transition_data: &mut ToolActionMessageContext,
options: &Self::ToolOptions,
responses: &mut VecDeque<Message>,
update_cursor_on_transition: bool,
@@ -7,7 +7,7 @@ pub struct WorkspaceMessageHandler {
#[message_handler_data]
impl MessageHandler<WorkspaceMessage, ()> for WorkspaceMessageHandler {
fn process_message(&mut self, message: WorkspaceMessage, _responses: &mut VecDeque<Message>, _data: ()) {
fn process_message(&mut self, message: WorkspaceMessage, _responses: &mut VecDeque<Message>, _: ()) {
match message {
// Messages
WorkspaceMessage::NodeGraphToggleVisibility => {
+4 -13
View File
@@ -53,7 +53,7 @@ pub enum NodeGraphUpdate {
NodeGraphUpdateMessage(NodeGraphUpdateMessage),
}
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct NodeGraphExecutor {
runtime_io: NodeRuntimeIO,
futures: HashMap<u64, ExecutionContext>,
@@ -66,17 +66,6 @@ struct ExecutionContext {
export_config: Option<ExportConfig>,
}
impl Default for NodeGraphExecutor {
fn default() -> Self {
Self {
futures: Default::default(),
runtime_io: NodeRuntimeIO::new(),
node_graph_hash: 0,
old_inspect_node: None,
}
}
}
impl NodeGraphExecutor {
/// A local runtime is useful on threads since having global state causes flakes
#[cfg(test)]
@@ -394,7 +383,9 @@ impl NodeGraphExecutor {
return Err(format!("Invalid node graph output type: {node_graph_output:#?}"));
}
};
responses.add(Message::EndBuffer(render_output_metadata));
responses.add(Message::EndBuffer {
render_metadata: render_output_metadata,
});
responses.add(DocumentMessage::RenderScrollbars);
responses.add(DocumentMessage::RenderRulers);
responses.add(OverlaysMessage::Draw);
+12
View File
@@ -328,6 +328,18 @@ impl NodeRuntime {
return;
}
// Skip thumbnails if the layer is too complex (for performance)
if graphic_element.render_complexity() > 1000 {
let old = thumbnail_renders.insert(parent_network_node_id, Vec::new());
if old.is_none_or(|v| !v.is_empty()) {
responses.push_back(FrontendMessage::UpdateNodeThumbnail {
id: parent_network_node_id,
value: "<svg viewBox=\"0 0 10 10\"><title>Dense thumbnail omitted for performance</title><line x1=\"0\" y1=\"10\" x2=\"10\" y2=\"0\" stroke=\"red\" /></svg>".to_string(),
});
}
return;
}
let bounds = graphic_element.bounding_box(DAffine2::IDENTITY, true);
// Render the thumbnail from a `GraphicElement` into an SVG string
+2 -3
View File
@@ -31,7 +31,7 @@ impl EditorTestUtils {
// It isn't sufficient to guard the message dispatch here with a check if the once_cell is empty, because that isn't atomic and the time between checking and handling the dispatch can let multiple through.
let _ = GLOBAL_PLATFORM.set(Platform::Windows).is_ok();
editor.handle_message(Message::Init);
editor.handle_message(PortfolioMessage::Init);
Self { editor, runtime }
}
@@ -326,8 +326,7 @@ pub mod test_prelude {
pub use crate::node_graph_executor::NodeRuntime;
pub use crate::test_utils::EditorTestUtils;
pub use core::f64;
pub use glam::DVec2;
pub use glam::IVec2;
pub use glam::{DVec2, IVec2};
pub use graph_craft::document::DocumentNode;
pub use graphene_std::raster::{Color, Image};
pub use graphene_std::transform::Footprint;
+4 -5
View File
@@ -2,15 +2,14 @@ pub use crate::dispatcher::*;
use crate::messages::prelude::*;
/// Implements a message handler struct for a separate message struct.
/// - The first generic argument (`M`) is that message struct type, representing a message enum variant to be matched and handled in `process_message()`.
/// - The second generic argument (`D`) is the type of data that can be passed along by the caller to `process_message()`.
pub trait MessageHandler<M: ToDiscriminant, D>
/// - The first type argument (`M`) is that message struct type, representing a message enum variant to be matched and handled in `process_message()`.
/// - The second type argument (`C`) is the type of the context struct that can be passed along by the caller to `process_message()`.
pub trait MessageHandler<M: ToDiscriminant, C>
where
M::Discriminant: AsMessage,
<M::Discriminant as TransitiveChild>::TopParent: TransitiveChild<Parent = <M::Discriminant as TransitiveChild>::TopParent, TopParent = <M::Discriminant as TransitiveChild>::TopParent> + AsMessage,
{
/// Return true if the Action is consumed.
fn process_message(&mut self, message: M, responses: &mut VecDeque<Message>, data: D);
fn process_message(&mut self, message: M, responses: &mut VecDeque<Message>, context: C);
fn actions(&self) -> ActionList;
}

Some files were not shown because too many files have changed in this diff Show More