mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-07-07 15:55:00 +00:00
merge
This commit is contained in:
commit
8002e7ccc1
101 changed files with 3737 additions and 2830 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,3 +5,4 @@ perf.data*
|
|||
profile.json
|
||||
flamegraph.svg
|
||||
.idea/
|
||||
.direnv
|
||||
|
|
|
@ -51,7 +51,7 @@
|
|||
libraw
|
||||
|
||||
|
||||
# Tauri dependencies: keep in sync with https://v2.tauri.app/start/prerequisites/
|
||||
# Tauri dependencies: keep in sync with https://v2.tauri.app/start/prerequisites/#system-dependencies (under the NixOS tab)
|
||||
at-spi2-atk
|
||||
atkmm
|
||||
cairo
|
||||
|
|
378
Cargo.lock
generated
378
Cargo.lock
generated
|
@ -82,6 +82,12 @@ dependencies = [
|
|||
"alloc-no-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android-activity"
|
||||
version = "0.5.2"
|
||||
|
@ -486,11 +492,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "block2"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d59b4c170e16f0405a2e95aff44432a0d41aa97675f3d52623effe95792a037"
|
||||
checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2"
|
||||
dependencies = [
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -528,9 +534,9 @@ checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
|
|||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.22.0"
|
||||
version = "1.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540"
|
||||
checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
|
@ -822,6 +828,12 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ae467d04a8a8aea5d9a49018a6ade2e4221d92968e8ce55a48c0b1164e5f698"
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
|
@ -1255,6 +1267,16 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
|
||||
|
||||
[[package]]
|
||||
name = "dispatch2"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2 0.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.5"
|
||||
|
@ -1569,6 +1591,25 @@ dependencies = [
|
|||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "font-types"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02a596f5713680923a2080d86de50fe472fb290693cf0f701187a1c8b36996b7"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontconfig-cache-parser"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7f8afb20c8069fd676d27b214559a337cc619a605d25a87baa90b49a06f3b18"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontconfig-parser"
|
||||
version = "0.5.7"
|
||||
|
@ -1592,6 +1633,29 @@ dependencies = [
|
|||
"ttf-parser 0.24.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fontique"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39f97079e1293b8c1e9fb03a2875d328bd2ee8f3b95ce62959c0acc04049c708"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"fontconfig-cache-parser",
|
||||
"hashbrown 0.15.4",
|
||||
"icu_locid",
|
||||
"memmap2",
|
||||
"objc2 0.6.1",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-text",
|
||||
"objc2-foundation 0.3.1",
|
||||
"peniko 0.4.0",
|
||||
"read-fonts 0.29.3",
|
||||
"roxmltree",
|
||||
"smallvec",
|
||||
"windows 0.58.0",
|
||||
"windows-core 0.58.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
|
@ -2109,7 +2173,7 @@ checksum = "dcf29e94d6d243368b7a56caa16bc213e4f9f8ed38c4d9557069527b5d5281ca"
|
|||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"gpu-descriptor-types",
|
||||
"hashbrown 0.15.2",
|
||||
"hashbrown 0.15.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2131,8 +2195,10 @@ dependencies = [
|
|||
"glam",
|
||||
"graph-craft",
|
||||
"graphene-application-io",
|
||||
"graphene-brush",
|
||||
"graphene-core",
|
||||
"graphene-path-bool",
|
||||
"graphene-raster-nodes",
|
||||
"graphene-svg-renderer",
|
||||
"iai-callgrind",
|
||||
"js-sys",
|
||||
|
@ -2164,6 +2230,19 @@ dependencies = [
|
|||
"wgpu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphene-brush"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dyn-any",
|
||||
"glam",
|
||||
"graphene-core",
|
||||
"graphene-raster-nodes",
|
||||
"node-macro",
|
||||
"serde",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphene-cli"
|
||||
version = "0.1.0"
|
||||
|
@ -2200,13 +2279,14 @@ dependencies = [
|
|||
"node-macro",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"parley",
|
||||
"petgraph 0.7.1",
|
||||
"rand 0.9.0",
|
||||
"rand_chacha 0.9.0",
|
||||
"rustc-hash 2.1.1",
|
||||
"rustybuzz 0.20.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"skrifa 0.32.0",
|
||||
"specta",
|
||||
"tinyvec",
|
||||
"tokio",
|
||||
|
@ -2240,6 +2320,27 @@ dependencies = [
|
|||
"specta",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphene-raster-nodes"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bezier-rs",
|
||||
"bytemuck",
|
||||
"dyn-any",
|
||||
"fastnoise-lite",
|
||||
"futures",
|
||||
"glam",
|
||||
"graphene-core",
|
||||
"image",
|
||||
"ndarray",
|
||||
"node-macro",
|
||||
"rand 0.9.0",
|
||||
"rand_chacha 0.9.0",
|
||||
"serde",
|
||||
"specta",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphene-std"
|
||||
version = "0.1.0"
|
||||
|
@ -2252,9 +2353,11 @@ dependencies = [
|
|||
"glam",
|
||||
"graph-craft",
|
||||
"graphene-application-io",
|
||||
"graphene-brush",
|
||||
"graphene-core",
|
||||
"graphene-math-nodes",
|
||||
"graphene-path-bool",
|
||||
"graphene-raster-nodes",
|
||||
"graphene-svg-renderer",
|
||||
"image",
|
||||
"log",
|
||||
|
@ -2470,10 +2573,12 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
|||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.2"
|
||||
version = "0.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
|
@ -2949,7 +3054,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.2",
|
||||
"hashbrown 0.15.4",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
@ -3204,9 +3309,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "kurbo"
|
||||
version = "0.11.1"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f"
|
||||
checksum = "1077d333efea6170d9ccb96d3c3026f300ca0773da4938cc4c811daa6df68b0c"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"serde",
|
||||
|
@ -3282,7 +3387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.48.5",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3506,10 +3611,10 @@ dependencies = [
|
|||
"dpi",
|
||||
"gtk",
|
||||
"keyboard-types",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
"once_cell",
|
||||
"png",
|
||||
"serde",
|
||||
|
@ -3790,9 +3895,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
version = "0.6.0"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3531f65190d9cff863b77a99857e74c314dd16bf56c538c4b57c7cbc3f3a6e59"
|
||||
checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551"
|
||||
dependencies = [
|
||||
"objc2-encode 4.1.0",
|
||||
"objc2-exception-helper",
|
||||
|
@ -3805,15 +3910,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "5906f93257178e2f7ae069efb89fbd6ee94f0592740b5f8a1512ca498814d0fb"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"block2 0.6.0",
|
||||
"block2 0.6.1",
|
||||
"libc",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-cloud-kit",
|
||||
"objc2-core-data",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-core-image",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2-quartz-core 0.3.0",
|
||||
]
|
||||
|
||||
|
@ -3824,8 +3929,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "6c1948a9be5f469deadbd6bcb86ad7ff9e47b4f632380139722f7d9840c0d42c"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-foundation 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3835,18 +3940,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1f860f8e841f6d32f754836f51e6bc7777cd7e7053cf18528233f6811d3eceb4"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-foundation 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-foundation"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "daeaf60f25471d26948a1c2f840e3f7d86f4109e3af4e8e4b5cd70c39690d925"
|
||||
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2 0.6.0",
|
||||
"dispatch2",
|
||||
"objc2 0.6.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3856,7 +3962,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "f8dca602628b65356b6513290a21a6405b4d4027b8b250f0b98dddbb28b7de02"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-core-foundation",
|
||||
"objc2-io-surface",
|
||||
]
|
||||
|
@ -3867,8 +3973,18 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ffa6bea72bf42c78b0b34e89c0bafac877d5f80bf91e159a5d96ea7f693ca56"
|
||||
dependencies = [
|
||||
"objc2 0.6.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-foundation 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-text"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ba833d4a1cb1aac330f8c973fd92b6ff1858e4aef5cdd00a255eefb28022fb5"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3906,14 +4022,14 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "objc2-foundation"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a21c6c9014b82c39515db5b396f91645182611c97d24637cf56ac01e5f8d998"
|
||||
checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"block2 0.6.0",
|
||||
"block2 0.6.1",
|
||||
"libc",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
|
@ -3924,7 +4040,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "161a8b87e32610086e1a7a9e9ec39f84459db7b3a0881c1f16ca5a2605581c19"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
|
@ -3960,8 +4076,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "6fb3794501bb1bee12f08dcad8c61f2a5875791ad1c6f47faa71a0f033f20071"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-foundation 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3971,9 +4087,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "777a571be14a42a3990d4ebedaeb8b54cd17377ec21b92e8200ac03797b3bee1"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3983,11 +4099,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "b717127e4014b0f9f3e8bba3d3f2acec81f1bde01f656823036e823ed2c94dce"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"block2 0.6.0",
|
||||
"objc2 0.6.0",
|
||||
"block2 0.6.1",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4149,6 +4265,19 @@ dependencies = [
|
|||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parley"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13e57638545cf2ba4c3e72cc5715e53b1880b829cc3dbefda3d1700c58efe723"
|
||||
dependencies = [
|
||||
"fontique",
|
||||
"hashbrown 0.15.4",
|
||||
"peniko 0.4.0",
|
||||
"skrifa 0.31.3",
|
||||
"swash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
|
@ -4180,7 +4309,18 @@ name = "peniko"
|
|||
version = "0.2.0"
|
||||
source = "git+https://github.com/linebender/peniko.git?rev=d114c62#d114c6292dbcfb03e7360692198be423168a0edd"
|
||||
dependencies = [
|
||||
"color",
|
||||
"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",
|
||||
"kurbo",
|
||||
"smallvec",
|
||||
]
|
||||
|
@ -4968,7 +5108,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "f6f9e8a4f503e5c8750e4cd3b32a4e090035c46374b305a15c70bad833dca05f"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"font-types",
|
||||
"font-types 0.8.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "read-fonts"
|
||||
version = "0.29.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04ca636dac446b5664bd16c069c00a9621806895b8bb02c2dc68542b23b8f25d"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"font-types 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "read-fonts"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "192735ef611aac958468e670cb98432c925426f3cb71521fda202130f7388d91"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"font-types 0.9.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5261,26 +5421,8 @@ dependencies = [
|
|||
"log",
|
||||
"smallvec",
|
||||
"ttf-parser 0.24.1",
|
||||
"unicode-bidi-mirroring 0.3.0",
|
||||
"unicode-ccc 0.3.0",
|
||||
"unicode-properties",
|
||||
"unicode-script",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustybuzz"
|
||||
version = "0.20.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd3c7c96f8a08ee34eff8857b11b49b07d71d1c3f4e88f8a88d4c9e9f90b1702"
|
||||
dependencies = [
|
||||
"bitflags 2.9.0",
|
||||
"bytemuck",
|
||||
"core_maths",
|
||||
"log",
|
||||
"smallvec",
|
||||
"ttf-parser 0.25.1",
|
||||
"unicode-bidi-mirroring 0.4.0",
|
||||
"unicode-ccc 0.4.0",
|
||||
"unicode-bidi-mirroring",
|
||||
"unicode-ccc",
|
||||
"unicode-properties",
|
||||
"unicode-script",
|
||||
]
|
||||
|
@ -5415,9 +5557,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.218"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
@ -5446,9 +5588,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.218"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -5652,7 +5794,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "8cc1aa86c26dbb1b63875a7180aa0819709b33348eb5b1491e4321fae388179d"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"read-fonts",
|
||||
"read-fonts 0.25.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "skrifa"
|
||||
version = "0.31.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbeb4ca4399663735553a09dd17ce7e49a0a0203f03b706b39628c4d913a8607"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"read-fonts 0.29.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "skrifa"
|
||||
version = "0.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d632b5a73f566303dbeabd344dc3e716fd4ddc9a70d6fc8ea8e6f06617da97"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"read-fonts 0.30.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -5675,9 +5837,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.14.0"
|
||||
version = "1.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
|
||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
@ -5895,6 +6057,17 @@ dependencies = [
|
|||
"siphasher 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swash"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f745de914febc7c9ab4388dfaf94bbc87e69f57bb41133a9b0c84d4be49856f3"
|
||||
dependencies = [
|
||||
"skrifa 0.31.3",
|
||||
"yazi",
|
||||
"zeno",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "swift-rs"
|
||||
version = "1.0.7"
|
||||
|
@ -6005,9 +6178,9 @@ dependencies = [
|
|||
"ndk 0.9.0",
|
||||
"ndk-context",
|
||||
"ndk-sys 0.6.0+11769913",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"raw-window-handle",
|
||||
|
@ -6060,9 +6233,9 @@ dependencies = [
|
|||
"log",
|
||||
"mime",
|
||||
"muda",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
"percent-encoding",
|
||||
"plist",
|
||||
"raw-window-handle",
|
||||
|
@ -6266,9 +6439,9 @@ dependencies = [
|
|||
"http",
|
||||
"jni",
|
||||
"log",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"raw-window-handle",
|
||||
|
@ -6688,11 +6861,11 @@ dependencies = [
|
|||
"dirs",
|
||||
"libappindicator",
|
||||
"muda",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-core-graphics",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
"once_cell",
|
||||
"png",
|
||||
"serde",
|
||||
|
@ -6720,9 +6893,6 @@ name = "ttf-parser"
|
|||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
|
||||
dependencies = [
|
||||
"core_maths",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typeid"
|
||||
|
@ -6795,24 +6965,12 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64af057ad7466495ca113126be61838d8af947f41d93a949980b2389a118082f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi-mirroring"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ccc"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "260bc6647b3893a9a90668360803a15f96b85a5257b1c3a0c3daf6ae2496de42"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ccc"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce61d488bcdc9bc8b5d1772c404828b17fc481c0a582b5581e95fb233aef503e"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
|
@ -6900,7 +7058,7 @@ dependencies = [
|
|||
"log",
|
||||
"pico-args",
|
||||
"roxmltree",
|
||||
"rustybuzz 0.18.0",
|
||||
"rustybuzz",
|
||||
"simplecss",
|
||||
"siphasher 1.0.1",
|
||||
"strict-num",
|
||||
|
@ -6971,9 +7129,9 @@ dependencies = [
|
|||
"bytemuck",
|
||||
"futures-intrusive",
|
||||
"log",
|
||||
"peniko",
|
||||
"peniko 0.2.0",
|
||||
"png",
|
||||
"skrifa",
|
||||
"skrifa 0.26.6",
|
||||
"static_assertions",
|
||||
"thiserror 2.0.12",
|
||||
"vello_encoding",
|
||||
|
@ -6988,8 +7146,8 @@ source = "git+https://github.com/linebender/vello.git?rev=3275ec8#3275ec85d83118
|
|||
dependencies = [
|
||||
"bytemuck",
|
||||
"guillotiere",
|
||||
"peniko",
|
||||
"skrifa",
|
||||
"peniko 0.2.0",
|
||||
"skrifa 0.26.6",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
|
@ -7556,10 +7714,10 @@ version = "0.6.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c"
|
||||
dependencies = [
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
"raw-window-handle",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-version",
|
||||
|
@ -8077,7 +8235,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "b19b78efae8b853c6c817e8752fc1dbf9cab8a8ffe9c30f399bd750ccf0f0730"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"block2 0.6.0",
|
||||
"block2 0.6.1",
|
||||
"cookie",
|
||||
"crossbeam-channel",
|
||||
"dpi",
|
||||
|
@ -8091,10 +8249,10 @@ dependencies = [
|
|||
"kuchikiki",
|
||||
"libc",
|
||||
"ndk 0.9.0",
|
||||
"objc2 0.6.0",
|
||||
"objc2 0.6.1",
|
||||
"objc2-app-kit",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation 0.3.0",
|
||||
"objc2-foundation 0.3.1",
|
||||
"objc2-ui-kit",
|
||||
"objc2-web-kit",
|
||||
"once_cell",
|
||||
|
@ -8199,6 +8357,12 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||
|
||||
[[package]]
|
||||
name = "yazi"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5"
|
||||
|
||||
[[package]]
|
||||
name = "yoke"
|
||||
version = "0.7.5"
|
||||
|
@ -8223,6 +8387,12 @@ dependencies = [
|
|||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeno"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -5,12 +5,14 @@ members = [
|
|||
"frontend/wasm",
|
||||
"frontend/src-tauri",
|
||||
"node-graph/gapplication-io",
|
||||
"node-graph/gbrush",
|
||||
"node-graph/gcore",
|
||||
"node-graph/gstd",
|
||||
"node-graph/gmath-nodes",
|
||||
"node-graph/gpath-bool",
|
||||
"node-graph/graph-craft",
|
||||
"node-graph/graphene-cli",
|
||||
"node-graph/graster-nodes",
|
||||
"node-graph/gsvg-renderer",
|
||||
"node-graph/interpreted-executor",
|
||||
"node-graph/node-macro",
|
||||
|
@ -24,12 +26,14 @@ members = [
|
|||
default-members = [
|
||||
"editor",
|
||||
"frontend/wasm",
|
||||
"node-graph/gbrush",
|
||||
"node-graph/gcore",
|
||||
"node-graph/gstd",
|
||||
"node-graph/gmath-nodes",
|
||||
"node-graph/gpath-bool",
|
||||
"node-graph/graph-craft",
|
||||
"node-graph/graphene-cli",
|
||||
"node-graph/graster-nodes",
|
||||
"node-graph/gsvg-renderer",
|
||||
"node-graph/interpreted-executor",
|
||||
"node-graph/node-macro",
|
||||
|
@ -44,10 +48,12 @@ preprocessor = { path = "node-graph/preprocessor"}
|
|||
math-parser = { path = "libraries/math-parser" }
|
||||
path-bool = { path = "libraries/path-bool" }
|
||||
graphene-application-io = { path = "node-graph/gapplication-io" }
|
||||
graphene-brush = { path = "node-graph/gbrush" }
|
||||
graphene-core = { path = "node-graph/gcore" }
|
||||
graphene-math-nodes = { path = "node-graph/gmath-nodes" }
|
||||
graphene-path-bool = { path = "node-graph/gpath-bool" }
|
||||
graph-craft = { path = "node-graph/graph-craft" }
|
||||
graphene-raster-nodes = { path = "node-graph/graster-nodes" }
|
||||
graphene-std = { path = "node-graph/gstd" }
|
||||
graphene-svg-renderer = { path = "node-graph/gsvg-renderer" }
|
||||
interpreted-executor = { path = "node-graph/interpreted-executor" }
|
||||
|
@ -116,7 +122,8 @@ rand_chacha = "0.9"
|
|||
glam = { version = "0.29", default-features = false, features = ["serde", "scalar-math", "debug-glam-assert"] }
|
||||
base64 = "0.22"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp"] }
|
||||
rustybuzz = "0.20"
|
||||
parley = "0.5.0"
|
||||
skrifa = "0.32.0"
|
||||
pretty_assertions = "1.4.1"
|
||||
fern = { version = "0.7", features = ["colored"] }
|
||||
num_enum = "0.7"
|
||||
|
@ -143,9 +150,10 @@ petgraph = { version = "0.7.1", default-features = false, features = [
|
|||
"graphmap",
|
||||
] }
|
||||
half = { version = "2.4.1", default-features = false, features = ["bytemuck", "serde"] }
|
||||
tinyvec = { version = "1" }
|
||||
tinyvec = { version = "1", features = ["std"] }
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
iai-callgrind = { version = "0.12.3" }
|
||||
ndarray = "0.16.1"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
|
|
@ -6,6 +6,7 @@ accepted = [
|
|||
"BSD-3-Clause",
|
||||
"BSL-1.0",
|
||||
"CC0-1.0",
|
||||
"CDLA-Permissive-2.0",
|
||||
"ISC",
|
||||
"MIT-0",
|
||||
"MIT",
|
||||
|
@ -14,6 +15,7 @@ accepted = [
|
|||
"Unicode-3.0",
|
||||
"Unicode-DFS-2016",
|
||||
"Zlib",
|
||||
"NCSA",
|
||||
]
|
||||
workarounds = ["ring"]
|
||||
ignore-build-dependencies = true
|
||||
|
|
2
demo-artwork/changing-seasons.graphite
generated
2
demo-artwork/changing-seasons.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/isometric-fountain.graphite
generated
2
demo-artwork/isometric-fountain.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/marbled-mandelbrot.graphite
generated
2
demo-artwork/marbled-mandelbrot.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/painted-dreams.graphite
generated
2
demo-artwork/painted-dreams.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/parametric-dunescape.graphite
generated
2
demo-artwork/parametric-dunescape.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/procedural-string-lights.graphite
generated
2
demo-artwork/procedural-string-lights.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/red-dress.graphite
generated
2
demo-artwork/red-dress.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/valley-of-spires.graphite
generated
2
demo-artwork/valley-of-spires.graphite
generated
File diff suppressed because one or more lines are too long
|
@ -75,11 +75,14 @@ allow = [
|
|||
"BSD-3-Clause",
|
||||
"BSL-1.0",
|
||||
"CC0-1.0",
|
||||
"CDLA-Permissive-2.0",
|
||||
"ISC",
|
||||
"MIT-0",
|
||||
"MIT",
|
||||
"MPL-2.0",
|
||||
"OpenSSL",
|
||||
"Unicode-3.0",
|
||||
"Unicode-DFS-2016",
|
||||
"Zlib",
|
||||
"NCSA",
|
||||
]
|
||||
|
|
|
@ -40,7 +40,6 @@ impl DispatcherMessageHandlers {
|
|||
/// The last occurrence of the message in the message queue is sufficient to ensure correct behavior.
|
||||
/// In addition, these messages do not change any state in the backend (aside from caches).
|
||||
const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
|
||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::NodeGraph(NodeGraphMessageDiscriminant::SendGraph))),
|
||||
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
|
||||
PropertiesPanelMessageDiscriminant::Refresh,
|
||||
))),
|
||||
|
@ -319,12 +318,11 @@ impl Dispatcher {
|
|||
}))
|
||||
}
|
||||
|
||||
/// Logs a message that is about to be executed,
|
||||
/// either as a tree with a discriminant or the entire payload (depending on settings)
|
||||
/// Logs a message that is about to be executed, either as a tree
|
||||
/// with a discriminant or the entire payload (depending on settings)
|
||||
fn log_message(&self, message: &Message, queues: &[VecDeque<Message>], message_logging_verbosity: MessageLoggingVerbosity) {
|
||||
let discriminant = MessageDiscriminant::from(message);
|
||||
let is_blocked = DEBUG_MESSAGE_BLOCK_LIST.iter().any(|&blocked_discriminant| discriminant == blocked_discriminant)
|
||||
|| DEBUG_MESSAGE_ENDING_BLOCK_LIST.iter().any(|blocked_name| discriminant.local_name().ends_with(blocked_name));
|
||||
let is_blocked = DEBUG_MESSAGE_BLOCK_LIST.contains(&discriminant) || DEBUG_MESSAGE_ENDING_BLOCK_LIST.iter().any(|blocked_name| discriminant.local_name().ends_with(blocked_name));
|
||||
|
||||
if !is_blocked {
|
||||
match message_logging_verbosity {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::consts::{VIEWPORT_ZOOM_WHEEL_RATE, VIEWPORT_ZOOM_WHEEL_RATE_CHANGE};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle;
|
||||
use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::{
|
||||
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, FrontendNodeWire, Transform, WirePath,
|
||||
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, Transform,
|
||||
};
|
||||
use crate::messages::portfolio::document::utility_types::nodes::{JsRawBuffer, LayerPanelEntry, RawBuffer};
|
||||
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::utility_types::HintData;
|
||||
use graph_craft::document::NodeId;
|
||||
|
@ -250,12 +251,16 @@ pub enum FrontendMessage {
|
|||
UpdateMouseCursor {
|
||||
cursor: MouseCursorIcon,
|
||||
},
|
||||
UpdateNodeGraph {
|
||||
UpdateNodeGraphNodes {
|
||||
nodes: Vec<FrontendNode>,
|
||||
wires: Vec<FrontendNodeWire>,
|
||||
#[serde(rename = "wiresDirectNotGridAligned")]
|
||||
wires_direct_not_grid_aligned: bool,
|
||||
},
|
||||
UpdateVisibleNodes {
|
||||
nodes: Vec<NodeId>,
|
||||
},
|
||||
UpdateNodeGraphWires {
|
||||
wires: Vec<WirePathUpdate>,
|
||||
},
|
||||
ClearAllNodeGraphWires,
|
||||
UpdateNodeGraphControlBarLayout {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
layout_target: LayoutTarget,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use super::utility_types::input_keyboard::KeysGroup;
|
||||
use super::utility_types::misc::Mapping;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{self, Key};
|
||||
use crate::messages::input_mapper::utility_types::misc::MappingEntry;
|
||||
use crate::messages::portfolio::utility_types::KeyboardPlatformLayout;
|
||||
use crate::messages::prelude::*;
|
||||
use std::fmt::Write;
|
||||
|
@ -47,12 +48,12 @@ impl InputMapperMessageHandler {
|
|||
ma.map(|a| ((i as u8).try_into().unwrap(), a))
|
||||
})
|
||||
.for_each(|(k, a): (Key, _)| {
|
||||
let _ = write!(output, "{}: {}, ", k.to_discriminant().local_name(), a.local_name().split('.').last().unwrap());
|
||||
let _ = write!(output, "{}: {}, ", k.to_discriminant().local_name(), a.local_name().split('.').next_back().unwrap());
|
||||
});
|
||||
output.replace("Key", "")
|
||||
}
|
||||
|
||||
pub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant) -> Vec<KeysGroup> {
|
||||
pub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant) -> Option<KeysGroup> {
|
||||
let all_key_mapping_entries = std::iter::empty()
|
||||
.chain(self.mapping.key_up.iter())
|
||||
.chain(self.mapping.key_down.iter())
|
||||
|
@ -66,55 +67,65 @@ impl InputMapperMessageHandler {
|
|||
// Filter for the desired message
|
||||
let found_actions = all_mapping_entries.filter(|entry| entry.action.to_discriminant() == *action_to_find);
|
||||
|
||||
// Get the `Key` for this platform's accelerator key
|
||||
let keyboard_layout = || GLOBAL_PLATFORM.get().copied().unwrap_or_default().as_keyboard_platform_layout();
|
||||
let platform_accel_key = match keyboard_layout() {
|
||||
KeyboardPlatformLayout::Standard => Key::Control,
|
||||
KeyboardPlatformLayout::Mac => Key::Command,
|
||||
};
|
||||
|
||||
let entry_to_key = |entry: &MappingEntry| {
|
||||
// Get the modifier keys for the entry (and convert them to Key)
|
||||
let mut keys = entry
|
||||
.modifiers
|
||||
.iter()
|
||||
.map(|i| {
|
||||
// TODO: Use a safe solution eventually
|
||||
assert!(
|
||||
i < input_keyboard::NUMBER_OF_KEYS,
|
||||
"Attempting to convert a Key with enum index {i}, which is larger than the number of Key enums",
|
||||
);
|
||||
(i as u8).try_into().unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Append the key button for the entry
|
||||
use InputMapperMessage as IMM;
|
||||
match entry.input {
|
||||
IMM::KeyDown(key) | IMM::KeyUp(key) | IMM::KeyDownNoRepeat(key) | IMM::KeyUpNoRepeat(key) => keys.push(key),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
keys.sort_by(|&a, &b| {
|
||||
// Order according to platform guidelines mentioned at https://ux.stackexchange.com/questions/58185/normative-ordering-for-modifier-key-combinations
|
||||
const ORDER: [Key; 4] = [Key::Control, Key::Alt, Key::Shift, Key::Command];
|
||||
|
||||
// Treat the `Accel` virtual key as the platform's accel key for sorting comparison purposes
|
||||
let a = if a == Key::Accel { platform_accel_key } else { a };
|
||||
let b = if b == Key::Accel { platform_accel_key } else { b };
|
||||
|
||||
// Find where the keys are in the order, or put them at the end if they're not found
|
||||
let a = ORDER.iter().position(|&key| key == a).unwrap_or(ORDER.len());
|
||||
let b = ORDER.iter().position(|&key| key == b).unwrap_or(ORDER.len());
|
||||
|
||||
// Compare the positions of both keys
|
||||
a.cmp(&b)
|
||||
});
|
||||
|
||||
KeysGroup(keys)
|
||||
};
|
||||
|
||||
// If a canonical key combination is found, return it
|
||||
if let Some(canonical) = found_actions.clone().find(|entry| entry.canonical).map(entry_to_key) {
|
||||
return Some(canonical);
|
||||
}
|
||||
|
||||
// Find the key combinations for all keymaps matching the desired action
|
||||
assert!(std::mem::size_of::<usize>() >= std::mem::size_of::<Key>());
|
||||
found_actions
|
||||
.map(|entry| {
|
||||
// Get the modifier keys for the entry (and convert them to Key)
|
||||
let mut keys = entry
|
||||
.modifiers
|
||||
.iter()
|
||||
.map(|i| {
|
||||
// TODO: Use a safe solution eventually
|
||||
assert!(
|
||||
i < input_keyboard::NUMBER_OF_KEYS,
|
||||
"Attempting to convert a Key with enum index {i}, which is larger than the number of Key enums",
|
||||
);
|
||||
(i as u8).try_into().unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut key_sequences = found_actions.map(entry_to_key).collect::<Vec<_>>();
|
||||
|
||||
// Append the key button for the entry
|
||||
use InputMapperMessage as IMM;
|
||||
match entry.input {
|
||||
IMM::KeyDown(key) | IMM::KeyUp(key) | IMM::KeyDownNoRepeat(key) | IMM::KeyUpNoRepeat(key) => keys.push(key),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
keys.sort_by(|&a, &b| {
|
||||
// Order according to platform guidelines mentioned at https://ux.stackexchange.com/questions/58185/normative-ordering-for-modifier-key-combinations
|
||||
const ORDER: [Key; 4] = [Key::Control, Key::Alt, Key::Shift, Key::Command];
|
||||
|
||||
// Treat the `Accel` virtual key as the platform's accel key for sorting comparison purposes
|
||||
let a = if a == Key::Accel { platform_accel_key } else { a };
|
||||
let b = if b == Key::Accel { platform_accel_key } else { b };
|
||||
|
||||
// Find where the keys are in the order, or put them at the end if they're not found
|
||||
let a = ORDER.iter().position(|&key| key == a).unwrap_or(ORDER.len());
|
||||
let b = ORDER.iter().position(|&key| key == b).unwrap_or(ORDER.len());
|
||||
|
||||
// Compare the positions of both keys
|
||||
a.cmp(&b)
|
||||
});
|
||||
|
||||
KeysGroup(keys)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
// Return the shortest key sequence, if any
|
||||
key_sequences.sort_by_key(|keys| keys.0.len());
|
||||
key_sequences.first().cloned()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ use crate::messages::input_mapper::utility_types::misc::{KeyMappingEntries, Mapp
|
|||
use crate::messages::portfolio::document::node_graph::utility_types::Direction;
|
||||
use crate::messages::portfolio::document::utility_types::clipboards::Clipboard;
|
||||
use crate::messages::portfolio::document::utility_types::misc::GroupFolderType;
|
||||
use crate::messages::portfolio::document::utility_types::transformation::TransformType;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::tool_messages::brush_tool::BrushToolMessageOptionsUpdate;
|
||||
use crate::messages::tool::tool_messages::select_tool::SelectToolPointerKeys;
|
||||
|
@ -221,11 +220,12 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(PointerMove; refresh_keys=[KeyC, Space, Control, Shift, Alt], action_dispatch=PathToolMessage::PointerMove { toggle_colinear: KeyC, equidistant: Alt, move_anchor_with_handles: Space, snap_angle: Shift, lock_angle: Control, delete_segment: Alt, break_colinear_molding: Alt }),
|
||||
entry!(KeyDown(Delete); action_dispatch=PathToolMessage::Delete),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=PathToolMessage::SelectAllAnchors),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], action_dispatch=PathToolMessage::DeselectAllPoints),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=PathToolMessage::DeselectAllPoints),
|
||||
entry!(KeyDown(KeyA); modifiers=[Alt], action_dispatch=PathToolMessage::DeselectAllPoints),
|
||||
entry!(KeyDown(Backspace); action_dispatch=PathToolMessage::Delete),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=PathToolMessage::DragStop { extend_selection: Shift, shrink_selection: Alt }),
|
||||
entry!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter { extend_selection: Shift, shrink_selection: Alt }),
|
||||
entry!(DoubleClick(MouseButton::Left); action_dispatch=PathToolMessage::FlipSmoothSharp),
|
||||
entry!(DoubleClick(MouseButton::Left); action_dispatch=PathToolMessage::DoubleClick { extend_selection: Shift, shrink_selection: Alt }),
|
||||
entry!(KeyDown(ArrowRight); action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: 0. }),
|
||||
entry!(KeyDown(ArrowRight); modifiers=[Shift], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: BIG_NUDGE_AMOUNT, delta_y: 0. }),
|
||||
entry!(KeyDown(ArrowRight); modifiers=[ArrowUp], action_dispatch=PathToolMessage::NudgeSelectedPoints { delta_x: NUDGE_AMOUNT, delta_y: -NUDGE_AMOUNT }),
|
||||
|
@ -313,9 +313,10 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(KeyDown(KeyE); action_dispatch=ToolMessage::ActivateToolShapeEllipse),
|
||||
entry!(KeyDown(KeyY); action_dispatch=ToolMessage::ActivateToolShape),
|
||||
entry!(KeyDown(KeyB); action_dispatch=ToolMessage::ActivateToolBrush),
|
||||
entry!(KeyDown(KeyX); modifiers=[Accel, Shift], action_dispatch=ToolMessage::ResetColors),
|
||||
entry!(KeyDown(KeyD); action_dispatch=ToolMessage::ResetColors),
|
||||
entry!(KeyDown(KeyX); modifiers=[Shift], action_dispatch=ToolMessage::SwapColors),
|
||||
entry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=ToolMessage::SelectRandomPrimaryColor),
|
||||
entry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=ToolMessage::SelectRandomWorkingColor { primary: true }),
|
||||
entry!(KeyDown(KeyC); modifiers=[Alt, Shift], action_dispatch=ToolMessage::SelectRandomWorkingColor { primary: false }),
|
||||
//
|
||||
// DocumentMessage
|
||||
entry!(KeyDown(Space); modifiers=[Control], action_dispatch=DocumentMessage::GraphViewOverlayToggle),
|
||||
|
@ -327,20 +328,21 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(KeyDown(KeyH); modifiers=[Accel], action_dispatch=DocumentMessage::ToggleSelectedVisibility),
|
||||
entry!(KeyDown(KeyL); modifiers=[Accel], action_dispatch=DocumentMessage::ToggleSelectedLocked),
|
||||
entry!(KeyDown(KeyG); modifiers=[Alt], action_dispatch=DocumentMessage::ToggleGridVisibility),
|
||||
entry!(KeyDown(KeyZ); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::Redo),
|
||||
entry!(KeyDown(KeyZ); modifiers=[Accel, Shift], canonical, action_dispatch=DocumentMessage::Redo),
|
||||
entry!(KeyDown(KeyY); modifiers=[Accel], action_dispatch=DocumentMessage::Redo),
|
||||
entry!(KeyDown(KeyZ); modifiers=[Accel], action_dispatch=DocumentMessage::Undo),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=DocumentMessage::SelectAllLayers),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::DeselectAllLayers),
|
||||
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=DocumentMessage::DeselectAllLayers),
|
||||
entry!(KeyDown(KeyA); modifiers=[Alt], action_dispatch=DocumentMessage::DeselectAllLayers),
|
||||
entry!(KeyDown(KeyS); modifiers=[Accel], action_dispatch=DocumentMessage::SaveDocument),
|
||||
entry!(KeyDown(KeyD); modifiers=[Accel], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
|
||||
entry!(KeyDown(KeyD); modifiers=[Accel], canonical, action_dispatch=DocumentMessage::DuplicateSelectedLayers),
|
||||
entry!(KeyDown(KeyJ); modifiers=[Accel], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
|
||||
entry!(KeyDown(KeyG); modifiers=[Accel], action_dispatch=DocumentMessage::GroupSelectedLayers { group_folder_type: GroupFolderType::Layer }),
|
||||
entry!(KeyDown(KeyG); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::UngroupSelectedLayers),
|
||||
entry!(KeyDown(KeyN); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::CreateEmptyFolder),
|
||||
entry!(KeyDown(Backslash); modifiers=[Alt], action_dispatch=DocumentMessage::SelectParentLayer),
|
||||
entry!(KeyDown(BracketLeft); modifiers=[Alt], action_dispatch=DocumentMessage::SelectionStepBack),
|
||||
entry!(KeyDown(BracketRight); modifiers=[Alt], action_dispatch=DocumentMessage::SelectionStepForward),
|
||||
entry!(KeyDown(Escape); modifiers=[Shift], action_dispatch=DocumentMessage::SelectParentLayer),
|
||||
entry!(KeyDown(BracketLeft); modifiers=[Alt], canonical, action_dispatch=DocumentMessage::SelectionStepBack),
|
||||
entry!(KeyDown(BracketRight); modifiers=[Alt], canonical, action_dispatch=DocumentMessage::SelectionStepForward),
|
||||
entry!(KeyDown(MouseBack); action_dispatch=DocumentMessage::SelectionStepBack),
|
||||
entry!(KeyDown(MouseForward); action_dispatch=DocumentMessage::SelectionStepForward),
|
||||
entry!(KeyDown(Digit0); modifiers=[Accel], action_dispatch=DocumentMessage::ZoomCanvasToFitAll),
|
||||
|
@ -376,9 +378,9 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(KeyDown(ArrowRight); action_dispatch=DocumentMessage::NudgeSelectedLayers { delta_x: NUDGE_AMOUNT, delta_y: 0., resize: Alt, resize_opposite_corner: Control }),
|
||||
//
|
||||
// TransformLayerMessage
|
||||
entry!(KeyDown(KeyG); action_dispatch=TransformLayerMessage::BeginGRS { transform_type: TransformType::Grab }),
|
||||
entry!(KeyDown(KeyR); action_dispatch=TransformLayerMessage::BeginGRS { transform_type: TransformType::Rotate }),
|
||||
entry!(KeyDown(KeyS); action_dispatch=TransformLayerMessage::BeginGRS { transform_type: TransformType::Scale }),
|
||||
entry!(KeyDown(KeyG); action_dispatch=TransformLayerMessage::BeginGrab),
|
||||
entry!(KeyDown(KeyR); action_dispatch=TransformLayerMessage::BeginRotate),
|
||||
entry!(KeyDown(KeyS); action_dispatch=TransformLayerMessage::BeginScale),
|
||||
entry!(KeyDown(Digit0); action_dispatch=TransformLayerMessage::TypeDigit { digit: 0 }),
|
||||
entry!(KeyDown(Digit1); action_dispatch=TransformLayerMessage::TypeDigit { digit: 1 }),
|
||||
entry!(KeyDown(Digit2); action_dispatch=TransformLayerMessage::TypeDigit { digit: 2 }),
|
||||
|
|
|
@ -25,7 +25,7 @@ impl MessageHandler<KeyMappingMessage, KeyMappingMessageData<'_>> for KeyMapping
|
|||
}
|
||||
|
||||
impl KeyMappingMessageHandler {
|
||||
pub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant) -> Vec<KeysGroup> {
|
||||
pub fn action_input_mapping(&self, action_to_find: &MessageDiscriminant) -> Option<KeysGroup> {
|
||||
self.mapping_handler.action_input_mapping(action_to_find)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,44 +24,54 @@ macro_rules! modifiers {
|
|||
/// Each handler adds or removes actions in the form of message discriminants. Here, we tie an input condition (such as a hotkey) to an action's full message.
|
||||
/// When an action is currently available, and the user enters that input, the action's message is dispatched on the message bus.
|
||||
macro_rules! entry {
|
||||
// Pattern with canonical parameter
|
||||
($input:expr_2021; $(modifiers=[$($modifier:ident),*],)? $(refresh_keys=[$($refresh:ident),* $(,)?],)? canonical, action_dispatch=$action_dispatch:expr_2021$(,)?) => {
|
||||
entry!($input; $($($modifier),*)?; $($($refresh),*)?; $action_dispatch; true)
|
||||
};
|
||||
|
||||
// Pattern without canonical parameter
|
||||
($input:expr_2021; $(modifiers=[$($modifier:ident),*],)? $(refresh_keys=[$($refresh:ident),* $(,)?],)? action_dispatch=$action_dispatch:expr_2021$(,)?) => {
|
||||
entry!($input; $($($modifier),*)?; $($($refresh),*)?; $action_dispatch; false)
|
||||
};
|
||||
|
||||
// Implementation macro to avoid code duplication
|
||||
($input:expr; $($modifier:ident),*; $($refresh:ident),*; $action_dispatch:expr; $canonical:expr) => {
|
||||
&[&[
|
||||
// Cause the `action_dispatch` message to be sent when the specified input occurs.
|
||||
MappingEntry {
|
||||
action: $action_dispatch.into(),
|
||||
input: $input,
|
||||
modifiers: modifiers!($($($modifier),*)?),
|
||||
modifiers: modifiers!($($modifier),*),
|
||||
canonical: $canonical,
|
||||
},
|
||||
|
||||
// Also cause the `action_dispatch` message to be sent when any of the specified refresh keys change.
|
||||
//
|
||||
// For example, a snapping state bound to the Shift key may change if the user presses or releases that key.
|
||||
// In that case, we want to dispatch the action's message even though the pointer didn't necessarily move so
|
||||
// the input handler can update the snapping state without making the user move the mouse to see the change.
|
||||
$(
|
||||
$(
|
||||
MappingEntry {
|
||||
action: $action_dispatch.into(),
|
||||
input: InputMapperMessage::KeyDown(Key::$refresh),
|
||||
modifiers: modifiers!(),
|
||||
canonical: $canonical,
|
||||
},
|
||||
MappingEntry {
|
||||
action: $action_dispatch.into(),
|
||||
input: InputMapperMessage::KeyUp(Key::$refresh),
|
||||
modifiers: modifiers!(),
|
||||
canonical: $canonical,
|
||||
},
|
||||
MappingEntry {
|
||||
action: $action_dispatch.into(),
|
||||
input: InputMapperMessage::KeyDownNoRepeat(Key::$refresh),
|
||||
modifiers: modifiers!(),
|
||||
canonical: $canonical,
|
||||
},
|
||||
MappingEntry {
|
||||
action: $action_dispatch.into(),
|
||||
input: InputMapperMessage::KeyUpNoRepeat(Key::$refresh),
|
||||
modifiers: modifiers!(),
|
||||
canonical: $canonical,
|
||||
},
|
||||
)*
|
||||
)*
|
||||
]]
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use super::input_keyboard::{Key, KeysGroup, LayoutKeysGroup, all_required_modifiers_pressed};
|
||||
use super::input_keyboard::{KeysGroup, LayoutKeysGroup, all_required_modifiers_pressed};
|
||||
use crate::messages::input_mapper::key_mapping::MappingVariant;
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::{KeyStates, NUMBER_OF_KEYS};
|
||||
use crate::messages::input_mapper::utility_types::input_mouse::NUMBER_OF_MOUSE_BUTTONS;
|
||||
|
@ -120,6 +120,8 @@ pub struct MappingEntry {
|
|||
pub input: InputMapperMessage,
|
||||
/// Any additional keys that must be also pressed for this input mapping to match
|
||||
pub modifiers: KeyStates,
|
||||
/// True indicates that this takes priority as the labeled hotkey shown in UI menus and tooltips instead of an alternate binding for the same action
|
||||
pub canonical: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
|
@ -130,36 +132,12 @@ pub enum ActionKeys {
|
|||
}
|
||||
|
||||
impl ActionKeys {
|
||||
pub fn to_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) -> String {
|
||||
pub fn to_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) -> String {
|
||||
match self {
|
||||
Self::Action(action) => {
|
||||
// Take the shortest sequence of keys
|
||||
let mut key_sequences = action_input_mapping(action);
|
||||
key_sequences.sort_by_key(|keys| keys.0.len());
|
||||
let mut secondary_key_sequence = key_sequences.get(1).cloned();
|
||||
let mut key_sequence = key_sequences.get_mut(0);
|
||||
|
||||
// TODO: Replace this exception with a per-action choice of canonical hotkey
|
||||
if let Some(key_sequence) = &mut key_sequence {
|
||||
if key_sequence.0.as_slice() == [Key::MouseBack] {
|
||||
if let Some(replacement) = &mut secondary_key_sequence {
|
||||
std::mem::swap(*key_sequence, replacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(key_sequence) = &mut key_sequence {
|
||||
if key_sequence.0.as_slice() == [Key::MouseForward] {
|
||||
if let Some(replacement) = &mut secondary_key_sequence {
|
||||
std::mem::swap(*key_sequence, replacement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(keys) = key_sequence {
|
||||
let mut taken_keys = KeysGroup::default();
|
||||
std::mem::swap(keys, &mut taken_keys);
|
||||
let description = taken_keys.to_string();
|
||||
*self = Self::Keys(taken_keys.into());
|
||||
if let Some(keys) = action_input_mapping(action) {
|
||||
let description = keys.to_string();
|
||||
*self = Self::Keys(keys.into());
|
||||
description
|
||||
} else {
|
||||
*self = Self::Keys(KeysGroup::default().into());
|
||||
|
|
|
@ -342,7 +342,7 @@ impl LayoutMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
impl<F: Fn(&MessageDiscriminant) -> Vec<KeysGroup>> MessageHandler<LayoutMessage, F> for LayoutMessageHandler {
|
||||
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 } => {
|
||||
|
@ -385,7 +385,7 @@ impl LayoutMessageHandler {
|
|||
layout_target: LayoutTarget,
|
||||
new_layout: Layout,
|
||||
responses: &mut VecDeque<Message>,
|
||||
action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>,
|
||||
action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>,
|
||||
) {
|
||||
match new_layout {
|
||||
Layout::WidgetLayout(_) => {
|
||||
|
@ -419,7 +419,7 @@ impl LayoutMessageHandler {
|
|||
}
|
||||
|
||||
/// Send a diff to the frontend based on the layout target.
|
||||
fn send_diff(&self, mut diff: Vec<WidgetDiff>, layout_target: LayoutTarget, responses: &mut VecDeque<Message>, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) {
|
||||
fn send_diff(&self, mut diff: Vec<WidgetDiff>, layout_target: LayoutTarget, responses: &mut VecDeque<Message>, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) {
|
||||
diff.iter_mut().for_each(|diff| diff.new_value.apply_keyboard_shortcut(action_input_mapping));
|
||||
|
||||
let message = match layout_target {
|
||||
|
|
|
@ -109,7 +109,7 @@ pub enum Layout {
|
|||
}
|
||||
|
||||
impl Layout {
|
||||
pub fn unwrap_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) -> MenuLayout {
|
||||
pub fn unwrap_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) -> MenuLayout {
|
||||
if let Self::MenuLayout(mut menu) = self {
|
||||
menu.layout
|
||||
.iter_mut()
|
||||
|
@ -589,7 +589,7 @@ pub enum DiffUpdate {
|
|||
|
||||
impl DiffUpdate {
|
||||
/// Append the keyboard shortcut to the tooltip where applicable
|
||||
pub fn apply_keyboard_shortcut(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) {
|
||||
pub fn apply_keyboard_shortcut(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) {
|
||||
// Function used multiple times later in this code block to convert `ActionKeys::Action` to `ActionKeys::Keys` and append its shortcut to the tooltip
|
||||
let apply_shortcut_to_tooltip = |tooltip_shortcut: &mut ActionKeys, tooltip: &mut String| {
|
||||
let shortcut_text = tooltip_shortcut.to_keys(action_input_mapping);
|
||||
|
|
|
@ -12,7 +12,7 @@ impl MenuBarEntryChildren {
|
|||
Self(Vec::new())
|
||||
}
|
||||
|
||||
pub fn fill_in_shortcut_actions_with_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Vec<KeysGroup>) {
|
||||
pub fn fill_in_shortcut_actions_with_keys(&mut self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) {
|
||||
let entries = self.0.iter_mut().flatten();
|
||||
|
||||
for entry in entries {
|
||||
|
|
|
@ -444,6 +444,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
DocumentMessage::EnterNestedNetwork { node_id } => {
|
||||
self.breadcrumb_network_path.push(node_id);
|
||||
self.selection_network_path.clone_from(&self.breadcrumb_network_path);
|
||||
responses.add(NodeGraphMessage::UnloadWires);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(DocumentMessage::ZoomCanvasToFitAll);
|
||||
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
||||
|
@ -473,9 +474,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
self.breadcrumb_network_path.pop();
|
||||
self.selection_network_path.clone_from(&self.breadcrumb_network_path);
|
||||
}
|
||||
responses.add(NodeGraphMessage::UnloadWires);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(DocumentMessage::PTZUpdate);
|
||||
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
DocumentMessage::FlipSelectedLayers { flip_axis } => {
|
||||
let scale = match flip_axis {
|
||||
|
@ -525,6 +527,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
}
|
||||
}
|
||||
DocumentMessage::GraphViewOverlay { open } => {
|
||||
let opened = !self.graph_view_overlay_open && open;
|
||||
self.graph_view_overlay_open = open;
|
||||
|
||||
responses.add(FrontendMessage::UpdateGraphViewOverlay { open });
|
||||
|
@ -537,6 +540,9 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||
|
||||
responses.add(DocumentMessage::RenderRulers);
|
||||
responses.add(DocumentMessage::RenderScrollbars);
|
||||
if opened {
|
||||
responses.add(NodeGraphMessage::UnloadWires);
|
||||
}
|
||||
if open {
|
||||
responses.add(ToolMessage::DeactivateTools);
|
||||
responses.add(OverlaysMessage::Draw); // Clear the overlays
|
||||
|
@ -1878,6 +1884,7 @@ impl DocumentMessageHandler {
|
|||
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
||||
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
|
||||
// TODO: Remove once the footprint is used to load the imports/export distances from the edge
|
||||
responses.add(NodeGraphMessage::UnloadWires);
|
||||
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
||||
responses.add(Message::StartBuffer);
|
||||
Some(previous_network)
|
||||
|
@ -1909,7 +1916,8 @@ impl DocumentMessageHandler {
|
|||
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
||||
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
|
||||
|
||||
responses.add(NodeGraphMessage::UnloadWires);
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
Some(previous_network)
|
||||
}
|
||||
|
||||
|
@ -2066,7 +2074,7 @@ impl DocumentMessageHandler {
|
|||
/// Loads all of the fonts in the document.
|
||||
pub fn load_layer_resources(&self, responses: &mut VecDeque<Message>) {
|
||||
let mut fonts = HashSet::new();
|
||||
for (_node_id, node) in self.document_network().recursive_nodes() {
|
||||
for (_node_id, node, _) in self.document_network().recursive_nodes() {
|
||||
for input in &node.inputs {
|
||||
if let Some(TaggedValue::Font(font)) = input.as_value() {
|
||||
fonts.insert(font.clone());
|
||||
|
|
|
@ -6,12 +6,12 @@ use bezier_rs::Subpath;
|
|||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Artboard;
|
||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{CPU, RasterDataTable};
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::VectorModificationType;
|
||||
use graphene_std::vector::brush_stroke::BrushStroke;
|
||||
use graphene_std::vector::style::{Fill, Stroke};
|
||||
|
||||
#[impl_message(Message, DocumentMessage, GraphOperation)]
|
||||
|
|
|
@ -9,10 +9,10 @@ use graph_craft::concrete;
|
|||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::Artboard;
|
||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{CPU, RasterDataTable};
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::brush_stroke::BrushStroke;
|
||||
use graphene_std::vector::style::{Fill, Stroke};
|
||||
use graphene_std::vector::{PointId, VectorModificationType};
|
||||
use graphene_std::vector::{VectorData, VectorDataTable};
|
||||
|
@ -97,6 +97,8 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
};
|
||||
}
|
||||
|
||||
let layer_input_connector = post_node_input_connector.clone();
|
||||
|
||||
// Sink post_node down to the end of the non layer chain that feeds into post_node, such that pre_node is the layer node at insert_index + 1, or None if insert_index is the last layer
|
||||
loop {
|
||||
let pre_node_output_connector = network_interface.upstream_output_connector(&post_node_input_connector, &[]);
|
||||
|
@ -105,6 +107,11 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
Some(OutputConnector::Node { node_id: pre_node_id, .. }) if !network_interface.is_layer(&pre_node_id, &[]) => {
|
||||
// Update post_node_input_connector for the next iteration
|
||||
post_node_input_connector = InputConnector::node(pre_node_id, 0);
|
||||
// Insert directly under layer if moving to the end of a layer stack that ends with a non layer node that does not have an exposed primary input
|
||||
let primary_is_exposed = network_interface.input_from_connector(&post_node_input_connector, &[]).is_some_and(|input| input.is_exposed());
|
||||
if !primary_is_exposed {
|
||||
return layer_input_connector;
|
||||
}
|
||||
}
|
||||
_ => break, // Break if pre_node_output_connector is None or if pre_node_id is a layer
|
||||
}
|
||||
|
@ -190,6 +197,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
Some(NodeInput::value(TaggedValue::F64(typesetting.character_spacing), false)),
|
||||
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_width), false)),
|
||||
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_height), false)),
|
||||
Some(NodeInput::value(TaggedValue::F64(typesetting.tilt), false)),
|
||||
]);
|
||||
|
||||
let text_id = NodeId::new();
|
||||
|
|
|
@ -5,8 +5,8 @@ use super::node_properties::{self, ParameterWidgetsInfo};
|
|||
use super::utility_types::FrontendNodeType;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{
|
||||
DocumentNodeMetadata, DocumentNodePersistentMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata, NumberInputSettings,
|
||||
PropertiesRow, Vec2InputSettings, WidgetOverride,
|
||||
DocumentNodeMetadata, DocumentNodePersistentMetadata, InputMetadata, NodeNetworkInterface, NodeNetworkMetadata, NodeNetworkPersistentMetadata, NodeTemplate, NodeTypePersistentMetadata,
|
||||
NumberInputSettings, Vec2InputSettings, WidgetOverride,
|
||||
};
|
||||
use crate::messages::portfolio::utility_types::PersistentData;
|
||||
use crate::messages::prelude::Message;
|
||||
|
@ -16,9 +16,9 @@ use graph_craft::ProtoNodeIdentifier;
|
|||
use graph_craft::concrete;
|
||||
use graph_craft::document::value::*;
|
||||
use graph_craft::document::*;
|
||||
use graphene_std::brush::brush_cache::BrushCache;
|
||||
use graphene_std::extract_xy::XY;
|
||||
use graphene_std::raster::brush_cache::BrushCache;
|
||||
use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha};
|
||||
use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha};
|
||||
use graphene_std::raster_types::{CPU, RasterDataTable};
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::transform::Footprint;
|
||||
|
@ -37,26 +37,14 @@ pub struct NodePropertiesContext<'a> {
|
|||
|
||||
impl NodePropertiesContext<'_> {
|
||||
pub fn call_widget_override(&mut self, node_id: &NodeId, index: usize) -> Option<Vec<LayoutGroup>> {
|
||||
let input_properties_row = self.network_interface.input_properties_row(node_id, index, self.selection_network_path)?;
|
||||
let input_properties_row = self.network_interface.persistent_input_metadata(node_id, index, self.selection_network_path)?;
|
||||
if let Some(widget_override) = &input_properties_row.widget_override {
|
||||
let Some(widget_override_lambda) = INPUT_OVERRIDES.get(widget_override) else {
|
||||
log::error!("Could not get widget override '{widget_override}' lambda in call_widget_override");
|
||||
return None;
|
||||
};
|
||||
widget_override_lambda(*node_id, index, self)
|
||||
.map(|layout_group| {
|
||||
let Some(input_properties_row) = self.network_interface.input_properties_row(node_id, index, self.selection_network_path) else {
|
||||
log::error!("Could not get input properties row in call_widget_override");
|
||||
return Vec::new();
|
||||
};
|
||||
match &input_properties_row.input_data.get("tooltip").and_then(|tooltip| tooltip.as_str()) {
|
||||
Some(tooltip) => layout_group.into_iter().map(|widget| widget.with_tooltip(*tooltip)).collect::<Vec<_>>(),
|
||||
_ => layout_group,
|
||||
}
|
||||
})
|
||||
.map_err(|error| {
|
||||
log::error!("Error in widget override lambda: {}", error);
|
||||
})
|
||||
.map_err(|error| log::error!("Error in widget override lambda: {}", error))
|
||||
.ok()
|
||||
} else {
|
||||
None
|
||||
|
@ -105,7 +93,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("In", "TODO").into()],
|
||||
input_metadata: vec![("In", "TODO").into()],
|
||||
output_names: vec!["Out".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -126,7 +114,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("In", "TODO").into()],
|
||||
input_metadata: vec![("In", "TODO").into()],
|
||||
output_names: vec!["Out".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -223,7 +211,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
},
|
||||
..Default::default()
|
||||
}),
|
||||
input_properties: vec![("Data", "TODO").into()],
|
||||
input_metadata: vec![("Data", "TODO").into()],
|
||||
output_names: vec!["Data".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -285,7 +273,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Graphical Data", "TODO").into(), ("Over", "TODO").into()],
|
||||
input_metadata: vec![("Graphical Data", "TODO").into(), ("Over", "TODO").into()],
|
||||
output_names: vec!["Out".to_string()],
|
||||
node_type_metadata: NodeTypePersistentMetadata::layer(IVec2::new(0, 0)),
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
|
@ -397,10 +385,10 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![
|
||||
input_metadata: vec![
|
||||
("Artboards", "TODO").into(),
|
||||
PropertiesRow::with_override("Contents", "TODO", WidgetOverride::Hidden),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override("Contents", "TODO", WidgetOverride::Hidden),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Location",
|
||||
"TODO",
|
||||
WidgetOverride::Vec2(Vec2InputSettings {
|
||||
|
@ -410,7 +398,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Dimensions",
|
||||
"TODO",
|
||||
WidgetOverride::Vec2(Vec2InputSettings {
|
||||
|
@ -420,7 +408,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override("Background", "TODO", WidgetOverride::Custom("artboard_background".to_string())),
|
||||
InputMetadata::with_name_description_override("Background", "TODO", WidgetOverride::Custom("artboard_background".to_string())),
|
||||
("Clip", "TODO").into(),
|
||||
],
|
||||
output_names: vec!["Out".to_string()],
|
||||
|
@ -498,7 +486,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Empty", "TODO").into(), ("URL", "TODO").into()],
|
||||
input_metadata: vec![("Empty", "TODO").into(), ("URL", "TODO").into()],
|
||||
output_names: vec!["Image".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
|
@ -640,7 +628,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("In", "TODO").into()],
|
||||
input_metadata: vec![("In", "TODO").into()],
|
||||
output_names: vec!["Canvas".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
|
@ -740,7 +728,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Artwork", "TODO").into(), ("Footprint", "TODO").into()],
|
||||
input_metadata: vec![("Artwork", "TODO").into(), ("Footprint", "TODO").into()],
|
||||
output_names: vec!["Canvas".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
|
@ -790,7 +778,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
node_template: NodeTemplate {
|
||||
document_node: DocumentNode {
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::raster::NoisePatternNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_raster_nodes::std_nodes::NoisePatternNode")),
|
||||
inputs: vec![
|
||||
NodeInput::value(TaggedValue::None, false),
|
||||
NodeInput::value(TaggedValue::Bool(true), false),
|
||||
|
@ -812,23 +800,23 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![
|
||||
input_metadata: vec![
|
||||
("Spacer", "TODO").into(),
|
||||
("Clip", "TODO").into(),
|
||||
("Seed", "TODO").into(),
|
||||
PropertiesRow::with_override("Scale", "TODO", WidgetOverride::Custom("noise_properties_scale".to_string())),
|
||||
PropertiesRow::with_override("Noise Type", "TODO", WidgetOverride::Custom("noise_properties_noise_type".to_string())),
|
||||
PropertiesRow::with_override("Domain Warp Type", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_type".to_string())),
|
||||
PropertiesRow::with_override("Domain Warp Amplitude", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_amplitude".to_string())),
|
||||
PropertiesRow::with_override("Fractal Type", "TODO", WidgetOverride::Custom("noise_properties_fractal_type".to_string())),
|
||||
PropertiesRow::with_override("Fractal Octaves", "TODO", WidgetOverride::Custom("noise_properties_fractal_octaves".to_string())),
|
||||
PropertiesRow::with_override("Fractal Lacunarity", "TODO", WidgetOverride::Custom("noise_properties_fractal_lacunarity".to_string())),
|
||||
PropertiesRow::with_override("Fractal Gain", "TODO", WidgetOverride::Custom("noise_properties_fractal_gain".to_string())),
|
||||
PropertiesRow::with_override("Fractal Weighted Strength", "TODO", WidgetOverride::Custom("noise_properties_fractal_weighted_strength".to_string())),
|
||||
PropertiesRow::with_override("Fractal Ping Pong Strength", "TODO", WidgetOverride::Custom("noise_properties_ping_pong_strength".to_string())),
|
||||
PropertiesRow::with_override("Cellular Distance Function", "TODO", WidgetOverride::Custom("noise_properties_cellular_distance_function".to_string())),
|
||||
PropertiesRow::with_override("Cellular Return Type", "TODO", WidgetOverride::Custom("noise_properties_cellular_return_type".to_string())),
|
||||
PropertiesRow::with_override("Cellular Jitter", "TODO", WidgetOverride::Custom("noise_properties_cellular_jitter".to_string())),
|
||||
InputMetadata::with_name_description_override("Scale", "TODO", WidgetOverride::Custom("noise_properties_scale".to_string())),
|
||||
InputMetadata::with_name_description_override("Noise Type", "TODO", WidgetOverride::Custom("noise_properties_noise_type".to_string())),
|
||||
InputMetadata::with_name_description_override("Domain Warp Type", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_type".to_string())),
|
||||
InputMetadata::with_name_description_override("Domain Warp Amplitude", "TODO", WidgetOverride::Custom("noise_properties_domain_warp_amplitude".to_string())),
|
||||
InputMetadata::with_name_description_override("Fractal Type", "TODO", WidgetOverride::Custom("noise_properties_fractal_type".to_string())),
|
||||
InputMetadata::with_name_description_override("Fractal Octaves", "TODO", WidgetOverride::Custom("noise_properties_fractal_octaves".to_string())),
|
||||
InputMetadata::with_name_description_override("Fractal Lacunarity", "TODO", WidgetOverride::Custom("noise_properties_fractal_lacunarity".to_string())),
|
||||
InputMetadata::with_name_description_override("Fractal Gain", "TODO", WidgetOverride::Custom("noise_properties_fractal_gain".to_string())),
|
||||
InputMetadata::with_name_description_override("Fractal Weighted Strength", "TODO", WidgetOverride::Custom("noise_properties_fractal_weighted_strength".to_string())),
|
||||
InputMetadata::with_name_description_override("Fractal Ping Pong Strength", "TODO", WidgetOverride::Custom("noise_properties_ping_pong_strength".to_string())),
|
||||
InputMetadata::with_name_description_override("Cellular Distance Function", "TODO", WidgetOverride::Custom("noise_properties_cellular_distance_function".to_string())),
|
||||
InputMetadata::with_name_description_override("Cellular Return Type", "TODO", WidgetOverride::Custom("noise_properties_cellular_return_type".to_string())),
|
||||
InputMetadata::with_name_description_override("Cellular Jitter", "TODO", WidgetOverride::Custom("noise_properties_cellular_jitter".to_string())),
|
||||
],
|
||||
output_names: vec!["Image".to_string()],
|
||||
..Default::default()
|
||||
|
@ -897,7 +885,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Image", "TODO").into()],
|
||||
input_metadata: vec![("Image", "TODO").into()],
|
||||
output_names: vec!["Red".to_string(), "Green".to_string(), "Blue".to_string(), "Alpha".to_string()],
|
||||
has_primary_output: false,
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
|
@ -982,7 +970,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Coordinate", "TODO").into()],
|
||||
input_metadata: vec![("Coordinate", "TODO").into()],
|
||||
output_names: vec!["X".to_string(), "Y".to_string()],
|
||||
has_primary_output: false,
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
|
@ -1016,7 +1004,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
},
|
||||
description: Cow::Borrowed("TODO"),
|
||||
description: Cow::Borrowed(
|
||||
"Decomposes the X and Y components of a 2D coordinate.\n\nThe inverse of this node is \"Coordinate Value\", which can have either or both its X and Y exposed as graph inputs.",
|
||||
),
|
||||
properties: None,
|
||||
},
|
||||
// TODO: Remove this and just use the proto node definition directly
|
||||
|
@ -1030,11 +1020,11 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
nodes: vec![DocumentNode {
|
||||
inputs: vec![
|
||||
NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
|
||||
NodeInput::network(concrete!(Vec<graphene_std::vector::brush_stroke::BrushStroke>), 1),
|
||||
NodeInput::network(concrete!(Vec<brush::brush_stroke::BrushStroke>), 1),
|
||||
NodeInput::network(concrete!(BrushCache), 2),
|
||||
],
|
||||
manual_composition: Some(concrete!(Context)),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::brush::BrushNode")),
|
||||
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_brush::brush::BrushNode")),
|
||||
..Default::default()
|
||||
}]
|
||||
.into_iter()
|
||||
|
@ -1051,7 +1041,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Background", "TODO").into(), ("Trace", "TODO").into(), ("Cache", "TODO").into()],
|
||||
input_metadata: vec![("Background", "TODO").into(), ("Trace", "TODO").into(), ("Cache", "TODO").into()],
|
||||
output_names: vec!["Image".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
|
@ -1088,7 +1078,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Image", "TODO").into()],
|
||||
input_metadata: vec![("Image", "TODO").into()],
|
||||
output_names: vec!["Image".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -1107,7 +1097,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Image", "TODO").into()],
|
||||
input_metadata: vec![("Image", "TODO").into()],
|
||||
output_names: vec!["Image".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -1150,7 +1140,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("In", "TODO").into()],
|
||||
input_metadata: vec![("In", "TODO").into()],
|
||||
output_names: vec!["Storage".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
|
@ -1229,7 +1219,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("In", "TODO").into(), ("In", "TODO").into()],
|
||||
input_metadata: vec![("In", "TODO").into(), ("In", "TODO").into()],
|
||||
output_names: vec!["Output Buffer".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
|
@ -1376,7 +1366,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("In", "TODO").into()],
|
||||
input_metadata: vec![("In", "TODO").into()],
|
||||
output_names: vec!["Texture".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
|
@ -1430,7 +1420,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Node", "TODO").into()],
|
||||
input_metadata: vec![("Node", "TODO").into()],
|
||||
output_names: vec!["Document Node".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -1501,7 +1491,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![("Vector Data", "TODO").into(), ("Modification", "TODO").into()],
|
||||
input_metadata: vec![("Vector Data", "TODO").into(), ("Modification", "TODO").into()],
|
||||
output_names: vec!["Vector Data".to_string()],
|
||||
network_metadata: Some(NodeNetworkMetadata {
|
||||
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||
|
@ -1556,15 +1546,16 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().character_spacing), false),
|
||||
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),
|
||||
],
|
||||
..Default::default()
|
||||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
input_properties: vec![
|
||||
input_metadata: vec![
|
||||
("Editor API", "TODO").into(),
|
||||
PropertiesRow::with_override("Text", "TODO", WidgetOverride::Custom("text_area".to_string())),
|
||||
PropertiesRow::with_override("Font", "TODO", WidgetOverride::Custom("text_font".to_string())),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override("Text", "TODO", WidgetOverride::Custom("text_area".to_string())),
|
||||
InputMetadata::with_name_description_override("Font", "TODO", WidgetOverride::Custom("text_font".to_string())),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Size",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
|
@ -1573,42 +1564,56 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Line Height",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
unit: Some("x".to_string()),
|
||||
min: Some(0.),
|
||||
step: Some(0.1),
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Character Spacing",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
unit: Some(" px".to_string()),
|
||||
min: Some(0.),
|
||||
step: Some(0.1),
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Max Width",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
unit: Some(" px".to_string()),
|
||||
min: Some(1.),
|
||||
blank_assist: false,
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Max Height",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
unit: Some(" px".to_string()),
|
||||
min: Some(1.),
|
||||
blank_assist: false,
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Tilt",
|
||||
"Faux italic",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
min: Some(-85.),
|
||||
max: Some(85.),
|
||||
unit: Some("°".to_string()),
|
||||
..Default::default()
|
||||
}),
|
||||
),
|
||||
],
|
||||
output_names: vec!["Vector".to_string()],
|
||||
..Default::default()
|
||||
|
@ -1691,9 +1696,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
},
|
||||
..Default::default()
|
||||
}),
|
||||
input_properties: vec![
|
||||
input_metadata: vec![
|
||||
("Vector Data", "TODO").into(),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Translation",
|
||||
"TODO",
|
||||
WidgetOverride::Vec2(Vec2InputSettings {
|
||||
|
@ -1703,8 +1708,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override("Rotation", "TODO", WidgetOverride::Custom("transform_rotation".to_string())),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override("Rotation", "TODO", WidgetOverride::Custom("transform_rotation".to_string())),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Scale",
|
||||
"TODO",
|
||||
WidgetOverride::Vec2(Vec2InputSettings {
|
||||
|
@ -1714,8 +1719,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())),
|
||||
PropertiesRow::with_override("Pivot", "TODO", WidgetOverride::Hidden),
|
||||
InputMetadata::with_name_description_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())),
|
||||
InputMetadata::with_name_description_override("Pivot", "TODO", WidgetOverride::Hidden),
|
||||
],
|
||||
output_names: vec!["Data".to_string()],
|
||||
..Default::default()
|
||||
|
@ -1814,7 +1819,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
},
|
||||
..Default::default()
|
||||
}),
|
||||
input_properties: vec![("Group of Paths", "TODO").into(), ("Operation", "TODO").into()],
|
||||
input_metadata: vec![("Group of Paths", "TODO").into(), ("Operation", "TODO").into()],
|
||||
output_names: vec!["Vector".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
|
@ -1841,7 +1846,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0),
|
||||
NodeInput::network(concrete!(vector::misc::PointSpacingType), 1),
|
||||
NodeInput::network(concrete!(f64), 2),
|
||||
NodeInput::network(concrete!(f64), 3),
|
||||
NodeInput::network(concrete!(u32), 3),
|
||||
NodeInput::network(concrete!(f64), 4),
|
||||
NodeInput::network(concrete!(f64), 5),
|
||||
NodeInput::network(concrete!(bool), 6),
|
||||
|
@ -1880,7 +1885,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
NodeInput::value(TaggedValue::VectorData(graphene_std::vector::VectorDataTable::default()), true),
|
||||
NodeInput::value(TaggedValue::PointSpacingType(Default::default()), false),
|
||||
NodeInput::value(TaggedValue::F64(100.), false),
|
||||
NodeInput::value(TaggedValue::F64(100.), false),
|
||||
NodeInput::value(TaggedValue::U32(100), false),
|
||||
NodeInput::value(TaggedValue::F64(0.), false),
|
||||
NodeInput::value(TaggedValue::F64(0.), false),
|
||||
NodeInput::value(TaggedValue::Bool(false), false),
|
||||
|
@ -1940,10 +1945,10 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
},
|
||||
..Default::default()
|
||||
}),
|
||||
input_properties: vec![
|
||||
input_metadata: vec![
|
||||
("Vector Data", "The shape to be resampled and converted into a polyline.").into(),
|
||||
Into::<PropertiesRow>::into(("Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_SPACING)),
|
||||
PropertiesRow::with_override(
|
||||
("Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_SPACING).into(),
|
||||
InputMetadata::with_name_description_override(
|
||||
"Separation",
|
||||
node_properties::SAMPLE_POLYLINE_TOOLTIP_SEPARATION,
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
|
@ -1952,7 +1957,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Quantity",
|
||||
node_properties::SAMPLE_POLYLINE_TOOLTIP_QUANTITY,
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
|
@ -1961,7 +1966,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Start Offset",
|
||||
node_properties::SAMPLE_POLYLINE_TOOLTIP_START_OFFSET,
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
|
@ -1970,7 +1975,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Stop Offset",
|
||||
node_properties::SAMPLE_POLYLINE_TOOLTIP_STOP_OFFSET,
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
|
@ -1979,7 +1984,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
Into::<PropertiesRow>::into(("Adaptive Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING)),
|
||||
("Adaptive Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_ADAPTIVE_SPACING).into(),
|
||||
],
|
||||
output_names: vec!["Vector".to_string()],
|
||||
..Default::default()
|
||||
|
@ -2083,9 +2088,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
},
|
||||
..Default::default()
|
||||
}),
|
||||
input_properties: vec![
|
||||
input_metadata: vec![
|
||||
("Vector Data", "TODO").into(),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Separation Disk Diameter",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
|
@ -2096,7 +2101,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
..Default::default()
|
||||
}),
|
||||
),
|
||||
PropertiesRow::with_override(
|
||||
InputMetadata::with_name_description_override(
|
||||
"Seed",
|
||||
"TODO",
|
||||
WidgetOverride::Number(NumberInputSettings {
|
||||
|
@ -2138,7 +2143,7 @@ fn static_node_properties() -> NodeProperties {
|
|||
map.insert("sample_polyline_properties".to_string(), Box::new(node_properties::sample_polyline_properties));
|
||||
map.insert(
|
||||
"identity_properties".to_string(),
|
||||
Box::new(|_node_id, _context| node_properties::string_properties("The identity node simply passes its data through.")),
|
||||
Box::new(|_node_id, _context| node_properties::string_properties("The identity node passes its data through.")),
|
||||
);
|
||||
map.insert(
|
||||
"monitor_properties".to_string(),
|
||||
|
@ -2158,7 +2163,7 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"string".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let Some(value) = context.network_interface.input_metadata(&node_id, index, "string_properties", context.selection_network_path) else {
|
||||
let Some(value) = context.network_interface.input_data(&node_id, index, "string_properties", context.selection_network_path) else {
|
||||
return Err(format!("Could not get string properties for node {}", node_id));
|
||||
};
|
||||
let Some(string) = value.as_str() else {
|
||||
|
@ -2170,37 +2175,36 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"number".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let mut number_input = NumberInput::default();
|
||||
if let Some(unit) = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "unit", context.selection_network_path)
|
||||
.input_data(&node_id, index, "unit", context.selection_network_path)
|
||||
.and_then(|value| value.as_str())
|
||||
{
|
||||
number_input = number_input.unit(unit);
|
||||
}
|
||||
if let Some(min) = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "min", context.selection_network_path)
|
||||
.input_data(&node_id, index, "min", context.selection_network_path)
|
||||
.and_then(|value| value.as_f64())
|
||||
{
|
||||
number_input = number_input.min(min);
|
||||
}
|
||||
if let Some(max) = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "max", context.selection_network_path)
|
||||
.input_data(&node_id, index, "max", context.selection_network_path)
|
||||
.and_then(|value| value.as_f64())
|
||||
{
|
||||
number_input = number_input.max(max);
|
||||
}
|
||||
if let Some(step) = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "step", context.selection_network_path)
|
||||
.input_data(&node_id, index, "step", context.selection_network_path)
|
||||
.and_then(|value| value.as_f64())
|
||||
{
|
||||
number_input = number_input.step(step);
|
||||
}
|
||||
if let Some(mode) = context.network_interface.input_metadata(&node_id, index, "mode", context.selection_network_path).map(|value| {
|
||||
if let Some(mode) = context.network_interface.input_data(&node_id, index, "mode", context.selection_network_path).map(|value| {
|
||||
let mode: NumberInputMode = serde_json::from_value(value.clone()).unwrap();
|
||||
mode
|
||||
}) {
|
||||
|
@ -2208,87 +2212,83 @@ fn static_input_properties() -> InputProperties {
|
|||
}
|
||||
if let Some(range_min) = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "range_min", context.selection_network_path)
|
||||
.input_data(&node_id, index, "range_min", context.selection_network_path)
|
||||
.and_then(|value| value.as_f64())
|
||||
{
|
||||
number_input = number_input.range_min(Some(range_min));
|
||||
}
|
||||
if let Some(range_max) = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "range_max", context.selection_network_path)
|
||||
.input_data(&node_id, index, "range_max", context.selection_network_path)
|
||||
.and_then(|value| value.as_f64())
|
||||
{
|
||||
number_input = number_input.range_max(Some(range_max));
|
||||
}
|
||||
if let Some(is_integer) = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "is_integer", context.selection_network_path)
|
||||
.input_data(&node_id, index, "is_integer", context.selection_network_path)
|
||||
.and_then(|value| value.as_bool())
|
||||
{
|
||||
number_input = number_input.is_integer(is_integer);
|
||||
}
|
||||
let blank_assist = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "blank_assist", context.selection_network_path)
|
||||
.input_data(&node_id, index, "blank_assist", context.selection_network_path)
|
||||
.and_then(|value| value.as_bool())
|
||||
.unwrap_or_else(|| {
|
||||
log::error!("Could not get blank assist when displaying number input for node {node_id}, index {index}");
|
||||
true
|
||||
});
|
||||
|
||||
Ok(vec![LayoutGroup::Row {
|
||||
widgets: node_properties::number_widget(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, blank_assist), number_input),
|
||||
widgets: node_properties::number_widget(ParameterWidgetsInfo::new(node_id, index, blank_assist, context), number_input),
|
||||
}])
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"vec2".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let x = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "x", context.selection_network_path)
|
||||
.input_data(&node_id, index, "x", context.selection_network_path)
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_else(|| {
|
||||
log::error!("Could not get x for vec2 input");
|
||||
""
|
||||
});
|
||||
})
|
||||
.to_string();
|
||||
let y = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "y", context.selection_network_path)
|
||||
.input_data(&node_id, index, "y", context.selection_network_path)
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_else(|| {
|
||||
log::error!("Could not get y for vec2 input");
|
||||
""
|
||||
});
|
||||
})
|
||||
.to_string();
|
||||
let unit = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "unit", context.selection_network_path)
|
||||
.input_data(&node_id, index, "unit", context.selection_network_path)
|
||||
.and_then(|value| value.as_str())
|
||||
.unwrap_or_else(|| {
|
||||
log::error!("Could not get unit for vec2 input");
|
||||
""
|
||||
});
|
||||
})
|
||||
.to_string();
|
||||
let min = context
|
||||
.network_interface
|
||||
.input_metadata(&node_id, index, "min", context.selection_network_path)
|
||||
.input_data(&node_id, index, "min", context.selection_network_path)
|
||||
.and_then(|value| value.as_f64());
|
||||
|
||||
Ok(vec![node_properties::coordinate_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
x,
|
||||
y,
|
||||
unit,
|
||||
min,
|
||||
)])
|
||||
Ok(vec![node_properties::coordinate_widget(ParameterWidgetsInfo::new(node_id, index, true, context), &x, &y, &unit, min)])
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"noise_properties_scale".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let scale = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default().min(0.).disabled(!coherent_noise_active),
|
||||
);
|
||||
Ok(vec![scale.into()])
|
||||
|
@ -2297,20 +2297,16 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_noise_type".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let noise_type_row = enum_choice::<NoiseType>()
|
||||
.for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true))
|
||||
.property_row();
|
||||
let noise_type_row = enum_choice::<NoiseType>().for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)).property_row();
|
||||
Ok(vec![noise_type_row, LayoutGroup::Row { widgets: Vec::new() }])
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"noise_properties_domain_warp_type".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let domain_warp_type = enum_choice::<DomainWarpType>()
|
||||
.for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true))
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, index, true, context))
|
||||
.disabled(!coherent_noise_active)
|
||||
.property_row();
|
||||
Ok(vec![domain_warp_type])
|
||||
|
@ -2319,10 +2315,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_domain_warp_amplitude".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (_, coherent_noise_active, _, _, domain_warp_active, _) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let domain_warp_amplitude = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default().min(0.).disabled(!coherent_noise_active || !domain_warp_active),
|
||||
);
|
||||
Ok(vec![domain_warp_amplitude.into(), LayoutGroup::Row { widgets: Vec::new() }])
|
||||
|
@ -2331,10 +2326,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_fractal_type".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (_, coherent_noise_active, _, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let fractal_type_row = enum_choice::<FractalType>()
|
||||
.for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true))
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, index, true, context))
|
||||
.disabled(!coherent_noise_active)
|
||||
.property_row();
|
||||
Ok(vec![fractal_type_row])
|
||||
|
@ -2343,10 +2337,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_fractal_octaves".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let fractal_octaves = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default()
|
||||
.mode_range()
|
||||
.min(1.)
|
||||
|
@ -2361,10 +2354,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_fractal_lacunarity".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let fractal_lacunarity = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default()
|
||||
.mode_range()
|
||||
.min(0.)
|
||||
|
@ -2377,10 +2369,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_fractal_gain".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let fractal_gain = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default()
|
||||
.mode_range()
|
||||
.min(0.)
|
||||
|
@ -2393,10 +2384,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_fractal_weighted_strength".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (fractal_active, coherent_noise_active, _, _, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let fractal_weighted_strength = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default()
|
||||
.mode_range()
|
||||
.min(0.)
|
||||
|
@ -2409,10 +2399,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_ping_pong_strength".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (fractal_active, coherent_noise_active, _, ping_pong_active, _, domain_warp_only_fractal_type_wrongly_active) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let fractal_ping_pong_strength = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default()
|
||||
.mode_range()
|
||||
.min(0.)
|
||||
|
@ -2425,10 +2414,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_cellular_distance_function".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let cellular_distance_function_row = enum_choice::<CellularDistanceFunction>()
|
||||
.for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true))
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, index, true, context))
|
||||
.disabled(!coherent_noise_active || !cellular_noise_active)
|
||||
.property_row();
|
||||
Ok(vec![cellular_distance_function_row])
|
||||
|
@ -2437,10 +2425,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_cellular_return_type".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let cellular_return_type = enum_choice::<CellularReturnType>()
|
||||
.for_socket(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true))
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, index, true, context))
|
||||
.disabled(!coherent_noise_active || !cellular_noise_active)
|
||||
.property_row();
|
||||
Ok(vec![cellular_return_type])
|
||||
|
@ -2449,10 +2436,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"noise_properties_cellular_jitter".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (_, coherent_noise_active, cellular_noise_active, _, _, _) = node_properties::query_noise_pattern_state(node_id, context)?;
|
||||
let cellular_jitter = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default()
|
||||
.mode_range()
|
||||
.range_min(Some(0.))
|
||||
|
@ -2465,7 +2451,7 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"brightness".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let document_node = node_properties::get_document_node(node_id, context)?;
|
||||
let is_use_classic = document_node
|
||||
.inputs
|
||||
.iter()
|
||||
|
@ -2476,7 +2462,7 @@ fn static_input_properties() -> InputProperties {
|
|||
.unwrap_or(false);
|
||||
let (b_min, b_max) = if is_use_classic { (-100., 100.) } else { (-100., 150.) };
|
||||
let brightness = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default().mode_range().range_min(Some(b_min)).range_max(Some(b_max)).unit("%").display_decimal_places(2),
|
||||
);
|
||||
Ok(vec![brightness.into()])
|
||||
|
@ -2485,7 +2471,8 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"contrast".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let document_node = node_properties::get_document_node(node_id, context)?;
|
||||
|
||||
let is_use_classic = document_node
|
||||
.inputs
|
||||
.iter()
|
||||
|
@ -2496,7 +2483,7 @@ fn static_input_properties() -> InputProperties {
|
|||
.unwrap_or(false);
|
||||
let (c_min, c_max) = if is_use_classic { (-100., 100.) } else { (-50., 100.) };
|
||||
let contrast = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default().mode_range().range_min(Some(c_min)).range_max(Some(c_max)).unit("%").display_decimal_places(2),
|
||||
);
|
||||
Ok(vec![contrast.into()])
|
||||
|
@ -2505,21 +2492,16 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"assign_colors_gradient".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let gradient_row = node_properties::color_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ColorInput::default().allow_none(false),
|
||||
);
|
||||
let gradient_row = node_properties::color_widget(ParameterWidgetsInfo::new(node_id, index, true, context), ColorInput::default().allow_none(false));
|
||||
Ok(vec![gradient_row])
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"assign_colors_seed".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let randomize_enabled = node_properties::query_assign_colors_randomize(node_id, context)?;
|
||||
let seed_row = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default().min(0.).int().disabled(!randomize_enabled),
|
||||
);
|
||||
Ok(vec![seed_row.into()])
|
||||
|
@ -2528,10 +2510,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"assign_colors_repeat_every".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let randomize_enabled = node_properties::query_assign_colors_randomize(node_id, context)?;
|
||||
let repeat_every_row = node_properties::number_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
NumberInput::default().min(0.).int().disabled(randomize_enabled),
|
||||
);
|
||||
Ok(vec![repeat_every_row.into()])
|
||||
|
@ -2540,33 +2521,24 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"mask_stencil".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let mask = node_properties::color_widget(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true), ColorInput::default());
|
||||
let mask = node_properties::color_widget(ParameterWidgetsInfo::new(node_id, index, true, context), ColorInput::default());
|
||||
Ok(vec![mask])
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"spline_input".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
Ok(vec![LayoutGroup::Row {
|
||||
widgets: node_properties::array_of_coordinates_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
TextInput::default().centered(true),
|
||||
),
|
||||
widgets: node_properties::array_of_coordinates_widget(ParameterWidgetsInfo::new(node_id, index, true, context), TextInput::default().centered(true)),
|
||||
}])
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"transform_rotation".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
|
||||
let mut widgets = node_properties::start_widgets(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
super::utility_types::FrontendGraphDataType::Number,
|
||||
);
|
||||
let mut widgets = node_properties::start_widgets(ParameterWidgetsInfo::new(node_id, index, true, context));
|
||||
|
||||
let document_node = node_properties::get_document_node(node_id, context)?;
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
return Err("Input not found in transform rotation input override".to_string());
|
||||
};
|
||||
|
@ -2595,13 +2567,9 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"transform_skew".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
|
||||
let mut widgets = node_properties::start_widgets(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
super::utility_types::FrontendGraphDataType::Number,
|
||||
);
|
||||
let mut widgets = node_properties::start_widgets(ParameterWidgetsInfo::new(node_id, index, true, context));
|
||||
|
||||
let document_node = node_properties::get_document_node(node_id, context)?;
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
return Err("Input not found in transform skew input override".to_string());
|
||||
};
|
||||
|
@ -2643,17 +2611,15 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"text_area".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
Ok(vec![LayoutGroup::Row {
|
||||
widgets: node_properties::text_area_widget(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true)),
|
||||
widgets: node_properties::text_area_widget(ParameterWidgetsInfo::new(node_id, index, true, context)),
|
||||
}])
|
||||
}),
|
||||
);
|
||||
map.insert(
|
||||
"text_font".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
let (font, style) = node_properties::font_inputs(ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true));
|
||||
let (font, style) = node_properties::font_inputs(ParameterWidgetsInfo::new(node_id, index, true, context));
|
||||
let mut result = vec![LayoutGroup::Row { widgets: font }];
|
||||
if let Some(style) = style {
|
||||
result.push(LayoutGroup::Row { widgets: style });
|
||||
|
@ -2664,9 +2630,8 @@ fn static_input_properties() -> InputProperties {
|
|||
map.insert(
|
||||
"artboard_background".to_string(),
|
||||
Box::new(|node_id, index, context| {
|
||||
let (document_node, input_name, input_description) = node_properties::query_node_and_input_info(node_id, index, context)?;
|
||||
Ok(vec![node_properties::color_widget(
|
||||
ParameterWidgetsInfo::new(document_node, node_id, index, input_name, input_description, true),
|
||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||
ColorInput::default().allow_none(false),
|
||||
)])
|
||||
}),
|
||||
|
@ -2707,7 +2672,7 @@ pub fn collect_node_types() -> Vec<FrontendNodeType> {
|
|||
// Extract input types (already creates owned Strings)
|
||||
let input_types = implementations
|
||||
.iter()
|
||||
.flat_map(|(_, node_io)| node_io.inputs.iter().map(|ty| ty.clone().nested_type().to_string()))
|
||||
.flat_map(|(_, node_io)| node_io.inputs.iter().map(|ty| ty.nested_type().to_string()))
|
||||
.collect::<HashSet<String>>()
|
||||
.into_iter()
|
||||
.collect::<Vec<String>>();
|
||||
|
@ -2813,7 +2778,7 @@ impl DocumentNodeDefinition {
|
|||
log::error!("Path is not valid for network");
|
||||
return;
|
||||
};
|
||||
nested_node_metadata.persistent_metadata.input_properties.resize_with(input_length, PropertiesRow::default);
|
||||
nested_node_metadata.persistent_metadata.input_metadata.resize_with(input_length, InputMetadata::default);
|
||||
|
||||
// Recurse over all sub-nodes if the current node is a network implementation
|
||||
let mut current_path = path.clone();
|
||||
|
@ -2832,7 +2797,7 @@ impl DocumentNodeDefinition {
|
|||
} else {
|
||||
// Base case
|
||||
let input_len = node_template.document_node.inputs.len();
|
||||
node_template.persistent_node_metadata.input_properties.resize_with(input_len, PropertiesRow::default);
|
||||
node_template.persistent_node_metadata.input_metadata.resize_with(input_len, InputMetadata::default);
|
||||
if let DocumentNodeImplementation::Network(node_template_network) = &node_template.document_node.implementation {
|
||||
for sub_node_id in node_template_network.nodes.keys().cloned().collect::<Vec<_>>() {
|
||||
populate_input_properties(node_template, vec![sub_node_id]);
|
||||
|
@ -2844,6 +2809,10 @@ impl DocumentNodeDefinition {
|
|||
|
||||
// Set the reference to the node definition
|
||||
template.persistent_node_metadata.reference = Some(self.identifier.to_string());
|
||||
// If the display name is empty and it is not a merge node, then set it to the reference
|
||||
if template.persistent_node_metadata.display_name.is_empty() && self.identifier != "Merge" {
|
||||
template.persistent_node_metadata.display_name = self.identifier.to_string();
|
||||
}
|
||||
template
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use super::DocumentNodeDefinition;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, NodeTemplate, PropertiesRow, WidgetOverride};
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{DocumentNodePersistentMetadata, InputMetadata, NodeTemplate, WidgetOverride};
|
||||
use graph_craft::ProtoNodeIdentifier;
|
||||
use graph_craft::document::*;
|
||||
use graphene_std::registry::*;
|
||||
|
@ -67,13 +67,13 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
|
|||
},
|
||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||
// TODO: Store information for input overrides in the node macro
|
||||
input_properties: fields
|
||||
input_metadata: fields
|
||||
.iter()
|
||||
.map(|f| match f.widget_override {
|
||||
RegistryWidgetOverride::None => (f.name, f.description).into(),
|
||||
RegistryWidgetOverride::Hidden => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Hidden),
|
||||
RegistryWidgetOverride::String(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::String(str.to_string())),
|
||||
RegistryWidgetOverride::Custom(str) => PropertiesRow::with_override(f.name, f.description, WidgetOverride::Custom(str.to_string())),
|
||||
RegistryWidgetOverride::Hidden => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Hidden),
|
||||
RegistryWidgetOverride::String(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::String(str.to_string())),
|
||||
RegistryWidgetOverride::Custom(str) => InputMetadata::with_name_description_override(f.name, f.description, WidgetOverride::Custom(str.to_string())),
|
||||
})
|
||||
.collect(),
|
||||
output_names: vec![output_type.to_string()],
|
||||
|
|
|
@ -33,6 +33,7 @@ pub enum NodeGraphMessage {
|
|||
node_id: Option<NodeId>,
|
||||
node_type: String,
|
||||
xy: Option<(i32, i32)>,
|
||||
add_transaction: bool,
|
||||
},
|
||||
CreateWire {
|
||||
output_connector: OutputConnector,
|
||||
|
@ -123,6 +124,9 @@ pub enum NodeGraphMessage {
|
|||
},
|
||||
SendClickTargets,
|
||||
EndSendClickTargets,
|
||||
UnloadWires,
|
||||
SendWires,
|
||||
UpdateVisibleNodes,
|
||||
SendGraph,
|
||||
SetGridAlignedEdges,
|
||||
SetInputValue {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeWire, WirePath};
|
||||
use super::utility_types::{BoxSelection, ContextMenuInformation, DragStart, FrontendGraphInput, FrontendGraphOutput, FrontendNode};
|
||||
use super::{document_node_definitions, node_properties};
|
||||
use crate::consts::GRID_SIZE;
|
||||
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
||||
|
@ -13,6 +13,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{
|
|||
self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource,
|
||||
};
|
||||
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry};
|
||||
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire};
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode;
|
||||
|
@ -67,6 +68,7 @@ pub struct NodeGraphMessageHandler {
|
|||
select_if_not_dragged: Option<NodeId>,
|
||||
/// The start of the dragged line (cannot be moved), stored in node graph coordinates
|
||||
pub wire_in_progress_from_connector: Option<DVec2>,
|
||||
wire_in_progress_type: FrontendGraphDataType,
|
||||
/// The end point of the dragged line (cannot be moved), stored in node graph coordinates
|
||||
pub wire_in_progress_to_connector: Option<DVec2>,
|
||||
/// State for the context menu popups.
|
||||
|
@ -77,12 +79,16 @@ pub struct NodeGraphMessageHandler {
|
|||
auto_panning: AutoPanning,
|
||||
/// The node to preview on mouse up if alt-clicked
|
||||
preview_on_mouse_up: Option<NodeId>,
|
||||
// The index of the import that is being moved
|
||||
/// The index of the import that is being moved
|
||||
reordering_import: Option<usize>,
|
||||
// The index of the export that is being moved
|
||||
/// The index of the export that is being moved
|
||||
reordering_export: Option<usize>,
|
||||
// The end index of the moved port
|
||||
/// The end index of the moved port
|
||||
end_index: Option<usize>,
|
||||
/// Used to keep track of what nodes are sent to the front end so that only visible ones are sent to the frontend
|
||||
frontend_nodes: Vec<NodeId>,
|
||||
/// Used to keep track of what wires are sent to the front end so the old ones can be removed
|
||||
frontend_wires: HashSet<(NodeId, usize)>,
|
||||
}
|
||||
|
||||
/// NodeGraphMessageHandler always modifies the network which the selected nodes are in. No GraphOperationMessages should be added here, since those messages will always affect the document network.
|
||||
|
@ -175,7 +181,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
NodeGraphMessage::CreateNodeFromContextMenu { node_id, node_type, xy } => {
|
||||
NodeGraphMessage::CreateNodeFromContextMenu {
|
||||
node_id,
|
||||
node_type,
|
||||
xy,
|
||||
add_transaction,
|
||||
} => {
|
||||
let (x, y) = if let Some((x, y)) = xy {
|
||||
(x, y)
|
||||
} else if let Some(node_graph_ptz) = network_interface.node_graph_ptz(breadcrumb_network_path) {
|
||||
|
@ -197,7 +208,10 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
let node_template = document_node_type.default_node_template();
|
||||
self.context_menu = None;
|
||||
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
if add_transaction {
|
||||
responses.add(DocumentMessage::AddTransaction);
|
||||
}
|
||||
|
||||
responses.add(NodeGraphMessage::InsertNode {
|
||||
node_id,
|
||||
node_template: node_template.clone(),
|
||||
|
@ -220,13 +234,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
};
|
||||
|
||||
// Ensure connection is to correct input of new node. If it does not have an input then do not connect
|
||||
if let Some((input_index, _)) = node_template
|
||||
.document_node
|
||||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, input)| input.is_exposed_to_frontend(selection_network_path.is_empty()))
|
||||
{
|
||||
if let Some((input_index, _)) = node_template.document_node.inputs.iter().enumerate().find(|(_, input)| input.is_exposed()) {
|
||||
responses.add(NodeGraphMessage::CreateWire {
|
||||
output_connector: *output_connector,
|
||||
input_connector: InputConnector::node(node_id, input_index),
|
||||
|
@ -236,6 +244,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
}
|
||||
|
||||
self.wire_in_progress_from_connector = None;
|
||||
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||
self.wire_in_progress_to_connector = None;
|
||||
}
|
||||
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
|
||||
|
@ -367,9 +376,14 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
responses.add(DocumentMessage::CommitTransaction);
|
||||
|
||||
// Update the graph UI and re-render
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
if graph_view_overlay_open {
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
} else {
|
||||
responses.add(DocumentMessage::GraphViewOverlay { open: true });
|
||||
responses.add(NavigationMessage::FitViewportToSelection);
|
||||
responses.add(DocumentMessage::ZoomCanvasTo100Percent);
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::InsertNode { node_id, node_template } => {
|
||||
network_interface.insert_node(node_id, node_template, selection_network_path);
|
||||
|
@ -629,6 +643,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
// Abort dragging a wire
|
||||
if self.wire_in_progress_from_connector.is_some() {
|
||||
self.wire_in_progress_from_connector = None;
|
||||
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||
self.wire_in_progress_to_connector = None;
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
responses.add(FrontendMessage::UpdateWirePathInProgress { wire_path: None });
|
||||
|
@ -707,6 +722,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
if self.context_menu.is_some() {
|
||||
self.context_menu = None;
|
||||
self.wire_in_progress_from_connector = None;
|
||||
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||
self.wire_in_progress_to_connector = None;
|
||||
responses.add(FrontendMessage::UpdateContextMenuInformation {
|
||||
context_menu_information: self.context_menu.clone(),
|
||||
|
@ -740,6 +756,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
};
|
||||
let Some(output_connector) = output_connector else { return };
|
||||
self.wire_in_progress_from_connector = network_interface.output_position(&output_connector, selection_network_path);
|
||||
self.wire_in_progress_type = FrontendGraphDataType::from_type(&network_interface.input_type(clicked_input, breadcrumb_network_path).0);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -749,6 +766,15 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
self.initial_disconnecting = false;
|
||||
|
||||
self.wire_in_progress_from_connector = network_interface.output_position(&clicked_output, selection_network_path);
|
||||
if let Some((output_type, source)) = clicked_output
|
||||
.node_id()
|
||||
.map(|node_id| network_interface.output_type(&node_id, clicked_output.index(), breadcrumb_network_path))
|
||||
{
|
||||
self.wire_in_progress_type = FrontendGraphDataType::displayed_type(&output_type, &source);
|
||||
} else {
|
||||
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||
}
|
||||
|
||||
self.update_node_graph_hints(responses);
|
||||
return;
|
||||
}
|
||||
|
@ -895,9 +921,18 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
false
|
||||
}
|
||||
});
|
||||
let vector_wire = build_vector_wire(
|
||||
wire_in_progress_from_connector,
|
||||
wire_in_progress_to_connector,
|
||||
from_connector_is_layer,
|
||||
to_connector_is_layer,
|
||||
GraphWireStyle::Direct,
|
||||
);
|
||||
let mut path_string = String::new();
|
||||
let _ = vector_wire.subpath_to_svg(&mut path_string, DAffine2::IDENTITY);
|
||||
let wire_path = WirePath {
|
||||
path_string: Self::build_wire_path_string(wire_in_progress_from_connector, wire_in_progress_to_connector, from_connector_is_layer, to_connector_is_layer),
|
||||
data_type: FrontendGraphDataType::General,
|
||||
path_string,
|
||||
data_type: self.wire_in_progress_type,
|
||||
thick: false,
|
||||
dashed: false,
|
||||
};
|
||||
|
@ -941,7 +976,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
self.update_node_graph_hints(responses);
|
||||
} else if self.reordering_import.is_some() {
|
||||
let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {
|
||||
log::error!("Could not get modify import export in PointerUp");
|
||||
log::error!("Could not get modify import export in PointerMove");
|
||||
return;
|
||||
};
|
||||
// Find the first import that is below the mouse position
|
||||
|
@ -961,7 +996,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
responses.add(FrontendMessage::UpdateImportReorderIndex { index: self.end_index });
|
||||
} else if self.reordering_export.is_some() {
|
||||
let Some(modify_import_export) = network_interface.modify_import_export(selection_network_path) else {
|
||||
log::error!("Could not get modify import export in PointerUp");
|
||||
log::error!("Could not get modify import export in PointerMove");
|
||||
return;
|
||||
};
|
||||
// Find the first export that is below the mouse position
|
||||
|
@ -1043,15 +1078,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
// Get the compatible type from the output connector
|
||||
let compatible_type = output_connector.and_then(|output_connector| {
|
||||
output_connector.node_id().and_then(|node_id| {
|
||||
let output_index = output_connector.index();
|
||||
// Get the output types from the network interface
|
||||
let output_types = network_interface.output_types(&node_id, selection_network_path);
|
||||
let (output_type, type_source) = network_interface.output_type(&node_id, output_connector.index(), selection_network_path);
|
||||
|
||||
// Extract the type if available
|
||||
output_types.get(output_index).and_then(|type_option| type_option.as_ref()).map(|(output_type, _)| {
|
||||
// Create a search term based on the type
|
||||
format!("type:{}", output_type.clone().nested_type())
|
||||
})
|
||||
match type_source {
|
||||
TypeSource::RandomProtonodeImplementation | TypeSource::Error(_) => None,
|
||||
_ => Some(format!("type:{}", output_type.nested_type())),
|
||||
}
|
||||
})
|
||||
});
|
||||
let appear_right_of_mouse = if ipp.mouse.position.x > ipp.viewport_bounds.size().x - 173. { -173. } else { 0. };
|
||||
|
@ -1117,107 +1150,56 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
let has_primary_output_connection = network_interface
|
||||
.outward_wires(selection_network_path)
|
||||
.is_some_and(|outward_wires| outward_wires.get(&OutputConnector::node(selected_node_id, 0)).is_some_and(|outward_wires| !outward_wires.is_empty()));
|
||||
let Some(network) = network_interface.nested_network(selection_network_path) else {
|
||||
return;
|
||||
};
|
||||
if let Some(selected_node) = network.nodes.get(&selected_node_id) {
|
||||
// Check if any downstream node has any input that feeds into the primary export of the selected node
|
||||
let primary_input_is_value = selected_node.inputs.first().is_some_and(|first_input| first_input.as_value().is_some());
|
||||
// Check that neither the primary input or output of the selected node are already connected.
|
||||
if !has_primary_output_connection && primary_input_is_value {
|
||||
if !has_primary_output_connection {
|
||||
let Some(network) = network_interface.nested_network(selection_network_path) else {
|
||||
return;
|
||||
};
|
||||
let Some(selected_node) = network.nodes.get(&selected_node_id) else {
|
||||
return;
|
||||
};
|
||||
// Check that the first visible input is disconnected
|
||||
let selected_node_input_connect_index = selected_node
|
||||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|input| input.1.is_exposed())
|
||||
.filter(|input| input.1.as_value().is_some())
|
||||
.map(|input| input.0);
|
||||
if let Some(selected_node_input_connect_index) = selected_node_input_connect_index {
|
||||
let Some(bounding_box) = network_interface.node_bounding_box(&selected_node_id, selection_network_path) else {
|
||||
log::error!("Could not get bounding box for node: {selected_node_id}");
|
||||
return;
|
||||
};
|
||||
// TODO: Cache all wire locations if this is a performance issue
|
||||
let overlapping_wires = Self::collect_wires(network_interface, selection_network_path)
|
||||
.into_iter()
|
||||
.filter(|frontend_wire| {
|
||||
// Prevent inserting on a link that is connected upstream to the selected node
|
||||
if network_interface
|
||||
.upstream_flow_back_from_nodes(vec![selected_node_id], selection_network_path, network_interface::FlowType::UpstreamFlow)
|
||||
.any(|upstream_id| {
|
||||
frontend_wire.wire_end.node_id().is_some_and(|wire_end_id| wire_end_id == upstream_id)
|
||||
|| frontend_wire.wire_start.node_id().is_some_and(|wire_start_id| wire_start_id == upstream_id)
|
||||
}) {
|
||||
return false;
|
||||
}
|
||||
let mut wires_to_check = network_interface.node_graph_input_connectors(selection_network_path).into_iter().collect::<HashSet<_>>();
|
||||
// Prevent inserting on a link that is connected upstream to the selected node
|
||||
for upstream_node in network_interface.upstream_flow_back_from_nodes(vec![selected_node_id], selection_network_path, network_interface::FlowType::UpstreamFlow) {
|
||||
for input_index in 0..network_interface.number_of_inputs(&upstream_node, selection_network_path) {
|
||||
wires_to_check.remove(&InputConnector::node(upstream_node, input_index));
|
||||
}
|
||||
}
|
||||
|
||||
let overlapping_wires = wires_to_check
|
||||
.into_iter()
|
||||
.filter_map(|input| {
|
||||
// Prevent inserting a layer into a chain
|
||||
if network_interface.is_layer(&selected_node_id, selection_network_path)
|
||||
&& frontend_wire
|
||||
.wire_start
|
||||
.node_id()
|
||||
.is_some_and(|wire_start_id| network_interface.is_chain(&wire_start_id, selection_network_path))
|
||||
&& input.node_id().is_some_and(|input_node_id| network_interface.is_chain(&input_node_id, selection_network_path))
|
||||
{
|
||||
return false;
|
||||
return None;
|
||||
}
|
||||
|
||||
let Some(input_position) = network_interface.input_position(&frontend_wire.wire_end, selection_network_path) else {
|
||||
log::error!("Could not get input port position for {:?}", frontend_wire.wire_end);
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(output_position) = network_interface.output_position(&frontend_wire.wire_start, selection_network_path) else {
|
||||
log::error!("Could not get output port position for {:?}", frontend_wire.wire_start);
|
||||
return false;
|
||||
};
|
||||
|
||||
let start_node_is_layer = frontend_wire
|
||||
.wire_end
|
||||
.node_id()
|
||||
.is_some_and(|wire_start_id| network_interface.is_layer(&wire_start_id, selection_network_path));
|
||||
let end_node_is_layer = frontend_wire
|
||||
.wire_end
|
||||
.node_id()
|
||||
.is_some_and(|wire_end_id| network_interface.is_layer(&wire_end_id, selection_network_path));
|
||||
|
||||
let locations = Self::build_wire_path_locations(output_position, input_position, start_node_is_layer, end_node_is_layer);
|
||||
let bezier = bezier_rs::Bezier::from_cubic_dvec2(
|
||||
(locations[0].x, locations[0].y).into(),
|
||||
(locations[1].x, locations[1].y).into(),
|
||||
(locations[2].x, locations[2].y).into(),
|
||||
(locations[3].x, locations[3].y).into(),
|
||||
);
|
||||
|
||||
!bezier.rectangle_intersections(bounding_box[0], bounding_box[1]).is_empty() || bezier.is_contained_within(bounding_box[0], bounding_box[1])
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.filter_map(|mut wire| {
|
||||
if let Some(end_node_id) = wire.wire_end.node_id() {
|
||||
let Some(actual_index_from_exposed) = (0..network_interface.number_of_inputs(&end_node_id, selection_network_path))
|
||||
.filter(|&input_index| {
|
||||
network_interface
|
||||
.input_from_connector(&InputConnector::Node { node_id: end_node_id, input_index }, selection_network_path)
|
||||
.is_some_and(|input| input.is_exposed_to_frontend(selection_network_path.is_empty()))
|
||||
})
|
||||
.nth(wire.wire_end.input_index())
|
||||
else {
|
||||
log::error!("Could not get exposed input index for {:?}", wire.wire_end);
|
||||
return None;
|
||||
};
|
||||
wire.wire_end = InputConnector::Node {
|
||||
node_id: end_node_id,
|
||||
input_index: actual_index_from_exposed,
|
||||
};
|
||||
}
|
||||
Some(wire)
|
||||
let (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?;
|
||||
wire.rectangle_intersections_exist(bounding_box[0], bounding_box[1]).then_some((input, is_stack))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let is_stack_wire = |wire: &FrontendNodeWire| match (wire.wire_start.node_id(), wire.wire_end.node_id(), wire.wire_end.input_index()) {
|
||||
(Some(start_id), Some(end_id), input_index) => {
|
||||
input_index == 0 && network_interface.is_layer(&start_id, selection_network_path) && network_interface.is_layer(&end_id, selection_network_path)
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
// Prioritize vertical thick lines and cancel if there are multiple potential wires
|
||||
let mut node_wires = Vec::new();
|
||||
let mut stack_wires = Vec::new();
|
||||
for wire in overlapping_wires {
|
||||
if is_stack_wire(&wire) { stack_wires.push(wire) } else { node_wires.push(wire) }
|
||||
for (overlapping_wire_input, is_stack) in overlapping_wires {
|
||||
if is_stack {
|
||||
stack_wires.push(overlapping_wire_input)
|
||||
} else {
|
||||
node_wires.push(overlapping_wire_input)
|
||||
}
|
||||
}
|
||||
|
||||
let overlapping_wire = if network_interface.is_layer(&selected_node_id, selection_network_path) {
|
||||
|
@ -1234,29 +1216,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
None
|
||||
};
|
||||
if let Some(overlapping_wire) = overlapping_wire {
|
||||
let Some(network) = network_interface.nested_network(selection_network_path) else {
|
||||
return;
|
||||
};
|
||||
// Ensure connection is to first visible input of selected node. If it does not have an input then do not connect
|
||||
if let Some((selected_node_input_index, _)) = network
|
||||
.nodes
|
||||
.get(&selected_node_id)
|
||||
.unwrap()
|
||||
.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, input)| input.is_exposed_to_frontend(selection_network_path.is_empty()))
|
||||
{
|
||||
responses.add(NodeGraphMessage::InsertNodeBetween {
|
||||
node_id: selected_node_id,
|
||||
input_connector: overlapping_wire.wire_end,
|
||||
insert_node_input_index: selected_node_input_index,
|
||||
});
|
||||
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
responses.add(NodeGraphMessage::InsertNodeBetween {
|
||||
node_id: selected_node_id,
|
||||
input_connector: *overlapping_wire,
|
||||
insert_node_input_index: selected_node_input_connect_index,
|
||||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1283,6 +1249,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
self.begin_dragging = false;
|
||||
self.box_selection_start = None;
|
||||
self.wire_in_progress_from_connector = None;
|
||||
self.wire_in_progress_type = FrontendGraphDataType::General;
|
||||
self.wire_in_progress_to_connector = None;
|
||||
self.reordering_export = None;
|
||||
self.reordering_import = None;
|
||||
|
@ -1357,23 +1324,52 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
click_targets: Some(network_interface.collect_frontend_click_targets(breadcrumb_network_path)),
|
||||
}),
|
||||
NodeGraphMessage::EndSendClickTargets => responses.add(FrontendMessage::UpdateClickTargets { click_targets: None }),
|
||||
NodeGraphMessage::UnloadWires => {
|
||||
for input in network_interface.node_graph_input_connectors(breadcrumb_network_path) {
|
||||
network_interface.unload_wire(&input, breadcrumb_network_path);
|
||||
}
|
||||
|
||||
responses.add(FrontendMessage::ClearAllNodeGraphWires);
|
||||
}
|
||||
NodeGraphMessage::SendWires => {
|
||||
let wires = self.collect_wires(network_interface, preferences.graph_wire_style, breadcrumb_network_path);
|
||||
responses.add(FrontendMessage::UpdateNodeGraphWires { wires });
|
||||
}
|
||||
NodeGraphMessage::UpdateVisibleNodes => {
|
||||
let Some(network_metadata) = network_interface.network_metadata(breadcrumb_network_path) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let viewport_bbox = ipp.document_bounds();
|
||||
let document_bbox: [DVec2; 2] = viewport_bbox.map(|p| network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport.inverse().transform_point2(p));
|
||||
|
||||
let mut nodes = Vec::new();
|
||||
for node_id in &self.frontend_nodes {
|
||||
let Some(node_bbox) = network_interface.node_bounding_box(node_id, breadcrumb_network_path) else {
|
||||
log::error!("Could not get bbox for node: {:?}", node_id);
|
||||
continue;
|
||||
};
|
||||
|
||||
if node_bbox[1].x >= document_bbox[0].x && node_bbox[0].x <= document_bbox[1].x && node_bbox[1].y >= document_bbox[0].y && node_bbox[0].y <= document_bbox[1].y {
|
||||
nodes.push(*node_id);
|
||||
}
|
||||
}
|
||||
|
||||
responses.add(FrontendMessage::UpdateVisibleNodes { nodes });
|
||||
}
|
||||
NodeGraphMessage::SendGraph => {
|
||||
responses.add(NodeGraphMessage::UpdateLayerPanel);
|
||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
if breadcrumb_network_path == selection_network_path && graph_view_overlay_open {
|
||||
// TODO: Implement culling of nodes and wires whose bounding boxes are outside of the viewport
|
||||
let wires = Self::collect_wires(network_interface, breadcrumb_network_path);
|
||||
let nodes = self.collect_nodes(network_interface, breadcrumb_network_path);
|
||||
self.frontend_nodes = nodes.iter().map(|node| node.id).collect();
|
||||
responses.add(FrontendMessage::UpdateNodeGraphNodes { nodes });
|
||||
responses.add(NodeGraphMessage::UpdateVisibleNodes);
|
||||
|
||||
let (layer_widths, chain_widths, has_left_input_wire) = network_interface.collect_layer_widths(breadcrumb_network_path);
|
||||
let wires_direct_not_grid_aligned = preferences.graph_wire_style.is_direct();
|
||||
|
||||
responses.add(NodeGraphMessage::UpdateImportsExports);
|
||||
responses.add(FrontendMessage::UpdateNodeGraph {
|
||||
nodes,
|
||||
wires,
|
||||
wires_direct_not_grid_aligned,
|
||||
});
|
||||
responses.add(FrontendMessage::UpdateLayerWidths {
|
||||
layer_widths,
|
||||
chain_widths,
|
||||
|
@ -1455,6 +1451,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
Ordering::Equal => {}
|
||||
}
|
||||
}
|
||||
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
}
|
||||
NodeGraphMessage::ToggleSelectedAsLayersOrNodes => {
|
||||
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
|
||||
|
@ -1474,6 +1472,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
}
|
||||
NodeGraphMessage::ShiftNodePosition { node_id, x, y } => {
|
||||
network_interface.shift_absolute_node_position(&node_id, IVec2::new(x, y), selection_network_path);
|
||||
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
}
|
||||
NodeGraphMessage::SetToNodeOrLayer { node_id, is_layer } => {
|
||||
if is_layer && !network_interface.is_eligible_to_be_layer(&node_id, selection_network_path) {
|
||||
|
@ -1487,6 +1487,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
}
|
||||
NodeGraphMessage::SetDisplayName {
|
||||
node_id,
|
||||
|
@ -1623,7 +1624,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
// }
|
||||
|
||||
let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {
|
||||
log::error!("Could not get network metadata in PointerMove");
|
||||
log::error!("Could not get network metadata in UpdateBoxSelection");
|
||||
return;
|
||||
};
|
||||
|
||||
|
@ -1689,7 +1690,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||
)
|
||||
.into_iter()
|
||||
.next();
|
||||
|
||||
responses.add(NodeGraphMessage::UpdateVisibleNodes);
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
responses.add(FrontendMessage::UpdateImportsExports {
|
||||
imports,
|
||||
exports,
|
||||
|
@ -1835,6 +1837,7 @@ impl NodeGraphMessageHandler {
|
|||
node_id: Some(node_id),
|
||||
node_type: node_type.clone(),
|
||||
xy: None,
|
||||
add_transaction: true,
|
||||
}
|
||||
.into(),
|
||||
NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(),
|
||||
|
@ -2151,69 +2154,39 @@ impl NodeGraphMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
fn collect_wires(network_interface: &NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec<FrontendNodeWire> {
|
||||
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
||||
log::error!("Could not get network when collecting wires");
|
||||
return Vec::new();
|
||||
};
|
||||
let mut wires = network
|
||||
.nodes
|
||||
fn collect_wires(&mut self, network_interface: &mut NodeNetworkInterface, graph_wire_style: GraphWireStyle, breadcrumb_network_path: &[NodeId]) -> Vec<WirePathUpdate> {
|
||||
let mut added_wires = network_interface
|
||||
.node_graph_input_connectors(breadcrumb_network_path)
|
||||
.iter()
|
||||
.flat_map(|(wire_end, node)| node.inputs.iter().filter(|input| input.is_exposed()).enumerate().map(move |(index, input)| (input, wire_end, index)))
|
||||
.filter_map(|(input, &wire_end, wire_end_input_index)| {
|
||||
match *input {
|
||||
NodeInput::Node {
|
||||
node_id: wire_start,
|
||||
output_index: wire_start_output_index,
|
||||
// TODO: add ui for lambdas
|
||||
lambda: _,
|
||||
} => Some(FrontendNodeWire {
|
||||
wire_start: OutputConnector::node(wire_start, wire_start_output_index),
|
||||
wire_end: InputConnector::node(wire_end, wire_end_input_index),
|
||||
dashed: false,
|
||||
}),
|
||||
NodeInput::Network { import_index, .. } => Some(FrontendNodeWire {
|
||||
wire_start: OutputConnector::Import(import_index),
|
||||
wire_end: InputConnector::node(wire_end, wire_end_input_index),
|
||||
dashed: false,
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.filter_map(|connector| network_interface.newly_loaded_input_wire(connector, graph_wire_style, breadcrumb_network_path))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Connect primary export to root node, since previewing a node will change the primary export
|
||||
if let Some(root_node) = network_interface.root_node(breadcrumb_network_path) {
|
||||
wires.push(FrontendNodeWire {
|
||||
wire_start: OutputConnector::node(root_node.node_id, root_node.output_index),
|
||||
wire_end: InputConnector::Export(0),
|
||||
dashed: false,
|
||||
});
|
||||
let changed_wire_inputs = added_wires.iter().map(|update| (update.id, update.input_index)).collect::<Vec<_>>();
|
||||
self.frontend_wires.extend(changed_wire_inputs);
|
||||
|
||||
let mut orphaned_wire_inputs = self.frontend_wires.clone();
|
||||
self.frontend_wires = network_interface
|
||||
.node_graph_wire_inputs(breadcrumb_network_path)
|
||||
.iter()
|
||||
.filter_map(|visible_wire_input| orphaned_wire_inputs.take(visible_wire_input))
|
||||
.collect::<HashSet<_>>();
|
||||
added_wires.extend(orphaned_wire_inputs.into_iter().map(|(id, input_index)| WirePathUpdate {
|
||||
id,
|
||||
input_index,
|
||||
wire_path_update: None,
|
||||
}));
|
||||
|
||||
if let Some(wire_to_root) = network_interface.wire_to_root(graph_wire_style, breadcrumb_network_path) {
|
||||
added_wires.push(wire_to_root);
|
||||
} else {
|
||||
added_wires.push(WirePathUpdate {
|
||||
id: NodeId(u64::MAX),
|
||||
input_index: usize::MAX,
|
||||
wire_path_update: None,
|
||||
})
|
||||
}
|
||||
|
||||
// Connect rest of exports to their actual export field since they are not affected by previewing. Only connect the primary export if it is dashed
|
||||
for (i, export) in network.exports.iter().enumerate() {
|
||||
let dashed = matches!(network_interface.previewing(breadcrumb_network_path), Previewing::Yes { .. }) && i == 0;
|
||||
if dashed || i != 0 {
|
||||
if let NodeInput::Node { node_id, output_index, .. } = export {
|
||||
wires.push(FrontendNodeWire {
|
||||
wire_start: OutputConnector::Node {
|
||||
node_id: *node_id,
|
||||
output_index: *output_index,
|
||||
},
|
||||
wire_end: InputConnector::Export(i),
|
||||
dashed,
|
||||
});
|
||||
} else if let NodeInput::Network { import_index, .. } = *export {
|
||||
wires.push(FrontendNodeWire {
|
||||
wire_start: OutputConnector::Import(import_index),
|
||||
wire_end: InputConnector::Export(i),
|
||||
dashed,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
wires
|
||||
added_wires
|
||||
}
|
||||
|
||||
fn collect_nodes(&self, network_interface: &mut NodeNetworkInterface, breadcrumb_network_path: &[NodeId]) -> Vec<FrontendNode> {
|
||||
|
@ -2237,6 +2210,7 @@ impl NodeGraphMessageHandler {
|
|||
log::error!("Could not get position for node {node_id}");
|
||||
}
|
||||
}
|
||||
|
||||
let mut frontend_inputs_lookup = frontend_inputs_lookup(breadcrumb_network_path, network_interface);
|
||||
let Some(network) = network_interface.nested_network(breadcrumb_network_path) else {
|
||||
log::error!("Could not get nested network when collecting nodes");
|
||||
|
@ -2252,13 +2226,14 @@ impl NodeGraphMessageHandler {
|
|||
let node_id_path = [breadcrumb_network_path, (&[node_id])].concat();
|
||||
|
||||
let inputs = frontend_inputs_lookup.remove(&node_id).unwrap_or_default();
|
||||
|
||||
let mut inputs = inputs.into_iter().map(|input| {
|
||||
input.map(|input| FrontendGraphInput {
|
||||
data_type: FrontendGraphDataType::displayed_type(&input.ty, &input.type_source),
|
||||
resolved_type: Some(format!("{:?}", &input.ty)),
|
||||
resolved_type: format!("{:?}", &input.ty),
|
||||
valid_types: input.valid_types.iter().map(|ty| ty.to_string()).collect(),
|
||||
name: input.input_name.unwrap_or_else(|| input.ty.nested_type().to_string()),
|
||||
description: input.input_description.unwrap_or_default(),
|
||||
name: input.input_name,
|
||||
description: input.input_description,
|
||||
connected_to: input.output_connector,
|
||||
})
|
||||
});
|
||||
|
@ -2266,20 +2241,16 @@ impl NodeGraphMessageHandler {
|
|||
let primary_input = inputs.next().flatten();
|
||||
let exposed_inputs = inputs.flatten().collect();
|
||||
|
||||
let output_types = network_interface.output_types(&node_id, breadcrumb_network_path);
|
||||
let primary_output_type = output_types.first().cloned().flatten();
|
||||
let frontend_data_type = if let Some((output_type, type_source)) = &primary_output_type {
|
||||
FrontendGraphDataType::displayed_type(output_type, type_source)
|
||||
} else {
|
||||
FrontendGraphDataType::General
|
||||
};
|
||||
let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
|
||||
let frontend_data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
|
||||
|
||||
let connected_to = outward_wires.get(&OutputConnector::node(node_id, 0)).cloned().unwrap_or_default();
|
||||
let primary_output = if network_interface.has_primary_output(&node_id, breadcrumb_network_path) && !output_types.is_empty() {
|
||||
let primary_output = if network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
|
||||
Some(FrontendGraphOutput {
|
||||
data_type: frontend_data_type,
|
||||
name: "Output 1".to_string(),
|
||||
description: String::new(),
|
||||
resolved_type: primary_output_type.map(|(input, _)| format!("{input:?}")),
|
||||
resolved_type: format!("{:?}", output_type),
|
||||
connected_to,
|
||||
})
|
||||
} else {
|
||||
|
@ -2287,15 +2258,13 @@ impl NodeGraphMessageHandler {
|
|||
};
|
||||
|
||||
let mut exposed_outputs = Vec::new();
|
||||
for (index, exposed_output) in output_types.iter().enumerate() {
|
||||
if index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
|
||||
for output_index in 0..network_interface.number_of_outputs(&node_id, breadcrumb_network_path) {
|
||||
if output_index == 0 && network_interface.has_primary_output(&node_id, breadcrumb_network_path) {
|
||||
continue;
|
||||
}
|
||||
let frontend_data_type = if let Some((output_type, type_source)) = &exposed_output {
|
||||
FrontendGraphDataType::displayed_type(output_type, type_source)
|
||||
} else {
|
||||
FrontendGraphDataType::General
|
||||
};
|
||||
let (output_type, type_source) = network_interface.output_type(&node_id, 0, breadcrumb_network_path);
|
||||
let data_type = FrontendGraphDataType::displayed_type(&output_type, &type_source);
|
||||
|
||||
let Some(node_metadata) = network_metadata.persistent_metadata.node_metadata.get(&node_id) else {
|
||||
log::error!("Could not get node_metadata when getting output for {node_id}");
|
||||
continue;
|
||||
|
@ -2303,17 +2272,17 @@ impl NodeGraphMessageHandler {
|
|||
let output_name = node_metadata
|
||||
.persistent_metadata
|
||||
.output_names
|
||||
.get(index)
|
||||
.map(|output_name| output_name.to_string())
|
||||
.get(output_index)
|
||||
.cloned()
|
||||
.filter(|output_name| !output_name.is_empty())
|
||||
.unwrap_or_else(|| exposed_output.clone().map(|(output_type, _)| output_type.nested_type().to_string()).unwrap_or_default());
|
||||
.unwrap_or_else(|| output_type.nested_type().to_string());
|
||||
|
||||
let connected_to = outward_wires.get(&OutputConnector::node(node_id, index)).cloned().unwrap_or_default();
|
||||
let connected_to = outward_wires.get(&OutputConnector::node(node_id, output_index)).cloned().unwrap_or_default();
|
||||
exposed_outputs.push(FrontendGraphOutput {
|
||||
data_type: frontend_data_type,
|
||||
data_type,
|
||||
name: output_name,
|
||||
description: String::new(),
|
||||
resolved_type: exposed_output.clone().map(|(input, _)| format!("{input:?}")),
|
||||
resolved_type: format!("{:?}", output_type),
|
||||
connected_to,
|
||||
});
|
||||
}
|
||||
|
@ -2416,9 +2385,9 @@ impl NodeGraphMessageHandler {
|
|||
network_interface.upstream_flow_back_from_nodes(vec![node_id], &[], network_interface::FlowType::HorizontalFlow).last().is_some_and(|node_id|
|
||||
network_interface.document_node(&node_id, &[]).map_or_else(||{log::error!("Could not get node {node_id} in update_layer_panel"); false}, |node| {
|
||||
if network_interface.is_layer(&node_id, &[]) {
|
||||
node.inputs.iter().filter(|input| input.is_exposed_to_frontend(true)).nth(1).is_some_and(|input| input.as_value().is_some())
|
||||
node.inputs.iter().filter(|input| input.is_exposed()).nth(1).is_some_and(|input| input.as_value().is_some())
|
||||
} else {
|
||||
node.inputs.iter().filter(|input| input.is_exposed_to_frontend(true)).nth(0).is_some_and(|input| input.as_value().is_some())
|
||||
node.inputs.iter().filter(|input| input.is_exposed()).nth(0).is_some_and(|input| input.as_value().is_some())
|
||||
}
|
||||
}))
|
||||
);
|
||||
|
@ -2467,66 +2436,6 @@ impl NodeGraphMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
fn build_wire_path_string(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> String {
|
||||
let locations = Self::build_wire_path_locations(output_position, input_position, vertical_out, vertical_in);
|
||||
let smoothing = 0.5;
|
||||
let delta01 = DVec2::new((locations[1].x - locations[0].x) * smoothing, (locations[1].y - locations[0].y) * smoothing);
|
||||
let delta23 = DVec2::new((locations[3].x - locations[2].x) * smoothing, (locations[3].y - locations[2].y) * smoothing);
|
||||
format!(
|
||||
"M{},{} L{},{} C{},{} {},{} {},{} L{},{}",
|
||||
locations[0].x,
|
||||
locations[0].y,
|
||||
locations[1].x,
|
||||
locations[1].y,
|
||||
locations[1].x + delta01.x,
|
||||
locations[1].y + delta01.y,
|
||||
locations[2].x - delta23.x,
|
||||
locations[2].y - delta23.y,
|
||||
locations[2].x,
|
||||
locations[2].y,
|
||||
locations[3].x,
|
||||
locations[3].y
|
||||
)
|
||||
}
|
||||
|
||||
fn build_wire_path_locations(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> Vec<DVec2> {
|
||||
let horizontal_gap = (output_position.x - input_position.x).abs();
|
||||
let vertical_gap = (output_position.y - input_position.y).abs();
|
||||
// TODO: Finish this commented out code replacement for the code below it based on this diagram: <https://files.keavon.com/-/SuperbWideFoxterrier/capture.png>
|
||||
// // Straight: stacking lines which are always straight, or a straight horizontal wire between two aligned nodes
|
||||
// if ((verticalOut && vertical_in) || (!verticalOut && !vertical_in && vertical_gap === 0)) {
|
||||
// return [
|
||||
// { x: output_position.x, y: output_position.y },
|
||||
// { x: input_position.x, y: input_position.y },
|
||||
// ];
|
||||
// }
|
||||
|
||||
// // L-shape bend
|
||||
// if (verticalOut !== vertical_in) {
|
||||
// }
|
||||
|
||||
let curve_length = 24.;
|
||||
let curve_falloff_rate = curve_length * std::f64::consts::PI * 2.;
|
||||
|
||||
let horizontal_curve_amount = -(2_f64.powf((-10. * horizontal_gap) / curve_falloff_rate)) + 1.;
|
||||
let vertical_curve_amount = -(2_f64.powf((-10. * vertical_gap) / curve_falloff_rate)) + 1.;
|
||||
let horizontal_curve = horizontal_curve_amount * curve_length;
|
||||
let vertical_curve = vertical_curve_amount * curve_length;
|
||||
|
||||
vec![
|
||||
output_position,
|
||||
DVec2::new(
|
||||
if vertical_out { output_position.x } else { output_position.x + horizontal_curve },
|
||||
if vertical_out { output_position.y - vertical_curve } else { output_position.y },
|
||||
),
|
||||
DVec2::new(
|
||||
if vertical_in { input_position.x } else { input_position.x - horizontal_curve },
|
||||
if vertical_in { input_position.y + vertical_curve } else { input_position.y },
|
||||
),
|
||||
DVec2::new(input_position.x, input_position.y),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn update_node_graph_hints(&self, responses: &mut VecDeque<Message>) {
|
||||
// A wire is in progress and its start and end connectors are set
|
||||
let wiring = self.wire_in_progress_from_connector.is_some();
|
||||
|
@ -2570,8 +2479,8 @@ impl NodeGraphMessageHandler {
|
|||
|
||||
#[derive(Default)]
|
||||
struct InputLookup {
|
||||
input_name: Option<String>,
|
||||
input_description: Option<String>,
|
||||
input_name: String,
|
||||
input_description: String,
|
||||
ty: Type,
|
||||
type_source: TypeSource,
|
||||
valid_types: Vec<Type>,
|
||||
|
@ -2586,34 +2495,31 @@ fn frontend_inputs_lookup(breadcrumb_network_path: &[NodeId], network_interface:
|
|||
return Default::default();
|
||||
};
|
||||
let mut frontend_inputs_lookup = HashMap::new();
|
||||
for (&node_id, node) in network.nodes.iter() {
|
||||
let mut inputs = Vec::with_capacity(node.inputs.len());
|
||||
for (index, input) in node.inputs.iter().enumerate() {
|
||||
let is_exposed = input.is_exposed_to_frontend(breadcrumb_network_path.is_empty());
|
||||
|
||||
// Skip not exposed inputs (they still get an entry to help with finding the primary input)
|
||||
if !is_exposed {
|
||||
inputs.push(None);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (node_id, index, output_connector, is_exposed) in network
|
||||
.nodes
|
||||
.iter()
|
||||
.flat_map(|(node_id, node)| {
|
||||
node.inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, input)| (*node_id, index, OutputConnector::from_input(input), input.is_exposed()))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
// Skip not exposed inputs (they still get an entry to help with finding the primary input)
|
||||
let lookup = if !is_exposed {
|
||||
None
|
||||
} else {
|
||||
// Get the name from the metadata here (since it also requires a reference to the `network_interface`)
|
||||
let input_name = network_interface
|
||||
.input_name(node_id, index, breadcrumb_network_path)
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|name| name.to_string());
|
||||
let input_description = network_interface.input_description(node_id, index, breadcrumb_network_path).map(|description| description.to_string());
|
||||
// Get the output connector that feeds into this input (done here as well for simplicity)
|
||||
let connector = OutputConnector::from_input(input);
|
||||
|
||||
inputs.push(Some(InputLookup {
|
||||
let (input_name, input_description) = network_interface.displayed_input_name_and_description(&node_id, index, breadcrumb_network_path);
|
||||
Some(InputLookup {
|
||||
input_name,
|
||||
input_description,
|
||||
output_connector: connector,
|
||||
output_connector,
|
||||
..Default::default()
|
||||
}));
|
||||
}
|
||||
frontend_inputs_lookup.insert(node_id, inputs);
|
||||
})
|
||||
};
|
||||
frontend_inputs_lookup.entry(node_id).or_insert_with(Vec::new).push(lookup);
|
||||
}
|
||||
|
||||
for (&node_id, value) in frontend_inputs_lookup.iter_mut() {
|
||||
|
@ -2656,6 +2562,7 @@ impl Default for NodeGraphMessageHandler {
|
|||
select_if_not_dragged: None,
|
||||
wire_in_progress_from_connector: None,
|
||||
wire_in_progress_to_connector: None,
|
||||
wire_in_progress_type: FrontendGraphDataType::General,
|
||||
context_menu: None,
|
||||
deselect_on_pointer_up: None,
|
||||
auto_panning: Default::default(),
|
||||
|
@ -2663,6 +2570,8 @@ impl Default for NodeGraphMessageHandler {
|
|||
reordering_export: None,
|
||||
reordering_import: None,
|
||||
end_index: None,
|
||||
frontend_nodes: Vec::new(),
|
||||
frontend_wires: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,17 +60,12 @@ pub fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphData
|
|||
"Expose this parameter as a node input in the graph"
|
||||
})
|
||||
.on_update(move |_parameter| {
|
||||
Message::Batched(Box::new([
|
||||
NodeGraphMessage::ExposeInput {
|
||||
input_connector: InputConnector::node(node_id, index),
|
||||
set_to_exposed: !exposed,
|
||||
start_transaction: true,
|
||||
}
|
||||
.into(),
|
||||
DocumentMessage::GraphViewOverlay { open: true }.into(),
|
||||
NavigationMessage::FitViewportToSelection.into(),
|
||||
DocumentMessage::ZoomCanvasTo100Percent.into(),
|
||||
]))
|
||||
Message::Batched(Box::new([NodeGraphMessage::ExposeInput {
|
||||
input_connector: InputConnector::node(node_id, index),
|
||||
set_to_exposed: !exposed,
|
||||
start_transaction: true,
|
||||
}
|
||||
.into()]))
|
||||
})
|
||||
.widget_holder()
|
||||
}
|
||||
|
@ -85,28 +80,31 @@ pub fn add_blank_assist(widgets: &mut Vec<WidgetHolder>) {
|
|||
]);
|
||||
}
|
||||
|
||||
pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo, data_type: FrontendGraphDataType) -> Vec<WidgetHolder> {
|
||||
start_widgets_exposable(parameter_widgets_info, data_type, true)
|
||||
}
|
||||
|
||||
pub fn start_widgets_exposable(parameter_widgets_info: ParameterWidgetsInfo, data_type: FrontendGraphDataType, exposable: bool) -> Vec<WidgetHolder> {
|
||||
pub fn start_widgets(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo {
|
||||
document_node,
|
||||
node_id,
|
||||
index,
|
||||
name,
|
||||
description,
|
||||
input_type,
|
||||
blank_assist,
|
||||
exposeable,
|
||||
} = parameter_widgets_info;
|
||||
|
||||
let Some(document_node) = document_node else {
|
||||
log::warn!("A widget failed to be built because its document node is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
};
|
||||
let description = if description != "TODO" { description } else { "" };
|
||||
let description = if description != "TODO" { description } else { String::new() };
|
||||
let mut widgets = Vec::with_capacity(6);
|
||||
if exposable {
|
||||
widgets.push(expose_widget(node_id, index, data_type, input.is_exposed()));
|
||||
if exposeable {
|
||||
widgets.push(expose_widget(node_id, index, input_type, input.is_exposed()));
|
||||
}
|
||||
widgets.push(TextLabel::new(name).tooltip(description).widget_holder());
|
||||
if blank_assist {
|
||||
|
@ -126,18 +124,6 @@ pub(crate) fn property_from_type(
|
|||
step: Option<f64>,
|
||||
context: &mut NodePropertiesContext,
|
||||
) -> Result<Vec<LayoutGroup>, Vec<LayoutGroup>> {
|
||||
let Some(network) = context.network_interface.nested_network(context.selection_network_path) else {
|
||||
log::warn!("A widget failed to be built for node {node_id}, index {index} because the network could not be determined");
|
||||
return Err(vec![]);
|
||||
};
|
||||
let Some(document_node) = network.nodes.get(&node_id) else {
|
||||
log::warn!("A widget failed to be built for node {node_id}, index {index} because the document node does not exist");
|
||||
return Err(vec![]);
|
||||
};
|
||||
|
||||
let name = context.network_interface.input_name(node_id, index, context.selection_network_path).unwrap_or_default();
|
||||
let description = context.network_interface.input_description(node_id, index, context.selection_network_path).unwrap_or_default();
|
||||
|
||||
let (mut number_min, mut number_max, range) = number_options;
|
||||
let mut number_input = NumberInput::default();
|
||||
if let Some((range_start, range_end)) = range {
|
||||
|
@ -158,7 +144,7 @@ pub(crate) fn property_from_type(
|
|||
let min = |x: f64| number_min.unwrap_or(x);
|
||||
let max = |x: f64| number_max.unwrap_or(x);
|
||||
|
||||
let default_info = ParameterWidgetsInfo::new(document_node, node_id, index, name, description, true);
|
||||
let default_info = ParameterWidgetsInfo::new(node_id, index, true, context);
|
||||
|
||||
let mut extra_widgets = vec![];
|
||||
let widgets = match ty {
|
||||
|
@ -176,6 +162,7 @@ pub(crate) fn property_from_type(
|
|||
Some("SeedValue") => number_widget(default_info, number_input.int().min(min(0.))).into(),
|
||||
Some("Resolution") => coordinate_widget(default_info, "W", "H", unit.unwrap_or(" px"), Some(64.)),
|
||||
Some("PixelSize") => coordinate_widget(default_info, "X", "Y", unit.unwrap_or(" px"), None),
|
||||
Some("TextArea") => text_area_widget(default_info).into(),
|
||||
|
||||
// For all other types, use TypeId-based matching
|
||||
_ => {
|
||||
|
@ -247,7 +234,7 @@ pub(crate) fn property_from_type(
|
|||
// OTHER
|
||||
// =====
|
||||
_ => {
|
||||
let mut widgets = start_widgets(default_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(default_info);
|
||||
widgets.extend_from_slice(&[
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
TextLabel::new("-")
|
||||
|
@ -277,8 +264,9 @@ pub(crate) fn property_from_type(
|
|||
pub fn text_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -298,8 +286,9 @@ pub fn text_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHo
|
|||
pub fn text_area_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -319,8 +308,9 @@ pub fn text_area_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<Wid
|
|||
pub fn bool_widget(parameter_widgets_info: ParameterWidgetsInfo, checkbox_input: CheckboxInput) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -341,8 +331,9 @@ pub fn bool_widget(parameter_widgets_info: ParameterWidgetsInfo, checkbox_input:
|
|||
pub fn reference_point_widget(parameter_widgets_info: ParameterWidgetsInfo, disabled: bool) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -371,7 +362,7 @@ pub fn reference_point_widget(parameter_widgets_info: ParameterWidgetsInfo, disa
|
|||
pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widgets: &mut Vec<LayoutGroup>) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut location_widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut location_widgets = start_widgets(parameter_widgets_info);
|
||||
location_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
|
||||
let mut scale_widgets = vec![TextLabel::new("").widget_holder()];
|
||||
|
@ -382,10 +373,12 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
|
|||
add_blank_assist(&mut resolution_widgets);
|
||||
resolution_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
|
||||
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return Vec::new().into();
|
||||
};
|
||||
|
||||
if let Some(&TaggedValue::Footprint(footprint)) = input.as_non_exposed_value() {
|
||||
let top_left = footprint.transform.transform_point2(DVec2::ZERO);
|
||||
let bounds = footprint.scale();
|
||||
|
@ -517,8 +510,9 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
|
|||
pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
|
@ -629,7 +623,7 @@ pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str,
|
|||
pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text_input: TextInput) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let from_string = |string: &str| {
|
||||
string
|
||||
|
@ -641,6 +635,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text
|
|||
.map(TaggedValue::VecF64)
|
||||
};
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -660,7 +655,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text
|
|||
pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let from_string = |string: &str| {
|
||||
string
|
||||
|
@ -672,6 +667,7 @@ pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo,
|
|||
.map(TaggedValue::VecDVec2)
|
||||
};
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -691,11 +687,12 @@ pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo,
|
|||
pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetHolder>, Option<Vec<WidgetHolder>>) {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut first_widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut first_widgets = start_widgets(parameter_widgets_info);
|
||||
let mut second_widgets = None;
|
||||
|
||||
let from_font_input = |font: &FontInput| TaggedValue::Font(Font::new(font.font_family.clone(), font.font_style.clone()));
|
||||
|
||||
let Some(document_node) = document_node else { return (Vec::new(), None) };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return (vec![], None);
|
||||
|
@ -725,7 +722,7 @@ pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetH
|
|||
}
|
||||
|
||||
pub fn vector_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::VectorData);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(TextLabel::new("Vector data is supplied through the node graph").widget_holder());
|
||||
|
@ -734,7 +731,7 @@ pub fn vector_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<W
|
|||
}
|
||||
|
||||
pub fn raster_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Raster);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(TextLabel::new("Raster data is supplied through the node graph").widget_holder());
|
||||
|
@ -743,7 +740,7 @@ pub fn raster_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<Widget
|
|||
}
|
||||
|
||||
pub fn group_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Group);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(TextLabel::new("Group data is supplied through the node graph").widget_holder());
|
||||
|
@ -754,8 +751,9 @@ pub fn group_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetH
|
|||
pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: NumberInput) -> Vec<WidgetHolder> {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::Number);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return Vec::new() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -825,7 +823,8 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props:
|
|||
pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
|
@ -859,8 +858,9 @@ pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Layout
|
|||
pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button: ColorInput) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||
// Return early with just the label if the input is exposed to the graph, meaning we don't want to show the color picker widget in the Properties panel
|
||||
let NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] else {
|
||||
return LayoutGroup::Row { widgets };
|
||||
|
@ -913,8 +913,9 @@ pub fn font_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup
|
|||
pub fn curve_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||
|
||||
let mut widgets = start_widgets(parameter_widgets_info, FrontendGraphDataType::General);
|
||||
let mut widgets = start_widgets(parameter_widgets_info);
|
||||
|
||||
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return LayoutGroup::Row { widgets: vec![] };
|
||||
|
@ -939,14 +940,11 @@ pub fn get_document_node<'a>(node_id: NodeId, context: &'a NodePropertiesContext
|
|||
network.nodes.get(&node_id).ok_or(format!("node {node_id} not found in get_document_node"))
|
||||
}
|
||||
|
||||
pub fn query_node_and_input_info<'a>(node_id: NodeId, input_index: usize, context: &'a NodePropertiesContext<'a>) -> Result<(&'a DocumentNode, &'a str, &'a str), String> {
|
||||
pub fn query_node_and_input_info<'a>(node_id: NodeId, input_index: usize, context: &'a mut NodePropertiesContext<'a>) -> Result<(&'a DocumentNode, String, String), String> {
|
||||
let (name, description) = context.network_interface.displayed_input_name_and_description(&node_id, input_index, context.selection_network_path);
|
||||
let document_node = get_document_node(node_id, context)?;
|
||||
let input_name = context.network_interface.input_name(node_id, input_index, context.selection_network_path).unwrap_or_else(|| {
|
||||
log::warn!("input name not found in query_node_and_input_info");
|
||||
""
|
||||
});
|
||||
let input_description = context.network_interface.input_description(node_id, input_index, context.selection_network_path).unwrap_or_default();
|
||||
Ok((document_node, input_name, input_description))
|
||||
|
||||
Ok((document_node, name, description))
|
||||
}
|
||||
|
||||
pub fn query_noise_pattern_state(node_id: NodeId, context: &NodePropertiesContext) -> Result<(bool, bool, bool, bool, bool, bool), String> {
|
||||
|
@ -995,6 +993,9 @@ pub fn query_assign_colors_randomize(node_id: NodeId, context: &NodePropertiesCo
|
|||
pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::raster::brightness_contrast::*;
|
||||
|
||||
// Use Classic
|
||||
let use_classic = bool_widget(ParameterWidgetsInfo::new(node_id, UseClassicInput::INDEX, true, context), CheckboxInput::default());
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
|
@ -1002,12 +1003,6 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
|
|||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
// Use Classic
|
||||
let use_classic = bool_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, UseClassicInput::INDEX, true, context),
|
||||
CheckboxInput::default(),
|
||||
);
|
||||
let use_classic_value = match document_node.inputs[UseClassicInput::INDEX].as_value() {
|
||||
Some(TaggedValue::Bool(use_classic_choice)) => *use_classic_choice,
|
||||
_ => false,
|
||||
|
@ -1015,7 +1010,7 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
|
|||
|
||||
// Brightness
|
||||
let brightness = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, BrightnessInput::INDEX, true, context),
|
||||
ParameterWidgetsInfo::new(node_id, BrightnessInput::INDEX, true, context),
|
||||
NumberInput::default()
|
||||
.unit("%")
|
||||
.mode_range()
|
||||
|
@ -1026,7 +1021,7 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
|
|||
|
||||
// Contrast
|
||||
let contrast = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, ContrastInput::INDEX, true, context),
|
||||
ParameterWidgetsInfo::new(node_id, ContrastInput::INDEX, true, context),
|
||||
NumberInput::default()
|
||||
.unit("%")
|
||||
.mode_range()
|
||||
|
@ -1047,6 +1042,11 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
|
|||
pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::raster::channel_mixer::*;
|
||||
|
||||
let is_monochrome = bool_widget(ParameterWidgetsInfo::new(node_id, MonochromeInput::INDEX, true, context), CheckboxInput::default());
|
||||
let mut parameter_info = ParameterWidgetsInfo::new(node_id, OutputChannelInput::INDEX, true, context);
|
||||
parameter_info.exposeable = false;
|
||||
let output_channel = enum_choice::<RedGreenBlue>().for_socket(parameter_info).property_row();
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
|
@ -1054,22 +1054,12 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper
|
|||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
// Monochrome
|
||||
let is_monochrome = bool_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, MonochromeInput::INDEX, true, context),
|
||||
CheckboxInput::default(),
|
||||
);
|
||||
let is_monochrome_value = match document_node.inputs[MonochromeInput::INDEX].as_value() {
|
||||
Some(TaggedValue::Bool(monochrome_choice)) => *monochrome_choice,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
// Output channel choice
|
||||
let output_channel = enum_choice::<RedGreenBlue>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, OutputChannelInput::INDEX, true, context))
|
||||
.exposable(false)
|
||||
.property_row();
|
||||
let output_channel_value = match &document_node.inputs[OutputChannelInput::INDEX].as_value() {
|
||||
Some(TaggedValue::RedGreenBlue(choice)) => choice,
|
||||
_ => {
|
||||
|
@ -1086,10 +1076,10 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper
|
|||
(false, RedGreenBlue::Blue) => (BlueRInput::INDEX, BlueGInput::INDEX, BlueBInput::INDEX, BlueCInput::INDEX),
|
||||
};
|
||||
let number_input = NumberInput::default().mode_range().min(-200.).max(200.).unit("%");
|
||||
let red = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, red_output_index, true, context), number_input.clone());
|
||||
let green = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, green_output_index, true, context), number_input.clone());
|
||||
let blue = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, blue_output_index, true, context), number_input.clone());
|
||||
let constant = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, constant_output_index, true, context), number_input);
|
||||
let red = number_widget(ParameterWidgetsInfo::new(node_id, red_output_index, true, context), number_input.clone());
|
||||
let green = number_widget(ParameterWidgetsInfo::new(node_id, green_output_index, true, context), number_input.clone());
|
||||
let blue = number_widget(ParameterWidgetsInfo::new(node_id, blue_output_index, true, context), number_input.clone());
|
||||
let constant = number_widget(ParameterWidgetsInfo::new(node_id, constant_output_index, true, context), number_input);
|
||||
|
||||
// Monochrome
|
||||
let mut layout = vec![LayoutGroup::Row { widgets: is_monochrome }];
|
||||
|
@ -1110,6 +1100,10 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper
|
|||
pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::raster::selective_color::*;
|
||||
|
||||
let mut default_info = ParameterWidgetsInfo::new(node_id, ColorsInput::INDEX, true, context);
|
||||
default_info.exposeable = false;
|
||||
let colors = enum_choice::<SelectiveColorChoice>().for_socket(default_info).property_row();
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
|
@ -1117,13 +1111,7 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
|
|||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
// Colors choice
|
||||
let colors = enum_choice::<SelectiveColorChoice>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, ColorsInput::INDEX, true, context))
|
||||
.exposable(false)
|
||||
.property_row();
|
||||
|
||||
let colors_choice = match &document_node.inputs[ColorsInput::INDEX].as_value() {
|
||||
Some(TaggedValue::SelectiveColorChoice(choice)) => choice,
|
||||
_ => {
|
||||
|
@ -1131,7 +1119,6 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
|
|||
return vec![];
|
||||
}
|
||||
};
|
||||
|
||||
// CMYK
|
||||
let (c_index, m_index, y_index, k_index) = match colors_choice {
|
||||
SelectiveColorChoice::Reds => (RCInput::INDEX, RMInput::INDEX, RYInput::INDEX, RKInput::INDEX),
|
||||
|
@ -1145,14 +1132,14 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
|
|||
SelectiveColorChoice::Blacks => (KCInput::INDEX, KMInput::INDEX, KYInput::INDEX, KKInput::INDEX),
|
||||
};
|
||||
let number_input = NumberInput::default().mode_range().min(-100.).max(100.).unit("%");
|
||||
let cyan = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, c_index, true, context), number_input.clone());
|
||||
let magenta = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, m_index, true, context), number_input.clone());
|
||||
let yellow = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, y_index, true, context), number_input.clone());
|
||||
let black = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, k_index, true, context), number_input);
|
||||
let cyan = number_widget(ParameterWidgetsInfo::new(node_id, c_index, true, context), number_input.clone());
|
||||
let magenta = number_widget(ParameterWidgetsInfo::new(node_id, m_index, true, context), number_input.clone());
|
||||
let yellow = number_widget(ParameterWidgetsInfo::new(node_id, y_index, true, context), number_input.clone());
|
||||
let black = number_widget(ParameterWidgetsInfo::new(node_id, k_index, true, context), number_input);
|
||||
|
||||
// Mode
|
||||
let mode = enum_choice::<RelativeAbsolute>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, ModeInput::INDEX, true, context))
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, ModeInput::INDEX, true, context))
|
||||
.property_row();
|
||||
|
||||
vec![
|
||||
|
@ -1171,19 +1158,19 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
|
|||
pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::vector::generator_nodes::grid::*;
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
log::error!("Could not get document node in exposure_properties: {err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let grid_type = enum_choice::<GridType>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, GridTypeInput::INDEX, true, context))
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, GridTypeInput::INDEX, true, context))
|
||||
.property_row();
|
||||
|
||||
let mut widgets = vec![grid_type];
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
log::error!("Could not get document node in grid_properties: {err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let Some(grid_type_input) = document_node.inputs.get(GridTypeInput::INDEX) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -1191,36 +1178,24 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||
if let Some(&TaggedValue::GridType(grid_type)) = grid_type_input.as_non_exposed_value() {
|
||||
match grid_type {
|
||||
GridType::Rectangular => {
|
||||
let spacing = coordinate_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::<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.));
|
||||
widgets.push(spacing);
|
||||
}
|
||||
GridType::Isometric => {
|
||||
let spacing = LayoutGroup::Row {
|
||||
widgets: number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::<f64>::INDEX, true, context),
|
||||
ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context),
|
||||
NumberInput::default().label("H").min(0.).unit(" px"),
|
||||
),
|
||||
};
|
||||
let angles = coordinate_widget(ParameterWidgetsInfo::from_index(document_node, node_id, AnglesInput::INDEX, true, context), "", "", "°", None);
|
||||
let angles = coordinate_widget(ParameterWidgetsInfo::new(node_id, AnglesInput::INDEX, true, context), "", "", "°", None);
|
||||
widgets.extend([spacing, angles]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let columns = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, ColumnsInput::INDEX, true, context),
|
||||
NumberInput::default().min(1.),
|
||||
);
|
||||
let rows = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, RowsInput::INDEX, true, context),
|
||||
NumberInput::default().min(1.),
|
||||
);
|
||||
let columns = number_widget(ParameterWidgetsInfo::new(node_id, ColumnsInput::INDEX, true, context), NumberInput::default().min(1.));
|
||||
let rows = number_widget(ParameterWidgetsInfo::new(node_id, RowsInput::INDEX, true, context), NumberInput::default().min(1.));
|
||||
|
||||
widgets.extend([LayoutGroup::Row { widgets: columns }, LayoutGroup::Row { widgets: rows }]);
|
||||
|
||||
|
@ -1249,26 +1224,14 @@ pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodeProp
|
|||
let is_quantity = matches!(current_spacing, Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity)));
|
||||
|
||||
let spacing = enum_choice::<PointSpacingType>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, SpacingInput::INDEX, true, context))
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, SpacingInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let separation = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, SeparationInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.).unit(" px"),
|
||||
);
|
||||
let quantity = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, QuantityInput::INDEX, true, context),
|
||||
NumberInput::default().min(2.).int(),
|
||||
);
|
||||
let start_offset = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, StartOffsetInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.).unit(" px"),
|
||||
);
|
||||
let stop_offset = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, StopOffsetInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.).unit(" px"),
|
||||
);
|
||||
let separation = number_widget(ParameterWidgetsInfo::new(node_id, SeparationInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px"));
|
||||
let quantity = number_widget(ParameterWidgetsInfo::new(node_id, QuantityInput::INDEX, true, context), NumberInput::default().min(2.).int());
|
||||
let start_offset = number_widget(ParameterWidgetsInfo::new(node_id, StartOffsetInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px"));
|
||||
let stop_offset = number_widget(ParameterWidgetsInfo::new(node_id, StopOffsetInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px"));
|
||||
let adaptive_spacing = bool_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, AdaptiveSpacingInput::INDEX, true, context),
|
||||
ParameterWidgetsInfo::new(node_id, AdaptiveSpacingInput::INDEX, true, context),
|
||||
CheckboxInput::default().disabled(is_quantity),
|
||||
);
|
||||
|
||||
|
@ -1288,23 +1251,10 @@ pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodeProp
|
|||
pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::raster::exposure::*;
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
log::error!("Could not get document node in exposure_properties: {err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let exposure = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, ExposureInput::INDEX, true, context),
|
||||
NumberInput::default().min(-20.).max(20.),
|
||||
);
|
||||
let offset = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, OffsetInput::INDEX, true, context),
|
||||
NumberInput::default().min(-0.5).max(0.5),
|
||||
);
|
||||
let exposure = number_widget(ParameterWidgetsInfo::new(node_id, ExposureInput::INDEX, true, context), NumberInput::default().min(-20.).max(20.));
|
||||
let offset = number_widget(ParameterWidgetsInfo::new(node_id, OffsetInput::INDEX, true, context), NumberInput::default().min(-0.5).max(0.5));
|
||||
let gamma_correction = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, GammaCorrectionInput::INDEX, true, context),
|
||||
ParameterWidgetsInfo::new(node_id, GammaCorrectionInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.01).max(9.99).increment_step(0.1),
|
||||
);
|
||||
|
||||
|
@ -1318,6 +1268,13 @@ pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesC
|
|||
pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::vector::generator_nodes::rectangle::*;
|
||||
|
||||
// Corner Radius
|
||||
let mut corner_radius_row_1 = start_widgets(ParameterWidgetsInfo::new(node_id, CornerRadiusInput::<f64>::INDEX, true, context));
|
||||
corner_radius_row_1.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
|
||||
let mut corner_radius_row_2 = vec![Separator::new(SeparatorType::Unrelated).widget_holder()];
|
||||
corner_radius_row_2.push(TextLabel::new("").widget_holder());
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
|
@ -1325,23 +1282,6 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
|||
return Vec::new();
|
||||
}
|
||||
};
|
||||
// Size X
|
||||
let size_x = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, WidthInput::INDEX, true, context), NumberInput::default());
|
||||
|
||||
// Size Y
|
||||
let size_y = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, HeightInput::INDEX, true, context), NumberInput::default());
|
||||
|
||||
// Corner Radius
|
||||
let mut corner_radius_row_1 = start_widgets(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, CornerRadiusInput::<f64>::INDEX, true, context),
|
||||
FrontendGraphDataType::Number,
|
||||
);
|
||||
corner_radius_row_1.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
|
||||
let mut corner_radius_row_2 = vec![Separator::new(SeparatorType::Unrelated).widget_holder()];
|
||||
corner_radius_row_2.push(TextLabel::new("").widget_holder());
|
||||
add_blank_assist(&mut corner_radius_row_2);
|
||||
|
||||
let Some(input) = document_node.inputs.get(IndividualCornerRadiiInput::INDEX) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -1427,6 +1367,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
|||
} else {
|
||||
NumberInput::default()
|
||||
.value(Some(uniform_val))
|
||||
.unit(" px")
|
||||
.on_update(update_value(move |x: &NumberInput| TaggedValue::F64(x.value.unwrap()), node_id, CornerRadiusInput::<f64>::INDEX))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder()
|
||||
|
@ -1434,8 +1375,16 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
|||
corner_radius_row_2.push(input_widget);
|
||||
}
|
||||
|
||||
// Size X
|
||||
let size_x = number_widget(ParameterWidgetsInfo::new(node_id, WidthInput::INDEX, true, context), NumberInput::default());
|
||||
|
||||
// Size Y
|
||||
let size_y = number_widget(ParameterWidgetsInfo::new(node_id, HeightInput::INDEX, true, context), NumberInput::default());
|
||||
|
||||
add_blank_assist(&mut corner_radius_row_2);
|
||||
|
||||
// Clamped
|
||||
let clamped = bool_widget(ParameterWidgetsInfo::from_index(document_node, node_id, ClampedInput::INDEX, true, context), CheckboxInput::default());
|
||||
let clamped = bool_widget(ParameterWidgetsInfo::new(node_id, ClampedInput::INDEX, true, context), CheckboxInput::default());
|
||||
|
||||
vec![
|
||||
LayoutGroup::Row { widgets: size_x },
|
||||
|
@ -1564,6 +1513,8 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
|
|||
pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::vector::fill::*;
|
||||
|
||||
let mut widgets_first_row = start_widgets(ParameterWidgetsInfo::new(node_id, FillInput::<Color>::INDEX, true, context));
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
|
@ -1572,11 +1523,6 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||
}
|
||||
};
|
||||
|
||||
let mut widgets_first_row = start_widgets(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, FillInput::<Color>::INDEX, true, context),
|
||||
FrontendGraphDataType::General,
|
||||
);
|
||||
|
||||
let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), &Some(&TaggedValue::OptionalColor(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = (
|
||||
&document_node.inputs[FillInput::<Color>::INDEX].as_value(),
|
||||
&document_node.inputs[BackupColorInput::INDEX].as_value(),
|
||||
|
@ -1755,47 +1701,42 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
|
|||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let join_value = match &document_node.inputs[JoinInput::INDEX].as_value() {
|
||||
Some(TaggedValue::StrokeJoin(x)) => x,
|
||||
_ => &StrokeJoin::Miter,
|
||||
};
|
||||
|
||||
let color = color_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, ColorInput::<Option<Color>>::INDEX, true, context),
|
||||
crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(),
|
||||
);
|
||||
let weight = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, WeightInput::INDEX, true, context),
|
||||
NumberInput::default().unit(" px").min(0.),
|
||||
);
|
||||
let align = enum_choice::<StrokeAlign>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, AlignInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let cap = enum_choice::<StrokeCap>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, CapInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let join = enum_choice::<StrokeJoin>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, JoinInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let miter_limit = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, MiterLimitInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.).disabled({
|
||||
let join_value = match &document_node.inputs[JoinInput::INDEX].as_value() {
|
||||
Some(TaggedValue::StrokeJoin(x)) => x,
|
||||
_ => &StrokeJoin::Miter,
|
||||
};
|
||||
join_value != &StrokeJoin::Miter
|
||||
}),
|
||||
);
|
||||
let paint_order = enum_choice::<PaintOrder>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, PaintOrderInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let dash_lengths_val = match &document_node.inputs[DashLengthsInput::INDEX].as_value() {
|
||||
Some(TaggedValue::VecF64(x)) => x,
|
||||
_ => &vec![],
|
||||
};
|
||||
let dash_lengths = array_of_number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, DashLengthsInput::INDEX, true, context),
|
||||
TextInput::default().centered(true),
|
||||
let has_dash_lengths = dash_lengths_val.is_empty();
|
||||
let miter_limit_disabled = join_value != &StrokeJoin::Miter;
|
||||
|
||||
let color = color_widget(
|
||||
ParameterWidgetsInfo::new(node_id, ColorInput::<Option<Color>>::INDEX, true, context),
|
||||
crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(),
|
||||
);
|
||||
let number_input = NumberInput::default().unit(" px").disabled(dash_lengths_val.is_empty());
|
||||
let dash_offset = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, DashOffsetInput::INDEX, true, context), number_input);
|
||||
let weight = number_widget(ParameterWidgetsInfo::new(node_id, WeightInput::INDEX, true, context), NumberInput::default().unit(" px").min(0.));
|
||||
let align = enum_choice::<StrokeAlign>()
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, AlignInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let cap = enum_choice::<StrokeCap>().for_socket(ParameterWidgetsInfo::new(node_id, CapInput::INDEX, true, context)).property_row();
|
||||
let join = enum_choice::<StrokeJoin>()
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, JoinInput::INDEX, true, context))
|
||||
.property_row();
|
||||
|
||||
let miter_limit = number_widget(
|
||||
ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context),
|
||||
NumberInput::default().min(0.).disabled(miter_limit_disabled),
|
||||
);
|
||||
let paint_order = enum_choice::<PaintOrder>()
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, PaintOrderInput::INDEX, true, context))
|
||||
.property_row();
|
||||
let disabled_number_input = NumberInput::default().unit(" px").disabled(has_dash_lengths);
|
||||
let dash_lengths = array_of_number_widget(ParameterWidgetsInfo::new(node_id, DashLengthsInput::INDEX, true, context), TextInput::default().centered(true));
|
||||
let number_input = disabled_number_input;
|
||||
let dash_offset = number_widget(ParameterWidgetsInfo::new(node_id, DashOffsetInput::INDEX, true, context), number_input);
|
||||
|
||||
vec![
|
||||
color,
|
||||
|
@ -1813,6 +1754,13 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
|
|||
pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::vector::offset_path::*;
|
||||
|
||||
let number_input = NumberInput::default().unit(" px");
|
||||
let distance = number_widget(ParameterWidgetsInfo::new(node_id, DistanceInput::INDEX, true, context), number_input);
|
||||
|
||||
let join = enum_choice::<StrokeJoin>()
|
||||
.for_socket(ParameterWidgetsInfo::new(node_id, JoinInput::INDEX, true, context))
|
||||
.property_row();
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
|
@ -1820,13 +1768,6 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let number_input = NumberInput::default().unit(" px");
|
||||
let distance = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, DistanceInput::INDEX, true, context), number_input);
|
||||
|
||||
let join = enum_choice::<StrokeJoin>()
|
||||
.for_socket(ParameterWidgetsInfo::from_index(document_node, node_id, JoinInput::INDEX, true, context))
|
||||
.property_row();
|
||||
|
||||
let number_input = NumberInput::default().min(0.).disabled({
|
||||
let join_val = match &document_node.inputs[JoinInput::INDEX].as_value() {
|
||||
Some(TaggedValue::StrokeJoin(x)) => x,
|
||||
|
@ -1834,7 +1775,7 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||
};
|
||||
join_val != &StrokeJoin::Miter
|
||||
});
|
||||
let miter_limit = number_widget(ParameterWidgetsInfo::from_index(document_node, node_id, MiterLimitInput::INDEX, true, context), number_input);
|
||||
let miter_limit = number_widget(ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context), number_input);
|
||||
|
||||
vec![LayoutGroup::Row { widgets: distance }, join, LayoutGroup::Row { widgets: miter_limit }]
|
||||
}
|
||||
|
@ -1842,20 +1783,16 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||
pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
use graphene_std::math_nodes::math::*;
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
log::error!("Could not get document node in offset_path_properties: {err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
|
||||
let expression = (|| {
|
||||
let mut widgets = start_widgets(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, ExpressionInput::INDEX, true, context),
|
||||
FrontendGraphDataType::General,
|
||||
);
|
||||
let mut widgets = start_widgets(ParameterWidgetsInfo::new(node_id, ExpressionInput::INDEX, true, context));
|
||||
|
||||
let document_node = match get_document_node(node_id, context) {
|
||||
Ok(document_node) => document_node,
|
||||
Err(err) => {
|
||||
log::error!("Could not get document node in offset_path_properties: {err}");
|
||||
return Vec::new();
|
||||
}
|
||||
};
|
||||
let Some(input) = document_node.inputs.get(ExpressionInput::INDEX) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
return vec![];
|
||||
|
@ -1888,10 +1825,7 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
|
|||
}
|
||||
widgets
|
||||
})();
|
||||
let operand_b = number_widget(
|
||||
ParameterWidgetsInfo::from_index(document_node, node_id, OperandBInput::<f64>::INDEX, true, context),
|
||||
NumberInput::default(),
|
||||
);
|
||||
let operand_b = number_widget(ParameterWidgetsInfo::new(node_id, OperandBInput::<f64>::INDEX, true, context), NumberInput::default());
|
||||
let operand_a_hint = vec![TextLabel::new("(Operand A is the primary input)").widget_holder()];
|
||||
|
||||
vec![
|
||||
|
@ -1902,44 +1836,37 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
|
|||
}
|
||||
|
||||
pub struct ParameterWidgetsInfo<'a> {
|
||||
document_node: &'a DocumentNode,
|
||||
document_node: Option<&'a DocumentNode>,
|
||||
node_id: NodeId,
|
||||
index: usize,
|
||||
name: &'a str,
|
||||
description: &'a str,
|
||||
name: String,
|
||||
description: String,
|
||||
input_type: FrontendGraphDataType,
|
||||
blank_assist: bool,
|
||||
exposeable: bool,
|
||||
}
|
||||
|
||||
impl<'a> ParameterWidgetsInfo<'a> {
|
||||
pub fn new(document_node: &'a DocumentNode, node_id: NodeId, index: usize, name: &'a str, description: &'a str, blank_assist: bool) -> ParameterWidgetsInfo<'a> {
|
||||
pub fn new(node_id: NodeId, index: usize, blank_assist: bool, context: &'a mut NodePropertiesContext) -> ParameterWidgetsInfo<'a> {
|
||||
let (name, description) = context.network_interface.displayed_input_name_and_description(&node_id, index, context.selection_network_path);
|
||||
let input_type = FrontendGraphDataType::from_type(&context.network_interface.input_type(&InputConnector::node(node_id, index), context.selection_network_path).0);
|
||||
let document_node = context.network_interface.document_node(&node_id, context.selection_network_path);
|
||||
|
||||
ParameterWidgetsInfo {
|
||||
document_node,
|
||||
node_id,
|
||||
index,
|
||||
name,
|
||||
description,
|
||||
input_type,
|
||||
blank_assist,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_index(document_node: &'a DocumentNode, node_id: NodeId, index: usize, blank_assist: bool, context: &'a NodePropertiesContext) -> ParameterWidgetsInfo<'a> {
|
||||
let name = context.network_interface.input_name(node_id, index, context.selection_network_path).unwrap_or_default();
|
||||
let description = context.network_interface.input_description(node_id, index, context.selection_network_path).unwrap_or_default();
|
||||
|
||||
Self {
|
||||
document_node,
|
||||
node_id,
|
||||
index,
|
||||
name,
|
||||
description,
|
||||
blank_assist,
|
||||
exposeable: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod choice {
|
||||
use super::ParameterWidgetsInfo;
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::registry::{ChoiceTypeStatic, ChoiceWidgetHint};
|
||||
|
@ -1972,11 +1899,7 @@ pub mod choice {
|
|||
|
||||
impl<E: ChoiceTypeStatic + 'static> EnumChoice<E> {
|
||||
pub fn for_socket(self, parameter_info: ParameterWidgetsInfo) -> ForSocket<Self> {
|
||||
ForSocket {
|
||||
widget_factory: self,
|
||||
parameter_info,
|
||||
exposable: true,
|
||||
}
|
||||
ForSocket { widget_factory: self, parameter_info }
|
||||
}
|
||||
|
||||
/// Not yet implemented!
|
||||
|
@ -2067,7 +1990,6 @@ pub mod choice {
|
|||
pub struct ForSocket<'p, W> {
|
||||
widget_factory: W,
|
||||
parameter_info: ParameterWidgetsInfo<'p>,
|
||||
exposable: bool,
|
||||
}
|
||||
|
||||
impl<'p, W> ForSocket<'p, W>
|
||||
|
@ -2084,14 +2006,14 @@ pub mod choice {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn exposable(self, exposable: bool) -> Self {
|
||||
Self { exposable, ..self }
|
||||
}
|
||||
|
||||
pub fn property_row(self) -> LayoutGroup {
|
||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = self.parameter_info;
|
||||
let Some(document_node) = document_node else {
|
||||
log::error!("Could not get document node when building property row for node {:?}", node_id);
|
||||
return LayoutGroup::Row { widgets: Vec::new() };
|
||||
};
|
||||
|
||||
let mut widgets = super::start_widgets_exposable(self.parameter_info, FrontendGraphDataType::General, self.exposable);
|
||||
let mut widgets = super::start_widgets(self.parameter_info);
|
||||
|
||||
let Some(input) = document_node.inputs.get(index) else {
|
||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||
|
|
|
@ -15,7 +15,7 @@ pub enum FrontendGraphDataType {
|
|||
}
|
||||
|
||||
impl FrontendGraphDataType {
|
||||
fn with_type(input: &Type) -> Self {
|
||||
pub fn from_type(input: &Type) -> Self {
|
||||
match TaggedValue::from_type_or_none(input) {
|
||||
TaggedValue::Image(_) | TaggedValue::RasterData(_) => Self::Raster,
|
||||
TaggedValue::Subpaths(_) | TaggedValue::VectorData(_) => Self::VectorData,
|
||||
|
@ -38,7 +38,7 @@ impl FrontendGraphDataType {
|
|||
pub fn displayed_type(input: &Type, type_source: &TypeSource) -> Self {
|
||||
match type_source {
|
||||
TypeSource::Error(_) | TypeSource::RandomProtonodeImplementation => Self::General,
|
||||
_ => Self::with_type(input),
|
||||
_ => Self::from_type(input),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ pub struct FrontendGraphInput {
|
|||
pub name: String,
|
||||
pub description: String,
|
||||
#[serde(rename = "resolvedType")]
|
||||
pub resolved_type: Option<String>,
|
||||
pub resolved_type: String,
|
||||
#[serde(rename = "validTypes")]
|
||||
pub valid_types: Vec<String>,
|
||||
#[serde(rename = "connectedTo")]
|
||||
|
@ -64,7 +64,7 @@ pub struct FrontendGraphOutput {
|
|||
pub name: String,
|
||||
pub description: String,
|
||||
#[serde(rename = "resolvedType")]
|
||||
pub resolved_type: Option<String>,
|
||||
pub resolved_type: String,
|
||||
#[serde(rename = "connectedTo")]
|
||||
pub connected_to: Vec<InputConnector>,
|
||||
}
|
||||
|
@ -96,15 +96,6 @@ pub struct FrontendNode {
|
|||
pub ui_only: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct FrontendNodeWire {
|
||||
#[serde(rename = "wireStart")]
|
||||
pub wire_start: OutputConnector,
|
||||
#[serde(rename = "wireEnd")]
|
||||
pub wire_end: InputConnector,
|
||||
pub dashed: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct FrontendNodeType {
|
||||
pub name: String,
|
||||
|
@ -153,16 +144,6 @@ pub struct Transform {
|
|||
pub y: f64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct WirePath {
|
||||
#[serde(rename = "pathString")]
|
||||
pub path_string: String,
|
||||
#[serde(rename = "dataType")]
|
||||
pub data_type: FrontendGraphDataType,
|
||||
pub thick: bool,
|
||||
pub dashed: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct BoxSelection {
|
||||
#[serde(rename = "startX")]
|
||||
|
@ -224,32 +205,3 @@ pub enum Direction {
|
|||
Left,
|
||||
Right,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum GraphWireStyle {
|
||||
#[default]
|
||||
Direct = 0,
|
||||
GridAligned = 1,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for GraphWireStyle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
GraphWireStyle::GridAligned => write!(f, "Grid-Aligned"),
|
||||
GraphWireStyle::Direct => write!(f, "Direct"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphWireStyle {
|
||||
pub fn tooltip_description(&self) -> &'static str {
|
||||
match self {
|
||||
GraphWireStyle::GridAligned => "Wires follow the grid, running in straight lines between nodes",
|
||||
GraphWireStyle::Direct => "Wires bend to run at an angle directly between nodes",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_direct(&self) -> bool {
|
||||
*self == GraphWireStyle::Direct
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,3 +5,4 @@ pub mod misc;
|
|||
pub mod network_interface;
|
||||
pub mod nodes;
|
||||
pub mod transformation;
|
||||
pub mod wires;
|
||||
|
|
File diff suppressed because it is too large
Load diff
589
editor/src/messages/portfolio/document/utility_types/wires.rs
Normal file
589
editor/src/messages/portfolio/document/utility_types/wires.rs
Normal file
|
@ -0,0 +1,589 @@
|
|||
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
|
||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use glam::{DVec2, IVec2};
|
||||
use graphene_std::uuid::NodeId;
|
||||
use graphene_std::vector::PointId;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct WirePath {
|
||||
#[serde(rename = "pathString")]
|
||||
pub path_string: String,
|
||||
#[serde(rename = "dataType")]
|
||||
pub data_type: FrontendGraphDataType,
|
||||
pub thick: bool,
|
||||
pub dashed: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub struct WirePathUpdate {
|
||||
pub id: NodeId,
|
||||
#[serde(rename = "inputIndex")]
|
||||
pub input_index: usize,
|
||||
// If none, then remove the wire from the map
|
||||
#[serde(rename = "wirePathUpdate")]
|
||||
pub wire_path_update: Option<WirePath>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum GraphWireStyle {
|
||||
#[default]
|
||||
Direct = 0,
|
||||
GridAligned = 1,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for GraphWireStyle {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
GraphWireStyle::GridAligned => write!(f, "Grid-Aligned"),
|
||||
GraphWireStyle::Direct => write!(f, "Direct"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl GraphWireStyle {
|
||||
pub fn tooltip_description(&self) -> &'static str {
|
||||
match self {
|
||||
GraphWireStyle::GridAligned => "Wires follow the grid, running in straight lines between nodes",
|
||||
GraphWireStyle::Direct => "Wires bend to run at an angle directly between nodes",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_direct(&self) -> bool {
|
||||
*self == GraphWireStyle::Direct
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_vector_wire(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool, graph_wire_style: GraphWireStyle) -> Subpath<PointId> {
|
||||
let grid_spacing = 24.;
|
||||
match graph_wire_style {
|
||||
GraphWireStyle::Direct => {
|
||||
let horizontal_gap = (output_position.x - input_position.x).abs();
|
||||
let vertical_gap = (output_position.y - input_position.y).abs();
|
||||
|
||||
let curve_length = grid_spacing;
|
||||
let curve_falloff_rate = curve_length * std::f64::consts::TAU;
|
||||
|
||||
let horizontal_curve_amount = -(2_f64.powf((-10. * horizontal_gap) / curve_falloff_rate)) + 1.;
|
||||
let vertical_curve_amount = -(2_f64.powf((-10. * vertical_gap) / curve_falloff_rate)) + 1.;
|
||||
let horizontal_curve = horizontal_curve_amount * curve_length;
|
||||
let vertical_curve = vertical_curve_amount * curve_length;
|
||||
|
||||
let locations = [
|
||||
output_position,
|
||||
DVec2::new(
|
||||
if vertical_out { output_position.x } else { output_position.x + horizontal_curve },
|
||||
if vertical_out { output_position.y - vertical_curve } else { output_position.y },
|
||||
),
|
||||
DVec2::new(
|
||||
if vertical_in { input_position.x } else { input_position.x - horizontal_curve },
|
||||
if vertical_in { input_position.y + vertical_curve } else { input_position.y },
|
||||
),
|
||||
DVec2::new(input_position.x, input_position.y),
|
||||
];
|
||||
|
||||
let smoothing = 0.5;
|
||||
let delta01 = DVec2::new((locations[1].x - locations[0].x) * smoothing, (locations[1].y - locations[0].y) * smoothing);
|
||||
let delta23 = DVec2::new((locations[3].x - locations[2].x) * smoothing, (locations[3].y - locations[2].y) * smoothing);
|
||||
|
||||
Subpath::new(
|
||||
vec![
|
||||
ManipulatorGroup {
|
||||
anchor: locations[0],
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: locations[1],
|
||||
in_handle: None,
|
||||
out_handle: Some(locations[1] + delta01),
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: locations[2],
|
||||
in_handle: Some(locations[2] - delta23),
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: locations[3],
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
],
|
||||
false,
|
||||
)
|
||||
}
|
||||
GraphWireStyle::GridAligned => {
|
||||
let locations = straight_wire_paths(output_position, input_position, vertical_out, vertical_in);
|
||||
straight_wire_subpath(locations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn straight_wire_paths(output_position: DVec2, input_position: DVec2, vertical_out: bool, vertical_in: bool) -> Vec<IVec2> {
|
||||
let grid_spacing = 24;
|
||||
let line_width = 2;
|
||||
|
||||
let in_x = input_position.x as i32;
|
||||
let in_y = input_position.y as i32;
|
||||
let out_x = output_position.x as i32;
|
||||
let out_y = output_position.y as i32;
|
||||
|
||||
let mid_x = (in_x + out_x) / 2 + (((in_x + out_x) / 2) % grid_spacing);
|
||||
let mid_y = (in_y + out_y) / 2 + (((in_y + out_y) / 2) % grid_spacing);
|
||||
let mid_y_alternate = (in_y + in_y) / 2 - (((in_y + in_y) / 2) % grid_spacing);
|
||||
|
||||
let x1 = out_x;
|
||||
let x2 = out_x + grid_spacing;
|
||||
let x3 = in_x - 2 * grid_spacing;
|
||||
let x4 = in_x;
|
||||
let x5 = in_x - 2 * grid_spacing + line_width;
|
||||
let x6 = out_x + grid_spacing + line_width;
|
||||
let x7 = out_x + 2 * grid_spacing + line_width;
|
||||
let x8 = in_x + line_width;
|
||||
let x9 = out_x + 2 * grid_spacing;
|
||||
let x10 = mid_x + line_width;
|
||||
let x11 = out_x - grid_spacing;
|
||||
let x12 = out_x - 4 * grid_spacing;
|
||||
let x13 = mid_x;
|
||||
let x14 = in_x + grid_spacing;
|
||||
let x15 = in_x - 4 * grid_spacing;
|
||||
let x16 = in_x + 8 * grid_spacing;
|
||||
let x17 = mid_x - 2 * line_width;
|
||||
let x18 = out_x + grid_spacing - 2 * line_width;
|
||||
let x19 = out_x - 2 * line_width;
|
||||
let x20 = mid_x - line_width;
|
||||
|
||||
let y1 = out_y;
|
||||
let y2 = out_y - grid_spacing;
|
||||
let y3 = in_y;
|
||||
let y4 = out_y - grid_spacing + 5 * line_width + 1;
|
||||
let y5 = in_y - 2 * grid_spacing;
|
||||
let y6 = out_y + 4 * line_width;
|
||||
let y7 = out_y + 5 * line_width;
|
||||
let y8 = out_y - 2 * grid_spacing + 5 * line_width + 1;
|
||||
let y9 = out_y + 6 * line_width;
|
||||
let y10 = in_y + 2 * grid_spacing;
|
||||
let y111 = in_y + grid_spacing + 6 * line_width + 1;
|
||||
let y12 = in_y + grid_spacing - 5 * line_width + 1;
|
||||
let y13 = in_y - grid_spacing;
|
||||
let y14 = in_y + grid_spacing;
|
||||
let y15 = mid_y;
|
||||
let y16 = mid_y_alternate;
|
||||
|
||||
let wire1 = vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x5, y4), IVec2::new(x5, y3), IVec2::new(x4, y3)];
|
||||
|
||||
let wire2 = vec![IVec2::new(x1, y1), IVec2::new(x1, y16), IVec2::new(x3, y16), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||
|
||||
let wire3 = vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x1, y4),
|
||||
IVec2::new(x12, y4),
|
||||
IVec2::new(x12, y10),
|
||||
IVec2::new(x3, y10),
|
||||
IVec2::new(x3, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
|
||||
let wire4 = vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x1, y4),
|
||||
IVec2::new(x13, y4),
|
||||
IVec2::new(x13, y10),
|
||||
IVec2::new(x3, y10),
|
||||
IVec2::new(x3, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
|
||||
if out_y == in_y && out_x > in_x && (vertical_out || !vertical_in) {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x3, y2), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// `outConnector` point and `inConnector` point lying on the same horizontal grid line and `outConnector` point lies to the right of `inConnector` point
|
||||
if out_y == in_y && out_x > in_x && (vertical_out || !vertical_in) {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x3, y2), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// Handle straight lines
|
||||
if out_y == in_y || (out_x == in_x && vertical_out) {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// Handle standard right-angle paths
|
||||
// Start vertical, then horizontal
|
||||
|
||||
// `outConnector` point lies to the left of `inConnector` point
|
||||
if vertical_out && in_x > out_x {
|
||||
// `outConnector` point lies above `inConnector` point
|
||||
if out_y < in_y {
|
||||
// `outConnector` point lies on the vertical grid line 4 units to the left of `inConnector` point point
|
||||
if -4 * grid_spacing <= out_x - in_x && out_x - in_x < -3 * grid_spacing {
|
||||
return wire1;
|
||||
};
|
||||
|
||||
// `outConnector` point lying on vertical grid lines 3 and 2 units to the left of `inConnector` point
|
||||
if -3 * grid_spacing <= out_x - in_x && out_x - in_x <= -grid_spacing {
|
||||
if -2 * grid_spacing <= out_y - in_y && out_y - in_y <= -grid_spacing {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x1, y2), IVec2::new(x2, y2), IVec2::new(x2, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
if -grid_spacing <= out_y - in_y && out_y - in_y <= 0 {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x6, y4), IVec2::new(x6, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
return vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x1, y4),
|
||||
IVec2::new(x7, y4),
|
||||
IVec2::new(x7, y5),
|
||||
IVec2::new(x3, y5),
|
||||
IVec2::new(x3, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
}
|
||||
|
||||
// `outConnector` point lying on vertical grid line 1 units to the left of `inConnector` point
|
||||
if -grid_spacing < out_x - in_x && out_x - in_x <= 0 {
|
||||
// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point
|
||||
if -2 * grid_spacing <= out_y - in_y && out_y - in_y <= -grid_spacing {
|
||||
return vec![IVec2::new(x1, y6), IVec2::new(x2, y6), IVec2::new(x8, y3)];
|
||||
};
|
||||
|
||||
// `outConnector` point lying on the same horizontal grid line as `inConnector` point
|
||||
if -grid_spacing <= out_y - in_y && out_y - in_y <= 0 {
|
||||
return vec![IVec2::new(x1, y7), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
return vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x1, y2),
|
||||
IVec2::new(x9, y2),
|
||||
IVec2::new(x9, y5),
|
||||
IVec2::new(x3, y5),
|
||||
IVec2::new(x3, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
}
|
||||
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x1, y4), IVec2::new(x10, y4), IVec2::new(x10, y3), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// `outConnector` point lies below `inConnector` point
|
||||
// `outConnector` point lying on vertical grid line 1 unit to the left of `inConnector` point
|
||||
if -grid_spacing <= out_x - in_x && out_x - in_x <= 0 {
|
||||
// `outConnector` point lying on the horizontal grid lines 1 and 2 units below the `inConnector` point
|
||||
if 0 <= out_y - in_y && out_y - in_y <= 2 * grid_spacing {
|
||||
return vec![IVec2::new(x1, y6), IVec2::new(x11, y6), IVec2::new(x11, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
return wire2;
|
||||
}
|
||||
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x1, y3), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// `outConnector` point lies to the right of `inConnector` point
|
||||
if vertical_out && in_x <= out_x {
|
||||
// `outConnector` point lying on any horizontal grid line above `inConnector` point
|
||||
if out_y < in_y {
|
||||
// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point
|
||||
if -2 * grid_spacing < out_y - in_y && out_y - in_y <= -grid_spacing {
|
||||
return wire1;
|
||||
};
|
||||
|
||||
// `outConnector` point lying on the same horizontal grid line as `inConnector` point
|
||||
if -grid_spacing < out_y - in_y && out_y - in_y <= 0 {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x1, y8), IVec2::new(x5, y8), IVec2::new(x5, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// `outConnector` point lying on vertical grid lines 1 and 2 units to the right of `inConnector` point
|
||||
if grid_spacing <= out_x - in_x && out_x - in_x <= 3 * grid_spacing {
|
||||
return vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x1, y4),
|
||||
IVec2::new(x9, y4),
|
||||
IVec2::new(x9, y5),
|
||||
IVec2::new(x3, y5),
|
||||
IVec2::new(x3, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
}
|
||||
|
||||
return vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x1, y4),
|
||||
IVec2::new(x10, y4),
|
||||
IVec2::new(x10, y5),
|
||||
IVec2::new(x5, y5),
|
||||
IVec2::new(x5, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
}
|
||||
|
||||
// `outConnector` point lies below `inConnector` point
|
||||
if out_y - in_y <= grid_spacing {
|
||||
// `outConnector` point lies on the horizontal grid line 1 unit below the `inConnector` Point
|
||||
if 0 <= out_x - in_x && out_x - in_x <= 13 * grid_spacing {
|
||||
return vec![IVec2::new(x1, y9), IVec2::new(x3, y9), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
if 13 < out_x - in_x && out_x - in_x <= 18 * grid_spacing {
|
||||
return wire3;
|
||||
};
|
||||
|
||||
return wire4;
|
||||
}
|
||||
|
||||
// `outConnector` point lies on the horizontal grid line 2 units below `outConnector` point
|
||||
if grid_spacing <= out_y - in_y && out_y - in_y <= 2 * grid_spacing {
|
||||
if 0 <= out_x - in_x && out_x - in_x <= 13 * grid_spacing {
|
||||
return vec![IVec2::new(x1, y7), IVec2::new(x5, y7), IVec2::new(x5, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
if 13 < out_x - in_x && out_x - in_x <= 18 * grid_spacing {
|
||||
return wire3;
|
||||
};
|
||||
|
||||
return wire4;
|
||||
}
|
||||
|
||||
// 0 to 4 units below the `outConnector` Point
|
||||
if out_y - in_y <= 4 * grid_spacing {
|
||||
return wire1;
|
||||
};
|
||||
|
||||
return wire2;
|
||||
}
|
||||
|
||||
// Start horizontal, then vertical
|
||||
if vertical_in {
|
||||
// when `outConnector` lies below `inConnector`
|
||||
if out_y > in_y {
|
||||
// `out_x` lies to the left of `in_x`
|
||||
if out_x < in_x {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x4, y1), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// `out_x` lies to the right of `in_x`
|
||||
if out_y - in_y <= grid_spacing {
|
||||
// `outConnector` point directly below `inConnector` point
|
||||
if 0 <= out_x - in_x && out_x - in_x <= grid_spacing {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x14, y1), IVec2::new(x14, y2), IVec2::new(x4, y2), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// `outConnector` point lies below `inConnector` point and strictly to the right of `inConnector` point
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y111), IVec2::new(x4, y111), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y2), IVec2::new(x4, y2), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// `out_y` lies on or above the `in_y` point
|
||||
if -6 * grid_spacing < in_x - out_x && in_x - out_x < 4 * grid_spacing {
|
||||
// edge case: `outConnector` point lying on vertical grid lines ranging from 4 units to left to 5 units to right of `inConnector` point
|
||||
if -grid_spacing < in_x - out_x && in_x - out_x < 4 * grid_spacing {
|
||||
return vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x2, y1),
|
||||
IVec2::new(x2, y2),
|
||||
IVec2::new(x15, y2),
|
||||
IVec2::new(x15, y12),
|
||||
IVec2::new(x4, y12),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
}
|
||||
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x16, y1), IVec2::new(x16, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// left of edge case: `outConnector` point lying on vertical grid lines more than 4 units to left of `inConnector` point
|
||||
if 4 * grid_spacing < in_x - out_x {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x17, y1), IVec2::new(x17, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// right of edge case: `outConnector` point lying on the vertical grid lines more than 5 units to right of `inConnector` point
|
||||
if 6 * grid_spacing > in_x - out_x {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x18, y1), IVec2::new(x18, y12), IVec2::new(x4, y12), IVec2::new(x4, y3)];
|
||||
};
|
||||
}
|
||||
|
||||
// Both horizontal - use horizontal middle point
|
||||
// When `inConnector` point is one of the two closest diagonally opposite points
|
||||
if 0 <= in_x - out_x && in_x - out_x <= grid_spacing && in_y - out_y >= -grid_spacing && in_y - out_y <= grid_spacing {
|
||||
return vec![IVec2::new(x19, y1), IVec2::new(x19, y3), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// When `inConnector` point lies on the horizontal line 1 unit above and below the `outConnector` point
|
||||
if -grid_spacing <= out_y - in_y && out_y - in_y <= grid_spacing && out_x > in_x {
|
||||
// Horizontal line above `out_y`
|
||||
if in_y < out_y {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y13), IVec2::new(x3, y13), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
// Horizontal line below `out_y`
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x2, y1), IVec2::new(x2, y14), IVec2::new(x3, y14), IVec2::new(x3, y3), IVec2::new(x4, y3)];
|
||||
}
|
||||
|
||||
// `outConnector` point to the right of `inConnector` point
|
||||
if out_x > in_x - grid_spacing {
|
||||
return vec![
|
||||
IVec2::new(x1, y1),
|
||||
IVec2::new(x18, y1),
|
||||
IVec2::new(x18, y15),
|
||||
IVec2::new(x5, y15),
|
||||
IVec2::new(x5, y3),
|
||||
IVec2::new(x4, y3),
|
||||
];
|
||||
};
|
||||
|
||||
// When `inConnector` point lies on the vertical grid line two units to the right of `outConnector` point
|
||||
if grid_spacing <= in_x - out_x && in_x - out_x <= 2 * grid_spacing {
|
||||
return vec![IVec2::new(x1, y1), IVec2::new(x18, y1), IVec2::new(x18, y3), IVec2::new(x4, y3)];
|
||||
};
|
||||
|
||||
vec![IVec2::new(x1, y1), IVec2::new(x20, y1), IVec2::new(x20, y3), IVec2::new(x4, y3)]
|
||||
}
|
||||
|
||||
fn straight_wire_subpath(locations: Vec<IVec2>) -> Subpath<PointId> {
|
||||
if locations.is_empty() {
|
||||
return Subpath::new(Vec::new(), false);
|
||||
}
|
||||
|
||||
if locations.len() == 2 {
|
||||
return Subpath::new(
|
||||
vec![
|
||||
ManipulatorGroup {
|
||||
anchor: locations[0].into(),
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: locations[1].into(),
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
],
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
let corner_radius = 10;
|
||||
|
||||
// Create path with rounded corners
|
||||
let mut path = vec![ManipulatorGroup {
|
||||
anchor: locations[0].into(),
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
}];
|
||||
|
||||
for i in 1..(locations.len() - 1) {
|
||||
let prev = locations[i - 1];
|
||||
let curr = locations[i];
|
||||
let next = locations[i + 1];
|
||||
|
||||
let corner_start = IVec2::new(
|
||||
curr.x
|
||||
+ if curr.x == prev.x {
|
||||
0
|
||||
} else if prev.x > curr.x {
|
||||
corner_radius
|
||||
} else {
|
||||
-corner_radius
|
||||
},
|
||||
curr.y
|
||||
+ if curr.y == prev.y {
|
||||
0
|
||||
} else if prev.y > curr.y {
|
||||
corner_radius
|
||||
} else {
|
||||
-corner_radius
|
||||
},
|
||||
);
|
||||
|
||||
let corner_start_mid = IVec2::new(
|
||||
curr.x
|
||||
+ if curr.x == prev.x {
|
||||
0
|
||||
} else if prev.x > curr.x {
|
||||
corner_radius / 2
|
||||
} else {
|
||||
-corner_radius / 2
|
||||
},
|
||||
curr.y
|
||||
+ if curr.y == prev.y {
|
||||
0
|
||||
} else {
|
||||
match prev.y > curr.y {
|
||||
true => corner_radius / 2,
|
||||
false => -corner_radius / 2,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
let corner_end = IVec2::new(
|
||||
curr.x
|
||||
+ if curr.x == next.x {
|
||||
0
|
||||
} else if next.x > curr.x {
|
||||
corner_radius
|
||||
} else {
|
||||
-corner_radius
|
||||
},
|
||||
curr.y
|
||||
+ if curr.y == next.y {
|
||||
0
|
||||
} else if next.y > curr.y {
|
||||
corner_radius
|
||||
} else {
|
||||
-corner_radius
|
||||
},
|
||||
);
|
||||
|
||||
let corner_end_mid = IVec2::new(
|
||||
curr.x
|
||||
+ if curr.x == next.x {
|
||||
0
|
||||
} else if next.x > curr.x {
|
||||
corner_radius / 2
|
||||
} else {
|
||||
-corner_radius / 2
|
||||
},
|
||||
curr.y
|
||||
+ if curr.y == next.y {
|
||||
0
|
||||
} else if next.y > curr.y {
|
||||
10 / 2
|
||||
} else {
|
||||
-corner_radius / 2
|
||||
},
|
||||
);
|
||||
|
||||
path.extend(vec![
|
||||
ManipulatorGroup {
|
||||
anchor: corner_start.into(),
|
||||
in_handle: None,
|
||||
out_handle: Some(corner_start_mid.into()),
|
||||
id: PointId::generate(),
|
||||
},
|
||||
ManipulatorGroup {
|
||||
anchor: corner_end.into(),
|
||||
in_handle: Some(corner_end_mid.into()),
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
path.push(ManipulatorGroup {
|
||||
anchor: (*locations.last().unwrap()).into(),
|
||||
in_handle: None,
|
||||
out_handle: None,
|
||||
id: PointId::generate(),
|
||||
});
|
||||
Subpath::new(path, false)
|
||||
}
|
|
@ -3,14 +3,14 @@
|
|||
|
||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, OutputConnector};
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};
|
||||
use crate::messages::prelude::DocumentMessageHandler;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::IVec2;
|
||||
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
|
||||
use graphene_std::text::TypesettingConfig;
|
||||
use graphene_std::uuid::NodeId;
|
||||
use graphene_std::vector::style::{Fill, FillType, Gradient, PaintOrder, StrokeAlign};
|
||||
use graphene_std::vector::style::{PaintOrder, StrokeAlign};
|
||||
use graphene_std::vector::{VectorData, VectorDataTable};
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
@ -24,6 +24,7 @@ const REPLACEMENTS: &[(&str, &str)] = &[
|
|||
("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"),
|
||||
("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"),
|
||||
("graphene_core::ToGraphicGroupNode", "graphene_core::graphic_element::ToGroupNode"),
|
||||
// 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"),
|
||||
|
@ -60,47 +61,93 @@ const REPLACEMENTS: &[(&str, &str)] = &[
|
|||
("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"),
|
||||
// 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"),
|
||||
// ???
|
||||
("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::ops::ConstructVector2", "graphene_core::ops::CoordinateValueNode"),
|
||||
("graphene_core::ops::Vector2ValueNode", "graphene_core::ops::CoordinateValueNode"),
|
||||
("graphene_core::raster::BlackAndWhiteNode", "graphene_core::raster::adjustments::BlackAndWhiteNode"),
|
||||
("graphene_core::raster::BlendNode", "graphene_core::raster::adjustments::BlendNode"),
|
||||
("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::raster::ChannelMixerNode", "graphene_core::raster::adjustments::ChannelMixerNode"),
|
||||
("graphene_core::raster::adjustments::ColorOverlayNode", "graphene_core::raster::adjustments::ColorOverlayNode"),
|
||||
("graphene_core::raster::ExposureNode", "graphene_core::raster::adjustments::ExposureNode"),
|
||||
("graphene_core::raster::ExtractChannelNode", "graphene_core::raster::adjustments::ExtractChannelNode"),
|
||||
("graphene_core::raster::GradientMapNode", "graphene_core::raster::adjustments::GradientMapNode"),
|
||||
("graphene_core::raster::HueSaturationNode", "graphene_core::raster::adjustments::HueSaturationNode"),
|
||||
("graphene_core::vector::GenerateHandlesNode", "graphene_core::vector::AutoTangentsNode"),
|
||||
("graphene_core::vector::RemoveHandlesNode", "graphene_core::vector::AutoTangentsNode"),
|
||||
("graphene_core::raster::InvertNode", "graphene_core::raster::adjustments::InvertNode"),
|
||||
("graphene_core::raster::InvertRGBNode", "graphene_core::raster::adjustments::InvertNode"),
|
||||
("graphene_core::raster::LevelsNode", "graphene_core::raster::adjustments::LevelsNode"),
|
||||
("graphene_core::raster::LuminanceNode", "graphene_core::raster::adjustments::LuminanceNode"),
|
||||
("graphene_core::raster::ExtractOpaqueNode", "graphene_core::raster::adjustments::MakeOpaqueNode"),
|
||||
("graphene_core::raster::PosterizeNode", "graphene_core::raster::adjustments::PosterizeNode"),
|
||||
("graphene_core::raster::ThresholdNode", "graphene_core::raster::adjustments::ThresholdNode"),
|
||||
("graphene_core::raster::VibranceNode", "graphene_core::raster::adjustments::VibranceNode"),
|
||||
// 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"),
|
||||
// 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"),
|
||||
// text
|
||||
("graphene_core::text::TextGeneratorNode", "graphene_core::text::TextNode"),
|
||||
// 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"),
|
||||
// ???
|
||||
("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"),
|
||||
|
@ -116,6 +163,10 @@ const REPLACEMENTS: &[(&str, &str)] = &[
|
|||
("graphene_std::raster::MaskImageNode", "graphene_std::raster::MaskNode"),
|
||||
("graphene_core::vector::FlattenVectorElementsNode", "graphene_core::vector::FlattenPathNode"),
|
||||
("graphene_std::vector::BooleanOperationNode", "graphene_path_bool::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"),
|
||||
];
|
||||
|
||||
pub fn document_migration_string_preprocessing(document_serialized_content: String) -> String {
|
||||
|
@ -139,90 +190,37 @@ pub fn document_migration_reset_node_definition(document_serialized_content: &st
|
|||
}
|
||||
|
||||
pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) {
|
||||
let mut network = document.network_interface.document_network().clone();
|
||||
network.generate_node_paths(&[]);
|
||||
let network = document.network_interface.document_network().clone();
|
||||
|
||||
// Apply string replacements to each node
|
||||
let node_ids: Vec<_> = network.recursive_nodes().map(|(&id, node)| (id, node.original_location.path.clone().unwrap())).collect();
|
||||
for (node_id, path) in &node_ids {
|
||||
let network_path: Vec<_> = path.iter().copied().take(path.len() - 1).collect();
|
||||
|
||||
if let Some(DocumentNodeImplementation::ProtoNode(protonode_id)) = document
|
||||
.network_interface
|
||||
.nested_network(&network_path)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.get(node_id)
|
||||
.map(|node| node.implementation.clone())
|
||||
{
|
||||
for (node_id, node, network_path) in network.recursive_nodes() {
|
||||
if let DocumentNodeImplementation::ProtoNode(protonode_id) = &node.implementation {
|
||||
for (old, new) in REPLACEMENTS {
|
||||
let node_path_without_type_args = protonode_id.name.split('<').next();
|
||||
let mut default_template = NodeTemplate::default();
|
||||
default_template.document_node.implementation = DocumentNodeImplementation::ProtoNode(new.to_string().into());
|
||||
if node_path_without_type_args == Some(old) {
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation(node_id, &network_path, DocumentNodeImplementation::ProtoNode(new.to_string().into()));
|
||||
document.network_interface.replace_implementation(node_id, &network_path, &mut default_template);
|
||||
document.network_interface.set_manual_compostion(node_id, &network_path, Some(graph_craft::Type::Generic("T".into())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if reset_node_definitions_on_open {
|
||||
// This can be used, if uncommented, to upgrade demo artwork with outdated document node internals from their definitions. Delete when it's no longer needed.
|
||||
// Used for upgrading old internal networks for demo artwork nodes. Will reset all node internals for any opened file
|
||||
for node_id in &document
|
||||
.network_interface
|
||||
.document_network_metadata()
|
||||
.persistent_metadata
|
||||
.node_metadata
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect::<Vec<NodeId>>()
|
||||
{
|
||||
if let Some(reference) = document
|
||||
.network_interface
|
||||
.document_network_metadata()
|
||||
.persistent_metadata
|
||||
.node_metadata
|
||||
.get(node_id)
|
||||
.and_then(|node| node.persistent_metadata.reference.as_ref())
|
||||
{
|
||||
// Apply upgrades to each unmodified node.
|
||||
let nodes = document
|
||||
.network_interface
|
||||
.document_network()
|
||||
.recursive_nodes()
|
||||
.map(|(node_id, node, path)| (*node_id, node.clone(), path))
|
||||
.collect::<Vec<(NodeId, graph_craft::document::DocumentNode, Vec<NodeId>)>>();
|
||||
for (node_id, node, network_path) in &nodes {
|
||||
if reset_node_definitions_on_open {
|
||||
if let Some(Some(reference)) = document.network_interface.reference(node_id, network_path) {
|
||||
let Some(node_definition) = resolve_document_node_type(reference) else { continue };
|
||||
let default_definition_node = node_definition.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, &[], default_definition_node.document_node.implementation);
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, &[], default_definition_node.persistent_node_metadata);
|
||||
document.network_interface.set_manual_compostion(node_id, &[], default_definition_node.document_node.manual_composition);
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_definition.default_node_template());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if document
|
||||
.network_interface
|
||||
.document_network_metadata()
|
||||
.persistent_metadata
|
||||
.node_metadata
|
||||
.iter()
|
||||
.any(|(node_id, node)| node.persistent_metadata.reference.as_ref().is_some_and(|reference| reference == "Output") && *node_id == NodeId(0))
|
||||
{
|
||||
document.network_interface.delete_nodes(vec![NodeId(0)], true, &[]);
|
||||
}
|
||||
|
||||
let mut network = document.network_interface.document_network().clone();
|
||||
network.generate_node_paths(&[]);
|
||||
|
||||
let node_ids: Vec<_> = network.recursive_nodes().map(|(&id, node)| (id, node.original_location.path.clone().unwrap())).collect();
|
||||
|
||||
// Apply upgrades to each node
|
||||
for (node_id, path) in &node_ids {
|
||||
let network_path: Vec<_> = path.iter().copied().take(path.len() - 1).collect();
|
||||
let network_path = &network_path;
|
||||
|
||||
let Some(node) = document.network_interface.nested_network(network_path).unwrap().nodes.get(node_id).cloned() else {
|
||||
log::error!("could not get node in deserialize_document");
|
||||
continue;
|
||||
};
|
||||
|
||||
// Upgrade old nodes to use `Context` instead of `()` or `Footprint` for manual composition
|
||||
if node.manual_composition == Some(graph_craft::concrete!(())) || node.manual_composition == Some(graph_craft::concrete!(graphene_std::transform::Footprint)) {
|
||||
|
@ -231,87 +229,19 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
.set_manual_compostion(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into());
|
||||
}
|
||||
|
||||
let Some(node_metadata) = document.network_interface.network_metadata(network_path).unwrap().persistent_metadata.node_metadata.get(node_id) else {
|
||||
log::error!("could not get node metadata for node {node_id} in deserialize_document");
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(ref reference) = node_metadata.persistent_metadata.reference.clone() else {
|
||||
// TODO: Investigate if this should be an expected case, because currently it runs hundreds of times normally.
|
||||
// TODO: Either delete the commented out error below if this is normal, or fix the underlying issue if this is not expected.
|
||||
// log::error!("could not get reference in deserialize_document");
|
||||
let Some(Some(reference)) = document.network_interface.reference(node_id, network_path).cloned() else {
|
||||
// Only nodes that have not been modified and still refer to a definition can be updated
|
||||
continue;
|
||||
};
|
||||
let reference = &reference;
|
||||
|
||||
let inputs_count = node.inputs.len();
|
||||
|
||||
// Upgrade Fill nodes to the format change in #1778
|
||||
if reference == "Fill" && inputs_count == 8 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
|
||||
let Some(fill_type) = old_inputs[1].as_value().cloned() else { continue };
|
||||
let TaggedValue::FillType(fill_type) = fill_type else { continue };
|
||||
let Some(solid_color) = old_inputs[2].as_value().cloned() else { continue };
|
||||
let TaggedValue::OptionalColor(solid_color) = solid_color else { continue };
|
||||
let Some(gradient_type) = old_inputs[3].as_value().cloned() else { continue };
|
||||
let TaggedValue::GradientType(gradient_type) = gradient_type else { continue };
|
||||
let Some(start) = old_inputs[4].as_value().cloned() else { continue };
|
||||
let TaggedValue::DVec2(start) = start else { continue };
|
||||
let Some(end) = old_inputs[5].as_value().cloned() else { continue };
|
||||
let TaggedValue::DVec2(end) = end else { continue };
|
||||
let Some(transform) = old_inputs[6].as_value().cloned() else { continue };
|
||||
let TaggedValue::DAffine2(transform) = transform else { continue };
|
||||
let Some(positions) = old_inputs[7].as_value().cloned() else { continue };
|
||||
let TaggedValue::GradientStops(positions) = positions else { continue };
|
||||
|
||||
let fill = match (fill_type, solid_color) {
|
||||
(FillType::Solid, None) => Fill::None,
|
||||
(FillType::Solid, Some(color)) => Fill::Solid(color),
|
||||
(FillType::Gradient, _) => Fill::Gradient(Gradient {
|
||||
stops: positions,
|
||||
gradient_type,
|
||||
start,
|
||||
end,
|
||||
transform,
|
||||
}),
|
||||
};
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 1), NodeInput::value(TaggedValue::Fill(fill.clone()), false), network_path);
|
||||
match fill {
|
||||
Fill::None => {
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::OptionalColor(None), false), network_path);
|
||||
}
|
||||
Fill::Solid(color) => {
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 2), NodeInput::value(TaggedValue::OptionalColor(Some(color)), false), network_path);
|
||||
}
|
||||
Fill::Gradient(gradient) => {
|
||||
document
|
||||
.network_interface
|
||||
.set_input(&InputConnector::node(*node_id, 3), NodeInput::value(TaggedValue::Gradient(gradient), false), network_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644)
|
||||
if reference == "Stroke" && inputs_count == 8 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document.network_interface.insert_input_properties_row(node_id, 8, network_path);
|
||||
document.network_interface.insert_input_properties_row(node_id, 9, network_path);
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false);
|
||||
let paint_order_input = NodeInput::value(TaggedValue::PaintOrder(PaintOrder::StrokeAbove), false);
|
||||
|
||||
|
@ -402,12 +332,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
// 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 != 8 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
if reference == "Text" && inputs_count != 9 {
|
||||
let mut template = resolve_document_node_type(reference).unwrap().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).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
|
@ -433,23 +361,39 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 6),
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false),
|
||||
if inputs_count >= 7 {
|
||||
old_inputs[6].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 7),
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false),
|
||||
if inputs_count >= 8 {
|
||||
old_inputs[7].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
document.network_interface.set_input(
|
||||
&InputConnector::node(*node_id, 8),
|
||||
if inputs_count >= 9 {
|
||||
old_inputs[8].clone()
|
||||
} else {
|
||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false)
|
||||
},
|
||||
network_path,
|
||||
);
|
||||
}
|
||||
|
||||
// Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default
|
||||
if (reference == "Sine" || reference == "Cosine" || reference == "Tangent") && inputs_count == 1 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document
|
||||
|
@ -459,11 +403,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
|
||||
// Upgrade the Modulo node to include a boolean input for whether the output should be always positive, which was previously not an option
|
||||
if reference == "Modulo" && inputs_count == 2 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
|
@ -474,11 +417,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
|
||||
// Upgrade the Mirror node to add the `keep_original` boolean input
|
||||
if reference == "Mirror" && inputs_count == 3 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let document_node = node_definition.default_node_template().document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
|
@ -490,15 +432,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
|
||||
// Upgrade the Mirror node to add the `reference_point` input and change `offset` from `DVec2` to `f64`
|
||||
if reference == "Mirror" && inputs_count == 4 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
let Some(&TaggedValue::DVec2(old_offset)) = old_inputs[1].as_value() else { return };
|
||||
let old_offset = if old_offset.x.abs() > old_offset.y.abs() { old_offset.x } else { old_offset.y };
|
||||
|
@ -525,9 +462,8 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Image" && inputs_count == 1 {
|
||||
let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
|
||||
let new_image_node = node_definition.default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, new_image_node.document_node.implementation);
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
// Insert a new empty input for the image
|
||||
document.network_interface.add_import(TaggedValue::None, false, 0, "Empty", "", &[*node_id]);
|
||||
|
@ -535,13 +471,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Noise Pattern" && inputs_count == 15 {
|
||||
let node_definition = crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type(reference).unwrap();
|
||||
let new_noise_pattern_node = node_definition.default_node_template();
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation(node_id, network_path, new_noise_pattern_node.document_node.implementation);
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, new_noise_pattern_node.document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document
|
||||
.network_interface
|
||||
|
@ -552,30 +485,20 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Instance on Points" && inputs_count == 2 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
}
|
||||
|
||||
if reference == "Morph" && inputs_count == 4 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
|
@ -584,15 +507,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Brush" && inputs_count == 4 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
// We have removed the second input ("bounds"), so we don't add index 1 and we shift the rest of the inputs down by one
|
||||
|
@ -601,15 +519,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Flatten Vector Elements" {
|
||||
let node_definition = resolve_document_node_type("Flatten Path").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
|
||||
|
@ -617,15 +530,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Remove Handles" {
|
||||
let node_definition = resolve_document_node_type("Auto-Tangents").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document
|
||||
|
@ -639,15 +547,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Generate Handles" {
|
||||
let node_definition = resolve_document_node_type("Auto-Tangents").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type("Auto-Tangents").unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
|
@ -659,15 +562,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Merge by Distance" && inputs_count == 2 {
|
||||
let node_definition = resolve_document_node_type("Merge by Distance").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
|
@ -679,15 +577,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Spatial Merge by Distance" {
|
||||
let node_definition = resolve_document_node_type("Merge by Distance").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type("Merge by Distance").unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
|
@ -701,51 +594,88 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
|
|||
}
|
||||
|
||||
if reference == "Sample Points" && inputs_count == 5 {
|
||||
let node_definition = resolve_document_node_type("Sample Polyline").unwrap();
|
||||
let new_node_template = node_definition.default_node_template();
|
||||
let document_node = new_node_template.document_node;
|
||||
document.network_interface.replace_implementation(node_id, network_path, document_node.implementation.clone());
|
||||
document
|
||||
.network_interface
|
||||
.replace_implementation_metadata(node_id, network_path, new_node_template.persistent_node_metadata);
|
||||
let mut node_template = resolve_document_node_type("Sample Polyline").unwrap().default_node_template();
|
||||
document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
|
||||
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, document_node.inputs.clone(), network_path);
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap();
|
||||
let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false);
|
||||
let new_quantity_value = NodeInput::value(TaggedValue::U32(100), false);
|
||||
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), new_spacing_value, network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[2].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 6), old_inputs[4].clone(), network_path);
|
||||
|
||||
document.network_interface.replace_reference_name(node_id, network_path, "Sample Polyline".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// 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<_>>();
|
||||
for layer in all_layers {
|
||||
let Some((downstream_node, input_index)) = document
|
||||
.network_interface
|
||||
.outward_wires(&[])
|
||||
.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0)))
|
||||
.and_then(|outward_wires| outward_wires.first())
|
||||
.and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
// If the downstream node is a layer and the input is the first input and the current layer is not in a stack
|
||||
if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) {
|
||||
// Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files
|
||||
let (Some(layer_position), Some(downstream_position)) = (document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[])) else {
|
||||
log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node);
|
||||
// Make the "Quantity" parameter a u32 instead of f64
|
||||
if reference == "Sample Polyline" {
|
||||
// Get the inputs, obtain the quantity value, and put the inputs back
|
||||
let quantity_value = document
|
||||
.network_interface
|
||||
.input_from_connector(&InputConnector::Node { node_id: *node_id, input_index: 3 }, network_path)
|
||||
.unwrap();
|
||||
|
||||
if let NodeInput::Value { tagged_value, exposed } = quantity_value {
|
||||
if let TaggedValue::F64(value) = **tagged_value {
|
||||
let new_quantity_value = NodeInput::value(TaggedValue::U32(value as u32), *exposed);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), new_quantity_value, network_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make the "Grid" node, if its input of index 3 is a DVec2 for "angles" instead of a u32 for the "columns" input that now succeeds "angles", move the angle to index 5 (after "columns" and "rows")
|
||||
if reference == "Grid" && inputs_count == 6 {
|
||||
let node_definition = resolve_document_node_type(reference).unwrap();
|
||||
let mut new_node_template = node_definition.default_node_template();
|
||||
|
||||
let mut current_node_template = document.network_interface.create_node_template(node_id, network_path).unwrap();
|
||||
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template).unwrap();
|
||||
let index_3_value = old_inputs.get(3).cloned();
|
||||
|
||||
if let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value {
|
||||
if matches!(*tagged_value, TaggedValue::DVec2(_)) {
|
||||
// Move index 3 to the end
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 2), old_inputs[2].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path);
|
||||
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
|
||||
} else {
|
||||
// Swap it back if we're not changing anything
|
||||
let _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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<_>>();
|
||||
for layer in all_layers {
|
||||
let Some((downstream_node, input_index)) = document
|
||||
.network_interface
|
||||
.outward_wires(&[])
|
||||
.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0)))
|
||||
.and_then(|outward_wires| outward_wires.first())
|
||||
.and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if layer_position.x == downstream_position.x {
|
||||
document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]);
|
||||
// If the downstream node is a layer and the input is the first input and the current layer is not in a stack
|
||||
if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) {
|
||||
// Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files
|
||||
let (Some(layer_position), Some(downstream_position)) = (document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[])) else {
|
||||
log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node);
|
||||
continue;
|
||||
};
|
||||
if layer_position.x == downstream_position.x {
|
||||
document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
|||
use crate::messages::portfolio::document::DocumentMessageData;
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::utility_types::clipboards::{Clipboard, CopyBufferEntry, INTERNAL_CLIPBOARD_COUNT};
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::OutputConnector;
|
||||
use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
|
||||
use crate::messages::portfolio::document_migration::*;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
|
@ -426,6 +427,43 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||
// Upgrade the document's nodes to be compatible with the latest version
|
||||
document_migration_upgrades(&mut document, reset_node_definitions_on_open);
|
||||
|
||||
// Ensure each node has the metadata for its inputs
|
||||
for (node_id, node, path) in document.network_interface.document_network().clone().recursive_nodes() {
|
||||
document.network_interface.validate_input_metadata(node_id, node, &path);
|
||||
document.network_interface.validate_display_name_metadata(node_id, &path);
|
||||
document.network_interface.validate_output_names(node_id, node, &path);
|
||||
}
|
||||
|
||||
// Ensure layers are positioned as stacks if they are upstream siblings of another layer
|
||||
document.network_interface.load_structure();
|
||||
let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::<Vec<_>>();
|
||||
for layer in all_layers {
|
||||
let Some((downstream_node, input_index)) = document
|
||||
.network_interface
|
||||
.outward_wires(&[])
|
||||
.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0)))
|
||||
.and_then(|outward_wires| outward_wires.first())
|
||||
.and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
// If the downstream node is a layer and the input is the first input and the current layer is not in a stack
|
||||
if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) {
|
||||
// Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files
|
||||
let (Some(layer_position), Some(downstream_position)) =
|
||||
(document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[]))
|
||||
else {
|
||||
log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node);
|
||||
continue;
|
||||
};
|
||||
|
||||
if layer_position.x == downstream_position.x {
|
||||
document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the save state of the document based on what's given to us by the caller to this message
|
||||
document.set_auto_save_state(document_is_auto_saved);
|
||||
document.set_save_state(document_is_saved);
|
||||
|
@ -709,7 +747,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMes
|
|||
}
|
||||
|
||||
let Some(document) = self.documents.get_mut(&document_id) else {
|
||||
warn!("Tried to read non existant document");
|
||||
warn!("Tried to read non existent document");
|
||||
return;
|
||||
};
|
||||
if !document.is_loaded {
|
||||
|
@ -924,11 +962,11 @@ impl PortfolioMessageHandler {
|
|||
let result = self.executor.poll_node_graph_evaluation(active_document, responses);
|
||||
if result.is_err() {
|
||||
let error = r#"
|
||||
<rect x="50%" y="50%" width="480" height="100" transform="translate(-240 -50)" rx="4" fill="var(--color-error-red)" />
|
||||
<rect x="50%" y="50%" width="460" height="100" transform="translate(-230 -50)" rx="4" fill="var(--color-warning-yellow)" />
|
||||
<text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-size="18" fill="var(--color-2-mildblack)">
|
||||
<tspan x="50%" dy="-24" font-weight="bold">The document cannot be rendered in its current state.</tspan>
|
||||
<tspan x="50%" dy="24">Check for error details in the node graph, which can be</tspan>
|
||||
<tspan x="50%" dy="24">opened with the viewport's top right <tspan font-style="italic">Node Graph</tspan> button.</tspan>
|
||||
<tspan x="50%" dy="-24" font-weight="bold">The document cannot render in its current state.</tspan>
|
||||
<tspan x="50%" dy="24">Undo to go back, if available, or check for error details</tspan>
|
||||
<tspan x="50%" dy="24">by clicking the <tspan font-style="italic">Node Graph</tspan> button up at the top right.</tspan>
|
||||
/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();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle;
|
||||
use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::consts::VIEWPORT_ZOOM_WHEEL_RATE;
|
||||
use crate::messages::input_mapper::key_mapping::MappingVariant;
|
||||
use crate::messages::portfolio::document::node_graph::utility_types::GraphWireStyle;
|
||||
use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
|
||||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::prelude::*;
|
||||
use graph_craft::wasm_application_io::EditorPreferences;
|
||||
|
@ -86,7 +86,8 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
|||
}
|
||||
PreferencesMessage::GraphWireStyle { style } => {
|
||||
self.graph_wire_style = style;
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(NodeGraphMessage::UnloadWires);
|
||||
responses.add(NodeGraphMessage::SendWires);
|
||||
}
|
||||
PreferencesMessage::ViewportZoomWheelRate { rate } => {
|
||||
self.viewport_zoom_wheel_rate = rate;
|
||||
|
|
|
@ -361,6 +361,7 @@ pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInter
|
|||
let Some(&TaggedValue::F64(character_spacing)) = inputs[5].as_value() else { return None };
|
||||
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 typesetting = TypesettingConfig {
|
||||
font_size,
|
||||
|
@ -368,6 +369,7 @@ pub fn get_text(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInter
|
|||
max_width,
|
||||
character_spacing,
|
||||
max_height,
|
||||
tilt,
|
||||
};
|
||||
Some((text, font, typesetting))
|
||||
}
|
||||
|
|
|
@ -96,6 +96,14 @@ impl SelectedLayerState {
|
|||
self.selected_segments.remove(&segment);
|
||||
}
|
||||
|
||||
pub fn deselect_all_points_in_layer(&mut self) {
|
||||
self.selected_points.clear();
|
||||
}
|
||||
|
||||
pub fn deselect_all_segments_in_layer(&mut self) {
|
||||
self.selected_segments.clear();
|
||||
}
|
||||
|
||||
pub fn clear_points(&mut self) {
|
||||
self.selected_points.clear();
|
||||
}
|
||||
|
@ -388,6 +396,10 @@ impl ClosestSegment {
|
|||
|
||||
// TODO Consider keeping a list of selected manipulators to minimize traversals of the layers
|
||||
impl ShapeState {
|
||||
pub fn is_selected_layer(&self, layer: LayerNodeIdentifier) -> bool {
|
||||
self.selected_shape_state.contains_key(&layer)
|
||||
}
|
||||
|
||||
pub fn is_point_ignored(&self, point: &ManipulatorPointId) -> bool {
|
||||
(point.as_handle().is_some() && self.ignore_handles) || (point.as_anchor().is_some() && self.ignore_anchors)
|
||||
}
|
||||
|
@ -637,7 +649,7 @@ impl ShapeState {
|
|||
}
|
||||
|
||||
/// Selects all anchors connected to the selected subpath, and deselects all handles, for the given layer.
|
||||
pub fn select_connected_anchors(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, mouse: DVec2) {
|
||||
pub fn select_connected(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, mouse: DVec2, points: bool, segments: bool) {
|
||||
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
|
||||
return;
|
||||
};
|
||||
|
@ -655,18 +667,39 @@ impl ShapeState {
|
|||
}
|
||||
}
|
||||
state.clear_points();
|
||||
|
||||
if selected_stack.is_empty() {
|
||||
// Fall back on just selecting all points in the layer
|
||||
for &point in vector_data.point_domain.ids() {
|
||||
state.select_point(ManipulatorPointId::Anchor(point))
|
||||
// Fall back on just selecting all points/segments in the layer
|
||||
if points {
|
||||
for &point in vector_data.point_domain.ids() {
|
||||
state.select_point(ManipulatorPointId::Anchor(point));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Select all connected points
|
||||
while let Some(point) = selected_stack.pop() {
|
||||
let anchor_point = ManipulatorPointId::Anchor(point);
|
||||
if !state.is_point_selected(anchor_point) {
|
||||
state.select_point(anchor_point);
|
||||
selected_stack.extend(vector_data.connected_points(point));
|
||||
if segments {
|
||||
for &segment in vector_data.segment_domain.ids() {
|
||||
state.select_segment(segment);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let mut connected_points = HashSet::new();
|
||||
|
||||
while let Some(point) = selected_stack.pop() {
|
||||
if !connected_points.contains(&point) {
|
||||
connected_points.insert(point);
|
||||
selected_stack.extend(vector_data.connected_points(point));
|
||||
}
|
||||
}
|
||||
|
||||
if points {
|
||||
connected_points.iter().for_each(|point| state.select_point(ManipulatorPointId::Anchor(*point)));
|
||||
}
|
||||
|
||||
if segments {
|
||||
for (id, _, start, end) in vector_data.segment_bezier_iter() {
|
||||
if connected_points.contains(&start) || connected_points.contains(&end) {
|
||||
state.select_segment(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::messages::tool::utility_types::ToolType;
|
|||
use bezier_rs::{Bezier, BezierHandles};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::text::{FontCache, load_face};
|
||||
use graphene_std::text::{FontCache, load_font};
|
||||
use graphene_std::vector::{HandleExt, HandleId, ManipulatorPointId, PointId, SegmentId, VectorData, VectorModificationType};
|
||||
use kurbo::{CubicBez, Line, ParamCurveExtrema, PathSeg, Point, QuadBez};
|
||||
|
||||
|
@ -70,8 +70,8 @@ pub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageH
|
|||
return Quad::from_box([DVec2::ZERO, DVec2::ZERO]);
|
||||
};
|
||||
|
||||
let buzz_face = font_cache.get(font).map(|data| load_face(data));
|
||||
let far = graphene_std::text::bounding_box(text, buzz_face.as_ref(), typesetting, false);
|
||||
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])
|
||||
}
|
||||
|
|
|
@ -80,12 +80,12 @@ pub enum ToolMessage {
|
|||
Redo,
|
||||
RefreshToolOptions,
|
||||
ResetColors,
|
||||
SelectPrimaryColor {
|
||||
SelectWorkingColor {
|
||||
color: Color,
|
||||
primary: bool,
|
||||
},
|
||||
SelectRandomPrimaryColor,
|
||||
SelectSecondaryColor {
|
||||
color: Color,
|
||||
SelectRandomWorkingColor {
|
||||
primary: bool,
|
||||
},
|
||||
SwapColors,
|
||||
Undo,
|
||||
|
|
|
@ -240,14 +240,8 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
|
|||
|
||||
document_data.update_working_colors(responses); // TODO: Make this an event
|
||||
}
|
||||
ToolMessage::SelectPrimaryColor { color } => {
|
||||
let document_data = &mut self.tool_state.document_tool_data;
|
||||
document_data.primary_color = color;
|
||||
|
||||
document_data.update_working_colors(responses); // TODO: Make this an event
|
||||
}
|
||||
ToolMessage::SelectRandomPrimaryColor => {
|
||||
// Select a random primary color (rgba) based on an UUID
|
||||
ToolMessage::SelectRandomWorkingColor { primary } => {
|
||||
// Select a random working color (RGBA) based on an UUID
|
||||
let document_data = &mut self.tool_state.document_tool_data;
|
||||
|
||||
let random_number = generate_uuid();
|
||||
|
@ -255,13 +249,23 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
|
|||
let g = (random_number >> 8) as u8;
|
||||
let b = random_number as u8;
|
||||
let random_color = Color::from_rgba8_srgb(r, g, b, 255);
|
||||
document_data.primary_color = random_color;
|
||||
|
||||
if primary {
|
||||
document_data.primary_color = random_color;
|
||||
} else {
|
||||
document_data.secondary_color = random_color;
|
||||
}
|
||||
|
||||
document_data.update_working_colors(responses); // TODO: Make this an event
|
||||
}
|
||||
ToolMessage::SelectSecondaryColor { color } => {
|
||||
ToolMessage::SelectWorkingColor { color, primary } => {
|
||||
let document_data = &mut self.tool_state.document_tool_data;
|
||||
document_data.secondary_color = color;
|
||||
|
||||
if primary {
|
||||
document_data.primary_color = color;
|
||||
} else {
|
||||
document_data.secondary_color = color;
|
||||
}
|
||||
|
||||
document_data.update_working_colors(responses); // TODO: Make this an event
|
||||
}
|
||||
|
@ -340,7 +344,7 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
|
|||
|
||||
ActivateToolBrush,
|
||||
|
||||
SelectRandomPrimaryColor,
|
||||
SelectRandomWorkingColor,
|
||||
ResetColors,
|
||||
SwapColors,
|
||||
Undo,
|
||||
|
|
|
@ -8,8 +8,8 @@ use crate::messages::tool::common_functionality::color_selector::{ToolColorOptio
|
|||
use graph_craft::document::NodeId;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::brush::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::vector::brush_stroke::{BrushInputSample, BrushStroke, BrushStyle};
|
||||
|
||||
const BRUSH_MAX_SIZE: f64 = 5000.;
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use super::select_tool::extend_lasso;
|
||||
use super::tool_prelude::*;
|
||||
use crate::consts::{
|
||||
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE, SEGMENT_INSERTION_DISTANCE,
|
||||
SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE,
|
||||
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, DOUBLE_CLICK_MILLISECONDS, DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD, DRAG_THRESHOLD, HANDLE_ROTATE_SNAP_ANGLE,
|
||||
SEGMENT_INSERTION_DISTANCE, SEGMENT_OVERLAY_SIZE, SELECTION_THRESHOLD, SELECTION_TOLERANCE,
|
||||
};
|
||||
use crate::messages::portfolio::document::overlays::utility_functions::{path_overlays, selected_segments};
|
||||
use crate::messages::portfolio::document::overlays::utility_types::{DrawHandles, OverlayContext};
|
||||
|
@ -12,11 +12,11 @@ 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::shape_editor::{
|
||||
ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState,
|
||||
ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedLayerState, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState,
|
||||
};
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
|
||||
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate};
|
||||
use bezier_rs::{Bezier, TValue};
|
||||
use bezier_rs::{Bezier, BezierHandles, TValue};
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, VectorData};
|
||||
use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType};
|
||||
|
@ -58,7 +58,10 @@ pub enum PathToolMessage {
|
|||
},
|
||||
Escape,
|
||||
ClosePath,
|
||||
FlipSmoothSharp,
|
||||
DoubleClick {
|
||||
extend_selection: Key,
|
||||
shrink_selection: Key,
|
||||
},
|
||||
GRS {
|
||||
// Should be `Key::KeyG` (Grab), `Key::KeyR` (Rotate), or `Key::KeyS` (Scale)
|
||||
key: Key,
|
||||
|
@ -319,7 +322,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
|||
fn actions(&self) -> ActionList {
|
||||
match self.fsm_state {
|
||||
PathToolFsmState::Ready => actions!(PathToolMessageDiscriminant;
|
||||
FlipSmoothSharp,
|
||||
DoubleClick,
|
||||
MouseDown,
|
||||
Delete,
|
||||
NudgeSelectedPoints,
|
||||
|
@ -334,7 +337,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
|||
PathToolFsmState::Dragging(_) => actions!(PathToolMessageDiscriminant;
|
||||
Escape,
|
||||
RightClick,
|
||||
FlipSmoothSharp,
|
||||
DoubleClick,
|
||||
DragStop,
|
||||
PointerMove,
|
||||
Delete,
|
||||
|
@ -343,7 +346,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
|||
SwapSelectedHandles,
|
||||
),
|
||||
PathToolFsmState::Drawing { .. } => actions!(PathToolMessageDiscriminant;
|
||||
FlipSmoothSharp,
|
||||
DoubleClick,
|
||||
DragStop,
|
||||
PointerMove,
|
||||
Delete,
|
||||
|
@ -359,12 +362,6 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
|
|||
Escape,
|
||||
RightClick
|
||||
),
|
||||
PathToolFsmState::MoldingSegment => actions!(PathToolMessageDiscriminant;
|
||||
PointerMove,
|
||||
DragStop,
|
||||
RightClick,
|
||||
Escape,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -416,7 +413,6 @@ enum PathToolFsmState {
|
|||
selection_shape: SelectionShapeType,
|
||||
},
|
||||
SlidingPoint,
|
||||
MoldingSegment,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -462,6 +458,8 @@ struct PathToolData {
|
|||
adjacent_anchor_offset: Option<DVec2>,
|
||||
sliding_point_info: Option<SlidingPointInfo>,
|
||||
started_drawing_from_inside: bool,
|
||||
first_selected_with_single_click: bool,
|
||||
stored_selection: Option<HashMap<LayerNodeIdentifier, SelectedLayerState>>,
|
||||
}
|
||||
|
||||
impl PathToolData {
|
||||
|
@ -544,8 +542,9 @@ impl PathToolData {
|
|||
|
||||
self.drag_start_pos = input.mouse.position;
|
||||
|
||||
if !self.saved_points_before_anchor_convert_smooth_sharp.is_empty() && (input.time - self.last_click_time > 500) {
|
||||
if input.time - self.last_click_time > DOUBLE_CLICK_MILLISECONDS {
|
||||
self.saved_points_before_anchor_convert_smooth_sharp.clear();
|
||||
self.stored_selection = None;
|
||||
}
|
||||
|
||||
self.last_click_time = input.time;
|
||||
|
@ -675,30 +674,30 @@ impl PathToolData {
|
|||
responses.add(OverlaysMessage::Draw);
|
||||
PathToolFsmState::Dragging(self.dragging_state)
|
||||
} else {
|
||||
let handle1 = ManipulatorPointId::PrimaryHandle(segment.segment());
|
||||
let handle2 = ManipulatorPointId::EndHandle(segment.segment());
|
||||
if let Some(vector_data) = document.network_interface.compute_modified_vector(segment.layer()) {
|
||||
if let (Some(pos1), Some(pos2)) = (handle1.get_position(&vector_data), handle2.get_position(&vector_data)) {
|
||||
self.molding_info = Some((pos1, pos2))
|
||||
}
|
||||
}
|
||||
PathToolFsmState::MoldingSegment
|
||||
let start_pos = segment.bezier().start;
|
||||
let end_pos = segment.bezier().end;
|
||||
|
||||
let [pos1, pos2] = match segment.bezier().handles {
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [handle_start, handle_end],
|
||||
BezierHandles::Quadratic { handle } => [handle, end_pos],
|
||||
BezierHandles::Linear => [start_pos + (end_pos - start_pos) / 3., end_pos + (start_pos - end_pos) / 3.],
|
||||
};
|
||||
self.molding_info = Some((pos1, pos2));
|
||||
PathToolFsmState::Dragging(self.dragging_state)
|
||||
}
|
||||
}
|
||||
// We didn't find a segment, so consider selecting the nearest shape instead and start drawing
|
||||
// If no other layers are selected and this is a single-click, then also select the layer (exception)
|
||||
else if let Some(layer) = document.click(input) {
|
||||
shape_editor.deselect_all_points();
|
||||
shape_editor.deselect_all_segments();
|
||||
if extend_selection {
|
||||
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] });
|
||||
} else {
|
||||
if shape_editor.selected_shape_state.is_empty() {
|
||||
self.first_selected_with_single_click = true;
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
|
||||
}
|
||||
self.drag_start_pos = input.mouse.position;
|
||||
self.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);
|
||||
|
||||
self.started_drawing_from_inside = true;
|
||||
|
||||
self.drag_start_pos = input.mouse.position;
|
||||
self.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);
|
||||
|
||||
let selection_shape = if lasso_select { SelectionShapeType::Lasso } else { SelectionShapeType::Box };
|
||||
PathToolFsmState::Drawing { selection_shape }
|
||||
}
|
||||
|
@ -1501,7 +1500,6 @@ impl Fsm for PathToolFsmState {
|
|||
}
|
||||
}
|
||||
Self::SlidingPoint => {}
|
||||
Self::MoldingSegment => {}
|
||||
}
|
||||
|
||||
responses.add(PathToolMessage::SelectedPointUpdated);
|
||||
|
@ -1557,7 +1555,9 @@ impl Fsm for PathToolFsmState {
|
|||
},
|
||||
) => {
|
||||
tool_data.previous_mouse_position = document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position);
|
||||
|
||||
tool_data.started_drawing_from_inside = false;
|
||||
tool_data.stored_selection = None;
|
||||
|
||||
if selection_shape == SelectionShapeType::Lasso {
|
||||
extend_lasso(&mut tool_data.lasso_polygon, input.mouse.position);
|
||||
|
@ -1604,21 +1604,35 @@ impl Fsm for PathToolFsmState {
|
|||
break_colinear_molding,
|
||||
},
|
||||
) => {
|
||||
let mut selected_only_handles = true;
|
||||
|
||||
let selected_points = shape_editor.selected_points();
|
||||
|
||||
for point in selected_points {
|
||||
if matches!(point, ManipulatorPointId::Anchor(_)) {
|
||||
selected_only_handles = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let selected_only_handles = !shape_editor.selected_points().any(|point| matches!(point, ManipulatorPointId::Anchor(_)));
|
||||
tool_data.stored_selection = None;
|
||||
|
||||
if !tool_data.saved_points_before_handle_drag.is_empty() && (tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD) && (selected_only_handles) {
|
||||
tool_data.handle_drag_toggle = true;
|
||||
}
|
||||
|
||||
if tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD {
|
||||
tool_data.molding_segment = true;
|
||||
}
|
||||
|
||||
let break_molding = input.keyboard.get(break_colinear_molding as usize);
|
||||
|
||||
// Logic for molding segment
|
||||
if let Some(segment) = &mut tool_data.segment {
|
||||
if let Some(molding_segment_handles) = tool_data.molding_info {
|
||||
tool_data.temporary_adjacent_handles_while_molding = segment.mold_handle_positions(
|
||||
document,
|
||||
responses,
|
||||
molding_segment_handles,
|
||||
input.mouse.position,
|
||||
break_molding,
|
||||
tool_data.temporary_adjacent_handles_while_molding,
|
||||
);
|
||||
}
|
||||
|
||||
return PathToolFsmState::Dragging(tool_data.dragging_state);
|
||||
}
|
||||
|
||||
let anchor_and_handle_toggled = input.keyboard.get(move_anchor_with_handles as usize);
|
||||
let initial_press = anchor_and_handle_toggled && !tool_data.select_anchor_toggled;
|
||||
let released_from_toggle = tool_data.select_anchor_toggled && !anchor_and_handle_toggled;
|
||||
|
@ -1694,29 +1708,6 @@ impl Fsm for PathToolFsmState {
|
|||
tool_data.slide_point(input.mouse.position, responses, &document.network_interface, shape_editor);
|
||||
PathToolFsmState::SlidingPoint
|
||||
}
|
||||
(PathToolFsmState::MoldingSegment, PathToolMessage::PointerMove { break_colinear_molding, .. }) => {
|
||||
if tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD {
|
||||
tool_data.molding_segment = true;
|
||||
}
|
||||
|
||||
let break_colinear_molding = input.keyboard.get(break_colinear_molding as usize);
|
||||
|
||||
// Logic for molding segment
|
||||
if let Some(segment) = &mut tool_data.segment {
|
||||
if let Some(molding_segment_handles) = tool_data.molding_info {
|
||||
tool_data.temporary_adjacent_handles_while_molding = segment.mold_handle_positions(
|
||||
document,
|
||||
responses,
|
||||
molding_segment_handles,
|
||||
input.mouse.position,
|
||||
break_colinear_molding,
|
||||
tool_data.temporary_adjacent_handles_while_molding,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PathToolFsmState::MoldingSegment
|
||||
}
|
||||
(PathToolFsmState::Ready, PathToolMessage::PointerMove { delete_segment, .. }) => {
|
||||
tool_data.delete_segment_pressed = input.keyboard.get(delete_segment as usize);
|
||||
|
||||
|
@ -1727,6 +1718,7 @@ impl Fsm for PathToolFsmState {
|
|||
if tool_data.adjacent_anchor_offset.is_some() {
|
||||
tool_data.adjacent_anchor_offset = None;
|
||||
}
|
||||
tool_data.stored_selection = None;
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
||||
|
@ -1847,6 +1839,9 @@ impl Fsm for PathToolFsmState {
|
|||
tool_data.saved_points_before_handle_drag.clear();
|
||||
tool_data.handle_drag_toggle = false;
|
||||
}
|
||||
tool_data.molding_info = None;
|
||||
tool_data.molding_segment = false;
|
||||
tool_data.temporary_adjacent_handles_while_molding = None;
|
||||
tool_data.angle_locked = false;
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
@ -1864,17 +1859,6 @@ impl Fsm for PathToolFsmState {
|
|||
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(PathToolFsmState::MoldingSegment, PathToolMessage::Escape | PathToolMessage::RightClick) => {
|
||||
// Undo the molding and go back to the state before
|
||||
tool_data.molding_info = None;
|
||||
tool_data.molding_segment = false;
|
||||
tool_data.temporary_adjacent_handles_while_molding = None;
|
||||
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
tool_data.snap_manager.cleanup(responses);
|
||||
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
// Mouse up
|
||||
(PathToolFsmState::Drawing { selection_shape }, PathToolMessage::DragStop { extend_selection, shrink_selection }) => {
|
||||
let extend_selection = input.keyboard.get(extend_selection as usize);
|
||||
|
@ -1895,12 +1879,16 @@ impl Fsm for PathToolFsmState {
|
|||
SelectionMode::Directional => tool_data.calculate_selection_mode_from_direction(document.metadata()),
|
||||
selection_mode => selection_mode,
|
||||
};
|
||||
tool_data.started_drawing_from_inside = false;
|
||||
|
||||
if tool_data.drag_start_pos.distance(previous_mouse) < 1e-8 {
|
||||
// If click happens inside of a shape then don't set selected nodes to empty
|
||||
if document.click(input).is_none() {
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
||||
// Clicked inside or outside the shape then deselect all of the points/segments
|
||||
if document.click(input).is_some() && tool_data.stored_selection.is_none() {
|
||||
tool_data.stored_selection = Some(shape_editor.selected_shape_state.clone());
|
||||
}
|
||||
|
||||
shape_editor.deselect_all_points();
|
||||
shape_editor.deselect_all_segments();
|
||||
} else {
|
||||
match selection_shape {
|
||||
SelectionShapeType::Box => {
|
||||
|
@ -2072,8 +2060,8 @@ impl Fsm for PathToolFsmState {
|
|||
shape_editor.delete_point_and_break_path(document, responses);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::FlipSmoothSharp) => {
|
||||
// Double-clicked on a point
|
||||
(_, PathToolMessage::DoubleClick { extend_selection, shrink_selection }) => {
|
||||
// Double-clicked on a point (flip smooth/sharp behavior)
|
||||
let nearest_point = shape_editor.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD);
|
||||
if nearest_point.is_some() {
|
||||
// Flip the selected point between smooth and sharp
|
||||
|
@ -2090,13 +2078,70 @@ impl Fsm for PathToolFsmState {
|
|||
|
||||
return PathToolFsmState::Ready;
|
||||
}
|
||||
|
||||
// Double-clicked on a filled region
|
||||
if let Some(layer) = document.click(input) {
|
||||
// Select all points in the layer
|
||||
shape_editor.select_connected_anchors(document, layer, input.mouse.position);
|
||||
else if let Some(layer) = document.click(input) {
|
||||
let extend_selection = input.keyboard.get(extend_selection as usize);
|
||||
let shrink_selection = input.keyboard.get(shrink_selection as usize);
|
||||
|
||||
if shape_editor.is_selected_layer(layer) {
|
||||
if extend_selection && !tool_data.first_selected_with_single_click {
|
||||
responses.add(NodeGraphMessage::SelectedNodesRemove { nodes: vec![layer.to_node()] });
|
||||
|
||||
if let Some(selection) = &tool_data.stored_selection {
|
||||
let mut selection = selection.clone();
|
||||
selection.remove(&layer);
|
||||
shape_editor.selected_shape_state = selection;
|
||||
tool_data.stored_selection = None;
|
||||
}
|
||||
} else if shrink_selection && !tool_data.first_selected_with_single_click {
|
||||
// Only deselect all the points of the double clicked layer
|
||||
if let Some(selection) = &tool_data.stored_selection {
|
||||
let selection = selection.clone();
|
||||
shape_editor.selected_shape_state = selection;
|
||||
tool_data.stored_selection = None;
|
||||
}
|
||||
|
||||
let state = shape_editor.selected_shape_state.get_mut(&layer).expect("No state for selected layer");
|
||||
state.deselect_all_points_in_layer();
|
||||
state.deselect_all_segments_in_layer();
|
||||
} else if !tool_data.first_selected_with_single_click {
|
||||
// Select according to the selected editing mode
|
||||
let point_editing_mode = tool_options.path_editing_mode.point_editing_mode;
|
||||
let segment_editing_mode = tool_options.path_editing_mode.segment_editing_mode;
|
||||
shape_editor.select_connected(document, layer, input.mouse.position, point_editing_mode, segment_editing_mode);
|
||||
|
||||
// Select all the other layers back again
|
||||
if let Some(selection) = &tool_data.stored_selection {
|
||||
let mut selection = selection.clone();
|
||||
selection.remove(&layer);
|
||||
|
||||
for (layer, state) in selection {
|
||||
shape_editor.selected_shape_state.insert(layer, state);
|
||||
}
|
||||
tool_data.stored_selection = None;
|
||||
}
|
||||
}
|
||||
|
||||
// If it was the very first click without there being an existing selection,
|
||||
// then the single-click behavior and double-click behavior should not collide
|
||||
tool_data.first_selected_with_single_click = false;
|
||||
} else if extend_selection {
|
||||
responses.add(NodeGraphMessage::SelectedNodesAdd { nodes: vec![layer.to_node()] });
|
||||
|
||||
if let Some(selection) = &tool_data.stored_selection {
|
||||
shape_editor.selected_shape_state = selection.clone();
|
||||
tool_data.stored_selection = None;
|
||||
}
|
||||
} else {
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![layer.to_node()] });
|
||||
}
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
// Double clicked on the background
|
||||
else {
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
|
||||
}
|
||||
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
|
@ -2529,7 +2574,40 @@ fn update_dynamic_hints(
|
|||
dragging_hint_data.0.push(HintGroup(hold_group));
|
||||
}
|
||||
|
||||
dragging_hint_data
|
||||
if tool_data.molding_segment {
|
||||
let mut has_colinear_anchors = false;
|
||||
|
||||
if let Some(segment) = &tool_data.segment {
|
||||
let handle1 = HandleId::primary(segment.segment());
|
||||
let handle2 = HandleId::end(segment.segment());
|
||||
|
||||
if let Some(vector_data) = document.network_interface.compute_modified_vector(segment.layer()) {
|
||||
let other_handle1 = vector_data.other_colinear_handle(handle1);
|
||||
let other_handle2 = vector_data.other_colinear_handle(handle2);
|
||||
if other_handle1.is_some() || other_handle2.is_some() {
|
||||
has_colinear_anchors = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let handles_stored = if let Some(other_handles) = tool_data.temporary_adjacent_handles_while_molding {
|
||||
other_handles[0].is_some() || other_handles[1].is_some()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let molding_disable_possible = has_colinear_anchors || handles_stored;
|
||||
|
||||
let mut molding_hints = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])];
|
||||
|
||||
if molding_disable_possible {
|
||||
molding_hints.push(HintGroup(vec![HintInfo::keys([Key::Alt], "Break Colinear Handles")]));
|
||||
}
|
||||
|
||||
HintData(molding_hints)
|
||||
} else {
|
||||
dragging_hint_data
|
||||
}
|
||||
}
|
||||
PathToolFsmState::Drawing { .. } => HintData(vec![
|
||||
HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()]),
|
||||
|
@ -2539,38 +2617,6 @@ fn update_dynamic_hints(
|
|||
HintInfo::keys([Key::Alt], "Subtract").prepend_plus(),
|
||||
]),
|
||||
]),
|
||||
PathToolFsmState::MoldingSegment => {
|
||||
let mut has_colinear_anchors = false;
|
||||
|
||||
if let Some(segment) = &tool_data.segment {
|
||||
let handle1 = HandleId::primary(segment.segment());
|
||||
let handle2 = HandleId::end(segment.segment());
|
||||
|
||||
if let Some(vector_data) = document.network_interface.compute_modified_vector(segment.layer()) {
|
||||
let other_handle1 = vector_data.other_colinear_handle(handle1);
|
||||
let other_handle2 = vector_data.other_colinear_handle(handle2);
|
||||
if other_handle1.is_some() || other_handle2.is_some() {
|
||||
has_colinear_anchors = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let handles_stored = if let Some(other_handles) = tool_data.temporary_adjacent_handles_while_molding {
|
||||
other_handles[0].is_some() || other_handles[1].is_some()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let molding_disable_possible = has_colinear_anchors || handles_stored;
|
||||
|
||||
let mut molding_hints = vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])];
|
||||
|
||||
if molding_disable_possible {
|
||||
molding_hints.push(HintGroup(vec![HintInfo::keys([Key::Alt], "Break Colinear Handles")]));
|
||||
}
|
||||
|
||||
HintData(molding_hints)
|
||||
}
|
||||
PathToolFsmState::SlidingPoint => HintData(vec![HintGroup(vec![HintInfo::mouse(MouseMotion::Rmb, ""), HintInfo::keys([Key::Escape], "Cancel").prepend_slash()])]),
|
||||
};
|
||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||
|
|
|
@ -18,7 +18,7 @@ use graph_craft::document::value::TaggedValue;
|
|||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::text::{Font, FontCache, TypesettingConfig, lines_clipping, load_face};
|
||||
use graphene_std::text::{Font, FontCache, TypesettingConfig, lines_clipping, load_font};
|
||||
use graphene_std::vector::style::Fill;
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -35,6 +35,7 @@ pub struct TextOptions {
|
|||
font_name: String,
|
||||
font_style: String,
|
||||
fill: ToolColorOptions,
|
||||
tilt: f64,
|
||||
}
|
||||
|
||||
impl Default for TextOptions {
|
||||
|
@ -42,10 +43,11 @@ impl Default for TextOptions {
|
|||
Self {
|
||||
font_size: 24.,
|
||||
line_height_ratio: 1.2,
|
||||
character_spacing: 1.,
|
||||
character_spacing: 0.,
|
||||
font_name: graphene_std::consts::DEFAULT_FONT_FAMILY.into(),
|
||||
font_style: graphene_std::consts::DEFAULT_FONT_STYLE.into(),
|
||||
fill: ToolColorOptions::new_primary(),
|
||||
tilt: 0.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -468,8 +470,8 @@ impl Fsm for TextToolFsmState {
|
|||
transform: document.metadata().transform_to_viewport(tool_data.layer).to_cols_array(),
|
||||
});
|
||||
if let Some(editing_text) = tool_data.editing_text.as_mut() {
|
||||
let buzz_face = font_cache.get(&editing_text.font).map(|data| load_face(data));
|
||||
let far = graphene_std::text::bounding_box(&tool_data.new_text, buzz_face.as_ref(), editing_text.typesetting, false);
|
||||
let font_data = font_cache.get(&editing_text.font).map(|data| load_font(data));
|
||||
let far = graphene_std::text::bounding_box(&tool_data.new_text, font_data, editing_text.typesetting, false);
|
||||
if far.x != 0. && far.y != 0. {
|
||||
let quad = Quad::from_box([DVec2::ZERO, far]);
|
||||
let transformed_quad = document.metadata().transform_to_viewport(tool_data.layer) * quad;
|
||||
|
@ -517,8 +519,8 @@ impl Fsm for TextToolFsmState {
|
|||
// 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) {
|
||||
let buzz_face = font_cache.get(font).map(|data| load_face(data));
|
||||
if lines_clipping(text.as_str(), buzz_face, typesetting) {
|
||||
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.));
|
||||
}
|
||||
}
|
||||
|
@ -784,6 +786,7 @@ impl Fsm for TextToolFsmState {
|
|||
max_width: constraint_size.map(|size| size.x),
|
||||
character_spacing: tool_options.character_spacing,
|
||||
max_height: constraint_size.map(|size| size.y),
|
||||
tilt: tool_options.tilt,
|
||||
},
|
||||
font: Font::new(tool_options.font_name.clone(), tool_options.font_style.clone()),
|
||||
color: tool_options.fill.active_color(),
|
||||
|
|
|
@ -12,10 +12,11 @@ pub enum TransformLayerMessage {
|
|||
|
||||
// Messages
|
||||
ApplyTransformOperation { final_transform: bool },
|
||||
BeginTransformOperation { operation: TransformType },
|
||||
BeginGrab,
|
||||
BeginRotate,
|
||||
BeginScale,
|
||||
BeginGRS { transform_type: TransformType },
|
||||
BeginGRS { operation: TransformType },
|
||||
BeginGrabPen { last_point: DVec2, handle: DVec2 },
|
||||
BeginRotatePen { last_point: DVec2, handle: DVec2 },
|
||||
BeginScalePen { last_point: DVec2, handle: DVec2 },
|
||||
|
|
|
@ -368,6 +368,15 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
|||
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
|
||||
}
|
||||
}
|
||||
TransformLayerMessage::BeginTransformOperation { operation } => {
|
||||
begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse, &mut self.initial_transform);
|
||||
self.transform_operation = match operation {
|
||||
TransformType::Grab => TransformOperation::Grabbing(Default::default()),
|
||||
TransformType::Rotate => TransformOperation::Rotating(Default::default()),
|
||||
TransformType::Scale => TransformOperation::Scaling(Default::default()),
|
||||
};
|
||||
self.layer_bounding_box = selected.bounding_box();
|
||||
}
|
||||
TransformLayerMessage::BeginGrabPen { last_point, handle } | TransformLayerMessage::BeginRotatePen { last_point, handle } | TransformLayerMessage::BeginScalePen { last_point, handle } => {
|
||||
self.typing.clear();
|
||||
|
||||
|
@ -402,9 +411,10 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
|||
increments_key: INCREMENTS_KEY,
|
||||
});
|
||||
}
|
||||
TransformLayerMessage::BeginGRS { transform_type } => {
|
||||
TransformLayerMessage::BeginGRS { operation: transform_type } => {
|
||||
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()
|
||||
|
@ -439,42 +449,24 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
|||
}
|
||||
}
|
||||
|
||||
self.local = false;
|
||||
self.operation_count += 1;
|
||||
|
||||
let chain_operation = self.transform_operation != TransformOperation::None;
|
||||
if chain_operation {
|
||||
responses.add(TransformLayerMessage::ApplyTransformOperation { final_transform: false });
|
||||
} else {
|
||||
responses.add(OverlaysMessage::AddProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
|
||||
}
|
||||
|
||||
let response = match transform_type {
|
||||
TransformType::Grab => TransformLayerMessage::BeginGrab,
|
||||
TransformType::Rotate => TransformLayerMessage::BeginRotate,
|
||||
TransformType::Scale => TransformLayerMessage::BeginScale,
|
||||
};
|
||||
|
||||
self.local = false;
|
||||
self.operation_count += 1;
|
||||
responses.add(response);
|
||||
responses.add(TransformLayerMessage::BeginTransformOperation { operation: transform_type });
|
||||
responses.add(TransformLayerMessage::PointerMove {
|
||||
slow_key: SLOW_KEY,
|
||||
increments_key: INCREMENTS_KEY,
|
||||
});
|
||||
}
|
||||
TransformLayerMessage::BeginGrab => {
|
||||
begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse, &mut self.initial_transform);
|
||||
self.transform_operation = TransformOperation::Grabbing(Default::default());
|
||||
self.layer_bounding_box = selected.bounding_box();
|
||||
}
|
||||
TransformLayerMessage::BeginRotate => {
|
||||
begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse, &mut self.initial_transform);
|
||||
self.transform_operation = TransformOperation::Rotating(Default::default());
|
||||
self.layer_bounding_box = selected.bounding_box();
|
||||
}
|
||||
TransformLayerMessage::BeginScale => {
|
||||
begin_operation(self.transform_operation, &mut self.typing, &mut self.mouse_position, &mut self.start_mouse, &mut self.initial_transform);
|
||||
self.transform_operation = TransformOperation::Scaling(Default::default());
|
||||
self.layer_bounding_box = selected.bounding_box();
|
||||
}
|
||||
TransformLayerMessage::BeginGrab => responses.add_front(TransformLayerMessage::BeginGRS { operation: TransformType::Grab }),
|
||||
TransformLayerMessage::BeginRotate => responses.add_front(TransformLayerMessage::BeginGRS { operation: TransformType::Rotate }),
|
||||
TransformLayerMessage::BeginScale => responses.add_front(TransformLayerMessage::BeginGRS { operation: TransformType::Scale }),
|
||||
TransformLayerMessage::CancelTransformOperation => {
|
||||
if using_pen_tool {
|
||||
self.typing.clear();
|
||||
|
@ -707,7 +699,9 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
|
|||
|
||||
fn actions(&self) -> ActionList {
|
||||
let mut common = actions!(TransformLayerMessageDiscriminant;
|
||||
BeginGRS,
|
||||
BeginGrab,
|
||||
BeginRotate,
|
||||
BeginScale,
|
||||
);
|
||||
|
||||
if self.transform_operation != TransformOperation::None {
|
||||
|
|
|
@ -172,9 +172,10 @@ impl EditorTestUtils {
|
|||
pub fn get_node<'a, T: InputAccessor<'a, DocumentNode>>(&'a self) -> impl Iterator<Item = T> + 'a {
|
||||
self.active_document()
|
||||
.network_interface
|
||||
.iter_recursive()
|
||||
.inspect(|node| println!("{:#?}", node.1.implementation))
|
||||
.filter_map(move |(_, document)| T::new_with_source(document))
|
||||
.document_network()
|
||||
.recursive_nodes()
|
||||
.inspect(|(_, node, _)| println!("{:#?}", node.implementation))
|
||||
.filter_map(move |(_, document, _)| T::new_with_source(document))
|
||||
}
|
||||
|
||||
pub async fn move_mouse(&mut self, x: f64, y: f64, modifier_keys: ModifierKeys, mouse_keys: MouseKeys) {
|
||||
|
@ -227,11 +228,11 @@ impl EditorTestUtils {
|
|||
}
|
||||
|
||||
pub async fn select_primary_color(&mut self, color: Color) {
|
||||
self.handle_message(Message::Tool(ToolMessage::SelectPrimaryColor { color })).await;
|
||||
self.handle_message(Message::Tool(ToolMessage::SelectWorkingColor { color, primary: true })).await;
|
||||
}
|
||||
|
||||
pub async fn select_secondary_color(&mut self, color: Color) {
|
||||
self.handle_message(Message::Tool(ToolMessage::SelectSecondaryColor { color })).await;
|
||||
self.handle_message(Message::Tool(ToolMessage::SelectWorkingColor { color, primary: false })).await;
|
||||
}
|
||||
|
||||
pub async fn create_raster_image(&mut self, image: graphene_std::raster::Image<Color>, mouse: Option<(f64, f64)>) {
|
||||
|
@ -300,7 +301,7 @@ pub trait FrontendMessageTestUtils {
|
|||
|
||||
impl FrontendMessageTestUtils for FrontendMessage {
|
||||
fn check_node_graph_error(&self) {
|
||||
let FrontendMessage::UpdateNodeGraph { nodes, .. } = self else { return };
|
||||
let FrontendMessage::UpdateNodeGraphNodes { nodes, .. } = self else { return };
|
||||
|
||||
for node in nodes {
|
||||
if let Some(error) = &node.errors {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
The Graphite frontend is a web app that provides the presentation for the editor. It displays the GUI based on state from the backend and provides users with interactive widgets that send updates to the backend, which is the source of truth for state information. The frontend is built out of reactive components using the [Svelte](https://svelte.dev/) framework. The backend is written in Rust and compiled to WebAssembly (WASM) to be run in the browser alongside the JS code.
|
||||
|
||||
For lack of other options, the frontend is currently written as a web app. Maintaining web compatibility will always be a requirement, but the long-term plan is to port this code to a Rust-based native GUI framework, either written by the Rust community or created by our project if necessary. As a medium-term compromise, we may wrap the web-based frontend in a desktop webview windowing solution like Electron (probably not) or [Tauri](https://tauri.studio/) (probably).
|
||||
For lack of other options, the frontend is currently written as a web app. Maintaining web compatibility will always be a requirement, but the long-term plan is to port this code to a Rust-based native GUI framework, either written by the Rust community or created by our project if necessary. As a medium-term compromise, we may wrap the web-based frontend in a desktop webview windowing solution like Electron (probably not) or [Tauri](https://tauri.app/) (probably).
|
||||
|
||||
## Bundled assets: `assets/`
|
||||
|
||||
|
|
|
@ -104,6 +104,8 @@
|
|||
--color-f-white-rgb: 255, 255, 255;
|
||||
--color-error-red: #d6536e;
|
||||
--color-error-red-rgb: 214, 83, 110;
|
||||
--color-warning-yellow: #d5aa43;
|
||||
--color-warning-yellow-rgb: 213, 170, 67;
|
||||
|
||||
--color-data-general: #cfcfcf;
|
||||
--color-data-general-dim: #8a8a8a;
|
||||
|
|
|
@ -760,6 +760,8 @@
|
|||
|
||||
.text-input {
|
||||
word-break: break-all;
|
||||
unicode-bidi: plaintext;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-input div {
|
||||
|
@ -773,6 +775,8 @@
|
|||
overflow-wrap: anywhere;
|
||||
white-space: pre-wrap;
|
||||
word-break: normal;
|
||||
unicode-bidi: plaintext;
|
||||
text-align: left;
|
||||
display: inline-block;
|
||||
// Workaround to force Chrome to display the flashing text entry cursor when text is empty
|
||||
padding-left: 1px;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<script lang="ts">
|
||||
import { getContext, onMount, tick } from "svelte";
|
||||
import { getContext } from "svelte";
|
||||
import { cubicInOut } from "svelte/easing";
|
||||
import { fade } from "svelte/transition";
|
||||
|
||||
import type { Editor } from "@graphite/editor";
|
||||
import type { Node } from "@graphite/messages";
|
||||
import type { FrontendNodeWire, FrontendNode, FrontendGraphInput, FrontendGraphOutput, FrontendGraphDataType, WirePath } from "@graphite/messages";
|
||||
import type { FrontendNode, FrontendGraphInput, FrontendGraphOutput } from "@graphite/messages";
|
||||
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
|
||||
import type { IconName } from "@graphite/utility-functions/icons";
|
||||
|
||||
|
@ -27,26 +27,13 @@
|
|||
const nodeGraph = getContext<NodeGraphState>("nodeGraph");
|
||||
|
||||
let graph: HTMLDivElement | undefined;
|
||||
let nodesContainer: HTMLDivElement | undefined;
|
||||
|
||||
// TODO: Using this not-complete code, or another better approach, make it so the dragged in-progress connector correctly handles showing/hiding the SVG shape of the connector caps
|
||||
// let wireInProgressFromLayerTop: bigint | undefined = undefined;
|
||||
// let wireInProgressFromLayerBottom: bigint | undefined = undefined;
|
||||
|
||||
let nodeWirePaths: WirePath[] = [];
|
||||
|
||||
// TODO: Convert these arrays-of-arrays to a Map?
|
||||
let inputs: SVGSVGElement[][] = [];
|
||||
let outputs: SVGSVGElement[][] = [];
|
||||
let nodeElements: HTMLDivElement[] = [];
|
||||
|
||||
$: watchNodes($nodeGraph.nodes);
|
||||
// Key value is node id + input/output index
|
||||
// Imports/Export are stored at a key value of 0
|
||||
|
||||
$: gridSpacing = calculateGridSpacing($nodeGraph.transform.scale);
|
||||
$: dotRadius = 1 + Math.floor($nodeGraph.transform.scale - 0.5 + 0.001) / 2;
|
||||
|
||||
$: wirePaths = createWirePaths($nodeGraph.wirePathInProgress, nodeWirePaths);
|
||||
|
||||
let inputElement: HTMLInputElement;
|
||||
let hoveringImportIndex: number | undefined = undefined;
|
||||
let hoveringExportIndex: number | undefined = undefined;
|
||||
|
@ -121,80 +108,6 @@
|
|||
return sparse;
|
||||
}
|
||||
|
||||
function createWirePaths(wirePathInProgress: WirePath | undefined, nodeWirePaths: WirePath[]): WirePath[] {
|
||||
const maybeWirePathInProgress = wirePathInProgress ? [wirePathInProgress] : [];
|
||||
return [...maybeWirePathInProgress, ...nodeWirePaths];
|
||||
}
|
||||
|
||||
async function watchNodes(nodes: Map<bigint, FrontendNode>) {
|
||||
Array.from(nodes.keys()).forEach((_, index) => {
|
||||
if (!inputs[index + 1]) inputs[index + 1] = [];
|
||||
if (!outputs[index + 1]) outputs[index + 1] = [];
|
||||
});
|
||||
if (!inputs[0]) inputs[0] = [];
|
||||
if (!outputs[0]) outputs[0] = [];
|
||||
|
||||
await refreshWires();
|
||||
}
|
||||
|
||||
function resolveWire(wire: FrontendNodeWire): { nodeOutput: SVGSVGElement; nodeInput: SVGSVGElement } | undefined {
|
||||
// TODO: Avoid the linear search
|
||||
const wireStartNodeIdIndex = Array.from($nodeGraph.nodes.keys()).findIndex((nodeId) => nodeId === (wire.wireStart as Node).nodeId);
|
||||
let nodeOutputConnectors = outputs[wireStartNodeIdIndex + 1];
|
||||
if (nodeOutputConnectors === undefined && (wire.wireStart as Node).nodeId === undefined) {
|
||||
nodeOutputConnectors = outputs[0];
|
||||
}
|
||||
const indexOutput = Number(wire.wireStart.index);
|
||||
const nodeOutput = nodeOutputConnectors?.[indexOutput] as SVGSVGElement | undefined;
|
||||
if (nodeOutput === undefined) return undefined;
|
||||
|
||||
// TODO: Avoid the linear search
|
||||
const wireEndNodeIdIndex = Array.from($nodeGraph.nodes.keys()).findIndex((nodeId) => nodeId === (wire.wireEnd as Node).nodeId);
|
||||
let nodeInputConnectors = inputs[wireEndNodeIdIndex + 1] || undefined;
|
||||
if (nodeInputConnectors === undefined && (wire.wireEnd as Node).nodeId === undefined) {
|
||||
nodeInputConnectors = inputs[0];
|
||||
}
|
||||
const indexInput = Number(wire.wireEnd.index);
|
||||
const nodeInput = nodeInputConnectors?.[indexInput] as SVGSVGElement | undefined;
|
||||
if (nodeInput === undefined) return undefined;
|
||||
|
||||
return { nodeOutput, nodeInput };
|
||||
}
|
||||
|
||||
function createWirePath(outputPort: SVGSVGElement, inputPort: SVGSVGElement, verticalOut: boolean, verticalIn: boolean, dashed: boolean, directNotGridAligned: boolean): WirePath {
|
||||
const inputPortRect = inputPort.getBoundingClientRect();
|
||||
const outputPortRect = outputPort.getBoundingClientRect();
|
||||
|
||||
const pathString = directNotGridAligned
|
||||
? buildCurvedWirePathString(outputPortRect, inputPortRect, verticalOut, verticalIn)
|
||||
: buildStraightWirePathString(outputPortRect, inputPortRect, verticalOut, verticalIn);
|
||||
const dataType = (outputPort.getAttribute("data-datatype") as FrontendGraphDataType) || "General";
|
||||
const thick = verticalIn && verticalOut;
|
||||
|
||||
return { pathString, dataType, thick, dashed };
|
||||
}
|
||||
|
||||
async function refreshWires() {
|
||||
await tick();
|
||||
|
||||
nodeWirePaths = $nodeGraph.wires.flatMap((wire) => {
|
||||
// TODO: This call contains linear searches, which combined with the loop we're in, causes O(n^2) complexity as the graph grows
|
||||
const resolvedWires = resolveWire(wire);
|
||||
if (!resolvedWires) return [];
|
||||
const { nodeOutput, nodeInput } = resolvedWires;
|
||||
|
||||
const wireStartNode = wire.wireStart.nodeId !== undefined ? $nodeGraph.nodes.get(wire.wireStart.nodeId) : undefined;
|
||||
const wireStart = wireStartNode?.isLayer || false;
|
||||
|
||||
const wireEndNode = wire.wireEnd.nodeId !== undefined ? $nodeGraph.nodes.get(wire.wireEnd.nodeId) : undefined;
|
||||
const wireEnd = (wireEndNode?.isLayer && Number(wire.wireEnd.index) === 0) || false;
|
||||
|
||||
return [createWirePath(nodeOutput, nodeInput, wireStart, wireEnd, wire.dashed, $nodeGraph.wiresDirectNotGridAligned)];
|
||||
});
|
||||
}
|
||||
|
||||
onMount(refreshWires);
|
||||
|
||||
function nodeIcon(icon?: string): IconName {
|
||||
if (!icon) return "NodeNodes";
|
||||
const iconMap: Record<string, IconName> = {
|
||||
|
@ -203,325 +116,6 @@
|
|||
return iconMap[icon] || "NodeNodes";
|
||||
}
|
||||
|
||||
function buildStraightWirePathLocations(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): { x: number; y: number }[] {
|
||||
if (!nodesContainer) return [];
|
||||
|
||||
const VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP = 1;
|
||||
const LINE_WIDTH = 2;
|
||||
|
||||
// Calculate coordinates for input and output connectors
|
||||
const inX = verticalIn ? inputBounds.x + inputBounds.width / 2 : inputBounds.x;
|
||||
const inY = verticalIn ? inputBounds.y + inputBounds.height - VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : inputBounds.y + inputBounds.height / 2;
|
||||
const outX = verticalOut ? outputBounds.x + outputBounds.width / 2 : outputBounds.x + outputBounds.width - 1;
|
||||
const outY = verticalOut ? outputBounds.y + VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : outputBounds.y + outputBounds.height / 2;
|
||||
|
||||
// Adjust for scale
|
||||
const containerBounds = nodesContainer.getBoundingClientRect();
|
||||
const scale = $nodeGraph.transform.scale;
|
||||
const inConnectorX = Math.round((inX - containerBounds.x) / scale);
|
||||
const inConnectorY = Math.round((inY - containerBounds.y) / scale);
|
||||
const outConnectorX = Math.round((outX - containerBounds.x) / scale);
|
||||
const outConnectorY = Math.round((outY - containerBounds.y) / scale);
|
||||
|
||||
// Helper functions for calculating coordinates
|
||||
const calculateMidX = () => (inConnectorX + outConnectorX) / 2 + (((inConnectorX + outConnectorX) / 2) % gridSpacing);
|
||||
const calculateMidY = () => (inConnectorY + outConnectorY) / 2 + (((inConnectorY + outConnectorY) / 2) % gridSpacing);
|
||||
const calculateMidYAlternate = () => (inConnectorY + outConnectorY) / 2 - (((inConnectorY + outConnectorY) / 2) % gridSpacing);
|
||||
|
||||
// Define X coordinate calculations
|
||||
const x1 = () => outConnectorX;
|
||||
const x2 = () => outConnectorX + gridSpacing;
|
||||
const x3 = () => inConnectorX - 2 * gridSpacing;
|
||||
const x4 = () => inConnectorX;
|
||||
const x5 = () => inConnectorX - 2 * gridSpacing + LINE_WIDTH;
|
||||
const x6 = () => outConnectorX + gridSpacing + LINE_WIDTH;
|
||||
const x7 = () => outConnectorX + 2 * gridSpacing + LINE_WIDTH;
|
||||
const x8 = () => inConnectorX + LINE_WIDTH;
|
||||
const x9 = () => outConnectorX + 2 * gridSpacing;
|
||||
const x10 = () => calculateMidX() + LINE_WIDTH;
|
||||
const x11 = () => outConnectorX - gridSpacing;
|
||||
const x12 = () => outConnectorX - 4 * gridSpacing;
|
||||
const x13 = () => calculateMidX();
|
||||
const x14 = () => inConnectorX + gridSpacing;
|
||||
const x15 = () => inConnectorX - 4 * gridSpacing;
|
||||
const x16 = () => inConnectorX + 8 * gridSpacing;
|
||||
const x17 = () => calculateMidX() - 2 * LINE_WIDTH;
|
||||
const x18 = () => outConnectorX + gridSpacing - 2 * LINE_WIDTH;
|
||||
const x19 = () => outConnectorX - 2 * LINE_WIDTH;
|
||||
const x20 = () => calculateMidX() - LINE_WIDTH;
|
||||
|
||||
// Define Y coordinate calculations
|
||||
const y1 = () => outConnectorY;
|
||||
const y2 = () => outConnectorY - gridSpacing;
|
||||
const y3 = () => inConnectorY;
|
||||
const y4 = () => outConnectorY - gridSpacing + 5.5 * LINE_WIDTH;
|
||||
const y5 = () => inConnectorY - 2 * gridSpacing;
|
||||
const y6 = () => outConnectorY + 4 * LINE_WIDTH;
|
||||
const y7 = () => outConnectorY + 5 * LINE_WIDTH;
|
||||
const y8 = () => outConnectorY - 2 * gridSpacing + 5.5 * LINE_WIDTH;
|
||||
const y9 = () => outConnectorY + 6 * LINE_WIDTH;
|
||||
const y10 = () => inConnectorY + 2 * gridSpacing;
|
||||
const y111 = () => inConnectorY + gridSpacing + 6.5 * LINE_WIDTH;
|
||||
const y12 = () => inConnectorY + gridSpacing - 5.5 * LINE_WIDTH;
|
||||
const y13 = () => inConnectorY - gridSpacing;
|
||||
const y14 = () => inConnectorY + gridSpacing;
|
||||
const y15 = () => calculateMidY();
|
||||
const y16 = () => calculateMidYAlternate();
|
||||
|
||||
// Helper function for constructing coordinate pairs
|
||||
const construct = (...coords: [() => number, () => number][]) => coords.map(([x, y]) => ({ x: x(), y: y() }));
|
||||
|
||||
// Define wire path shapes that get used more than once
|
||||
const wire1 = () => construct([x1, y1], [x1, y4], [x5, y4], [x5, y3], [x4, y3]);
|
||||
const wire2 = () => construct([x1, y1], [x1, y16], [x3, y16], [x3, y3], [x4, y3]);
|
||||
const wire3 = () => construct([x1, y1], [x1, y4], [x12, y4], [x12, y10], [x3, y10], [x3, y3], [x4, y3]);
|
||||
const wire4 = () => construct([x1, y1], [x1, y4], [x13, y4], [x13, y10], [x3, y10], [x3, y3], [x4, y3]);
|
||||
|
||||
// `outConnector` point and `inConnector` point lying on the same horizontal grid line and `outConnector` point lies to the right of `inConnector` point
|
||||
if (outConnectorY === inConnectorY && outConnectorX > inConnectorX && (verticalOut || !verticalIn)) return construct([x1, y1], [x2, y1], [x2, y2], [x3, y2], [x3, y3], [x4, y3]);
|
||||
|
||||
// Handle straight lines
|
||||
if (outConnectorY === inConnectorY || (outConnectorX === inConnectorX && verticalOut)) return construct([x1, y1], [x4, y3]);
|
||||
|
||||
// Handle standard right-angle paths
|
||||
// Start vertical, then horizontal
|
||||
|
||||
// `outConnector` point lies to the left of `inConnector` point
|
||||
if (verticalOut && inConnectorX > outConnectorX) {
|
||||
// `outConnector` point lies above `inConnector` point
|
||||
if (outConnectorY < inConnectorY) {
|
||||
// `outConnector` point lies on the vertical grid line 4 units to the left of `inConnector` point point
|
||||
if (-4 * gridSpacing <= outConnectorX - inConnectorX && outConnectorX - inConnectorX < -3 * gridSpacing) return wire1();
|
||||
|
||||
// `outConnector` point lying on vertical grid lines 3 and 2 units to the left of `inConnector` point
|
||||
if (-3 * gridSpacing <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= -1 * gridSpacing) {
|
||||
if (-2 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= -1 * gridSpacing) return construct([x1, y1], [x1, y2], [x2, y2], [x2, y3], [x4, y3]);
|
||||
|
||||
if (-1 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 0 * gridSpacing) return construct([x1, y1], [x1, y4], [x6, y4], [x6, y3], [x4, y3]);
|
||||
|
||||
return construct([x1, y1], [x1, y4], [x7, y4], [x7, y5], [x3, y5], [x3, y3], [x4, y3]);
|
||||
}
|
||||
|
||||
// `outConnector` point lying on vertical grid line 1 units to the left of `inConnector` point
|
||||
if (-1 * gridSpacing < outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 0 * gridSpacing) {
|
||||
// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point
|
||||
if (-2 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= -1 * gridSpacing) return construct([x1, y6], [x2, y6], [x8, y3]);
|
||||
|
||||
// `outConnector` point lying on the same horizontal grid line as `inConnector` point
|
||||
if (-1 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 0 * gridSpacing) return construct([x1, y7], [x4, y3]);
|
||||
|
||||
return construct([x1, y1], [x1, y2], [x9, y2], [x9, y5], [x3, y5], [x3, y3], [x4, y3]);
|
||||
}
|
||||
|
||||
return construct([x1, y1], [x1, y4], [x10, y4], [x10, y3], [x4, y3]);
|
||||
}
|
||||
|
||||
// `outConnector` point lies below `inConnector` point
|
||||
// `outConnector` point lying on vertical grid line 1 unit to the left of `inConnector` point
|
||||
if (-1 * gridSpacing <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 0 * gridSpacing) {
|
||||
// `outConnector` point lying on the horizontal grid lines 1 and 2 units below the `inConnector` point
|
||||
if (0 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 2 * gridSpacing) construct([x1, y6], [x11, y6], [x11, y3], [x4, y3]);
|
||||
|
||||
return wire2();
|
||||
}
|
||||
|
||||
return construct([x1, y1], [x1, y3], [x4, y3]);
|
||||
}
|
||||
|
||||
// `outConnector` point lies to the right of `inConnector` point
|
||||
if (verticalOut && inConnectorX <= outConnectorX) {
|
||||
// `outConnector` point lying on any horizontal grid line above `inConnector` point
|
||||
if (outConnectorY < inConnectorY) {
|
||||
// `outConnector` point lying on horizontal grid line 1 unit above `inConnector` point
|
||||
if (-2 * gridSpacing < outConnectorY - inConnectorY && outConnectorY - inConnectorY <= -1 * gridSpacing) return wire1();
|
||||
|
||||
// `outConnector` point lying on the same horizontal grid line as `inConnector` point
|
||||
if (-1 * gridSpacing < outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 0 * gridSpacing) return construct([x1, y1], [x1, y8], [x5, y8], [x5, y3], [x4, y3]);
|
||||
|
||||
// `outConnector` point lying on vertical grid lines 1 and 2 units to the right of `inConnector` point
|
||||
if (gridSpacing <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 3 * gridSpacing) {
|
||||
return construct([x1, y1], [x1, y4], [x9, y4], [x9, y5], [x3, y5], [x3, y3], [x4, y3]);
|
||||
}
|
||||
|
||||
return construct([x1, y1], [x1, y4], [x10, y4], [x10, y5], [x5, y5], [x5, y3], [x4, y3]);
|
||||
}
|
||||
|
||||
// `outConnector` point lies below `inConnector` point
|
||||
if (outConnectorY - inConnectorY <= gridSpacing) {
|
||||
// `outConnector` point lies on the horizontal grid line 1 unit below the `inConnector` Point
|
||||
if (0 <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 13 * gridSpacing) return construct([x1, y9], [x3, y9], [x3, y3], [x4, y3]);
|
||||
|
||||
if (13 < outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 18 * gridSpacing) return wire3();
|
||||
|
||||
return wire4();
|
||||
}
|
||||
|
||||
// `outConnector` point lies on the horizontal grid line 2 units below `outConnector` point
|
||||
if (gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= 2 * gridSpacing) {
|
||||
if (0 <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 13 * gridSpacing) return construct([x1, y7], [x5, y7], [x5, y3], [x4, y3]);
|
||||
|
||||
if (13 < outConnectorX - inConnectorX && outConnectorX - inConnectorX <= 18 * gridSpacing) return wire3();
|
||||
|
||||
return wire4();
|
||||
}
|
||||
|
||||
// 0 to 4 units below the `outConnector` Point
|
||||
if (outConnectorY - inConnectorY <= 4 * gridSpacing) return wire1();
|
||||
|
||||
return wire2();
|
||||
}
|
||||
|
||||
// Start horizontal, then vertical
|
||||
if (verticalIn) {
|
||||
// when `outConnector` lies below `inConnector`
|
||||
if (outConnectorY > inConnectorY) {
|
||||
// `outConnectorX` lies to the left of `inConnectorX`
|
||||
if (outConnectorX < inConnectorX) return construct([x1, y1], [x4, y1], [x4, y3]);
|
||||
|
||||
// `outConnectorX` lies to the right of `inConnectorX`
|
||||
if (outConnectorY - inConnectorY <= gridSpacing) {
|
||||
// `outConnector` point directly below `inConnector` point
|
||||
if (0 <= outConnectorX - inConnectorX && outConnectorX - inConnectorX <= gridSpacing) return construct([x1, y1], [x14, y1], [x14, y2], [x4, y2], [x4, y3]);
|
||||
|
||||
// `outConnector` point lies below `inConnector` point and strictly to the right of `inConnector` point
|
||||
return construct([x1, y1], [x2, y1], [x2, y111], [x4, y111], [x4, y3]);
|
||||
}
|
||||
|
||||
return construct([x1, y1], [x2, y1], [x2, y2], [x4, y2], [x4, y3]);
|
||||
}
|
||||
|
||||
// `outConnectorY` lies on or above the `inConnectorY` point
|
||||
if (-6 * gridSpacing < inConnectorX - outConnectorX && inConnectorX - outConnectorX < 4 * gridSpacing) {
|
||||
// edge case: `outConnector` point lying on vertical grid lines ranging from 4 units to left to 5 units to right of `inConnector` point
|
||||
if (-1 * gridSpacing < inConnectorX - outConnectorX && inConnectorX - outConnectorX < 4 * gridSpacing) {
|
||||
return construct([x1, y1], [x2, y1], [x2, y2], [x15, y2], [x15, y12], [x4, y12], [x4, y3]);
|
||||
}
|
||||
|
||||
return construct([x1, y1], [x16, y1], [x16, y12], [x4, y12], [x4, y3]);
|
||||
}
|
||||
|
||||
// left of edge case: `outConnector` point lying on vertical grid lines more than 4 units to left of `inConnector` point
|
||||
if (4 * gridSpacing < inConnectorX - outConnectorX) return construct([x1, y1], [x17, y1], [x17, y12], [x4, y12], [x4, y3]);
|
||||
|
||||
// right of edge case: `outConnector` point lying on the vertical grid lines more than 5 units to right of `inConnector` point
|
||||
if (6 * gridSpacing > inConnectorX - outConnectorX) return construct([x1, y1], [x18, y1], [x18, y12], [x4, y12], [x4, y3]);
|
||||
}
|
||||
|
||||
// Both horizontal - use horizontal middle point
|
||||
// When `inConnector` point is one of the two closest diagonally opposite points
|
||||
if (0 <= inConnectorX - outConnectorX && inConnectorX - outConnectorX <= gridSpacing && inConnectorY - outConnectorY >= -1 * gridSpacing && inConnectorY - outConnectorY <= gridSpacing) {
|
||||
return construct([x19, y1], [x19, y3], [x4, y3]);
|
||||
}
|
||||
|
||||
// When `inConnector` point lies on the horizontal line 1 unit above and below the `outConnector` point
|
||||
if (-1 * gridSpacing <= outConnectorY - inConnectorY && outConnectorY - inConnectorY <= gridSpacing && outConnectorX > inConnectorX) {
|
||||
// Horizontal line above `outConnectorY`
|
||||
if (inConnectorY < outConnectorY) return construct([x1, y1], [x2, y1], [x2, y13], [x3, y13], [x3, y3], [x4, y3]);
|
||||
|
||||
// Horizontal line below `outConnectorY`
|
||||
return construct([x1, y1], [x2, y1], [x2, y14], [x3, y14], [x3, y3], [x4, y3]);
|
||||
}
|
||||
|
||||
// `outConnector` point to the right of `inConnector` point
|
||||
if (outConnectorX > inConnectorX - gridSpacing) return construct([x1, y1], [x18, y1], [x18, y15], [x5, y15], [x5, y3], [x4, y3]);
|
||||
|
||||
// When `inConnector` point lies on the vertical grid line two units to the right of `outConnector` point
|
||||
if (gridSpacing <= inConnectorX - outConnectorX && inConnectorX - outConnectorX <= 2 * gridSpacing) return construct([x1, y1], [x18, y1], [x18, y3], [x4, y3]);
|
||||
|
||||
return construct([x1, y1], [x20, y1], [x20, y3], [x4, y3]);
|
||||
}
|
||||
|
||||
function buildStraightWirePathString(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): string {
|
||||
const locations = buildStraightWirePathLocations(outputBounds, inputBounds, verticalOut, verticalIn);
|
||||
if (locations.length === 0) return "[error]";
|
||||
if (locations.length === 2) return `M${locations[0].x},${locations[0].y} L${locations[1].x},${locations[1].y}`;
|
||||
|
||||
const CORNER_RADIUS = 10;
|
||||
|
||||
// Create path with rounded corners
|
||||
let path = `M${locations[0].x},${locations[0].y}`;
|
||||
|
||||
for (let i = 1; i < locations.length - 1; i++) {
|
||||
const prev = locations[i - 1];
|
||||
const curr = locations[i];
|
||||
const next = locations[i + 1];
|
||||
|
||||
// Calculate corner points
|
||||
const isVertical = curr.x === prev.x;
|
||||
const cornerStart = {
|
||||
x: curr.x + (isVertical ? 0 : prev.x < curr.x ? -CORNER_RADIUS : CORNER_RADIUS),
|
||||
y: curr.y + (isVertical ? (prev.y < curr.y ? -CORNER_RADIUS : CORNER_RADIUS) : 0),
|
||||
};
|
||||
const cornerEnd = {
|
||||
x: curr.x + (isVertical ? (next.x < curr.x ? -CORNER_RADIUS : CORNER_RADIUS) : 0),
|
||||
y: curr.y + (isVertical ? 0 : next.y < curr.y ? -CORNER_RADIUS : CORNER_RADIUS),
|
||||
};
|
||||
|
||||
// Add line to corner start, quadratic curve for corner, then continue to next point
|
||||
path += ` L${cornerStart.x},${cornerStart.y}`;
|
||||
path += ` Q${curr.x},${curr.y} ${cornerEnd.x},${cornerEnd.y}`;
|
||||
}
|
||||
|
||||
path += ` L${locations[locations.length - 1].x},${locations[locations.length - 1].y}`;
|
||||
return path;
|
||||
}
|
||||
|
||||
function buildCurvedWirePathLocations(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): { x: number; y: number }[] {
|
||||
if (!nodesContainer) return [];
|
||||
|
||||
const VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP = 1;
|
||||
|
||||
const containerBounds = nodesContainer.getBoundingClientRect();
|
||||
|
||||
const outX = verticalOut ? outputBounds.x + outputBounds.width / 2 : outputBounds.x + outputBounds.width - 1;
|
||||
const outY = verticalOut ? outputBounds.y + VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : outputBounds.y + outputBounds.height / 2;
|
||||
const outConnectorX = (outX - containerBounds.x) / $nodeGraph.transform.scale;
|
||||
const outConnectorY = (outY - containerBounds.y) / $nodeGraph.transform.scale;
|
||||
|
||||
const inX = verticalIn ? inputBounds.x + inputBounds.width / 2 : inputBounds.x;
|
||||
const inY = verticalIn ? inputBounds.y + inputBounds.height - VERTICAL_WIRE_OVERLAP_ON_SHAPED_CAP : inputBounds.y + inputBounds.height / 2;
|
||||
const inConnectorX = (inX - containerBounds.x) / $nodeGraph.transform.scale;
|
||||
const inConnectorY = (inY - containerBounds.y) / $nodeGraph.transform.scale;
|
||||
const horizontalGap = Math.abs(outConnectorX - inConnectorX);
|
||||
const verticalGap = Math.abs(outConnectorY - inConnectorY);
|
||||
|
||||
const curveLength = 24;
|
||||
const curveFalloffRate = curveLength * Math.PI * 2;
|
||||
|
||||
const horizontalCurveAmount = -(2 ** ((-10 * horizontalGap) / curveFalloffRate)) + 1;
|
||||
const verticalCurveAmount = -(2 ** ((-10 * verticalGap) / curveFalloffRate)) + 1;
|
||||
const horizontalCurve = horizontalCurveAmount * curveLength;
|
||||
const verticalCurve = verticalCurveAmount * curveLength;
|
||||
|
||||
return [
|
||||
{ x: outConnectorX, y: outConnectorY },
|
||||
{ x: verticalOut ? outConnectorX : outConnectorX + horizontalCurve, y: verticalOut ? outConnectorY - verticalCurve : outConnectorY },
|
||||
{ x: verticalIn ? inConnectorX : inConnectorX - horizontalCurve, y: verticalIn ? inConnectorY + verticalCurve : inConnectorY },
|
||||
{ x: inConnectorX, y: inConnectorY },
|
||||
];
|
||||
}
|
||||
|
||||
function buildCurvedWirePathString(outputBounds: DOMRect, inputBounds: DOMRect, verticalOut: boolean, verticalIn: boolean): string {
|
||||
const locations = buildCurvedWirePathLocations(outputBounds, inputBounds, verticalOut, verticalIn);
|
||||
if (locations.length === 0) return "[error]";
|
||||
|
||||
const SMOOTHING = 0.5;
|
||||
const delta01 = { x: (locations[1].x - locations[0].x) * SMOOTHING, y: (locations[1].y - locations[0].y) * SMOOTHING };
|
||||
const delta23 = { x: (locations[3].x - locations[2].x) * SMOOTHING, y: (locations[3].y - locations[2].y) * SMOOTHING };
|
||||
|
||||
return `
|
||||
M${locations[0].x},${locations[0].y}
|
||||
L${locations[1].x},${locations[1].y}
|
||||
C${locations[1].x + delta01.x},${locations[1].y + delta01.y}
|
||||
${locations[2].x - delta23.x},${locations[2].y - delta23.y}
|
||||
${locations[2].x},${locations[2].y}
|
||||
L${locations[3].x},${locations[3].y}
|
||||
`
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
function toggleLayerDisplay(displayAsLayer: boolean, toggleId: bigint) {
|
||||
let node = $nodeGraph.nodes.get(toggleId);
|
||||
if (node) editor.handle.setToNodeOrLayer(node.id, displayAsLayer);
|
||||
|
@ -719,19 +313,21 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Node connection wires -->
|
||||
<!-- Thick vertical layer connection wires -->
|
||||
<div class="wires" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
|
||||
<svg>
|
||||
{#each wirePaths as { pathString, dataType, thick, dashed }}
|
||||
{#if thick}
|
||||
<path
|
||||
d={pathString}
|
||||
style:--data-line-width={`${thick ? 8 : 2}px`}
|
||||
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
|
||||
style:--data-dasharray={`3,${dashed ? 2 : 0}`}
|
||||
/>
|
||||
{/if}
|
||||
{#each $nodeGraph.wires.values() as map}
|
||||
{#each map.values() as { pathString, dataType, thick, dashed }}
|
||||
{#if thick}
|
||||
<path
|
||||
d={pathString}
|
||||
style:--data-line-width={"8px"}
|
||||
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
|
||||
style:--data-dasharray={`3,${dashed ? 2 : 0}`}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
{/each}
|
||||
</svg>
|
||||
</div>
|
||||
|
@ -749,7 +345,6 @@
|
|||
style:--data-color-dim={`var(--color-data-${outputMetadata.dataType.toLowerCase()}-dim)`}
|
||||
style:--offset-left={position.x / 24}
|
||||
style:--offset-top={position.y / 24}
|
||||
bind:this={outputs[0][index]}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(outputMetadata)}\n\n${outputConnectedToText(outputMetadata)}`}</title>
|
||||
{#if outputMetadata.connectedTo !== undefined}
|
||||
|
@ -823,7 +418,6 @@
|
|||
style:--data-color-dim={`var(--color-data-${inputMetadata.dataType.toLowerCase()}-dim)`}
|
||||
style:--offset-left={position.x / 24}
|
||||
style:--offset-top={position.y / 24}
|
||||
bind:this={inputs[0][index]}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(inputMetadata)}\n\n${inputConnectedToText(inputMetadata)}`}</title>
|
||||
{#if inputMetadata.connectedTo !== undefined}
|
||||
|
@ -887,14 +481,11 @@
|
|||
</div>
|
||||
|
||||
<!-- Layers and nodes -->
|
||||
<div
|
||||
class="layers-and-nodes"
|
||||
style:transform-origin={`0 0`}
|
||||
style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}
|
||||
bind:this={nodesContainer}
|
||||
>
|
||||
<div class="layers-and-nodes" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
|
||||
<!-- Layers -->
|
||||
{#each Array.from($nodeGraph.nodes.values()).flatMap((node, nodeIndex) => (node.isLayer ? [{ node, nodeIndex }] : [])) as { node, nodeIndex } (nodeIndex)}
|
||||
{#each Array.from($nodeGraph.nodes)
|
||||
.filter(([nodeId, node]) => node.isLayer && $nodeGraph.visibleNodes.has(nodeId))
|
||||
.map(([_, node], nodeIndex) => ({ node, nodeIndex })) as { node, nodeIndex } (nodeIndex)}
|
||||
{@const clipPathId = String(Math.random()).substring(2)}
|
||||
{@const stackDataInput = node.exposedInputs[0]}
|
||||
{@const layerAreaWidth = $nodeGraph.layerWidths.get(node.id) || 8}
|
||||
|
@ -916,7 +507,6 @@
|
|||
style:--node-chain-area-left-extension={layerChainWidth !== 0 ? layerChainWidth + 0.5 : 0}
|
||||
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")}
|
||||
data-node={node.id}
|
||||
bind:this={nodeElements[nodeIndex]}
|
||||
>
|
||||
{#if node.errors}
|
||||
<span class="node-error faded" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
|
||||
|
@ -936,7 +526,6 @@
|
|||
data-datatype={node.primaryOutput.dataType}
|
||||
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
|
||||
bind:this={outputs[nodeIndex + 1][0]}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
|
||||
{#if node.primaryOutput.connectedTo.length > 0}
|
||||
|
@ -958,7 +547,6 @@
|
|||
data-datatype={node.primaryInput?.dataType}
|
||||
style:--data-color={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()}-dim)`}
|
||||
bind:this={inputs[nodeIndex + 1][0]}
|
||||
>
|
||||
{#if node.primaryInput}
|
||||
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
|
||||
|
@ -984,7 +572,6 @@
|
|||
data-datatype={stackDataInput.dataType}
|
||||
style:--data-color={`var(--color-data-${stackDataInput.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`}
|
||||
bind:this={inputs[nodeIndex + 1][1]}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(stackDataInput)}\n\n${validTypesText(stackDataInput)}\n\n${inputConnectedToText(stackDataInput)}`}</title>
|
||||
{#if stackDataInput.connectedTo !== undefined}
|
||||
|
@ -1025,22 +612,35 @@
|
|||
<!-- Node connection wires -->
|
||||
<div class="wires">
|
||||
<svg>
|
||||
{#each wirePaths as { pathString, dataType, thick, dashed }}\
|
||||
{#if !thick}
|
||||
<path
|
||||
d={pathString}
|
||||
style:--data-line-width={`${thick ? 8 : 2}px`}
|
||||
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
|
||||
style:--data-dasharray={dashed ? "4" : undefined}
|
||||
/>
|
||||
{/if}
|
||||
{#each $nodeGraph.wires.values() as map}
|
||||
{#each map.values() as { pathString, dataType, thick, dashed }}
|
||||
{#if !thick}
|
||||
<path
|
||||
d={pathString}
|
||||
style:--data-line-width={"2px"}
|
||||
style:--data-color={`var(--color-data-${dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${dataType.toLowerCase()}-dim)`}
|
||||
style:--data-dasharray={`3,${dashed ? 2 : 0}`}
|
||||
/>
|
||||
{/if}
|
||||
{/each}
|
||||
{/each}
|
||||
{#if $nodeGraph.wirePathInProgress}
|
||||
<path
|
||||
d={$nodeGraph.wirePathInProgress?.pathString}
|
||||
style:--data-line-width={`${$nodeGraph.wirePathInProgress.thick ? 8 : 2}px`}
|
||||
style:--data-color={`var(--color-data-${$nodeGraph.wirePathInProgress.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${$nodeGraph.wirePathInProgress.dataType.toLowerCase()}-dim)`}
|
||||
style:--data-dasharray={`3,${$nodeGraph.wirePathInProgress.dashed ? 2 : 0}`}
|
||||
/>
|
||||
{/if}
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Nodes -->
|
||||
{#each Array.from($nodeGraph.nodes.values()).flatMap((node, nodeIndex) => (node.isLayer ? [] : [{ node, nodeIndex }])) as { node, nodeIndex } (nodeIndex)}
|
||||
{#each Array.from($nodeGraph.nodes)
|
||||
.filter(([nodeId, node]) => !node.isLayer && $nodeGraph.visibleNodes.has(nodeId))
|
||||
.map(([_, node], nodeIndex) => ({ node, nodeIndex })) as { node, nodeIndex } (nodeIndex)}
|
||||
{@const exposedInputsOutputs = zipWithUndefined(node.exposedInputs, node.exposedOutputs)}
|
||||
{@const clipPathId = String(Math.random()).substring(2)}
|
||||
{@const description = (node.reference && $nodeGraph.nodeDescriptions.get(node.reference)) || undefined}
|
||||
|
@ -1056,7 +656,6 @@
|
|||
style:--data-color-dim={`var(--color-data-${(node.primaryOutput?.dataType || "General").toLowerCase()}-dim)`}
|
||||
title={`${node.displayName}\n\n${description || ""}`.trim() + (editor.handle.inDevelopmentMode() ? `\n\nNode ID: ${node.id}` : "")}
|
||||
data-node={node.id}
|
||||
bind:this={nodeElements[nodeIndex]}
|
||||
>
|
||||
{#if node.errors}
|
||||
<span class="node-error faded" transition:fade={FADE_TRANSITION} title="" data-node-error>{node.errors}</span>
|
||||
|
@ -1091,7 +690,6 @@
|
|||
data-datatype={node.primaryInput?.dataType}
|
||||
style:--data-color={`var(--color-data-${node.primaryInput.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`}
|
||||
bind:this={inputs[nodeIndex + 1][0]}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(node.primaryInput)}\n\n${validTypesText(node.primaryInput)}\n\n${inputConnectedToText(node.primaryInput)}`}</title>
|
||||
{#if node.primaryInput.connectedTo !== undefined}
|
||||
|
@ -1111,7 +709,6 @@
|
|||
data-datatype={secondary.dataType}
|
||||
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
|
||||
bind:this={inputs[nodeIndex + 1][index + (node.primaryInput ? 1 : 0)]}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(secondary)}\n\n${validTypesText(secondary)}\n\n${inputConnectedToText(secondary)}`}</title>
|
||||
{#if secondary.connectedTo !== undefined}
|
||||
|
@ -1134,7 +731,6 @@
|
|||
data-datatype={node.primaryOutput.dataType}
|
||||
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
|
||||
bind:this={outputs[nodeIndex + 1][0]}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(node.primaryOutput)}\n\n${outputConnectedToText(node.primaryOutput)}`}</title>
|
||||
{#if node.primaryOutput.connectedTo !== undefined}
|
||||
|
@ -1144,7 +740,7 @@
|
|||
{/if}
|
||||
</svg>
|
||||
{/if}
|
||||
{#each node.exposedOutputs as secondary, outputIndex}
|
||||
{#each node.exposedOutputs as secondary}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 8 8"
|
||||
|
@ -1153,7 +749,6 @@
|
|||
data-datatype={secondary.dataType}
|
||||
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
|
||||
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
|
||||
bind:this={outputs[nodeIndex + 1][outputIndex + (node.primaryOutput ? 1 : 0)]}
|
||||
>
|
||||
<title>{`${dataTypeTooltip(secondary)}\n\n${outputConnectedToText(secondary)}`}</title>
|
||||
{#if secondary.connectedTo !== undefined}
|
||||
|
|
|
@ -158,6 +158,7 @@
|
|||
background: none;
|
||||
color: var(--color-e-nearwhite);
|
||||
caret-color: var(--color-e-nearwhite);
|
||||
unicode-bidi: plaintext;
|
||||
|
||||
&::selection {
|
||||
background-color: var(--color-4-dimgray);
|
||||
|
|
|
@ -96,16 +96,21 @@ export class UpdateLayerWidths extends JsMessage {
|
|||
readonly hasLeftInputWire!: Map<bigint, boolean>;
|
||||
}
|
||||
|
||||
export class UpdateNodeGraph extends JsMessage {
|
||||
export class UpdateNodeGraphNodes extends JsMessage {
|
||||
@Type(() => FrontendNode)
|
||||
readonly nodes!: FrontendNode[];
|
||||
|
||||
@Type(() => FrontendNodeWire)
|
||||
readonly wires!: FrontendNodeWire[];
|
||||
|
||||
readonly wiresDirectNotGridAligned!: boolean;
|
||||
}
|
||||
|
||||
export class UpdateVisibleNodes extends JsMessage {
|
||||
readonly nodes!: bigint[];
|
||||
}
|
||||
|
||||
export class UpdateNodeGraphWires extends JsMessage {
|
||||
readonly wires!: WireUpdate[];
|
||||
}
|
||||
|
||||
export class ClearAllNodeGraphWires extends JsMessage {}
|
||||
|
||||
export class UpdateNodeGraphTransform extends JsMessage {
|
||||
readonly transform!: NodeGraphTransform;
|
||||
}
|
||||
|
@ -219,7 +224,7 @@ export class FrontendGraphInput {
|
|||
|
||||
readonly description!: string;
|
||||
|
||||
readonly resolvedType!: string | undefined;
|
||||
readonly resolvedType!: string;
|
||||
|
||||
readonly validTypes!: string[];
|
||||
|
||||
|
@ -252,7 +257,7 @@ export class FrontendGraphOutput {
|
|||
|
||||
readonly description!: string;
|
||||
|
||||
readonly resolvedType!: string | undefined;
|
||||
readonly resolvedType!: string;
|
||||
|
||||
@CreateInputConnectorArray
|
||||
connectedTo!: Node[];
|
||||
|
@ -297,44 +302,6 @@ export class FrontendNode {
|
|||
readonly uiOnly!: boolean;
|
||||
}
|
||||
|
||||
const CreateOutputConnector = Transform(({ obj }) => {
|
||||
if (obj.wireStart.export !== undefined) {
|
||||
return { index: obj.wireStart.export };
|
||||
} else if (obj.wireStart.import !== undefined) {
|
||||
return { index: obj.wireStart.import };
|
||||
} else {
|
||||
if (obj.wireStart.node.inputIndex !== undefined) {
|
||||
return { nodeId: obj.wireStart.node.nodeId, index: obj.wireStart.node.inputIndex };
|
||||
} else {
|
||||
return { nodeId: obj.wireStart.node.nodeId, index: obj.wireStart.node.outputIndex };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const CreateInputConnector = Transform(({ obj }) => {
|
||||
if (obj.wireEnd.export !== undefined) {
|
||||
return { index: obj.wireEnd.export };
|
||||
} else if (obj.wireEnd.import !== undefined) {
|
||||
return { index: obj.wireEnd.import };
|
||||
} else {
|
||||
if (obj.wireEnd.node.inputIndex !== undefined) {
|
||||
return { nodeId: obj.wireEnd.node.nodeId, index: obj.wireEnd.node.inputIndex };
|
||||
} else {
|
||||
return { nodeId: obj.wireEnd.node.nodeId, index: obj.wireEnd.node.outputIndex };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export class FrontendNodeWire {
|
||||
@CreateOutputConnector
|
||||
readonly wireStart!: Node;
|
||||
|
||||
@CreateInputConnector
|
||||
readonly wireEnd!: Node;
|
||||
|
||||
readonly dashed!: boolean;
|
||||
}
|
||||
|
||||
export class FrontendNodeType {
|
||||
readonly name!: string;
|
||||
|
||||
|
@ -356,6 +323,12 @@ export class WirePath {
|
|||
readonly dashed!: boolean;
|
||||
}
|
||||
|
||||
export class WireUpdate {
|
||||
readonly id!: bigint;
|
||||
readonly inputIndex!: number;
|
||||
readonly wirePathUpdate!: WirePath | undefined;
|
||||
}
|
||||
|
||||
export class IndexedDbDocumentDetails extends DocumentDetails {
|
||||
@Transform(({ value }: { value: bigint }) => value.toString())
|
||||
id!: string;
|
||||
|
@ -1645,6 +1618,7 @@ type JSMessageFactory = (data: any, wasm: WebAssembly.Memory, handle: EditorHand
|
|||
type MessageMaker = typeof JsMessage | JSMessageFactory;
|
||||
|
||||
export const messageMakers: Record<string, MessageMaker> = {
|
||||
ClearAllNodeGraphWires,
|
||||
DisplayDialog,
|
||||
DisplayDialogDismiss,
|
||||
DisplayDialogPanic,
|
||||
|
@ -1700,10 +1674,12 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||
UpdateLayerWidths,
|
||||
UpdateMenuBarLayout,
|
||||
UpdateMouseCursor,
|
||||
UpdateNodeGraph,
|
||||
UpdateNodeGraphNodes,
|
||||
UpdateVisibleNodes,
|
||||
UpdateNodeGraphWires,
|
||||
UpdateNodeGraphTransform,
|
||||
UpdateNodeGraphControlBarLayout,
|
||||
UpdateNodeGraphSelection,
|
||||
UpdateNodeGraphTransform,
|
||||
UpdateNodeThumbnail,
|
||||
UpdateOpenDocumentsList,
|
||||
UpdatePropertyPanelSectionsLayout,
|
||||
|
|
|
@ -7,9 +7,9 @@ import {
|
|||
type FrontendClickTargets,
|
||||
type ContextMenuInformation,
|
||||
type FrontendNode,
|
||||
type FrontendNodeWire as FrontendNodeWire,
|
||||
type FrontendNodeType,
|
||||
type WirePath,
|
||||
ClearAllNodeGraphWires,
|
||||
SendUIMetadata,
|
||||
UpdateBox,
|
||||
UpdateClickTargets,
|
||||
|
@ -19,7 +19,9 @@ import {
|
|||
UpdateExportReorderIndex,
|
||||
UpdateImportsExports,
|
||||
UpdateLayerWidths,
|
||||
UpdateNodeGraph,
|
||||
UpdateNodeGraphNodes,
|
||||
UpdateVisibleNodes,
|
||||
UpdateNodeGraphWires,
|
||||
UpdateNodeGraphSelection,
|
||||
UpdateNodeGraphTransform,
|
||||
UpdateNodeThumbnail,
|
||||
|
@ -40,8 +42,9 @@ export function createNodeGraphState(editor: Editor) {
|
|||
addImport: undefined as { x: number; y: number } | undefined,
|
||||
addExport: undefined as { x: number; y: number } | undefined,
|
||||
nodes: new Map<bigint, FrontendNode>(),
|
||||
wires: [] as FrontendNodeWire[],
|
||||
wiresDirectNotGridAligned: false,
|
||||
visibleNodes: new Set<bigint>(),
|
||||
/// The index is the exposed input index. The exports have a first key value of u32::MAX.
|
||||
wires: new Map<bigint, Map<number, WirePath>>(),
|
||||
wirePathInProgress: undefined as WirePath | undefined,
|
||||
nodeDescriptions: new Map<string, string>(),
|
||||
nodeTypes: [] as FrontendNodeType[],
|
||||
|
@ -114,15 +117,42 @@ export function createNodeGraphState(editor: Editor) {
|
|||
return state;
|
||||
});
|
||||
});
|
||||
// TODO: Add a way to only update the nodes that have changed
|
||||
editor.subscriptions.subscribeJsMessage(UpdateNodeGraph, (updateNodeGraph) => {
|
||||
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphNodes, (updateNodeGraphNodes) => {
|
||||
update((state) => {
|
||||
state.nodes.clear();
|
||||
updateNodeGraph.nodes.forEach((node) => {
|
||||
updateNodeGraphNodes.nodes.forEach((node) => {
|
||||
state.nodes.set(node.id, node);
|
||||
});
|
||||
state.wires = updateNodeGraph.wires;
|
||||
state.wiresDirectNotGridAligned = updateNodeGraph.wiresDirectNotGridAligned;
|
||||
return state;
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateVisibleNodes, (updateVisibleNodes) => {
|
||||
update((state) => {
|
||||
state.visibleNodes = new Set<bigint>(updateVisibleNodes.nodes);
|
||||
return state;
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphWires, (updateNodeWires) => {
|
||||
update((state) => {
|
||||
updateNodeWires.wires.forEach((wireUpdate) => {
|
||||
let inputMap = state.wires.get(wireUpdate.id);
|
||||
// If it doesn't exist, create it and set it in the outer map
|
||||
if (!inputMap) {
|
||||
inputMap = new Map();
|
||||
state.wires.set(wireUpdate.id, inputMap);
|
||||
}
|
||||
if (wireUpdate.wirePathUpdate !== undefined) {
|
||||
inputMap.set(wireUpdate.inputIndex, wireUpdate.wirePathUpdate);
|
||||
} else {
|
||||
inputMap.delete(wireUpdate.inputIndex);
|
||||
}
|
||||
});
|
||||
return state;
|
||||
});
|
||||
});
|
||||
editor.subscriptions.subscribeJsMessage(ClearAllNodeGraphWires, (_) => {
|
||||
update((state) => {
|
||||
state.wires.clear();
|
||||
return state;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -21,6 +21,7 @@ const ALLOWED_LICENSES = [
|
|||
"BSD-3-Clause",
|
||||
"BSL-1.0",
|
||||
"CC0-1.0",
|
||||
"CDLA-Permissive-2.0",
|
||||
"ISC",
|
||||
"MIT-0",
|
||||
"MIT",
|
||||
|
@ -29,6 +30,7 @@ const ALLOWED_LICENSES = [
|
|||
"Unicode-3.0",
|
||||
"Unicode-DFS-2016",
|
||||
"Zlib",
|
||||
"NCSA",
|
||||
];
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
|
|
|
@ -467,8 +467,9 @@ impl EditorHandle {
|
|||
return Err(Error::new("Invalid color").into());
|
||||
};
|
||||
|
||||
let message = ToolMessage::SelectPrimaryColor {
|
||||
let message = ToolMessage::SelectWorkingColor {
|
||||
color: primary_color.to_linear_srgb(),
|
||||
primary: true,
|
||||
};
|
||||
self.dispatch(message);
|
||||
|
||||
|
@ -482,8 +483,9 @@ impl EditorHandle {
|
|||
return Err(Error::new("Invalid color").into());
|
||||
};
|
||||
|
||||
let message = ToolMessage::SelectSecondaryColor {
|
||||
let message = ToolMessage::SelectWorkingColor {
|
||||
color: secondary_color.to_linear_srgb(),
|
||||
primary: false,
|
||||
};
|
||||
self.dispatch(message);
|
||||
|
||||
|
@ -602,6 +604,7 @@ impl EditorHandle {
|
|||
node_id: Some(id),
|
||||
node_type,
|
||||
xy: Some((x / 24, y / 24)),
|
||||
add_transaction: true,
|
||||
};
|
||||
self.dispatch(message);
|
||||
}
|
||||
|
|
28
node-graph/gbrush/Cargo.toml
Normal file
28
node-graph/gbrush/Cargo.toml
Normal file
|
@ -0,0 +1,28 @@
|
|||
[package]
|
||||
name = "graphene-brush"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
description = "graphene brush"
|
||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
serde = ["dep:serde"]
|
||||
|
||||
[dependencies]
|
||||
# Local dependencies
|
||||
dyn-any = { workspace = true }
|
||||
graphene-core = { workspace = true }
|
||||
graphene-raster-nodes = { workspace = true }
|
||||
node-macro = { workspace = true }
|
||||
|
||||
# Workspace dependencies
|
||||
glam = { workspace = true }
|
||||
|
||||
# Optional workspace dependencies
|
||||
serde = { workspace = true, optional = true, features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
# Workspace dependencies
|
||||
tokio = { workspace = true }
|
|
@ -1,19 +1,21 @@
|
|||
use crate::raster::{empty_image, extend_image_to_bounds};
|
||||
use crate::brush_cache::BrushCache;
|
||||
use crate::brush_stroke::{BrushStroke, BrushStyle};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::generic::FnNode;
|
||||
use graph_craft::proto::FutureWrapperNode;
|
||||
use graphene_core::blending::BlendMode;
|
||||
use graphene_core::bounds::BoundingBox;
|
||||
use graphene_core::color::{Alpha, Color, Pixel, Sample};
|
||||
use graphene_core::generic::FnNode;
|
||||
use graphene_core::instances::Instance;
|
||||
use graphene_core::math::bbox::{AxisAlignedBbox, Bbox};
|
||||
use graphene_core::raster::adjustments::blend_colors;
|
||||
use graphene_core::raster::brush_cache::BrushCache;
|
||||
use graphene_core::raster::BitmapMut;
|
||||
use graphene_core::raster::image::Image;
|
||||
use graphene_core::raster::{Alpha, BitmapMut, BlendMode, Color, Pixel, Sample};
|
||||
use graphene_core::raster_types::{CPU, Raster, RasterDataTable};
|
||||
use graphene_core::registry::FutureWrapperNode;
|
||||
use graphene_core::transform::Transform;
|
||||
use graphene_core::value::ClonedNode;
|
||||
use graphene_core::vector::brush_stroke::{BrushStroke, BrushStyle};
|
||||
use graphene_core::{Ctx, GraphicElement, Node};
|
||||
use graphene_core::{Ctx, Node};
|
||||
use graphene_raster_nodes::adjustments::blend_colors;
|
||||
use graphene_raster_nodes::std_nodes::{empty_image, extend_image_to_bounds};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct BrushStampGenerator<P: Pixel + Alpha> {
|
||||
|
@ -50,13 +52,13 @@ impl<P: Pixel + Alpha> Sample for BrushStampGenerator<P> {
|
|||
return None;
|
||||
};
|
||||
|
||||
use graphene_core::raster::Channel;
|
||||
use graphene_core::color::Channel;
|
||||
Some(self.color.multiplied_alpha(P::AlphaChannel::from_linear(result)))
|
||||
}
|
||||
}
|
||||
|
||||
#[node_macro::node(skip_impl)]
|
||||
fn brush_stamp_generator(diameter: f64, color: Color, hardness: f64, flow: f64) -> BrushStampGenerator<Color> {
|
||||
fn brush_stamp_generator(#[unit(" px")] diameter: f64, color: Color, hardness: f64, flow: f64) -> BrushStampGenerator<Color> {
|
||||
// Diameter
|
||||
let radius = diameter / 2.;
|
||||
|
||||
|
@ -78,7 +80,6 @@ fn brush_stamp_generator(diameter: f64, color: Color, hardness: f64, flow: f64)
|
|||
fn blit<BlendFn>(mut target: RasterDataTable<CPU>, texture: Raster<CPU>, positions: Vec<DVec2>, blend_mode: BlendFn) -> RasterDataTable<CPU>
|
||||
where
|
||||
BlendFn: for<'any_input> Node<'any_input, (Color, Color), Output = Color>,
|
||||
GraphicElement: From<Raster<CPU>>,
|
||||
{
|
||||
if positions.is_empty() {
|
||||
return target;
|
||||
|
@ -239,7 +240,6 @@ async fn brush(_: impl Ctx, mut image_frame_table: RasterDataTable<CPU>, strokes
|
|||
let target = core::mem::take(&mut brush_plan.first_stroke_texture);
|
||||
extend_image_to_bounds((), target.to_table(), stroke_to_layer)
|
||||
} else {
|
||||
use crate::raster::empty_image;
|
||||
empty_image((), stroke_to_layer, Color::TRANSPARENT)
|
||||
// EmptyImageNode::new(CopiedNode::new(stroke_to_layer), CopiedNode::new(Color::TRANSPARENT)).eval(())
|
||||
};
|
||||
|
@ -393,7 +393,7 @@ mod test {
|
|||
(),
|
||||
RasterDataTable::<CPU>::new(Raster::new_cpu(Image::<Color>::default())),
|
||||
vec![BrushStroke {
|
||||
trace: vec![crate::vector::brush_stroke::BrushInputSample { position: DVec2::ZERO }],
|
||||
trace: vec![crate::brush_stroke::BrushInputSample { position: DVec2::ZERO }],
|
||||
style: BrushStyle {
|
||||
color: Color::BLACK,
|
||||
diameter: 20.,
|
|
@ -1,9 +1,9 @@
|
|||
use crate::instances::Instance;
|
||||
use crate::raster_types::CPU;
|
||||
use crate::raster_types::Raster;
|
||||
use crate::vector::brush_stroke::BrushStroke;
|
||||
use crate::vector::brush_stroke::BrushStyle;
|
||||
use crate::brush_stroke::BrushStroke;
|
||||
use crate::brush_stroke::BrushStyle;
|
||||
use dyn_any::DynAny;
|
||||
use graphene_core::instances::Instance;
|
||||
use graphene_core::raster_types::CPU;
|
||||
use graphene_core::raster_types::Raster;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
use std::sync::Arc;
|
||||
|
@ -15,11 +15,11 @@ struct BrushCacheImpl {
|
|||
prev_input: Vec<BrushStroke>,
|
||||
|
||||
// The strokes that have been fully processed and blended into the background.
|
||||
#[serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame_instance")]
|
||||
#[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_instance")]
|
||||
background: Instance<Raster<CPU>>,
|
||||
#[serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame_instance")]
|
||||
#[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_instance")]
|
||||
blended_image: Instance<Raster<CPU>>,
|
||||
#[serde(deserialize_with = "crate::graphene_core::raster::image::migrate_image_frame_instance")]
|
||||
#[serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame_instance")]
|
||||
last_stroke_texture: Instance<Raster<CPU>>,
|
||||
|
||||
// A cache for brush textures.
|
|
@ -1,8 +1,8 @@
|
|||
use crate::Color;
|
||||
use crate::math::bbox::AxisAlignedBbox;
|
||||
use crate::raster::BlendMode;
|
||||
use dyn_any::DynAny;
|
||||
use glam::DVec2;
|
||||
use graphene_core::blending::BlendMode;
|
||||
use graphene_core::color::Color;
|
||||
use graphene_core::math::bbox::AxisAlignedBbox;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
/// The style of a brush.
|
3
node-graph/gbrush/src/lib.rs
Normal file
3
node-graph/gbrush/src/lib.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub mod brush;
|
||||
pub mod brush_cache;
|
||||
pub mod brush_stroke;
|
|
@ -29,10 +29,11 @@ ctor = { workspace = true }
|
|||
rand_chacha = { workspace = true }
|
||||
bezier-rs = { workspace = true }
|
||||
specta = { workspace = true }
|
||||
rustybuzz = { workspace = true }
|
||||
image = { workspace = true }
|
||||
half = { workspace = true }
|
||||
tinyvec = { workspace = true }
|
||||
parley = { workspace = true }
|
||||
skrifa = { workspace = true }
|
||||
kurbo = { workspace = true }
|
||||
log = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
use crate::raster_types::{CPU, RasterDataTable};
|
||||
use crate::vector::VectorDataTable;
|
||||
use crate::{Color, Ctx};
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[node_macro::node(category("Debug"), name("Log to Console"))]
|
||||
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2, Color, Option<Color>)] value: T) -> T {
|
||||
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
|
||||
log::debug!("{:#?}", value);
|
||||
value
|
||||
}
|
||||
|
||||
/// Meant for debugging purposes, not general use. Returns the size of the input type in bytes.
|
||||
#[node_macro::node(category("Debug"))]
|
||||
|
|
|
@ -2,7 +2,9 @@ use crate::Ctx;
|
|||
use dyn_any::DynAny;
|
||||
use glam::{DVec2, IVec2, UVec2};
|
||||
|
||||
/// Obtain the X or Y component of a coordinate.
|
||||
/// Obtains the X or Y component of a coordinate point.
|
||||
///
|
||||
/// The inverse of this node is "Coordinate Value", which can have either or both its X and Y exposed as graph inputs.
|
||||
#[node_macro::node(name("Extract XY"), category("Math: Vector"))]
|
||||
fn extract_xy<T: Into<DVec2>>(_: impl Ctx, #[implementations(DVec2, IVec2, UVec2)] vector: T, axis: XY) -> f64 {
|
||||
match axis {
|
||||
|
|
|
@ -558,3 +558,53 @@ impl From<GraphicGroupTable> for GraphicElement {
|
|||
pub trait ToGraphicElement {
|
||||
fn to_graphic_element(&self) -> GraphicElement;
|
||||
}
|
||||
|
||||
/// Returns the value at the specified index in the collection.
|
||||
/// If that index has no value, the type's default value is returned.
|
||||
#[node_macro::node(category("General"))]
|
||||
fn index<T: AtIndex + Clone + Default>(
|
||||
_: impl Ctx,
|
||||
/// The collection of data, such as a list or table.
|
||||
#[implementations(
|
||||
Vec<Color>,
|
||||
Vec<Option<Color>>,
|
||||
Vec<f64>, Vec<u64>,
|
||||
Vec<DVec2>,
|
||||
VectorDataTable,
|
||||
RasterDataTable<CPU>,
|
||||
GraphicGroupTable,
|
||||
)]
|
||||
collection: T,
|
||||
/// The index of the item to retrieve, starting from 0 for the first item.
|
||||
index: u32,
|
||||
) -> T::Output
|
||||
where
|
||||
T::Output: Clone + Default,
|
||||
{
|
||||
collection.at_index(index as usize).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub trait AtIndex {
|
||||
type Output;
|
||||
fn at_index(&self, index: usize) -> Option<Self::Output>;
|
||||
}
|
||||
impl<T: Clone> AtIndex for Vec<T> {
|
||||
type Output = T;
|
||||
|
||||
fn at_index(&self, index: usize) -> Option<Self::Output> {
|
||||
self.get(index).cloned()
|
||||
}
|
||||
}
|
||||
impl<T: Clone> AtIndex for Instances<T> {
|
||||
type Output = Instances<T>;
|
||||
|
||||
fn at_index(&self, index: usize) -> Option<Self::Output> {
|
||||
let mut result_table = Self::default();
|
||||
if let Some(row) = self.instance_ref_iter().nth(index) {
|
||||
result_table.push(row.to_instance_cloned());
|
||||
Some(result_table)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
use crate::ArtboardGroupTable;
|
||||
use crate::Color;
|
||||
use crate::GraphicElement;
|
||||
use crate::GraphicGroupTable;
|
||||
use crate::gradient::GradientStops;
|
||||
use crate::graphene_core::registry::types::TextArea;
|
||||
use crate::raster_types::{CPU, GPU, RasterDataTable};
|
||||
use crate::vector::VectorDataTable;
|
||||
use crate::{Color, Context, Ctx};
|
||||
use crate::{Context, Ctx};
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[node_macro::node(category("Debug"), name("Log to Console"))]
|
||||
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2, Color, Option<Color>)] value: T) -> T {
|
||||
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
|
||||
log::debug!("{:#?}", value);
|
||||
value
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Text"))]
|
||||
fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> String {
|
||||
format!("{:?}", value)
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Text"))]
|
||||
fn string_concatenate(_: impl Ctx, #[implementations(String)] first: String, #[implementations(String)] second: String) -> String {
|
||||
fn string_concatenate(_: impl Ctx, #[implementations(String)] first: String, second: TextArea) -> String {
|
||||
first.clone() + &second
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Text"))]
|
||||
fn string_replace(_: impl Ctx, #[implementations(String)] string: String, from: String, to: String) -> String {
|
||||
fn string_replace(_: impl Ctx, #[implementations(String)] string: String, from: TextArea, to: TextArea) -> String {
|
||||
string.replace(&from, &to)
|
||||
}
|
||||
|
||||
|
@ -45,24 +45,42 @@ async fn switch<T, C: Send + 'n + Clone>(
|
|||
#[implementations(
|
||||
Context -> String,
|
||||
Context -> bool,
|
||||
Context -> f32,
|
||||
Context -> f64,
|
||||
Context -> u32,
|
||||
Context -> u64,
|
||||
Context -> DVec2,
|
||||
Context -> VectorDataTable,
|
||||
Context -> DAffine2,
|
||||
Context -> ArtboardGroupTable,
|
||||
Context -> VectorDataTable,
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> RasterDataTable<CPU>,
|
||||
Context -> RasterDataTable<GPU>,
|
||||
Context -> GraphicElement,
|
||||
Context -> Color,
|
||||
Context -> Option<Color>,
|
||||
Context -> GradientStops,
|
||||
)]
|
||||
if_true: impl Node<C, Output = T>,
|
||||
#[expose]
|
||||
#[implementations(
|
||||
Context -> String,
|
||||
Context -> bool,
|
||||
Context -> f32,
|
||||
Context -> f64,
|
||||
Context -> u32,
|
||||
Context -> u64,
|
||||
Context -> DVec2,
|
||||
Context -> VectorDataTable,
|
||||
Context -> DAffine2,
|
||||
Context -> ArtboardGroupTable,
|
||||
Context -> VectorDataTable,
|
||||
Context -> GraphicGroupTable,
|
||||
Context -> RasterDataTable<CPU>,
|
||||
Context -> RasterDataTable<GPU>,
|
||||
Context -> GraphicElement,
|
||||
Context -> Color,
|
||||
Context -> Option<Color>,
|
||||
Context -> GradientStops,
|
||||
)]
|
||||
if_false: impl Node<C, Output = T>,
|
||||
) -> T {
|
||||
|
|
|
@ -73,9 +73,17 @@ pub trait Convert<T>: Sized {
|
|||
fn convert(self) -> T;
|
||||
}
|
||||
|
||||
impl<T: ToString> Convert<String> for T {
|
||||
/// Converts this type into a `String` using its `ToString` implementation.
|
||||
#[inline]
|
||||
fn convert(self) -> String {
|
||||
self.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements the [`Convert`] trait for conversion between the cartesian product of Rust's primitive numeric types.
|
||||
macro_rules! impl_convert {
|
||||
($from:ty,$to:ty) => {
|
||||
($from:ty, $to:ty) => {
|
||||
impl Convert<$to> for $from {
|
||||
fn convert(self) -> $to {
|
||||
self as $to
|
||||
|
|
|
@ -12,13 +12,9 @@ pub mod color {
|
|||
pub use super::*;
|
||||
}
|
||||
|
||||
pub mod adjustments;
|
||||
pub mod brush_cache;
|
||||
pub mod curve;
|
||||
pub mod image;
|
||||
|
||||
pub use self::image::Image;
|
||||
pub use adjustments::*;
|
||||
|
||||
pub trait Bitmap {
|
||||
type Pixel: Pixel;
|
||||
|
|
|
@ -97,11 +97,20 @@ impl Raster<GPU> {
|
|||
let RasterStorage::Gpu(gpu) = &self.data else { unreachable!() };
|
||||
gpu.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Raster<GPU> {
|
||||
#[cfg(feature = "wgpu")]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
let data = self.data();
|
||||
data.width() == 0 || data.height() == 0
|
||||
}
|
||||
#[cfg(not(feature = "wgpu"))]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
impl Deref for Raster<GPU> {
|
||||
type Target = wgpu::Texture;
|
||||
|
@ -110,6 +119,7 @@ impl Deref for Raster<GPU> {
|
|||
self.data()
|
||||
}
|
||||
}
|
||||
|
||||
pub type RasterDataTable<Storage> = Instances<Raster<Storage>>;
|
||||
|
||||
// TODO: Make this not dupliated
|
||||
|
|
|
@ -30,6 +30,8 @@ pub mod types {
|
|||
pub type Resolution = glam::UVec2;
|
||||
/// DVec2 with px unit
|
||||
pub type PixelSize = glam::DVec2;
|
||||
/// String with one or more than one line
|
||||
pub type TextArea = String;
|
||||
}
|
||||
|
||||
// Translation struct between macro and definition
|
||||
|
|
|
@ -22,11 +22,12 @@ impl Default for Font {
|
|||
/// A cache of all loaded font data and preview urls along with the default font (send from `init_app` in `editor_api.rs`)
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq, DynAny)]
|
||||
pub struct FontCache {
|
||||
/// Actual font file data used for rendering a font with ttf_parser and rustybuzz
|
||||
/// Actual font file data used for rendering a font
|
||||
font_file_data: HashMap<Font, Vec<u8>>,
|
||||
/// Web font preview URLs used for showing fonts when live editing
|
||||
preview_urls: HashMap<Font, String>,
|
||||
}
|
||||
|
||||
impl FontCache {
|
||||
/// Returns the font family name if the font is cached, otherwise returns the fallback font family name if that is cached
|
||||
pub fn resolve_font<'a>(&'a self, font: &'a Font) -> Option<&'a Font> {
|
||||
|
@ -40,7 +41,7 @@ impl FontCache {
|
|||
}
|
||||
|
||||
/// Try to get the bytes for a font
|
||||
pub fn get<'a>(&'a self, font: &Font) -> Option<&'a Vec<u8>> {
|
||||
pub fn get(&self, font: &Font) -> Option<&Vec<u8>> {
|
||||
self.resolve_font(font).and_then(|font| self.font_file_data.get(font))
|
||||
}
|
||||
|
||||
|
|
|
@ -1,29 +1,67 @@
|
|||
use crate::vector::PointId;
|
||||
use bezier_rs::{ManipulatorGroup, Subpath};
|
||||
use glam::DVec2;
|
||||
use rustybuzz::ttf_parser::{GlyphId, OutlineBuilder};
|
||||
use rustybuzz::{GlyphBuffer, UnicodeBuffer};
|
||||
use core::cell::RefCell;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use parley::fontique::Blob;
|
||||
use parley::{Alignment, AlignmentOptions, FontContext, GlyphRun, Layout, LayoutContext, LineHeight, PositionedLayoutItem, StyleProperty};
|
||||
use skrifa::GlyphId;
|
||||
use skrifa::instance::{LocationRef, NormalizedCoord, Size};
|
||||
use skrifa::outline::{DrawSettings, OutlinePen};
|
||||
use skrifa::raw::FontRef as ReadFontsRef;
|
||||
use skrifa::{MetadataProvider, OutlineGlyph};
|
||||
use std::sync::Arc;
|
||||
|
||||
struct Builder {
|
||||
// Thread-local storage avoids expensive re-initialization of font and layout contexts
|
||||
// across multiple text rendering operations within the same thread
|
||||
thread_local! {
|
||||
static FONT_CONTEXT: RefCell<FontContext> = RefCell::new(FontContext::new());
|
||||
static LAYOUT_CONTEXT: RefCell<LayoutContext<()>> = RefCell::new(LayoutContext::new());
|
||||
}
|
||||
|
||||
struct PathBuilder {
|
||||
current_subpath: Subpath<PointId>,
|
||||
glyph_subpaths: Vec<Subpath<PointId>>,
|
||||
other_subpaths: Vec<Subpath<PointId>>,
|
||||
text_cursor: DVec2,
|
||||
offset: DVec2,
|
||||
ascender: f64,
|
||||
origin: DVec2,
|
||||
scale: f64,
|
||||
id: PointId,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
impl PathBuilder {
|
||||
fn point(&self, x: f32, y: f32) -> DVec2 {
|
||||
self.text_cursor + self.offset + DVec2::new(x as f64, self.ascender - y as f64) * self.scale
|
||||
// Y-axis inversion converts from font coordinate system (Y-up) to graphics coordinate system (Y-down)
|
||||
DVec2::new(self.origin.x + x as f64, self.origin.y - y as f64) * self.scale
|
||||
}
|
||||
|
||||
fn set_origin(&mut self, x: f64, y: f64) {
|
||||
self.origin = DVec2::new(x, y);
|
||||
}
|
||||
|
||||
fn draw_glyph(&mut self, glyph: &OutlineGlyph<'_>, size: f32, normalized_coords: &[NormalizedCoord], style_skew: Option<DAffine2>, skew: DAffine2) {
|
||||
let location_ref = LocationRef::new(normalized_coords);
|
||||
let settings = DrawSettings::unhinted(Size::new(size), location_ref);
|
||||
glyph.draw(settings, self).unwrap();
|
||||
|
||||
// Apply transforms in correct order: style-based skew first, then user-requested skew
|
||||
// This ensures font synthesis (italic) is applied before user transformations
|
||||
for glyph_subpath in &mut self.glyph_subpaths {
|
||||
if let Some(style_skew) = style_skew {
|
||||
glyph_subpath.apply_transform(style_skew);
|
||||
}
|
||||
|
||||
glyph_subpath.apply_transform(skew);
|
||||
}
|
||||
|
||||
if !self.glyph_subpaths.is_empty() {
|
||||
self.other_subpaths.extend(core::mem::take(&mut self.glyph_subpaths));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OutlineBuilder for Builder {
|
||||
impl OutlinePen for PathBuilder {
|
||||
fn move_to(&mut self, x: f32, y: f32) {
|
||||
if !self.current_subpath.is_empty() {
|
||||
self.other_subpaths.push(std::mem::replace(&mut self.current_subpath, Subpath::new(Vec::new(), false)));
|
||||
self.glyph_subpaths.push(std::mem::replace(&mut self.current_subpath, Subpath::new(Vec::new(), false)));
|
||||
}
|
||||
self.current_subpath.push_manipulator_group(ManipulatorGroup::new_anchor_with_id(self.point(x, y), self.id.next_id()));
|
||||
}
|
||||
|
@ -47,36 +85,10 @@ impl OutlineBuilder for Builder {
|
|||
|
||||
fn close(&mut self) {
|
||||
self.current_subpath.set_closed(true);
|
||||
self.other_subpaths.push(std::mem::replace(&mut self.current_subpath, Subpath::new(Vec::new(), false)));
|
||||
self.glyph_subpaths.push(std::mem::replace(&mut self.current_subpath, Subpath::new(Vec::new(), false)));
|
||||
}
|
||||
}
|
||||
|
||||
fn font_properties(buzz_face: &rustybuzz::Face, font_size: f64, line_height_ratio: f64) -> (f64, f64, UnicodeBuffer) {
|
||||
let scale = (buzz_face.units_per_em() as f64).recip() * font_size;
|
||||
let line_height = font_size * line_height_ratio;
|
||||
let buffer = UnicodeBuffer::new();
|
||||
(scale, line_height, buffer)
|
||||
}
|
||||
|
||||
fn push_str(buffer: &mut UnicodeBuffer, word: &str) {
|
||||
buffer.push_str(word);
|
||||
}
|
||||
|
||||
fn wrap_word(max_width: Option<f64>, glyph_buffer: &GlyphBuffer, font_size: f64, character_spacing: f64, x_pos: f64, space_glyph: Option<GlyphId>) -> bool {
|
||||
if let Some(max_width) = max_width {
|
||||
// We don't word wrap spaces (to match the browser)
|
||||
let all_glyphs = glyph_buffer.glyph_positions().iter().zip(glyph_buffer.glyph_infos());
|
||||
let non_space_glyphs = all_glyphs.take_while(|(_, info)| space_glyph != Some(GlyphId(info.glyph_id as u16)));
|
||||
let word_length: f64 = non_space_glyphs.map(|(pos, _)| pos.x_advance as f64 * character_spacing).sum();
|
||||
let scaled_word_length = word_length * font_size;
|
||||
|
||||
if scaled_word_length + x_pos > max_width {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct TypesettingConfig {
|
||||
pub font_size: f64,
|
||||
|
@ -84,6 +96,7 @@ pub struct TypesettingConfig {
|
|||
pub character_spacing: f64,
|
||||
pub max_width: Option<f64>,
|
||||
pub max_height: Option<f64>,
|
||||
pub tilt: f64,
|
||||
}
|
||||
|
||||
impl Default for TypesettingConfig {
|
||||
|
@ -91,163 +104,130 @@ impl Default for TypesettingConfig {
|
|||
Self {
|
||||
font_size: 24.,
|
||||
line_height_ratio: 1.2,
|
||||
character_spacing: 1.,
|
||||
character_spacing: 0.,
|
||||
max_width: None,
|
||||
max_height: None,
|
||||
tilt: 0.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_path(str: &str, buzz_face: Option<rustybuzz::Face>, typesetting: TypesettingConfig) -> Vec<Subpath<PointId>> {
|
||||
let Some(buzz_face) = buzz_face else { return vec![] };
|
||||
let space_glyph = buzz_face.glyph_index(' ');
|
||||
fn render_glyph_run(glyph_run: &GlyphRun<'_, ()>, path_builder: &mut PathBuilder, tilt: f64) {
|
||||
let mut run_x = glyph_run.offset();
|
||||
let run_y = glyph_run.baseline();
|
||||
|
||||
let (scale, line_height, mut buffer) = font_properties(&buzz_face, typesetting.font_size, typesetting.line_height_ratio);
|
||||
let run = glyph_run.run();
|
||||
|
||||
let mut builder = Builder {
|
||||
// User-requested tilt applied around baseline to avoid vertical displacement
|
||||
// Translation ensures rotation point is at the baseline, not origin
|
||||
let skew = DAffine2::from_translation(DVec2::new(0., run_y as f64))
|
||||
* DAffine2::from_cols_array(&[1., 0., -tilt.to_radians().tan(), 1., 0., 0.])
|
||||
* DAffine2::from_translation(DVec2::new(0., -run_y as f64));
|
||||
|
||||
let synthesis = run.synthesis();
|
||||
|
||||
// Font synthesis (e.g., synthetic italic) applied separately from user transforms
|
||||
// This preserves the distinction between font styling and user transformations
|
||||
let style_skew = synthesis.skew().map(|angle| {
|
||||
DAffine2::from_translation(DVec2::new(0., run_y as f64))
|
||||
* DAffine2::from_cols_array(&[1., 0., -angle.to_radians().tan() as f64, 1., 0., 0.])
|
||||
* DAffine2::from_translation(DVec2::new(0., -run_y as f64))
|
||||
});
|
||||
|
||||
let font = run.font();
|
||||
let font_size = run.font_size();
|
||||
|
||||
let normalized_coords = run.normalized_coords().iter().map(|coord| NormalizedCoord::from_bits(*coord)).collect::<Vec<_>>();
|
||||
|
||||
// TODO: This can be cached for better performance
|
||||
let font_collection_ref = font.data.as_ref();
|
||||
let font_ref = ReadFontsRef::from_index(font_collection_ref, font.index).unwrap();
|
||||
let outlines = font_ref.outline_glyphs();
|
||||
|
||||
for glyph in glyph_run.glyphs() {
|
||||
let glyph_x = run_x + glyph.x;
|
||||
let glyph_y = run_y - glyph.y;
|
||||
run_x += glyph.advance;
|
||||
|
||||
let glyph_id = GlyphId::from(glyph.id);
|
||||
if let Some(glyph_outline) = outlines.get(glyph_id) {
|
||||
path_builder.set_origin(glyph_x as f64, glyph_y as f64);
|
||||
path_builder.draw_glyph(&glyph_outline, font_size, &normalized_coords, style_skew, skew);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn layout_text(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig) -> Option<Layout<()>> {
|
||||
let font_cx = FONT_CONTEXT.with(Clone::clone);
|
||||
let mut font_cx = font_cx.borrow_mut();
|
||||
let layout_cx = LAYOUT_CONTEXT.with(Clone::clone);
|
||||
let mut layout_cx = layout_cx.borrow_mut();
|
||||
|
||||
let font_family = font_data.and_then(|font_data| {
|
||||
font_cx
|
||||
.collection
|
||||
.register_fonts(font_data, None)
|
||||
.first()
|
||||
.and_then(|(family_id, _)| font_cx.collection.family_name(*family_id).map(String::from))
|
||||
})?;
|
||||
|
||||
const DISPLAY_SCALE: f32 = 1.;
|
||||
let mut builder = layout_cx.ranged_builder(&mut font_cx, str, DISPLAY_SCALE, true);
|
||||
|
||||
builder.push_default(StyleProperty::FontSize(typesetting.font_size as f32));
|
||||
builder.push_default(StyleProperty::LetterSpacing(typesetting.character_spacing as f32));
|
||||
builder.push_default(StyleProperty::FontStack(parley::FontStack::Single(parley::FontFamily::Named(std::borrow::Cow::Owned(font_family)))));
|
||||
builder.push_default(LineHeight::FontSizeRelative(typesetting.line_height_ratio as f32));
|
||||
|
||||
let mut layout: Layout<()> = builder.build(str);
|
||||
|
||||
layout.break_all_lines(typesetting.max_width.map(|mw| mw as f32));
|
||||
layout.align(typesetting.max_width.map(|max_w| max_w as f32), Alignment::Left, AlignmentOptions::default());
|
||||
|
||||
Some(layout)
|
||||
}
|
||||
|
||||
pub fn to_path(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig) -> Vec<Subpath<PointId>> {
|
||||
let Some(layout) = layout_text(str, font_data, typesetting) else { return Vec::new() };
|
||||
|
||||
let mut path_builder = PathBuilder {
|
||||
current_subpath: Subpath::new(Vec::new(), false),
|
||||
glyph_subpaths: Vec::new(),
|
||||
other_subpaths: Vec::new(),
|
||||
text_cursor: DVec2::ZERO,
|
||||
offset: DVec2::ZERO,
|
||||
ascender: (buzz_face.ascender() as f64 / buzz_face.height() as f64) * typesetting.font_size / scale,
|
||||
scale,
|
||||
origin: DVec2::ZERO,
|
||||
scale: layout.scale() as f64,
|
||||
id: PointId::ZERO,
|
||||
};
|
||||
|
||||
for line in str.split('\n') {
|
||||
for (index, word) in SplitWordsIncludingSpaces::new(line).enumerate() {
|
||||
push_str(&mut buffer, word);
|
||||
let glyph_buffer = rustybuzz::shape(&buzz_face, &[], buffer);
|
||||
|
||||
// Don't wrap the first word
|
||||
if index != 0 && wrap_word(typesetting.max_width, &glyph_buffer, scale, typesetting.character_spacing, builder.text_cursor.x, space_glyph) {
|
||||
builder.text_cursor = DVec2::new(0., builder.text_cursor.y + line_height);
|
||||
for line in layout.lines() {
|
||||
for item in line.items() {
|
||||
if let PositionedLayoutItem::GlyphRun(glyph_run) = item {
|
||||
render_glyph_run(&glyph_run, &mut path_builder, typesetting.tilt);
|
||||
}
|
||||
|
||||
for (glyph_position, glyph_info) in glyph_buffer.glyph_positions().iter().zip(glyph_buffer.glyph_infos()) {
|
||||
let glyph_id = GlyphId(glyph_info.glyph_id as u16);
|
||||
if let Some(max_width) = typesetting.max_width {
|
||||
if space_glyph != Some(glyph_id) && builder.text_cursor.x + (glyph_position.x_advance as f64 * builder.scale * typesetting.character_spacing) >= max_width {
|
||||
builder.text_cursor = DVec2::new(0., builder.text_cursor.y + line_height);
|
||||
}
|
||||
}
|
||||
// Clip when the height is exceeded
|
||||
if typesetting.max_height.is_some_and(|max_height| builder.text_cursor.y > max_height - line_height) {
|
||||
return builder.other_subpaths;
|
||||
}
|
||||
|
||||
builder.offset = DVec2::new(glyph_position.x_offset as f64, glyph_position.y_offset as f64) * builder.scale;
|
||||
buzz_face.outline_glyph(glyph_id, &mut builder);
|
||||
if !builder.current_subpath.is_empty() {
|
||||
builder.other_subpaths.push(std::mem::replace(&mut builder.current_subpath, Subpath::new(Vec::new(), false)));
|
||||
}
|
||||
|
||||
builder.text_cursor += DVec2::new(glyph_position.x_advance as f64 * typesetting.character_spacing, glyph_position.y_advance as f64) * builder.scale;
|
||||
}
|
||||
|
||||
buffer = glyph_buffer.clear();
|
||||
}
|
||||
|
||||
builder.text_cursor = DVec2::new(0., builder.text_cursor.y + line_height);
|
||||
}
|
||||
|
||||
builder.other_subpaths
|
||||
path_builder.other_subpaths
|
||||
}
|
||||
|
||||
pub fn bounding_box(str: &str, buzz_face: Option<&rustybuzz::Face>, typesetting: TypesettingConfig, for_clipping_test: bool) -> DVec2 {
|
||||
// Show blank layer if font has not loaded
|
||||
let Some(buzz_face) = buzz_face else { return DVec2::ZERO };
|
||||
let space_glyph = buzz_face.glyph_index(' ');
|
||||
|
||||
let (scale, line_height, mut buffer) = font_properties(buzz_face, typesetting.font_size, typesetting.line_height_ratio);
|
||||
|
||||
let [mut text_cursor, mut bounds] = [DVec2::ZERO; 2];
|
||||
pub fn bounding_box(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig, for_clipping_test: bool) -> DVec2 {
|
||||
if !for_clipping_test {
|
||||
if let (Some(max_height), Some(max_width)) = (typesetting.max_height, typesetting.max_width) {
|
||||
return DVec2::new(max_width, max_height);
|
||||
}
|
||||
}
|
||||
|
||||
for line in str.split('\n') {
|
||||
for (index, word) in SplitWordsIncludingSpaces::new(line).enumerate() {
|
||||
push_str(&mut buffer, word);
|
||||
let Some(layout) = layout_text(str, font_data, typesetting) else { return DVec2::ZERO };
|
||||
|
||||
let glyph_buffer = rustybuzz::shape(buzz_face, &[], buffer);
|
||||
|
||||
// Don't wrap the first word
|
||||
if index != 0 && wrap_word(typesetting.max_width, &glyph_buffer, scale, typesetting.character_spacing, text_cursor.x, space_glyph) {
|
||||
text_cursor = DVec2::new(0., text_cursor.y + line_height);
|
||||
}
|
||||
|
||||
for (glyph_position, glyph_info) in glyph_buffer.glyph_positions().iter().zip(glyph_buffer.glyph_infos()) {
|
||||
let glyph_id = GlyphId(glyph_info.glyph_id as u16);
|
||||
if let Some(max_width) = typesetting.max_width {
|
||||
if space_glyph != Some(glyph_id) && text_cursor.x + (glyph_position.x_advance as f64 * scale * typesetting.character_spacing) >= max_width {
|
||||
text_cursor = DVec2::new(0., text_cursor.y + line_height);
|
||||
}
|
||||
}
|
||||
text_cursor += DVec2::new(glyph_position.x_advance as f64 * typesetting.character_spacing, glyph_position.y_advance as f64) * scale;
|
||||
bounds = bounds.max(text_cursor + DVec2::new(0., line_height));
|
||||
}
|
||||
|
||||
buffer = glyph_buffer.clear();
|
||||
}
|
||||
text_cursor = DVec2::new(0., text_cursor.y + line_height);
|
||||
bounds = bounds.max(text_cursor);
|
||||
}
|
||||
|
||||
if !for_clipping_test {
|
||||
if let Some(max_width) = typesetting.max_width {
|
||||
bounds.x = max_width;
|
||||
}
|
||||
if let Some(max_height) = typesetting.max_height {
|
||||
bounds.y = max_height;
|
||||
}
|
||||
}
|
||||
|
||||
bounds
|
||||
DVec2::new(layout.full_width() as f64, layout.height() as f64)
|
||||
}
|
||||
|
||||
pub fn load_face(data: &[u8]) -> rustybuzz::Face<'_> {
|
||||
rustybuzz::Face::from_slice(data, 0).expect("Loading font failed")
|
||||
pub fn load_font(data: &[u8]) -> Blob<u8> {
|
||||
Blob::new(Arc::new(data.to_vec()))
|
||||
}
|
||||
|
||||
pub fn lines_clipping(str: &str, buzz_face: Option<rustybuzz::Face>, typesetting: TypesettingConfig) -> bool {
|
||||
pub fn lines_clipping(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig) -> bool {
|
||||
let Some(max_height) = typesetting.max_height else { return false };
|
||||
let bounds = bounding_box(str, buzz_face.as_ref(), typesetting, true);
|
||||
let bounds = bounding_box(str, font_data, typesetting, true);
|
||||
max_height < bounds.y
|
||||
}
|
||||
|
||||
struct SplitWordsIncludingSpaces<'a> {
|
||||
text: &'a str,
|
||||
start_byte: usize,
|
||||
}
|
||||
|
||||
impl<'a> SplitWordsIncludingSpaces<'a> {
|
||||
pub fn new(text: &'a str) -> Self {
|
||||
Self { text, start_byte: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for SplitWordsIncludingSpaces<'a> {
|
||||
type Item = &'a str;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let mut eaten_chars = self.text[self.start_byte..].char_indices().skip_while(|(_, c)| *c != ' ').skip_while(|(_, c)| *c == ' ');
|
||||
let start_byte = self.start_byte;
|
||||
self.start_byte = eaten_chars.next().map_or(self.text.len(), |(offset, _)| self.start_byte + offset);
|
||||
(self.start_byte > start_byte).then(|| self.text.get(start_byte..self.start_byte)).flatten()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn split_words_including_spaces() {
|
||||
let mut split_words = SplitWordsIncludingSpaces::new("hello world .");
|
||||
assert_eq!(split_words.next(), Some("hello "));
|
||||
assert_eq!(split_words.next(), Some("world "));
|
||||
assert_eq!(split_words.next(), Some("."));
|
||||
assert_eq!(split_words.next(), None);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,10 +19,10 @@ async fn transform<T: 'n + 'static>(
|
|||
translate: DVec2,
|
||||
rotate: f64,
|
||||
scale: DVec2,
|
||||
shear: DVec2,
|
||||
skew: DVec2,
|
||||
_pivot: DVec2,
|
||||
) -> Instances<T> {
|
||||
let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., shear.y, shear.x, 1., 0., 0.]);
|
||||
let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., skew.y, skew.x, 1., 0., 0.]);
|
||||
|
||||
let footprint = ctx.try_footprint().copied();
|
||||
|
||||
|
@ -70,6 +70,7 @@ async fn boundless_footprint<T: 'n + 'static>(
|
|||
|
||||
transform_target.eval(ctx.into_context()).await
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Debug"))]
|
||||
async fn freeze_real_time<T: 'n + 'static>(
|
||||
ctx: impl Ctx + CloneVarArgs + ExtractAll,
|
||||
|
|
|
@ -86,6 +86,7 @@ async fn instance_position(ctx: impl Ctx + ExtractVarArgs) -> DVec2 {
|
|||
Default::default()
|
||||
}
|
||||
|
||||
// TODO: Make this return a u32 instead of an f64, but we ned to improve math-related compatibility with integer types first.
|
||||
#[node_macro::node(category("Instancing"), path(graphene_core::vector))]
|
||||
async fn instance_index(ctx: impl Ctx + ExtractIndex) -> f64 {
|
||||
match ctx.try_index() {
|
||||
|
|
|
@ -67,6 +67,10 @@ impl ClickTarget {
|
|||
self.bounding_box
|
||||
}
|
||||
|
||||
pub fn bounding_box_center(&self) -> Option<DVec2> {
|
||||
self.bounding_box.map(|bbox| bbox[0] + (bbox[1] - bbox[0]) / 2.)
|
||||
}
|
||||
|
||||
pub fn bounding_box_with_transform(&self, transform: DAffine2) -> Option<[DVec2; 2]> {
|
||||
self.bounding_box.map(|[a, b]| [transform.transform_point2(a), transform.transform_point2(b)])
|
||||
}
|
||||
|
|
|
@ -37,7 +37,13 @@ impl CornerRadius for [f64; 4] {
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn circle(_: impl Ctx, _primary: (), #[default(50.)] radius: f64) -> VectorDataTable {
|
||||
fn circle(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
#[unit(" px")]
|
||||
#[default(50.)]
|
||||
radius: f64,
|
||||
) -> VectorDataTable {
|
||||
let radius = radius.abs();
|
||||
VectorDataTable::new(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
|
||||
}
|
||||
|
@ -46,7 +52,9 @@ fn circle(_: impl Ctx, _primary: (), #[default(50.)] radius: f64) -> VectorDataT
|
|||
fn arc(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
#[default(50.)] radius: f64,
|
||||
#[unit(" px")]
|
||||
#[default(50.)]
|
||||
radius: f64,
|
||||
start_angle: Angle,
|
||||
#[default(270.)]
|
||||
#[range((0., 360.))]
|
||||
|
@ -66,7 +74,16 @@ fn arc(
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn ellipse(_: impl Ctx, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorDataTable {
|
||||
fn ellipse(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
#[unit(" px")]
|
||||
#[default(50)]
|
||||
radius_x: f64,
|
||||
#[unit(" px")]
|
||||
#[default(25)]
|
||||
radius_y: f64,
|
||||
) -> VectorDataTable {
|
||||
let radius = DVec2::new(radius_x, radius_y);
|
||||
let corner1 = -radius;
|
||||
let corner2 = radius;
|
||||
|
@ -87,8 +104,12 @@ fn ellipse(_: impl Ctx, _primary: (), #[default(50)] radius_x: f64, #[default(25
|
|||
fn rectangle<T: CornerRadius>(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
#[default(100)] width: f64,
|
||||
#[default(100)] height: f64,
|
||||
#[unit(" px")]
|
||||
#[default(100)]
|
||||
width: f64,
|
||||
#[unit(" px")]
|
||||
#[default(100)]
|
||||
height: f64,
|
||||
_individual_corner_radii: bool, // TODO: Move this to the bottom once we have a migration capability
|
||||
#[implementations(f64, [f64; 4])] corner_radius: T,
|
||||
#[default(true)] clamped: bool,
|
||||
|
@ -104,7 +125,9 @@ fn regular_polygon<T: AsU64>(
|
|||
#[hard_min(3.)]
|
||||
#[implementations(u32, u64, f64)]
|
||||
sides: T,
|
||||
#[default(50)] radius: f64,
|
||||
#[unit(" px")]
|
||||
#[default(50)]
|
||||
radius: f64,
|
||||
) -> VectorDataTable {
|
||||
let points = sides.as_u64();
|
||||
let radius: f64 = radius * 2.;
|
||||
|
@ -119,8 +142,12 @@ fn star<T: AsU64>(
|
|||
#[hard_min(2.)]
|
||||
#[implementations(u32, u64, f64)]
|
||||
sides: T,
|
||||
#[default(50)] radius_1: f64,
|
||||
#[default(25)] radius_2: f64,
|
||||
#[unit(" px")]
|
||||
#[default(50)]
|
||||
radius_1: f64,
|
||||
#[unit(" px")]
|
||||
#[default(25)]
|
||||
radius_2: f64,
|
||||
) -> VectorDataTable {
|
||||
let points = sides.as_u64();
|
||||
let diameter: f64 = radius_1 * 2.;
|
||||
|
@ -130,7 +157,7 @@ fn star<T: AsU64>(
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Shape"))]
|
||||
fn line(_: impl Ctx, _primary: (), #[default((0., -50.))] start: PixelSize, #[default((0., 50.))] end: PixelSize) -> VectorDataTable {
|
||||
fn line(_: impl Ctx, _primary: (), #[default(0., 0.)] start: PixelSize, #[default(100., 100.)] end: PixelSize) -> VectorDataTable {
|
||||
VectorDataTable::new(VectorData::from_subpath(Subpath::new_line(start, end)))
|
||||
}
|
||||
|
||||
|
@ -153,13 +180,14 @@ fn grid<T: GridSpacing>(
|
|||
_: impl Ctx,
|
||||
_primary: (),
|
||||
grid_type: GridType,
|
||||
#[unit(" px")]
|
||||
#[hard_min(0.)]
|
||||
#[default(10)]
|
||||
#[implementations(f64, DVec2)]
|
||||
spacing: T,
|
||||
#[default(30., 30.)] angles: DVec2,
|
||||
#[default(10)] columns: u32,
|
||||
#[default(10)] rows: u32,
|
||||
#[default(30., 30.)] angles: DVec2,
|
||||
) -> VectorDataTable {
|
||||
let (x_spacing, y_spacing) = spacing.as_dvec2().into();
|
||||
let (angle_a, angle_b) = angles.into();
|
||||
|
@ -251,11 +279,11 @@ mod tests {
|
|||
#[test]
|
||||
fn isometric_grid_test() {
|
||||
// Doesn't crash with weird angles
|
||||
grid((), (), GridType::Isometric, 0., (0., 0.).into(), 5, 5);
|
||||
grid((), (), GridType::Isometric, 90., (90., 90.).into(), 5, 5);
|
||||
grid((), (), GridType::Isometric, 0., 5, 5, (0., 0.).into());
|
||||
grid((), (), GridType::Isometric, 90., 5, 5, (90., 90.).into());
|
||||
|
||||
// Works properly
|
||||
let grid = grid((), (), GridType::Isometric, 10., (30., 30.).into(), 5, 5);
|
||||
let grid = grid((), (), GridType::Isometric, 10., 5, 5, (30., 30.).into());
|
||||
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() {
|
||||
|
@ -270,7 +298,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn skew_isometric_grid_test() {
|
||||
let grid = grid((), (), GridType::Isometric, 10., (40., 30.).into(), 5, 5);
|
||||
let grid = grid((), (), GridType::Isometric, 10., 5, 5, (40., 30.).into());
|
||||
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.point_domain.ids().len(), 5 * 5);
|
||||
assert_eq!(grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
|
||||
for (_, bezier, _, _) in grid.instance_ref_iter().next().unwrap().instance.segment_bezier_iter() {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
pub mod algorithms;
|
||||
pub mod brush_stroke;
|
||||
pub mod click_target;
|
||||
pub mod generator_nodes;
|
||||
pub mod misc;
|
||||
|
|
|
@ -65,6 +65,7 @@ async fn assign_colors<T>(
|
|||
randomize: bool,
|
||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_seed")]
|
||||
/// The seed used for randomization.
|
||||
/// Seed to determine unique variations on the randomized color selection.
|
||||
seed: SeedValue,
|
||||
#[widget(ParsedWidgetOverride::Custom = "assign_colors_repeat_every")]
|
||||
/// The number of elements to span across the gradient before repeating. A 0 value will span the entire gradient once.
|
||||
|
@ -165,6 +166,7 @@ async fn stroke<C: Into<Option<Color>> + 'n + Send, V>(
|
|||
#[default(Color::BLACK)]
|
||||
/// The stroke color.
|
||||
color: C,
|
||||
#[unit(" px")]
|
||||
#[default(2.)]
|
||||
/// The stroke weight.
|
||||
weight: f64,
|
||||
|
@ -183,6 +185,7 @@ async fn stroke<C: Into<Option<Color>> + 'n + Send, V>(
|
|||
/// The stroke dash lengths. Each length forms a distance in a pattern where the first length is a dash, the second is a gap, and so on. If the list is an odd length, the pattern repeats with solid-gap roles reversed.
|
||||
dash_lengths: Vec<f64>,
|
||||
/// The phase offset distance from the starting point of the dash pattern.
|
||||
#[unit(" px")]
|
||||
dash_offset: f64,
|
||||
) -> Instances<V>
|
||||
where
|
||||
|
@ -253,7 +256,9 @@ async fn circular_repeat<I: 'n + Send + Clone>(
|
|||
// TODO: Implement other GraphicElementRendered types.
|
||||
#[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>)] instance: Instances<I>,
|
||||
angle_offset: Angle,
|
||||
#[default(5)] radius: f64,
|
||||
#[unit(" px")]
|
||||
#[default(5)]
|
||||
radius: f64,
|
||||
#[default(5)] instances: IntegerCount,
|
||||
) -> Instances<I> {
|
||||
let count = instances.max(1);
|
||||
|
@ -363,7 +368,7 @@ async fn mirror<I: 'n + Send + Clone>(
|
|||
_: impl Ctx,
|
||||
#[implementations(GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>)] instance: Instances<I>,
|
||||
#[default(ReferencePoint::Center)] relative_to_bounds: ReferencePoint,
|
||||
offset: f64,
|
||||
#[unit(" px")] offset: f64,
|
||||
#[range((-90., 90.))] angle: Angle,
|
||||
#[default(true)] keep_original: bool,
|
||||
) -> Instances<I>
|
||||
|
@ -1143,10 +1148,10 @@ async fn sample_polyline(
|
|||
_: impl Ctx,
|
||||
vector_data: VectorDataTable,
|
||||
spacing: PointSpacingType,
|
||||
separation: f64,
|
||||
quantity: f64,
|
||||
start_offset: f64,
|
||||
stop_offset: f64,
|
||||
#[unit(" px")] separation: f64,
|
||||
quantity: u32,
|
||||
#[unit(" px")] start_offset: f64,
|
||||
#[unit(" px")] stop_offset: f64,
|
||||
adaptive_spacing: bool,
|
||||
subpath_segment_lengths: Vec<f64>,
|
||||
) -> VectorDataTable {
|
||||
|
@ -1186,7 +1191,7 @@ async fn sample_polyline(
|
|||
|
||||
let amount = match spacing {
|
||||
PointSpacingType::Separation => separation,
|
||||
PointSpacingType::Quantity => quantity,
|
||||
PointSpacingType::Quantity => quantity as f64,
|
||||
};
|
||||
let Some(mut sample_bezpath) = sample_polyline_on_bezpath(bezpath, spacing, amount, start_offset, stop_offset, adaptive_spacing, current_bezpath_segments_length) else {
|
||||
continue;
|
||||
|
@ -1392,6 +1397,7 @@ async fn tangent_on_path(
|
|||
async fn poisson_disk_points(
|
||||
_: impl Ctx,
|
||||
vector_data: VectorDataTable,
|
||||
#[unit(" px")]
|
||||
#[default(10.)]
|
||||
#[hard_min(0.01)]
|
||||
separation_disk_diameter: f64,
|
||||
|
@ -1502,7 +1508,14 @@ async fn spline(_: impl Ctx, vector_data: VectorDataTable) -> VectorDataTable {
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Vector: Modifier"), path(graphene_core::vector))]
|
||||
async fn jitter_points(_: impl Ctx, vector_data: VectorDataTable, #[default(5.)] amount: f64, seed: SeedValue) -> VectorDataTable {
|
||||
async fn jitter_points(
|
||||
_: impl Ctx,
|
||||
vector_data: VectorDataTable,
|
||||
#[unit(" px")]
|
||||
#[default(5.)]
|
||||
amount: f64,
|
||||
seed: SeedValue,
|
||||
) -> VectorDataTable {
|
||||
let mut result_table = VectorDataTable::default();
|
||||
|
||||
for mut vector_data_instance in vector_data.instance_iter() {
|
||||
|
@ -2084,7 +2097,7 @@ mod test {
|
|||
#[tokio::test]
|
||||
async fn sample_polyline() {
|
||||
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
||||
let sample_polyline = super::sample_polyline(Footprint::default(), vector_node(path), PointSpacingType::Separation, 30., 0., 0., 0., false, vec![100.]).await;
|
||||
let sample_polyline = super::sample_polyline(Footprint::default(), vector_node(path), PointSpacingType::Separation, 30., 0, 0., 0., false, vec![100.]).await;
|
||||
let sample_polyline = sample_polyline.instance_ref_iter().next().unwrap().instance;
|
||||
assert_eq!(sample_polyline.point_domain.positions().len(), 4);
|
||||
for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 0., DVec2::X * 30., DVec2::X * 60., DVec2::X * 90.]) {
|
||||
|
@ -2094,7 +2107,7 @@ mod test {
|
|||
#[tokio::test]
|
||||
async fn sample_polyline_adaptive_spacing() {
|
||||
let path = Subpath::from_bezier(&Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::ZERO, DVec2::X * 100., DVec2::X * 100.));
|
||||
let sample_polyline = super::sample_polyline(Footprint::default(), vector_node(path), PointSpacingType::Separation, 18., 0., 45., 10., true, vec![100.]).await;
|
||||
let sample_polyline = super::sample_polyline(Footprint::default(), vector_node(path), PointSpacingType::Separation, 18., 0, 45., 10., true, vec![100.]).await;
|
||||
let sample_polyline = sample_polyline.instance_ref_iter().next().unwrap().instance;
|
||||
assert_eq!(sample_polyline.point_domain.positions().len(), 4);
|
||||
for (pos, expected) in sample_polyline.point_domain.positions().iter().zip([DVec2::X * 45., DVec2::X * 60., DVec2::X * 75., DVec2::X * 90.]) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use glam::DVec2;
|
||||
use graphene_core::gradient::GradientStops;
|
||||
use graphene_core::registry::types::{Fraction, Percentage};
|
||||
use graphene_core::registry::types::{Fraction, Percentage, TextArea};
|
||||
use graphene_core::{Color, Ctx, num_traits};
|
||||
use log::warn;
|
||||
use math_parser::ast;
|
||||
|
@ -78,8 +78,12 @@ fn math<U: num_traits::float::Float>(
|
|||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn add<U: Add<T>, T>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] augend: U,
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] addend: T,
|
||||
/// The left-hand side of the addition operation.
|
||||
#[implementations(f64, f32, u32, DVec2, f64, DVec2)]
|
||||
augend: U,
|
||||
/// The right-hand side of the addition operation.
|
||||
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
|
||||
addend: T,
|
||||
) -> <U as Add<T>>::Output {
|
||||
augend + addend
|
||||
}
|
||||
|
@ -88,8 +92,12 @@ fn add<U: Add<T>, T>(
|
|||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn subtract<U: Sub<T>, T>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] minuend: U,
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)] subtrahend: T,
|
||||
/// The left-hand side of the subtraction operation.
|
||||
#[implementations(f64, f32, u32, DVec2, f64, DVec2)]
|
||||
minuend: U,
|
||||
/// The right-hand side of the subtraction operation.
|
||||
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
|
||||
subtrahend: T,
|
||||
) -> <U as Sub<T>>::Output {
|
||||
minuend - subtrahend
|
||||
}
|
||||
|
@ -98,9 +106,12 @@ fn subtract<U: Sub<T>, T>(
|
|||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn multiply<U: Mul<T>, T>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, f64, DVec2)] multiplier: U,
|
||||
/// The left-hand side of the multiplication operation.
|
||||
#[implementations(f64, f32, u32, DVec2, f64, DVec2)]
|
||||
multiplier: U,
|
||||
/// The right-hand side of the multiplication operation.
|
||||
#[default(1.)]
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, DVec2, f64)]
|
||||
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
|
||||
multiplicand: T,
|
||||
) -> <U as Mul<T>>::Output {
|
||||
multiplier * multiplicand
|
||||
|
@ -112,7 +123,10 @@ fn multiply<U: Mul<T>, T>(
|
|||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn divide<U: Div<T> + Default + PartialEq, T: Default + PartialEq>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, f64, f32, f32, u32, u32, DVec2, DVec2, f64)] numerator: U,
|
||||
/// The left-hand side of the division operation.
|
||||
#[implementations(f64, f64, f32, f32, u32, u32, DVec2, DVec2, f64)]
|
||||
numerator: U,
|
||||
/// The right-hand side of the division operation.
|
||||
#[default(1.)]
|
||||
#[implementations(f64, f64, f32, f32, u32, u32, DVec2, f64, DVec2)]
|
||||
denominator: T,
|
||||
|
@ -130,10 +144,15 @@ where
|
|||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn modulo<U: Rem<T, Output: Add<T, Output: Rem<T, Output = U::Output>>>, T: Copy>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32, DVec2, DVec2, f64)] numerator: U,
|
||||
/// The left-hand side of the modulo operation.
|
||||
#[implementations(f64, f32, u32, DVec2, DVec2, f64)]
|
||||
numerator: U,
|
||||
/// The right-hand side of the modulo operation.
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32, DVec2, f64, DVec2)]
|
||||
#[implementations(f64, f32, u32, DVec2, f64, DVec2)]
|
||||
modulus: T,
|
||||
/// Ensures the result will always be positive, even if the numerator is negative.
|
||||
#[default(true)]
|
||||
always_positive: bool,
|
||||
) -> <U as Rem<T>>::Output {
|
||||
if always_positive { (numerator % modulus + modulus) % modulus } else { numerator % modulus }
|
||||
|
@ -143,9 +162,12 @@ fn modulo<U: Rem<T, Output: Add<T, Output: Rem<T, Output = U::Output>>>, T: Copy
|
|||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn exponent<U: Pow<T>, T>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f64, &f64, f32, &f32, f32, &f32, u32, &u32, u32, &u32)] base: U,
|
||||
/// The base number that will be raised to the power.
|
||||
#[implementations(f64, f32, u32)]
|
||||
base: U,
|
||||
/// The power to which the base number will be raised.
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f64, &f64, &f64, f32, f32, &f32, &f32, u32, u32, &u32, &u32)]
|
||||
#[implementations(f64, f32, u32)]
|
||||
power: T,
|
||||
) -> <U as num_traits::Pow<T>>::Output {
|
||||
base.pow(power)
|
||||
|
@ -155,9 +177,11 @@ fn exponent<U: Pow<T>, T>(
|
|||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn root<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The number for which the nth root will be calculated.
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f32)]
|
||||
radicand: U,
|
||||
/// The degree of the root to be calculated. Square root is 2, cube root is 3, and so on.
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f32)]
|
||||
degree: U,
|
||||
|
@ -175,7 +199,10 @@ fn root<U: num_traits::float::Float>(
|
|||
#[node_macro::node(category("Math: Arithmetic"))]
|
||||
fn logarithm<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, f32)] value: U,
|
||||
/// The number for which the logarithm will be calculated.
|
||||
#[implementations(f64, f32)]
|
||||
value: U,
|
||||
/// The base of the logarithm, such as 2 (binary), 10 (decimal), and e (natural logarithm).
|
||||
#[default(2.)]
|
||||
#[implementations(f64, f32)]
|
||||
base: U,
|
||||
|
@ -193,39 +220,83 @@ fn logarithm<U: num_traits::float::Float>(
|
|||
|
||||
/// The sine trigonometric function (sin) calculates the ratio of the angle's opposite side length to its hypotenuse length.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn sine<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U {
|
||||
fn sine<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The given angle.
|
||||
#[implementations(f64, f32)]
|
||||
theta: U,
|
||||
/// Whether the given angle should be interpreted as radians instead of degrees.
|
||||
radians: bool,
|
||||
) -> U {
|
||||
if radians { theta.sin() } else { theta.to_radians().sin() }
|
||||
}
|
||||
|
||||
/// The cosine trigonometric function (cos) calculates the ratio of the angle's adjacent side length to its hypotenuse length.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn cosine<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U {
|
||||
fn cosine<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The given angle.
|
||||
#[implementations(f64, f32)]
|
||||
theta: U,
|
||||
/// Whether the given angle should be interpreted as radians instead of degrees.
|
||||
radians: bool,
|
||||
) -> U {
|
||||
if radians { theta.cos() } else { theta.to_radians().cos() }
|
||||
}
|
||||
|
||||
/// The tangent trigonometric function (tan) calculates the ratio of the angle's opposite side length to its adjacent side length.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn tangent<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] theta: U, radians: bool) -> U {
|
||||
fn tangent<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The given angle.
|
||||
#[implementations(f64, f32)]
|
||||
theta: U,
|
||||
/// Whether the given angle should be interpreted as radians instead of degrees.
|
||||
radians: bool,
|
||||
) -> U {
|
||||
if radians { theta.tan() } else { theta.to_radians().tan() }
|
||||
}
|
||||
|
||||
/// The inverse sine trigonometric function (asin) calculates the angle whose sine is the specified value.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn sine_inverse<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U {
|
||||
fn sine_inverse<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The given value for which the angle will be calculated. Must be in the range [-1, 1] or else the result will be NaN.
|
||||
#[implementations(f64, f32)]
|
||||
value: U,
|
||||
/// Whether the resulting angle should be given in as radians instead of degrees.
|
||||
radians: bool,
|
||||
) -> U {
|
||||
if radians { value.asin() } else { value.asin().to_degrees() }
|
||||
}
|
||||
|
||||
/// The inverse cosine trigonometric function (acos) calculates the angle whose cosine is the specified value.
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn cosine_inverse<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U, radians: bool) -> U {
|
||||
fn cosine_inverse<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The given value for which the angle will be calculated. Must be in the range [-1, 1] or else the result will be NaN.
|
||||
#[implementations(f64, f32)]
|
||||
value: U,
|
||||
/// Whether the resulting angle should be given in as radians instead of degrees.
|
||||
radians: bool,
|
||||
) -> U {
|
||||
if radians { value.acos() } else { value.acos().to_degrees() }
|
||||
}
|
||||
|
||||
/// The inverse tangent trigonometric function (atan or atan2, depending on input type) calculates:
|
||||
/// atan: the angle whose tangent is the specified scalar number.
|
||||
/// atan2: the angle of a ray from the origin to the specified coordinate.
|
||||
///
|
||||
/// The resulting angle is always in the range [0°, 180°] or, in radians, [-π/2, π/2].
|
||||
#[node_macro::node(category("Math: Trig"))]
|
||||
fn tangent_inverse<U: TangentInverse>(_: impl Ctx, #[implementations(f64, f32, DVec2)] value: U, radians: bool) -> U::Output {
|
||||
fn tangent_inverse<U: TangentInverse>(
|
||||
_: impl Ctx,
|
||||
/// The given value for which the angle will be calculated.
|
||||
#[implementations(f64, f32, DVec2)]
|
||||
value: U,
|
||||
/// Whether the resulting angle should be given in as radians instead of degrees.
|
||||
radians: bool,
|
||||
) -> U::Output {
|
||||
value.atan(radians)
|
||||
}
|
||||
|
||||
|
@ -257,10 +328,13 @@ impl TangentInverse for DVec2 {
|
|||
fn random<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
/// Seed to determine the unique variation of which number will be generated.
|
||||
seed: u64,
|
||||
/// The smaller end of the range within which the random number will be generated.
|
||||
#[implementations(f64, f32)]
|
||||
#[default(0.)]
|
||||
min: U,
|
||||
/// The larger end of the range within which the random number will be generated.
|
||||
#[implementations(f64, f32)]
|
||||
#[default(1.)]
|
||||
max: U,
|
||||
|
@ -294,37 +368,73 @@ fn to_f64<U: num_traits::int::PrimInt>(_: impl Ctx, #[implementations(u32, u64)]
|
|||
|
||||
/// The rounding function (round) maps an input value to its nearest whole number. Halfway values are rounded away from zero.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn round<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
|
||||
fn round<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The number which will be rounded.
|
||||
#[implementations(f64, f32)]
|
||||
value: U,
|
||||
) -> U {
|
||||
value.round()
|
||||
}
|
||||
|
||||
/// The floor function (floor) reduces an input value to its nearest larger whole number, unless the input number is already whole.
|
||||
/// The floor function (floor) rounds down an input value to the nearest whole number, unless the input number is already whole.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn floor<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
|
||||
fn floor<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The number which will be rounded down.
|
||||
#[implementations(f64, f32)]
|
||||
value: U,
|
||||
) -> U {
|
||||
value.floor()
|
||||
}
|
||||
|
||||
/// The ceiling function (ceil) increases an input value to its nearest smaller whole number, unless the input number is already whole.
|
||||
/// The ceiling function (ceil) rounds up an input value to the nearest whole number, unless the input number is already whole.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn ceiling<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
|
||||
fn ceiling<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The number which will be rounded up.
|
||||
#[implementations(f64, f32)]
|
||||
value: U,
|
||||
) -> U {
|
||||
value.ceil()
|
||||
}
|
||||
|
||||
/// The absolute value function (abs) removes the negative sign from an input value, if present.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn absolute_value<U: num_traits::float::Float>(_: impl Ctx, #[implementations(f64, f32)] value: U) -> U {
|
||||
fn absolute_value<U: num_traits::float::Float>(
|
||||
_: impl Ctx,
|
||||
/// The number which will be made positive.
|
||||
#[implementations(f64, f32)]
|
||||
value: U,
|
||||
) -> U {
|
||||
value.abs()
|
||||
}
|
||||
|
||||
/// The minimum function (min) picks the smaller of two numbers.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn min<T: std::cmp::PartialOrd>(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T {
|
||||
fn min<T: std::cmp::PartialOrd>(
|
||||
_: impl Ctx,
|
||||
/// One of the two numbers, of which the lesser will be returned.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
value: T,
|
||||
/// The other of the two numbers, of which the lesser will be returned.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
other_value: T,
|
||||
) -> T {
|
||||
if value < other_value { value } else { other_value }
|
||||
}
|
||||
|
||||
/// The maximum function (max) picks the larger of two numbers.
|
||||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn max<T: std::cmp::PartialOrd>(_: impl Ctx, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T, #[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] other_value: T) -> T {
|
||||
fn max<T: std::cmp::PartialOrd>(
|
||||
_: impl Ctx,
|
||||
/// One of the two numbers, of which the greater will be returned.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
value: T,
|
||||
/// The other of the two numbers, of which the greater will be returned.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
other_value: T,
|
||||
) -> T {
|
||||
if value > other_value { value } else { other_value }
|
||||
}
|
||||
|
||||
|
@ -332,9 +442,15 @@ fn max<T: std::cmp::PartialOrd>(_: impl Ctx, #[implementations(f64, &f64, f32, &
|
|||
#[node_macro::node(category("Math: Numeric"))]
|
||||
fn clamp<T: std::cmp::PartialOrd>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] value: T,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] min: T,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, &str)] max: T,
|
||||
/// The number to be clamped, which will be restricted to the range between the minimum and maximum values.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
value: T,
|
||||
/// The left (smaller) side of the range. The output will never be less than this number.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
min: T,
|
||||
/// The right (greater) side of the range. The output will never be greater than this number.
|
||||
#[implementations(f64, f32, u32, &str)]
|
||||
max: T,
|
||||
) -> T {
|
||||
let (min, max) = if min < max { (min, max) } else { (max, min) };
|
||||
if value < min {
|
||||
|
@ -350,8 +466,12 @@ fn clamp<T: std::cmp::PartialOrd>(
|
|||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn equals<U: std::cmp::PartialEq<T>, T>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] other_value: U,
|
||||
/// One of the two numbers to compare for equality.
|
||||
#[implementations(f64, f32, u32, DVec2, &str)]
|
||||
value: T,
|
||||
/// The other of the two numbers to compare for equality.
|
||||
#[implementations(f64, f32, u32, DVec2, &str)]
|
||||
other_value: U,
|
||||
) -> bool {
|
||||
other_value == value
|
||||
}
|
||||
|
@ -360,8 +480,12 @@ fn equals<U: std::cmp::PartialEq<T>, T>(
|
|||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn not_equals<U: std::cmp::PartialEq<T>, T>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] value: T,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32, DVec2, &DVec2, &str)] other_value: U,
|
||||
/// One of the two numbers to compare for inequality.
|
||||
#[implementations(f64, f32, u32, DVec2, &str)]
|
||||
value: T,
|
||||
/// The other of the two numbers to compare for inequality.
|
||||
#[implementations(f64, f32, u32, DVec2, &str)]
|
||||
other_value: U,
|
||||
) -> bool {
|
||||
other_value != value
|
||||
}
|
||||
|
@ -371,8 +495,13 @@ fn not_equals<U: std::cmp::PartialEq<T>, T>(
|
|||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn less_than<T: std::cmp::PartialOrd<T>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32)] value: T,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32)] other_value: T,
|
||||
/// The number on the left-hand side of the comparison.
|
||||
#[implementations(f64, f32, u32)]
|
||||
value: T,
|
||||
/// The number on the right-hand side of the comparison.
|
||||
#[implementations(f64, f32, u32)]
|
||||
other_value: T,
|
||||
/// Uses the less-than-or-equal operation (<=) instead of the less-than operation (<).
|
||||
or_equal: bool,
|
||||
) -> bool {
|
||||
if or_equal { value <= other_value } else { value < other_value }
|
||||
|
@ -383,8 +512,13 @@ fn less_than<T: std::cmp::PartialOrd<T>>(
|
|||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn greater_than<T: std::cmp::PartialOrd<T>>(
|
||||
_: impl Ctx,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32)] value: T,
|
||||
#[implementations(f64, &f64, f32, &f32, u32, &u32)] other_value: T,
|
||||
/// The number on the left-hand side of the comparison.
|
||||
#[implementations(f64, f32, u32)]
|
||||
value: T,
|
||||
/// The number on the right-hand side of the comparison.
|
||||
#[implementations(f64, f32, u32)]
|
||||
other_value: T,
|
||||
/// Uses the greater-than-or-equal operation (>=) instead of the greater-than operation (>).
|
||||
or_equal: bool,
|
||||
) -> bool {
|
||||
if or_equal { value >= other_value } else { value > other_value }
|
||||
|
@ -392,19 +526,35 @@ fn greater_than<T: std::cmp::PartialOrd<T>>(
|
|||
|
||||
/// The logical or operation (||) returns true if either of the two inputs are true, or false if both are false.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn logical_or(_: impl Ctx, value: bool, other_value: bool) -> bool {
|
||||
fn logical_or(
|
||||
_: impl Ctx,
|
||||
/// One of the two boolean values, either of which may be true for the node to output true.
|
||||
value: bool,
|
||||
/// The other of the two boolean values, either of which may be true for the node to output true.
|
||||
other_value: bool,
|
||||
) -> bool {
|
||||
value || other_value
|
||||
}
|
||||
|
||||
/// The logical and operation (&&) returns true if both of the two inputs are true, or false if any are false.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn logical_and(_: impl Ctx, value: bool, other_value: bool) -> bool {
|
||||
fn logical_and(
|
||||
_: impl Ctx,
|
||||
/// One of the two boolean values, both of which must be true for the node to output true.
|
||||
value: bool,
|
||||
/// The other of the two boolean values, both of which must be true for the node to output true.
|
||||
other_value: bool,
|
||||
) -> bool {
|
||||
value && other_value
|
||||
}
|
||||
|
||||
/// The logical not operation (!) reverses true and false value of the input.
|
||||
#[node_macro::node(category("Math: Logic"))]
|
||||
fn logical_not(_: impl Ctx, input: bool) -> bool {
|
||||
fn logical_not(
|
||||
_: impl Ctx,
|
||||
/// The boolean value to be reversed.
|
||||
input: bool,
|
||||
) -> bool {
|
||||
!input
|
||||
}
|
||||
|
||||
|
@ -453,7 +603,7 @@ fn gradient_value(_: impl Ctx, _primary: (), gradient: GradientStops) -> Gradien
|
|||
|
||||
/// Constructs a string value which may be set to any plain text.
|
||||
#[node_macro::node(category("Value"))]
|
||||
fn string_value(_: impl Ctx, _primary: (), string: String) -> String {
|
||||
fn string_value(_: impl Ctx, _primary: (), string: TextArea) -> String {
|
||||
string
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,10 @@ loading = ["serde_json"]
|
|||
dyn-any = { workspace = true }
|
||||
graphene-core = { workspace = true }
|
||||
graphene-path-bool = { workspace = true }
|
||||
graphene-brush = { workspace = true }
|
||||
graphene-application-io = { workspace = true }
|
||||
graphene-svg-renderer = { workspace = true }
|
||||
graphene-raster-nodes = { workspace = true }
|
||||
|
||||
# Workspace dependencies
|
||||
log = { workspace = true }
|
||||
|
|
|
@ -363,17 +363,6 @@ impl NodeInput {
|
|||
NodeInput::Reflection(_) => false,
|
||||
}
|
||||
}
|
||||
/// Network node inputs in the document network are not displayed, but still exist in the compiled network
|
||||
pub fn is_exposed_to_frontend(&self, is_document_network: bool) -> bool {
|
||||
match self {
|
||||
NodeInput::Node { .. } => true,
|
||||
NodeInput::Value { exposed, .. } => *exposed,
|
||||
NodeInput::Network { .. } => !is_document_network,
|
||||
NodeInput::Inline(_) => false,
|
||||
NodeInput::Scope(_) => false,
|
||||
NodeInput::Reflection(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ty(&self) -> Type {
|
||||
match self {
|
||||
|
@ -1250,24 +1239,28 @@ impl NodeNetwork {
|
|||
|
||||
/// Create a [`RecursiveNodeIter`] that iterates over all [`DocumentNode`]s, including ones that are deeply nested.
|
||||
pub fn recursive_nodes(&self) -> RecursiveNodeIter<'_> {
|
||||
let nodes = self.nodes.iter().collect();
|
||||
let nodes = self.nodes.iter().map(|(id, node)| (id, node, Vec::new())).collect();
|
||||
RecursiveNodeIter { nodes }
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over all [`DocumentNode`]s, including ones that are deeply nested.
|
||||
pub struct RecursiveNodeIter<'a> {
|
||||
nodes: Vec<(&'a NodeId, &'a DocumentNode)>,
|
||||
nodes: Vec<(&'a NodeId, &'a DocumentNode, Vec<NodeId>)>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for RecursiveNodeIter<'a> {
|
||||
type Item = (&'a NodeId, &'a DocumentNode);
|
||||
type Item = (&'a NodeId, &'a DocumentNode, Vec<NodeId>);
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let node = self.nodes.pop()?;
|
||||
if let DocumentNodeImplementation::Network(network) = &node.1.implementation {
|
||||
self.nodes.extend(network.nodes.iter());
|
||||
let (current_id, node, path) = self.nodes.pop()?;
|
||||
if let DocumentNodeImplementation::Network(network) = &node.implementation {
|
||||
self.nodes.extend(network.nodes.iter().map(|(id, node)| {
|
||||
let mut nested_path = path.clone();
|
||||
nested_path.push(*current_id);
|
||||
(id, node, nested_path)
|
||||
}));
|
||||
}
|
||||
Some(node)
|
||||
Some((current_id, node, path))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@ use dyn_any::DynAny;
|
|||
pub use dyn_any::StaticType;
|
||||
pub use glam::{DAffine2, DVec2, IVec2, UVec2};
|
||||
use graphene_application_io::SurfaceFrame;
|
||||
use graphene_core::raster::brush_cache::BrushCache;
|
||||
use graphene_core::raster::{BlendMode, LuminanceCalculation};
|
||||
use graphene_brush::brush_cache::BrushCache;
|
||||
use graphene_brush::brush_stroke::BrushStroke;
|
||||
use graphene_core::raster_types::CPU;
|
||||
use graphene_core::transform::ReferencePoint;
|
||||
use graphene_core::uuid::NodeId;
|
||||
|
@ -209,29 +209,29 @@ tagged_value! {
|
|||
#[serde(alias = "GradientPositions")] // TODO: Eventually remove this alias document upgrade code
|
||||
GradientStops(graphene_core::vector::style::GradientStops),
|
||||
Font(graphene_core::text::Font),
|
||||
BrushStrokes(Vec<graphene_core::vector::brush_stroke::BrushStroke>),
|
||||
BrushStrokes(Vec<BrushStroke>),
|
||||
BrushCache(BrushCache),
|
||||
DocumentNode(DocumentNode),
|
||||
Curve(graphene_core::raster::curve::Curve),
|
||||
Curve(graphene_raster_nodes::curve::Curve),
|
||||
Footprint(graphene_core::transform::Footprint),
|
||||
VectorModification(Box<graphene_core::vector::VectorModification>),
|
||||
FontCache(Arc<graphene_core::text::FontCache>),
|
||||
// ==========
|
||||
// ENUM TYPES
|
||||
// ==========
|
||||
BlendMode(BlendMode),
|
||||
LuminanceCalculation(LuminanceCalculation),
|
||||
BlendMode(graphene_core::blending::BlendMode),
|
||||
LuminanceCalculation(graphene_raster_nodes::adjustments::LuminanceCalculation),
|
||||
XY(graphene_core::extract_xy::XY),
|
||||
RedGreenBlue(graphene_core::raster::RedGreenBlue),
|
||||
RedGreenBlueAlpha(graphene_core::raster::RedGreenBlueAlpha),
|
||||
RedGreenBlue(graphene_raster_nodes::adjustments::RedGreenBlue),
|
||||
RedGreenBlueAlpha(graphene_raster_nodes::adjustments::RedGreenBlueAlpha),
|
||||
RealTimeMode(graphene_core::animation::RealTimeMode),
|
||||
NoiseType(graphene_core::raster::NoiseType),
|
||||
FractalType(graphene_core::raster::FractalType),
|
||||
CellularDistanceFunction(graphene_core::raster::CellularDistanceFunction),
|
||||
CellularReturnType(graphene_core::raster::CellularReturnType),
|
||||
DomainWarpType(graphene_core::raster::DomainWarpType),
|
||||
RelativeAbsolute(graphene_core::raster::RelativeAbsolute),
|
||||
SelectiveColorChoice(graphene_core::raster::SelectiveColorChoice),
|
||||
NoiseType(graphene_raster_nodes::adjustments::NoiseType),
|
||||
FractalType(graphene_raster_nodes::adjustments::FractalType),
|
||||
CellularDistanceFunction(graphene_raster_nodes::adjustments::CellularDistanceFunction),
|
||||
CellularReturnType(graphene_raster_nodes::adjustments::CellularReturnType),
|
||||
DomainWarpType(graphene_raster_nodes::adjustments::DomainWarpType),
|
||||
RelativeAbsolute(graphene_raster_nodes::adjustments::RelativeAbsolute),
|
||||
SelectiveColorChoice(graphene_raster_nodes::adjustments::SelectiveColorChoice),
|
||||
GridType(graphene_core::vector::misc::GridType),
|
||||
ArcType(graphene_core::vector::misc::ArcType),
|
||||
MergeByDistanceAlgorithm(graphene_core::vector::misc::MergeByDistanceAlgorithm),
|
||||
|
|
35
node-graph/graster-nodes/Cargo.toml
Normal file
35
node-graph/graster-nodes/Cargo.toml
Normal file
|
@ -0,0 +1,35 @@
|
|||
[package]
|
||||
name = "graphene-raster-nodes"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
description = "graphene raster data format"
|
||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[features]
|
||||
default = ["serde"]
|
||||
serde = ["dep:serde"]
|
||||
|
||||
[dependencies]
|
||||
# Local dependencies
|
||||
dyn-any = { workspace = true }
|
||||
graphene-core = { workspace = true }
|
||||
node-macro = { workspace = true }
|
||||
|
||||
# Workspace dependencies
|
||||
glam = { workspace = true }
|
||||
specta = { workspace = true }
|
||||
image = { workspace = true }
|
||||
bytemuck = { workspace = true }
|
||||
ndarray = { workspace = true }
|
||||
bezier-rs = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
fastnoise-lite = { workspace = true }
|
||||
|
||||
# Optional workspace dependencies
|
||||
serde = { workspace = true, optional = true, features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true }
|
||||
futures = { workspace = true }
|
|
@ -1,16 +1,16 @@
|
|||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use crate::GraphicElement;
|
||||
use crate::blending::BlendMode;
|
||||
use crate::raster::curve::{CubicSplines, CurveManipulatorGroup};
|
||||
use crate::raster::curve::{Curve, ValueMapperNode};
|
||||
use crate::raster::image::Image;
|
||||
use crate::raster::{Channel, Color, Pixel};
|
||||
use crate::raster_types::{CPU, Raster, RasterDataTable};
|
||||
use crate::registry::types::{Angle, Percentage, SignedPercentage};
|
||||
use crate::vector::style::GradientStops;
|
||||
use crate::{Ctx, Node};
|
||||
use crate::curve::CubicSplines;
|
||||
use dyn_any::DynAny;
|
||||
use graphene_core::Node;
|
||||
use graphene_core::blending::BlendMode;
|
||||
use graphene_core::color::Color;
|
||||
use graphene_core::color::Pixel;
|
||||
use graphene_core::context::Ctx;
|
||||
use graphene_core::gradient::GradientStops;
|
||||
use graphene_core::raster::image::Image;
|
||||
use graphene_core::raster_types::{CPU, Raster, RasterDataTable};
|
||||
use graphene_core::registry::types::{Angle, Percentage, SignedPercentage};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Debug;
|
||||
|
||||
|
@ -576,10 +576,7 @@ impl Adjust<Color> for GradientStops {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl Adjust<Color> for RasterDataTable<CPU>
|
||||
where
|
||||
GraphicElement: From<Image<Color>>,
|
||||
{
|
||||
impl Adjust<Color> for RasterDataTable<CPU> {
|
||||
fn adjust(&mut self, map_fn: impl Fn(&Color) -> Color) {
|
||||
for instance in self.instance_mut_iter() {
|
||||
for c in instance.instance.data_mut().data.iter_mut() {
|
||||
|
@ -1130,48 +1127,6 @@ async fn exposure<T: Adjust<Color>>(
|
|||
input
|
||||
}
|
||||
|
||||
const WINDOW_SIZE: usize = 1024;
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
fn generate_curves<C: Channel + crate::raster::Linear>(_: impl Ctx, curve: Curve, #[implementations(f32, f64)] _target_format: C) -> ValueMapperNode<C> {
|
||||
use bezier_rs::{Bezier, TValue};
|
||||
|
||||
let [mut pos, mut param]: [[f32; 2]; 2] = [[0.; 2], curve.first_handle];
|
||||
let mut lut = vec![C::from_f64(0.); WINDOW_SIZE];
|
||||
let end = CurveManipulatorGroup {
|
||||
anchor: [1.; 2],
|
||||
handles: [curve.last_handle, [0.; 2]],
|
||||
};
|
||||
for sample in curve.manipulator_groups.iter().chain(std::iter::once(&end)) {
|
||||
let [x0, y0, x1, y1, x2, y2, x3, y3] = [pos[0], pos[1], param[0], param[1], sample.handles[0][0], sample.handles[0][1], sample.anchor[0], sample.anchor[1]].map(f64::from);
|
||||
|
||||
let bezier = Bezier::from_cubic_coordinates(x0, y0, x1, y1, x2, y2, x3, y3);
|
||||
|
||||
let [left, right] = [pos[0], sample.anchor[0]].map(|c| c.clamp(0., 1.));
|
||||
let lut_index_left: usize = (left * (lut.len() - 1) as f32).floor() as _;
|
||||
let lut_index_right: usize = (right * (lut.len() - 1) as f32).ceil() as _;
|
||||
for index in lut_index_left..=lut_index_right {
|
||||
let x = index as f64 / (lut.len() - 1) as f64;
|
||||
let y = if x <= x0 {
|
||||
y0
|
||||
} else if x >= x3 {
|
||||
y3
|
||||
} else {
|
||||
bezier.find_tvalues_for_x(x)
|
||||
.next()
|
||||
.map(|t| bezier.evaluate(TValue::Parametric(t.clamp(0., 1.))).y)
|
||||
// Fall back to a very bad approximation if Bezier-rs fails
|
||||
.unwrap_or_else(|| (x - x0) / (x3 - x0) * (y3 - y0) + y0)
|
||||
};
|
||||
lut[index] = C::from_f64(y);
|
||||
}
|
||||
|
||||
pos = sample.anchor;
|
||||
param = sample.handles[1];
|
||||
}
|
||||
ValueMapperNode::new(lut)
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster: Adjustment"))]
|
||||
fn color_overlay<T: Adjust<Color>>(
|
||||
_: impl Ctx,
|
||||
|
@ -1224,10 +1179,10 @@ fn color_overlay<T: Adjust<Color>>(
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::Color;
|
||||
use crate::blending::BlendMode;
|
||||
use crate::raster::image::Image;
|
||||
use crate::raster_types::{Raster, RasterDataTable};
|
||||
use graphene_core::blending::BlendMode;
|
||||
use graphene_core::color::Color;
|
||||
use graphene_core::raster::image::Image;
|
||||
use graphene_core::raster_types::{Raster, RasterDataTable};
|
||||
|
||||
#[tokio::test]
|
||||
async fn color_overlay_multiply() {
|
|
@ -1,6 +1,7 @@
|
|||
use super::{Channel, Linear, LuminanceMut};
|
||||
use crate::Node;
|
||||
use dyn_any::{DynAny, StaticType, StaticTypeSized};
|
||||
use graphene_core::Node;
|
||||
use graphene_core::color::{Channel, Linear, LuminanceMut};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::{Add, Mul, Sub};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, DynAny, specta::Type, serde::Serialize, serde::Deserialize)]
|
||||
|
@ -23,8 +24,8 @@ impl Default for Curve {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Curve {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
impl Hash for Curve {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.manipulator_groups.hash(state);
|
||||
[self.first_handle, self.last_handle].iter().flatten().for_each(|f| f.to_bits().hash(state));
|
||||
}
|
||||
|
@ -36,8 +37,8 @@ pub struct CurveManipulatorGroup {
|
|||
pub handles: [[f32; 2]; 2],
|
||||
}
|
||||
|
||||
impl std::hash::Hash for CurveManipulatorGroup {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
impl Hash for CurveManipulatorGroup {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
for c in self.handles.iter().chain([&self.anchor]).flatten() {
|
||||
c.to_bits().hash(state);
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
use graph_craft::proto::types::Percentage;
|
||||
use graphene_core::Ctx;
|
||||
use graphene_core::context::Ctx;
|
||||
use graphene_core::raster::image::Image;
|
||||
use graphene_core::raster_types::{CPU, Raster, RasterDataTable};
|
||||
use graphene_core::registry::types::Percentage;
|
||||
use image::{DynamicImage, GenericImage, GenericImageView, GrayImage, ImageBuffer, Luma, Rgba, RgbaImage};
|
||||
use ndarray::{Array2, ArrayBase, Dim, OwnedRepr};
|
||||
use std::cmp::{max, min};
|
||||
|
@ -15,7 +15,7 @@ async fn dehaze(_: impl Ctx, image_frame: RasterDataTable<CPU>, strength: Percen
|
|||
// Prepare the image data for processing
|
||||
let image_data = bytemuck::cast_vec(image.data.clone());
|
||||
let image_buffer = image::Rgba32FImage::from_raw(image.width, image.height, image_data).expect("Failed to convert internal image format into image-rs data type.");
|
||||
let dynamic_image: image::DynamicImage = image_buffer.into();
|
||||
let dynamic_image: DynamicImage = image_buffer.into();
|
||||
|
||||
// Run the dehaze algorithm
|
||||
let dehazed_dynamic_image = dehaze_image(dynamic_image, strength / 100.);
|
|
@ -1,8 +1,9 @@
|
|||
use graph_craft::proto::types::PixelLength;
|
||||
use graphene_core::color::Color;
|
||||
use graphene_core::context::Ctx;
|
||||
use graphene_core::raster::image::Image;
|
||||
use graphene_core::raster::{Bitmap, BitmapMut};
|
||||
use graphene_core::raster_types::{CPU, Raster, RasterDataTable};
|
||||
use graphene_core::{Color, Ctx};
|
||||
use graphene_core::registry::types::PixelLength;
|
||||
|
||||
/// Blurs the image with a Gaussian or blur kernel filter.
|
||||
#[node_macro::node(category("Raster: Filter"))]
|
46
node-graph/graster-nodes/src/generate_curves.rs
Normal file
46
node-graph/graster-nodes/src/generate_curves.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
//! requires bezier-rs
|
||||
|
||||
use crate::curve::{Curve, CurveManipulatorGroup, ValueMapperNode};
|
||||
use bezier_rs::{Bezier, TValue};
|
||||
use graphene_core::color::{Channel, Linear};
|
||||
use graphene_core::context::Ctx;
|
||||
|
||||
const WINDOW_SIZE: usize = 1024;
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
fn generate_curves<C: Channel + Linear>(_: impl Ctx, curve: Curve, #[implementations(f32, f64)] _target_format: C) -> ValueMapperNode<C> {
|
||||
let [mut pos, mut param]: [[f32; 2]; 2] = [[0.; 2], curve.first_handle];
|
||||
let mut lut = vec![C::from_f64(0.); WINDOW_SIZE];
|
||||
let end = CurveManipulatorGroup {
|
||||
anchor: [1.; 2],
|
||||
handles: [curve.last_handle, [0.; 2]],
|
||||
};
|
||||
for sample in curve.manipulator_groups.iter().chain(std::iter::once(&end)) {
|
||||
let [x0, y0, x1, y1, x2, y2, x3, y3] = [pos[0], pos[1], param[0], param[1], sample.handles[0][0], sample.handles[0][1], sample.anchor[0], sample.anchor[1]].map(f64::from);
|
||||
|
||||
let bezier = Bezier::from_cubic_coordinates(x0, y0, x1, y1, x2, y2, x3, y3);
|
||||
|
||||
let [left, right] = [pos[0], sample.anchor[0]].map(|c| c.clamp(0., 1.));
|
||||
let lut_index_left: usize = (left * (lut.len() - 1) as f32).floor() as _;
|
||||
let lut_index_right: usize = (right * (lut.len() - 1) as f32).ceil() as _;
|
||||
for index in lut_index_left..=lut_index_right {
|
||||
let x = index as f64 / (lut.len() - 1) as f64;
|
||||
let y = if x <= x0 {
|
||||
y0
|
||||
} else if x >= x3 {
|
||||
y3
|
||||
} else {
|
||||
bezier.find_tvalues_for_x(x)
|
||||
.next()
|
||||
.map(|t| bezier.evaluate(TValue::Parametric(t.clamp(0., 1.))).y)
|
||||
// Fall back to a very bad approximation if Bezier-rs fails
|
||||
.unwrap_or_else(|| (x - x0) / (x3 - x0) * (y3 - y0) + y0)
|
||||
};
|
||||
lut[index] = C::from_f64(y);
|
||||
}
|
||||
|
||||
pos = sample.anchor;
|
||||
param = sample.handles[1];
|
||||
}
|
||||
ValueMapperNode::new(lut)
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
use graphene_core::color::Color;
|
||||
use graphene_core::context::Ctx;
|
||||
use graphene_core::raster_types::{CPU, RasterDataTable};
|
||||
use graphene_core::{Color, Ctx};
|
||||
|
||||
#[node_macro::node(category("Color"))]
|
||||
async fn image_color_palette(
|
7
node-graph/graster-nodes/src/lib.rs
Normal file
7
node-graph/graster-nodes/src/lib.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
pub mod adjustments;
|
||||
pub mod curve;
|
||||
pub mod dehaze;
|
||||
pub mod filter;
|
||||
pub mod generate_curves;
|
||||
pub mod image_color_palette;
|
||||
pub mod std_nodes;
|
|
@ -1,14 +1,19 @@
|
|||
use crate::instances::Instances;
|
||||
use graphene_core::instances::Instances;
|
||||
use crate::adjustments::{CellularDistanceFunction, CellularReturnType, DomainWarpType, FractalType, NoiseType};
|
||||
use dyn_any::DynAny;
|
||||
use fastnoise_lite;
|
||||
use glam::{DAffine2, DVec2, Vec2};
|
||||
use graphene_core::blending::AlphaBlending;
|
||||
use graphene_core::color::Color;
|
||||
use graphene_core::color::{Alpha, AlphaMut, Channel, LinearChannel, Luminance, RGBMut};
|
||||
use graphene_core::context::{Ctx, ExtractFootprint};
|
||||
use graphene_core::instances::Instance;
|
||||
use graphene_core::math::bbox::Bbox;
|
||||
pub use graphene_core::raster::*;
|
||||
use graphene_core::raster::image::Image;
|
||||
use graphene_core::raster::{Bitmap, BitmapMut};
|
||||
use graphene_core::raster_types::{CPU, Raster, RasterDataTable};
|
||||
use graphene_core::transform::Transform;
|
||||
use graphene_core::vector::VectorDataTable;
|
||||
use graphene_core::{Ctx, ExtractFootprint};
|
||||
use graphene_core::{GraphicElement, GraphicGroupTable};
|
||||
use rand::prelude::*;
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
|
@ -28,7 +33,7 @@ impl From<std::io::Error> for Error {
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Debug: Raster"))]
|
||||
fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: RasterDataTable<CPU>) -> RasterDataTable<CPU> {
|
||||
pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: RasterDataTable<CPU>) -> RasterDataTable<CPU> {
|
||||
let mut result_table = RasterDataTable::default();
|
||||
|
||||
for mut image_frame_instance in image_frame.instance_iter() {
|
||||
|
@ -95,7 +100,7 @@ fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: RasterDa
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Raster: Channels"))]
|
||||
fn combine_channels(
|
||||
pub fn combine_channels(
|
||||
_: impl Ctx,
|
||||
_primary: (),
|
||||
#[expose] red: RasterDataTable<CPU>,
|
||||
|
@ -185,7 +190,7 @@ fn combine_channels(
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Raster"))]
|
||||
fn mask<T, E>(
|
||||
pub fn mask<T, E>(
|
||||
_: impl Ctx,
|
||||
/// The image to be masked.
|
||||
#[implementations(
|
||||
|
@ -212,7 +217,8 @@ where
|
|||
image
|
||||
}
|
||||
|
||||
fn mask_lambda(image: RasterDataTable<CPU>, stencil: RasterDataTable<CPU>) -> RasterDataTable<CPU> {
|
||||
// TODO: Use as in-place raster modifier
|
||||
fn _mask_lambda(image: RasterDataTable<CPU>, stencil: RasterDataTable<CPU>) -> RasterDataTable<CPU> {
|
||||
// TODO: Support multiple stencil instances
|
||||
let Some(stencil_instance) = stencil.instance_iter().next() else {
|
||||
// No stencil provided so we return the original image
|
||||
|
@ -255,7 +261,7 @@ fn mask_lambda(image: RasterDataTable<CPU>, stencil: RasterDataTable<CPU>) -> Ra
|
|||
}
|
||||
|
||||
#[node_macro::node(category(""))]
|
||||
fn extend_image_to_bounds(_: impl Ctx, image: RasterDataTable<CPU>, bounds: DAffine2) -> RasterDataTable<CPU> {
|
||||
pub fn extend_image_to_bounds(_: impl Ctx, image: RasterDataTable<CPU>, bounds: DAffine2) -> RasterDataTable<CPU> {
|
||||
let mut result_table = RasterDataTable::default();
|
||||
|
||||
for mut image_instance in image.instance_iter() {
|
||||
|
@ -308,7 +314,7 @@ fn extend_image_to_bounds(_: impl Ctx, image: RasterDataTable<CPU>, bounds: DAff
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Debug: Raster"))]
|
||||
fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> RasterDataTable<CPU> {
|
||||
pub fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> RasterDataTable<CPU> {
|
||||
let width = transform.transform_vector2(DVec2::new(1., 0.)).length() as u32;
|
||||
let height = transform.transform_vector2(DVec2::new(0., 1.)).length() as u32;
|
||||
|
||||
|
@ -325,13 +331,13 @@ fn empty_image(_: impl Ctx, transform: DAffine2, color: Color) -> RasterDataTabl
|
|||
|
||||
/// Constructs a raster image.
|
||||
#[node_macro::node(category(""))]
|
||||
fn image_value(_: impl Ctx, _primary: (), image: RasterDataTable<CPU>) -> RasterDataTable<CPU> {
|
||||
pub fn image_value(_: impl Ctx, _primary: (), image: RasterDataTable<CPU>) -> RasterDataTable<CPU> {
|
||||
image
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Raster: Pattern"))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn noise_pattern(
|
||||
pub fn noise_pattern(
|
||||
ctx: impl ExtractFootprint + Ctx,
|
||||
_primary: (),
|
||||
clip: bool,
|
||||
|
@ -487,7 +493,7 @@ fn noise_pattern(
|
|||
}
|
||||
|
||||
#[node_macro::node(category("Raster: Pattern"))]
|
||||
fn mandelbrot(ctx: impl ExtractFootprint + Send) -> RasterDataTable<CPU> {
|
||||
pub fn mandelbrot(ctx: impl ExtractFootprint + Send) -> RasterDataTable<CPU> {
|
||||
let footprint = ctx.footprint();
|
||||
let viewport_bounds = footprint.viewport_bounds_in_local_space();
|
||||
|
|
@ -32,6 +32,8 @@ graphene-path-bool = { workspace = true }
|
|||
graphene-math-nodes = { workspace = true }
|
||||
graphene-svg-renderer = { workspace = true }
|
||||
graphene-application-io = { workspace = true }
|
||||
graphene-raster-nodes = { workspace = true }
|
||||
graphene-brush = { workspace = true }
|
||||
|
||||
# Workspace dependencies
|
||||
fastnoise-lite = { workspace = true }
|
||||
|
|
|
@ -1,23 +1,26 @@
|
|||
pub mod any;
|
||||
pub mod brush;
|
||||
pub mod dehaze;
|
||||
pub mod filter;
|
||||
pub mod http;
|
||||
pub mod image_color_palette;
|
||||
pub mod raster;
|
||||
pub mod text;
|
||||
#[cfg(feature = "wasm")]
|
||||
pub mod wasm_application_io;
|
||||
|
||||
pub use graphene_application_io as application_io;
|
||||
pub use graphene_brush as brush;
|
||||
pub use graphene_core::vector;
|
||||
pub use graphene_core::*;
|
||||
pub use graphene_math_nodes as math_nodes;
|
||||
pub use graphene_path_bool as path_bool;
|
||||
pub use graphene_raster_nodes as raster_nodes;
|
||||
|
||||
/// stop gap solution until all `Quad` and `Rect` paths have been replaced with their absolute ones
|
||||
/// stop gap solutions until all paths have been replaced with their absolute ones
|
||||
pub mod renderer {
|
||||
pub use graphene_core::math::quad::Quad;
|
||||
pub use graphene_core::math::rect::Rect;
|
||||
pub use graphene_svg_renderer::*;
|
||||
}
|
||||
|
||||
pub mod raster {
|
||||
pub use graphene_core::raster::*;
|
||||
pub use graphene_raster_nodes::adjustments::*;
|
||||
pub use graphene_raster_nodes::*;
|
||||
}
|
||||
|
|
|
@ -9,23 +9,37 @@ fn text<'i: 'n>(
|
|||
editor: &'i WasmEditorApi,
|
||||
text: String,
|
||||
font_name: Font,
|
||||
#[default(24.)] font_size: f64,
|
||||
#[default(1.2)] line_height_ratio: f64,
|
||||
#[default(1.)] character_spacing: f64,
|
||||
#[default(None)] max_width: Option<f64>,
|
||||
#[default(None)] max_height: Option<f64>,
|
||||
#[unit(" px")]
|
||||
#[default(24.)]
|
||||
font_size: f64,
|
||||
#[unit("x")]
|
||||
#[default(1.2)]
|
||||
line_height_ratio: f64,
|
||||
#[unit(" px")]
|
||||
#[default(0.)]
|
||||
character_spacing: f64,
|
||||
#[unit(" px")]
|
||||
#[default(None)]
|
||||
max_width: Option<f64>,
|
||||
#[unit(" px")]
|
||||
#[default(None)]
|
||||
max_height: Option<f64>,
|
||||
#[unit("°")]
|
||||
#[default(0.)]
|
||||
tilt: f64,
|
||||
) -> VectorDataTable {
|
||||
let buzz_face = editor.font_cache.get(&font_name).map(|data| load_face(data));
|
||||
|
||||
let typesetting = TypesettingConfig {
|
||||
font_size,
|
||||
line_height_ratio,
|
||||
character_spacing,
|
||||
max_width,
|
||||
max_height,
|
||||
tilt,
|
||||
};
|
||||
|
||||
let result = VectorData::from_subpaths(to_path(&text, buzz_face, typesetting), false);
|
||||
let font_data = editor.font_cache.get(&font_name).map(|f| load_font(f));
|
||||
|
||||
let result = VectorData::from_subpaths(to_path(&text, font_data, typesetting), false);
|
||||
|
||||
VectorDataTable::new(result)
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ static NODE_ID: AtomicU64 = AtomicU64::new(0);
|
|||
|
||||
pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStream2> {
|
||||
let ParsedNodeFn {
|
||||
vis,
|
||||
attributes,
|
||||
fn_name,
|
||||
struct_name,
|
||||
|
@ -345,7 +346,7 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
|
|||
/// Underlying implementation for [#struct_name]
|
||||
#[inline]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) #async_keyword fn #fn_name <'n, #(#fn_generics,)*> (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body
|
||||
#vis #async_keyword fn #fn_name <'n, #(#fn_generics,)*> (#input_ident: #input_type #(, #field_idents: #field_types)*) -> #output_type #where_clause #body
|
||||
|
||||
#[automatically_derived]
|
||||
impl<'n, #(#fn_generics,)* #(#struct_generics,)* #(#future_idents,)*> #graphene_core::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_generics,)*>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue