Merge branch 'master' into ir/ref-centroid

This commit is contained in:
indierusty 2025-08-05 07:53:36 +05:30
commit 20afb25698
98 changed files with 2696 additions and 2151 deletions

1
.gitignore vendored
View file

@ -3,6 +3,7 @@ target/
*.exrc
perf.data*
profile.json
profile.json.gz
flamegraph.svg
.idea/
.direnv

23
.nix/flake.lock generated
View file

@ -34,27 +34,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1748190013,
"narHash": "sha256-R5HJFflOfsP5FBtk+zE8FpL8uqE7n62jqOsADvVshhE=",
"lastModified": 1754214453,
"narHash": "sha256-Q/I2xJn/j1wpkGhWkQnm20nShYnG7TI99foDBpXm1SY=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "62b852f6c6742134ade1abdd2a21685fd617a291",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1748190013,
"narHash": "sha256-R5HJFflOfsP5FBtk+zE8FpL8uqE7n62jqOsADvVshhE=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "62b852f6c6742134ade1abdd2a21685fd617a291",
"rev": "5b09dc45f24cf32316283e62aec81ffee3c3e376",
"type": "github"
},
"original": {
@ -69,7 +53,6 @@
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"nixpkgs-unstable": "nixpkgs-unstable",
"rust-overlay": "rust-overlay"
}
},

View file

@ -16,7 +16,6 @@
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
@ -27,17 +26,14 @@
flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz";
};
outputs = { nixpkgs, nixpkgs-unstable, rust-overlay, flake-utils, ... }:
outputs = { nixpkgs, rust-overlay, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
overlays = [ (import rust-overlay) ];
pkgs = import nixpkgs {
inherit system overlays;
};
pkgs-unstable = import nixpkgs-unstable {
inherit system overlays;
};
rustc-wasm = pkgs.rust-bin.stable.latest.default.override {
targets = [ "wasm32-unknown-unknown" ];
extensions = [ "rust-src" "rust-analyzer" "clippy" "cargo" ];
@ -75,10 +71,8 @@
buildInputs = with pkgs; [
# System libraries
wayland
wayland.dev
openssl
vulkan-loader
mesa
libraw
libGL
];
@ -90,11 +84,10 @@
pkgs.nodePackages.npm
pkgs.binaryen
pkgs.wasm-bindgen-cli
pkgs-unstable.wasm-pack
pkgs.wasm-pack
pkgs.pkg-config
pkgs.git
pkgs.gobject-introspection
pkgs-unstable.cargo-about
pkgs.cargo-about
# Linker
pkgs.mold

543
Cargo.lock generated
View file

@ -224,6 +224,181 @@ dependencies = [
"libloading",
]
[[package]]
name = "ashpd"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df"
dependencies = [
"async-fs",
"async-net",
"enumflags2",
"futures-channel",
"futures-util",
"rand 0.9.1",
"raw-window-handle",
"serde",
"serde_repr",
"url",
"wayland-backend",
"wayland-client",
"wayland-protocols",
"zbus",
]
[[package]]
name = "async-broadcast"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
dependencies = [
"event-listener",
"event-listener-strategy",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-channel"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
dependencies = [
"concurrent-queue",
"event-listener-strategy",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-executor"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa"
dependencies = [
"async-task",
"concurrent-queue",
"fastrand",
"futures-lite",
"pin-project-lite",
"slab",
]
[[package]]
name = "async-fs"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09f7e37c0ed80b2a977691c47dae8625cfb21e205827106c64f7c588766b2e50"
dependencies = [
"async-lock",
"blocking",
"futures-lite",
]
[[package]]
name = "async-io"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca"
dependencies = [
"async-lock",
"cfg-if",
"concurrent-queue",
"futures-io",
"futures-lite",
"parking",
"polling",
"rustix 1.0.7",
"slab",
"windows-sys 0.60.2",
]
[[package]]
name = "async-lock"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
dependencies = [
"event-listener",
"event-listener-strategy",
"pin-project-lite",
]
[[package]]
name = "async-net"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7"
dependencies = [
"async-io",
"blocking",
"futures-lite",
]
[[package]]
name = "async-process"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00"
dependencies = [
"async-channel",
"async-io",
"async-lock",
"async-signal",
"async-task",
"blocking",
"cfg-if",
"event-listener",
"futures-lite",
"rustix 1.0.7",
]
[[package]]
name = "async-recursion"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
]
[[package]]
name = "async-signal"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f567af260ef69e1d52c2b560ce0ea230763e6fbb9214a85d768760a920e3e3c1"
dependencies = [
"async-io",
"async-lock",
"atomic-waker",
"cfg-if",
"futures-core",
"futures-io",
"rustix 1.0.7",
"signal-hook-registry",
"slab",
"windows-sys 0.60.2",
]
[[package]]
name = "async-task"
version = "4.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]]
name = "async-trait"
version = "0.1.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
]
[[package]]
name = "atomic-waker"
version = "1.1.2"
@ -382,6 +557,28 @@ dependencies = [
"objc2 0.5.2",
]
[[package]]
name = "block2"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2"
dependencies = [
"objc2 0.6.1",
]
[[package]]
name = "blocking"
version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
dependencies = [
"async-channel",
"async-task",
"futures-io",
"futures-lite",
"piper",
]
[[package]]
name = "built"
version = "0.7.7"
@ -965,6 +1162,18 @@ 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.1",
"block2 0.6.1",
"libc",
"objc2 0.6.1",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
@ -1068,6 +1277,33 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "endi"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf"
[[package]]
name = "enumflags2"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
dependencies = [
"enumflags2_derive",
"serde",
]
[[package]]
name = "enumflags2_derive"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
]
[[package]]
name = "env_filter"
version = "0.1.3"
@ -1136,6 +1372,27 @@ dependencies = [
"num-traits",
]
[[package]]
name = "event-listener"
version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
dependencies = [
"event-listener",
"pin-project-lite",
]
[[package]]
name = "exr"
version = "1.73.0"
@ -1406,6 +1663,19 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-lite"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532"
dependencies = [
"fastrand",
"futures-core",
"futures-io",
"parking",
"pin-project-lite",
]
[[package]]
name = "futures-macro"
version = "0.3.31"
@ -1844,6 +2114,7 @@ dependencies = [
"graphene-std",
"graphite-editor",
"include_dir",
"rfd",
"ron",
"thiserror 2.0.12",
"tracing",
@ -1987,6 +2258,12 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hexf-parse"
version = "0.2.1"
@ -2776,6 +3053,15 @@ dependencies = [
"libc",
]
[[package]]
name = "memoffset"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
dependencies = [
"autocfg",
]
[[package]]
name = "metal"
version = "0.31.0"
@ -2927,6 +3213,19 @@ version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
[[package]]
name = "nix"
version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [
"bitflags 2.9.1",
"cfg-if",
"cfg_aliases",
"libc",
"memoffset",
]
[[package]]
name = "node-macro"
version = "0.0.0"
@ -3097,7 +3396,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff"
dependencies = [
"bitflags 2.9.1",
"block2",
"block2 0.5.1",
"libc",
"objc2 0.5.2",
"objc2-core-data",
@ -3106,6 +3405,18 @@ dependencies = [
"objc2-quartz-core",
]
[[package]]
name = "objc2-app-kit"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc"
dependencies = [
"bitflags 2.9.1",
"block2 0.6.1",
"objc2 0.6.1",
"objc2-foundation 0.3.1",
]
[[package]]
name = "objc2-cloud-kit"
version = "0.2.2"
@ -3113,7 +3424,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
dependencies = [
"bitflags 2.9.1",
"block2",
"block2 0.5.1",
"objc2 0.5.2",
"objc2-core-location",
"objc2-foundation 0.2.2",
@ -3125,7 +3436,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
dependencies = [
"block2",
"block2 0.5.1",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
]
@ -3137,7 +3448,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
dependencies = [
"bitflags 2.9.1",
"block2",
"block2 0.5.1",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
]
@ -3149,6 +3460,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
dependencies = [
"bitflags 2.9.1",
"dispatch2",
"objc2 0.6.1",
]
[[package]]
@ -3157,7 +3470,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
dependencies = [
"block2",
"block2 0.5.1",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
"objc2-metal",
@ -3169,7 +3482,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
dependencies = [
"block2",
"block2 0.5.1",
"objc2 0.5.2",
"objc2-contacts",
"objc2-foundation 0.2.2",
@ -3198,7 +3511,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
dependencies = [
"bitflags 2.9.1",
"block2",
"block2 0.5.1",
"dispatch",
"libc",
"objc2 0.5.2",
@ -3212,6 +3525,7 @@ checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c"
dependencies = [
"bitflags 2.9.1",
"objc2 0.6.1",
"objc2-core-foundation",
]
[[package]]
@ -3220,9 +3534,9 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
dependencies = [
"block2",
"block2 0.5.1",
"objc2 0.5.2",
"objc2-app-kit",
"objc2-app-kit 0.2.2",
"objc2-foundation 0.2.2",
]
@ -3233,7 +3547,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
dependencies = [
"bitflags 2.9.1",
"block2",
"block2 0.5.1",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
]
@ -3245,7 +3559,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
dependencies = [
"bitflags 2.9.1",
"block2",
"block2 0.5.1",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
"objc2-metal",
@ -3268,7 +3582,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
dependencies = [
"bitflags 2.9.1",
"block2",
"block2 0.5.1",
"objc2 0.5.2",
"objc2-cloud-kit",
"objc2-core-data",
@ -3288,7 +3602,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
dependencies = [
"block2",
"block2 0.5.1",
"objc2 0.5.2",
"objc2-foundation 0.2.2",
]
@ -3300,7 +3614,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
dependencies = [
"bitflags 2.9.1",
"block2",
"block2 0.5.1",
"objc2 0.5.2",
"objc2-core-location",
"objc2-foundation 0.2.2",
@ -3401,6 +3715,16 @@ dependencies = [
"num-traits",
]
[[package]]
name = "ordered-stream"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
dependencies = [
"futures-core",
"pin-project-lite",
]
[[package]]
name = "overload"
version = "0.1.1"
@ -3416,6 +3740,12 @@ dependencies = [
"ttf-parser 0.25.1",
]
[[package]]
name = "parking"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]]
name = "parking_lot"
version = "0.12.4"
@ -3593,6 +3923,17 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "piper"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
dependencies = [
"atomic-waker",
"fastrand",
"futures-io",
]
[[package]]
name = "pkg-config"
version = "0.3.32"
@ -3655,6 +3996,12 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "pollster"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
[[package]]
name = "portable-atomic"
version = "1.11.1"
@ -4205,6 +4552,30 @@ dependencies = [
"zune-jpeg",
]
[[package]]
name = "rfd"
version = "0.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed"
dependencies = [
"ashpd",
"block2 0.6.1",
"dispatch2",
"js-sys",
"log",
"objc2 0.6.1",
"objc2-app-kit 0.3.1",
"objc2-core-foundation",
"objc2-foundation 0.3.1",
"pollster",
"raw-window-handle",
"urlencoding",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows-sys 0.59.0",
]
[[package]]
name = "rgb"
version = "0.8.51"
@ -4480,6 +4851,17 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_repr"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
]
[[package]]
name = "serde_spanned"
version = "0.6.9"
@ -4533,6 +4915,15 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
dependencies = [
"libc",
]
[[package]]
name = "simd-adler32"
version = "0.3.7"
@ -5302,6 +5693,17 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
[[package]]
name = "uds_windows"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9"
dependencies = [
"memoffset",
"tempfile",
"winapi",
]
[[package]]
name = "unicode-bidi"
version = "0.3.18"
@ -5411,8 +5813,15 @@ dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
"serde",
]
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "usvg"
version = "0.44.0"
@ -6413,7 +6822,7 @@ dependencies = [
"android-activity",
"atomic-waker",
"bitflags 2.9.1",
"block2",
"block2 0.5.1",
"bytemuck",
"calloop",
"cfg_aliases",
@ -6427,7 +6836,7 @@ dependencies = [
"memmap2",
"ndk",
"objc2 0.5.2",
"objc2-app-kit",
"objc2-app-kit 0.2.2",
"objc2-foundation 0.2.2",
"objc2-ui-kit",
"orbclient",
@ -6601,6 +7010,66 @@ dependencies = [
"synstructure",
]
[[package]]
name = "zbus"
version = "5.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bb4f9a464286d42851d18a605f7193b8febaf5b0919d71c6399b7b26e5b0aad"
dependencies = [
"async-broadcast",
"async-executor",
"async-io",
"async-lock",
"async-process",
"async-recursion",
"async-task",
"async-trait",
"blocking",
"enumflags2",
"event-listener",
"futures-core",
"futures-lite",
"hex",
"nix",
"ordered-stream",
"serde",
"serde_repr",
"tracing",
"uds_windows",
"windows-sys 0.59.0",
"winnow",
"zbus_macros",
"zbus_names",
"zvariant",
]
[[package]]
name = "zbus_macros"
version = "5.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef9859f68ee0c4ee2e8cde84737c78e3f4c54f946f2a38645d0d4c7a95327659"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.104",
"zbus_names",
"zvariant",
"zvariant_utils",
]
[[package]]
name = "zbus_names"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97"
dependencies = [
"serde",
"static_assertions",
"winnow",
"zvariant",
]
[[package]]
name = "zeno"
version = "0.3.3"
@ -6710,3 +7179,45 @@ checksum = "2c9e525af0a6a658e031e95f14b7f889976b74a11ba0eca5a5fc9ac8a1c43a6a"
dependencies = [
"zune-core",
]
[[package]]
name = "zvariant"
version = "5.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d91b3680bb339216abd84714172b5138a4edac677e641ef17e1d8cb1b3ca6e6f"
dependencies = [
"endi",
"enumflags2",
"serde",
"url",
"winnow",
"zvariant_derive",
"zvariant_utils",
]
[[package]]
name = "zvariant_derive"
version = "5.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a8c68501be459a8dbfffbe5d792acdd23b4959940fc87785fb013b32edbc208"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.104",
"zvariant_utils",
]
[[package]]
name = "zvariant_utils"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e16edfee43e5d7b553b77872d99bc36afdda75c223ca7ad5e3fbecd82ca5fc34"
dependencies = [
"proc-macro2",
"quote",
"serde",
"static_assertions",
"syn 2.0.104",
"winnow",
]

View file

@ -163,6 +163,7 @@ cef = "138.5.0"
include_dir = "0.7.4"
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
tracing = "0.1.41"
rfd = "0.15.4"
[profile.dev]
opt-level = 1

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

3
desktop/.gitignore vendored
View file

@ -1,3 +0,0 @@
# Generated by Cargo
# will have compiled files and executables
/target/

View file

@ -37,3 +37,4 @@ bytemuck = { workspace = true }
glam = { workspace = true }
vello = { workspace = true }
derivative = { workspace = true }
rfd = { workspace = true }

View file

@ -1,10 +0,0 @@
use std::fs::metadata;
fn main() {
let frontend_dir = format!("{}/../frontend/dist", env!("CARGO_MANIFEST_DIR"));
metadata(&frontend_dir).expect("Failed to find frontend directory. Please build the frontend first.");
metadata(format!("{}/index.html", &frontend_dir)).expect("Failed to find index.html in frontend directory.");
println!("cargo:rerun-if-changed=.");
println!("cargo:rerun-if-changed=../frontend/dist");
}

View file

@ -1,5 +1,7 @@
use crate::CustomEvent;
use crate::WindowSize;
use crate::dialogs::dialog_open_graphite_file;
use crate::dialogs::dialog_save_graphite_file;
use crate::render::GraphicsState;
use crate::render::WgpuContext;
use graph_craft::wasm_application_io::WasmApplicationIo;
@ -7,6 +9,7 @@ use graphite_editor::application::Editor;
use graphite_editor::messages::prelude::*;
use std::sync::Arc;
use std::sync::mpsc::Sender;
use std::thread;
use std::time::Duration;
use std::time::Instant;
use winit::application::ApplicationHandler;
@ -15,23 +18,25 @@ use winit::event::StartCause;
use winit::event::WindowEvent;
use winit::event_loop::ActiveEventLoop;
use winit::event_loop::ControlFlow;
use winit::event_loop::EventLoopProxy;
use winit::window::Window;
use winit::window::WindowId;
use crate::cef;
pub(crate) struct WinitApp {
pub(crate) cef_context: cef::Context<cef::Initialized>,
pub(crate) window: Option<Arc<Window>>,
cef_context: cef::Context<cef::Initialized>,
window: Option<Arc<Window>>,
cef_schedule: Option<Instant>,
window_size_sender: Sender<WindowSize>,
graphics_state: Option<GraphicsState>,
wgpu_context: WgpuContext,
pub(crate) editor: Editor,
event_loop_proxy: EventLoopProxy<CustomEvent>,
editor: Editor,
}
impl WinitApp {
pub(crate) fn new(cef_context: cef::Context<cef::Initialized>, window_size_sender: Sender<WindowSize>, wgpu_context: WgpuContext) -> Self {
pub(crate) fn new(cef_context: cef::Context<cef::Initialized>, window_size_sender: Sender<WindowSize>, wgpu_context: WgpuContext, event_loop_proxy: EventLoopProxy<CustomEvent>) -> Self {
Self {
cef_context,
window: None,
@ -39,6 +44,7 @@ impl WinitApp {
graphics_state: None,
window_size_sender,
wgpu_context,
event_loop_proxy,
editor: Editor::new(),
}
}
@ -57,6 +63,49 @@ impl WinitApp {
}
}
for _ in responses.extract_if(.., |m| matches!(m, FrontendMessage::TriggerOpenDocument)) {
let event_loop_proxy = self.event_loop_proxy.clone();
let _ = thread::spawn(move || {
let path = futures::executor::block_on(dialog_open_graphite_file());
if let Some(path) = path {
let content = std::fs::read_to_string(&path).unwrap_or_else(|_| {
tracing::error!("Failed to read file: {}", path.display());
String::new()
});
let message = PortfolioMessage::OpenDocumentFile {
document_name: path.file_name().and_then(|s| s.to_str()).unwrap_or("unknown").to_string(),
document_serialized_content: content,
};
let _ = event_loop_proxy.send_event(CustomEvent::DispatchMessage(message.into()));
}
});
}
for message in responses.extract_if(.., |m| matches!(m, FrontendMessage::TriggerSaveDocument { .. })) {
let FrontendMessage::TriggerSaveDocument { document_id, name, path, document } = message else {
unreachable!()
};
if let Some(path) = path {
let _ = std::fs::write(&path, document);
} else {
let event_loop_proxy = self.event_loop_proxy.clone();
let _ = thread::spawn(move || {
let path = futures::executor::block_on(dialog_save_graphite_file(name));
if let Some(path) = path {
if let Err(e) = std::fs::write(&path, document) {
tracing::error!("Failed to save file: {}: {}", path.display(), e);
} else {
let message = Message::Portfolio(PortfolioMessage::DocumentPassMessage {
document_id,
message: DocumentMessage::SavedDocument { path: Some(path) },
});
let _ = event_loop_proxy.send_event(CustomEvent::DispatchMessage(message));
}
}
});
}
}
if responses.is_empty() {
return;
}
@ -132,7 +181,10 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
self.cef_schedule = Some(instant);
}
}
CustomEvent::MessageReceived { message } => {
CustomEvent::DispatchMessage(message) => {
self.dispatch_message(message);
}
CustomEvent::MessageReceived(message) => {
if let Message::InputPreprocessor(_) = &message {
if let Some(window) = &self.window {
window.request_redraw();
@ -155,7 +207,7 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
self.dispatch_message(message);
}
CustomEvent::NodeGraphRan { texture } => {
CustomEvent::NodeGraphRan(texture) => {
if let Some(texture) = texture
&& let Some(graphics_state) = &mut self.graphics_state
{

View file

@ -124,7 +124,7 @@ impl CefEventHandler for CefHandler {
let str = std::str::from_utf8(message).unwrap();
match ron::from_str(str) {
Ok(message) => {
let _ = self.event_loop_proxy.send_event(CustomEvent::MessageReceived { message });
let _ = self.event_loop_proxy.send_event(CustomEvent::MessageReceived(message));
}
Err(e) => {
tracing::error!("Failed to deserialize message {:?}", e)

22
desktop/src/dialogs.rs Normal file
View file

@ -0,0 +1,22 @@
use std::path::PathBuf;
use rfd::AsyncFileDialog;
pub(crate) async fn dialog_open_graphite_file() -> Option<PathBuf> {
AsyncFileDialog::new()
.add_filter("Graphite", &["graphite"])
.set_title("Open Graphite Document")
.pick_file()
.await
.map(|f| f.path().to_path_buf())
}
pub(crate) async fn dialog_save_graphite_file(name: String) -> Option<PathBuf> {
AsyncFileDialog::new()
.add_filter("Graphite", &["graphite"])
.set_title("Save Graphite Document")
.set_file_name(name)
.save_file()
.await
.map(|f| f.path().to_path_buf())
}

View file

@ -17,12 +17,15 @@ use app::WinitApp;
mod dirs;
mod dialogs;
#[derive(Debug)]
pub(crate) enum CustomEvent {
UiUpdate(wgpu::Texture),
ScheduleBrowserWork(Instant),
MessageReceived { message: Message },
NodeGraphRan { texture: Option<wgpu::Texture> },
DispatchMessage(Message),
MessageReceived(Message),
NodeGraphRan(Option<wgpu::Texture>),
}
fn main() {
@ -63,9 +66,7 @@ fn main() {
let last_render = Instant::now();
let (has_run, texture) = futures::executor::block_on(graphite_editor::node_graph_executor::run_node_graph());
if has_run {
let _ = rendering_loop_proxy.send_event(CustomEvent::NodeGraphRan {
texture: texture.map(|t| (*t.texture).clone()),
});
let _ = rendering_loop_proxy.send_event(CustomEvent::NodeGraphRan(texture.map(|t| (*t.texture).clone())));
}
let frame_time = Duration::from_secs_f32((target_fps as f32).recip());
let sleep = last_render + frame_time - Instant::now();
@ -73,7 +74,7 @@ fn main() {
}
});
let mut winit_app = WinitApp::new(cef_context, window_size_sender, wgpu_context);
let mut winit_app = WinitApp::new(cef_context, window_size_sender, wgpu_context, event_loop.create_proxy());
event_loop.run_app(&mut winit_app).unwrap();
}

View file

@ -25,8 +25,8 @@ impl MessageHandler<DeferMessage, ()> for DeferMessageHandler {
return;
}
// Find the index of the last message we can process
let num_elements_to_remove = self.after_graph_run.binary_search_by_key(&(execution_id + 1), |x| x.0).unwrap_or_else(|pos| pos - 1);
let elements = self.after_graph_run.drain(0..=num_elements_to_remove);
let split = self.after_graph_run.partition_point(|&(id, _)| id <= execution_id);
let elements = self.after_graph_run.drain(..split);
for (_, message) in elements.rev() {
responses.add_front(message);
}

View file

@ -12,6 +12,7 @@ use graph_craft::document::NodeId;
use graphene_std::raster::Image;
use graphene_std::raster::color::Color;
use graphene_std::text::{Font, TextAlign};
use std::path::PathBuf;
#[cfg(not(target_family = "wasm"))]
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
@ -63,6 +64,12 @@ pub enum FrontendMessage {
#[serde(rename = "commitDate")]
commit_date: String,
},
TriggerSaveDocument {
document_id: DocumentId,
name: String,
path: Option<PathBuf>,
document: String,
},
TriggerDownloadImage {
svg: String,
name: String,

View file

@ -1,3 +1,5 @@
use std::path::PathBuf;
use super::utility_types::misc::{GroupFolderType, SnappingState};
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
@ -105,6 +107,9 @@ pub enum DocumentMessage {
RenderRulers,
RenderScrollbars,
SaveDocument,
SavedDocument {
path: Option<PathBuf>,
},
SelectParentLayer,
SelectAllLayers,
SelectedLayersLower,

View file

@ -37,6 +37,7 @@ use graphene_std::table::Table;
use graphene_std::vector::PointId;
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::style::ViewMode;
use std::path::PathBuf;
use std::time::Duration;
#[derive(ExtractField)]
@ -115,6 +116,9 @@ pub struct DocumentMessageHandler {
/// Stack of document network snapshots for future history states.
#[serde(skip)]
document_redo_history: VecDeque<NodeNetworkInterface>,
/// The path of the to the document file.
#[serde(skip)]
path: Option<PathBuf>,
/// Hash of the document snapshot that was most recently saved to disk by the user.
#[serde(skip)]
saved_hash: Option<u64>,
@ -162,6 +166,7 @@ impl Default for DocumentMessageHandler {
selection_network_path: Vec::new(),
document_undo_history: VecDeque::new(),
document_redo_history: VecDeque::new(),
path: None,
saved_hash: None,
auto_saved_hash: None,
layer_range_selection_reference: None,
@ -692,7 +697,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
});
if layer_to_move.parent(self.metadata()) != Some(parent) {
// TODO: Fix this so it works when dragging a layer into a group parent which has a Transform node, which used to work before #2689 caused this regression by removing the empty VectorData table row.
// TODO: Fix this so it works when dragging a layer into a group parent which has a Transform node, which used to work before #2689 caused this regression by removing the empty vector table row.
// TODO: See #2688 for this issue.
let layer_local_transform = self.network_interface.document_metadata().transform_to_viewport(layer_to_move);
let undo_transform = self.network_interface.document_metadata().transform_to_viewport(parent).inverse();
@ -996,11 +1001,16 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
true => self.name.clone(),
false => self.name.clone() + FILE_SAVE_SUFFIX,
};
responses.add(FrontendMessage::TriggerDownloadTextFile {
document: self.serialize_document(),
responses.add(FrontendMessage::TriggerSaveDocument {
document_id,
name,
path: self.path.clone(),
document: self.serialize_document(),
})
}
DocumentMessage::SavedDocument { path } => {
self.path = path;
}
DocumentMessage::SelectParentLayer => {
let selected_nodes = self.network_interface.selected_nodes();
let selected_layers = selected_nodes.selected_layers(self.metadata());
@ -2907,7 +2917,7 @@ impl DocumentMessageHandler {
/// Create a network interface with a single export
fn default_document_network_interface() -> NodeNetworkInterface {
let mut network_interface = NodeNetworkInterface::default();
network_interface.add_export(TaggedValue::ArtboardGroup(Default::default()), -1, "", &[]);
network_interface.add_export(TaggedValue::Artboard(Default::default()), -1, "", &[]);
network_interface
}

View file

@ -174,7 +174,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for
GraphOperationMessage::NewVectorLayer { id, subpaths, parent, insert_index } => {
let mut modify_inputs = ModifyInputsContext::new(network_interface, responses);
let layer = modify_inputs.create_layer(id);
modify_inputs.insert_vector_data(subpaths, layer, true, true, true);
modify_inputs.insert_vector(subpaths, layer, true, true, true);
network_interface.move_layer_to_stack(layer, parent, insert_index, &[]);
responses.add(NodeGraphMessage::RunDocumentGraph);
}
@ -349,7 +349,7 @@ fn import_usvg_node(modify_inputs: &mut ModifyInputsContext, node: &usvg::Node,
let subpaths = convert_usvg_path(path);
let bounds = subpaths.iter().filter_map(|subpath| subpath.bounding_box()).reduce(Quad::combine_bounds).unwrap_or_default();
modify_inputs.insert_vector_data(subpaths, layer, true, path.fill().is_some(), path.stroke().is_some());
modify_inputs.insert_vector(subpaths, layer, true, path.fill().is_some(), path.stroke().is_some());
if let Some(transform_node_id) = modify_inputs.existing_node_id("Transform", true) {
transform_utils::update_transform(modify_inputs.network_interface, &transform_node_id, transform * usvg_transform(node.abs_transform()));

View file

@ -14,7 +14,7 @@ use graphene_std::raster::BlendMode;
use graphene_std::raster_types::{CPU, Raster};
use graphene_std::table::Table;
use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::VectorData;
use graphene_std::vector::Vector;
use graphene_std::vector::style::{Fill, Stroke};
use graphene_std::vector::{PointId, VectorModificationType};
use graphene_std::{Graphic, NodeInputDecleration};
@ -131,8 +131,8 @@ impl<'a> ModifyInputsContext<'a> {
/// Creates an artboard as the primary export for the document network
pub fn create_artboard(&mut self, new_id: NodeId, artboard: Artboard) -> LayerNodeIdentifier {
let artboard_node_template = resolve_document_node_type("Artboard").expect("Node").node_template_input_override([
Some(NodeInput::value(TaggedValue::ArtboardGroup(Default::default()), true)),
Some(NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true)),
Some(NodeInput::value(TaggedValue::Artboard(Default::default()), true)),
Some(NodeInput::value(TaggedValue::Group(Default::default()), true)),
Some(NodeInput::value(TaggedValue::DVec2(artboard.location.into()), false)),
Some(NodeInput::value(TaggedValue::DVec2(artboard.dimensions.into()), false)),
Some(NodeInput::value(TaggedValue::Color(artboard.background), false)),
@ -144,7 +144,7 @@ impl<'a> ModifyInputsContext<'a> {
pub fn insert_boolean_data(&mut self, operation: graphene_std::path_bool::BooleanOperation, layer: LayerNodeIdentifier) {
let boolean = resolve_document_node_type("Boolean Operation").expect("Boolean node does not exist").node_template_input_override([
Some(NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true)),
Some(NodeInput::value(TaggedValue::Group(Default::default()), true)),
Some(NodeInput::value(TaggedValue::BooleanOperation(operation), false)),
]);
@ -153,12 +153,12 @@ impl<'a> ModifyInputsContext<'a> {
self.network_interface.move_node_to_chain_start(&boolean_id, layer, &[]);
}
pub fn insert_vector_data(&mut self, subpaths: Vec<Subpath<PointId>>, layer: LayerNodeIdentifier, include_transform: bool, include_fill: bool, include_stroke: bool) {
let vector_data = Table::new_from_element(VectorData::from_subpaths(subpaths, true));
pub fn insert_vector(&mut self, subpaths: Vec<Subpath<PointId>>, layer: LayerNodeIdentifier, include_transform: bool, include_fill: bool, include_stroke: bool) {
let vector = Table::new_from_element(Vector::from_subpaths(subpaths, true));
let shape = resolve_document_node_type("Path")
.expect("Path node does not exist")
.node_template_input_override([Some(NodeInput::value(TaggedValue::VectorData(vector_data), false))]);
.node_template_input_override([Some(NodeInput::value(TaggedValue::Vector(vector), false))]);
let shape_id = NodeId::new();
self.network_interface.insert_node(shape_id, shape, &[]);
self.network_interface.move_node_to_chain_start(&shape_id, layer, &[]);
@ -223,7 +223,7 @@ impl<'a> ModifyInputsContext<'a> {
let transform = resolve_document_node_type("Transform").expect("Transform node does not exist").default_node_template();
let image = resolve_document_node_type("Image Value")
.expect("ImageValue node does not exist")
.node_template_input_override([Some(NodeInput::value(TaggedValue::None, false)), Some(NodeInput::value(TaggedValue::RasterData(image_frame), false))]);
.node_template_input_override([Some(NodeInput::value(TaggedValue::None, false)), Some(NodeInput::value(TaggedValue::Raster(image_frame), false))]);
let image_id = NodeId::new();
self.network_interface.insert_node(image_id, image, &[]);
@ -296,11 +296,12 @@ impl<'a> ModifyInputsContext<'a> {
pub fn create_node(&mut self, reference: &str) -> Option<NodeId> {
let output_layer = self.get_output_layer()?;
let Some(node_definition) = resolve_document_node_type(reference) else {
log::error!("Node type {} does not exist in ModifyInputsContext::existing_node_id", reference);
log::error!("Node type {reference} does not exist in ModifyInputsContext::existing_node_id");
return None;
};
// If inserting a path node, insert a Flatten Path if the type is a graphic group.
// TODO: Allow the path node to operate on Graphic Group data by utilizing the reference for each vector data in a group.
// If inserting a path node, insert a Flatten Path if the type is Group.
// TODO: Allow the path node to operate on Group data by utilizing the reference for each Vector in a group.
if node_definition.identifier == "Path" {
let layer_input_type = self.network_interface.input_type(&InputConnector::node(output_layer.to_node(), 1), &[]).0.nested_type().clone();
if layer_input_type == concrete!(Table<Graphic>) {

View file

@ -24,7 +24,7 @@ use graphene_std::table::Table;
use graphene_std::text::{Font, TypesettingConfig};
#[allow(unused_imports)]
use graphene_std::transform::Footprint;
use graphene_std::vector::VectorData;
use graphene_std::vector::Vector;
use graphene_std::*;
use std::collections::{HashMap, HashSet, VecDeque};
@ -232,14 +232,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// Secondary (left) input type coercion
DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 1)],
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_element::IDENTIFIER),
implementation: DocumentNodeImplementation::ProtoNode(graphic::to_element::IDENTIFIER),
manual_composition: Some(concrete!(Context)),
..Default::default()
},
// Primary (bottom) input type coercion
DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 0)],
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_group::IDENTIFIER),
implementation: DocumentNodeImplementation::ProtoNode(graphic::to_group::IDENTIFIER),
manual_composition: Some(concrete!(Context)),
..Default::default()
},
@ -258,7 +258,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::node(NodeId(2), 0),
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
],
implementation: DocumentNodeImplementation::ProtoNode(graphic_element::layer::IDENTIFIER),
implementation: DocumentNodeImplementation::ProtoNode(graphic::layer::IDENTIFIER),
..Default::default()
},
]
@ -269,8 +269,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true),
NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true),
NodeInput::value(TaggedValue::Group(Default::default()), true),
NodeInput::value(TaggedValue::Group(Default::default()), true),
],
..Default::default()
},
@ -377,8 +377,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::ArtboardGroup(Default::default()), true),
NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true),
NodeInput::value(TaggedValue::Artboard(Default::default()), true),
NodeInput::value(TaggedValue::Group(Default::default()), true),
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
NodeInput::value(TaggedValue::DVec2(DVec2::new(1920., 1080.)), false),
NodeInput::value(TaggedValue::Color(Color::WHITE), false),
@ -628,7 +628,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::VectorData(Default::default()), true),
NodeInput::value(TaggedValue::Vector(Default::default()), true),
NodeInput::value(
TaggedValue::Footprint(Footprint {
transform: DAffine2::from_scale_angle_translation(DVec2::new(1000., 1000.), 0., DVec2::new(0., 0.)),
@ -682,7 +682,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
},
},
description: Cow::Borrowed("Rasterizes the given vector data"),
description: Cow::Borrowed("TODO"),
properties: None,
},
DocumentNodeDefinition {
@ -794,7 +794,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::RasterData(Default::default()), true)],
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
@ -879,7 +879,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::RasterData(Default::default()), true)],
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
@ -947,7 +947,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::RasterData(Default::default()), true),
NodeInput::value(TaggedValue::Raster(Default::default()), true),
NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false),
NodeInput::value(TaggedValue::BrushCache(BrushCache::default()), false),
],
@ -986,7 +986,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::RasterData(Default::default()), true)],
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
manual_composition: Some(concrete!(Context)),
..Default::default()
},
@ -1005,7 +1005,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::RasterData(Default::default()), true)],
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
manual_composition: Some(concrete!(Context)),
..Default::default()
},
@ -1117,7 +1117,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::RasterData(Default::default()), true)],
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
@ -1196,7 +1196,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// document_node: DocumentNode {
// implementation: DocumentNodeImplementation::proto("graphene_core::raster::CurvesNode"),
// inputs: vec![
// NodeInput::value(TaggedValue::RasterData(Default::default()), true),
// NodeInput::value(TaggedValue::Raster(Default::default()), true),
// NodeInput::value(TaggedValue::Curve(Default::default()), false),
// ],
// ..Default::default()
@ -1219,7 +1219,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: vec![
DocumentNode {
inputs: vec![NodeInput::network(concrete!(Table<VectorData>), 0)],
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0)],
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
manual_composition: Some(generic!(T)),
skip_deduplication: true,
@ -1243,14 +1243,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::VectorData(Default::default()), true),
NodeInput::value(TaggedValue::Vector(Default::default()), true),
NodeInput::value(TaggedValue::VectorModification(Default::default()), false),
],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("Vector Data", "TODO").into(), ("Modification", "TODO").into()],
output_names: vec!["Vector Data".to_string()],
input_metadata: vec![("Content", "TODO").into(), ("Modification", "TODO").into()],
output_names: vec!["Modified".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
@ -1374,7 +1374,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}),
),
InputMetadata::with_name_description_override("Align", "TODO", WidgetOverride::Custom("text_align".to_string())),
("Per-Glyph Instances", "Splits each text glyph into its own row in the table of vector data.").into(),
("Per-Glyph Instances", "Splits each text glyph into its own row in the table of vector geometry.").into(),
],
output_names: vec!["Vector".to_string()],
..Default::default()
@ -1496,7 +1496,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(3), 0)],
nodes: vec![
DocumentNode {
inputs: vec![NodeInput::network(concrete!(Table<VectorData>), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER),
manual_composition: Some(generic!(T)),
..Default::default()
@ -1527,7 +1527,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true),
NodeInput::value(TaggedValue::Group(Default::default()), true),
NodeInput::value(TaggedValue::BooleanOperation(path_bool::BooleanOperation::Union), false),
],
..Default::default()
@ -1594,14 +1594,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
exports: vec![NodeInput::node(NodeId(4), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::network(concrete!(Table<VectorData>), 0)],
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0)],
implementation: DocumentNodeImplementation::ProtoNode(vector::subpath_segment_lengths::IDENTIFIER),
manual_composition: Some(generic!(T)),
..Default::default()
},
DocumentNode {
inputs: vec![
NodeInput::network(concrete!(Table<VectorData>), 0),
NodeInput::network(concrete!(Table<Vector>), 0),
NodeInput::network(concrete!(vector::misc::PointSpacingType), 1),
NodeInput::network(concrete!(f64), 2),
NodeInput::network(concrete!(u32), 3),
@ -1640,7 +1640,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::VectorData(Default::default()), true),
NodeInput::value(TaggedValue::Vector(Default::default()), true),
NodeInput::value(TaggedValue::PointSpacingType(Default::default()), false),
NodeInput::value(TaggedValue::F64(100.), false),
NodeInput::value(TaggedValue::U32(100), false),
@ -1704,7 +1704,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
input_metadata: vec![
("Vector Data", "The shape to be resampled and converted into a polyline.").into(),
("Content", "The shape to be resampled and converted into a polyline.").into(),
("Spacing", node_properties::SAMPLE_POLYLINE_TOOLTIP_SPACING).into(),
InputMetadata::with_name_description_override(
"Separation",
@ -1761,7 +1761,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [
DocumentNode {
inputs: vec![
NodeInput::network(concrete!(Table<VectorData>), 0),
NodeInput::network(concrete!(Table<Vector>), 0),
NodeInput::network(concrete!(f64), 1),
NodeInput::network(concrete!(u32), 2),
],
@ -1795,7 +1795,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
inputs: vec![
NodeInput::value(TaggedValue::VectorData(Default::default()), true),
NodeInput::value(TaggedValue::Vector(Default::default()), true),
NodeInput::value(TaggedValue::F64(10.), false),
NodeInput::value(TaggedValue::U32(0), false),
],
@ -1847,7 +1847,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
..Default::default()
}),
input_metadata: vec![
("Vector Data", "TODO").into(),
("Content", "TODO").into(),
InputMetadata::with_name_description_override(
"Separation Disk Diameter",
"TODO",

View file

@ -11,6 +11,7 @@ use glam::{DAffine2, DVec2};
use graph_craft::Type;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
use graphene_std::NodeInputDecleration;
use graphene_std::animation::RealTimeMode;
use graphene_std::extract_xy::XY;
use graphene_std::path_bool::BooleanOperation;
@ -19,14 +20,10 @@ use graphene_std::raster::{
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
SelectiveColorChoice,
};
use graphene_std::raster_types::{CPU, GPU, Raster};
use graphene_std::table::Table;
use graphene_std::text::{Font, TextAlign};
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
use graphene_std::vector::VectorData;
use graphene_std::vector::misc::{ArcType, CentroidType, GridType, MergeByDistanceAlgorithm, PointSpacingType};
use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops, GradientType, PaintOrder, StrokeAlign, StrokeCap, StrokeJoin};
use graphene_std::{Graphic, NodeInputDecleration};
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
let widget = TextLabel::new(text).widget_holder();
@ -180,12 +177,6 @@ pub(crate) fn property_from_type(
// ==========================
Some(x) if x == TypeId::of::<Vec<f64>>() => array_of_number_widget(default_info, TextInput::default()).into(),
Some(x) if x == TypeId::of::<Vec<DVec2>>() => array_of_vec2_widget(default_info, TextInput::default()).into(),
// ====================
// GRAPHICAL DATA TYPES
// ====================
Some(x) if x == TypeId::of::<Table<VectorData>>() => vector_data_widget(default_info).into(),
Some(x) if x == TypeId::of::<Table<Raster<CPU>>>() || x == TypeId::of::<Table<Raster<GPU>>>() => raster_widget(default_info).into(),
Some(x) if x == TypeId::of::<Table<Graphic>>() => group_widget(default_info).into(),
// ============
// STRUCT TYPES
// ============
@ -794,33 +785,6 @@ pub fn font_inputs(parameter_widgets_info: ParameterWidgetsInfo) -> (Vec<WidgetH
(first_widgets, second_widgets)
}
pub fn vector_data_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
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());
widgets
}
pub fn raster_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
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());
widgets
}
pub fn group_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Vec<WidgetHolder> {
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());
widgets
}
pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props: NumberInput) -> Vec<WidgetHolder> {
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
@ -1880,7 +1844,7 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
let mut expression = x.value.trim().to_string();
if ["+", "-", "*", "/", "^", "%"].iter().any(|&infix| infix == expression) {
expression = format!("A {} B", expression);
expression = format!("A {expression} B");
} else if expression == "^" {
expression = String::from("A^B");
}
@ -2078,7 +2042,7 @@ pub mod choice {
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);
log::error!("Could not get document node when building property row for node {node_id:?}");
return LayoutGroup::Row { widgets: Vec::new() };
};

View file

@ -9,7 +9,7 @@ pub enum FrontendGraphDataType {
#[default]
General,
Raster,
VectorData,
Vector,
Number,
Group,
Artboard,
@ -18,8 +18,8 @@ pub enum FrontendGraphDataType {
impl FrontendGraphDataType {
pub fn from_type(input: &Type) -> Self {
match TaggedValue::from_type_or_none(input) {
TaggedValue::RasterData(_) => Self::Raster,
TaggedValue::VectorData(_) => Self::VectorData,
TaggedValue::Raster(_) => Self::Raster,
TaggedValue::Vector(_) => Self::Vector,
TaggedValue::U32(_)
| TaggedValue::U64(_)
| TaggedValue::F64(_)
@ -28,8 +28,8 @@ impl FrontendGraphDataType {
| TaggedValue::VecF64(_)
| TaggedValue::VecDVec2(_)
| TaggedValue::DAffine2(_) => Self::Number,
TaggedValue::GraphicGroup(_) => Self::Group,
TaggedValue::ArtboardGroup(_) => Self::Artboard,
TaggedValue::Group(_) => Self::Group,
TaggedValue::Artboard(_) => Self::Artboard,
_ => Self::General,
}
}

View file

@ -5,7 +5,7 @@ use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerSta
use crate::messages::tool::tool_messages::tool_prelude::{DocumentMessageHandler, PreferencesMessageHandler};
use bezier_rs::{Bezier, BezierHandles};
use glam::{DAffine2, DVec2};
use graphene_std::vector::ManipulatorPointId;
use graphene_std::vector::misc::ManipulatorPointId;
use graphene_std::vector::{PointId, SegmentId};
use wasm_bindgen::JsCast;
@ -42,9 +42,9 @@ pub fn selected_segments(network_interface: &NodeNetworkInterface, shape_editor:
// TODO: Currently if there are two duplicate layers, both of their segments get overlays
// Adding segments which are are connected to selected anchors
for layer in network_interface.selected_nodes().selected_layers(network_interface.document_metadata()) {
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { continue };
let Some(vector) = network_interface.compute_modified_vector(layer) else { continue };
for (segment_id, _bezier, start, end) in vector_data.segment_bezier_iter() {
for (segment_id, _bezier, start, end) in vector.segment_bezier_iter() {
if selected_anchors.contains(&start) || selected_anchors.contains(&end) {
selected_segments.push(segment_id);
}
@ -118,14 +118,14 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
let display_anchors = overlay_context.visibility_settings.anchors();
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
if display_path {
overlay_context.outline_vector(&vector_data, transform);
overlay_context.outline_vector(&vector, transform);
}
// Get the selected segments and then add a bold line overlay on them
for (segment_id, bezier, _, _) in vector_data.segment_bezier_iter() {
for (segment_id, bezier, _, _) in vector.segment_bezier_iter() {
let Some(selected_shape_state) = shape_editor.selected_shape_state.get_mut(&layer) else {
continue;
};
@ -139,30 +139,30 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
let is_selected = |point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point));
if display_handles {
let opposite_handles_data: Vec<(PointId, SegmentId)> = shape_editor.selected_points().filter_map(|point_id| vector_data.adjacent_segment(point_id)).collect();
let opposite_handles_data: Vec<(PointId, SegmentId)> = shape_editor.selected_points().filter_map(|point_id| vector.adjacent_segment(point_id)).collect();
match draw_handles {
DrawHandles::All => {
vector_data.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| {
vector.segment_bezier_iter().for_each(|(segment_id, bezier, _start, _end)| {
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
});
}
DrawHandles::SelectedAnchors(ref selected_segments) => {
vector_data
vector
.segment_bezier_iter()
.filter(|(segment_id, ..)| selected_segments.contains(segment_id))
.for_each(|(segment_id, bezier, _start, _end)| {
overlay_bezier_handles(bezier, segment_id, transform, is_selected, overlay_context);
});
for (segment_id, bezier, start, end) in vector_data.segment_bezier_iter() {
for (segment_id, bezier, start, end) in vector.segment_bezier_iter() {
if let Some((corresponding_anchor, _)) = opposite_handles_data.iter().find(|(_, adj_segment_id)| adj_segment_id == &segment_id) {
overlay_bezier_handle_specific_point(bezier, segment_id, (start, end), *corresponding_anchor, transform, is_selected, overlay_context);
}
}
}
DrawHandles::FrontierHandles(ref segment_endpoints) => {
vector_data
vector
.segment_bezier_iter()
.filter(|(segment_id, ..)| segment_endpoints.contains_key(segment_id))
.for_each(|(segment_id, bezier, start, end)| {
@ -179,7 +179,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
}
if display_anchors {
for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
for (&id, &position) in vector.point_domain.ids().iter().zip(vector.point_domain.positions()) {
overlay_context.manipulator_anchor(transform.transform_point2(position), is_selected(ManipulatorPointId::Anchor(id)), None);
}
}
@ -192,7 +192,7 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
}
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
let Some(vector) = document.network_interface.compute_modified_vector(layer) else {
continue;
};
//let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
@ -200,8 +200,8 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
let selected = shape_editor.selected_shape_state.get(&layer);
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point));
for point in vector_data.extendable_points(preferences.vector_meshes) {
let Some(position) = vector_data.point_domain.position_from_id(point) else { continue };
for point in vector.extendable_points(preferences.vector_meshes) {
let Some(position) = vector.point_domain.position_from_id(point) else { continue };
let position = transform.transform_point2(position);
overlay_context.manipulator_anchor(position, is_selected(selected, ManipulatorPointId::Anchor(point)), None);
}

View file

@ -12,7 +12,7 @@ use glam::{DAffine2, DVec2};
use graphene_std::Color;
use graphene_std::math::quad::Quad;
use graphene_std::vector::click_target::ClickTargetType;
use graphene_std::vector::{PointId, SegmentId, VectorData};
use graphene_std::vector::{PointId, SegmentId, Vector};
use std::collections::HashMap;
use wasm_bindgen::{JsCast, JsValue};
use web_sys::{OffscreenCanvas, OffscreenCanvasRenderingContext2d};
@ -757,12 +757,12 @@ impl OverlayContext {
}
/// Used by the Pen and Path tools to outline the path of the shape.
pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
pub fn outline_vector(&mut self, vector: &Vector, transform: DAffine2) {
self.start_dpi_aware_transform();
self.render_context.begin_path();
let mut last_point = None;
for (_, bezier, start_id, end_id) in vector_data.segment_bezier_iter() {
for (_, bezier, start_id, end_id) in vector.segment_bezier_iter() {
let move_to = last_point != Some(start_id);
last_point = Some(end_id);

View file

@ -11,7 +11,7 @@ use glam::{DAffine2, DVec2};
use graphene_std::Color;
use graphene_std::math::quad::Quad;
use graphene_std::vector::click_target::ClickTargetType;
use graphene_std::vector::{PointId, SegmentId, VectorData};
use graphene_std::vector::{PointId, SegmentId, Vector};
use std::collections::HashMap;
use std::sync::{Arc, Mutex, MutexGuard};
use vello::Scene;
@ -338,8 +338,8 @@ impl OverlayContext {
}
/// Used by the Pen and Path tools to outline the path of the shape.
pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
self.internal().outline_vector(vector_data, transform);
pub fn outline_vector(&mut self, vector: &Vector, transform: DAffine2) {
self.internal().outline_vector(vector, transform);
}
/// Used by the Pen tool in order to show how the bezier curve would look like.
@ -834,12 +834,12 @@ impl OverlayContextInternal {
}
/// Used by the Pen and Path tools to outline the path of the shape.
fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
fn outline_vector(&mut self, vector: &Vector, transform: DAffine2) {
let vello_transform = self.get_transform();
let mut path = BezPath::new();
let mut last_point = None;
for (_, bezier, start_id, end_id) in vector_data.segment_bezier_iter() {
for (_, bezier, start_id, end_id) in vector.segment_bezier_iter() {
let move_to = last_point != Some(start_id);
last_point = Some(end_id);

View file

@ -8,7 +8,7 @@ use graph_craft::document::NodeId;
use graphene_std::math::quad::Quad;
use graphene_std::transform::Footprint;
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::{PointId, VectorData};
use graphene_std::vector::{PointId, Vector};
use std::collections::{HashMap, HashSet};
use std::num::NonZeroU64;
@ -26,7 +26,7 @@ pub struct DocumentMetadata {
pub structure: HashMap<LayerNodeIdentifier, NodeRelations>,
pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
pub clip_targets: HashSet<NodeId>,
pub vector_modify: HashMap<NodeId, VectorData>,
pub vector_modify: HashMap<NodeId, Vector>,
/// Transform from document space to viewport space.
pub document_to_viewport: DAffine2,
}

View file

@ -18,7 +18,7 @@ use graphene_std::math::quad::Quad;
use graphene_std::table::Table;
use graphene_std::transform::Footprint;
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::{PointId, VectorData, VectorModificationType};
use graphene_std::vector::{PointId, Vector, VectorModificationType};
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
use interpreted_executor::node_registry::NODE_REGISTRY;
use serde_json::{Value, json};
@ -675,7 +675,7 @@ impl NodeNetworkInterface {
let valid_implementation = (0..number_of_inputs).filter(|iterator_index| iterator_index != input_index).all(|iterator_index| {
let input_type = self.input_type(&InputConnector::node(*node_id, iterator_index), network_path).0;
// Value inputs are stored as concrete, so they are compared to the nested type. Node inputs are stored as fn, so they are compared to the entire type.
// For example a node input of (Footprint) -> VectorData would not be compatible with () -> VectorData
// For example a node input of (Footprint) -> Vector would not be compatible with () -> Vector
node_io.inputs.get(iterator_index).map(|ty| ty.nested_type().clone()).as_ref() == Some(&input_type) || node_io.inputs.get(iterator_index) == Some(&input_type)
});
if valid_implementation { node_io.inputs.get(*input_index).cloned() } else { None }
@ -3444,12 +3444,12 @@ impl NodeNetworkInterface {
(layer_widths, chain_widths, has_left_input_wire)
}
pub fn compute_modified_vector(&self, layer: LayerNodeIdentifier) -> Option<VectorData> {
pub fn compute_modified_vector(&self, layer: LayerNodeIdentifier) -> Option<Vector> {
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, self);
if let Some(path_node) = graph_layer.upstream_visible_node_id_from_name_in_layer("Path") {
if let Some(vector_data) = self.document_metadata.vector_modify.get(&path_node) {
let mut modified = vector_data.clone();
if let Some(vector) = self.document_metadata.vector_modify.get(&path_node) {
let mut modified = vector.clone();
let path_node = self.document_network().nodes.get(&path_node);
let modification_input = path_node.and_then(|node: &DocumentNode| node.inputs.get(1)).and_then(|input| input.as_value());
@ -3464,7 +3464,7 @@ impl NodeNetworkInterface {
.click_targets
.get(&layer)
.map(|click| click.iter().map(ClickTarget::target_type))
.map(|target_types| VectorData::from_target_types(target_types, true))
.map(|target_types| Vector::from_target_types(target_types, true))
}
/// Loads the structure of layer nodes from a node graph.
@ -3575,7 +3575,7 @@ impl NodeNetworkInterface {
}
/// Update the vector modify of the layers
pub fn update_vector_modify(&mut self, new_vector_modify: HashMap<NodeId, VectorData>) {
pub fn update_vector_modify(&mut self, new_vector_modify: HashMap<NodeId, Vector>) {
self.document_metadata.vector_modify = new_vector_modify;
}
}
@ -6613,22 +6613,26 @@ fn migrate_output_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Re
const REPLACEMENTS: &[(&str, &str)] = &[
// Single to table data
("VectorData", "Table<VectorData>"),
("GraphicGroup", "Table<GraphicGroup>"),
("VectorData", "Table<Vector>"),
("GraphicGroup", "Table<Group>"),
("ImageFrame", "Table<Image>"),
// `ImageFrame` to `Image` rename
("Instances<ImageFrame>", "Table<Image>"),
// `Instances` to `Table` rename
("Instances<VectorData>", "Table<VectorData>"),
("Instances<GraphicGroup>", "Table<GraphicGroup>"),
("Instances<VectorData>", "Table<Vector>"),
("Instances<GraphicGroup>", "Table<Group>"),
("Instances<Image>", "Table<Image>"),
("Instances<GraphicElement>", "Table<Graphic>"),
("Table<GraphicElement>", "Table<Graphic>"),
("Future<Instances<VectorData>>", "Future<Table<VectorData>>"),
("Future<Instances<GraphicGroup>>", "Future<Table<GraphicGroup>>"),
("Future<Instances<Vector>>", "Future<Table<Vector>>"),
("Future<Instances<GraphicGroup>>", "Future<Table<Group>>"),
("Future<Instances<Image>>", "Future<Table<Image>>"),
("Future<Instances<GraphicElement>>", "Future<Table<Graphic>>"),
("Future<Table<GraphicElement>>", "Future<Table<Graphic>>"),
("Future<Table<VectorData>>", "Future<Table<Vector>>"),
("Table<VectorData>", "Table<Vector>"),
("Table<GraphicGroup>", "Table<Group>"),
("Future<Table<GraphicGroup>>", "Future<Table<Group>>"),
];
let mut names = Vec::<String>::deserialize(deserializer)?;

View file

@ -8,7 +8,8 @@ use crate::messages::tool::common_functionality::shape_editor::ShapeState;
use crate::messages::tool::utility_types::ToolType;
use glam::{DAffine2, DMat2, DVec2};
use graphene_std::renderer::Quad;
use graphene_std::vector::{HandleExt, HandleId, ManipulatorPointId, PointId, VectorModificationType};
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
use graphene_std::vector::{HandleExt, PointId, VectorModificationType};
use std::collections::{HashMap, VecDeque};
use std::f64::consts::PI;
@ -79,7 +80,7 @@ impl OriginalTransforms {
if path_map.contains_key(&layer) {
continue;
}
let Some(vector_data) = network_interface.compute_modified_vector(layer) else {
let Some(vector) = network_interface.compute_modified_vector(layer) else {
continue;
};
let Some(selected_points) = shape_editor.selected_points_in_layer(layer) else {
@ -91,7 +92,7 @@ impl OriginalTransforms {
let mut selected_points = selected_points.clone();
for (segment_id, _, start, end) in vector_data.segment_bezier_iter() {
for (segment_id, _, start, end) in vector.segment_bezier_iter() {
if selected_segments.contains(&segment_id) {
selected_points.insert(ManipulatorPointId::Anchor(start));
selected_points.insert(ManipulatorPointId::Anchor(end));
@ -100,23 +101,23 @@ impl OriginalTransforms {
// Anchors also move their handles
let anchor_ids = selected_points.iter().filter_map(|point| point.as_anchor());
let anchors = anchor_ids.filter_map(|id| vector_data.point_domain.position_from_id(id).map(|pos| (id, AnchorPoint { initial: pos, current: pos })));
let anchors = anchor_ids.filter_map(|id| vector.point_domain.position_from_id(id).map(|pos| (id, AnchorPoint { initial: pos, current: pos })));
let anchors = anchors.collect();
let selected_handles = selected_points.iter().filter_map(|point| point.as_handle());
let anchor_ids = selected_points.iter().filter_map(|point| point.as_anchor());
let connected_handles = anchor_ids.flat_map(|point| vector_data.all_connected(point));
let connected_handles = anchor_ids.flat_map(|point| vector.all_connected(point));
let all_handles = selected_handles.chain(connected_handles);
let handles = all_handles
.filter_map(|id| {
let anchor = id.to_manipulator_point().get_anchor(&vector_data)?;
let initial = id.to_manipulator_point().get_position(&vector_data)?;
let relative = vector_data.point_domain.position_from_id(anchor)?;
let other_handle = vector_data
let anchor = id.to_manipulator_point().get_anchor(&vector)?;
let initial = id.to_manipulator_point().get_position(&vector)?;
let relative = vector.point_domain.position_from_id(anchor)?;
let other_handle = vector
.other_colinear_handle(id)
.filter(|other| !selected_points.contains(&other.to_manipulator_point()) && !selected_points.contains(&ManipulatorPointId::Anchor(anchor)));
let mirror = other_handle.and_then(|id| Some((id, id.to_manipulator_point().get_position(&vector_data)?)));
let mirror = other_handle.and_then(|id| Some((id, id.to_manipulator_point().get_position(&vector)?)));
Some((id, HandlePoint { initial, relative, anchor, mirror }))
})

View file

@ -13,7 +13,7 @@ use graphene_std::ProtoNodeIdentifier;
use graphene_std::table::Table;
use graphene_std::text::{TextAlign, TypesettingConfig};
use graphene_std::uuid::NodeId;
use graphene_std::vector::VectorData;
use graphene_std::vector::Vector;
use graphene_std::vector::style::{PaintOrder, StrokeAlign};
use std::collections::HashMap;
@ -38,12 +38,28 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
aliases: &["graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"],
},
NodeReplacement {
node: graphene_std::graphic_element::to_element::IDENTIFIER,
node: graphene_std::graphic::to_element::IDENTIFIER,
aliases: &["graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"],
},
NodeReplacement {
node: graphene_std::graphic_element::to_group::IDENTIFIER,
aliases: &["graphene_core::ToGraphicGroupNode"],
node: graphene_std::graphic::to_group::IDENTIFIER,
aliases: &["graphene_core::ToGraphicGroupNode", "graphene_core::graphic_element::ToGroupNode"],
},
NodeReplacement {
node: graphene_std::graphic::layer::IDENTIFIER,
aliases: &["graphene_core::graphic_element::LayerNode"],
},
NodeReplacement {
node: graphene_std::graphic::flatten_group::IDENTIFIER,
aliases: &["graphene_core::graphic_element::FlattenGroupNode"],
},
NodeReplacement {
node: graphene_std::graphic::flatten_vector::IDENTIFIER,
aliases: &["graphene_core::graphic_element::FlattenVectorNode"],
},
NodeReplacement {
node: graphene_std::graphic::index::IDENTIFIER,
aliases: &["graphene_core::graphic_element::IndexNode"],
},
// math_nodes
NodeReplacement {
@ -456,6 +472,10 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
node: graphene_std::path_bool::boolean_operation::IDENTIFIER,
aliases: &["graphene_std::vector::BooleanOperationNode"],
},
NodeReplacement {
node: graphene_std::vector::path_modify::IDENTIFIER,
aliases: &["graphene_core::vector::vector_data::modification::PathModifyNode"],
},
// brush
NodeReplacement {
node: graphene_std::brush::brush::brush_stamp_generator::IDENTIFIER,
@ -591,13 +611,13 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
return None;
}
// Obtain the document node for the given node ID, extract the vector points, and create vector data from the list of points
// Obtain the document node for the given node ID, extract the vector points, and create a Vector path from the list of points
let node = document.network_interface.document_node(node_id, network_path)?;
let Some(TaggedValue::VecDVec2(points)) = node.inputs.get(1).and_then(|tagged_value| tagged_value.as_value()) else {
log::error!("The old Spline node's input at index 1 is not a TaggedValue::VecDVec2");
return None;
};
let vector_data = VectorData::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false));
let vector = Vector::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false));
// Retrieve the output connectors linked to the "Spline" node's output port
let Some(spline_outputs) = document.network_interface.outward_wires(network_path)?.get(&OutputConnector::node(*node_id, 0)).cloned() else {
@ -611,13 +631,13 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
return None;
};
// Get the "Path" node definition and fill it in with the vector data and default vector modification
// Get the "Path" node definition and fill it in with the Vector path and default vector modification
let Some(path_node_type) = resolve_document_node_type("Path") else {
log::error!("Path node does not exist.");
return None;
};
let path_node = path_node_type.node_template_input_override([
Some(NodeInput::value(TaggedValue::VectorData(Table::new_from_element(vector_data)), true)),
Some(NodeInput::value(TaggedValue::Vector(Table::new_from_element(vector)), true)),
Some(NodeInput::value(TaggedValue::VectorModification(Default::default()), false)),
]);

View file

@ -29,7 +29,8 @@ use graph_craft::document::NodeId;
use graphene_std::Color;
use graphene_std::renderer::Quad;
use graphene_std::text::Font;
use graphene_std::vector::{HandleId, PointId, SegmentId, VectorData, VectorModificationType};
use graphene_std::vector::misc::HandleId;
use graphene_std::vector::{PointId, SegmentId, Vector, VectorModificationType};
use std::vec;
#[derive(ExtractField)]
@ -567,7 +568,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
// If not using Path tool, create new layers and add paths into those
if let Some(document) = self.active_document() {
let Ok(data) = serde_json::from_str::<Vec<(LayerNodeIdentifier, VectorData, DAffine2)>>(&data) else {
let Ok(data) = serde_json::from_str::<Vec<(LayerNodeIdentifier, Vector, DAffine2)>>(&data) else {
return;
};
@ -603,7 +604,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
let stroke = graphene_std::vector::style::Stroke::new(Some(stroke_color.to_gamma_srgb()), DEFAULT_STROKE_WIDTH);
responses.add(GraphOperationMessage::StrokeSet { layer, stroke });
// Create new point ids and add those into the existing vector data
// Create new point ids and add those into the existing Vector path
let mut points_map = HashMap::new();
for (point, position) in new_vector.point_domain.iter() {
let new_point_id = PointId::generate();
@ -612,7 +613,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
// Create new segment ids and add the segments into the existing vector data
// Create new segment ids and add the segments into the existing Vector path
let mut segments_map = HashMap::new();
for (segment_id, bezier, start, end) in new_vector.segment_bezier_iter() {
let new_segment_id = SegmentId::generate();

View file

@ -19,13 +19,13 @@ pub enum SpreadsheetMessage {
len: usize,
},
ViewVectorDataDomain {
domain: VectorDataDomain,
ViewVectorDomain {
domain: VectorDomain,
},
}
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]
pub enum VectorDataDomain {
pub enum VectorDomain {
#[default]
Points,
Segments,

View file

@ -1,4 +1,4 @@
use super::VectorDataDomain;
use super::VectorDomain;
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, LayoutTarget, WidgetLayout};
use crate::messages::prelude::*;
use crate::messages::tool::tool_messages::tool_prelude::*;
@ -8,7 +8,7 @@ use graphene_std::Context;
use graphene_std::memo::IORecord;
use graphene_std::raster::Image;
use graphene_std::table::Table;
use graphene_std::vector::VectorData;
use graphene_std::vector::Vector;
use graphene_std::{Artboard, Graphic};
use std::any::Any;
use std::sync::Arc;
@ -21,7 +21,7 @@ pub struct SpreadsheetMessageHandler {
inspect_node: Option<NodeId>,
introspected_data: Option<Arc<dyn Any + Send + Sync>>,
element_path: Vec<usize>,
viewing_vector_data_domain: VectorDataDomain,
viewing_vector_domain: VectorDomain,
}
#[message_handler_data]
@ -54,8 +54,8 @@ impl MessageHandler<SpreadsheetMessage, ()> for SpreadsheetMessageHandler {
self.update_layout(responses);
}
SpreadsheetMessage::ViewVectorDataDomain { domain } => {
self.viewing_vector_data_domain = domain;
SpreadsheetMessage::ViewVectorDomain { domain } => {
self.viewing_vector_domain = domain;
self.update_layout(responses);
}
}
@ -79,7 +79,7 @@ impl SpreadsheetMessageHandler {
current_depth: 0,
desired_path: &mut self.element_path,
breadcrumbs: Vec::new(),
vector_data_domain: self.viewing_vector_data_domain,
vector_domain: self.viewing_vector_domain,
};
let mut layout = self
.introspected_data
@ -106,7 +106,7 @@ struct LayoutData<'a> {
current_depth: usize,
desired_path: &'a mut Vec<usize>,
breadcrumbs: Vec<String>,
vector_data_domain: VectorDataDomain,
vector_domain: VectorDomain,
}
fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
@ -116,9 +116,9 @@ fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'st
Some(io.output.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), Table<Artboard>>>() {
Some(io.output.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<VectorData>>>() {
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Vector>>>() {
Some(io.output.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), Table<VectorData>>>() {
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), Table<Vector>>>() {
Some(io.output.layout_with_breadcrumb(data))
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Graphic>>>() {
Some(io.output.layout_with_breadcrumb(data))
@ -154,10 +154,10 @@ impl TableRowLayout for Graphic {
}
fn identifier(&self) -> String {
match self {
Self::GraphicGroup(table) => table.identifier(),
Self::VectorData(table) => table.identifier(),
Self::RasterDataCPU(_) => "RasterDataCPU".to_string(),
Self::RasterDataGPU(_) => "RasterDataGPU".to_string(),
Self::Group(group) => group.identifier(),
Self::Vector(vector) => vector.identifier(),
Self::RasterCPU(_) => "Raster (on CPU)".to_string(),
Self::RasterGPU(_) => "Raster (on GPU)".to_string(),
}
}
// Don't put a breadcrumb for Graphic
@ -166,48 +166,48 @@ impl TableRowLayout for Graphic {
}
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
match self {
Self::GraphicGroup(table) => table.layout_with_breadcrumb(data),
Self::VectorData(table) => table.layout_with_breadcrumb(data),
Self::RasterDataCPU(_) => label("Raster is not supported"),
Self::RasterDataGPU(_) => label("Raster is not supported"),
Self::Group(table) => table.layout_with_breadcrumb(data),
Self::Vector(table) => table.layout_with_breadcrumb(data),
Self::RasterCPU(_) => label("Raster is not supported"),
Self::RasterGPU(_) => label("Raster is not supported"),
}
}
}
impl TableRowLayout for VectorData {
impl TableRowLayout for Vector {
fn type_name() -> &'static str {
"VectorData"
"Vector"
}
fn identifier(&self) -> String {
format!("Vector Data (points={}, segments={})", self.point_domain.ids().len(), self.segment_domain.ids().len())
format!("Vector ({} points, {} segments)", self.point_domain.ids().len(), self.segment_domain.ids().len())
}
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
let colinear = self.colinear_manipulators.iter().map(|[a, b]| format!("[{a} / {b}]")).collect::<Vec<_>>().join(", ");
let colinear = if colinear.is_empty() { "None" } else { &colinear };
let style = vec![
TextLabel::new(format!(
"{}\n\nColinear Handle IDs: {}\n\nUpstream Graphic Group Table: {}",
"{}\n\nColinear Handle IDs: {}\n\nUpstream Group Table: {}",
self.style,
colinear,
if self.upstream_graphic_group.is_some() { "Yes" } else { "No" }
if self.upstream_group.is_some() { "Yes" } else { "No" }
))
.multiline(true)
.widget_holder(),
];
let domain_entries = [VectorDataDomain::Points, VectorDataDomain::Segments, VectorDataDomain::Regions]
let domain_entries = [VectorDomain::Points, VectorDomain::Segments, VectorDomain::Regions]
.into_iter()
.map(|domain| {
RadioEntryData::new(format!("{domain:?}"))
.label(format!("{domain:?}"))
.on_update(move |_| SpreadsheetMessage::ViewVectorDataDomain { domain }.into())
.on_update(move |_| SpreadsheetMessage::ViewVectorDomain { domain }.into())
})
.collect();
let domain = vec![RadioInput::new(domain_entries).selected_index(Some(data.vector_data_domain as u32)).widget_holder()];
let domain = vec![RadioInput::new(domain_entries).selected_index(Some(data.vector_domain as u32)).widget_holder()];
let mut table_rows = Vec::new();
match data.vector_data_domain {
VectorDataDomain::Points => {
match data.vector_domain {
VectorDomain::Points => {
table_rows.push(column_headings(&["", "position"]));
table_rows.extend(
self.point_domain
@ -215,7 +215,7 @@ impl TableRowLayout for VectorData {
.map(|(id, position)| vec![TextLabel::new(format!("{}", id.inner())).widget_holder(), TextLabel::new(format!("{}", position)).widget_holder()]),
);
}
VectorDataDomain::Segments => {
VectorDomain::Segments => {
table_rows.push(column_headings(&["", "start_index", "end_index", "handles"]));
table_rows.extend(self.segment_domain.iter().map(|(id, start, end, handles)| {
vec![
@ -226,7 +226,7 @@ impl TableRowLayout for VectorData {
]
}));
}
VectorDataDomain::Regions => {
VectorDomain::Regions => {
table_rows.push(column_headings(&["", "segment_range", "fill"]));
table_rows.extend(self.region_domain.iter().map(|(id, segment_range, fill)| {
vec![
@ -263,7 +263,7 @@ impl TableRowLayout for Artboard {
self.label.clone()
}
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
self.graphic_group.compute_layout(data)
self.group.compute_layout(data)
}
}
@ -288,7 +288,7 @@ impl<T: TableRowLayout> TableRowLayout for Table<T> {
}
let mut rows = self
.iter_ref()
.iter()
.enumerate()
.map(|(index, row)| {
let (scale, angle, translation) = row.transform.to_scale_angle_translation();

View file

@ -14,8 +14,9 @@ use graphene_std::raster::BlendMode;
use graphene_std::raster_types::{CPU, GPU, Raster};
use graphene_std::table::Table;
use graphene_std::text::{Font, TypesettingConfig};
use graphene_std::vector::misc::ManipulatorPointId;
use graphene_std::vector::style::Gradient;
use graphene_std::vector::{ManipulatorPointId, PointId, SegmentId, VectorModificationType};
use graphene_std::vector::{PointId, SegmentId, VectorModificationType};
use std::collections::VecDeque;
/// Returns the ID of the first Spline node in the horizontal flow which is not followed by a `Path` node, or `None` if none exists.
@ -34,7 +35,7 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde
if first_layer == second_layer {
return;
}
// Calculate the downstream transforms in order to bring the other vector data into the same layer space
// Calculate the downstream transforms in order to bring the other vector geometry into the same layer space
let first_layer_transform = document.metadata().downstream_transform_to_document(first_layer);
let second_layer_transform = document.metadata().downstream_transform_to_document(second_layer);
@ -162,24 +163,24 @@ pub fn merge_layers(document: &DocumentMessageHandler, first_layer: LayerNodeIde
/// Merge the `first_endpoint` with `second_endpoint`.
pub fn merge_points(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, first_endpoint: PointId, second_endpont: PointId, responses: &mut VecDeque<Message>) {
let transform = document.metadata().transform_to_document(layer);
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { return };
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return };
let segment = vector_data.segment_bezier_iter().find(|(_, _, start, end)| *end == second_endpont || *start == second_endpont);
let segment = vector.segment_bezier_iter().find(|(_, _, start, end)| *end == second_endpont || *start == second_endpont);
let Some((segment, _, mut segment_start_point, mut segment_end_point)) = segment else {
log::error!("Could not get the segment for second_endpoint.");
return;
};
let mut handles = [None; 2];
if let Some(handle_position) = ManipulatorPointId::PrimaryHandle(segment).get_position(&vector_data) {
let anchor_position = ManipulatorPointId::Anchor(segment_start_point).get_position(&vector_data).unwrap();
if let Some(handle_position) = ManipulatorPointId::PrimaryHandle(segment).get_position(&vector) {
let anchor_position = ManipulatorPointId::Anchor(segment_start_point).get_position(&vector).unwrap();
let handle_position = transform.transform_point2(handle_position);
let anchor_position = transform.transform_point2(anchor_position);
let anchor_to_handle = handle_position - anchor_position;
handles[0] = Some(anchor_to_handle);
}
if let Some(handle_position) = ManipulatorPointId::EndHandle(segment).get_position(&vector_data) {
let anchor_position = ManipulatorPointId::Anchor(segment_end_point).get_position(&vector_data).unwrap();
if let Some(handle_position) = ManipulatorPointId::EndHandle(segment).get_position(&vector) {
let anchor_position = ManipulatorPointId::Anchor(segment_end_point).get_position(&vector).unwrap();
let handle_position = transform.transform_point2(handle_position);
let anchor_position = transform.transform_point2(anchor_position);
let anchor_to_handle = handle_position - anchor_position;

View file

@ -8,7 +8,8 @@ use crate::messages::tool::tool_messages::path_tool::PathOptionsUpdate;
use crate::messages::tool::tool_messages::select_tool::SelectOptionsUpdate;
use crate::messages::tool::tool_messages::tool_prelude::*;
use glam::{DAffine2, DVec2};
use graphene_std::{transform::ReferencePoint, vector::ManipulatorPointId};
use graphene_std::transform::ReferencePoint;
use graphene_std::vector::misc::ManipulatorPointId;
use std::fmt;
pub fn pin_pivot_widget(active: bool, enabled: bool, source: PivotToolSource) -> WidgetHolder {

View file

@ -13,8 +13,8 @@ use crate::messages::tool::common_functionality::utility_functions::{is_intersec
use crate::messages::tool::tool_messages::path_tool::{PathOverlayMode, PointSelectState};
use bezier_rs::{Bezier, BezierHandles, Subpath, TValue};
use glam::{DAffine2, DVec2};
use graphene_std::vector::{HandleExt, HandleId, SegmentId};
use graphene_std::vector::{ManipulatorPointId, PointId, VectorData, VectorModificationType};
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
use graphene_std::vector::{HandleExt, PointId, SegmentId, Vector, VectorModificationType};
use std::f64::consts::TAU;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -164,13 +164,13 @@ pub struct ShapeState {
pub struct SelectedPointsInfo {
pub points: Vec<ManipulatorPointInfo>,
pub offset: DVec2,
pub vector_data: VectorData,
pub vector: Vector,
}
#[derive(Debug)]
pub struct SelectedSegmentsInfo {
pub segments: Vec<SegmentId>,
pub vector_data: VectorData,
pub vector: Vector,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@ -323,10 +323,10 @@ impl ClosestSegment {
(handle1 - handle2).try_normalize()
} else {
let [first_point, last_point] = self.points();
if let Some(vector_data) = document.network_interface.compute_modified_vector(self.layer()) {
if let Some(vector) = document.network_interface.compute_modified_vector(self.layer()) {
if let (Some(pos1), Some(pos2)) = (
ManipulatorPointId::Anchor(first_point).get_position(&vector_data),
ManipulatorPointId::Anchor(last_point).get_position(&vector_data),
ManipulatorPointId::Anchor(first_point).get_position(&vector),
ManipulatorPointId::Anchor(last_point).get_position(&vector),
) {
(pos1 - pos2).try_normalize()
} else {
@ -373,13 +373,13 @@ impl ClosestSegment {
// If adjacent segments have colinear handles, their direction is changed but their handle lengths is preserved
// TODO: Find something which is more appropriate
let vector_data = document.network_interface.compute_modified_vector(self.layer())?;
let vector = document.network_interface.compute_modified_vector(self.layer())?;
if break_colinear_molding {
// Disable G1 continuity
let other_handles = [
restore_previous_handle_position(handle1, c1, start, &vector_data, layer, responses),
restore_previous_handle_position(handle2, c2, end, &vector_data, layer, responses),
restore_previous_handle_position(handle1, c1, start, &vector, layer, responses),
restore_previous_handle_position(handle2, c2, end, &vector, layer, responses),
];
// Store other HandleId in tool data to regain colinearity later
@ -390,15 +390,15 @@ impl ClosestSegment {
}
} else {
// Move the colinear handles so that colinearity is maintained
adjust_handle_colinearity(handle1, start, nc1, &vector_data, layer, responses);
adjust_handle_colinearity(handle2, end, nc2, &vector_data, layer, responses);
adjust_handle_colinearity(handle1, start, nc1, &vector, layer, responses);
adjust_handle_colinearity(handle2, end, nc2, &vector, layer, responses);
if let Some(adjacent_handles) = temporary_adjacent_handles_while_molding {
if let Some(other_handle1) = adjacent_handles[0] {
restore_g1_continuity(handle1, other_handle1, nc1, start, &vector_data, layer, responses);
restore_g1_continuity(handle1, other_handle1, nc1, start, &vector, layer, responses);
}
if let Some(other_handle2) = adjacent_handles[1] {
restore_g1_continuity(handle2, other_handle2, nc2, end, &vector_data, layer, responses);
restore_g1_continuity(handle2, other_handle2, nc2, end, &vector, layer, responses);
}
}
None
@ -441,10 +441,10 @@ impl ShapeState {
let (layer1, start_point) = all_selected_points[0];
let (layer2, end_point) = all_selected_points[1];
let Some(vector_data1) = document.network_interface.compute_modified_vector(layer1) else { return };
let Some(vector_data2) = document.network_interface.compute_modified_vector(layer2) else { return };
let Some(vector1) = document.network_interface.compute_modified_vector(layer1) else { return };
let Some(vector2) = document.network_interface.compute_modified_vector(layer2) else { return };
if vector_data1.all_connected(start_point).count() != 1 || vector_data2.all_connected(end_point).count() != 1 {
if vector1.all_connected(start_point).count() != 1 || vector2.all_connected(end_point).count() != 1 {
return;
}
@ -477,15 +477,9 @@ impl ShapeState {
// If no points are selected, try to find a single continuous subpath in each layer to connect the endpoints of
for &layer in self.selected_shape_state.keys() {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
let endpoints: Vec<PointId> = vector_data
.point_domain
.ids()
.iter()
.copied()
.filter(|&point_id| vector_data.all_connected(point_id).count() == 1)
.collect();
let endpoints: Vec<PointId> = vector.point_domain.ids().iter().copied().filter(|&point_id| vector.all_connected(point_id).count() == 1).collect();
if endpoints.len() == 2 {
let start_point = endpoints[0];
@ -515,29 +509,27 @@ impl ShapeState {
let mut offset = mouse_delta;
let mut best_snapped = SnappedPoint::infinite_snap(document.metadata().document_to_viewport.inverse().transform_point2(input.mouse.position));
for (layer, state) in &self.selected_shape_state {
let Some(vector_data) = document.network_interface.compute_modified_vector(*layer) else {
continue;
};
let Some(vector) = document.network_interface.compute_modified_vector(*layer) else { continue };
let to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);
for &selected in &state.selected_points {
let source = match selected {
ManipulatorPointId::Anchor(_) if vector_data.colinear(selected) => SnapSource::Path(PathSnapSource::AnchorPointWithColinearHandles),
ManipulatorPointId::Anchor(_) if vector.colinear(selected) => SnapSource::Path(PathSnapSource::AnchorPointWithColinearHandles),
ManipulatorPointId::Anchor(_) => SnapSource::Path(PathSnapSource::AnchorPointWithFreeHandles),
// TODO: This doesn't actually work for handles, instead handles enter the arm above for free handles
ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => SnapSource::Path(PathSnapSource::HandlePoint),
};
let Some(position) = selected.get_position(&vector_data) else { continue };
let Some(position) = selected.get_position(&vector) else { continue };
let mut point = SnapCandidatePoint::new_source(to_document.transform_point2(position) + mouse_delta, source);
if let Some(id) = selected.as_anchor() {
for neighbor in vector_data.connected_points(id) {
for neighbor in vector.connected_points(id) {
if state.is_point_selected(ManipulatorPointId::Anchor(neighbor)) {
continue;
}
let Some(position) = vector_data.point_domain.position_from_id(neighbor) else { continue };
let Some(position) = vector.point_domain.position_from_id(neighbor) else { continue };
point.neighbors.push(to_document.transform_point2(position));
}
}
@ -569,8 +561,8 @@ impl ShapeState {
}
if let Some((layer, manipulator_point_id)) = self.find_nearest_visible_point_indices(network_interface, mouse_position, select_threshold, path_overlay_mode, frontier_handles_info) {
let vector_data = network_interface.compute_modified_vector(layer)?;
let point_position = manipulator_point_id.get_position(&vector_data)?;
let vector = network_interface.compute_modified_vector(layer)?;
let point_position = manipulator_point_id.get_position(&vector)?;
let selected_shape_state = self.selected_shape_state.get(&layer)?;
let already_selected = selected_shape_state.is_point_selected(manipulator_point_id);
@ -600,7 +592,7 @@ impl ShapeState {
.flat_map(|(layer, state)| state.selected_points.iter().map(|&point_id| ManipulatorPointInfo { layer: *layer, point_id }))
.collect();
return Some(Some(SelectedPointsInfo { points, offset, vector_data }));
return Some(Some(SelectedPointsInfo { points, offset, vector }));
}
None
}
@ -623,13 +615,13 @@ impl ShapeState {
if !point_editing_mode && matches!(manipulator_point_id, ManipulatorPointId::Anchor(_)) {
return None;
}
let vector_data = network_interface.compute_modified_vector(layer)?;
let point_position = manipulator_point_id.get_position(&vector_data)?;
let vector = network_interface.compute_modified_vector(layer)?;
let point_position = manipulator_point_id.get_position(&vector)?;
// Check if point is visible under current overlay mode or not
let selected_segments = selected_segments(network_interface, self);
let selected_points = self.selected_points().cloned().collect::<HashSet<_>>();
if !is_visible_point(manipulator_point_id, &vector_data, path_overlay_mode, frontier_handles_info, selected_segments, &selected_points) {
if !is_visible_point(manipulator_point_id, &vector, path_overlay_mode, frontier_handles_info, selected_segments, &selected_points) {
return None;
}
@ -650,7 +642,7 @@ impl ShapeState {
.flat_map(|(layer, state)| state.selected_points.iter().map(|&point_id| ManipulatorPointInfo { layer: *layer, point_id }))
.collect();
let selection_info = SelectedPointsInfo { points, offset, vector_data };
let selection_info = SelectedPointsInfo { points, offset, vector };
// Return the current selection state and info
return Some((already_selected, Some(selection_info)));
@ -670,16 +662,14 @@ impl ShapeState {
/// Selects all anchors connected to the selected subpath, and deselects all handles, for the given layer.
pub fn select_connected(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, mouse: DVec2, points: bool, segments: bool) {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
return;
};
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return };
let to_viewport = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
let layer_mouse = to_viewport.inverse().transform_point2(mouse);
let state = self.selected_shape_state.entry(layer).or_default();
let mut selected_stack = Vec::new();
// Find all subpaths that have been clicked
for stroke in vector_data.stroke_bezier_paths() {
for stroke in vector.stroke_bezier_paths() {
if stroke.contains_point(layer_mouse) {
if let Some(first) = stroke.manipulator_groups().first() {
selected_stack.push(first.id);
@ -691,12 +681,12 @@ impl ShapeState {
if selected_stack.is_empty() {
// Fall back on just selecting all points/segments in the layer
if points {
for &point in vector_data.point_domain.ids() {
for &point in vector.point_domain.ids() {
state.select_point(ManipulatorPointId::Anchor(point));
}
}
if segments {
for &segment in vector_data.segment_domain.ids() {
for &segment in vector.segment_domain.ids() {
state.select_segment(segment);
}
}
@ -708,7 +698,7 @@ impl ShapeState {
while let Some(point) = selected_stack.pop() {
if !connected_points.contains(&point) {
connected_points.insert(point);
selected_stack.extend(vector_data.connected_points(point));
selected_stack.extend(vector.connected_points(point));
}
}
@ -717,7 +707,7 @@ impl ShapeState {
}
if segments {
for (id, _, start, end) in vector_data.segment_bezier_iter() {
for (id, _, start, end) in vector.segment_bezier_iter() {
if connected_points.contains(&start) || connected_points.contains(&end) {
state.select_segment(id);
}
@ -740,13 +730,11 @@ impl ShapeState {
/// Internal helper function that selects all anchors, and deselects all handles, for a layer given its [`LayerNodeIdentifier`] and [`SelectedLayerState`].
fn select_all_anchors_in_layer_with_state(document: &DocumentMessageHandler, layer: LayerNodeIdentifier, state: &mut SelectedLayerState) {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
return;
};
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return };
state.clear_points();
for &point in vector_data.point_domain.ids() {
for &point in vector.point_domain.ids() {
state.select_point(ManipulatorPointId::Anchor(point))
}
}
@ -856,7 +844,7 @@ impl ShapeState {
});
}
pub fn move_anchor(&self, point: PointId, vector_data: &VectorData, delta: DVec2, layer: LayerNodeIdentifier, selected: Option<&SelectedLayerState>, responses: &mut VecDeque<Message>) {
pub fn move_anchor(&self, point: PointId, vector: &Vector, delta: DVec2, layer: LayerNodeIdentifier, selected: Option<&SelectedLayerState>, responses: &mut VecDeque<Message>) {
// Move anchor
responses.add(GraphOperationMessage::Vector {
layer,
@ -864,8 +852,8 @@ impl ShapeState {
});
// Move the other handle for a quadratic bezier
for segment in vector_data.end_connected(point) {
let Some((start, _end, bezier)) = vector_data.segment_points_from_id(segment) else { continue };
for segment in vector.end_connected(point) {
let Some((start, _end, bezier)) = vector.segment_points_from_id(segment) else { continue };
if let BezierHandles::Quadratic { handle } = bezier.handles {
if selected.is_some_and(|selected| selected.is_point_selected(ManipulatorPointId::Anchor(start))) {
@ -894,18 +882,18 @@ impl ShapeState {
return None;
}
let vector_data = network_interface.compute_modified_vector(layer)?;
let vector = network_interface.compute_modified_vector(layer)?;
let transform = network_interface.document_metadata().transform_to_document_if_feeds(layer, network_interface).inverse();
let position = transform.transform_point2(new_position);
let current_position = point.get_position(&vector_data)?;
let current_position = point.get_position(&vector)?;
let delta = position - current_position;
match *point {
ManipulatorPointId::Anchor(point) => self.move_anchor(point, &vector_data, delta, layer, None, responses),
ManipulatorPointId::Anchor(point) => self.move_anchor(point, &vector, delta, layer, None, responses),
ManipulatorPointId::PrimaryHandle(segment) => {
self.move_primary(segment, delta, layer, responses);
if let Some(handle) = point.as_handle() {
if let Some(handles) = vector_data.colinear_manipulators.iter().find(|handles| handles[0] == handle || handles[1] == handle) {
if let Some(handles) = vector.colinear_manipulators.iter().find(|handles| handles[0] == handle || handles[1] == handle) {
let modification_type = VectorModificationType::SetG1Continuous { handles: *handles, enabled: false };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
@ -914,7 +902,7 @@ impl ShapeState {
ManipulatorPointId::EndHandle(segment) => {
self.move_end(segment, delta, layer, responses);
if let Some(handle) = point.as_handle() {
if let Some(handles) = vector_data.colinear_manipulators.iter().find(|handles| handles[0] == handle || handles[1] == handle) {
if let Some(handles) = vector.colinear_manipulators.iter().find(|handles| handles[0] == handle || handles[1] == handle) {
let modification_type = VectorModificationType::SetG1Continuous { handles: *handles, enabled: false };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
@ -948,28 +936,26 @@ impl ShapeState {
if first_is_colinear { ManipulatorAngle::Colinear } else { ManipulatorAngle::Free }
}
pub fn convert_manipulator_handles_to_colinear(&self, vector_data: &VectorData, point_id: PointId, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier) {
let Some(anchor_position) = ManipulatorPointId::Anchor(point_id).get_position(vector_data) else {
pub fn convert_manipulator_handles_to_colinear(&self, vector: &Vector, point_id: PointId, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier) {
let Some(anchor_position) = ManipulatorPointId::Anchor(point_id).get_position(vector) else {
return;
};
let handles = vector_data.all_connected(point_id).take(2).collect::<Vec<_>>();
let non_zero_handles = handles.iter().filter(|handle| handle.length(vector_data) > 1e-6).count();
let handles = vector.all_connected(point_id).take(2).collect::<Vec<_>>();
let non_zero_handles = handles.iter().filter(|handle| handle.length(vector) > 1e-6).count();
let handle_segments = handles.iter().map(|handles| handles.segment).collect::<Vec<_>>();
// Check if the anchor is connected to linear segments and has no handles
let linear_segments = vector_data.connected_linear_segments(point_id) != 0;
let linear_segments = vector.connected_linear_segments(point_id) != 0;
// Grab the next and previous manipulator groups by simply looking at the next / previous index
let points = handles.iter().map(|handle| vector_data.other_point(handle.segment, point_id));
let anchor_positions = points
.map(|point| point.and_then(|point| ManipulatorPointId::Anchor(point).get_position(vector_data)))
.collect::<Vec<_>>();
let points = handles.iter().map(|handle| vector.other_point(handle.segment, point_id));
let anchor_positions = points.map(|point| point.and_then(|point| ManipulatorPointId::Anchor(point).get_position(vector))).collect::<Vec<_>>();
let mut segment_angle = 0.;
let mut segment_count = 0.;
for segment in &handle_segments {
let Some(angle) = calculate_segment_angle(point_id, *segment, vector_data, false) else {
let Some(angle) = calculate_segment_angle(point_id, *segment, vector, false) else {
continue;
};
segment_angle += angle;
@ -1002,15 +988,15 @@ impl ShapeState {
if non_zero_handles != 0 && !linear_segments {
let [a, b] = handles.as_slice() else { return };
let (non_zero_handle, zero_handle) = if a.length(vector_data) > 1e-6 { (a, b) } else { (b, a) };
let (non_zero_handle, zero_handle) = if a.length(vector) > 1e-6 { (a, b) } else { (b, a) };
let Some(direction) = non_zero_handle
.to_manipulator_point()
.get_position(vector_data)
.get_position(vector)
.and_then(|position| (position - anchor_position).try_normalize())
else {
return;
};
let new_position = -direction * non_zero_handle.length(vector_data);
let new_position = -direction * non_zero_handle.length(vector);
let modification_type = zero_handle.set_relative_position(new_position);
responses.add(GraphOperationMessage::Vector { layer, modification_type });
} else {
@ -1031,7 +1017,7 @@ impl ShapeState {
responses.add(GraphOperationMessage::Vector { layer, modification_type });
// Create the opposite handle if it doesn't exist (if it is not a cubic segment)
if handle.opposite().to_manipulator_point().get_position(vector_data).is_none() {
if handle.opposite().to_manipulator_point().get_position(vector).is_none() {
let modification_type = handle.opposite().set_relative_position(DVec2::ZERO);
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
@ -1050,36 +1036,34 @@ impl ShapeState {
let mut skip_set = HashSet::new();
for (&layer, layer_state) in self.selected_shape_state.iter() {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
continue;
};
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
for &point in layer_state.selected_points.iter() {
// Skip a point which has more than 2 segments connected (vector meshes)
if let ManipulatorPointId::Anchor(anchor) = point {
if vector_data.all_connected(anchor).count() > 2 {
if vector.all_connected(anchor).count() > 2 {
continue;
}
}
// Here we take handles as the current handle and the most opposite non-colinear-handle
let is_handle_colinear = |handle: HandleId| -> bool { vector_data.colinear_manipulators.iter().any(|&handles| handles[0] == handle || handles[1] == handle) };
let is_handle_colinear = |handle: HandleId| -> bool { vector.colinear_manipulators.iter().any(|&handles| handles[0] == handle || handles[1] == handle) };
let other_handles = if matches!(point, ManipulatorPointId::Anchor(_)) {
point.get_handle_pair(&vector_data)
point.get_handle_pair(&vector)
} else {
point.get_all_connected_handles(&vector_data).and_then(|handles| {
point.get_all_connected_handles(&vector).and_then(|handles| {
let mut non_colinear_handles = handles.iter().filter(|&handle| !is_handle_colinear(*handle)).clone().collect::<Vec<_>>();
// Sort these by angle from the current handle
non_colinear_handles.sort_by(|&handle_a, &handle_b| {
let anchor = point.get_anchor_position(&vector_data).expect("No anchor position for handle");
let orig_handle_pos = point.get_position(&vector_data).expect("No handle position");
let anchor = point.get_anchor_position(&vector).expect("No anchor position for handle");
let orig_handle_pos = point.get_position(&vector).expect("No handle position");
let a_pos = handle_a.to_manipulator_point().get_position(&vector_data).expect("No handle position");
let b_pos = handle_b.to_manipulator_point().get_position(&vector_data).expect("No handle position");
let a_pos = handle_a.to_manipulator_point().get_position(&vector).expect("No handle position");
let b_pos = handle_b.to_manipulator_point().get_position(&vector).expect("No handle position");
let v_orig = (orig_handle_pos - anchor).normalize_or_zero();
@ -1112,13 +1096,13 @@ impl ShapeState {
skip_set.insert(handles);
let [selected0, selected1] = handles.map(|handle| layer_state.selected_points.contains(&handle.to_manipulator_point()));
let handle_positions = handles.map(|handle| handle.to_manipulator_point().get_position(&vector_data));
let handle_positions = handles.map(|handle| handle.to_manipulator_point().get_position(&vector));
let Some(anchor_id) = point.get_anchor(&vector_data) else { continue };
let Some(anchor) = vector_data.point_domain.position_from_id(anchor_id) else { continue };
let Some(anchor_id) = point.get_anchor(&vector) else { continue };
let Some(anchor) = vector.point_domain.position_from_id(anchor_id) else { continue };
let anchor_points = handles.map(|handle| vector_data.other_point(handle.segment, anchor_id));
let anchor_positions = anchor_points.map(|point| point.and_then(|point| vector_data.point_domain.position_from_id(point)));
let anchor_points = handles.map(|handle| vector.other_point(handle.segment, anchor_id));
let anchor_positions = anchor_points.map(|point| point.and_then(|point| vector.point_domain.position_from_id(point)));
// If one handle is selected (but both exist), only move the other handle
if let (true, [Some(pos0), Some(pos1)]) = ((selected0 ^ selected1), handle_positions) {
@ -1160,7 +1144,7 @@ impl ShapeState {
responses.add(GraphOperationMessage::Vector { layer, modification_type });
// Create the opposite handle if it doesn't exist (if it is not a cubic segment)
if handles[index].opposite().to_manipulator_point().get_position(&vector_data).is_none() {
if handles[index].opposite().to_manipulator_point().get_position(&vector).is_none() {
let modification_type = handles[index].opposite().set_relative_position(DVec2::ZERO);
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
@ -1187,7 +1171,7 @@ impl ShapeState {
responses: &mut VecDeque<Message>,
) {
for (&layer, state) in &self.selected_shape_state {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
let opposing_handles = handle_lengths.as_ref().and_then(|handle_lengths| handle_lengths.get(&layer));
@ -1203,7 +1187,7 @@ impl ShapeState {
// Make a new collection of anchor points which needs to be moved
let mut affected_points = state.selected_points.clone();
for (segment_id, _, start, end) in vector_data.segment_bezier_iter() {
for (segment_id, _, start, end) in vector.segment_bezier_iter() {
if state.is_segment_selected(segment_id) {
affected_points.insert(ManipulatorPointId::Anchor(start));
affected_points.insert(ManipulatorPointId::Anchor(end));
@ -1217,28 +1201,28 @@ impl ShapeState {
let handle = match point {
ManipulatorPointId::Anchor(point) => {
self.move_anchor(point, &vector_data, delta, layer, Some(state), responses);
self.move_anchor(point, &vector, delta, layer, Some(state), responses);
continue;
}
ManipulatorPointId::PrimaryHandle(segment) => HandleId::primary(segment),
ManipulatorPointId::EndHandle(segment) => HandleId::end(segment),
};
let Some(anchor_id) = point.get_anchor(&vector_data) else { continue };
let Some(anchor_id) = point.get_anchor(&vector) else { continue };
if state.is_point_selected(ManipulatorPointId::Anchor(anchor_id)) {
continue;
}
let Some(anchor_position) = vector_data.point_domain.position_from_id(anchor_id) else { continue };
let Some(anchor_position) = vector.point_domain.position_from_id(anchor_id) else { continue };
let Some(handle_position) = point.get_position(&vector_data) else { continue };
let Some(handle_position) = point.get_position(&vector) else { continue };
let handle_position = handle_position + delta;
let modification_type = handle.set_relative_position(handle_position - anchor_position);
responses.add(GraphOperationMessage::Vector { layer, modification_type });
let Some(other) = vector_data.other_colinear_handle(handle) else { continue };
let Some(other) = vector.other_colinear_handle(handle) else { continue };
if skip_opposite_handle {
continue;
@ -1262,7 +1246,7 @@ impl ShapeState {
} else {
// TODO: Is this equivalent to `transform_to_document_space`? If changed, the before and after should be tested.
let transform = document.metadata().document_to_viewport.inverse() * transform_to_viewport_space;
let Some(other_position) = other.to_manipulator_point().get_position(&vector_data) else {
let Some(other_position) = other.to_manipulator_point().get_position(&vector) else {
continue;
};
let direction = transform.transform_vector2(handle_position - anchor_position).try_normalize();
@ -1284,9 +1268,9 @@ impl ShapeState {
self.selected_shape_state
.iter()
.filter_map(|(&layer, state)| {
let vector_data = document.network_interface.compute_modified_vector(layer)?;
let vector = document.network_interface.compute_modified_vector(layer)?;
let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
let opposing_handle_lengths = vector_data
let opposing_handle_lengths = vector
.colinear_manipulators
.iter()
.filter_map(|&handles| {
@ -1294,7 +1278,7 @@ impl ShapeState {
// i) Exactly one handle is selected.
// ii) The anchor is not selected.
let anchor = handles[0].to_manipulator_point().get_anchor(&vector_data)?;
let anchor = handles[0].to_manipulator_point().get_anchor(&vector)?;
let anchor_selected = state.is_point_selected(ManipulatorPointId::Anchor(anchor));
if anchor_selected {
return None;
@ -1308,8 +1292,8 @@ impl ShapeState {
_ => return None,
};
let opposing_handle_position = other.to_manipulator_point().get_position(&vector_data)?;
let anchor_position = vector_data.point_domain.position_from_id(anchor)?;
let opposing_handle_position = other.to_manipulator_point().get_position(&vector)?;
let anchor_position = vector.point_domain.position_from_id(anchor)?;
let opposing_handle_length = transform.transform_vector2(opposing_handle_position - anchor_position).length();
Some((other, opposing_handle_length))
@ -1320,15 +1304,15 @@ impl ShapeState {
.collect::<HashMap<_, _>>()
}
pub fn dissolve_segment(&self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector_data: &VectorData, segment: SegmentId, points: [PointId; 2]) {
pub fn dissolve_segment(&self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector: &Vector, segment: SegmentId, points: [PointId; 2]) {
// Checking which point is terminal point
let is_point1_terminal = vector_data.connected_count(points[0]) == 1;
let is_point2_terminal = vector_data.connected_count(points[1]) == 1;
let is_point1_terminal = vector.connected_count(points[0]) == 1;
let is_point2_terminal = vector.connected_count(points[1]) == 1;
// Delete the segment and terminal points
let modification_type = VectorModificationType::RemoveSegment { id: segment };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
for &handles in vector_data.colinear_manipulators.iter().filter(|handles| handles.iter().any(|handle| handle.segment == segment)) {
for &handles in vector.colinear_manipulators.iter().filter(|handles| handles.iter().any(|handle| handle.segment == segment)) {
let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
@ -1343,27 +1327,27 @@ impl ShapeState {
}
}
fn dissolve_anchor(anchor: PointId, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector_data: &VectorData) -> Option<[(HandleId, PointId); 2]> {
fn dissolve_anchor(anchor: PointId, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector: &Vector) -> Option<[(HandleId, PointId); 2]> {
// Delete point
let modification_type = VectorModificationType::RemovePoint { id: anchor };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
// Delete connected segments
for HandleId { segment, .. } in vector_data.all_connected(anchor) {
for HandleId { segment, .. } in vector.all_connected(anchor) {
let modification_type = VectorModificationType::RemoveSegment { id: segment };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
for &handles in vector_data.colinear_manipulators.iter().filter(|handles| handles.iter().any(|handle| handle.segment == segment)) {
for &handles in vector.colinear_manipulators.iter().filter(|handles| handles.iter().any(|handle| handle.segment == segment)) {
let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
}
// Add in new segment if possible
let mut handles = ManipulatorPointId::Anchor(anchor).get_handle_pair(vector_data)?;
let mut handles = ManipulatorPointId::Anchor(anchor).get_handle_pair(vector)?;
handles.reverse();
let opposites = handles.map(|handle| handle.opposite());
let [Some(start), Some(end)] = opposites.map(|opposite| opposite.to_manipulator_point().get_anchor(vector_data)) else {
let [Some(start), Some(end)] = opposites.map(|opposite| opposite.to_manipulator_point().get_anchor(vector)) else {
return None;
};
Some([(handles[0], start), (handles[1], end)])
@ -1374,17 +1358,15 @@ impl ShapeState {
for (&layer, state) in &mut self.selected_shape_state {
let mut missing_anchors = HashMap::new();
let mut deleted_anchors = HashSet::new();
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
continue;
};
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
let selected_segments = &state.selected_segments;
for point in std::mem::take(&mut state.selected_points) {
match point {
ManipulatorPointId::Anchor(anchor) => {
if let Some(handles) = Self::dissolve_anchor(anchor, responses, layer, &vector_data) {
if !vector_data.all_connected(anchor).any(|a| selected_segments.contains(&a.segment)) && vector_data.all_connected(anchor).count() <= 2 {
if let Some(handles) = Self::dissolve_anchor(anchor, responses, layer, &vector) {
if !vector.all_connected(anchor).any(|a| selected_segments.contains(&a.segment)) && vector.all_connected(anchor).count() <= 2 {
missing_anchors.insert(anchor, handles);
}
}
@ -1398,7 +1380,7 @@ impl ShapeState {
responses.add(GraphOperationMessage::Vector { layer, modification_type });
// Disable the g1 continuous
for &handles in &vector_data.colinear_manipulators {
for &handles in &vector.colinear_manipulators {
if handles.contains(&handle) {
let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
@ -1436,11 +1418,8 @@ impl ShapeState {
// Grab the handles from the opposite side of the segment(s) being deleted and make it relative to the anchor
let [handle_start, handle_end] = [start, end].map(|(handle, _)| {
let handle = handle.opposite();
let handle_position = handle.to_manipulator_point().get_position(&vector_data);
let relative_position = handle
.to_manipulator_point()
.get_anchor(&vector_data)
.and_then(|anchor| vector_data.point_domain.position_from_id(anchor));
let handle_position = handle.to_manipulator_point().get_position(&vector);
let relative_position = handle.to_manipulator_point().get_anchor(&vector).and_then(|anchor| vector.point_domain.position_from_id(anchor));
handle_position.and_then(|handle| relative_position.map(|relative| handle - relative)).unwrap_or_default()
});
@ -1454,12 +1433,12 @@ impl ShapeState {
responses.add(GraphOperationMessage::Vector { layer, modification_type });
for &handles in vector_data.colinear_manipulators.iter() {
for &handles in vector.colinear_manipulators.iter() {
if !handles.iter().any(|&handle| handle == start.0.opposite() || handle == end.0.opposite()) {
continue;
}
let Some(anchor) = handles[0].to_manipulator_point().get_anchor(&vector_data) else { continue };
let Some(anchor) = handles[0].to_manipulator_point().get_anchor(&vector) else { continue };
let Some(other) = handles.iter().find(|&&handle| handle != start.0.opposite() && handle != end.0.opposite()) else {
continue;
};
@ -1482,13 +1461,11 @@ impl ShapeState {
pub fn delete_selected_segments(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
for (&layer, state) in &self.selected_shape_state {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
continue;
};
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
for (segment, _, start, end) in vector_data.segment_bezier_iter() {
for (segment, _, start, end) in vector.segment_bezier_iter() {
if state.selected_segments.contains(&segment) {
self.dissolve_segment(responses, layer, &vector_data, segment, [start, end]);
self.dissolve_segment(responses, layer, &vector, segment, [start, end]);
}
}
}
@ -1496,13 +1473,11 @@ impl ShapeState {
pub fn delete_hanging_selected_anchors(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
for (&layer, state) in &self.selected_shape_state {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
continue;
};
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
for point in &state.selected_points {
if let ManipulatorPointId::Anchor(anchor) = point {
if vector_data.all_connected(*anchor).all(|segment| state.is_segment_selected(segment.segment)) {
if vector.all_connected(*anchor).all(|segment| state.is_segment_selected(segment.segment)) {
let modification_type = VectorModificationType::RemovePoint { id: *anchor };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
@ -1513,16 +1488,16 @@ impl ShapeState {
pub fn break_path_at_selected_point(&self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
for (&layer, state) in &self.selected_shape_state {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
for &delete in &state.selected_points {
let Some(point) = delete.get_anchor(&vector_data) else { continue };
let Some(pos) = vector_data.point_domain.position_from_id(point) else { continue };
let Some(point) = delete.get_anchor(&vector) else { continue };
let Some(pos) = vector.point_domain.position_from_id(point) else { continue };
let mut used_initial_point = false;
for handle in vector_data.all_connected(point) {
for handle in vector.all_connected(point) {
// Disable the g1 continuous
for &handles in &vector_data.colinear_manipulators {
for &handles in &vector.colinear_manipulators {
if handles.contains(&handle) {
let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
@ -1544,8 +1519,8 @@ impl ShapeState {
// Update segment
let HandleId { ty, segment } = handle;
let modification_type = match ty {
graphene_std::vector::HandleType::Primary => VectorModificationType::SetStartPoint { segment, id },
graphene_std::vector::HandleType::End => VectorModificationType::SetEndPoint { segment, id },
graphene_std::vector::misc::HandleType::Primary => VectorModificationType::SetStartPoint { segment, id },
graphene_std::vector::misc::HandleType::End => VectorModificationType::SetEndPoint { segment, id },
};
responses.add(GraphOperationMessage::Vector { layer, modification_type });
@ -1557,19 +1532,17 @@ impl ShapeState {
/// Delete point(s) and adjacent segments.
pub fn delete_point_and_break_path(&mut self, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
for (&layer, state) in &mut self.selected_shape_state {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
continue;
};
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
for delete in std::mem::take(&mut state.selected_points) {
let Some(point) = delete.get_anchor(&vector_data) else { continue };
let Some(point) = delete.get_anchor(&vector) else { continue };
// Delete point
let modification_type = VectorModificationType::RemovePoint { id: point };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
// Delete connected segments
for HandleId { segment, .. } in vector_data.all_connected(point) {
for HandleId { segment, .. } in vector.all_connected(point) {
let modification_type = VectorModificationType::RemoveSegment { id: segment };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
@ -1580,18 +1553,18 @@ impl ShapeState {
/// Disable colinear handles colinear.
pub fn disable_colinear_handles_state_on_selected(&self, network_interface: &NodeNetworkInterface, responses: &mut VecDeque<Message>) {
for (&layer, state) in &self.selected_shape_state {
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { continue };
let Some(vector) = network_interface.compute_modified_vector(layer) else { continue };
for &point in &state.selected_points {
if let ManipulatorPointId::Anchor(point) = point {
for connected in vector_data.all_connected(point) {
if let Some(&handles) = vector_data.colinear_manipulators.iter().find(|target| target.contains(&connected)) {
for connected in vector.all_connected(point) {
if let Some(&handles) = vector.colinear_manipulators.iter().find(|target| target.contains(&connected)) {
let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
}
} else if let Some(handle) = point.as_handle() {
if let Some(handles) = vector_data.colinear_manipulators.iter().find(|handles| handles[0] == handle || handles[1] == handle) {
if let Some(handles) = vector.colinear_manipulators.iter().find(|handles| handles[0] == handle || handles[1] == handle) {
let modification_type = VectorModificationType::SetG1Continuous { handles: *handles, enabled: false };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
@ -1642,11 +1615,11 @@ impl ShapeState {
// Choose the first point under the threshold
if distance_squared < select_threshold_squared {
// Check if point is visible in current PathOverlayMode
let vector_data = network_interface.compute_modified_vector(layer)?;
let vector = network_interface.compute_modified_vector(layer)?;
let selected_segments = selected_segments(network_interface, self);
let selected_points = self.selected_points().cloned().collect::<HashSet<_>>();
if !is_visible_point(manipulator_point_id, &vector_data, path_overlay_mode, frontier_handles_info, selected_segments, &selected_points) {
if !is_visible_point(manipulator_point_id, &vector, path_overlay_mode, frontier_handles_info, selected_segments, &selected_points) {
return None;
}
@ -1666,11 +1639,11 @@ impl ShapeState {
let mut closest_distance_squared: f64 = f64::MAX;
let mut manipulator_point = None;
let vector_data = network_interface.compute_modified_vector(layer)?;
let vector = network_interface.compute_modified_vector(layer)?;
let viewspace = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
// Handles
for (segment_id, bezier, _, _) in vector_data.segment_bezier_iter() {
for (segment_id, bezier, _, _) in vector.segment_bezier_iter() {
let bezier = bezier.apply_transformation(|point| viewspace.transform_point2(point));
let valid = |handle: DVec2, control: DVec2| handle.distance_squared(control) > crate::consts::HIDE_HANDLE_DISTANCE.powi(2);
@ -1689,7 +1662,7 @@ impl ShapeState {
}
// Anchors
for (&id, &point) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
for (&id, &point) in vector.point_domain.ids().iter().zip(vector.point_domain.positions()) {
let point = viewspace.transform_point2(point);
if point.distance_squared(pos) <= closest_distance_squared {
@ -1711,9 +1684,9 @@ impl ShapeState {
let mut closest = None;
let mut closest_distance_squared: f64 = tolerance * tolerance;
let vector_data = network_interface.compute_modified_vector(layer)?;
let vector = network_interface.compute_modified_vector(layer)?;
for (segment, mut bezier, start, end) in vector_data.segment_bezier_iter() {
for (segment, mut bezier, start, end) in vector.segment_bezier_iter() {
let t = bezier.project(layer_pos);
let layerspace = bezier.evaluate(TValue::Parametric(t));
@ -1730,8 +1703,8 @@ impl ShapeState {
}
}
let primary_handle = vector_data.colinear_manipulators.iter().find(|handles| handles.contains(&HandleId::primary(segment)));
let end_handle = vector_data.colinear_manipulators.iter().find(|handles| handles.contains(&HandleId::end(segment)));
let primary_handle = vector.colinear_manipulators.iter().find(|handles| handles.contains(&HandleId::primary(segment)));
let end_handle = vector.colinear_manipulators.iter().find(|handles| handles.contains(&HandleId::end(segment)));
let primary_handle = primary_handle.and_then(|&handles| handles.into_iter().find(|handle| handle.segment != segment));
let end_handle = end_handle.and_then(|&handles| handles.into_iter().find(|handle| handle.segment != segment));
@ -1761,13 +1734,13 @@ impl ShapeState {
}
pub fn get_dragging_state(&self, network_interface: &NodeNetworkInterface) -> PointSelectState {
for &layer in self.selected_shape_state.keys() {
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { continue };
let Some(vector) = network_interface.compute_modified_vector(layer) else { continue };
for point in self.selected_points() {
if point.as_anchor().is_some() {
return PointSelectState::Anchor;
}
if point.get_handle_pair(&vector_data).is_some() {
if point.get_handle_pair(&vector).is_some() {
return PointSelectState::HandleWithPair;
}
}
@ -1778,13 +1751,13 @@ impl ShapeState {
/// Returns true if at least one handle with pair is selected
pub fn handle_with_pair_selected(&mut self, network_interface: &NodeNetworkInterface) -> bool {
for &layer in self.selected_shape_state.keys() {
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { continue };
let Some(vector) = network_interface.compute_modified_vector(layer) else { continue };
for point in self.selected_points() {
if point.as_anchor().is_some() {
return false;
}
if point.get_handle_pair(&vector_data).is_some() {
if point.get_handle_pair(&vector).is_some() {
return true;
}
}
@ -1798,22 +1771,22 @@ impl ShapeState {
let mut handles_to_update = Vec::new();
for &layer in self.selected_shape_state.keys() {
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { continue };
let Some(vector) = network_interface.compute_modified_vector(layer) else { continue };
for point in self.selected_points() {
if point.as_anchor().is_some() {
continue;
}
if let Some(other_handles) = point.get_all_connected_handles(&vector_data) {
if let Some(other_handles) = point.get_all_connected_handles(&vector) {
// Find the next closest handle in the clockwise sense
let mut candidates = other_handles.clone();
candidates.sort_by(|&handle_a, &handle_b| {
let anchor = point.get_anchor_position(&vector_data).expect("No anchor position for handle");
let orig_handle_pos = point.get_position(&vector_data).expect("No handle position");
let anchor = point.get_anchor_position(&vector).expect("No anchor position for handle");
let orig_handle_pos = point.get_position(&vector).expect("No handle position");
let a_pos = handle_a.to_manipulator_point().get_position(&vector_data).expect("No handle position");
let b_pos = handle_b.to_manipulator_point().get_position(&vector_data).expect("No handle position");
let a_pos = handle_a.to_manipulator_point().get_position(&vector).expect("No handle position");
let b_pos = handle_b.to_manipulator_point().get_position(&vector).expect("No handle position");
let v_orig = (orig_handle_pos - anchor).normalize_or_zero();
@ -1861,13 +1834,11 @@ impl ShapeState {
let mut points_to_select: Vec<(LayerNodeIdentifier, Option<PointId>, Option<ManipulatorPointId>)> = Vec::new();
for &layer in self.selected_shape_state.keys() {
let Some(vector_data) = network_interface.compute_modified_vector(layer) else {
continue;
};
let Some(vector) = network_interface.compute_modified_vector(layer) else { continue };
for point in self.selected_points().filter(|point| point.as_handle().is_some()) {
let anchor = point.get_anchor(&vector_data);
match point.get_handle_pair(&vector_data) {
let anchor = point.get_anchor(&vector);
match point.get_handle_pair(&vector) {
Some(handles) => {
points_to_select.push((layer, anchor, Some(handles[1].to_manipulator_point())));
}
@ -1907,14 +1878,14 @@ impl ShapeState {
/// This can can be activated by double clicking on an anchor with the Path tool.
pub fn flip_smooth_sharp(&self, network_interface: &NodeNetworkInterface, target: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool {
let mut process_layer = |layer| {
let vector_data = network_interface.compute_modified_vector(layer)?;
let vector = network_interface.compute_modified_vector(layer)?;
let transform_to_screenspace = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
let mut result = None;
let mut closest_distance_squared = tolerance * tolerance;
// Find the closest anchor point on the current layer
for (&id, &anchor) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
for (&id, &anchor) in vector.point_domain.ids().iter().zip(vector.point_domain.positions()) {
let screenspace = transform_to_screenspace.transform_point2(anchor);
let distance_squared = screenspace.distance_squared(target);
@ -1925,23 +1896,23 @@ impl ShapeState {
}
let (id, anchor) = result?;
let handles = vector_data.all_connected(id);
let handles = vector.all_connected(id);
let positions = handles
.filter_map(|handle| handle.to_manipulator_point().get_position(&vector_data))
.filter_map(|handle| handle.to_manipulator_point().get_position(&vector))
.filter(|&handle| anchor.abs_diff_eq(handle, 1e-5))
.count();
// Check if the anchor is connected to linear segments.
let one_or_more_segment_linear = vector_data.connected_linear_segments(id) != 0;
let one_or_more_segment_linear = vector.connected_linear_segments(id) != 0;
// Check by comparing the handle positions to the anchor if this manipulator group is a point
for point in self.selected_points() {
let Some(point_id) = point.as_anchor() else { continue };
if positions != 0 || one_or_more_segment_linear {
self.convert_manipulator_handles_to_colinear(&vector_data, point_id, responses, layer);
self.convert_manipulator_handles_to_colinear(&vector, point_id, responses, layer);
} else {
for handle in vector_data.all_connected(point_id) {
let Some(bezier) = vector_data.segment_from_id(handle.segment) else { continue };
for handle in vector.all_connected(point_id) {
let Some(bezier) = vector.segment_from_id(handle.segment) else { continue };
match bezier.handles {
BezierHandles::Linear => {}
@ -1952,7 +1923,7 @@ impl ShapeState {
responses.add(GraphOperationMessage::Vector { layer, modification_type });
// Set the manipulator to have non-colinear handles
for &handles in &vector_data.colinear_manipulators {
for &handles in &vector.colinear_manipulators {
if handles.contains(&HandleId::primary(segment)) {
let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
@ -1965,7 +1936,7 @@ impl ShapeState {
responses.add(GraphOperationMessage::Vector { layer, modification_type });
// Set the manipulator to have non-colinear handles
for &handles in &vector_data.colinear_manipulators {
for &handles in &vector.colinear_manipulators {
if handles.contains(&handle) {
let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
@ -2019,14 +1990,14 @@ impl ShapeState {
for (layer, points) in points_inside {
let Some(state) = self.selected_shape_state.get_mut(&layer) else { continue };
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { continue };
let Some(vector) = network_interface.compute_modified_vector(layer) else { continue };
for point in points {
match (point, selection_change) {
(_, SelectionChange::Shrink) => state.deselect_point(point),
(ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_), _) => {
let handle = point.as_handle().expect("Handle cannot be converted");
if handle.length(&vector_data) > 0. {
if handle.length(&vector) > 0. {
state.select_point(point);
}
}
@ -2043,9 +2014,9 @@ impl ShapeState {
}
// Also select/deselect the endpoints of respective segments
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { continue };
let Some(vector) = network_interface.compute_modified_vector(layer) else { continue };
if !select_points && select_segments {
vector_data
vector
.segment_bezier_iter()
.filter(|(segment, _, _, _)| segments.contains(segment))
.for_each(|(_, _, start, end)| match selection_change {
@ -2081,17 +2052,17 @@ impl ShapeState {
let mut segments_inside: HashMap<LayerNodeIdentifier, HashSet<SegmentId>> = HashMap::new();
for &layer in self.selected_shape_state.keys() {
let vector_data = network_interface.compute_modified_vector(layer);
let Some(vector_data) = vector_data else { continue };
let vector = network_interface.compute_modified_vector(layer);
let Some(vector) = vector else { continue };
let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
assert_eq!(vector_data.segment_domain.ids().len(), vector_data.start_point().count());
assert_eq!(vector_data.segment_domain.ids().len(), vector_data.end_point().count());
for start in vector_data.start_point() {
assert!(vector_data.point_domain.ids().contains(&start));
assert_eq!(vector.segment_domain.ids().len(), vector.start_point().count());
assert_eq!(vector.segment_domain.ids().len(), vector.end_point().count());
for start in vector.start_point() {
assert!(vector.point_domain.ids().contains(&start));
}
for end in vector_data.end_point() {
assert!(vector_data.point_domain.ids().contains(&end));
for end in vector.end_point() {
assert!(vector.point_domain.ids().contains(&end));
}
let polygon_subpath = if let SelectionShape::Lasso(polygon) = selection_shape {
@ -2105,7 +2076,7 @@ impl ShapeState {
};
// Selection segments
for (id, bezier, _, _) in vector_data.segment_bezier_iter() {
for (id, bezier, _, _) in vector.segment_bezier_iter() {
if select_segments {
// Select segments if they lie inside the bounding box or lasso polygon
let segment_bbox = calculate_bezier_bbox(bezier);
@ -2154,7 +2125,7 @@ impl ShapeState {
};
if select && select_points {
let is_visible_handle = is_visible_point(id, &vector_data, path_overlay_mode, frontier_handles_info, selected_segments.clone(), &selected_points);
let is_visible_handle = is_visible_point(id, &vector, path_overlay_mode, frontier_handles_info, selected_segments.clone(), &selected_points);
if is_visible_handle {
points_inside.entry(layer).or_default().insert(id);
@ -2164,7 +2135,7 @@ impl ShapeState {
}
// Checking for selection of anchor points
for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
for (&id, &position) in vector.point_domain.ids().iter().zip(vector.point_domain.positions()) {
let transformed_position = transform.transform_point2(position);
let select = match selection_shape {

View file

@ -205,12 +205,12 @@ pub fn transform_cage_overlays(document: &DocumentMessageHandler, tool_data: &mu
pub fn anchor_overlays(document: &DocumentMessageHandler, overlay_context: &mut OverlayContext) {
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
let transform = document.metadata().transform_to_viewport(layer);
overlay_context.outline_vector(&vector_data, transform);
overlay_context.outline_vector(&vector, transform);
for (_, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
for (_, &position) in vector.point_domain.ids().iter().zip(vector.point_domain.positions()) {
overlay_context.manipulator_anchor(transform.transform_point2(position), false, None);
}
}

View file

@ -16,7 +16,8 @@ use graph_craft::document::value::TaggedValue;
use graphene_std::renderer::Quad;
use graphene_std::table::Table;
use graphene_std::text::{FontCache, load_font};
use graphene_std::vector::{HandleExt, HandleId, ManipulatorPointId, PointId, SegmentId, VectorData, VectorModification, VectorModificationType};
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
use graphene_std::vector::{HandleExt, PointId, SegmentId, Vector, VectorModification, VectorModificationType};
use kurbo::{CubicBez, Line, ParamCurveExtrema, PathSeg, Point, QuadBez};
/// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable.
@ -47,14 +48,12 @@ where
let mut best_distance_squared = max_distance * max_distance;
for layer in layers {
let viewspace = document.metadata().transform_to_viewport(layer);
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
continue;
};
for id in vector_data.extendable_points(preferences.vector_meshes) {
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
for id in vector.extendable_points(preferences.vector_meshes) {
if exclude(id) {
continue;
}
let Some(point) = vector_data.point_domain.position_from_id(id) else { continue };
let Some(point) = vector.point_domain.position_from_id(id) else { continue };
let distance_squared = viewspace.transform_point2(point).distance_squared(goal);
@ -88,16 +87,16 @@ pub fn text_bounding_box(layer: LayerNodeIdentifier, document: &DocumentMessageH
Quad::from_box([DVec2::ZERO + vertical_offset, far + vertical_offset])
}
pub fn calculate_segment_angle(anchor: PointId, segment: SegmentId, vector_data: &VectorData, prefer_handle_direction: bool) -> Option<f64> {
let is_start = |point: PointId, segment: SegmentId| vector_data.segment_start_from_id(segment) == Some(point);
let anchor_position = vector_data.point_domain.position_from_id(anchor)?;
let end_handle = ManipulatorPointId::EndHandle(segment).get_position(vector_data);
let start_handle = ManipulatorPointId::PrimaryHandle(segment).get_position(vector_data);
pub fn calculate_segment_angle(anchor: PointId, segment: SegmentId, vector: &Vector, prefer_handle_direction: bool) -> Option<f64> {
let is_start = |point: PointId, segment: SegmentId| vector.segment_start_from_id(segment) == Some(point);
let anchor_position = vector.point_domain.position_from_id(anchor)?;
let end_handle = ManipulatorPointId::EndHandle(segment).get_position(vector);
let start_handle = ManipulatorPointId::PrimaryHandle(segment).get_position(vector);
let start_point = if is_start(anchor, segment) {
vector_data.segment_end_from_id(segment).and_then(|id| vector_data.point_domain.position_from_id(id))
vector.segment_end_from_id(segment).and_then(|id| vector.point_domain.position_from_id(id))
} else {
vector_data.segment_start_from_id(segment).and_then(|id| vector_data.point_domain.position_from_id(id))
vector.segment_start_from_id(segment).and_then(|id| vector.point_domain.position_from_id(id))
};
let required_handle = if is_start(anchor, segment) {
@ -115,9 +114,9 @@ pub fn calculate_segment_angle(anchor: PointId, segment: SegmentId, vector_data:
required_handle.map(|handle| -(handle - anchor_position).angle_to(DVec2::X))
}
pub fn adjust_handle_colinearity(handle: HandleId, anchor_position: DVec2, target_control_point: DVec2, vector_data: &VectorData, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
let Some(other_handle) = vector_data.other_colinear_handle(handle) else { return };
let Some(handle_position) = other_handle.to_manipulator_point().get_position(vector_data) else {
pub fn adjust_handle_colinearity(handle: HandleId, anchor_position: DVec2, target_control_point: DVec2, vector: &Vector, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
let Some(other_handle) = vector.other_colinear_handle(handle) else { return };
let Some(handle_position) = other_handle.to_manipulator_point().get_position(vector) else {
return;
};
let Some(direction) = (anchor_position - target_control_point).try_normalize() else { return };
@ -132,12 +131,12 @@ pub fn restore_previous_handle_position(
handle: HandleId,
original_c: DVec2,
anchor_position: DVec2,
vector_data: &VectorData,
vector: &Vector,
layer: LayerNodeIdentifier,
responses: &mut VecDeque<Message>,
) -> Option<HandleId> {
let other_handle = vector_data.other_colinear_handle(handle)?;
let handle_position = other_handle.to_manipulator_point().get_position(vector_data)?;
let other_handle = vector.other_colinear_handle(handle)?;
let handle_position = other_handle.to_manipulator_point().get_position(vector)?;
let direction = (anchor_position - original_c).try_normalize()?;
let old_relative_position = (handle_position - anchor_position).length() * direction;
@ -151,16 +150,8 @@ pub fn restore_previous_handle_position(
Some(other_handle)
}
pub fn restore_g1_continuity(
handle: HandleId,
other_handle: HandleId,
control_point: DVec2,
anchor_position: DVec2,
vector_data: &VectorData,
layer: LayerNodeIdentifier,
responses: &mut VecDeque<Message>,
) {
let Some(handle_position) = other_handle.to_manipulator_point().get_position(vector_data) else {
pub fn restore_g1_continuity(handle: HandleId, other_handle: HandleId, control_point: DVec2, anchor_position: DVec2, vector: &Vector, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>) {
let Some(handle_position) = other_handle.to_manipulator_point().get_position(vector) else {
return;
};
let Some(direction) = (anchor_position - control_point).try_normalize() else { return };
@ -177,7 +168,7 @@ pub fn restore_g1_continuity(
/// Check whether a point is visible in the current overlay mode.
pub fn is_visible_point(
manipulator_point_id: ManipulatorPointId,
vector_data: &VectorData,
vector: &Vector,
path_overlay_mode: PathOverlayMode,
frontier_handles_info: &Option<HashMap<SegmentId, Vec<PointId>>>,
selected_segments: Vec<SegmentId>,
@ -194,14 +185,14 @@ pub fn is_visible_point(
}
// Either the segment is a part of selected segments or the opposite handle is a part of existing selection
let Some(handle_pair) = manipulator_point_id.get_handle_pair(vector_data) else { return false };
let Some(handle_pair) = manipulator_point_id.get_handle_pair(vector) else { return false };
let other_handle = handle_pair[1].to_manipulator_point();
// Return whether the list of selected points contain the other handle
selected_points.contains(&other_handle)
}
(PathOverlayMode::FrontierHandles, false) => {
let Some(anchor) = manipulator_point_id.get_anchor(vector_data) else {
let Some(anchor) = manipulator_point_id.get_anchor(vector) else {
warn!("No anchor for selected handle");
return false;
};
@ -600,13 +591,13 @@ pub fn make_path_editable_is_allowed(network_interface: &NodeNetworkInterface, m
return None;
}
// Must be a layer of type Table<VectorData>
// Must be a layer of type Table<Vector>
let compatible_type = NodeGraphLayer::new(first_layer, network_interface)
.horizontal_layer_flow()
.nth(1)
.map(|node_id| {
let (output_type, _) = network_interface.output_type(&node_id, 0, &[]);
output_type.nested_type() == concrete!(Table<VectorData>).nested_type()
output_type.nested_type() == concrete!(Table<Vector>).nested_type()
})
.unwrap_or_default();
if !compatible_type {

View file

@ -11,6 +11,7 @@ use crate::messages::tool::common_functionality::snapping::SnapData;
use crate::messages::tool::common_functionality::snapping::SnapManager;
use crate::messages::tool::common_functionality::transformation_cage::*;
use graph_craft::document::NodeId;
use graphene_std::Artboard;
use graphene_std::renderer::Quad;
use graphene_std::table::Table;
@ -337,8 +338,8 @@ impl Fsm for ArtboardToolFsmState {
responses.add(GraphOperationMessage::NewArtboard {
id,
artboard: graphene_std::Artboard {
graphic_group: Table::new(),
artboard: Artboard {
group: Table::new(),
label: String::from("Artboard"),
location: start.min(end).round().as_ivec2(),
dimensions: (start.round() - end.round()).abs().as_ivec2(),

View file

@ -360,9 +360,9 @@ mod test_freehand {
use crate::messages::tool::tool_messages::freehand_tool::FreehandOptionsUpdate;
use crate::test_utils::test_prelude::*;
use glam::{DAffine2, DVec2};
use graphene_std::vector::VectorData;
use graphene_std::vector::Vector;
async fn get_vector_data(editor: &mut EditorTestUtils) -> Vec<(VectorData, DAffine2)> {
async fn get_vector_and_transform_list(editor: &mut EditorTestUtils) -> Vec<(Vector, DAffine2)> {
let document = editor.active_document();
let layers = document.metadata().all_layers();
@ -372,23 +372,22 @@ mod test_freehand {
// Only get layers with path nodes
let _ = graph_layer.upstream_visible_node_id_from_name_in_layer("Path")?;
let vector_data = document.network_interface.compute_modified_vector(layer)?;
let vector = document.network_interface.compute_modified_vector(layer)?;
let transform = document.metadata().transform_to_viewport(layer);
Some((vector_data, transform))
Some((vector, transform))
})
.collect()
}
fn verify_path_points(vector_data_list: &[(VectorData, DAffine2)], expected_captured_points: &[DVec2], tolerance: f64) -> Result<(), String> {
assert_eq!(vector_data_list.len(), 1, "there should be one vector data");
fn verify_path_points(vector_and_transform_list: &[(Vector, DAffine2)], expected_captured_points: &[DVec2], tolerance: f64) -> Result<(), String> {
assert_eq!(vector_and_transform_list.len(), 1, "There should be one row of Vector geometry");
let path_data = vector_data_list.iter().find(|(data, _)| data.point_domain.ids().len() > 0).ok_or("Could not find path data")?;
let (vector, transform) = vector_and_transform_list.iter().find(|(data, _)| data.point_domain.ids().len() > 0).ok_or("Could not find path data")?;
let (vector_data, transform) = path_data;
let point_count = vector_data.point_domain.ids().len();
let segment_count = vector_data.segment_domain.ids().len();
let point_count = vector.point_domain.ids().len();
let segment_count = vector.segment_domain.ids().len();
let actual_positions: Vec<DVec2> = vector_data.point_domain.positions().iter().map(|&position| transform.transform_point2(position)).collect();
let actual_positions: Vec<DVec2> = vector.point_domain.positions().iter().map(|&position| transform.transform_point2(position)).collect();
if segment_count != point_count - 1 {
return Err(format!("Expected segments to be one less than points, got {} segments for {} points", segment_count, point_count));
@ -435,8 +434,8 @@ mod test_freehand {
let expected_captured_points = &mouse_points[1..];
editor.drag_path(&mouse_points, ModifierKeys::empty()).await;
let vector_data_list = get_vector_data(&mut editor).await;
verify_path_points(&vector_data_list, expected_captured_points, 1.).expect("Path points verification failed");
let vector_and_transform_list = get_vector_and_transform_list(&mut editor).await;
verify_path_points(&vector_and_transform_list, expected_captured_points, 1.).expect("Path points verification failed");
}
#[tokio::test]
@ -468,12 +467,12 @@ mod test_freehand {
)
.await;
let initial_vector_data = get_vector_data(&mut editor).await;
assert!(!initial_vector_data.is_empty(), "No vector data found after initial drawing");
let initial_vector_and_transform_list = get_vector_and_transform_list(&mut editor).await;
assert!(!initial_vector_and_transform_list.is_empty(), "No Vector geometry found after initial drawing");
let (initial_data, transform) = &initial_vector_data[0];
let initial_point_count = initial_data.point_domain.ids().len();
let initial_segment_count = initial_data.segment_domain.ids().len();
let (initial_vector, initial_transform) = &initial_vector_and_transform_list[0];
let initial_point_count = initial_vector.point_domain.ids().len();
let initial_segment_count = initial_vector.segment_domain.ids().len();
assert!(initial_point_count >= 2, "Expected at least 2 points in initial path, found {}", initial_point_count);
assert_eq!(
@ -484,15 +483,15 @@ mod test_freehand {
initial_segment_count
);
let extendable_points = initial_data.extendable_points(false).collect::<Vec<_>>();
let extendable_points = initial_vector.extendable_points(false).collect::<Vec<_>>();
assert!(!extendable_points.is_empty(), "No extendable points found in the path");
let endpoint_id = extendable_points[0];
let endpoint_pos_option = initial_data.point_domain.position_from_id(endpoint_id);
let endpoint_pos_option = initial_vector.point_domain.position_from_id(endpoint_id);
assert!(endpoint_pos_option.is_some(), "Could not find position for endpoint");
let endpoint_pos = endpoint_pos_option.unwrap();
let endpoint_viewport_pos = transform.transform_point2(endpoint_pos);
let endpoint_viewport_pos = initial_transform.transform_point2(endpoint_pos);
assert!(endpoint_viewport_pos.is_finite(), "Endpoint position is not finite");
@ -527,12 +526,12 @@ mod test_freehand {
)
.await;
let extended_vector_data = get_vector_data(&mut editor).await;
assert!(!extended_vector_data.is_empty(), "No vector data found after extension");
let extended_vector_and_transform = get_vector_and_transform_list(&mut editor).await;
assert!(!extended_vector_and_transform.is_empty(), "No Vector geometry found after extension");
let (extended_data, _) = &extended_vector_data[0];
let extended_point_count = extended_data.point_domain.ids().len();
let extended_segment_count = extended_data.segment_domain.ids().len();
let (extended_vector, _) = &extended_vector_and_transform[0];
let extended_point_count = extended_vector.point_domain.ids().len();
let extended_segment_count = extended_vector.segment_domain.ids().len();
assert!(
extended_point_count > initial_point_count,
@ -585,12 +584,12 @@ mod test_freehand {
)
.await;
let initial_vector_data = get_vector_data(&mut editor).await;
assert!(!initial_vector_data.is_empty(), "No vector data found after initial drawing");
let initial_vector_and_transform = get_vector_and_transform_list(&mut editor).await;
assert!(!initial_vector_and_transform.is_empty(), "No vector geometry found after initial drawing");
let (initial_data, _) = &initial_vector_data[0];
let initial_point_count = initial_data.point_domain.ids().len();
let initial_segment_count = initial_data.segment_domain.ids().len();
let (initial_vector, _) = &initial_vector_and_transform[0];
let initial_point_count = initial_vector.point_domain.ids().len();
let initial_segment_count = initial_vector.segment_domain.ids().len();
let existing_layer_id = {
let document = editor.active_document();
@ -636,8 +635,8 @@ mod test_freehand {
)
.await;
let final_vector_data = get_vector_data(&mut editor).await;
assert!(!final_vector_data.is_empty(), "No vector data found after second drawing");
let final_vector_and_transform = get_vector_and_transform_list(&mut editor).await;
assert!(!final_vector_and_transform.is_empty(), "No vector geometry found after second drawing");
// Verify we still have only one layer
let layer_count = {
@ -646,9 +645,9 @@ mod test_freehand {
};
assert_eq!(layer_count, 1, "Expected only one layer after drawing with Shift key");
let (final_data, _) = &final_vector_data[0];
let final_point_count = final_data.point_domain.ids().len();
let final_segment_count = final_data.segment_domain.ids().len();
let (final_vector, _) = &final_vector_and_transform[0];
let final_point_count = final_vector.point_domain.ids().len();
let final_segment_count = final_vector.segment_domain.ids().len();
assert!(
final_point_count > initial_point_count,

View file

@ -27,8 +27,8 @@ use graphene_std::renderer::Quad;
use graphene_std::transform::ReferencePoint;
use graphene_std::uuid::NodeId;
use graphene_std::vector::click_target::ClickTargetType;
use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, VectorData};
use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType};
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
use graphene_std::vector::{HandleExt, NoHashBuilder, PointId, SegmentId, Vector, VectorModificationType};
use std::vec;
#[derive(Default, ExtractField)]
@ -285,7 +285,7 @@ impl LayoutHolder for PathTool {
.selected_index(Some(self.options.path_overlay_mode as u32))
.widget_holder();
// Works only if a single layer is selected and its type is vectordata
// Works only if a single layer is selected and its type is Vector
let path_node_button = TextButton::new("Make Path Editable")
.icon(Some("NodeShape".into()))
.tooltip("Make Path Editable")
@ -619,10 +619,10 @@ impl PathToolData {
self.can_toggle_colinearity = match &selection_status {
SelectionStatus::None => false,
SelectionStatus::One(single_selected_point) => {
let vector_data = document.network_interface.compute_modified_vector(single_selected_point.layer).unwrap();
if single_selected_point.id.get_handle_pair(&vector_data).is_some() {
let anchor = single_selected_point.id.get_anchor(&vector_data).expect("Cannot find connected anchor");
vector_data.all_connected(anchor).count() <= 2
let vector = document.network_interface.compute_modified_vector(single_selected_point.layer).unwrap();
if single_selected_point.id.get_handle_pair(&vector).is_some() {
let anchor = single_selected_point.id.get_anchor(&vector).expect("Cannot find connected anchor");
vector.all_connected(anchor).count() <= 2
} else {
false
}
@ -761,13 +761,13 @@ impl PathToolData {
if handle_drag_from_anchor {
if let Some((layer, point)) = shape_editor.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD) {
// Check that selected point is an anchor
if let (Some(point_id), Some(vector_data)) = (point.as_anchor(), document.network_interface.compute_modified_vector(layer)) {
let handles = vector_data.all_connected(point_id).collect::<Vec<_>>();
if let (Some(point_id), Some(vector)) = (point.as_anchor(), document.network_interface.compute_modified_vector(layer)) {
let handles = vector.all_connected(point_id).collect::<Vec<_>>();
self.alt_clicked_on_anchor = true;
for handle in &handles {
let modification_type = handle.set_relative_position(DVec2::ZERO);
responses.add(GraphOperationMessage::Vector { layer, modification_type });
for &handles in &vector_data.colinear_manipulators {
for &handles in &vector.colinear_manipulators {
if handles.contains(handle) {
let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: false };
responses.add(GraphOperationMessage::Vector { layer, modification_type });
@ -783,16 +783,16 @@ impl PathToolData {
}
}
if let Some((Some(point), Some(vector_data))) = shape_editor
if let Some((Some(point), Some(vector))) = shape_editor
.find_nearest_point_indices(&document.network_interface, input.mouse.position, SELECTION_THRESHOLD)
.map(|(layer, point)| (point.as_anchor(), document.network_interface.compute_modified_vector(layer)))
{
let handles = vector_data
let handles = vector
.all_connected(point)
.filter(|handle| handle.length(&vector_data) < 1e-6)
.filter(|handle| handle.length(&vector) < 1e-6)
.map(|handle| handle.to_manipulator_point())
.collect::<Vec<_>>();
let endpoint = vector_data.extendable_points(false).any(|anchor| point == anchor);
let endpoint = vector.extendable_points(false).any(|anchor| point == anchor);
if drag_zero_handle && (handles.len() == 1 && !endpoint) {
shape_editor.deselect_all_points();
@ -883,27 +883,25 @@ impl PathToolData {
let mut manipulators = HashMap::with_hasher(NoHashBuilder);
let mut unselected = Vec::new();
for (&layer, state) in &shape_editor.selected_shape_state {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
continue;
};
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
let mut layer_manipulators = HashSet::with_hasher(NoHashBuilder);
for point in state.selected_points() {
let Some(anchor) = point.get_anchor(&vector_data) else { continue };
let Some(anchor) = point.get_anchor(&vector) else { continue };
layer_manipulators.insert(anchor);
let Some([handle1, handle2]) = point.get_handle_pair(&vector_data) else { continue };
let Some([handle1, handle2]) = point.get_handle_pair(&vector) else { continue };
let Some(handle) = point.as_handle() else { continue };
// Check which handle is selected and which is opposite
let opposite = if handle == handle1 { handle2 } else { handle1 };
self.opposite_handle_position = if self.opposite_handle_position.is_none() {
opposite.to_manipulator_point().get_position(&vector_data)
opposite.to_manipulator_point().get_position(&vector)
} else {
self.opposite_handle_position
};
}
for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
for (&id, &position) in vector.point_domain.ids().iter().zip(vector.point_domain.positions()) {
if layer_manipulators.contains(&id) {
continue;
}
@ -956,17 +954,17 @@ impl PathToolData {
return false;
};
let Some(vector_data) = document.network_interface.compute_modified_vector(*layer) else {
let Some(vector) = document.network_interface.compute_modified_vector(*layer) else {
self.opposing_handle_lengths = Some(shape_editor.opposing_handle_lengths(document));
return false;
};
// Check if handle has a pair (to ignore handles of edges of open paths)
if let Some(handle_pair) = selected_handle_id.get_handle_pair(&vector_data) {
if let Some(handle_pair) = selected_handle_id.get_handle_pair(&vector) {
let opposite_handle_length = handle_pair.iter().filter(|&&h| h.to_manipulator_point() != selected_handle_id).find_map(|&h| {
let opp_handle_pos = h.to_manipulator_point().get_position(&vector_data)?;
let opp_anchor_id = h.to_manipulator_point().get_anchor(&vector_data)?;
let opp_anchor_pos = vector_data.point_domain.position_from_id(opp_anchor_id)?;
let opp_handle_pos = h.to_manipulator_point().get_position(&vector)?;
let opp_anchor_id = h.to_manipulator_point().get_anchor(&vector)?;
let opp_anchor_pos = vector.point_domain.position_from_id(opp_anchor_id)?;
Some((opp_handle_pos - opp_anchor_pos).length())
});
@ -997,11 +995,11 @@ impl PathToolData {
let handle_id = selected_handle.to_manipulator_point();
let layer_to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);
let vector_data = document.network_interface.compute_modified_vector(*layer)?;
let vector = document.network_interface.compute_modified_vector(*layer)?;
let handle_position_local = selected_handle.to_manipulator_point().get_position(&vector_data)?;
let anchor_id = selected_handle.to_manipulator_point().get_anchor(&vector_data)?;
let anchor_position_local = vector_data.point_domain.position_from_id(anchor_id)?;
let handle_position_local = selected_handle.to_manipulator_point().get_position(&vector)?;
let anchor_id = selected_handle.to_manipulator_point().get_anchor(&vector)?;
let anchor_position_local = vector.point_domain.position_from_id(anchor_id)?;
let handle_position_document = layer_to_document.transform_point2(handle_position_local);
let anchor_position_document = layer_to_document.transform_point2(anchor_position_local);
@ -1024,24 +1022,24 @@ impl PathToolData {
) -> f64 {
let current_angle = -handle_vector.angle_to(DVec2::X);
if let Some((vector_data, layer)) = shape_editor
if let Some((vector, layer)) = shape_editor
.selected_shape_state
.iter()
.next()
.and_then(|(layer, _)| document.network_interface.compute_modified_vector(*layer).map(|vector_data| (vector_data, layer)))
.and_then(|(layer, _)| document.network_interface.compute_modified_vector(*layer).map(|vector| (vector, layer)))
{
let adjacent_anchor = check_handle_over_adjacent_anchor(handle_id, &vector_data);
let adjacent_anchor = check_handle_over_adjacent_anchor(handle_id, &vector);
let mut required_angle = None;
// If the handle is dragged over one of its adjacent anchors while holding down the Ctrl key, compute the angle based on the tangent formed with the neighboring anchor points.
if adjacent_anchor.is_some() && lock_angle && !self.angle_locked {
let anchor = handle_id.get_anchor(&vector_data);
let (angle, anchor_position) = calculate_adjacent_anchor_tangent(handle_id, anchor, adjacent_anchor, &vector_data);
let anchor = handle_id.get_anchor(&vector);
let (angle, anchor_position) = calculate_adjacent_anchor_tangent(handle_id, anchor, adjacent_anchor, &vector);
let layer_to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);
self.adjacent_anchor_offset = handle_id
.get_anchor_position(&vector_data)
.get_anchor_position(&vector)
.and_then(|handle_anchor| anchor_position.map(|adjacent_anchor| layer_to_document.transform_point2(adjacent_anchor) - layer_to_document.transform_point2(handle_anchor)));
required_angle = angle;
@ -1049,7 +1047,7 @@ impl PathToolData {
// If the handle is dragged near its adjacent anchors while holding down the Ctrl key, compute the angle using the tangent direction of neighboring segments.
if relative_vector.length() < 25. && lock_angle && !self.angle_locked {
required_angle = calculate_lock_angle(self, shape_editor, responses, document, &vector_data, handle_id, tangent_to_neighboring_tangents);
required_angle = calculate_lock_angle(self, shape_editor, responses, document, &vector, handle_id, tangent_to_neighboring_tangents);
}
// Finalize and apply angle locking if a valid target angle was determined.
@ -1158,17 +1156,17 @@ impl PathToolData {
self.snapping_axis = None;
}
fn get_normalized_tangent(&mut self, point: PointId, segment: SegmentId, vector_data: &VectorData) -> Option<DVec2> {
let other_point = vector_data.other_point(segment, point)?;
let position = ManipulatorPointId::Anchor(point).get_position(vector_data)?;
fn get_normalized_tangent(&mut self, point: PointId, segment: SegmentId, vector: &Vector) -> Option<DVec2> {
let other_point = vector.other_point(segment, point)?;
let position = ManipulatorPointId::Anchor(point).get_position(vector)?;
let mut handles = vector_data.all_connected(other_point);
let mut handles = vector.all_connected(other_point);
let other_handle = handles.find(|handle| handle.segment == segment)?;
let target_position = if other_handle.length(vector_data) == 0. {
ManipulatorPointId::Anchor(other_point).get_position(vector_data)?
let target_position = if other_handle.length(vector) == 0. {
ManipulatorPointId::Anchor(other_point).get_position(vector)?
} else {
other_handle.to_manipulator_point().get_position(vector_data)?
other_handle.to_manipulator_point().get_position(vector)?
};
let tangent_vector = target_position - position;
@ -1207,19 +1205,17 @@ impl PathToolData {
let Some(layer) = document.network_interface.selected_nodes().selected_layers(document.metadata()).next() else {
return false;
};
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
return false;
};
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return false };
// Check that the handles of anchor point are also colinear
if !vector_data.colinear(*anchor) {
if !vector.colinear(*anchor) {
return false;
};
let Some(point_id) = anchor.as_anchor() else { return false };
let mut connected_segments = [None, None];
for (segment, bezier, start, end) in vector_data.segment_bezier_iter() {
for (segment, bezier, start, end) in vector.segment_bezier_iter() {
if start == point_id || end == point_id {
match (connected_segments[0], connected_segments[1]) {
(None, None) => connected_segments[0] = Some(SlidingSegmentData { segment_id: segment, bezier, start }),
@ -1253,7 +1249,7 @@ impl PathToolData {
let anchor = sliding_point_info.anchor;
let layer = sliding_point_info.layer;
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { return };
let Some(vector) = network_interface.compute_modified_vector(layer) else { return };
let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
let layer_pos = transform.inverse().transform_point2(target_position);
@ -1272,12 +1268,12 @@ impl PathToolData {
};
// Move the anchor to the new position
let Some(current_position) = ManipulatorPointId::Anchor(anchor).get_position(&vector_data) else {
let Some(current_position) = ManipulatorPointId::Anchor(anchor).get_position(&vector) else {
return;
};
let delta = new_position - current_position;
shape_editor.move_anchor(anchor, &vector_data, delta, layer, None, responses);
shape_editor.move_anchor(anchor, &vector, delta, layer, None, responses);
// Make a split at the t_value
let [first, second] = closer_segment.bezier.split(TValue::Parametric(t_value));
@ -1419,19 +1415,19 @@ impl PathToolData {
let Some(layer) = document.network_interface.selected_nodes().selected_layers(document.metadata()).next() else {
return;
};
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { return };
let Some(point_id) = shape_editor.selected_points().next().unwrap().get_anchor(&vector_data) else {
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return };
let Some(point_id) = shape_editor.selected_points().next().unwrap().get_anchor(&vector) else {
return;
};
if vector_data.connected_count(point_id) == 2 {
let connected_segments: Vec<HandleId> = vector_data.all_connected(point_id).collect();
if vector.connected_count(point_id) == 2 {
let connected_segments: Vec<HandleId> = vector.all_connected(point_id).collect();
let segment1 = connected_segments[0];
let Some(tangent1) = self.get_normalized_tangent(point_id, segment1.segment, &vector_data) else {
let Some(tangent1) = self.get_normalized_tangent(point_id, segment1.segment, &vector) else {
return;
};
let segment2 = connected_segments[1];
let Some(tangent2) = self.get_normalized_tangent(point_id, segment2.segment, &vector_data) else {
let Some(tangent2) = self.get_normalized_tangent(point_id, segment2.segment, &vector) else {
return;
};
@ -1565,11 +1561,11 @@ impl Fsm for PathToolFsmState {
let selected_layers = shape_editor.selected_layers().cloned().collect::<Vec<_>>();
for layer in selected_layers {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
let selected_state = shape_editor.selected_shape_state.entry(layer).or_default();
for (segment, _, start, end) in vector_data.segment_bezier_iter() {
for (segment, _, start, end) in vector.segment_bezier_iter() {
if selected_state.is_segment_selected(segment) {
selected_state.select_point(ManipulatorPointId::Anchor(start));
selected_state.select_point(ManipulatorPointId::Anchor(end));
@ -1610,11 +1606,11 @@ impl Fsm for PathToolFsmState {
let selected_layers = shape_editor.selected_layers().cloned().collect::<Vec<_>>();
for layer in selected_layers {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
let selected_state = shape_editor.selected_shape_state.entry(layer).or_default();
for (segment, _, start, end) in vector_data.segment_bezier_iter() {
for (segment, _, start, end) in vector.segment_bezier_iter() {
let first_selected = selected_state.is_point_selected(ManipulatorPointId::Anchor(start));
let second_selected = selected_state.is_point_selected(ManipulatorPointId::Anchor(end));
if first_selected && second_selected {
@ -1663,12 +1659,12 @@ impl Fsm for PathToolFsmState {
let mut segment_endpoints: HashMap<SegmentId, Vec<PointId>> = HashMap::new();
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
// The points which are part of only one segment will be rendered
let mut selected_segments_by_point: HashMap<PointId, Vec<SegmentId>> = HashMap::new();
for (segment_id, _bezier, start, end) in vector_data.segment_bezier_iter() {
for (segment_id, _bezier, start, end) in vector.segment_bezier_iter() {
if selected_segments.contains(&segment_id) {
selected_segments_by_point.entry(start).or_default().push(segment_id);
selected_segments_by_point.entry(end).or_default().push(segment_id);
@ -1721,8 +1717,8 @@ impl Fsm for PathToolFsmState {
);
let Some((layer, manipulator_point_id)) = nearest_visible_point_indices else { return };
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { return };
let Some(position) = manipulator_point_id.get_position(&vector_data) else {
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return };
let Some(position) = manipulator_point_id.get_position(&vector) else {
error!("No position for hovered point");
return;
};
@ -1847,10 +1843,10 @@ impl Fsm for PathToolFsmState {
};
for (layer, points) in points_inside {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
for point in points {
let Some(position) = point.get_position(&vector_data) else { continue };
let Some(position) = point.get_position(&vector) else { continue };
let transform = document.metadata().transform_to_viewport(layer);
let position = transform.transform_point2(position);
@ -1865,11 +1861,11 @@ impl Fsm for PathToolFsmState {
}
for (layer, segments) in segments_inside {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
for (segment, bezier, _, _) in vector_data.segment_bezier_iter() {
for (segment, bezier, _, _) in vector.segment_bezier_iter() {
if segments.contains(&segment) {
overlay_context.outline_overlay_bezier(bezier, transform);
}
@ -2392,8 +2388,8 @@ impl Fsm for PathToolFsmState {
if !drag_occurred && !tool_data.molding_segment && ((point_mode && !segment_mode) || (segment_mode && tool_data.segment_editing_modifier)) {
if tool_data.delete_segment_pressed {
if let Some(vector_data) = document.network_interface.compute_modified_vector(segment.layer()) {
shape_editor.dissolve_segment(responses, segment.layer(), &vector_data, segment.segment(), segment.points());
if let Some(vector) = document.network_interface.compute_modified_vector(segment.layer()) {
shape_editor.dissolve_segment(responses, segment.layer(), &vector, segment.segment(), segment.points());
}
} else {
let is_segment_selected = shape_editor
@ -2580,17 +2576,15 @@ impl Fsm for PathToolFsmState {
continue;
}
let Some(old_vector_data) = document.network_interface.compute_modified_vector(layer) else {
continue;
};
let Some(old_vector) = document.network_interface.compute_modified_vector(layer) else { continue };
// Also get the transform node that is applied on the layer if it exists
let transform = document.metadata().transform_to_document(layer);
let mut new_vector_data = VectorData::default();
let mut new_vector = Vector::default();
let mut selected_points_by_segment = HashSet::new();
old_vector_data
old_vector
.segment_bezier_iter()
.filter(|(segment, _, _, _)| layer_selection_state.is_segment_selected(*segment))
.for_each(|(_, _, start, end)| {
@ -2599,16 +2593,16 @@ impl Fsm for PathToolFsmState {
});
// Add all the selected points
for (point, position) in old_vector_data.point_domain.iter() {
for (point, position) in old_vector.point_domain.iter() {
if layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(point)) || selected_points_by_segment.contains(&point) {
new_vector_data.point_domain.push(point, position);
new_vector.point_domain.push(point, position);
}
}
let find_index = |id: PointId| new_vector_data.point_domain.iter().enumerate().find(|(_, (point_id, _))| *point_id == id).map(|(index, _)| index);
let find_index = |id: PointId| new_vector.point_domain.iter().enumerate().find(|(_, (point_id, _))| *point_id == id).map(|(index, _)| index);
// Add segments which have selected ends
for ((segment_id, bezier, start, end), stroke) in old_vector_data.segment_bezier_iter().zip(old_vector_data.segment_domain.stroke().iter()) {
for ((segment_id, bezier, start, end), stroke) in old_vector.segment_bezier_iter().zip(old_vector.segment_domain.stroke().iter()) {
let both_ends_selected = layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(start)) && layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(end));
let segment_selected = layer_selection_state.is_segment_selected(segment_id);
@ -2618,17 +2612,17 @@ impl Fsm for PathToolFsmState {
error!("Point does not exist in point domain");
return PathToolFsmState::Ready;
};
new_vector_data.segment_domain.push(segment_id, start_index, end_index, bezier.handles, *stroke);
new_vector.segment_domain.push(segment_id, start_index, end_index, bezier.handles, *stroke);
}
}
for handles in old_vector_data.colinear_manipulators {
if new_vector_data.segment_domain.ids().contains(&handles[0].segment) && new_vector_data.segment_domain.ids().contains(&handles[1].segment) {
new_vector_data.colinear_manipulators.push(handles);
for handles in old_vector.colinear_manipulators {
if new_vector.segment_domain.ids().contains(&handles[0].segment) && new_vector.segment_domain.ids().contains(&handles[1].segment) {
new_vector.colinear_manipulators.push(handles);
}
}
buffer.push((layer, new_vector_data, transform));
buffer.push((layer, new_vector, transform));
}
if clipboard == Clipboard::Device {
@ -2650,7 +2644,7 @@ impl Fsm for PathToolFsmState {
}
(_, PathToolMessage::Paste { data }) => {
// Deserialize the data
if let Ok(data) = serde_json::from_str::<Vec<(LayerNodeIdentifier, VectorData, DAffine2)>>(&data) {
if let Ok(data) = serde_json::from_str::<Vec<(LayerNodeIdentifier, Vector, DAffine2)>>(&data) {
shape_editor.deselect_all_points();
responses.add(DocumentMessage::AddTransaction);
let mut new_layers = Vec::new();
@ -2690,7 +2684,7 @@ impl Fsm for PathToolFsmState {
layer
};
// Create new point ids and add those into the existing vector data
// Create new point ids and add those into the existing vector content
let mut points_map = HashMap::new();
for (point, position) in new_vector.point_domain.iter() {
let new_point_id = PointId::generate();
@ -2701,7 +2695,7 @@ impl Fsm for PathToolFsmState {
responses.add(GraphOperationMessage::Vector { layer, modification_type });
}
// Create new segment ids and add the segments into the existing vector data
// Create new segment ids and add the segments into the existing vector content
let mut segments_map = HashMap::new();
for (segment_id, bezier, start, end) in new_vector.segment_bezier_iter() {
let new_segment_id = SegmentId::generate();
@ -2777,13 +2771,13 @@ impl Fsm for PathToolFsmState {
if layer_selection_state.is_empty() {
continue;
}
let Some(old_vector_data) = document.network_interface.compute_modified_vector(layer) else {
let Some(old_vector) = document.network_interface.compute_modified_vector(layer) else {
continue;
};
// Add all the selected points
let mut selected_points_by_segment = HashSet::new();
old_vector_data
old_vector
.segment_bezier_iter()
.filter(|(segment, _, _, _)| layer_selection_state.is_segment_selected(*segment))
.for_each(|(_, _, start, end)| {
@ -2792,7 +2786,7 @@ impl Fsm for PathToolFsmState {
});
let mut points_map = HashMap::new();
for (point, position) in old_vector_data.point_domain.iter() {
for (point, position) in old_vector.point_domain.iter() {
// TODO: Either the point is selected or it is an endpoint of a selected segment
if layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(point)) || selected_points_by_segment.contains(&point) {
@ -2808,7 +2802,7 @@ impl Fsm for PathToolFsmState {
let mut segments_map = HashMap::new();
for (segment_id, bezier, start, end) in old_vector_data.segment_bezier_iter() {
for (segment_id, bezier, start, end) in old_vector.segment_bezier_iter() {
let both_ends_selected = layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(start)) && layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(end));
let segment_selected = layer_selection_state.is_segment_selected(segment_id);
@ -2830,7 +2824,7 @@ impl Fsm for PathToolFsmState {
}
}
for handles in old_vector_data.colinear_manipulators {
for handles in old_vector.colinear_manipulators {
let to_new_handle = |handle: HandleId| -> HandleId {
HandleId {
ty: handle.ty,
@ -3104,13 +3098,13 @@ fn get_selection_status(network_interface: &NodeNetworkInterface, shape_state: &
let Some(layer) = selection_layers.find(|(_, v)| *v > 0).map(|(k, _)| k) else {
return SelectionStatus::None;
};
let Some(vector_data) = network_interface.compute_modified_vector(layer) else {
let Some(vector) = network_interface.compute_modified_vector(layer) else {
return SelectionStatus::None;
};
let Some(&point) = shape_state.selected_points().next() else {
return SelectionStatus::None;
};
let Some(local_position) = point.get_position(&vector_data) else {
let Some(local_position) = point.get_position(&vector) else {
return SelectionStatus::None;
};
@ -3118,7 +3112,7 @@ fn get_selection_status(network_interface: &NodeNetworkInterface, shape_state: &
.document_metadata()
.transform_to_document_if_feeds(layer, network_interface)
.transform_point2(local_position);
let manipulator_angle = if vector_data.colinear(point) { ManipulatorAngle::Colinear } else { ManipulatorAngle::Free };
let manipulator_angle = if vector.colinear(point) { ManipulatorAngle::Colinear } else { ManipulatorAngle::Free };
return SelectionStatus::One(SingleSelectedPoint {
coordinates,
@ -3143,40 +3137,40 @@ fn calculate_lock_angle(
shape_state: &mut ShapeState,
responses: &mut VecDeque<Message>,
document: &DocumentMessageHandler,
vector_data: &VectorData,
vector: &Vector,
handle_id: ManipulatorPointId,
tangent_to_neighboring_tangents: bool,
) -> Option<f64> {
let anchor = handle_id.get_anchor(vector_data)?;
let anchor_position = vector_data.point_domain.position_from_id(anchor);
let anchor = handle_id.get_anchor(vector)?;
let anchor_position = vector.point_domain.position_from_id(anchor);
let current_segment = handle_id.get_segment();
let points_connected = vector_data.connected_count(anchor);
let points_connected = vector.connected_count(anchor);
let (anchor_position, segment) = anchor_position.zip(current_segment)?;
if points_connected == 1 {
calculate_segment_angle(anchor, segment, vector_data, false)
calculate_segment_angle(anchor, segment, vector, false)
} else {
let opposite_handle = handle_id
.get_handle_pair(vector_data)
.get_handle_pair(vector)
.iter()
.flatten()
.find(|&h| h.to_manipulator_point() != handle_id)
.copied()
.map(|h| h.to_manipulator_point());
let opposite_handle_position = opposite_handle.and_then(|h| h.get_position(vector_data)).filter(|pos| (pos - anchor_position).length() > 1e-6);
let opposite_handle_position = opposite_handle.and_then(|h| h.get_position(vector)).filter(|pos| (pos - anchor_position).length() > 1e-6);
if let Some(opposite_pos) = opposite_handle_position {
if !vector_data.colinear_manipulators.iter().flatten().map(|h| h.to_manipulator_point()).any(|h| h == handle_id) {
if !vector.colinear_manipulators.iter().flatten().map(|h| h.to_manipulator_point()).any(|h| h == handle_id) {
shape_state.convert_selected_manipulators_to_colinear_handles(responses, document);
tool_data.temporary_colinear_handles = true;
}
Some(-(opposite_pos - anchor_position).angle_to(DVec2::X))
} else {
let angle_1 = vector_data
let angle_1 = vector
.adjacent_segment(&handle_id)
.and_then(|(_, adjacent_segment)| calculate_segment_angle(anchor, adjacent_segment, vector_data, false));
.and_then(|(_, adjacent_segment)| calculate_segment_angle(anchor, adjacent_segment, vector, false));
let angle_2 = calculate_segment_angle(anchor, segment, vector_data, false);
let angle_2 = calculate_segment_angle(anchor, segment, vector, false);
match (angle_1, angle_2) {
(Some(angle_1), Some(angle_2)) => {
@ -3195,37 +3189,32 @@ fn calculate_lock_angle(
}
}
fn check_handle_over_adjacent_anchor(handle_id: ManipulatorPointId, vector_data: &VectorData) -> Option<PointId> {
let (anchor, handle_position) = handle_id.get_anchor(vector_data).zip(handle_id.get_position(vector_data))?;
fn check_handle_over_adjacent_anchor(handle_id: ManipulatorPointId, vector: &Vector) -> Option<PointId> {
let (anchor, handle_position) = handle_id.get_anchor(vector).zip(handle_id.get_position(vector))?;
let check_if_close = |point_id: &PointId| {
let Some(anchor_position) = vector_data.point_domain.position_from_id(*point_id) else {
let Some(anchor_position) = vector.point_domain.position_from_id(*point_id) else {
return false;
};
(anchor_position - handle_position).length() < 10.
};
vector_data.connected_points(anchor).find(check_if_close)
vector.connected_points(anchor).find(check_if_close)
}
fn calculate_adjacent_anchor_tangent(
currently_dragged_handle: ManipulatorPointId,
anchor: Option<PointId>,
adjacent_anchor: Option<PointId>,
vector_data: &VectorData,
) -> (Option<f64>, Option<DVec2>) {
fn calculate_adjacent_anchor_tangent(currently_dragged_handle: ManipulatorPointId, anchor: Option<PointId>, adjacent_anchor: Option<PointId>, vector: &Vector) -> (Option<f64>, Option<DVec2>) {
// Early return if no anchor or no adjacent anchors
let Some((dragged_handle_anchor, adjacent_anchor)) = anchor.zip(adjacent_anchor) else {
return (None, None);
};
let adjacent_anchor_position = vector_data.point_domain.position_from_id(adjacent_anchor);
let adjacent_anchor_position = vector.point_domain.position_from_id(adjacent_anchor);
let handles: Vec<_> = vector_data.all_connected(adjacent_anchor).filter(|handle| handle.length(vector_data) > 1e-6).collect();
let handles: Vec<_> = vector.all_connected(adjacent_anchor).filter(|handle| handle.length(vector) > 1e-6).collect();
match handles.len() {
0 => {
// Find non-shared segments
let non_shared_segment: Vec<_> = vector_data
let non_shared_segment: Vec<_> = vector
.segment_bezier_iter()
.filter_map(|(segment_id, _, start, end)| {
let touches_adjacent = start == adjacent_anchor || end == adjacent_anchor;
@ -3237,7 +3226,7 @@ fn calculate_adjacent_anchor_tangent(
match non_shared_segment.first() {
Some(&segment) => {
let angle = calculate_segment_angle(adjacent_anchor, segment, vector_data, true);
let angle = calculate_segment_angle(adjacent_anchor, segment, vector, true);
(angle, adjacent_anchor_position)
}
None => (None, None),
@ -3246,7 +3235,7 @@ fn calculate_adjacent_anchor_tangent(
1 => {
let segment = handles[0].segment;
let angle = calculate_segment_angle(adjacent_anchor, segment, vector_data, true);
let angle = calculate_segment_angle(adjacent_anchor, segment, vector, true);
(angle, adjacent_anchor_position)
}
@ -3261,7 +3250,7 @@ fn calculate_adjacent_anchor_tangent(
};
let angle = shared_segment_handle
.get_position(vector_data)
.get_position(vector)
.zip(adjacent_anchor_position)
.map(|(handle, anchor)| -(handle - anchor).angle_to(DVec2::X));
@ -3296,8 +3285,8 @@ fn update_dynamic_hints(
shape_editor.selected_points().next(),
document.network_interface.selected_nodes().selected_layers(document.metadata()).next(),
) {
if let Some(vector_data) = document.network_interface.compute_modified_vector(layer) {
single_colinear_anchor_selected = vector_data.colinear(*anchor)
if let Some(vector) = document.network_interface.compute_modified_vector(layer) {
single_colinear_anchor_selected = vector.colinear(*anchor)
}
}
}
@ -3448,9 +3437,9 @@ fn update_dynamic_hints(
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 let Some(vector) = document.network_interface.compute_modified_vector(segment.layer()) {
let other_handle1 = vector.other_colinear_handle(handle1);
let other_handle2 = vector.other_colinear_handle(handle2);
if other_handle1.is_some() || other_handle2.is_some() {
has_colinear_anchors = true;
}

View file

@ -14,8 +14,8 @@ use crate::messages::tool::common_functionality::utility_functions::{calculate_s
use bezier_rs::{Bezier, BezierHandles};
use graph_craft::document::NodeId;
use graphene_std::Color;
use graphene_std::vector::{HandleId, ManipulatorPointId, NoHashBuilder, SegmentId, StrokeId, VectorData};
use graphene_std::vector::{PointId, VectorModificationType};
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
use graphene_std::vector::{NoHashBuilder, PointId, SegmentId, StrokeId, Vector, VectorModificationType};
#[derive(Default, ExtractField)]
pub struct PenTool {
@ -395,11 +395,11 @@ impl PenToolData {
}
/// Check whether target handle is primary, end, or `self.handle_end`
fn check_end_handle_type(&self, vector_data: &VectorData) -> TargetHandle {
fn check_end_handle_type(&self, vector: &Vector) -> TargetHandle {
match (self.handle_end, self.prior_segment_endpoint, self.prior_segment, self.path_closed) {
(Some(_), _, _, false) => TargetHandle::PreviewInHandle,
(None, Some(point), Some(segment), false) | (Some(_), Some(point), Some(segment), true) => {
if vector_data.segment_start_from_id(segment) == Some(point) {
if vector.segment_start_from_id(segment) == Some(point) {
TargetHandle::PriorOutHandle(segment)
} else {
TargetHandle::PriorInHandle(segment)
@ -409,23 +409,23 @@ impl PenToolData {
}
}
fn check_grs_end_handle(&self, vector_data: &VectorData) -> TargetHandle {
fn check_grs_end_handle(&self, vector: &Vector) -> TargetHandle {
let Some(point) = self.latest_point().map(|point| point.id) else { return TargetHandle::None };
let Some(segment) = self.prior_segment else { return TargetHandle::None };
if vector_data.segment_start_from_id(segment) == Some(point) {
if vector.segment_start_from_id(segment) == Some(point) {
TargetHandle::PriorOutHandle(segment)
} else {
TargetHandle::PriorInHandle(segment)
}
}
fn get_opposite_handle_type(&self, handle_type: TargetHandle, vector_data: &VectorData) -> TargetHandle {
fn get_opposite_handle_type(&self, handle_type: TargetHandle, vector: &Vector) -> TargetHandle {
match handle_type {
TargetHandle::FuturePreviewOutHandle => self.check_end_handle_type(vector_data),
TargetHandle::FuturePreviewOutHandle => self.check_end_handle_type(vector),
TargetHandle::PreviewInHandle => match (self.path_closed, self.prior_segment_endpoint, self.prior_segment) {
(true, Some(point), Some(segment)) => {
if vector_data.segment_start_from_id(segment) == Some(point) {
if vector.segment_start_from_id(segment) == Some(point) {
TargetHandle::PriorOutHandle(segment)
} else {
TargetHandle::PriorInHandle(segment)
@ -472,10 +472,10 @@ impl PenToolData {
}
}
fn target_handle_position(&self, handle_type: TargetHandle, vector_data: &VectorData) -> Option<DVec2> {
fn target_handle_position(&self, handle_type: TargetHandle, vector: &Vector) -> Option<DVec2> {
match handle_type {
TargetHandle::PriorOutHandle(segment) => ManipulatorPointId::PrimaryHandle(segment).get_position(vector_data),
TargetHandle::PriorInHandle(segment) => ManipulatorPointId::EndHandle(segment).get_position(vector_data),
TargetHandle::PriorOutHandle(segment) => ManipulatorPointId::PrimaryHandle(segment).get_position(vector),
TargetHandle::PriorInHandle(segment) => ManipulatorPointId::EndHandle(segment).get_position(vector),
TargetHandle::PreviewInHandle => self.handle_end,
TargetHandle::FuturePreviewOutHandle => Some(self.next_handle_start),
TargetHandle::None => None,
@ -488,11 +488,11 @@ impl PenToolData {
return;
};
let Some(vector_data) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else {
let Some(vector) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else {
return;
};
match self.check_end_handle_type(&vector_data) {
match self.check_end_handle_type(&vector) {
TargetHandle::PriorInHandle(segment) => shape_state.deselect_point(ManipulatorPointId::EndHandle(segment)),
TargetHandle::PriorOutHandle(segment) => shape_state.deselect_point(ManipulatorPointId::PrimaryHandle(segment)),
_ => {}
@ -518,18 +518,14 @@ impl PenToolData {
self.latest_points.len() == 1 && self.latest_point().is_some_and(|point| point.pos == self.next_point)
}
// When the vector data transform changes, the positions of the points must be recalculated.
// When the vector transform changes, the positions of the points must be recalculated.
fn recalculate_latest_points_position(&mut self, document: &DocumentMessageHandler) {
let selected_nodes = document.network_interface.selected_nodes();
let mut selected_layers = selected_nodes.selected_layers(document.metadata());
if let (Some(layer), None) = (selected_layers.next(), selected_layers.next()) {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
return;
};
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return };
for point in &mut self.latest_points {
let Some(pos) = vector_data.point_domain.position_from_id(point.id) else {
continue;
};
let Some(pos) = vector.point_domain.position_from_id(point.id) else { continue };
point.pos = pos;
point.handle_start = point.pos;
}
@ -549,7 +545,7 @@ impl PenToolData {
self.g1_continuous = true;
let document = snap_data.document;
self.next_handle_start = self.next_point;
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
let vector = document.network_interface.compute_modified_vector(layer).unwrap();
self.update_handle_type(TargetHandle::FuturePreviewOutHandle);
self.handle_mode = HandleMode::ColinearLocked;
@ -565,7 +561,7 @@ impl PenToolData {
self.store_clicked_endpoint(document, &transform, snap_data.input, preferences);
if self.modifiers.lock_angle {
self.set_lock_angle(&vector_data, id, self.prior_segment);
self.set_lock_angle(&vector, id, self.prior_segment);
let last_segment = self.prior_segment;
let Some(point) = self.latest_point_mut() else { return };
point.in_segment = last_segment;
@ -579,7 +575,7 @@ impl PenToolData {
}
// Closing path
let closing_path_on_point = self.close_path_on_point(snap_data, &vector_data, document, preferences, id, &transform);
let closing_path_on_point = self.close_path_on_point(snap_data, &vector, document, preferences, id, &transform);
if !closing_path_on_point && preferences.vector_meshes {
// Attempt to find nearest segment and close path on segment by creating an anchor point on it
let tolerance = crate::consts::SNAP_POINT_TOLERANCE;
@ -600,24 +596,16 @@ impl PenToolData {
self.handle_mode = HandleMode::Free;
if let (true, Some(prior_endpoint)) = (self.modifiers.lock_angle, self.prior_segment_endpoint) {
self.set_lock_angle(&vector_data, prior_endpoint, self.prior_segment);
self.set_lock_angle(&vector, prior_endpoint, self.prior_segment);
self.switch_to_free_on_ctrl_release = true;
}
}
}
}
fn close_path_on_point(
&mut self,
snap_data: SnapData,
vector_data: &VectorData,
document: &DocumentMessageHandler,
preferences: &PreferencesMessageHandler,
id: PointId,
transform: &DAffine2,
) -> bool {
for id in vector_data.extendable_points(preferences.vector_meshes).filter(|&point| point != id) {
let Some(pos) = vector_data.point_domain.position_from_id(id) else { continue };
fn close_path_on_point(&mut self, snap_data: SnapData, vector: &Vector, document: &DocumentMessageHandler, preferences: &PreferencesMessageHandler, id: PointId, transform: &DAffine2) -> bool {
for id in vector.extendable_points(preferences.vector_meshes).filter(|&point| point != id) {
let Some(pos) = vector.point_domain.position_from_id(id) else { continue };
let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(self.next_point));
let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);
@ -629,7 +617,7 @@ impl PenToolData {
self.store_clicked_endpoint(document, transform, snap_data.input, preferences);
self.handle_mode = HandleMode::Free;
if let (true, Some(prior_endpoint)) = (self.modifiers.lock_angle, self.prior_segment_endpoint) {
self.set_lock_angle(vector_data, prior_endpoint, self.prior_segment);
self.set_lock_angle(vector, prior_endpoint, self.prior_segment);
self.switch_to_free_on_ctrl_release = true;
}
return true;
@ -662,11 +650,11 @@ impl PenToolData {
let selected_nodes = document.network_interface.selected_nodes();
let mut selected_layers = selected_nodes.selected_layers(document.metadata());
let layer = selected_layers.next().filter(|_| selected_layers.next().is_none()).or(self.current_layer)?;
let vector_data = document.network_interface.compute_modified_vector(layer)?;
let vector = document.network_interface.compute_modified_vector(layer)?;
let start = self.latest_point()?.id;
let transform = document.metadata().document_to_viewport * transform;
for id in vector_data.extendable_points(preferences.vector_meshes).filter(|&point| point != start) {
let Some(pos) = vector_data.point_domain.position_from_id(id) else { continue };
for id in vector.extendable_points(preferences.vector_meshes).filter(|&point| point != start) {
let Some(pos) = vector.point_domain.position_from_id(id) else { continue };
let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(next_point));
let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);
if transformed_distance_between_squared < snap_point_tolerance_squared {
@ -687,15 +675,15 @@ impl PenToolData {
// Store the segment
let id = SegmentId::generate();
if self.path_closed {
if let Some((handles, handle1_pos)) = match self.get_opposite_handle_type(TargetHandle::PreviewInHandle, &vector_data) {
if let Some((handles, handle1_pos)) = match self.get_opposite_handle_type(TargetHandle::PreviewInHandle, &vector) {
TargetHandle::PriorOutHandle(segment) => {
let handles = [HandleId::end(id), HandleId::primary(segment)];
let handle1_pos = handles[1].to_manipulator_point().get_position(&vector_data);
let handle1_pos = handles[1].to_manipulator_point().get_position(&vector);
handle1_pos.map(|pos| (handles, pos))
}
TargetHandle::PriorInHandle(segment) => {
let handles = [HandleId::end(id), HandleId::end(segment)];
let handle1_pos = handles[1].to_manipulator_point().get_position(&vector_data);
let handle1_pos = handles[1].to_manipulator_point().get_position(&vector);
handle1_pos.map(|pos| (handles, pos))
}
_ => None,
@ -719,14 +707,14 @@ impl PenToolData {
// Mirror
if let Some((last_segment, last_point)) = self.latest_point().and_then(|point| point.in_segment).zip(self.latest_point()) {
let end = vector_data.segment_end_from_id(last_segment) == Some(last_point.id);
let end = vector.segment_end_from_id(last_segment) == Some(last_point.id);
let handles = if end {
[HandleId::end(last_segment), HandleId::primary(id)]
} else {
[HandleId::primary(last_segment), HandleId::primary(id)]
};
if let Some(h1) = handles[0].to_manipulator_point().get_position(&vector_data) {
if let Some(h1) = handles[0].to_manipulator_point().get_position(&vector) {
let angle = (h1 - last_point.pos).angle_to(last_point.handle_start - last_point.pos);
let pi = std::f64::consts::PI;
let colinear = (angle - pi).abs() < 1e-6 || (angle + pi).abs() < 1e-6;
@ -758,13 +746,13 @@ impl PenToolData {
transform: &DAffine2,
snap_data: &SnapData<'_>,
mouse: &DVec2,
vector_data: &VectorData,
vector: &Vector,
input: &InputPreprocessorMessageHandler,
) -> Option<DVec2> {
let reference_handle = if self.path_closed { TargetHandle::PreviewInHandle } else { TargetHandle::FuturePreviewOutHandle };
let end_handle = self.get_opposite_handle_type(reference_handle, vector_data);
let end_handle_pos = self.target_handle_position(end_handle, vector_data);
let ref_pos = self.target_handle_position(reference_handle, vector_data)?;
let end_handle = self.get_opposite_handle_type(reference_handle, vector);
let end_handle_pos = self.target_handle_position(end_handle, vector);
let ref_pos = self.target_handle_position(reference_handle, vector)?;
let snap = &mut self.snap_manager;
let snap_data = SnapData::new_snap_cache(snap_data.document, input, &self.snap_cache);
@ -826,7 +814,7 @@ impl PenToolData {
responses: &mut VecDeque<Message>,
) {
// Validate necessary data exists
let Some(vector_data) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else {
let Some(vector) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else {
return;
};
@ -842,9 +830,9 @@ impl PenToolData {
let should_swap_to_start = !self.path_closed && !matches!(self.handle_type, TargetHandle::None | TargetHandle::FuturePreviewOutHandle);
if should_swap_to_opposite {
let opposite_type = self.get_opposite_handle_type(self.handle_type, &vector_data);
let opposite_type = self.get_opposite_handle_type(self.handle_type, &vector);
// Update offset
let Some(handle_pos) = self.target_handle_position(opposite_type, &vector_data) else {
let Some(handle_pos) = self.target_handle_position(opposite_type, &vector) else {
self.handle_swapped = false;
return;
};
@ -894,7 +882,7 @@ impl PenToolData {
Some(PenToolFsmState::DraggingHandle(self.handle_mode))
}
fn move_anchor_and_handles(&mut self, delta: DVec2, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>, vector_data: &VectorData) {
fn move_anchor_and_handles(&mut self, delta: DVec2, layer: LayerNodeIdentifier, responses: &mut VecDeque<Message>, vector: &Vector) {
if self.handle_end.is_none() {
if let Some(latest_pt) = self.latest_point_mut() {
latest_pt.pos += delta;
@ -912,10 +900,10 @@ impl PenToolData {
let reference_handle = if self.path_closed { TargetHandle::PreviewInHandle } else { TargetHandle::FuturePreviewOutHandle };
// Move the end handle
let end_handle_type = self.get_opposite_handle_type(reference_handle, vector_data);
let end_handle_type = self.get_opposite_handle_type(reference_handle, vector);
match end_handle_type {
TargetHandle::PriorInHandle(..) | TargetHandle::PriorOutHandle(..) => {
let Some(handle_pos) = self.target_handle_position(end_handle_type, vector_data) else { return };
let Some(handle_pos) = self.target_handle_position(end_handle_type, vector) else { return };
self.update_target_handle_pos(end_handle_type, self.next_point, responses, handle_pos + delta, layer);
}
_ => {}
@ -934,13 +922,13 @@ impl PenToolData {
let colinear = (self.handle_mode == HandleMode::ColinearEquidistant && self.modifiers.break_handle) || (self.handle_mode == HandleMode::ColinearLocked && !self.modifiers.break_handle);
let document = snap_data.document;
let Some(layer) = layer else { return Some(PenToolFsmState::DraggingHandle(self.handle_mode)) };
let vector_data = document.network_interface.compute_modified_vector(layer)?;
let vector = document.network_interface.compute_modified_vector(layer)?;
let viewport_to_document = document.metadata().document_to_viewport.inverse();
let mut mouse_pos = mouse;
// Handles pressing Space to drag anchor and its handles
if self.modifiers.move_anchor_with_handles {
let Some(delta) = self.space_anchor_handle_snap(&viewport_to_document, &transform, &snap_data, &mouse, &vector_data, input) else {
let Some(delta) = self.space_anchor_handle_snap(&viewport_to_document, &transform, &snap_data, &mouse, &vector, input) else {
return Some(PenToolFsmState::DraggingHandle(self.handle_mode));
};
@ -959,7 +947,7 @@ impl PenToolData {
};
}
self.move_anchor_and_handles(delta, layer, responses, &vector_data);
self.move_anchor_and_handles(delta, layer, responses, &vector);
responses.add(OverlaysMessage::Draw);
return Some(PenToolFsmState::DraggingHandle(self.handle_mode));
@ -994,8 +982,8 @@ impl PenToolData {
match self.handle_mode {
HandleMode::ColinearLocked | HandleMode::ColinearEquidistant => {
self.g1_continuous = true;
self.apply_colinear_constraint(responses, layer, self.next_point, &vector_data);
self.adjust_handle_length(responses, layer, &vector_data);
self.apply_colinear_constraint(responses, layer, self.next_point, &vector);
self.adjust_handle_length(responses, layer, &vector);
}
HandleMode::Free => {
self.g1_continuous = false;
@ -1006,7 +994,7 @@ impl PenToolData {
let Some(endpoint) = self.prior_segment_endpoint else {
return Some(PenToolFsmState::DraggingHandle(self.handle_mode));
};
self.set_lock_angle(&vector_data, endpoint, self.prior_segment);
self.set_lock_angle(&vector, endpoint, self.prior_segment);
self.switch_to_free_on_ctrl_release = true;
let last_segment = self.prior_segment;
if let Some(latest) = self.latest_point_mut() {
@ -1020,19 +1008,19 @@ impl PenToolData {
}
/// Makes the opposite handle equidistant or locks its length.
fn adjust_handle_length(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector_data: &VectorData) {
let opposite_handle_type = self.get_opposite_handle_type(self.handle_type, vector_data);
fn adjust_handle_length(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, vector: &Vector) {
let opposite_handle_type = self.get_opposite_handle_type(self.handle_type, vector);
match self.handle_mode {
HandleMode::ColinearEquidistant => {
if self.modifiers.break_handle {
// Store handle for later restoration only when Alt is first pressed
if !self.alt_pressed {
self.previous_handle_end_pos = self.target_handle_position(opposite_handle_type, vector_data);
self.previous_handle_end_pos = self.target_handle_position(opposite_handle_type, vector);
self.alt_pressed = true;
}
// Set handle to opposite position of the other handle
let Some(new_position) = self.target_handle_position(self.handle_type, vector_data).map(|handle| self.next_point * 2. - handle) else {
let Some(new_position) = self.target_handle_position(self.handle_type, vector).map(|handle| self.next_point * 2. - handle) else {
return;
};
self.update_target_handle_pos(opposite_handle_type, self.next_point, responses, new_position, layer);
@ -1047,7 +1035,7 @@ impl PenToolData {
}
HandleMode::ColinearLocked => {
if !self.modifiers.break_handle {
let Some(new_position) = self.target_handle_position(self.handle_type, vector_data).map(|handle| self.next_point * 2. - handle) else {
let Some(new_position) = self.target_handle_position(self.handle_type, vector).map(|handle| self.next_point * 2. - handle) else {
return;
};
self.update_target_handle_pos(opposite_handle_type, self.next_point, responses, new_position, layer);
@ -1057,23 +1045,23 @@ impl PenToolData {
}
}
fn apply_colinear_constraint(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, anchor_pos: DVec2, vector_data: &VectorData) {
let Some(handle) = self.target_handle_position(self.handle_type, vector_data) else {
return;
};
fn apply_colinear_constraint(&mut self, responses: &mut VecDeque<Message>, layer: LayerNodeIdentifier, anchor_pos: DVec2, vector: &Vector) {
let Some(handle) = self.target_handle_position(self.handle_type, vector) else { return };
if (anchor_pos - handle).length() < 1e-6 && self.modifiers.lock_angle {
return;
}
let Some(direction) = (anchor_pos - handle).try_normalize() else {
return;
};
let opposite_handle = self.get_opposite_handle_type(self.handle_type, vector_data);
let Some(handle_offset) = self.target_handle_position(opposite_handle, vector_data).map(|handle| (handle - anchor_pos).length()) else {
let Some(direction) = (anchor_pos - handle).try_normalize() else { return };
let opposite_handle = self.get_opposite_handle_type(self.handle_type, vector);
let Some(handle_offset) = self.target_handle_position(opposite_handle, vector).map(|handle| (handle - anchor_pos).length()) else {
return;
};
let new_handle_position = anchor_pos + handle_offset * direction;
self.update_target_handle_pos(opposite_handle, self.next_point, responses, new_handle_position, layer);
}
@ -1086,10 +1074,10 @@ impl PenToolData {
let selected_nodes = document.network_interface.selected_nodes();
let mut selected_layers = selected_nodes.selected_layers(document.metadata());
let layer = selected_layers.next().filter(|_| selected_layers.next().is_none()).or(self.current_layer)?;
let vector_data = document.network_interface.compute_modified_vector(layer)?;
let vector = document.network_interface.compute_modified_vector(layer)?;
let transform = document.metadata().document_to_viewport * transform;
for point in vector_data.extendable_points(preferences.vector_meshes) {
let Some(pos) = vector_data.point_domain.position_from_id(point) else { continue };
for point in vector.extendable_points(preferences.vector_meshes) {
let Some(pos) = vector.point_domain.position_from_id(point) else { continue };
let transformed_distance_between_squared = transform.transform_point2(pos).distance_squared(transform.transform_point2(self.next_point));
let snap_point_tolerance_squared = crate::consts::SNAP_POINT_TOLERANCE.powi(2);
if transformed_distance_between_squared < snap_point_tolerance_squared {
@ -1217,11 +1205,11 @@ impl PenToolData {
if append {
if let Some((layer, point, _)) = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences) {
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
let vector = document.network_interface.compute_modified_vector(layer).unwrap();
let segment = vector.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
if self.modifiers.lock_angle {
self.set_lock_angle(&vector_data, point, segment);
self.set_lock_angle(&vector, point, segment);
self.switch_to_free_on_ctrl_release = true;
}
}
@ -1235,11 +1223,11 @@ impl PenToolData {
}
if let Some((layer, point, _position)) = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences) {
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
let vector = document.network_interface.compute_modified_vector(layer).unwrap();
let segment = vector.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
self.handle_mode = HandleMode::Free;
if self.modifiers.lock_angle {
self.set_lock_angle(&vector_data, point, segment);
self.set_lock_angle(&vector, point, segment);
self.switch_to_free_on_ctrl_release = true;
}
}
@ -1265,9 +1253,9 @@ impl PenToolData {
/// Perform extension of an existing path
fn extend_existing_path(&mut self, document: &DocumentMessageHandler, layer: LayerNodeIdentifier, point: PointId, position: DVec2) {
let vector_data = document.network_interface.compute_modified_vector(layer);
let (handle_start, in_segment) = if let Some(vector_data) = &vector_data {
vector_data
let vector = document.network_interface.compute_modified_vector(layer);
let (handle_start, in_segment) = if let Some(vector) = &vector {
vector
.segment_bezier_iter()
.find_map(|(segment_id, bezier, start, end)| {
let is_end = point == end;
@ -1310,12 +1298,12 @@ impl PenToolData {
self.next_point = position;
self.next_handle_start = handle_start;
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
let vector = document.network_interface.compute_modified_vector(layer).unwrap();
let segment = vector.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
self.handle_mode = HandleMode::Free;
if self.modifiers.lock_angle {
self.set_lock_angle(&vector_data, point, segment);
self.set_lock_angle(&vector, point, segment);
self.switch_to_free_on_ctrl_release = true;
}
}
@ -1340,11 +1328,11 @@ impl PenToolData {
if let Some((layer, point, _position)) = closest_point(document, viewport, tolerance, document.metadata().all_layers(), |_| false, preferences) {
self.prior_segment_endpoint = Some(point);
self.prior_segment_layer = Some(layer);
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
let segment = vector_data.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
let vector = document.network_interface.compute_modified_vector(layer).unwrap();
let segment = vector.all_connected(point).collect::<Vec<_>>().first().map(|s| s.segment);
self.prior_segment = segment;
layer_manipulators.insert(point);
for (&id, &position) in vector_data.point_domain.ids().iter().zip(vector_data.point_domain.positions()) {
for (&id, &position) in vector.point_domain.ids().iter().zip(vector.point_domain.positions()) {
if id == point {
continue;
}
@ -1355,8 +1343,8 @@ impl PenToolData {
}
}
fn set_lock_angle(&mut self, vector_data: &VectorData, anchor: PointId, segment: Option<SegmentId>) {
let anchor_position = vector_data.point_domain.position_from_id(anchor);
fn set_lock_angle(&mut self, vector: &Vector, anchor: PointId, segment: Option<SegmentId>) {
let anchor_position = vector.point_domain.position_from_id(anchor);
let Some((anchor_position, segment)) = anchor_position.zip(segment) else {
self.handle_mode = HandleMode::Free;
@ -1365,7 +1353,7 @@ impl PenToolData {
match (self.handle_type, self.path_closed) {
(TargetHandle::FuturePreviewOutHandle, _) | (TargetHandle::PreviewInHandle, true) => {
if let Some(required_handle) = calculate_segment_angle(anchor, segment, vector_data, true) {
if let Some(required_handle) = calculate_segment_angle(anchor, segment, vector, true) {
self.angle = required_handle;
self.handle_mode = HandleMode::ColinearEquidistant;
}
@ -1460,12 +1448,12 @@ impl Fsm for PenToolFsmState {
responses.add(TransformLayerMessage::BeginScalePen { last_point, handle });
}
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
let vector = document.network_interface.compute_modified_vector(layer).unwrap();
tool_data.previous_handle_start_pos = latest.handle_start;
let opposite_handle = tool_data.check_grs_end_handle(&vector_data);
tool_data.previous_handle_end_pos = tool_data.target_handle_position(opposite_handle, &vector_data);
let opposite_handle = tool_data.check_grs_end_handle(&vector);
tool_data.previous_handle_end_pos = tool_data.target_handle_position(opposite_handle, &vector);
let handle1 = latest_handle_start - latest_pos;
let Some(opposite_handle_pos) = tool_data.target_handle_position(opposite_handle, &vector_data) else {
let Some(opposite_handle_pos) = tool_data.target_handle_position(opposite_handle, &vector) else {
return PenToolFsmState::GRSHandle;
};
let handle2 = opposite_handle_pos - latest_pos;
@ -1476,8 +1464,8 @@ impl Fsm for PenToolFsmState {
}
(PenToolFsmState::GRSHandle, PenToolMessage::FinalPosition { final_position }) => {
let Some(layer) = layer else { return PenToolFsmState::GRSHandle };
let vector_data = document.network_interface.compute_modified_vector(layer);
let Some(vector_data) = vector_data else { return PenToolFsmState::GRSHandle };
let vector = document.network_interface.compute_modified_vector(layer);
let Some(vector) = vector else { return PenToolFsmState::GRSHandle };
if let Some(latest_pt) = tool_data.latest_point_mut() {
let layer_space_to_viewport = document.metadata().transform_to_viewport(layer);
@ -1489,8 +1477,8 @@ impl Fsm for PenToolFsmState {
let Some(latest) = tool_data.latest_point() else {
return PenToolFsmState::GRSHandle;
};
let opposite_handle = tool_data.check_grs_end_handle(&vector_data);
let Some(opposite_handle_pos) = tool_data.target_handle_position(opposite_handle, &vector_data) else {
let opposite_handle = tool_data.check_grs_end_handle(&vector);
let Some(opposite_handle_pos) = tool_data.target_handle_position(opposite_handle, &vector) else {
return PenToolFsmState::GRSHandle;
};
@ -1531,8 +1519,8 @@ impl Fsm for PenToolFsmState {
tool_data.next_handle_start = input.mouse.position;
let Some(layer) = layer else { return PenToolFsmState::GRSHandle };
let vector_data = document.network_interface.compute_modified_vector(layer).unwrap();
let opposite_handle = tool_data.check_grs_end_handle(&vector_data);
let vector = document.network_interface.compute_modified_vector(layer).unwrap();
let opposite_handle = tool_data.check_grs_end_handle(&vector);
let previous = tool_data.previous_handle_start_pos;
if let Some(latest) = tool_data.latest_point_mut() {
@ -1722,10 +1710,10 @@ impl Fsm for PenToolFsmState {
let start = latest_point.id;
if let Some(layer) = layer
&& let Some(mut vector_data) = document.network_interface.compute_modified_vector(layer)
&& let Some(mut vector) = document.network_interface.compute_modified_vector(layer)
{
let closest_point = vector_data.extendable_points(preferences.vector_meshes).filter(|&id| id != start).find(|&id| {
vector_data.point_domain.position_from_id(id).map_or(false, |pos| {
let closest_point = vector.extendable_points(preferences.vector_meshes).filter(|&id| id != start).find(|&id| {
vector.point_domain.position_from_id(id).is_some_and(|pos| {
let dist_sq = transform.transform_point2(pos).distance_squared(transform.transform_point2(next_point));
dist_sq < crate::consts::SNAP_POINT_TOLERANCE.powi(2)
})
@ -1734,21 +1722,21 @@ impl Fsm for PenToolFsmState {
// We have the point. Join the 2 vertices and check if any path is closed.
if let Some(end) = closest_point {
let segment_id = SegmentId::generate();
vector_data.push(segment_id, start, end, BezierHandles::Cubic { handle_start, handle_end }, StrokeId::ZERO);
vector.push(segment_id, start, end, BezierHandles::Cubic { handle_start, handle_end }, StrokeId::ZERO);
let grouped_segments = vector_data.auto_join_paths();
let grouped_segments = vector.auto_join_paths();
let closed_paths = grouped_segments.iter().filter(|path| path.is_closed() && path.contains(segment_id));
let subpaths: Vec<_> = closed_paths
.filter_map(|path| {
let segments = path.edges.iter().filter_map(|edge| {
vector_data
vector
.segment_domain
.iter()
.find(|(id, _, _, _)| id == &edge.id)
.map(|(_, start, end, bezier)| if start == edge.start { (bezier, start, end) } else { (bezier.reversed(), end, start) })
});
vector_data.subpath_from_segments_ignore_discontinuities(segments)
vector.subpath_from_segments_ignore_discontinuities(segments)
})
.collect();
@ -1817,7 +1805,8 @@ impl Fsm for PenToolFsmState {
}
// Merge two layers if the point is connected to the end point of another path
// This might not be the correct solution to artboards being included as the other layer, which occurs due to the compute_modified_vector call in should_extend using the click targets for a layer instead of vector data.
// This might not be the correct solution to artboards being included as the other layer,
// which occurs due to the `compute_modified_vector` call in `should_extend` using the click targets for a layer instead of vector.
let layers = LayerNodeIdentifier::ROOT_PARENT
.descendants(document.metadata())
.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]));
@ -1887,7 +1876,7 @@ impl Fsm for PenToolFsmState {
tool_data.toggle_colinear_debounce = true;
}
let Some(vector_data) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else {
let Some(vector) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) else {
return self;
};
@ -1901,7 +1890,7 @@ impl Fsm for PenToolFsmState {
document
.metadata()
.transform_to_viewport(layer)
.transform_point2(tool_data.target_handle_position(reference_handle, &vector_data).unwrap())
.transform_point2(tool_data.target_handle_position(reference_handle, &vector).unwrap())
});
tool_data.handle_start_offset = handle_start.map(|start| start - input.mouse.position);
tool_data.space_pressed = true;
@ -2075,8 +2064,8 @@ impl Fsm for PenToolFsmState {
}
(PenToolFsmState::DraggingHandle(..), PenToolMessage::Confirm) => {
// Confirm to end path
if let Some((vector_data, layer)) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)).zip(layer) {
let single_point_in_layer = vector_data.point_domain.ids().len() == 1;
if let Some((vector, layer)) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)).zip(layer) {
let single_point_in_layer = vector.point_domain.ids().len() == 1;
tool_data.finish_placing_handle(SnapData::new(document, input), transform, preferences, responses);
let latest_points = tool_data.latest_points.len() == 1;
@ -2129,8 +2118,8 @@ impl Fsm for PenToolFsmState {
}
}
(PenToolFsmState::PlacingAnchor, PenToolMessage::Abort) => {
let should_delete_layer = if let Some(vector_data) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) {
vector_data.point_domain.ids().len() == 1
let should_delete_layer = if let Some(vector) = layer.and_then(|layer| document.network_interface.compute_modified_vector(layer)) {
vector.point_domain.ids().len() == 1
} else {
false
};

View file

@ -548,15 +548,15 @@ mod test_spline_tool {
use crate::test_utils::test_prelude::*;
use glam::DAffine2;
use graphene_std::vector::PointId;
use graphene_std::vector::VectorData;
use graphene_std::vector::Vector;
fn assert_point_positions(vector_data: &VectorData, layer_to_viewport: DAffine2, expected_points: &[DVec2], epsilon: f64) {
let points_in_viewport: Vec<DVec2> = vector_data
fn assert_point_positions(vector: &Vector, layer_to_viewport: DAffine2, expected_points: &[DVec2], epsilon: f64) {
let points_in_viewport: Vec<DVec2> = vector
.point_domain
.ids()
.iter()
.filter_map(|&point_id| {
let position = vector_data.point_domain.position_from_id(point_id)?;
let position = vector.point_domain.position_from_id(point_id)?;
Some(layer_to_viewport.transform_point2(position))
})
.collect();
@ -601,19 +601,19 @@ mod test_spline_tool {
let first_spline_node = find_spline(document, spline_layer).expect("Spline node not found in the layer");
let first_vector_data = document.network_interface.compute_modified_vector(spline_layer).expect("Vector data not found for the spline layer");
let first_vector = document.network_interface.compute_modified_vector(spline_layer).expect("Vector not found for the spline layer");
// Verify initial spline has correct number of points and segments
let initial_point_count = first_vector_data.point_domain.ids().len();
let initial_segment_count = first_vector_data.segment_domain.ids().len();
let initial_point_count = first_vector.point_domain.ids().len();
let initial_segment_count = first_vector.segment_domain.ids().len();
assert_eq!(initial_point_count, 3, "Expected 3 points in initial spline, found {}", initial_point_count);
assert_eq!(initial_segment_count, 2, "Expected 2 segments in initial spline, found {}", initial_segment_count);
let layer_to_viewport = document.metadata().transform_to_viewport(spline_layer);
let endpoints: Vec<(PointId, DVec2)> = first_vector_data
let endpoints: Vec<(PointId, DVec2)> = first_vector
.extendable_points(false)
.filter_map(|point_id| first_vector_data.point_domain.position_from_id(point_id).map(|pos| (point_id, layer_to_viewport.transform_point2(pos))))
.filter_map(|point_id| first_vector.point_domain.position_from_id(point_id).map(|pos| (point_id, layer_to_viewport.transform_point2(pos))))
.collect();
assert_eq!(endpoints.len(), 2, "Expected 2 endpoints in the initial spline");
@ -632,14 +632,14 @@ mod test_spline_tool {
editor.press(Key::Enter, ModifierKeys::empty()).await;
let document = editor.active_document();
let extended_vector_data = document
let extended_vector = document
.network_interface
.compute_modified_vector(spline_layer)
.expect("Vector data not found for the extended spline layer");
.expect("Vector not found for the extended spline layer");
// Verify extended spline has correct number of points and segments
let extended_point_count = extended_vector_data.point_domain.ids().len();
let extended_segment_count = extended_vector_data.segment_domain.ids().len();
let extended_point_count = extended_vector.point_domain.ids().len();
let extended_segment_count = extended_vector.segment_domain.ids().len();
assert_eq!(extended_point_count, 5, "Expected 5 points in extended spline, found {}", extended_point_count);
assert_eq!(extended_segment_count, 4, "Expected 4 segments in extended spline, found {}", extended_segment_count);
@ -653,7 +653,7 @@ mod test_spline_tool {
let all_expected_points = [initial_points[0], initial_points[1], initial_points[2], continuation_points[0], continuation_points[1]];
assert_point_positions(&extended_vector_data, layer_to_viewport, &all_expected_points, 1e-10);
assert_point_positions(&extended_vector, layer_to_viewport, &all_expected_points, 1e-10);
}
#[tokio::test]
@ -688,14 +688,14 @@ mod test_spline_tool {
.selected_visible_and_unlocked_layers(network_interface)
.next()
.expect("Should have a selected layer");
let vector_data = network_interface.compute_modified_vector(layer).expect("Should have vector data");
let vector = network_interface.compute_modified_vector(layer).expect("Should have vector data");
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
// Expected points in viewport coordinates
let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];
// Assert all points are correctly positioned
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
assert_point_positions(&vector, layer_to_viewport, &expected_points, 1e-10);
}
#[tokio::test]
@ -728,14 +728,14 @@ mod test_spline_tool {
.selected_visible_and_unlocked_layers(network_interface)
.next()
.expect("Should have a selected layer");
let vector_data = network_interface.compute_modified_vector(layer).expect("Should have vector data");
let vector = network_interface.compute_modified_vector(layer).expect("Should have vector data");
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
// Expected points in viewport coordinates
let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];
// Assert all points are correctly positioned
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
assert_point_positions(&vector, layer_to_viewport, &expected_points, 1e-10);
}
#[tokio::test]
@ -766,14 +766,14 @@ mod test_spline_tool {
.selected_visible_and_unlocked_layers(network_interface)
.next()
.expect("Should have a selected layer");
let vector_data = network_interface.compute_modified_vector(layer).expect("Should have vector data");
let vector = network_interface.compute_modified_vector(layer).expect("Should have vector data");
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
// Expected points in viewport coordinates
let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];
// Assert all points are correctly positioned
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
assert_point_positions(&vector, layer_to_viewport, &expected_points, 1e-10);
}
#[tokio::test]
@ -805,14 +805,14 @@ mod test_spline_tool {
.selected_visible_and_unlocked_layers(network_interface)
.next()
.expect("Should have a selected layer");
let vector_data = network_interface.compute_modified_vector(layer).expect("Should have vector data");
let vector = network_interface.compute_modified_vector(layer).expect("Should have vector data");
let layer_to_viewport = document.metadata().transform_to_viewport(layer);
// Expected points in viewport coordinates
let expected_points = vec![DVec2::new(50., 50.), DVec2::new(100., 50.), DVec2::new(150., 100.)];
// Assert all points are correctly positioned
assert_point_positions(&vector_data, layer_to_viewport, &expected_points, 1e-10);
assert_point_positions(&vector, layer_to_viewport, &expected_points, 1e-10);
}
#[tokio::test]
@ -845,17 +845,17 @@ mod test_spline_tool {
let spline_layer = layers.next().expect("Failed to find the spline layer");
assert!(find_spline(document, spline_layer).is_some(), "Spline node not found in the layer");
let vector_data = document.network_interface.compute_modified_vector(spline_layer).expect("Vector data not found for the spline layer");
let vector = document.network_interface.compute_modified_vector(spline_layer).expect("Vector not found for the spline layer");
// Verify we have the correct number of points and segments
let point_count = vector_data.point_domain.ids().len();
let segment_count = vector_data.segment_domain.ids().len();
let point_count = vector.point_domain.ids().len();
let segment_count = vector.segment_domain.ids().len();
assert_eq!(point_count, 3, "Expected 3 points in the spline, found {}", point_count);
assert_eq!(segment_count, 2, "Expected 2 segments in the spline, found {}", segment_count);
let layer_to_viewport = document.metadata().transform_to_viewport(spline_layer);
assert_point_positions(&vector_data, layer_to_viewport, &spline_points, 1e-10);
assert_point_positions(&vector, layer_to_viewport, &spline_points, 1e-10);
}
}

View file

@ -11,9 +11,9 @@ use crate::messages::tool::tool_messages::tool_prelude::Key;
use crate::messages::tool::utility_types::{ToolData, ToolType};
use glam::{DAffine2, DVec2};
use graphene_std::renderer::Quad;
use graphene_std::vector::ManipulatorPointId;
use graphene_std::vector::click_target::ClickTargetType;
use graphene_std::vector::{VectorData, VectorModificationType};
use graphene_std::vector::misc::ManipulatorPointId;
use graphene_std::vector::{Vector, VectorModificationType};
use std::f64::consts::{PI, TAU};
const TRANSFORM_GRS_OVERLAY_PROVIDER: OverlayProvider = |context| TransformLayerMessage::Overlays(context).into();
@ -123,14 +123,14 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
self.grab_target = self.local_pivot;
}
// Here vector data from all layers is not considered which can be a problem in pivot calculation
else if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
else if let Some(vector) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
*selected.original_transforms = OriginalTransforms::default();
let viewspace = document.metadata().transform_to_viewport(selected_layers[0]);
let selected_segments = shape_editor.selected_segments().collect::<HashSet<_>>();
let mut affected_points = shape_editor.selected_points().copied().collect::<Vec<_>>();
for (segment_id, _, start, end) in vector_data.segment_bezier_iter() {
for (segment_id, _, start, end) in vector.segment_bezier_iter() {
if selected_segments.contains(&segment_id) {
affected_points.push(ManipulatorPointId::Anchor(start));
affected_points.push(ManipulatorPointId::Anchor(end));
@ -139,11 +139,11 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
let affected_point_refs = affected_points.iter().collect();
let get_location = |point: &&ManipulatorPointId| point.get_position(&vector_data).map(|position| viewspace.transform_point2(position));
let get_location = |point: &&ManipulatorPointId| point.get_position(&vector).map(|position| viewspace.transform_point2(position));
if let (Some((new_pivot, grab_target)), bounds) = calculate_pivot(
document,
&affected_point_refs,
&vector_data,
&vector,
viewspace,
|point: &ManipulatorPointId| get_location(&point),
&mut self.pivot_gizmo,
@ -362,12 +362,12 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
}
}
if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
if let Some(vector) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
if let [point] = selected_points.as_slice() {
if matches!(point, ManipulatorPointId::Anchor(_)) {
if let Some([handle1, handle2]) = point.get_handle_pair(&vector_data) {
let handle1_length = handle1.length(&vector_data);
let handle2_length = handle2.length(&vector_data);
if let Some([handle1, handle2]) = point.get_handle_pair(&vector) {
let handle1_length = handle1.length(&vector);
let handle2_length = handle2.length(&vector);
if (handle1_length == 0. && handle2_length == 0. && !using_select_tool) || (handle1_length == f64::MAX && handle2_length == f64::MAX && !using_select_tool) {
// G should work for this point but not R and S
@ -378,7 +378,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
}
}
} else {
let handle_length = point.as_handle().map(|handle| handle.length(&vector_data));
let handle_length = point.as_handle().map(|handle| handle.length(&vector));
if handle_length == Some(0.) {
selected.original_transforms.clear();
@ -693,7 +693,7 @@ impl TransformLayerMessageHandler {
fn calculate_pivot(
document: &DocumentMessageHandler,
selected_points: &Vec<&ManipulatorPointId>,
vector_data: &VectorData,
vector: &Vector,
viewspace: DAffine2,
get_location: impl Fn(&ManipulatorPointId) -> Option<DVec2>,
gizmo: &mut PivotGizmo,
@ -736,8 +736,8 @@ fn calculate_pivot(
ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => {
// Get the anchor position and transform it to the pivot
let (Some(pivot_position), Some(position)) = (
point.get_anchor_position(vector_data).map(|anchor_position| viewspace.transform_point2(anchor_position)),
point.get_position(vector_data),
point.get_anchor_position(vector).map(|anchor_position| viewspace.transform_point2(anchor_position)),
point.get_position(vector),
) else {
return (None, None);
};
@ -774,15 +774,15 @@ fn project_edge_to_quad(edge: DVec2, quad: &Quad, local: bool, axis_constraint:
fn update_colinear_handles(selected_layers: &[LayerNodeIdentifier], document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) {
for &layer in selected_layers {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { continue };
for [handle1, handle2] in &vector_data.colinear_manipulators {
for [handle1, handle2] in &vector.colinear_manipulators {
let manipulator1 = handle1.to_manipulator_point();
let manipulator2 = handle2.to_manipulator_point();
let Some(anchor) = manipulator1.get_anchor_position(&vector_data) else { continue };
let Some(pos1) = manipulator1.get_position(&vector_data).map(|pos| pos - anchor) else { continue };
let Some(pos2) = manipulator2.get_position(&vector_data).map(|pos| pos - anchor) else { continue };
let Some(anchor) = manipulator1.get_anchor_position(&vector) else { continue };
let Some(pos1) = manipulator1.get_position(&vector).map(|pos| pos - anchor) else { continue };
let Some(pos2) = manipulator2.get_position(&vector).map(|pos| pos - anchor) else { continue };
let angle = pos1.angle_to(pos2);

View file

@ -13,7 +13,7 @@ use graphene_std::renderer::{Render, RenderParams, SvgRender};
use graphene_std::renderer::{RenderMetadata, format_transform_matrix};
use graphene_std::text::FontCache;
use graphene_std::transform::Footprint;
use graphene_std::vector::VectorData;
use graphene_std::vector::Vector;
use graphene_std::vector::style::ViewMode;
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;
@ -34,7 +34,7 @@ pub struct ExecutionResponse {
result: Result<TaggedValue, String>,
responses: VecDeque<FrontendMessage>,
transform: DAffine2,
vector_modify: HashMap<NodeId, VectorData>,
vector_modify: HashMap<NodeId, Vector>,
/// The resulting value from the temporary inspected during execution
inspect_result: Option<InspectResult>,
}
@ -377,9 +377,9 @@ impl NodeGraphExecutor {
TaggedValue::F64(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::DVec2(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::OptionalColor(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::VectorData(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::GraphicGroup(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::RasterData(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::Vector(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::Group(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::Raster(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::Palette(render_object) => Self::debug_render(render_object, transform, responses),
_ => {
return Err(format!("Invalid node graph output type: {node_graph_output:#?}"));

View file

@ -14,7 +14,7 @@ use graphene_std::renderer::{Render, RenderParams, SvgRender};
use graphene_std::renderer::{RenderSvgSegmentList, SvgSegment};
use graphene_std::table::{Table, TableRow};
use graphene_std::text::FontCache;
use graphene_std::vector::VectorData;
use graphene_std::vector::Vector;
use graphene_std::vector::style::ViewMode;
use graphene_std::wasm_application_io::{RenderOutputType, WasmApplicationIo, WasmEditorApi};
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
@ -51,7 +51,7 @@ pub struct NodeRuntime {
// TODO: Remove, it doesn't need to be persisted anymore
/// The current renders of the thumbnails for layer nodes.
thumbnail_renders: HashMap<NodeId, Vec<SvgSegment>>,
vector_modify: HashMap<NodeId, VectorData>,
vector_modify: HashMap<NodeId, Vector>,
}
/// Messages passed from the editor thread to the node runtime thread.
@ -309,26 +309,25 @@ impl NodeRuntime {
};
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, graphene_std::Graphic>>() {
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
Self::process_graphic(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, graphene_std::Artboard>>() {
Self::process_graphic_element(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
Self::process_graphic(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
// Insert the vector modify if we are dealing with vector data
} else if let Some(record) = introspected_data.downcast_ref::<IORecord<Context, Table<VectorData>>>() {
} else if let Some(record) = introspected_data.downcast_ref::<IORecord<Context, Table<Vector>>>() {
let default = TableRow::default();
self.vector_modify
.insert(parent_network_node_id, record.output.iter_ref().next().unwrap_or_else(|| default.as_ref()).element.clone());
.insert(parent_network_node_id, record.output.iter().next().unwrap_or_else(|| default.as_ref()).element.clone());
} else {
log::warn!("Failed to downcast monitor node output {parent_network_node_id:?}");
}
}
}
// If this is `Graphic` data:
// Regenerate click targets and thumbnails for the layers in the graph, modifying the state and updating the UI.
fn process_graphic_element(
// If this is `Graphic` data, regenerate click targets and thumbnails for the layers in the graph, modifying the state and updating the UI.
fn process_graphic(
thumbnail_renders: &mut HashMap<NodeId, Vec<SvgSegment>>,
parent_network_node_id: NodeId,
graphic_element: &impl Render,
graphic: &impl Render,
responses: &mut VecDeque<FrontendMessage>,
update_thumbnails: bool,
) {
@ -339,7 +338,7 @@ impl NodeRuntime {
}
// Skip thumbnails if the layer is too complex (for performance)
if graphic_element.render_complexity() > 1000 {
if graphic.render_complexity() > 1000 {
let old = thumbnail_renders.insert(parent_network_node_id, Vec::new());
if old.is_none_or(|v| !v.is_empty()) {
responses.push_back(FrontendMessage::UpdateNodeThumbnail {
@ -350,7 +349,7 @@ impl NodeRuntime {
return;
}
let bounds = graphic_element.bounding_box(DAffine2::IDENTITY, true);
let bounds = graphic.bounding_box(DAffine2::IDENTITY, true);
// Render the thumbnail from a `Graphic` into an SVG string
let render_params = RenderParams {
@ -363,7 +362,7 @@ impl NodeRuntime {
alignment_parent_transform: None,
};
let mut render = SvgRender::new();
graphic_element.render_svg(&mut render, &render_params);
graphic.render_svg(&mut render, &render_params);
// And give the SVG a viewbox and outer <svg>...</svg> wrapper tag
let [min, max] = bounds.unwrap_or_default();

View file

@ -115,8 +115,8 @@
--color-data-general-dim: #8a8a8a;
--color-data-raster: #e4bb72;
--color-data-raster-dim: #8b7752;
--color-data-vectordata: #65bbe5;
--color-data-vectordata-dim: #4b778c;
--color-data-vector: #65bbe5;
--color-data-vector-dim: #4b778c;
--color-data-group: #66b195;
--color-data-group-dim: #3d725e;
--color-data-artboard: #fbf9eb;

View file

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

View file

@ -192,7 +192,7 @@ export type ContextMenuInformation = {
contextMenuData: "CreateNode" | { type: "CreateNode"; compatibleType: string } | { nodeId: bigint; currentlyIsNode: boolean };
};
export type FrontendGraphDataType = "General" | "Raster" | "VectorData" | "Number" | "Group" | "Artboard";
export type FrontendGraphDataType = "General" | "Raster" | "Vector" | "Number" | "Group" | "Artboard";
export class Node {
readonly index!: bigint;
@ -791,6 +791,16 @@ export class TriggerImport extends JsMessage {}
export class TriggerPaste extends JsMessage {}
export class TriggerSaveDocument extends JsMessage {
readonly documentId!: bigint;
readonly name!: string;
readonly path!: string | undefined;
readonly document!: string;
}
export class TriggerDownloadImage extends JsMessage {
readonly svg!: string;
@ -1647,6 +1657,7 @@ export const messageMakers: Record<string, MessageMaker> = {
DisplayRemoveEditableTextbox,
SendUIMetadata,
TriggerAboutGraphiteLocalizedCommitDate,
TriggerSaveDocument,
TriggerDownloadImage,
TriggerDownloadTextFile,
TriggerFetchAndOpenDocument,

View file

@ -6,6 +6,7 @@ import { type Editor } from "@graphite/editor";
import {
type FrontendDocumentDetails,
TriggerFetchAndOpenDocument,
TriggerSaveDocument,
TriggerDownloadImage,
TriggerDownloadTextFile,
TriggerImport,
@ -84,6 +85,9 @@ export function createPortfolioState(editor: Editor) {
const imageData = await extractPixelData(new Blob([data.content.data], { type: data.type }));
editor.handle.pasteImage(data.filename, new Uint8Array(imageData.data), imageData.width, imageData.height);
});
editor.subscriptions.subscribeJsMessage(TriggerSaveDocument, (triggerSaveDocument) => {
downloadFileText(triggerSaveDocument.name, triggerSaveDocument.document);
});
editor.subscriptions.subscribeJsMessage(TriggerDownloadTextFile, (triggerFileDownload) => {
downloadFileText(triggerFileDownload.name, triggerFileDownload.document);
});

View file

@ -1,7 +1,7 @@
import { type Editor } from "@graphite/editor";
export function updateBoundsOfViewports(editor: Editor, container: HTMLElement) {
const viewports = Array.from(container.querySelectorAll("[data-viewport]"));
const viewports = Array.from(container.querySelectorAll("[data-viewport-container]"));
const boundsOfViewports = viewports.map((canvas) => {
const bounds = canvas.getBoundingClientRect();
return [bounds.left, bounds.top, bounds.right, bounds.bottom];

View file

@ -239,7 +239,7 @@ impl EditorHandle {
wasm_bindgen_futures::spawn_local(poll_node_graph_evaluation());
if !EDITOR_HAS_CRASHED.load(Ordering::SeqCst) {
editor_and_handle(|_, handle| {
handle(|handle| {
handle.dispatch(InputPreprocessorMessage::CurrentTime {
timestamp: js_sys::Date::now() as u64,
});
@ -636,7 +636,7 @@ impl EditorHandle {
self.dispatch(message);
}
/// Paste vector data into a new layer from a serialized JSON representation
/// Paste vector into a new layer from a serialized JSON representation
#[wasm_bindgen(js_name = pasteSerializedVector)]
pub fn paste_serialized_vector(&self, data: String) {
let message = PortfolioMessage::PasteSerializedVector { data };
@ -914,7 +914,10 @@ fn set_timeout(f: &Closure<dyn FnMut()>, delay: Duration) {
fn editor<T: Default>(callback: impl FnOnce(&mut editor::application::Editor) -> T) -> T {
EDITOR.with(|editor| {
let mut guard = editor.try_lock();
let Ok(Some(editor)) = guard.as_deref_mut() else { return T::default() };
let Ok(Some(editor)) = guard.as_deref_mut() else {
log::error!("Failed to borrow editor");
return T::default();
};
callback(editor)
})
@ -922,19 +925,26 @@ fn editor<T: Default>(callback: impl FnOnce(&mut editor::application::Editor) ->
/// Provides access to the `Editor` and its `EditorHandle` by calling the given closure with them as arguments.
pub(crate) fn editor_and_handle(callback: impl FnOnce(&mut Editor, &mut EditorHandle)) {
EDITOR_HANDLE.with(|editor_handle| {
handle(|editor_handle| {
editor(|editor| {
let mut guard = editor_handle.try_lock();
let Ok(Some(editor_handle)) = guard.as_deref_mut() else {
log::error!("Failed to borrow editor handle");
return;
};
// Call the closure with the editor and its handle
callback(editor, editor_handle);
})
});
}
/// Provides access to the `EditorHandle` by calling the given closure with them as arguments.
pub(crate) fn handle(callback: impl FnOnce(&mut EditorHandle)) {
EDITOR_HANDLE.with(|editor_handle| {
let mut guard = editor_handle.try_lock();
let Ok(Some(editor_handle)) = guard.as_deref_mut() else {
log::error!("Failed to borrow editor handle");
return;
};
// Call the closure with the editor and its handle
callback(editor_handle);
});
}
async fn poll_node_graph_evaluation() {
// Process no further messages after a crash to avoid spamming the console

View file

@ -157,7 +157,7 @@ raster_node!(graphene_core::raster::OpacityNode<_>, params: [f64]),
There is also the more general `register_node!` for nodes that do not need to run per pixel.
```rs
register_node!(graphene_core::transform_nodes::SetTransformNode<_>, input: VectorData, params: [DAffine2]),
register_node!(graphene_core::transform_nodes::SetTransformNode<_>, input: Vector, params: [DAffine2]),
```
## Debugging

View file

@ -130,7 +130,7 @@ where
pub async fn create_brush_texture(brush_style: &BrushStyle) -> Raster<CPU> {
let stamp = brush_stamp_generator(brush_style.diameter, brush_style.color, brush_style.hardness, brush_style.flow);
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(brush_style.diameter), 0., -DVec2::splat(brush_style.diameter / 2.));
let blank_texture = empty_image((), transform, Color::TRANSPARENT).iter().next().unwrap_or_default();
let blank_texture = empty_image((), transform, Color::TRANSPARENT).into_iter().next().unwrap_or_default();
let image = blend_stamp_closure(stamp, blank_texture, |a, b| blend_colors(a, b, BlendMode::Normal, 1.));
image.element
@ -184,7 +184,7 @@ async fn brush(_: impl Ctx, mut image_frame_table: Table<Raster<CPU>>, strokes:
image_frame_table.push(TableRow::default());
}
// TODO: Find a way to handle more than one row
let table_row = image_frame_table.iter_ref().next().expect("Expected the one row we just pushed").into_cloned();
let table_row = image_frame_table.iter().next().expect("Expected the one row we just pushed").into_cloned();
let [start, end] = Table::new_from_row(table_row.clone()).bounding_box(DAffine2::IDENTITY, false).unwrap_or([DVec2::ZERO, DVec2::ZERO]);
let image_bbox = AxisAlignedBbox { start, end };
@ -198,7 +198,7 @@ async fn brush(_: impl Ctx, mut image_frame_table: Table<Raster<CPU>>, strokes:
let mut brush_plan = cache.compute_brush_plan(table_row, &draw_strokes);
// TODO: Find a way to handle more than one row
let Some(mut actual_image) = extend_image_to_bounds((), Table::new_from_row(brush_plan.background), background_bounds).iter().next() else {
let Some(mut actual_image) = extend_image_to_bounds((), Table::new_from_row(brush_plan.background), background_bounds).into_iter().next() else {
return Table::new();
};
@ -246,7 +246,7 @@ async fn brush(_: impl Ctx, mut image_frame_table: Table<Raster<CPU>>, strokes:
let table = blit_node.eval(blit_target).await;
assert_eq!(table.len(), 1);
table.iter().next().unwrap_or_default()
table.into_iter().next().unwrap_or_default()
};
// Cache image before doing final blend, and store final stroke texture.
@ -285,7 +285,7 @@ async fn brush(_: impl Ctx, mut image_frame_table: Table<Raster<CPU>>, strokes:
FutureWrapperNode::new(ClonedNode::new(positions)),
FutureWrapperNode::new(ClonedNode::new(blend_params)),
);
erase_restore_mask = blit_node.eval(Table::new_from_row(erase_restore_mask)).await.iter().next().unwrap_or_default();
erase_restore_mask = blit_node.eval(Table::new_from_row(erase_restore_mask)).await.into_iter().next().unwrap_or_default();
}
// Yes, this is essentially the same as the above, but we duplicate to inline the blend mode.
BlendMode::Restore => {
@ -295,7 +295,7 @@ async fn brush(_: impl Ctx, mut image_frame_table: Table<Raster<CPU>>, strokes:
FutureWrapperNode::new(ClonedNode::new(positions)),
FutureWrapperNode::new(ClonedNode::new(blend_params)),
);
erase_restore_mask = blit_node.eval(Table::new_from_row(erase_restore_mask)).await.iter().next().unwrap_or_default();
erase_restore_mask = blit_node.eval(Table::new_from_row(erase_restore_mask)).await.into_iter().next().unwrap_or_default();
}
_ => unreachable!(),
}
@ -406,6 +406,6 @@ mod test {
BrushCache::default(),
)
.await;
assert_eq!(image.iter_ref().next().unwrap().element.width, 20);
assert_eq!(image.iter().next().unwrap().element.width, 20);
}
}

View file

@ -5,7 +5,7 @@ use crate::raster_types::{CPU, GPU, Raster};
use crate::table::{Table, TableRow};
use crate::transform::TransformMut;
use crate::uuid::NodeId;
use crate::vector::VectorData;
use crate::vector::Vector;
use crate::{CloneVarArgs, Color, Context, Ctx, ExtractAll, Graphic, OwnedContextImpl};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2, IVec2};
@ -14,7 +14,7 @@ use std::hash::Hash;
/// Some [`ArtboardData`] with some optional clipping bounds that can be exported.
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub struct Artboard {
pub graphic_group: Table<Graphic>,
pub group: Table<Graphic>,
pub label: String,
pub location: IVec2,
pub dimensions: IVec2,
@ -31,7 +31,7 @@ impl Default for Artboard {
impl Artboard {
pub fn new(location: IVec2, dimensions: IVec2) -> Self {
Self {
graphic_group: Table::new(),
group: Table::new(),
label: "Artboard".to_string(),
location: location.min(location + dimensions),
dimensions: dimensions.abs(),
@ -47,7 +47,7 @@ impl BoundingBox for Artboard {
if self.clip {
Some(artboard_bounds)
} else {
[self.graphic_group.bounding_box(transform, include_stroke), Some(artboard_bounds)]
[self.group.bounding_box(transform, include_stroke), Some(artboard_bounds)]
.into_iter()
.flatten()
.reduce(Quad::combine_bounds)
@ -68,7 +68,7 @@ pub fn migrate_artboard_group<'de, D: serde::Deserializer<'de>>(deserializer: D)
#[serde(untagged)]
enum EitherFormat {
ArtboardGroup(ArtboardGroup),
ArtboardGroupTable(Table<Artboard>),
ArtboardTable(Table<Artboard>),
}
Ok(match EitherFormat::deserialize(deserializer)? {
@ -84,13 +84,13 @@ pub fn migrate_artboard_group<'de, D: serde::Deserializer<'de>>(deserializer: D)
}
table
}
EitherFormat::ArtboardGroupTable(artboard_group_table) => artboard_group_table,
EitherFormat::ArtboardTable(artboard_group_table) => artboard_group_table,
})
}
impl BoundingBox for Table<Artboard> {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
self.iter_ref().filter_map(|row| row.element.bounding_box(transform, include_stroke)).reduce(Quad::combine_bounds)
self.iter().filter_map(|row| row.element.bounding_box(transform, include_stroke)).reduce(Quad::combine_bounds)
}
}
@ -99,7 +99,7 @@ async fn to_artboard<Data: Into<Table<Graphic>> + 'n>(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[implementations(
Context -> Table<Graphic>,
Context -> Table<VectorData>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
Context -> DAffine2,
@ -112,7 +112,6 @@ async fn to_artboard<Data: Into<Table<Graphic>> + 'n>(
clip: bool,
) -> Artboard {
let location = location.as_ivec2();
let dimensions = dimensions.as_ivec2().max(IVec2::ONE);
let footprint = ctx.try_footprint().copied();
let mut new_ctx = OwnedContextImpl::from(ctx);
@ -120,13 +119,19 @@ async fn to_artboard<Data: Into<Table<Graphic>> + 'n>(
footprint.translate(location.as_dvec2());
new_ctx = new_ctx.with_footprint(footprint);
}
let graphic_group = contents.eval(new_ctx.into_context()).await;
let group = contents.eval(new_ctx.into_context()).await.into();
let dimensions = dimensions.as_ivec2().max(IVec2::ONE);
let location = location.min(location + dimensions);
let dimensions = dimensions.abs();
Artboard {
graphic_group: graphic_group.into(),
group,
label,
location: location.min(location + dimensions),
dimensions: dimensions.abs(),
location,
dimensions,
background,
clip,
}

View file

@ -1,7 +1,7 @@
use crate::raster_types::{CPU, Raster};
use crate::registry::types::Percentage;
use crate::table::Table;
use crate::vector::VectorData;
use crate::vector::Vector;
use crate::{BlendMode, Color, Ctx, Graphic};
pub(super) trait MultiplyAlpha {
@ -13,7 +13,7 @@ impl MultiplyAlpha for Color {
*self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.))
}
}
impl MultiplyAlpha for Table<VectorData> {
impl MultiplyAlpha for Table<Vector> {
fn multiply_alpha(&mut self, factor: f64) {
for row in self.iter_mut() {
row.alpha_blending.opacity *= factor as f32;
@ -43,7 +43,7 @@ impl MultiplyFill for Color {
*self = Color::from_rgbaf32_unchecked(self.r(), self.g(), self.b(), (self.a() * factor as f32).clamp(0., 1.))
}
}
impl MultiplyFill for Table<VectorData> {
impl MultiplyFill for Table<Vector> {
fn multiply_fill(&mut self, factor: f64) {
for row in self.iter_mut() {
row.alpha_blending.fill *= factor as f32;
@ -69,7 +69,7 @@ trait SetBlendMode {
fn set_blend_mode(&mut self, blend_mode: BlendMode);
}
impl SetBlendMode for Table<VectorData> {
impl SetBlendMode for Table<Vector> {
fn set_blend_mode(&mut self, blend_mode: BlendMode) {
for row in self.iter_mut() {
row.alpha_blending.blend_mode = blend_mode;
@ -95,7 +95,7 @@ trait SetClip {
fn set_clip(&mut self, clip: bool);
}
impl SetClip for Table<VectorData> {
impl SetClip for Table<Vector> {
fn set_clip(&mut self, clip: bool) {
for row in self.iter_mut() {
row.alpha_blending.clip = clip;
@ -122,7 +122,7 @@ fn blend_mode<T: SetBlendMode>(
_: impl Ctx,
#[implementations(
Table<Graphic>,
Table<VectorData>,
Table<Vector>,
Table<Raster<CPU>>,
)]
mut value: T,
@ -138,7 +138,7 @@ fn opacity<T: MultiplyAlpha>(
_: impl Ctx,
#[implementations(
Table<Graphic>,
Table<VectorData>,
Table<Vector>,
Table<Raster<CPU>>,
)]
mut value: T,
@ -154,7 +154,7 @@ fn blending<T: SetBlendMode + MultiplyAlpha + MultiplyFill + SetClip>(
_: impl Ctx,
#[implementations(
Table<Graphic>,
Table<VectorData>,
Table<Vector>,
Table<Raster<CPU>>,
)]
mut value: T,

View file

@ -1,11 +1,11 @@
use crate::raster_types::{CPU, Raster};
use crate::table::Table;
use crate::vector::VectorData;
use crate::vector::Vector;
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, Table<VectorData>, DAffine2, Color, Option<Color>)] value: T) -> T {
fn log_to_console<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, Table<Vector>, DAffine2, Color, Option<Color>)] value: T) -> T {
// KEEP THIS `debug!()` - It acts as the output for the debug node itself
log::debug!("{:#?}", value);
value

View file

@ -4,7 +4,7 @@ use crate::math::quad::Quad;
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::{Table, TableRow};
use crate::uuid::NodeId;
use crate::vector::VectorData;
use crate::vector::Vector;
use crate::{Color, Ctx};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
@ -13,90 +13,88 @@ use std::hash::Hash;
/// The possible forms of graphical content that can be rendered by the Render node into either an image or SVG syntax.
#[derive(Clone, Debug, Hash, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub enum Graphic {
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
GraphicGroup(Table<Graphic>),
/// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
VectorData(Table<VectorData>),
RasterDataCPU(Table<Raster<CPU>>),
RasterDataGPU(Table<Raster<GPU>>),
Group(Table<Graphic>),
Vector(Table<Vector>),
RasterCPU(Table<Raster<CPU>>),
RasterGPU(Table<Raster<GPU>>),
}
impl Default for Graphic {
fn default() -> Self {
Self::GraphicGroup(Default::default())
Self::Group(Default::default())
}
}
// GraphicGroup
// Group
impl From<Table<Graphic>> for Graphic {
fn from(graphic_group: Table<Graphic>) -> Self {
Graphic::GraphicGroup(graphic_group)
fn from(group: Table<Graphic>) -> Self {
Graphic::Group(group)
}
}
// VectorData
impl From<VectorData> for Graphic {
fn from(vector_data: VectorData) -> Self {
Graphic::VectorData(Table::new_from_element(vector_data))
// Vector
impl From<Vector> for Graphic {
fn from(vector: Vector) -> Self {
Graphic::Vector(Table::new_from_element(vector))
}
}
impl From<Table<VectorData>> for Graphic {
fn from(vector_data: Table<VectorData>) -> Self {
Graphic::VectorData(vector_data)
impl From<Table<Vector>> for Graphic {
fn from(vector: Table<Vector>) -> Self {
Graphic::Vector(vector)
}
}
impl From<VectorData> for Table<Graphic> {
fn from(vector_data: VectorData) -> Self {
Table::new_from_element(Graphic::VectorData(Table::new_from_element(vector_data)))
impl From<Vector> for Table<Graphic> {
fn from(vector: Vector) -> Self {
Table::new_from_element(Graphic::Vector(Table::new_from_element(vector)))
}
}
impl From<Table<VectorData>> for Table<Graphic> {
fn from(vector_data: Table<VectorData>) -> Self {
Table::new_from_element(Graphic::VectorData(vector_data))
impl From<Table<Vector>> for Table<Graphic> {
fn from(vector: Table<Vector>) -> Self {
Table::new_from_element(Graphic::Vector(vector))
}
}
// Raster<CPU>
impl From<Raster<CPU>> for Graphic {
fn from(raster_data: Raster<CPU>) -> Self {
Graphic::RasterDataCPU(Table::new_from_element(raster_data))
fn from(raster: Raster<CPU>) -> Self {
Graphic::RasterCPU(Table::new_from_element(raster))
}
}
impl From<Table<Raster<CPU>>> for Graphic {
fn from(raster_data: Table<Raster<CPU>>) -> Self {
Graphic::RasterDataCPU(raster_data)
fn from(raster: Table<Raster<CPU>>) -> Self {
Graphic::RasterCPU(raster)
}
}
impl From<Raster<CPU>> for Table<Graphic> {
fn from(raster_data: Raster<CPU>) -> Self {
Table::new_from_element(Graphic::RasterDataCPU(Table::new_from_element(raster_data)))
fn from(raster: Raster<CPU>) -> Self {
Table::new_from_element(Graphic::RasterCPU(Table::new_from_element(raster)))
}
}
impl From<Table<Raster<CPU>>> for Table<Graphic> {
fn from(raster_data_table: Table<Raster<CPU>>) -> Self {
Table::new_from_element(Graphic::RasterDataCPU(raster_data_table))
fn from(raster: Table<Raster<CPU>>) -> Self {
Table::new_from_element(Graphic::RasterCPU(raster))
}
}
// Raster<GPU>
impl From<Raster<GPU>> for Graphic {
fn from(raster_data: Raster<GPU>) -> Self {
Graphic::RasterDataGPU(Table::new_from_element(raster_data))
fn from(raster: Raster<GPU>) -> Self {
Graphic::RasterGPU(Table::new_from_element(raster))
}
}
impl From<Table<Raster<GPU>>> for Graphic {
fn from(raster_data: Table<Raster<GPU>>) -> Self {
Graphic::RasterDataGPU(raster_data)
fn from(raster: Table<Raster<GPU>>) -> Self {
Graphic::RasterGPU(raster)
}
}
impl From<Raster<GPU>> for Table<Graphic> {
fn from(raster_data: Raster<GPU>) -> Self {
Table::new_from_element(Graphic::RasterDataGPU(Table::new_from_element(raster_data)))
fn from(raster: Raster<GPU>) -> Self {
Table::new_from_element(Graphic::RasterGPU(Table::new_from_element(raster)))
}
}
impl From<Table<Raster<GPU>>> for Table<Graphic> {
fn from(raster_data_table: Table<Raster<GPU>>) -> Self {
Table::new_from_element(Graphic::RasterDataGPU(raster_data_table))
fn from(raster: Table<Raster<GPU>>) -> Self {
Table::new_from_element(Graphic::RasterGPU(raster))
}
}
@ -115,58 +113,58 @@ impl From<DAffine2> for Table<Graphic> {
impl Graphic {
pub fn as_group(&self) -> Option<&Table<Graphic>> {
match self {
Graphic::GraphicGroup(group) => Some(group),
Graphic::Group(group) => Some(group),
_ => None,
}
}
pub fn as_group_mut(&mut self) -> Option<&mut Table<Graphic>> {
match self {
Graphic::GraphicGroup(group) => Some(group),
Graphic::Group(group) => Some(group),
_ => None,
}
}
pub fn as_vector_data(&self) -> Option<&Table<VectorData>> {
pub fn as_vector(&self) -> Option<&Table<Vector>> {
match self {
Graphic::VectorData(data) => Some(data),
Graphic::Vector(vector) => Some(vector),
_ => None,
}
}
pub fn as_vector_data_mut(&mut self) -> Option<&mut Table<VectorData>> {
pub fn as_vector_mut(&mut self) -> Option<&mut Table<Vector>> {
match self {
Graphic::VectorData(data) => Some(data),
Graphic::Vector(vector) => Some(vector),
_ => None,
}
}
pub fn as_raster(&self) -> Option<&Table<Raster<CPU>>> {
match self {
Graphic::RasterDataCPU(raster) => Some(raster),
Graphic::RasterCPU(raster) => Some(raster),
_ => None,
}
}
pub fn as_raster_mut(&mut self) -> Option<&mut Table<Raster<CPU>>> {
match self {
Graphic::RasterDataCPU(raster) => Some(raster),
Graphic::RasterCPU(raster) => Some(raster),
_ => None,
}
}
pub fn had_clip_enabled(&self) -> bool {
match self {
Graphic::VectorData(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
Graphic::GraphicGroup(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
Graphic::RasterDataCPU(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
Graphic::RasterDataGPU(data) => data.iter_ref().all(|row| row.alpha_blending.clip),
Graphic::Vector(vector) => vector.iter().all(|row| row.alpha_blending.clip),
Graphic::Group(group) => group.iter().all(|row| row.alpha_blending.clip),
Graphic::RasterCPU(raster) => raster.iter().all(|row| row.alpha_blending.clip),
Graphic::RasterGPU(raster) => raster.iter().all(|row| row.alpha_blending.clip),
}
}
pub fn can_reduce_to_clip_path(&self) -> bool {
match self {
Graphic::VectorData(vector_data_table) => vector_data_table.iter_ref().all(|row| {
Graphic::Vector(vector) => vector.iter().all(|row| {
let style = &row.element.style;
let alpha_blending = &row.alpha_blending;
(alpha_blending.opacity > 1. - f32::EPSILON) && style.fill().is_opaque() && style.stroke().is_none_or(|stroke| !stroke.has_renderable_stroke())
@ -179,17 +177,17 @@ impl Graphic {
impl BoundingBox for Graphic {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
match self {
Graphic::VectorData(vector_data) => vector_data.bounding_box(transform, include_stroke),
Graphic::RasterDataCPU(raster) => raster.bounding_box(transform, include_stroke),
Graphic::RasterDataGPU(raster) => raster.bounding_box(transform, include_stroke),
Graphic::GraphicGroup(graphic_group) => graphic_group.bounding_box(transform, include_stroke),
Graphic::Vector(vector) => vector.bounding_box(transform, include_stroke),
Graphic::RasterCPU(raster) => raster.bounding_box(transform, include_stroke),
Graphic::RasterGPU(raster) => raster.bounding_box(transform, include_stroke),
Graphic::Group(group) => group.bounding_box(transform, include_stroke),
}
}
}
impl BoundingBox for Table<Graphic> {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
self.iter_ref()
self.iter()
.filter_map(|element| element.element.bounding_box(transform * *element.transform, include_stroke))
.reduce(Quad::combine_bounds)
}
@ -198,8 +196,8 @@ impl BoundingBox for Table<Graphic> {
#[node_macro::node(category(""))]
async fn layer<I: 'n + Send + Clone>(
_: impl Ctx,
#[implementations(Table<Graphic>, Table<VectorData>, Table<Raster<CPU>>, Table<Raster<GPU>>)] mut stack: Table<I>,
#[implementations(Graphic, VectorData, Raster<CPU>, Raster<GPU>)] element: I,
#[implementations(Table<Graphic>, Table<Vector>, Table<Raster<CPU>>, Table<Raster<GPU>>)] mut stack: Table<I>,
#[implementations(Graphic, Vector, Raster<CPU>, Raster<GPU>)] element: I,
node_path: Vec<NodeId>,
) -> Table<I> {
// Get the penultimate element of the node path, or None if the path is too short
@ -220,7 +218,7 @@ async fn to_element<Data: Into<Graphic> + 'n>(
_: impl Ctx,
#[implementations(
Table<Graphic>,
Table<VectorData>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Raster<GPU>>,
DAffine2,
@ -235,7 +233,7 @@ async fn to_group<Data: Into<Table<Graphic>> + 'n>(
_: impl Ctx,
#[implementations(
Table<Graphic>,
Table<VectorData>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Raster<GPU>>,
)]
@ -248,23 +246,23 @@ async fn to_group<Data: Into<Table<Graphic>> + 'n>(
async fn flatten_group(_: impl Ctx, group: Table<Graphic>, fully_flatten: bool) -> Table<Graphic> {
// TODO: Avoid mutable reference, instead return a new Table<Graphic>?
fn flatten_group(output_group_table: &mut Table<Graphic>, current_group_table: Table<Graphic>, fully_flatten: bool, recursion_depth: usize) {
for current_row in current_group_table.iter_ref() {
for current_row in current_group_table.iter() {
let current_element = current_row.element.clone();
let reference = *current_row.source_node_id;
let recurse = fully_flatten || recursion_depth == 0;
match current_element {
// If we're allowed to recurse, flatten any GraphicGroups we encounter
Graphic::GraphicGroup(mut current_element) if recurse => {
// If we're allowed to recurse, flatten any groups we encounter
Graphic::Group(mut current_element) if recurse => {
// Apply the parent group's transform to all child elements
for graphic_element in current_element.iter_mut() {
*graphic_element.transform = *current_row.transform * *graphic_element.transform;
for graphic in current_element.iter_mut() {
*graphic.transform = *current_row.transform * *graphic.transform;
}
flatten_group(output_group_table, current_element, fully_flatten, recursion_depth + 1);
}
// Handle any leaf elements we encounter, which can be either non-GraphicGroup elements or GraphicGroups that we don't want to flatten
// Handle any leaf elements we encounter, which can be either non-group elements or groups that we don't want to flatten
_ => {
output_group_table.push(TableRow {
element: current_element,
@ -284,36 +282,36 @@ async fn flatten_group(_: impl Ctx, group: Table<Graphic>, fully_flatten: bool)
}
#[node_macro::node(category("Vector"))]
async fn flatten_vector(_: impl Ctx, group: Table<Graphic>) -> Table<VectorData> {
async fn flatten_vector(_: impl Ctx, group: Table<Graphic>) -> Table<Vector> {
// TODO: Avoid mutable reference, instead return a new Table<Graphic>?
fn flatten_group(output_group_table: &mut Table<VectorData>, current_group_table: Table<Graphic>) {
for current_graphic_element_row in current_group_table.iter_ref() {
let current_element = current_graphic_element_row.element.clone();
let reference = *current_graphic_element_row.source_node_id;
fn flatten_group(output_group_table: &mut Table<Vector>, current_group_table: Table<Graphic>) {
for current_graphic_row in current_group_table.iter() {
let current_graphic = current_graphic_row.element.clone();
let source_node_id = *current_graphic_row.source_node_id;
match current_element {
// If we're allowed to recurse, flatten any GraphicGroups we encounter
Graphic::GraphicGroup(mut current_element) => {
match current_graphic {
// If we're allowed to recurse, flatten any groups we encounter
Graphic::Group(mut current_graphic_table) => {
// Apply the parent group's transform to all child elements
for graphic_element in current_element.iter_mut() {
*graphic_element.transform = *current_graphic_element_row.transform * *graphic_element.transform;
for graphic in current_graphic_table.iter_mut() {
*graphic.transform = *current_graphic_row.transform * *graphic.transform;
}
flatten_group(output_group_table, current_element);
flatten_group(output_group_table, current_graphic_table);
}
// Handle any leaf elements we encounter, which can be either non-GraphicGroup elements or GraphicGroups that we don't want to flatten
Graphic::VectorData(vector_table) => {
for current_vector_row in vector_table.iter_ref() {
// Handle any leaf elements we encounter, which can be either non-group elements or groups that we don't want to flatten
Graphic::Vector(vector_table) => {
for current_vector_row in vector_table.iter() {
output_group_table.push(TableRow {
element: current_vector_row.element.clone(),
transform: *current_graphic_element_row.transform * *current_vector_row.transform,
transform: *current_graphic_row.transform * *current_vector_row.transform,
alpha_blending: AlphaBlending {
blend_mode: current_vector_row.alpha_blending.blend_mode,
opacity: current_graphic_element_row.alpha_blending.opacity * current_vector_row.alpha_blending.opacity,
opacity: current_graphic_row.alpha_blending.opacity * current_vector_row.alpha_blending.opacity,
fill: current_vector_row.alpha_blending.fill,
clip: current_vector_row.alpha_blending.clip,
},
source_node_id: reference,
source_node_id,
});
}
}
@ -339,7 +337,7 @@ fn index<T: AtIndex + Clone + Default>(
Vec<Option<Color>>,
Vec<f64>, Vec<u64>,
Vec<DVec2>,
Table<VectorData>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Graphic>,
)]
@ -369,7 +367,7 @@ impl<T: Clone> AtIndex for Table<T> {
fn at_index(&self, index: usize) -> Option<Self::Output> {
let mut result_table = Self::default();
if let Some(row) = self.iter_ref().nth(index) {
if let Some(row) = self.iter().nth(index) {
result_table.push(row.into_cloned());
Some(result_table)
} else {
@ -379,7 +377,7 @@ impl<T: Clone> AtIndex for Table<T> {
}
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Graphic>, D::Error> {
pub fn migrate_group<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Graphic>, D::Error> {
use serde::Deserialize;
#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]
@ -402,32 +400,32 @@ pub fn migrate_graphic_group<'de, D: serde::Deserializer<'de>>(deserializer: D)
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::OldGraphicGroup(old) => {
let mut graphic_group_table = Table::new();
for (graphic_element, source_node_id) in old.elements {
graphic_group_table.push(TableRow {
element: graphic_element,
let mut group_table = Table::new();
for (graphic, source_node_id) in old.elements {
group_table.push(TableRow {
element: graphic,
transform: old.transform,
alpha_blending: old.alpha_blending,
source_node_id,
});
}
graphic_group_table
group_table
}
EitherFormat::Table(value) => {
// Try to deserialize as either table format
if let Ok(old_table) = serde_json::from_value::<Table<GraphicGroup>>(value.clone()) {
let mut graphic_group_table = Table::new();
for row in old_table.iter_ref() {
for (graphic_element, source_node_id) in &row.element.elements {
graphic_group_table.push(TableRow {
element: graphic_element.clone(),
let mut group_table = Table::new();
for row in old_table.iter() {
for (graphic, source_node_id) in &row.element.elements {
group_table.push(TableRow {
element: graphic.clone(),
transform: *row.transform,
alpha_blending: *row.alpha_blending,
source_node_id: *source_node_id,
});
}
}
graphic_group_table
group_table
} else if let Ok(new_table) = serde_json::from_value::<Table<Graphic>>(value) {
new_table
} else {

View file

@ -11,7 +11,7 @@ pub mod debug;
pub mod extract_xy;
pub mod generic;
pub mod gradient;
pub mod graphic_element;
pub mod graphic;
pub mod logic;
pub mod math;
pub mod memo;
@ -41,7 +41,7 @@ pub use graphene_core_shaders::AsU32;
pub use graphene_core_shaders::blending;
pub use graphene_core_shaders::choice_type;
pub use graphene_core_shaders::color;
pub use graphic_element::Graphic;
pub use graphic::Graphic;
pub use memo::MemoHash;
pub use num_traits;
use std::any::TypeId;

View file

@ -5,19 +5,19 @@ use crate::gradient::GradientStops;
use crate::graphene_core::registry::types::TextArea;
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::Table;
use crate::vector::VectorData;
use crate::vector::Vector;
use crate::{Context, Ctx};
use glam::{DAffine2, DVec2};
#[node_macro::node(category("Text"))]
fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Table<VectorData>)] value: T) -> String {
fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Table<Vector>)] value: T) -> String {
format!("{:?}", value)
}
#[node_macro::node(category("Text"))]
fn serialize<T: serde::Serialize>(
_: impl Ctx,
#[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Color, Option<Color>, Table<Graphic>, Table<VectorData>, Table<Raster<CPU>>)] value: T,
#[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Color, Option<Color>, Table<Graphic>, Table<Vector>, Table<Raster<CPU>>)] value: T,
) -> String {
serde_json::to_string(&value).unwrap_or_else(|_| "Serialization Error".to_string())
}
@ -60,7 +60,7 @@ async fn switch<T, C: Send + 'n + Clone>(
Context -> DVec2,
Context -> DAffine2,
Context -> Table<Artboard>,
Context -> Table<VectorData>,
Context -> Table<Vector>,
Context -> Table<Graphic>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
@ -81,7 +81,7 @@ async fn switch<T, C: Send + 'n + Clone>(
Context -> DVec2,
Context -> DAffine2,
Context -> Table<Artboard>,
Context -> Table<VectorData>,
Context -> Table<Vector>,
Context -> Table<Graphic>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,

View file

@ -3,7 +3,7 @@ use crate::AlphaBlending;
use crate::color::float_to_srgb_u8;
use crate::raster_types::Raster;
use crate::table::{Table, TableRow};
use crate::vector::VectorData;
use crate::vector::Vector;
use core::hash::{Hash, Hasher};
use dyn_any::{DynAny, StaticType};
use glam::{DAffine2, DVec2};
@ -239,7 +239,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
GraphicGroup(Table<GraphicElement>),
/// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
VectorData(Table<VectorData>),
VectorData(Table<Vector>),
RasterFrame(RasterFrame),
}
@ -256,7 +256,7 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
fn from(element: GraphicElement) -> Self {
match element {
GraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self {
image: image.iter_ref().next().unwrap().element.clone(),
image: image.iter().next().unwrap().element.clone(),
},
_ => panic!("Expected Image, found {:?}", element),
}
@ -283,31 +283,30 @@ pub fn migrate_image_frame<'de, D: serde::Deserializer<'de>>(deserializer: D) ->
enum FormatVersions {
Image(Image<Color>),
OldImageFrame(OldImageFrame<Color>),
ImageFrame(Table<ImageFrame<Color>>),
ImageFrameTable(Table<Image<Color>>),
RasterDataTable(Table<Raster<CPU>>),
ImageFrameTable(Table<ImageFrame<Color>>),
ImageTable(Table<Image<Color>>),
RasterTable(Table<Raster<CPU>>),
}
Ok(match FormatVersions::deserialize(deserializer)? {
FormatVersions::Image(image) => Table::new_from_element(Raster::new_cpu(image)),
FormatVersions::OldImageFrame(image_frame_with_transform_and_blending) => {
let OldImageFrame { image, transform, alpha_blending } = image_frame_with_transform_and_blending;
FormatVersions::OldImageFrame(OldImageFrame { image, transform, alpha_blending }) => {
let mut image_frame_table = Table::new_from_element(Raster::new_cpu(image));
*image_frame_table.iter_mut().next().unwrap().transform = transform;
*image_frame_table.iter_mut().next().unwrap().alpha_blending = alpha_blending;
image_frame_table
}
FormatVersions::ImageFrame(image_frame) => Table::new_from_element(Raster::new_cpu(
FormatVersions::ImageFrameTable(image_frame) => Table::new_from_element(Raster::new_cpu(
image_frame
.iter_ref()
.iter()
.next()
.unwrap_or(Table::new_from_element(ImageFrame::default()).iter_ref().next().unwrap())
.unwrap_or(Table::new_from_element(ImageFrame::default()).iter().next().unwrap())
.element
.image
.clone(),
)),
FormatVersions::ImageFrameTable(image_frame_table) => Table::new_from_element(Raster::new_cpu(image_frame_table.iter_ref().next().unwrap().element.clone())),
FormatVersions::RasterDataTable(raster_data_table) => raster_data_table,
FormatVersions::ImageTable(table) => Table::new_from_element(Raster::new_cpu(table.iter().next().unwrap().element.clone())),
FormatVersions::RasterTable(table) => table,
})
}
@ -338,7 +337,7 @@ pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D
/// Equivalent to the SVG <g> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/g
GraphicGroup(Table<GraphicElement>),
/// A vector shape, equivalent to the SVG <path> tag: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/path
VectorData(Table<VectorData>),
VectorData(Table<Vector>),
RasterFrame(RasterFrame),
}
@ -355,7 +354,7 @@ pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D
fn from(element: GraphicElement) -> Self {
match element {
GraphicElement::RasterFrame(RasterFrame::ImageFrame(image)) => Self {
image: image.iter_ref().next().unwrap().element.clone(),
image: image.iter().next().unwrap().element.clone(),
},
_ => panic!("Expected Image, found {:?}", element),
}
@ -382,9 +381,9 @@ pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D
enum FormatVersions {
Image(Image<Color>),
OldImageFrame(OldImageFrame<Color>),
ImageFrame(Table<ImageFrame<Color>>),
RasterDataTable(Table<Raster<CPU>>),
ImageTableRow(TableRow<Raster<CPU>>),
ImageFrameTable(Table<ImageFrame<Color>>),
RasterTable(Table<Raster<CPU>>),
RasterTableRow(TableRow<Raster<CPU>>),
}
Ok(match FormatVersions::deserialize(deserializer)? {
@ -398,12 +397,12 @@ pub fn migrate_image_frame_row<'de, D: serde::Deserializer<'de>>(deserializer: D
alpha_blending: image_frame_with_transform_and_blending.alpha_blending,
source_node_id: None,
},
FormatVersions::ImageFrame(image_frame) => TableRow {
element: Raster::new_cpu(image_frame.iter_ref().next().unwrap().element.image.clone()),
FormatVersions::ImageFrameTable(image_frame) => TableRow {
element: Raster::new_cpu(image_frame.iter().next().unwrap().element.image.clone()),
..Default::default()
},
FormatVersions::RasterDataTable(image_frame_table) => image_frame_table.iter().next().unwrap_or_default(),
FormatVersions::ImageTableRow(image_table_row) => image_table_row,
FormatVersions::RasterTable(image_frame_table) => image_frame_table.into_iter().next().unwrap_or_default(),
FormatVersions::RasterTableRow(image_table_row) => image_table_row,
})
}

View file

@ -204,7 +204,7 @@ where
Raster<T>: Storage,
{
fn bounding_box(&self, transform: DAffine2, _include_stroke: bool) -> Option<[DVec2; 2]> {
self.iter_ref()
self.iter()
.filter(|row| !row.element.is_empty()) // Eliminate empty images
.flat_map(|row| {
let transform = transform * *row.transform;

View file

@ -1,6 +1,6 @@
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::Table;
use crate::vector::VectorData;
use crate::vector::Vector;
use crate::{Artboard, Color, Graphic};
use glam::DVec2;
@ -12,28 +12,28 @@ pub trait RenderComplexity {
impl<T: RenderComplexity> RenderComplexity for Table<T> {
fn render_complexity(&self) -> usize {
self.iter_ref().map(|row| row.element.render_complexity()).fold(0, usize::saturating_add)
self.iter().map(|row| row.element.render_complexity()).fold(0, usize::saturating_add)
}
}
impl RenderComplexity for Artboard {
fn render_complexity(&self) -> usize {
self.graphic_group.render_complexity()
self.group.render_complexity()
}
}
impl RenderComplexity for Graphic {
fn render_complexity(&self) -> usize {
match self {
Self::GraphicGroup(table) => table.render_complexity(),
Self::VectorData(table) => table.render_complexity(),
Self::RasterDataCPU(table) => table.render_complexity(),
Self::RasterDataGPU(table) => table.render_complexity(),
Self::Group(table) => table.render_complexity(),
Self::Vector(table) => table.render_complexity(),
Self::RasterCPU(table) => table.render_complexity(),
Self::RasterGPU(table) => table.render_complexity(),
}
}
}
impl RenderComplexity for VectorData {
impl RenderComplexity for Vector {
fn render_complexity(&self) -> usize {
self.segment_domain.ids().len()
}

View file

@ -60,48 +60,6 @@ impl<T> Table<T> {
self.source_node_id.extend(table.source_node_id);
}
pub fn iter(self) -> impl DoubleEndedIterator<Item = TableRow<T>> {
self.element
.into_iter()
.zip(self.transform)
.zip(self.alpha_blending)
.zip(self.source_node_id)
.map(|(((element, transform), alpha_blending), source_node_id)| TableRow {
element,
transform,
alpha_blending,
source_node_id,
})
}
pub fn iter_ref(&self) -> impl DoubleEndedIterator<Item = TableRowRef<'_, T>> + Clone {
self.element
.iter()
.zip(self.transform.iter())
.zip(self.alpha_blending.iter())
.zip(self.source_node_id.iter())
.map(|(((element, transform), alpha_blending), source_node_id)| TableRowRef {
element,
transform,
alpha_blending,
source_node_id,
})
}
pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = TableRowMut<'_, T>> {
self.element
.iter_mut()
.zip(self.transform.iter_mut())
.zip(self.alpha_blending.iter_mut())
.zip(self.source_node_id.iter_mut())
.map(|(((element, transform), alpha_blending), source_node_id)| TableRowMut {
element,
transform,
alpha_blending,
source_node_id,
})
}
pub fn get(&self, index: usize) -> Option<TableRowRef<'_, T>> {
if index >= self.element.len() {
return None;
@ -135,6 +93,75 @@ impl<T> Table<T> {
pub fn is_empty(&self) -> bool {
self.element.is_empty()
}
/// Borrows a [`Table`] and returns an iterator of [`TableRowRef`]s, each containing references to the data of the respective row from the table.
pub fn iter(&self) -> impl DoubleEndedIterator<Item = TableRowRef<'_, T>> + Clone {
self.element
.iter()
.zip(self.transform.iter())
.zip(self.alpha_blending.iter())
.zip(self.source_node_id.iter())
.map(|(((element, transform), alpha_blending), source_node_id)| TableRowRef {
element,
transform,
alpha_blending,
source_node_id,
})
}
/// Mutably borrows a [`Table`] and returns an iterator of [`TableRowMut`]s, each containing mutable references to the data of the respective row from the table.
pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = TableRowMut<'_, T>> {
self.element
.iter_mut()
.zip(self.transform.iter_mut())
.zip(self.alpha_blending.iter_mut())
.zip(self.source_node_id.iter_mut())
.map(|(((element, transform), alpha_blending), source_node_id)| TableRowMut {
element,
transform,
alpha_blending,
source_node_id,
})
}
}
impl<T> IntoIterator for Table<T> {
type Item = TableRow<T>;
type IntoIter = TableRowIter<T>;
/// Consumes a [`Table`] and returns an iterator of [`TableRow`]s, each containing the owned data of the respective row from the original table.
fn into_iter(self) -> Self::IntoIter {
TableRowIter {
element: self.element.into_iter(),
transform: self.transform.into_iter(),
alpha_blending: self.alpha_blending.into_iter(),
source_node_id: self.source_node_id.into_iter(),
}
}
}
pub struct TableRowIter<T> {
element: std::vec::IntoIter<T>,
transform: std::vec::IntoIter<DAffine2>,
alpha_blending: std::vec::IntoIter<AlphaBlending>,
source_node_id: std::vec::IntoIter<Option<NodeId>>,
}
impl<T> Iterator for TableRowIter<T> {
type Item = TableRow<T>;
fn next(&mut self) -> Option<Self::Item> {
let element = self.element.next()?;
let transform = self.transform.next()?;
let alpha_blending = self.alpha_blending.next()?;
let source_node_id = self.source_node_id.next()?;
Some(TableRow {
element,
transform,
alpha_blending,
source_node_id,
})
}
}
impl<T> Default for Table<T> {

View file

@ -1,6 +1,6 @@
use super::TextAlign;
use crate::table::{Table, TableRow};
use crate::vector::{PointId, VectorData};
use crate::vector::{PointId, Vector};
use bezier_rs::{ManipulatorGroup, Subpath};
use core::cell::RefCell;
use glam::{DAffine2, DVec2};
@ -24,7 +24,7 @@ struct PathBuilder {
current_subpath: Subpath<PointId>,
origin: DVec2,
glyph_subpaths: Vec<Subpath<PointId>>,
vector_table: Table<VectorData>,
vector_table: Table<Vector>,
scale: f64,
id: PointId,
}
@ -52,13 +52,13 @@ impl PathBuilder {
if per_glyph_instances {
self.vector_table.push(TableRow {
element: VectorData::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false),
element: Vector::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false),
transform: DAffine2::from_translation(glyph_offset),
..Default::default()
});
} else {
for subpath in self.glyph_subpaths.drain(..) {
// Unwrapping here is ok because `self.vector_table` is initialized with a single `VectorData`
// Unwrapping here is ok because `self.vector_table` is initialized with a single `Vector` table element
self.vector_table.get_mut(0).unwrap().element.append_subpath(subpath, false);
}
}
@ -205,15 +205,15 @@ fn layout_text(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingC
Some(layout)
}
pub fn to_path(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<VectorData> {
pub fn to_path(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingConfig, per_glyph_instances: bool) -> Table<Vector> {
let Some(layout) = layout_text(str, font_data, typesetting) else {
return Table::new_from_element(VectorData::default());
return Table::new_from_element(Vector::default());
};
let mut path_builder = PathBuilder {
current_subpath: Subpath::new(Vec::new(), false),
glyph_subpaths: Vec::new(),
vector_table: if per_glyph_instances { Table::new() } else { Table::new_from_element(VectorData::default()) },
vector_table: if per_glyph_instances { Table::new() } else { Table::new_from_element(Vector::default()) },
scale: layout.scale() as f64,
id: PointId::ZERO,
origin: DVec2::default(),
@ -228,7 +228,7 @@ pub fn to_path(str: &str, font_data: Option<Blob<u8>>, typesetting: TypesettingC
}
if path_builder.vector_table.is_empty() {
path_builder.vector_table = Table::new_from_element(VectorData::default());
path_builder.vector_table = Table::new_from_element(Vector::default());
}
path_builder.vector_table

View file

@ -1,7 +1,7 @@
use crate::raster_types::{CPU, GPU, Raster};
use crate::table::Table;
use crate::transform::{ApplyTransform, Footprint, Transform};
use crate::vector::VectorData;
use crate::vector::Vector;
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, Graphic, OwnedContextImpl};
use core::f64;
use glam::{DAffine2, DVec2};
@ -12,7 +12,7 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
#[implementations(
Context -> DAffine2,
Context -> DVec2,
Context -> Table<VectorData>,
Context -> Table<Vector>,
Context -> Table<Graphic>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
@ -43,7 +43,7 @@ async fn transform<T: ApplyTransform + 'n + 'static>(
#[node_macro::node(category(""))]
fn replace_transform<Data, TransformInput: Transform>(
_: impl Ctx,
#[implementations(Table<VectorData>, Table<Raster<CPU>>, Table<Graphic>)] mut data: Table<Data>,
#[implementations(Table<Vector>, Table<Raster<CPU>>, Table<Graphic>)] mut data: Table<Data>,
#[implementations(DAffine2)] transform: TransformInput,
) -> Table<Data> {
for data_transform in data.iter_mut() {
@ -57,13 +57,13 @@ async fn extract_transform<T>(
_: impl Ctx,
#[implementations(
Table<Graphic>,
Table<VectorData>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Raster<GPU>>,
)]
vector_data: Table<T>,
vector: Table<T>,
) -> DAffine2 {
vector_data.iter_ref().next().map(|vector_data| *vector_data.transform).unwrap_or_default()
vector.iter().next().map(|row| *row.transform).unwrap_or_default()
}
#[node_macro::node(category("Math: Transform"))]
@ -90,7 +90,7 @@ fn decompose_scale(_: impl Ctx, transform: DAffine2) -> DVec2 {
async fn boundless_footprint<T: 'n + 'static>(
ctx: impl Ctx + CloneVarArgs + ExtractAll,
#[implementations(
Context -> Table<VectorData>,
Context -> Table<Vector>,
Context -> Table<Graphic>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,
@ -108,7 +108,7 @@ async fn boundless_footprint<T: 'n + 'static>(
async fn freeze_real_time<T: 'n + 'static>(
ctx: impl Ctx + CloneVarArgs + ExtractAll,
#[implementations(
Context -> Table<VectorData>,
Context -> Table<Vector>,
Context -> Table<Graphic>,
Context -> Table<Raster<CPU>>,
Context -> Table<Raster<GPU>>,

View file

@ -167,8 +167,7 @@ fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer:
let name = match name.as_str() {
"f32" => "f64".to_string(),
"graphene_core::transform::Footprint" => "std::option::Option<std::sync::Arc<graphene_core::context::OwnedContextImpl>>".to_string(),
"graphene_core::graphic_element::GraphicGroup" => "graphene_core::table::Table<graphene_core::graphic_element::GraphicGroup>".to_string(),
"graphene_core::vector::vector_data::VectorData" => "graphene_core::table::Table<graphene_core::vector::vector_data::VectorData>".to_string(),
"graphene_core::graphic_element::GraphicGroup" => "graphene_core::table::Table<graphene_core::graphic::Graphic>".to_string(),
"graphene_core::raster::image::ImageFrame<Color>"
| "graphene_core::raster::image::ImageFrame<graphene_core::raster::color::Color>"
| "graphene_core::instances::Instances<graphene_core::raster::image::ImageFrame<Color>>"
@ -176,8 +175,13 @@ fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer:
| "graphene_core::instances::Instances<graphene_core::raster::image::Image<graphene_core::raster::color::Color>>" => {
"graphene_core::table::Table<graphene_core::raster::image::Image<graphene_core::raster::color::Color>>".to_string()
}
"graphene_core::instances::Instances<graphene_core::vector::vector_data::VectorData>" => "graphene_core::table::Table<graphene_core::vector::vector_data::VectorData>".to_string(),
"graphene_core::vector::vector_data::VectorData"
| "graphene_core::instances::Instances<graphene_core::vector::vector_data::VectorData>"
| "graphene_core::table::Table<graphene_core::vector::vector_data::VectorData>"
| "graphene_core::table::Table<graphene_core::vector::vector_data::Vector>" => "graphene_core::table::Table<graphene_core::vector::vector_types::Vector>".to_string(),
"graphene_core::instances::Instances<graphene_core::graphic_element::Artboard>" => "graphene_core::table::Table<graphene_core::artboard::Artboard>".to_string(),
"graphene_core::vector::vector_data::modification::VectorModification" => "graphene_core::vector::vector_modification::VectorModification".to_string(),
"graphene_core::table::Table<graphene_core::graphic_element::Graphic>" => "graphene_core::table::Table<graphene_core::graphic::Graphic>".to_string(),
_ => name,
};

View file

@ -1,16 +1,16 @@
use crate::raster_types::{CPU, Raster};
use crate::table::{Table, TableRowRef};
use crate::vector::VectorData;
use crate::vector::Vector;
use crate::{CloneVarArgs, Context, Ctx, ExtractAll, ExtractIndex, ExtractVarArgs, Graphic, OwnedContextImpl};
use glam::DVec2;
#[node_macro::node(name("Instance on Points"), category("Instancing"), path(graphene_core::vector))]
async fn instance_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Sync + Ctx,
points: Table<VectorData>,
points: Table<Vector>,
#[implementations(
Context -> Table<Graphic>,
Context -> Table<VectorData>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>
)]
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
@ -18,14 +18,14 @@ async fn instance_on_points<T: Into<Graphic> + Default + Send + Clone + 'static>
) -> Table<T> {
let mut result_table = Table::new();
for TableRowRef { element: points, transform, .. } in points.iter_ref() {
for TableRowRef { element: points, transform, .. } in points.iter() {
let mut iteration = async |index, point| {
let transformed_point = transform.transform_point2(point);
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index).with_vararg(Box::new(transformed_point));
let generated_instance = instance.eval(new_ctx.into_context()).await;
for mut generated_row in generated_instance.iter() {
for mut generated_row in generated_instance.into_iter() {
generated_row.transform.translation = transformed_point;
result_table.push(generated_row);
}
@ -51,7 +51,7 @@ async fn instance_repeat<T: Into<Graphic> + Default + Send + Clone + 'static>(
ctx: impl ExtractAll + CloneVarArgs + Ctx,
#[implementations(
Context -> Table<Graphic>,
Context -> Table<VectorData>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>
)]
instance: impl Node<'n, Context<'static>, Output = Table<T>>,
@ -68,7 +68,7 @@ async fn instance_repeat<T: Into<Graphic> + Default + Send + Clone + 'static>(
let new_ctx = OwnedContextImpl::from(ctx.clone()).with_index(index);
let generated_instance = instance.eval(new_ctx.into_context()).await;
for generated_row in generated_instance.iter() {
for generated_row in generated_instance.into_iter() {
result_table.push(generated_row);
}
}
@ -99,7 +99,7 @@ mod test {
use super::*;
use crate::Node;
use crate::extract_xy::{ExtractXyNode, XY};
use crate::vector::VectorData;
use crate::vector::Vector;
use bezier_rs::Subpath;
use glam::DVec2;
use std::pin::Pin;
@ -128,10 +128,10 @@ mod test {
);
let positions = [DVec2::new(40., 20.), DVec2::ONE, DVec2::new(-42., 9.), DVec2::new(10., 345.)];
let points = Table::new_from_element(VectorData::from_subpath(Subpath::from_anchors_linear(positions, false)));
let points = Table::new_from_element(Vector::from_subpath(Subpath::from_anchors_linear(positions, false)));
let generated = super::instance_on_points(owned, points, &rect, false).await;
assert_eq!(generated.len(), positions.len());
for (position, generated_row) in positions.into_iter().zip(generated.iter_ref()) {
for (position, generated_row) in positions.into_iter().zip(generated.iter()) {
let bounds = generated_row.element.bounding_box_with_transform(*generated_row.transform).unwrap();
assert!(position.abs_diff_eq((bounds[0] + bounds[1]) / 2., 1e-10));
assert_eq!((bounds[1] - bounds[0]).x, position.y);

View file

@ -1,6 +1,8 @@
use crate::vector::{PointDomain, PointId, SegmentDomain, VectorData, VectorDataIndex};
use crate::vector::{PointDomain, PointId, SegmentDomain, SegmentId, Vector};
use glam::{DAffine2, DVec2};
use petgraph::graph::{EdgeIndex, NodeIndex, UnGraph};
use petgraph::prelude::UnGraphMap;
use rustc_hash::FxHashMap;
use rustc_hash::FxHashSet;
pub trait MergeByDistanceExt {
@ -9,10 +11,10 @@ pub trait MergeByDistanceExt {
fn merge_by_distance_spatial(&mut self, transform: DAffine2, distance: f64);
}
impl MergeByDistanceExt for VectorData {
impl MergeByDistanceExt for Vector {
fn merge_by_distance_topological(&mut self, distance: f64) {
// Treat self as an undirected graph
let indices = VectorDataIndex::build_from(self);
let indices = VectorIndex::build_from(self);
// TODO: We lose information on the winding order by using an undirected graph. Switch to a directed graph and fix the algorithm to handle that.
// Graph containing only short edges, referencing the data graph
@ -207,8 +209,94 @@ impl MergeByDistanceExt for VectorData {
}
}
// Create new vector data
// Create new vector geometry
self.point_domain = new_point_domain;
self.segment_domain = new_segment_domain;
}
}
/// All the fixed fields of a point from the point domain.
pub(crate) struct Point {
pub id: PointId,
pub position: DVec2,
}
/// Useful indexes to speed up various operations on [`Vector`].
///
/// Important: It is the user's responsibility to ensure the indexes remain valid after mutations to the data.
pub struct VectorIndex {
/// Points and segments form a graph. Store it here in a form amenable to graph algorithms.
///
/// Currently, segment data is not stored as it is not used, but it could easily be added.
pub(crate) point_graph: UnGraph<Point, ()>,
pub(crate) segment_to_edge: FxHashMap<SegmentId, EdgeIndex>,
/// Get the offset from the point ID.
pub(crate) point_to_offset: FxHashMap<PointId, usize>,
// TODO: faces
}
impl VectorIndex {
/// Construct a [`VectorIndex`] by building indexes from the given [`Vector`]. Takes `O(n)` time.
pub fn build_from(data: &Vector) -> Self {
let point_to_offset = data.point_domain.ids().iter().copied().enumerate().map(|(a, b)| (b, a)).collect::<FxHashMap<_, _>>();
let mut point_to_node = FxHashMap::default();
let mut segment_to_edge = FxHashMap::default();
let mut graph = UnGraph::new_undirected();
for (point_id, position) in data.point_domain.iter() {
let idx = graph.add_node(Point { id: point_id, position });
point_to_node.insert(point_id, idx);
}
for (segment_id, start_offset, end_offset, ..) in data.segment_domain.iter() {
let start_id = data.point_domain.ids()[start_offset];
let end_id = data.point_domain.ids()[end_offset];
let edge = graph.add_edge(point_to_node[&start_id], point_to_node[&end_id], ());
segment_to_edge.insert(segment_id, edge);
}
Self {
point_graph: graph,
segment_to_edge,
point_to_offset,
}
}
/// Fetch the length of given segment's chord. Takes `O(1)` time.
///
/// # Panics
///
/// Will panic if no segment with the given ID is found.
pub fn segment_chord_length(&self, id: SegmentId) -> f64 {
let edge_idx = self.segment_to_edge[&id];
let (start, end) = self.point_graph.edge_endpoints(edge_idx).unwrap();
let start_position = self.point_graph.node_weight(start).unwrap().position;
let end_position = self.point_graph.node_weight(end).unwrap().position;
(start_position - end_position).length()
}
/// Get the ends of a segment. Takes `O(1)` time.
///
/// The IDs will be ordered [smallest, largest] so they can be used to find other segments with the same endpoints, regardless of direction.
///
/// # Panics
///
/// This function will panic if the ID is not present.
pub fn segment_ends(&self, id: SegmentId) -> [NodeIndex; 2] {
let (start, end) = self.point_graph.edge_endpoints(self.segment_to_edge[&id]).unwrap();
if start < end { [start, end] } else { [end, start] }
}
/// Get the physical location of a point. Takes `O(1)` time.
///
/// # Panics
///
/// Will panic if `id` isn't in the data.
pub fn point_position(&self, id: PointId, data: &Vector) -> DVec2 {
let offset = self.point_to_offset[&id];
data.point_domain.positions()[offset]
}
}

View file

@ -3,21 +3,22 @@ use super::{PointId, SegmentId, StrokeId};
use crate::Ctx;
use crate::registry::types::{Angle, PixelSize};
use crate::table::Table;
use crate::vector::{HandleId, VectorData};
use crate::vector::Vector;
use crate::vector::misc::HandleId;
use bezier_rs::Subpath;
use glam::DVec2;
trait CornerRadius {
fn generate(self, size: DVec2, clamped: bool) -> Table<VectorData>;
fn generate(self, size: DVec2, clamped: bool) -> Table<Vector>;
}
impl CornerRadius for f64 {
fn generate(self, size: DVec2, clamped: bool) -> Table<VectorData> {
fn generate(self, size: DVec2, clamped: bool) -> Table<Vector> {
let clamped_radius = if clamped { self.clamp(0., size.x.min(size.y).max(0.) / 2.) } else { self };
Table::new_from_element(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])))
Table::new_from_element(Vector::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., [clamped_radius; 4])))
}
}
impl CornerRadius for [f64; 4] {
fn generate(self, size: DVec2, clamped: bool) -> Table<VectorData> {
fn generate(self, size: DVec2, clamped: bool) -> Table<Vector> {
let clamped_radius = if clamped {
// Algorithm follows the CSS spec: <https://drafts.csswg.org/css-backgrounds/#corner-overlap>
@ -33,7 +34,7 @@ impl CornerRadius for [f64; 4] {
} else {
self
};
Table::new_from_element(VectorData::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)))
Table::new_from_element(Vector::from_subpath(Subpath::new_rounded_rect(size / -2., size / 2., clamped_radius)))
}
}
@ -44,9 +45,9 @@ fn circle(
#[unit(" px")]
#[default(50.)]
radius: f64,
) -> Table<VectorData> {
) -> Table<Vector> {
let radius = radius.abs();
Table::new_from_element(VectorData::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
Table::new_from_element(Vector::from_subpath(Subpath::new_ellipse(DVec2::splat(-radius), DVec2::splat(radius))))
}
#[node_macro::node(category("Vector: Shape"))]
@ -61,8 +62,8 @@ fn arc(
#[range((0., 360.))]
sweep_angle: Angle,
arc_type: ArcType,
) -> Table<VectorData> {
Table::new_from_element(VectorData::from_subpath(Subpath::new_arc(
) -> Table<Vector> {
Table::new_from_element(Vector::from_subpath(Subpath::new_arc(
radius,
start_angle / 360. * std::f64::consts::TAU,
sweep_angle / 360. * std::f64::consts::TAU,
@ -84,12 +85,12 @@ fn ellipse(
#[unit(" px")]
#[default(25)]
radius_y: f64,
) -> Table<VectorData> {
) -> Table<Vector> {
let radius = DVec2::new(radius_x, radius_y);
let corner1 = -radius;
let corner2 = radius;
let mut ellipse = VectorData::from_subpath(Subpath::new_ellipse(corner1, corner2));
let mut ellipse = Vector::from_subpath(Subpath::new_ellipse(corner1, corner2));
let len = ellipse.segment_domain.ids().len();
for i in 0..len {
@ -114,7 +115,7 @@ fn rectangle<T: CornerRadius>(
_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,
) -> Table<VectorData> {
) -> Table<Vector> {
corner_radius.generate(DVec2::new(width, height), clamped)
}
@ -129,10 +130,10 @@ fn regular_polygon<T: AsU64>(
#[unit(" px")]
#[default(50)]
radius: f64,
) -> Table<VectorData> {
) -> Table<Vector> {
let points = sides.as_u64();
let radius: f64 = radius * 2.;
Table::new_from_element(VectorData::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
Table::new_from_element(Vector::from_subpath(Subpath::new_regular_polygon(DVec2::splat(-radius), points, radius)))
}
#[node_macro::node(category("Vector: Shape"))]
@ -149,17 +150,17 @@ fn star<T: AsU64>(
#[unit(" px")]
#[default(25)]
radius_2: f64,
) -> Table<VectorData> {
) -> Table<Vector> {
let points = sides.as_u64();
let diameter: f64 = radius_1 * 2.;
let inner_diameter = radius_2 * 2.;
Table::new_from_element(VectorData::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
Table::new_from_element(Vector::from_subpath(Subpath::new_star_polygon(DVec2::splat(-diameter), points, diameter, inner_diameter)))
}
#[node_macro::node(category("Vector: Shape"))]
fn line(_: impl Ctx, _primary: (), #[default(0., 0.)] start: PixelSize, #[default(100., 100.)] end: PixelSize) -> Table<VectorData> {
Table::new_from_element(VectorData::from_subpath(Subpath::new_line(start, end)))
fn line(_: impl Ctx, _primary: (), #[default(0., 0.)] start: PixelSize, #[default(100., 100.)] end: PixelSize) -> Table<Vector> {
Table::new_from_element(Vector::from_subpath(Subpath::new_line(start, end)))
}
trait GridSpacing {
@ -189,11 +190,11 @@ fn grid<T: GridSpacing>(
#[default(10)] columns: u32,
#[default(10)] rows: u32,
#[default(30., 30.)] angles: DVec2,
) -> Table<VectorData> {
) -> Table<Vector> {
let (x_spacing, y_spacing) = spacing.as_dvec2().into();
let (angle_a, angle_b) = angles.into();
let mut vector_data = VectorData::default();
let mut vector = Vector::default();
let mut segment_id = SegmentId::ZERO;
let mut point_id = PointId::ZERO;
@ -203,13 +204,13 @@ fn grid<T: GridSpacing>(
for y in 0..rows {
for x in 0..columns {
// Add current point to the grid
let current_index = vector_data.point_domain.ids().len();
vector_data.point_domain.push(point_id.next_id(), DVec2::new(x_spacing * x as f64, y_spacing * y as f64));
let current_index = vector.point_domain.ids().len();
vector.point_domain.push(point_id.next_id(), DVec2::new(x_spacing * x as f64, y_spacing * y as f64));
// Helper function to connect points with line segments
let mut push_segment = |to_index: Option<usize>| {
if let Some(other_index) = to_index {
vector_data
vector
.segment_domain
.push(segment_id.next_id(), other_index, current_index, bezier_rs::BezierHandles::Linear, StrokeId::ZERO);
}
@ -233,7 +234,7 @@ fn grid<T: GridSpacing>(
for y in 0..rows {
for x in 0..columns {
// Add current point to the grid with offset for odd columns
let current_index = vector_data.point_domain.ids().len();
let current_index = vector.point_domain.ids().len();
let a_angles_eaten = x.div_ceil(2) as f64;
let b_angles_eaten = (x / 2) as f64;
@ -241,12 +242,12 @@ fn grid<T: GridSpacing>(
let offset_y_fraction = b_angles_eaten * tan_b - a_angles_eaten * tan_a;
let position = DVec2::new(spacing.x * x as f64, spacing.y * y as f64 + offset_y_fraction * spacing.x);
vector_data.point_domain.push(point_id.next_id(), position);
vector.point_domain.push(point_id.next_id(), position);
// Helper function to connect points with line segments
let mut push_segment = |to_index: Option<usize>| {
if let Some(other_index) = to_index {
vector_data
vector
.segment_domain
.push(segment_id.next_id(), other_index, current_index, bezier_rs::BezierHandles::Linear, StrokeId::ZERO);
}
@ -271,7 +272,7 @@ fn grid<T: GridSpacing>(
}
}
Table::new_from_element(vector_data)
Table::new_from_element(vector)
}
#[cfg(test)]
@ -285,9 +286,9 @@ mod tests {
// Works properly
let grid = grid((), (), GridType::Isometric, 10., 5, 5, (30., 30.).into());
assert_eq!(grid.iter_ref().next().unwrap().element.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.iter_ref().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.iter_ref().next().unwrap().element.segment_bezier_iter() {
assert_eq!(grid.iter().next().unwrap().element.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.iter().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.iter().next().unwrap().element.segment_bezier_iter() {
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
assert!(
((bezier.start - bezier.end).length() - 10.).abs() < 1e-5,
@ -300,9 +301,9 @@ mod tests {
#[test]
fn skew_isometric_grid_test() {
let grid = grid((), (), GridType::Isometric, 10., 5, 5, (40., 30.).into());
assert_eq!(grid.iter_ref().next().unwrap().element.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.iter_ref().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.iter_ref().next().unwrap().element.segment_bezier_iter() {
assert_eq!(grid.iter().next().unwrap().element.point_domain.ids().len(), 5 * 5);
assert_eq!(grid.iter().next().unwrap().element.segment_bezier_iter().count(), 4 * 5 + 4 * 9);
for (_, bezier, _, _) in grid.iter().next().unwrap().element.segment_bezier_iter() {
assert_eq!(bezier.handles, bezier_rs::BezierHandles::Linear);
let vector = bezier.start - bezier.end;
let angle = (vector.angle_to(DVec2::X).to_degrees() + 180.) % 180.;

View file

@ -1,5 +1,6 @@
use super::PointId;
use super::algorithms::offset_subpath::MAX_ABSOLUTE_DIFFERENCE;
use crate::vector::{SegmentId, Vector};
use bezier_rs::{BezierHandles, ManipulatorGroup, Subpath};
use dyn_any::DynAny;
use glam::DVec2;
@ -136,9 +137,9 @@ pub fn handles_to_segment(start: DVec2, handles: BezierHandles, end: DVec2) -> P
}
pub fn subpath_to_kurbo_bezpath(subpath: Subpath<PointId>) -> BezPath {
let maniputor_groups = subpath.manipulator_groups();
let manipulator_groups = subpath.manipulator_groups();
let closed = subpath.closed();
bezpath_from_manipulator_groups(maniputor_groups, closed)
bezpath_from_manipulator_groups(manipulator_groups, closed)
}
pub fn bezpath_from_manipulator_groups(manipulator_groups: &[ManipulatorGroup<PointId>], closed: bool) -> BezPath {
@ -181,8 +182,8 @@ pub fn bezpath_to_manipulator_groups(bezpath: &BezPath) -> (Vec<ManipulatorGroup
kurbo::PathEl::LineTo(point) => ManipulatorGroup::new(point_to_dvec2(point), None, None),
kurbo::PathEl::QuadTo(point, point1) => ManipulatorGroup::new(point_to_dvec2(point1), Some(point_to_dvec2(point)), None),
kurbo::PathEl::CurveTo(point, point1, point2) => {
if let Some(last_maipulator_group) = manipulator_groups.last_mut() {
last_maipulator_group.out_handle = Some(point_to_dvec2(point));
if let Some(last_manipulator_group) = manipulator_groups.last_mut() {
last_manipulator_group.out_handle = Some(point_to_dvec2(point));
}
ManipulatorGroup::new(point_to_dvec2(point2), Some(point_to_dvec2(point1)), None)
}
@ -237,3 +238,182 @@ pub fn pathseg_abs_diff_eq(seg1: PathSeg, seg2: PathSeg, max_abs_diff: f64) -> b
seg1_points.len() == seg2_points.len() && seg1_points.into_iter().zip(seg2_points).all(|(a, b)| cmp(a.x, b.x) && cmp(a.y, b.y))
}
/// A selectable part of a curve, either an anchor (start or end of a bézier) or a handle (doesn't necessarily go through the bézier but influences curvature).
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
pub enum ManipulatorPointId {
/// A control anchor - the start or end point of a bézier.
Anchor(PointId),
/// The handle for a bézier - the first handle on a cubic and the only handle on a quadratic.
PrimaryHandle(SegmentId),
/// The end handle on a cubic bézier.
EndHandle(SegmentId),
}
impl ManipulatorPointId {
/// Attempt to retrieve the manipulator position in layer space (no transformation applied).
#[must_use]
#[track_caller]
pub fn get_position(&self, vector: &Vector) -> Option<DVec2> {
match self {
ManipulatorPointId::Anchor(id) => vector.point_domain.position_from_id(*id),
ManipulatorPointId::PrimaryHandle(id) => vector.segment_from_id(*id).and_then(|bezier| bezier.handle_start()),
ManipulatorPointId::EndHandle(id) => vector.segment_from_id(*id).and_then(|bezier| bezier.handle_end()),
}
}
pub fn get_anchor_position(&self, vector: &Vector) -> Option<DVec2> {
match self {
ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_) => self.get_anchor(vector).and_then(|id| vector.point_domain.position_from_id(id)),
_ => self.get_position(vector),
}
}
/// Attempt to get a pair of handles. For an anchor this is the first two handles connected. For a handle it is self and the first opposing handle.
#[must_use]
pub fn get_handle_pair(self, vector: &Vector) -> Option<[HandleId; 2]> {
match self {
ManipulatorPointId::Anchor(point) => vector.all_connected(point).take(2).collect::<Vec<_>>().try_into().ok(),
ManipulatorPointId::PrimaryHandle(segment) => {
let point = vector.segment_domain.segment_start_from_id(segment)?;
let current = HandleId::primary(segment);
let other = vector.segment_domain.all_connected(point).find(|&value| value != current);
other.map(|other| [current, other])
}
ManipulatorPointId::EndHandle(segment) => {
let point = vector.segment_domain.segment_end_from_id(segment)?;
let current = HandleId::end(segment);
let other = vector.segment_domain.all_connected(point).find(|&value| value != current);
other.map(|other| [current, other])
}
}
}
/// Finds all the connected handles of a point.
/// For an anchor it is all the connected handles.
/// For a handle it is all the handles connected to its corresponding anchor other than the current handle.
pub fn get_all_connected_handles(self, vector: &Vector) -> Option<Vec<HandleId>> {
match self {
ManipulatorPointId::Anchor(point) => {
let connected = vector.all_connected(point).collect::<Vec<_>>();
Some(connected)
}
ManipulatorPointId::PrimaryHandle(segment) => {
let point = vector.segment_domain.segment_start_from_id(segment)?;
let current = HandleId::primary(segment);
let connected = vector.segment_domain.all_connected(point).filter(|&value| value != current).collect::<Vec<_>>();
Some(connected)
}
ManipulatorPointId::EndHandle(segment) => {
let point = vector.segment_domain.segment_end_from_id(segment)?;
let current = HandleId::end(segment);
let connected = vector.segment_domain.all_connected(point).filter(|&value| value != current).collect::<Vec<_>>();
Some(connected)
}
}
}
/// Attempt to find the closest anchor. If self is already an anchor then it is just self. If it is a start or end handle, then the start or end point is chosen.
#[must_use]
pub fn get_anchor(self, vector: &Vector) -> Option<PointId> {
match self {
ManipulatorPointId::Anchor(point) => Some(point),
ManipulatorPointId::PrimaryHandle(segment) => vector.segment_start_from_id(segment),
ManipulatorPointId::EndHandle(segment) => vector.segment_end_from_id(segment),
}
}
/// Attempt to convert self to a [`HandleId`], returning none for an anchor.
#[must_use]
pub fn as_handle(self) -> Option<HandleId> {
match self {
ManipulatorPointId::PrimaryHandle(segment) => Some(HandleId::primary(segment)),
ManipulatorPointId::EndHandle(segment) => Some(HandleId::end(segment)),
ManipulatorPointId::Anchor(_) => None,
}
}
/// Attempt to convert self to an anchor, returning None for a handle.
#[must_use]
pub fn as_anchor(self) -> Option<PointId> {
match self {
ManipulatorPointId::Anchor(point) => Some(point),
_ => None,
}
}
pub fn get_segment(self) -> Option<SegmentId> {
match self {
ManipulatorPointId::PrimaryHandle(segment) | ManipulatorPointId::EndHandle(segment) => Some(segment),
_ => None,
}
}
}
/// The type of handle found on a bézier curve.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
pub enum HandleType {
/// The first handle on a cubic bézier or the only handle on a quadratic bézier.
Primary,
/// The second handle on a cubic bézier.
End,
}
/// Represents a primary or end handle found in a particular segment.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
pub struct HandleId {
pub ty: HandleType,
pub segment: SegmentId,
}
impl std::fmt::Display for HandleId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.ty {
// I haven't checked if "out" and "in" are reversed, or are accurate translations of the "primary" and "end" terms used in the `HandleType` enum, so this naming is an assumption.
HandleType::Primary => write!(f, "{} out", self.segment.inner()),
HandleType::End => write!(f, "{} in", self.segment.inner()),
}
}
}
impl HandleId {
/// Construct a handle for the first handle on a cubic bézier or the only handle on a quadratic bézier.
#[must_use]
pub const fn primary(segment: SegmentId) -> Self {
Self { ty: HandleType::Primary, segment }
}
/// Construct a handle for the end handle on a cubic bézier.
#[must_use]
pub const fn end(segment: SegmentId) -> Self {
Self { ty: HandleType::End, segment }
}
/// Convert to [`ManipulatorPointId`].
#[must_use]
pub fn to_manipulator_point(self) -> ManipulatorPointId {
match self.ty {
HandleType::Primary => ManipulatorPointId::PrimaryHandle(self.segment),
HandleType::End => ManipulatorPointId::EndHandle(self.segment),
}
}
/// Calculate the magnitude of the handle from the anchor.
pub fn length(self, vector: &Vector) -> f64 {
let Some(anchor_position) = self.to_manipulator_point().get_anchor_position(vector) else {
// TODO: This was previously an unwrap which was encountered, so this is a temporary way to avoid a crash
return 0.;
};
let handle_position = self.to_manipulator_point().get_position(vector);
handle_position.map(|pos| (pos - anchor_position).length()).unwrap_or(f64::MAX)
}
/// Convert an end handle to the primary handle and a primary handle to an end handle. Note that the new handle may not exist (e.g. for a quadratic bézier).
#[must_use]
pub fn opposite(self) -> Self {
match self.ty {
HandleType::Primary => Self::end(self.segment),
HandleType::End => Self::primary(self.segment),
}
}
}

View file

@ -4,11 +4,13 @@ pub mod generator_nodes;
pub mod misc;
mod reference_point;
pub mod style;
mod vector_data;
mod vector_attributes;
mod vector_modification;
mod vector_nodes;
mod vector_types;
pub use bezier_rs;
pub use reference_point::*;
pub use style::PathStyle;
pub use vector_data::*;
pub use vector_nodes::*;
pub use vector_types::*;

View file

@ -24,7 +24,7 @@ impl std::fmt::Display for Fill {
match self {
Self::None => write!(f, "None"),
Self::Solid(color) => write!(f, "#{} (Alpha: {}%)", color.to_rgb_hex_srgb(), color.a() * 100.),
Self::Gradient(gradient) => write!(f, "{}", gradient),
Self::Gradient(gradient) => write!(f, "{gradient}"),
}
}
}

View file

@ -1,5 +1,5 @@
use crate::vector::misc::dvec2_to_point;
use crate::vector::vector_data::{HandleId, VectorData};
use crate::vector::misc::{HandleId, dvec2_to_point};
use crate::vector::vector_types::Vector;
use bezier_rs::{BezierHandles, ManipulatorGroup};
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
@ -673,7 +673,7 @@ impl FoundSubpath {
}
}
impl VectorData {
impl Vector {
/// Construct a [`kurbo::PathSeg`] by resolving the points from their ids.
fn path_segment_from_index(&self, start: usize, end: usize, handles: BezierHandles) -> PathSeg {
let start = dvec2_to_point(self.point_domain.positions()[start]);
@ -896,7 +896,7 @@ impl VectorData {
}
StrokePathIter {
vector_data: self,
vector: self,
points,
skip: 0,
done_one: false,
@ -952,13 +952,11 @@ impl VectorData {
self.stroke_bezier_paths().flat_map(|mut path| std::mem::take(path.manipulator_groups_mut()))
}
/// Get manipulator by id
pub fn manipulator_group_id(&self, id: impl Into<PointId>) -> Option<ManipulatorGroup<PointId>> {
let id = id.into();
self.manipulator_groups().find(|group| group.id == id)
}
/// Transforms this vector data
pub fn transform(&mut self, transform: DAffine2) {
self.point_domain.transform(transform);
self.segment_domain.transform(transform);
@ -1026,7 +1024,7 @@ impl StrokePathIterPointMetadata {
#[derive(Clone)]
pub struct StrokePathIter<'a> {
vector_data: &'a VectorData,
vector: &'a Vector,
points: Vec<StrokePathIterPointMetadata>,
skip: usize,
done_one: bool,
@ -1056,29 +1054,29 @@ impl Iterator for StrokePathIter<'_> {
let Some(val) = self.points[point_index].take_first() else {
// Dead end
groups.push(ManipulatorGroup {
anchor: self.vector_data.point_domain.positions()[point_index],
anchor: self.vector.point_domain.positions()[point_index],
in_handle,
out_handle: None,
id: self.vector_data.point_domain.ids()[point_index],
id: self.vector.point_domain.ids()[point_index],
});
break;
};
let mut handles = self.vector_data.segment_domain.handles()[val.segment_index];
let mut handles = self.vector.segment_domain.handles()[val.segment_index];
if val.start_from_end {
handles = handles.reversed();
}
let next_point_index = if val.start_from_end {
self.vector_data.segment_domain.start_point()[val.segment_index]
self.vector.segment_domain.start_point()[val.segment_index]
} else {
self.vector_data.segment_domain.end_point()[val.segment_index]
self.vector.segment_domain.end_point()[val.segment_index]
};
groups.push(ManipulatorGroup {
anchor: self.vector_data.point_domain.positions()[point_index],
anchor: self.vector.point_domain.positions()[point_index],
in_handle,
out_handle: handles.start(),
id: self.vector_data.point_domain.ids()[point_index],
id: self.vector.point_domain.ids()[point_index],
});
in_handle = handles.end();
@ -1102,7 +1100,7 @@ impl bezier_rs::Identifier for PointId {
}
}
/// Represents the conversion of ids used when concatenating vector data with conflicting ids.
/// Represents the conversion of IDs used when concatenating vector paths with conflicting IDs.
pub struct IdMap {
pub point_offset: usize,
pub point_map: HashMap<PointId, PointId>,

View file

@ -1,90 +0,0 @@
use super::{PointId, SegmentId, VectorData};
use glam::DVec2;
use petgraph::graph::{EdgeIndex, NodeIndex, UnGraph};
use rustc_hash::FxHashMap;
/// All the fixed fields of a point from the point domain.
pub struct Point {
pub id: PointId,
pub position: DVec2,
}
/// Useful indexes to speed up various operations on `VectorData`.
///
/// Important: It is the user's responsibility to ensure the indexes remain valid after mutations to the data.
pub struct VectorDataIndex {
/// Points and segments form a graph. Store it here in a form amenable to graph algorithms.
///
/// Currently, segment data is not stored as it is not used, but it could easily be added.
pub(crate) point_graph: UnGraph<Point, ()>,
pub(crate) segment_to_edge: FxHashMap<SegmentId, EdgeIndex>,
/// Get the offset from the point ID.
pub(crate) point_to_offset: FxHashMap<PointId, usize>,
// TODO: faces
}
impl VectorDataIndex {
/// Construct a [`VectorDataIndex`] by building indexes from the given [`VectorData`]. Takes `O(n)` time.
pub fn build_from(data: &VectorData) -> Self {
let point_to_offset = data.point_domain.ids().iter().copied().enumerate().map(|(a, b)| (b, a)).collect::<FxHashMap<_, _>>();
let mut point_to_node = FxHashMap::default();
let mut segment_to_edge = FxHashMap::default();
let mut graph = UnGraph::new_undirected();
for (point_id, position) in data.point_domain.iter() {
let idx = graph.add_node(Point { id: point_id, position });
point_to_node.insert(point_id, idx);
}
for (segment_id, start_offset, end_offset, ..) in data.segment_domain.iter() {
let start_id = data.point_domain.ids()[start_offset];
let end_id = data.point_domain.ids()[end_offset];
let edge = graph.add_edge(point_to_node[&start_id], point_to_node[&end_id], ());
segment_to_edge.insert(segment_id, edge);
}
Self {
point_graph: graph,
segment_to_edge,
point_to_offset,
}
}
/// Fetch the length of given segment's chord. Takes `O(1)` time.
///
/// # Panics
///
/// Will panic if no segment with the given ID is found.
pub fn segment_chord_length(&self, id: SegmentId) -> f64 {
let edge_idx = self.segment_to_edge[&id];
let (start, end) = self.point_graph.edge_endpoints(edge_idx).unwrap();
let start_position = self.point_graph.node_weight(start).unwrap().position;
let end_position = self.point_graph.node_weight(end).unwrap().position;
(start_position - end_position).length()
}
/// Get the ends of a segment. Takes `O(1)` time.
///
/// The IDs will be ordered [smallest, largest] so they can be used to find other segments with the same endpoints, regardless of direction.
///
/// # Panics
///
/// This function will panic if the ID is not present.
pub fn segment_ends(&self, id: SegmentId) -> [NodeIndex; 2] {
let (start, end) = self.point_graph.edge_endpoints(self.segment_to_edge[&id]).unwrap();
if start < end { [start, end] } else { [end, start] }
}
/// Get the physical location of a point. Takes `O(1)` time.
///
/// # Panics
///
/// Will panic if `id` isn't in the data.
pub fn point_position(&self, id: PointId, data: &VectorData) -> DVec2 {
let offset = self.point_to_offset[&id];
data.point_domain.positions()[offset]
}
}

View file

@ -1,14 +1,16 @@
use super::*;
use crate::Ctx;
use crate::table::TableRow;
use crate::table::{Table, TableRow};
use crate::uuid::{NodeId, generate_uuid};
use crate::vector::misc::{HandleId, HandleType, point_to_dvec2};
use bezier_rs::BezierHandles;
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
use kurbo::{BezPath, PathEl, Point};
use std::collections::{HashMap, HashSet};
use std::hash::BuildHasher;
/// Represents a procedural change to the [`PointDomain`] in [`VectorData`].
/// Represents a procedural change to the [`PointDomain`] in [`Vector`].
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct PointModification {
add: Vec<PointId>,
@ -58,12 +60,12 @@ impl PointModification {
}
}
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
pub fn create_from_vector(vector_data: &VectorData) -> Self {
/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].
pub fn create_from_vector(vector: &Vector) -> Self {
Self {
add: vector_data.point_domain.ids().to_vec(),
add: vector.point_domain.ids().to_vec(),
remove: HashSet::new(),
delta: vector_data.point_domain.ids().iter().copied().zip(vector_data.point_domain.positions().iter().cloned()).collect(),
delta: vector.point_domain.ids().iter().copied().zip(vector.point_domain.positions().iter().cloned()).collect(),
}
}
@ -79,7 +81,7 @@ impl PointModification {
}
}
/// Represents a procedural change to the [`SegmentDomain`] in [`VectorData`].
/// Represents a procedural change to the [`SegmentDomain`] in [`Vector`].
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct SegmentModification {
add: Vec<SegmentId>,
@ -177,11 +179,11 @@ impl SegmentModification {
let Some(&stroke) = self.stroke.get(&add_id) else { continue };
let Some(start_index) = point_domain.resolve_id(start) else {
warn!("invalid start id: {:#?}", start);
warn!("invalid start id: {start:#?}");
continue;
};
let Some(end_index) = point_domain.resolve_id(end) else {
warn!("invalid end id: {:#?}", end);
warn!("invalid end id: {end:#?}");
continue;
};
@ -206,27 +208,25 @@ impl SegmentModification {
assert!(
segment_domain.start_point().iter().all(|&index| index < point_domain.ids().len()),
"index should be in range {:#?}",
segment_domain
"index should be in range {segment_domain:#?}"
);
assert!(
segment_domain.end_point().iter().all(|&index| index < point_domain.ids().len()),
"index should be in range {:#?}",
segment_domain
"index should be in range {segment_domain:#?}"
);
}
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
pub fn create_from_vector(vector_data: &VectorData) -> Self {
let point_id = |(&segment, &index)| (segment, vector_data.point_domain.ids()[index]);
/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].
pub fn create_from_vector(vector: &Vector) -> Self {
let point_id = |(&segment, &index)| (segment, vector.point_domain.ids()[index]);
Self {
add: vector_data.segment_domain.ids().to_vec(),
add: vector.segment_domain.ids().to_vec(),
remove: HashSet::new(),
start_point: vector_data.segment_domain.ids().iter().zip(vector_data.segment_domain.start_point()).map(point_id).collect(),
end_point: vector_data.segment_domain.ids().iter().zip(vector_data.segment_domain.end_point()).map(point_id).collect(),
handle_primary: vector_data.segment_bezier_iter().map(|(id, b, _, _)| (id, b.handle_start().map(|handle| handle - b.start))).collect(),
handle_end: vector_data.segment_bezier_iter().map(|(id, b, _, _)| (id, b.handle_end().map(|handle| handle - b.end))).collect(),
stroke: vector_data.segment_domain.ids().iter().copied().zip(vector_data.segment_domain.stroke().iter().cloned()).collect(),
start_point: vector.segment_domain.ids().iter().zip(vector.segment_domain.start_point()).map(point_id).collect(),
end_point: vector.segment_domain.ids().iter().zip(vector.segment_domain.end_point()).map(point_id).collect(),
handle_primary: vector.segment_bezier_iter().map(|(id, b, _, _)| (id, b.handle_start().map(|handle| handle - b.start))).collect(),
handle_end: vector.segment_bezier_iter().map(|(id, b, _, _)| (id, b.handle_end().map(|handle| handle - b.end))).collect(),
stroke: vector.segment_domain.ids().iter().copied().zip(vector.segment_domain.stroke().iter().cloned()).collect(),
}
}
@ -251,7 +251,7 @@ impl SegmentModification {
}
}
/// Represents a procedural change to the [`RegionDomain`] in [`VectorData`].
/// Represents a procedural change to the [`RegionDomain`] in [`Vector`].
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct RegionModification {
add: Vec<RegionId>,
@ -284,18 +284,18 @@ impl RegionModification {
}
}
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
pub fn create_from_vector(vector_data: &VectorData) -> Self {
/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].
pub fn create_from_vector(vector: &Vector) -> Self {
Self {
add: vector_data.region_domain.ids().to_vec(),
add: vector.region_domain.ids().to_vec(),
remove: HashSet::new(),
segment_range: vector_data.region_domain.ids().iter().copied().zip(vector_data.region_domain.segment_range().iter().cloned()).collect(),
fill: vector_data.region_domain.ids().iter().copied().zip(vector_data.region_domain.fill().iter().cloned()).collect(),
segment_range: vector.region_domain.ids().iter().copied().zip(vector.region_domain.segment_range().iter().cloned()).collect(),
fill: vector.region_domain.ids().iter().copied().zip(vector.region_domain.fill().iter().cloned()).collect(),
}
}
}
/// Represents a procedural change to the [`VectorData`].
/// Represents a procedural change to the [`Vector`].
#[derive(Clone, Debug, Default, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub struct VectorModification {
points: PointModification,
@ -327,27 +327,27 @@ pub enum VectorModificationType {
}
impl VectorModification {
/// Apply this modification to the specified [`VectorData`].
pub fn apply(&self, vector_data: &mut VectorData) {
self.points.apply(&mut vector_data.point_domain, &mut vector_data.segment_domain);
self.segments.apply(&mut vector_data.segment_domain, &vector_data.point_domain);
self.regions.apply(&mut vector_data.region_domain);
/// Apply this modification to the specified [`Vector`].
pub fn apply(&self, vector: &mut Vector) {
self.points.apply(&mut vector.point_domain, &mut vector.segment_domain);
self.segments.apply(&mut vector.segment_domain, &vector.point_domain);
self.regions.apply(&mut vector.region_domain);
let valid = |val: &[HandleId; 2]| vector_data.segment_domain.ids().contains(&val[0].segment) && vector_data.segment_domain.ids().contains(&val[1].segment);
vector_data
let valid = |val: &[HandleId; 2]| vector.segment_domain.ids().contains(&val[0].segment) && vector.segment_domain.ids().contains(&val[1].segment);
vector
.colinear_manipulators
.retain(|val| !self.remove_g1_continuous.contains(val) && !self.remove_g1_continuous.contains(&[val[1], val[0]]) && valid(val));
for handles in &self.add_g1_continuous {
if !vector_data.colinear_manipulators.iter().any(|test| test == handles || test == &[handles[1], handles[0]]) && valid(handles) {
vector_data.colinear_manipulators.push(*handles);
if !vector.colinear_manipulators.iter().any(|test| test == handles || test == &[handles[1], handles[0]]) && valid(handles) {
vector.colinear_manipulators.push(*handles);
}
}
}
/// Add a [`VectorModificationType`] to this modification.
pub fn modify(&mut self, vector_data_modification: &VectorModificationType) {
match vector_data_modification {
pub fn modify(&mut self, vector_modification: &VectorModificationType) {
match vector_modification {
VectorModificationType::InsertSegment { id, points, handles } => self.segments.push(*id, *points, *handles, StrokeId::ZERO),
VectorModificationType::InsertPoint { id, position } => self.points.push(*id, *position),
@ -400,13 +400,13 @@ impl VectorModification {
}
}
/// Create a new modification that will convert an empty [`VectorData`] into the target [`VectorData`].
pub fn create_from_vector(vector_data: &VectorData) -> Self {
/// Create a new modification that will convert an empty [`Vector`] into the target [`Vector`].
pub fn create_from_vector(vector: &Vector) -> Self {
Self {
points: PointModification::create_from_vector(vector_data),
segments: SegmentModification::create_from_vector(vector_data),
regions: RegionModification::create_from_vector(vector_data),
add_g1_continuous: vector_data.colinear_manipulators.iter().copied().collect(),
points: PointModification::create_from_vector(vector),
segments: SegmentModification::create_from_vector(vector),
regions: RegionModification::create_from_vector(vector),
add_g1_continuous: vector.colinear_manipulators.iter().copied().collect(),
remove_g1_continuous: HashSet::new(),
}
}
@ -420,38 +420,38 @@ impl Hash for VectorModification {
/// Applies a diff modification to a vector path.
#[node_macro::node(category(""))]
async fn path_modify(_ctx: impl Ctx, mut vector_data: Table<VectorData>, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> Table<VectorData> {
if vector_data.is_empty() {
vector_data.push(TableRow::default());
async fn path_modify(_ctx: impl Ctx, mut vector: Table<Vector>, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> Table<Vector> {
if vector.is_empty() {
vector.push(TableRow::default());
}
let row = vector_data.get_mut(0).expect("push should give one item");
let row = vector.get_mut(0).expect("push should give one item");
modification.apply(row.element);
// Update the source node id
let this_node_path = node_path.iter().rev().nth(1).copied();
*row.source_node_id = row.source_node_id.or(this_node_path);
if vector_data.len() > 1 {
warn!("The path modify ran on {} rows of vector data. Only the first can be modified.", vector_data.len());
if vector.len() > 1 {
warn!("The path modify ran on {} vector rows. Only the first can be modified.", vector.len());
}
vector_data
vector
}
/// Applies the vector path's local transformation to its geometry and resets it to the identity.
#[node_macro::node(category("Vector"))]
async fn apply_transform(_ctx: impl Ctx, mut vector_data: Table<VectorData>) -> Table<VectorData> {
for row in vector_data.iter_mut() {
let vector_data = row.element;
async fn apply_transform(_ctx: impl Ctx, mut vector: Table<Vector>) -> Table<Vector> {
for row in vector.iter_mut() {
let vector = row.element;
let transform = *row.transform;
for (_, point) in vector_data.point_domain.positions_mut() {
for (_, point) in vector.point_domain.positions_mut() {
*point = transform.transform_point2(*point);
}
*row.transform = DAffine2::IDENTITY;
}
vector_data
vector
}
// Do we want to enforce that all serialized/deserialized hashmaps are a vec of tuples?
@ -524,11 +524,11 @@ pub struct AppendBezpath<'a> {
last_segment_id: Option<SegmentId>,
point_id: PointId,
segment_id: SegmentId,
vector_data: &'a mut VectorData,
vector: &'a mut Vector,
}
impl<'a> AppendBezpath<'a> {
fn new(vector_data: &'a mut VectorData) -> Self {
fn new(vector: &'a mut Vector) -> Self {
Self {
first_point: None,
last_point: None,
@ -536,9 +536,9 @@ impl<'a> AppendBezpath<'a> {
last_point_index: None,
first_segment_id: None,
last_segment_id: None,
point_id: vector_data.point_domain.next_id(),
segment_id: vector_data.segment_domain.next_id(),
vector_data,
point_id: vector.point_domain.next_id(),
segment_id: vector.segment_domain.next_id(),
vector,
}
}
@ -555,28 +555,28 @@ impl<'a> AppendBezpath<'a> {
// Create a new segment.
let next_segment_id = self.segment_id.next_id();
self.vector_data
self.vector
.segment_domain
.push(next_segment_id, self.last_point_index.unwrap(), self.first_point_index.unwrap(), handle, StrokeId::ZERO);
// Create a new region.
let next_region_id = self.vector_data.region_domain.next_id();
let next_region_id = self.vector.region_domain.next_id();
let first_segment_id = self.first_segment_id.unwrap_or(next_segment_id);
let last_segment_id = next_segment_id;
self.vector_data.region_domain.push(next_region_id, first_segment_id..=last_segment_id, FillId::ZERO);
self.vector.region_domain.push(next_region_id, first_segment_id..=last_segment_id, FillId::ZERO);
}
fn append_segment(&mut self, end_point: Point, handle: BezierHandles) {
// Append the point.
let next_point_index = self.vector_data.point_domain.ids().len();
let next_point_index = self.vector.point_domain.ids().len();
let next_point_id = self.point_id.next_id();
self.vector_data.point_domain.push(next_point_id, point_to_dvec2(end_point));
self.vector.point_domain.push(next_point_id, point_to_dvec2(end_point));
// Append the segment.
let next_segment_id = self.segment_id.next_id();
self.vector_data
self.vector
.segment_domain
.push(next_segment_id, self.last_point_index.unwrap(), next_point_index, handle, StrokeId::ZERO);
@ -593,8 +593,8 @@ impl<'a> AppendBezpath<'a> {
self.last_point = Some(point);
// Append the first point.
let next_point_index = self.vector_data.point_domain.ids().len();
self.vector_data.point_domain.push(self.point_id.next_id(), point_to_dvec2(point));
let next_point_index = self.vector.point_domain.ids().len();
self.vector.point_domain.push(self.point_id.next_id(), point_to_dvec2(point));
// Update the state.
self.first_point_index = Some(next_point_index);
@ -610,8 +610,8 @@ impl<'a> AppendBezpath<'a> {
self.last_segment_id = None;
}
pub fn append_bezpath(vector_data: &'a mut VectorData, bezpath: BezPath) {
let mut this = Self::new(vector_data);
pub fn append_bezpath(vector: &'a mut Vector, bezpath: BezPath) {
let mut this = Self::new(vector);
let mut elements = bezpath.elements().iter().peekable();
while let Some(element) = elements.next() {
@ -656,12 +656,11 @@ impl<'a> AppendBezpath<'a> {
}
}
pub trait VectorDataExt {
/// Appends a Kurbo BezPath to the vector data.
pub trait VectorExt {
fn append_bezpath(&mut self, bezpath: BezPath);
}
impl VectorDataExt for VectorData {
impl VectorExt for Vector {
fn append_bezpath(&mut self, bezpath: BezPath) {
AppendBezpath::append_bezpath(self, bezpath);
}
@ -689,16 +688,16 @@ mod tests {
#[test]
fn modify_new() {
let vector_data = VectorData::from_subpaths(
let vector = Vector::from_subpaths(
[bezier_rs::Subpath::new_ellipse(DVec2::ZERO, DVec2::ONE), bezier_rs::Subpath::new_rect(DVec2::NEG_ONE, DVec2::ZERO)],
false,
);
let modify = VectorModification::create_from_vector(&vector_data);
let modify = VectorModification::create_from_vector(&vector);
let mut new = VectorData::default();
let mut new = Vector::default();
modify.apply(&mut new);
assert_eq!(vector_data, new);
assert_eq!(vector, new);
}
#[test]
@ -715,32 +714,32 @@ mod tests {
false,
),
];
let mut vector_data = VectorData::from_subpaths(subpaths, false);
let mut vector = Vector::from_subpaths(subpaths, false);
let mut modify_new = VectorModification::create_from_vector(&vector_data);
let mut modify_new = VectorModification::create_from_vector(&vector);
let mut modify_original = VectorModification::default();
for modification in [&mut modify_new, &mut modify_original] {
let point = vector_data.point_domain.ids()[0];
let point = vector.point_domain.ids()[0];
modification.modify(&VectorModificationType::ApplyPointDelta { point, delta: DVec2::X * 0.5 });
let point = vector_data.point_domain.ids()[9];
let point = vector.point_domain.ids()[9];
modification.modify(&VectorModificationType::ApplyPointDelta { point, delta: DVec2::X });
}
let mut new = VectorData::default();
let mut new = Vector::default();
modify_new.apply(&mut new);
modify_original.apply(&mut vector_data);
modify_original.apply(&mut vector);
assert_eq!(vector_data, new);
assert_eq!(vector_data.point_domain.positions()[0], DVec2::X);
assert_eq!(vector_data.point_domain.positions()[9], DVec2::new(11., 0.));
assert_eq!(vector, new);
assert_eq!(vector.point_domain.positions()[0], DVec2::X);
assert_eq!(vector.point_domain.positions()[9], DVec2::new(11., 0.));
assert_eq!(
vector_data.segment_bezier_iter().nth(8).unwrap().1,
vector.segment_bezier_iter().nth(8).unwrap().1,
Bezier::from_quadratic_dvec2(DVec2::new(0., 0.), DVec2::new(5., 10.), DVec2::new(11., 0.))
);
assert_eq!(
vector_data.segment_bezier_iter().nth(9).unwrap().1,
vector.segment_bezier_iter().nth(9).unwrap().1,
Bezier::from_quadratic_dvec2(DVec2::new(11., 0.), DVec2::new(16., 10.), DVec2::new(20., 0.))
);
}

File diff suppressed because it is too large Load diff

View file

@ -1,83 +1,24 @@
mod attributes;
mod indexed;
mod modification;
use super::misc::{dvec2_to_point, point_to_dvec2};
use super::misc::dvec2_to_point;
use super::style::{PathStyle, Stroke};
pub use super::vector_attributes::*;
pub use super::vector_modification::*;
use crate::bounds::BoundingBox;
use crate::math::quad::Quad;
use crate::table::Table;
use crate::transform::Transform;
use crate::vector::click_target::{ClickTargetType, FreePoint};
use crate::vector::misc::{HandleId, ManipulatorPointId};
use crate::{AlphaBlending, Color, Graphic};
pub use attributes::*;
use bezier_rs::{BezierHandles, ManipulatorGroup};
use core::borrow::Borrow;
use core::hash::Hash;
use dyn_any::DynAny;
use glam::{DAffine2, DVec2};
pub use indexed::VectorDataIndex;
use kurbo::{Affine, BezPath, Rect, Shape};
pub use modification::*;
use std::collections::HashMap;
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_vector_data<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<VectorData>, D::Error> {
use serde::Deserialize;
#[derive(Clone, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub struct OldVectorData {
pub transform: DAffine2,
pub alpha_blending: AlphaBlending,
pub style: PathStyle,
/// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another).
/// This gets read in `graph_operation_message_handler.rs` by calling `inputs.as_mut_slice()` (search for the string `"Shape does not have both `subpath` and `colinear_manipulators` inputs"` to find it).
pub colinear_manipulators: Vec<[HandleId; 2]>,
pub point_domain: PointDomain,
pub segment_domain: SegmentDomain,
pub region_domain: RegionDomain,
// Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved.
pub upstream_graphic_group: Option<Table<Graphic>>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
enum EitherFormat {
VectorData(VectorData),
OldVectorData(OldVectorData),
VectorDataTable(Table<VectorData>),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::VectorData(vector_data) => Table::new_from_element(vector_data),
EitherFormat::OldVectorData(old) => {
let mut vector_data_table = Table::new_from_element(VectorData {
style: old.style,
colinear_manipulators: old.colinear_manipulators,
point_domain: old.point_domain,
segment_domain: old.segment_domain,
region_domain: old.region_domain,
upstream_graphic_group: old.upstream_graphic_group,
});
*vector_data_table.iter_mut().next().unwrap().transform = old.transform;
*vector_data_table.iter_mut().next().unwrap().alpha_blending = old.alpha_blending;
vector_data_table
}
EitherFormat::VectorDataTable(vector_data_table) => vector_data_table,
})
}
/// [VectorData] is passed between nodes.
/// It contains a list of subpaths (that may be open or closed), a transform, and some style information.
///
/// Segments are connected if they share endpoints.
/// Represents vector graphics data, composed of Bézier curves in a path or mesh arrangement.
#[derive(Clone, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub struct VectorData {
pub struct Vector {
pub style: PathStyle,
/// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another).
@ -88,11 +29,11 @@ pub struct VectorData {
pub segment_domain: SegmentDomain,
pub region_domain: RegionDomain,
// Used to store the upstream graphic group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved.
pub upstream_graphic_group: Option<Table<Graphic>>,
// Used to store the upstream group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved.
pub upstream_group: Option<Table<Graphic>>,
}
impl Default for VectorData {
impl Default for Vector {
fn default() -> Self {
Self {
style: PathStyle::new(Some(Stroke::new(Some(Color::BLACK), 0.)), super::style::Fill::None),
@ -100,12 +41,12 @@ impl Default for VectorData {
point_domain: PointDomain::new(),
segment_domain: SegmentDomain::new(),
region_domain: RegionDomain::new(),
upstream_graphic_group: None,
upstream_group: None,
}
}
}
impl std::hash::Hash for VectorData {
impl std::hash::Hash for Vector {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.point_domain.hash(state);
self.segment_domain.hash(state);
@ -115,8 +56,8 @@ impl std::hash::Hash for VectorData {
}
}
impl VectorData {
/// Push a subpath to the vector data
impl Vector {
/// Add a Bezier-rs subpath to this path.
pub fn append_subpath(&mut self, subpath: impl Borrow<bezier_rs::Subpath<PointId>>, preserve_id: bool) {
let subpath: &bezier_rs::Subpath<PointId> = subpath.borrow();
let stroke_id = StrokeId::ZERO;
@ -188,40 +129,40 @@ impl VectorData {
self.point_domain.push(id, point.position);
}
/// Construct some new vector data from a single subpath with an identity transform and black fill.
/// Construct some new vector path from a single Bezier-rs subpath with an identity transform and black fill.
pub fn from_subpath(subpath: impl Borrow<bezier_rs::Subpath<PointId>>) -> Self {
Self::from_subpaths([subpath], false)
}
/// Construct some new vector data from a single [`BezPath`] with an identity transform and black fill.
/// Construct some new vector path from a single [`BezPath`] with an identity transform and black fill.
pub fn from_bezpath(bezpath: BezPath) -> Self {
let mut vector_data = Self::default();
vector_data.append_bezpath(bezpath);
vector_data
let mut vector = Self::default();
vector.append_bezpath(bezpath);
vector
}
/// Construct some new vector data from subpaths with an identity transform and black fill.
/// Construct some new vector path from Bezier-rs subpaths with an identity transform and black fill.
pub fn from_subpaths(subpaths: impl IntoIterator<Item = impl Borrow<bezier_rs::Subpath<PointId>>>, preserve_id: bool) -> Self {
let mut vector_data = Self::default();
let mut vector = Self::default();
for subpath in subpaths.into_iter() {
vector_data.append_subpath(subpath, preserve_id);
vector.append_subpath(subpath, preserve_id);
}
vector_data
vector
}
pub fn from_target_types(target_types: impl IntoIterator<Item = impl Borrow<ClickTargetType>>, preserve_id: bool) -> Self {
let mut vector_data = Self::default();
let mut vector = Self::default();
for target_type in target_types.into_iter() {
match target_type.borrow() {
ClickTargetType::Subpath(subpath) => vector_data.append_subpath(subpath, preserve_id),
ClickTargetType::FreePoint(point) => vector_data.append_free_point(point, preserve_id),
ClickTargetType::Subpath(subpath) => vector.append_subpath(subpath, preserve_id),
ClickTargetType::FreePoint(point) => vector.append_free_point(point, preserve_id),
}
}
vector_data
vector
}
/// Compute the bounding boxes of the bezpaths without any transform
@ -374,12 +315,12 @@ impl VectorData {
self.point_domain.resolve_id(point).map_or(0, |point| self.segment_domain.connected_count(point))
}
pub fn check_point_inside_shape(&self, vector_data_transform: DAffine2, point: DVec2) -> bool {
pub fn check_point_inside_shape(&self, transform: DAffine2, point: DVec2) -> bool {
let number = self
.stroke_bezpath_iter()
.map(|mut bezpath| {
// TODO: apply transform to points instead of modifying the paths
bezpath.apply_affine(Affine::new(vector_data_transform.to_cols_array()));
bezpath.apply_affine(Affine::new(transform.to_cols_array()));
bezpath.close_path();
let bbox = bezpath.bounding_box();
(bezpath, bbox)
@ -493,9 +434,9 @@ impl VectorData {
}
}
impl BoundingBox for Table<VectorData> {
impl BoundingBox for Table<Vector> {
fn bounding_box(&self, transform: DAffine2, include_stroke: bool) -> Option<[DVec2; 2]> {
self.iter_ref()
self.iter()
.flat_map(|row| {
if !include_stroke {
return row.element.bounding_box_with_transform(transform * *row.transform);
@ -516,212 +457,84 @@ impl BoundingBox for Table<VectorData> {
}
}
/// A selectable part of a curve, either an anchor (start or end of a bézier) or a handle (doesn't necessarily go through the bézier but influences curvature).
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
pub enum ManipulatorPointId {
/// A control anchor - the start or end point of a bézier.
Anchor(PointId),
/// The handle for a bézier - the first handle on a cubic and the only handle on a quadratic.
PrimaryHandle(SegmentId),
/// The end handle on a cubic bézier.
EndHandle(SegmentId),
}
// TODO: Eventually remove this migration document upgrade code
pub fn migrate_vector<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Table<Vector>, D::Error> {
use serde::Deserialize;
impl ManipulatorPointId {
/// Attempt to retrieve the manipulator position in layer space (no transformation applied).
#[must_use]
#[track_caller]
pub fn get_position(&self, vector_data: &VectorData) -> Option<DVec2> {
match self {
ManipulatorPointId::Anchor(id) => vector_data.point_domain.position_from_id(*id),
ManipulatorPointId::PrimaryHandle(id) => vector_data.segment_from_id(*id).and_then(|bezier| bezier.handle_start()),
ManipulatorPointId::EndHandle(id) => vector_data.segment_from_id(*id).and_then(|bezier| bezier.handle_end()),
#[derive(Clone, Debug, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
pub struct OldVectorData {
pub transform: DAffine2,
pub alpha_blending: AlphaBlending,
pub style: PathStyle,
/// A list of all manipulator groups (referenced in `subpaths`) that have colinear handles (where they're locked at 180° angles from one another).
/// This gets read in `graph_operation_message_handler.rs` by calling `inputs.as_mut_slice()` (search for the string `"Shape does not have both `subpath` and `colinear_manipulators` inputs"` to find it).
pub colinear_manipulators: Vec<[HandleId; 2]>,
pub point_domain: PointDomain,
pub segment_domain: SegmentDomain,
pub region_domain: RegionDomain,
// Used to store the upstream group during destructive Boolean Operations (and other nodes with a similar effect) so that click targets can be preserved.
pub upstream_graphic_group: Option<Table<Graphic>>,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
enum EitherFormat {
Vector(Vector),
OldVectorData(OldVectorData),
VectorTable(Table<Vector>),
}
Ok(match EitherFormat::deserialize(deserializer)? {
EitherFormat::Vector(vector) => Table::new_from_element(vector),
EitherFormat::OldVectorData(old) => {
let mut vector_table = Table::new_from_element(Vector {
style: old.style,
colinear_manipulators: old.colinear_manipulators,
point_domain: old.point_domain,
segment_domain: old.segment_domain,
region_domain: old.region_domain,
upstream_group: old.upstream_graphic_group,
});
*vector_table.iter_mut().next().unwrap().transform = old.transform;
*vector_table.iter_mut().next().unwrap().alpha_blending = old.alpha_blending;
vector_table
}
}
pub fn get_anchor_position(&self, vector_data: &VectorData) -> Option<DVec2> {
match self {
ManipulatorPointId::EndHandle(_) | ManipulatorPointId::PrimaryHandle(_) => self.get_anchor(vector_data).and_then(|id| vector_data.point_domain.position_from_id(id)),
_ => self.get_position(vector_data),
}
}
/// Attempt to get a pair of handles. For an anchor this is the first two handles connected. For a handle it is self and the first opposing handle.
#[must_use]
pub fn get_handle_pair(self, vector_data: &VectorData) -> Option<[HandleId; 2]> {
match self {
ManipulatorPointId::Anchor(point) => vector_data.all_connected(point).take(2).collect::<Vec<_>>().try_into().ok(),
ManipulatorPointId::PrimaryHandle(segment) => {
let point = vector_data.segment_domain.segment_start_from_id(segment)?;
let current = HandleId::primary(segment);
let other = vector_data.segment_domain.all_connected(point).find(|&value| value != current);
other.map(|other| [current, other])
}
ManipulatorPointId::EndHandle(segment) => {
let point = vector_data.segment_domain.segment_end_from_id(segment)?;
let current = HandleId::end(segment);
let other = vector_data.segment_domain.all_connected(point).find(|&value| value != current);
other.map(|other| [current, other])
}
}
}
/// Finds all the connected handles of a point.
/// For an anchor it is all the connected handles.
/// For a handle it is all the handles connected to its corresponding anchor other than the current handle.
pub fn get_all_connected_handles(self, vector_data: &VectorData) -> Option<Vec<HandleId>> {
match self {
ManipulatorPointId::Anchor(point) => {
let connected = vector_data.all_connected(point).collect::<Vec<_>>();
Some(connected)
}
ManipulatorPointId::PrimaryHandle(segment) => {
let point = vector_data.segment_domain.segment_start_from_id(segment)?;
let current = HandleId::primary(segment);
let connected = vector_data.segment_domain.all_connected(point).filter(|&value| value != current).collect::<Vec<_>>();
Some(connected)
}
ManipulatorPointId::EndHandle(segment) => {
let point = vector_data.segment_domain.segment_end_from_id(segment)?;
let current = HandleId::end(segment);
let connected = vector_data.segment_domain.all_connected(point).filter(|&value| value != current).collect::<Vec<_>>();
Some(connected)
}
}
}
/// Attempt to find the closest anchor. If self is already an anchor then it is just self. If it is a start or end handle, then the start or end point is chosen.
#[must_use]
pub fn get_anchor(self, vector_data: &VectorData) -> Option<PointId> {
match self {
ManipulatorPointId::Anchor(point) => Some(point),
ManipulatorPointId::PrimaryHandle(segment) => vector_data.segment_start_from_id(segment),
ManipulatorPointId::EndHandle(segment) => vector_data.segment_end_from_id(segment),
}
}
/// Attempt to convert self to a [`HandleId`], returning none for an anchor.
#[must_use]
pub fn as_handle(self) -> Option<HandleId> {
match self {
ManipulatorPointId::PrimaryHandle(segment) => Some(HandleId::primary(segment)),
ManipulatorPointId::EndHandle(segment) => Some(HandleId::end(segment)),
ManipulatorPointId::Anchor(_) => None,
}
}
/// Attempt to convert self to an anchor, returning None for a handle.
#[must_use]
pub fn as_anchor(self) -> Option<PointId> {
match self {
ManipulatorPointId::Anchor(point) => Some(point),
_ => None,
}
}
pub fn get_segment(self) -> Option<SegmentId> {
match self {
ManipulatorPointId::PrimaryHandle(segment) | ManipulatorPointId::EndHandle(segment) => Some(segment),
_ => None,
}
}
}
/// The type of handle found on a bézier curve.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
pub enum HandleType {
/// The first handle on a cubic bézier or the only handle on a quadratic bézier.
Primary,
/// The second handle on a cubic bézier.
End,
}
/// Represents a primary or end handle found in a particular segment.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
pub struct HandleId {
pub ty: HandleType,
pub segment: SegmentId,
}
impl std::fmt::Display for HandleId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.ty {
// I haven't checked if "out" and "in" are reversed, or are accurate translations of the "primary" and "end" terms used in the `HandleType` enum, so this naming is an assumption.
HandleType::Primary => write!(f, "{} out", self.segment.inner()),
HandleType::End => write!(f, "{} in", self.segment.inner()),
}
}
}
impl HandleId {
/// Construct a handle for the first handle on a cubic bézier or the only handle on a quadratic bézier.
#[must_use]
pub const fn primary(segment: SegmentId) -> Self {
Self { ty: HandleType::Primary, segment }
}
/// Construct a handle for the end handle on a cubic bézier.
#[must_use]
pub const fn end(segment: SegmentId) -> Self {
Self { ty: HandleType::End, segment }
}
/// Convert to [`ManipulatorPointId`].
#[must_use]
pub fn to_manipulator_point(self) -> ManipulatorPointId {
match self.ty {
HandleType::Primary => ManipulatorPointId::PrimaryHandle(self.segment),
HandleType::End => ManipulatorPointId::EndHandle(self.segment),
}
}
/// Calculate the magnitude of the handle from the anchor.
pub fn length(self, vector_data: &VectorData) -> f64 {
let Some(anchor_position) = self.to_manipulator_point().get_anchor_position(vector_data) else {
// TODO: This was previously an unwrap which was encountered, so this is a temporary way to avoid a crash
return 0.;
};
let handle_position = self.to_manipulator_point().get_position(vector_data);
handle_position.map(|pos| (pos - anchor_position).length()).unwrap_or(f64::MAX)
}
/// Convert an end handle to the primary handle and a primary handle to an end handle. Note that the new handle may not exist (e.g. for a quadratic bézier).
#[must_use]
pub fn opposite(self) -> Self {
match self.ty {
HandleType::Primary => Self::end(self.segment),
HandleType::End => Self::primary(self.segment),
}
}
}
#[cfg(test)]
fn assert_subpath_eq(generated: &[bezier_rs::Subpath<PointId>], expected: &[bezier_rs::Subpath<PointId>]) {
assert_eq!(generated.len(), expected.len());
for (generated, expected) in generated.iter().zip(expected) {
assert_eq!(generated.manipulator_groups().len(), expected.manipulator_groups().len());
assert_eq!(generated.closed(), expected.closed());
for (generated, expected) in generated.manipulator_groups().iter().zip(expected.manipulator_groups()) {
assert_eq!(generated.in_handle, expected.in_handle);
assert_eq!(generated.out_handle, expected.out_handle);
assert_eq!(generated.anchor, expected.anchor);
}
}
EitherFormat::VectorTable(vector_table) => vector_table,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_subpath_eq(generated: &[bezier_rs::Subpath<PointId>], expected: &[bezier_rs::Subpath<PointId>]) {
assert_eq!(generated.len(), expected.len());
for (generated, expected) in generated.iter().zip(expected) {
assert_eq!(generated.manipulator_groups().len(), expected.manipulator_groups().len());
assert_eq!(generated.closed(), expected.closed());
for (generated, expected) in generated.manipulator_groups().iter().zip(expected.manipulator_groups()) {
assert_eq!(generated.in_handle, expected.in_handle);
assert_eq!(generated.out_handle, expected.out_handle);
assert_eq!(generated.anchor, expected.anchor);
}
}
}
#[test]
fn construct_closed_subpath() {
let circle = bezier_rs::Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
let vector_data = VectorData::from_subpath(&circle);
assert_eq!(vector_data.point_domain.ids().len(), 4);
let bezier_paths = vector_data.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
let vector = Vector::from_subpath(&circle);
assert_eq!(vector.point_domain.ids().len(), 4);
let bezier_paths = vector.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
assert_eq!(bezier_paths.len(), 4);
assert!(bezier_paths.iter().all(|&bezier| circle.iter().any(|original_bezier| original_bezier == bezier)));
let generated = vector_data.stroke_bezier_paths().collect::<Vec<_>>();
let generated = vector.stroke_bezier_paths().collect::<Vec<_>>();
assert_subpath_eq(&generated, &[circle]);
}
@ -729,12 +542,12 @@ mod tests {
fn construct_open_subpath() {
let bezier = bezier_rs::Bezier::from_cubic_dvec2(DVec2::ZERO, DVec2::NEG_ONE, DVec2::ONE, DVec2::X);
let subpath = bezier_rs::Subpath::from_bezier(&bezier);
let vector_data = VectorData::from_subpath(&subpath);
assert_eq!(vector_data.point_domain.ids().len(), 2);
let bezier_paths = vector_data.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
let vector = Vector::from_subpath(&subpath);
assert_eq!(vector.point_domain.ids().len(), 2);
let bezier_paths = vector.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
assert_eq!(bezier_paths, vec![bezier]);
let generated = vector_data.stroke_bezier_paths().collect::<Vec<_>>();
let generated = vector.stroke_bezier_paths().collect::<Vec<_>>();
assert_subpath_eq(&generated, &[subpath]);
}
@ -744,14 +557,14 @@ mod tests {
let curve = bezier_rs::Subpath::from_bezier(&curve);
let circle = bezier_rs::Subpath::new_ellipse(DVec2::NEG_ONE, DVec2::ONE);
let vector_data = VectorData::from_subpaths([&curve, &circle], false);
assert_eq!(vector_data.point_domain.ids().len(), 6);
let vector = Vector::from_subpaths([&curve, &circle], false);
assert_eq!(vector.point_domain.ids().len(), 6);
let bezier_paths = vector_data.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
let bezier_paths = vector.segment_bezier_iter().map(|(_, bezier, _, _)| bezier).collect::<Vec<_>>();
assert_eq!(bezier_paths.len(), 5);
assert!(bezier_paths.iter().all(|&bezier| circle.iter().chain(curve.iter()).any(|original_bezier| original_bezier == bezier)));
let generated = vector_data.stroke_bezier_paths().collect::<Vec<_>>();
let generated = vector.stroke_bezier_paths().collect::<Vec<_>>();
assert_subpath_eq(&generated, &[curve, circle]);
}
}

View file

@ -4,7 +4,7 @@ use glam::{DAffine2, DVec2};
use graphene_core::table::{Table, TableRow, TableRowRef};
use graphene_core::vector::algorithms::merge_by_distance::MergeByDistanceExt;
use graphene_core::vector::style::Fill;
use graphene_core::vector::{PointId, VectorData};
use graphene_core::vector::{PointId, Vector};
use graphene_core::{Color, Ctx, Graphic};
pub use path_bool as path_bool_lib;
use path_bool::{FillRule, PathBooleanOperation};
@ -35,7 +35,7 @@ pub enum BooleanOperation {
async fn boolean_operation<I: Into<Table<Graphic>> + 'n + Send + Clone>(
_: impl Ctx,
/// The group of paths to perform the boolean operation on. Nested groups are automatically flattened.
#[implementations(Table<Graphic>, Table<VectorData>)]
#[implementations(Table<Graphic>, Table<Vector>)]
group_of_paths: I,
/// Which boolean operation to perform on the paths.
///
@ -44,55 +44,55 @@ async fn boolean_operation<I: Into<Table<Graphic>> + 'n + Send + Clone>(
/// Intersection cuts away all but the overlapping areas shared by every path.
/// Difference cuts away the overlapping areas shared by every path, leaving only the non-overlapping areas.
operation: BooleanOperation,
) -> Table<VectorData> {
) -> Table<Vector> {
let group_of_paths = group_of_paths.into();
// The first index is the bottom of the stack
let mut result_vector_data_table = boolean_operation_on_vector_data_table(flatten_vector_data(&group_of_paths).iter_ref(), operation);
let mut result_vector_table = boolean_operation_on_vector_table(flatten_vector(&group_of_paths).iter(), operation);
// Replace the transformation matrix with a mutation of the vector points themselves
if let Some(result_vector_data) = result_vector_data_table.iter_mut().next() {
let transform = *result_vector_data.transform;
*result_vector_data.transform = DAffine2::IDENTITY;
if let Some(result_vector) = result_vector_table.iter_mut().next() {
let transform = *result_vector.transform;
*result_vector.transform = DAffine2::IDENTITY;
VectorData::transform(result_vector_data.element, transform);
result_vector_data.element.style.set_stroke_transform(DAffine2::IDENTITY);
result_vector_data.element.upstream_graphic_group = Some(group_of_paths.clone());
Vector::transform(result_vector.element, transform);
result_vector.element.style.set_stroke_transform(DAffine2::IDENTITY);
result_vector.element.upstream_group = Some(group_of_paths.clone());
// Clean up the boolean operation result by merging duplicated points
result_vector_data.element.merge_by_distance_spatial(*result_vector_data.transform, 0.0001);
result_vector.element.merge_by_distance_spatial(*result_vector.transform, 0.0001);
}
result_vector_data_table
result_vector_table
}
fn boolean_operation_on_vector_data_table<'a>(vector_data: impl DoubleEndedIterator<Item = TableRowRef<'a, VectorData>> + Clone, boolean_operation: BooleanOperation) -> Table<VectorData> {
fn boolean_operation_on_vector_table<'a>(vector: impl DoubleEndedIterator<Item = TableRowRef<'a, Vector>> + Clone, boolean_operation: BooleanOperation) -> Table<Vector> {
match boolean_operation {
BooleanOperation::Union => union(vector_data),
BooleanOperation::SubtractFront => subtract(vector_data),
BooleanOperation::SubtractBack => subtract(vector_data.rev()),
BooleanOperation::Intersect => intersect(vector_data),
BooleanOperation::Difference => difference(vector_data),
BooleanOperation::Union => union(vector),
BooleanOperation::SubtractFront => subtract(vector),
BooleanOperation::SubtractBack => subtract(vector.rev()),
BooleanOperation::Intersect => intersect(vector),
BooleanOperation::Difference => difference(vector),
}
}
fn union<'a>(vector_data: impl DoubleEndedIterator<Item = TableRowRef<'a, VectorData>>) -> Table<VectorData> {
// Reverse vector data so that the result style is the style of the first vector data
let mut vector_data_reversed = vector_data.rev();
fn union<'a>(vector: impl DoubleEndedIterator<Item = TableRowRef<'a, Vector>>) -> Table<Vector> {
// Reverse the vector table rows so that the result style is the style of the first vector row
let mut vector_reversed = vector.rev();
let mut result_vector_data_table = Table::new_from_row(vector_data_reversed.next().map(|x| x.into_cloned()).unwrap_or_default());
let mut first_row = result_vector_data_table.iter_mut().next().expect("Expected the one row we just pushed");
let mut result_vector_table = Table::new_from_row(vector_reversed.next().map(|x| x.into_cloned()).unwrap_or_default());
let mut first_row = result_vector_table.iter_mut().next().expect("Expected the one row we just pushed");
// Loop over all vector data and union it with the result
// Loop over all vector table rows and union it with the result
let default = TableRow::default();
let mut second_vector_data = Some(vector_data_reversed.next().unwrap_or(default.as_ref()));
while let Some(lower_vector_data) = second_vector_data {
let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector_data.transform;
let mut second_vector = Some(vector_reversed.next().unwrap_or(default.as_ref()));
while let Some(lower_vector) = second_vector {
let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector.transform;
let result = &mut first_row.element;
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.element, transform_of_lower_into_space_of_upper);
let lower_path_string = to_path(lower_vector.element, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_union(upper_path_string, lower_path_string) };
@ -103,27 +103,27 @@ fn union<'a>(vector_data: impl DoubleEndedIterator<Item = TableRowRef<'a, Vector
result.segment_domain = boolean_operation_result.segment_domain;
result.region_domain = boolean_operation_result.region_domain;
second_vector_data = vector_data_reversed.next();
second_vector = vector_reversed.next();
}
result_vector_data_table
result_vector_table
}
fn subtract<'a>(vector_data: impl Iterator<Item = TableRowRef<'a, VectorData>>) -> Table<VectorData> {
let mut vector_data = vector_data.into_iter();
fn subtract<'a>(vector: impl Iterator<Item = TableRowRef<'a, Vector>>) -> Table<Vector> {
let mut vector = vector.into_iter();
let mut result_vector_data_table = Table::new_from_row(vector_data.next().map(|x| x.into_cloned()).unwrap_or_default());
let mut first_row = result_vector_data_table.iter_mut().next().expect("Expected the one row we just pushed");
let mut result_vector_table = Table::new_from_row(vector.next().map(|x| x.into_cloned()).unwrap_or_default());
let mut first_row = result_vector_table.iter_mut().next().expect("Expected the one row we just pushed");
let mut next_vector_data = vector_data.next();
let mut next_vector = vector.next();
while let Some(lower_vector_data) = next_vector_data {
let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector_data.transform;
while let Some(lower_vector) = next_vector {
let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector.transform;
let result = &mut first_row.element;
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.element, transform_of_lower_into_space_of_upper);
let lower_path_string = to_path(lower_vector.element, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_subtract(upper_path_string, lower_path_string) };
@ -134,29 +134,29 @@ fn subtract<'a>(vector_data: impl Iterator<Item = TableRowRef<'a, VectorData>>)
result.segment_domain = boolean_operation_result.segment_domain;
result.region_domain = boolean_operation_result.region_domain;
next_vector_data = vector_data.next();
next_vector = vector.next();
}
result_vector_data_table
result_vector_table
}
fn intersect<'a>(vector_data: impl DoubleEndedIterator<Item = TableRowRef<'a, VectorData>>) -> Table<VectorData> {
let mut vector_data = vector_data.rev();
fn intersect<'a>(vector: impl DoubleEndedIterator<Item = TableRowRef<'a, Vector>>) -> Table<Vector> {
let mut vector = vector.rev();
let mut result_vector_data_table = Table::new_from_row(vector_data.next().map(|x| x.into_cloned()).unwrap_or_default());
let mut first_row = result_vector_data_table.iter_mut().next().expect("Expected the one row we just pushed");
let mut result_vector_table = Table::new_from_row(vector.next().map(|x| x.into_cloned()).unwrap_or_default());
let mut first_row = result_vector_table.iter_mut().next().expect("Expected the one row we just pushed");
let default = TableRow::default();
let mut second_vector_data = Some(vector_data.next().unwrap_or(default.as_ref()));
let mut second_vector = Some(vector.next().unwrap_or(default.as_ref()));
// For each vector data, set the result to the intersection of that data and the result
while let Some(lower_vector_data) = second_vector_data {
let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector_data.transform;
// For each vector table row, set the result to the intersection of that path and the current result
while let Some(lower_vector) = second_vector {
let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector.transform;
let result = &mut first_row.element;
let upper_path_string = to_path(result, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.element, transform_of_lower_into_space_of_upper);
let lower_path_string = to_path(lower_vector.element, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_operation_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
@ -166,28 +166,28 @@ fn intersect<'a>(vector_data: impl DoubleEndedIterator<Item = TableRowRef<'a, Ve
result.point_domain = boolean_operation_result.point_domain;
result.segment_domain = boolean_operation_result.segment_domain;
result.region_domain = boolean_operation_result.region_domain;
second_vector_data = vector_data.next();
second_vector = vector.next();
}
result_vector_data_table
result_vector_table
}
fn difference<'a>(vector_data: impl DoubleEndedIterator<Item = TableRowRef<'a, VectorData>> + Clone) -> Table<VectorData> {
let mut vector_data_iter = vector_data.clone().rev();
fn difference<'a>(vector: impl DoubleEndedIterator<Item = TableRowRef<'a, Vector>> + Clone) -> Table<Vector> {
let mut vector_iter = vector.clone().rev();
let mut any_intersection = TableRow::default();
let default = TableRow::default();
let mut second_vector_data = Some(vector_data_iter.next().unwrap_or(default.as_ref()));
let mut second_vector = Some(vector_iter.next().unwrap_or(default.as_ref()));
// Find where all vector data intersect at least once
while let Some(lower_vector_data) = second_vector_data {
let filtered_vector_data = vector_data.clone().filter(|v| *v != lower_vector_data).collect::<Vec<_>>().into_iter();
let unioned = boolean_operation_on_vector_data_table(filtered_vector_data, BooleanOperation::Union);
let first_row = unioned.iter_ref().next().expect("Expected at least one row after the boolean union");
// Find where all vector table row paths intersect at least once
while let Some(lower_vector) = second_vector {
let filtered_vector = vector.clone().filter(|v| *v != lower_vector).collect::<Vec<_>>().into_iter();
let unioned = boolean_operation_on_vector_table(filtered_vector, BooleanOperation::Union);
let first_row = unioned.iter().next().expect("Expected at least one row after the boolean union");
let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector_data.transform;
let transform_of_lower_into_space_of_upper = first_row.transform.inverse() * *lower_vector.transform;
let upper_path_string = to_path(first_row.element, DAffine2::IDENTITY);
let lower_path_string = to_path(lower_vector_data.element, transform_of_lower_into_space_of_upper);
let lower_path_string = to_path(lower_vector.element, transform_of_lower_into_space_of_upper);
#[allow(unused_unsafe)]
let boolean_intersection_string = unsafe { boolean_intersect(upper_path_string, lower_path_string) };
@ -213,79 +213,79 @@ fn difference<'a>(vector_data: impl DoubleEndedIterator<Item = TableRowRef<'a, V
any_intersection.element.style = boolean_intersection_result.element.style.clone();
any_intersection.alpha_blending = boolean_intersection_result.alpha_blending;
second_vector_data = vector_data_iter.next();
second_vector = vector_iter.next();
}
// Subtract the area where they intersect at least once from the union of all vector data
let union = boolean_operation_on_vector_data_table(vector_data, BooleanOperation::Union);
boolean_operation_on_vector_data_table(union.iter_ref().chain(std::iter::once(any_intersection.as_ref())), BooleanOperation::SubtractFront)
// Subtract the area where they intersect at least once from the union of all vector paths
let union = boolean_operation_on_vector_table(vector, BooleanOperation::Union);
boolean_operation_on_vector_table(union.iter().chain(std::iter::once(any_intersection.as_ref())), BooleanOperation::SubtractFront)
}
fn flatten_vector_data(graphic_group_table: &Table<Graphic>) -> Table<VectorData> {
graphic_group_table
.iter_ref()
fn flatten_vector(group_table: &Table<Graphic>) -> Table<Vector> {
group_table
.iter()
.flat_map(|element| {
match element.element.clone() {
Graphic::VectorData(vector_data) => {
// Apply the parent group's transform to each element of vector data
vector_data
.iter()
.map(|mut sub_vector_data| {
sub_vector_data.transform = *element.transform * sub_vector_data.transform;
Graphic::Vector(vector) => {
// Apply the parent group's transform to each element of the vector table
vector
.into_iter()
.map(|mut sub_vector| {
sub_vector.transform = *element.transform * sub_vector.transform;
sub_vector_data
sub_vector
})
.collect::<Vec<_>>()
}
Graphic::RasterDataCPU(image) => {
Graphic::RasterCPU(image) => {
let make_row = |transform| {
// Convert the image frame into a rectangular subpath with the image's transform
let mut subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
subpath.apply_transform(transform);
// Create a vector data table row from the rectangular subpath, with a default black fill
let mut element = VectorData::from_subpath(subpath);
// Create a vector table row from the rectangular subpath, with a default black fill
let mut element = Vector::from_subpath(subpath);
element.style.set_fill(Fill::Solid(Color::BLACK));
TableRow { element, ..Default::default() }
};
// Apply the parent group's transform to each element of raster data
image.iter_ref().map(|row| make_row(*element.transform * *row.transform)).collect::<Vec<_>>()
// Apply the parent group's transform to each raster element
image.iter().map(|row| make_row(*element.transform * *row.transform)).collect::<Vec<_>>()
}
Graphic::RasterDataGPU(image) => {
Graphic::RasterGPU(image) => {
let make_row = |transform| {
// Convert the image frame into a rectangular subpath with the image's transform
let mut subpath = Subpath::new_rect(DVec2::ZERO, DVec2::ONE);
subpath.apply_transform(transform);
// Create a vector data table row from the rectangular subpath, with a default black fill
let mut element = VectorData::from_subpath(subpath);
// Create a vector table row from the rectangular subpath, with a default black fill
let mut element = Vector::from_subpath(subpath);
element.style.set_fill(Fill::Solid(Color::BLACK));
TableRow { element, ..Default::default() }
};
// Apply the parent group's transform to each element of raster data
image.iter_ref().map(|row| make_row(*element.transform * *row.transform)).collect::<Vec<_>>()
// Apply the parent group's transform to each raster element
image.iter().map(|row| make_row(*element.transform * *row.transform)).collect::<Vec<_>>()
}
Graphic::GraphicGroup(mut graphic_group) => {
Graphic::Group(mut group) => {
// Apply the parent group's transform to each element of inner group
for sub_element in graphic_group.iter_mut() {
for sub_element in group.iter_mut() {
*sub_element.transform = *element.transform * *sub_element.transform;
}
// Recursively flatten the inner group into vector data
let unioned = boolean_operation_on_vector_data_table(flatten_vector_data(&graphic_group).iter_ref(), BooleanOperation::Union);
// Recursively flatten the inner group into the vector table
let unioned = boolean_operation_on_vector_table(flatten_vector(&group).iter(), BooleanOperation::Union);
unioned.iter().collect::<Vec<_>>()
unioned.into_iter().collect::<Vec<_>>()
}
}
})
.collect()
}
fn to_path(vector: &VectorData, transform: DAffine2) -> Vec<path_bool::PathSegment> {
fn to_path(vector: &Vector, transform: DAffine2) -> Vec<path_bool::PathSegment> {
let mut path = Vec::new();
for subpath in vector.stroke_bezier_paths() {
to_path_segments(&mut path, &subpath, transform);
@ -318,7 +318,7 @@ fn to_path_segments(path: &mut Vec<path_bool::PathSegment>, subpath: &Subpath<Po
}
}
fn from_path(path_data: &[Path]) -> VectorData {
fn from_path(path_data: &[Path]) -> Vector {
const EPSILON: f64 = 1e-5;
fn is_close(a: DVec2, b: DVec2) -> bool {
@ -362,7 +362,7 @@ fn from_path(path_data: &[Path]) -> VectorData {
}
}
VectorData::from_subpaths(all_subpaths, false)
Vector::from_subpaths(all_subpaths, false)
}
type Path = Vec<path_bool::PathSegment>;

View file

@ -920,7 +920,7 @@ impl NodeNetwork {
if !node.visible && node.implementation != identity_node {
node.implementation = identity_node;
// Connect layer node to the graphic group below
// Connect layer node to the group below
node.inputs.drain(1..);
node.manual_composition = None;
self.nodes.insert(id, node);

View file

@ -12,7 +12,7 @@ use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
use graphene_core::transform::ReferencePoint;
use graphene_core::uuid::NodeId;
use graphene_core::vector::VectorData;
use graphene_core::vector::Vector;
use graphene_core::vector::style::Fill;
use graphene_core::{Artboard, Color, Graphic, MemoHash, Node, Type};
use graphene_svg_renderer::RenderMetadata;
@ -180,17 +180,19 @@ tagged_value! {
// ===========
// TABLE TYPES
// ===========
#[serde(alias = "GraphicElement")]
Graphic(Graphic),
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::vector::migrate_vector_data"))] // TODO: Eventually remove this migration document upgrade code
VectorData(Table<VectorData>),
GraphicUnused(Graphic), // TODO: This is unused but removing it causes `cargo test` to infinitely recurse its type solving; figure out why and then remove this
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::vector::migrate_vector"))] // TODO: Eventually remove this migration document upgrade code
#[serde(alias = "VectorData")]
Vector(Table<Vector>),
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] // TODO: Eventually remove this migration document upgrade code
#[serde(alias = "ImageFrame")]
RasterData(Table<Raster<CPU>>),
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::graphic_element::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code
GraphicGroup(Table<Graphic>),
#[serde(alias = "ImageFrame", alias = "RasterData")]
Raster(Table<Raster<CPU>>),
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::graphic::migrate_group"))] // TODO: Eventually remove this migration document upgrade code
#[serde(alias = "GraphicGroup")]
Group(Table<Graphic>),
#[cfg_attr(target_family = "wasm", serde(deserialize_with = "graphene_core::artboard::migrate_artboard_group"))] // TODO: Eventually remove this migration document upgrade code
ArtboardGroup(Table<Artboard>),
#[serde(alias = "ArtboardGroup")]
Artboard(Table<Artboard>),
// ============
// STRUCT TYPES
// ============

View file

@ -38,7 +38,7 @@ mod blend_std {
impl Blend<Color> for Table<Raster<CPU>> {
fn blend(&self, under: &Self, blend_fn: impl Fn(Color, Color) -> Color) -> Self {
let mut result_table = self.clone();
for (over, under) in result_table.iter_mut().zip(under.iter_ref()) {
for (over, under) in result_table.iter_mut().zip(under.iter()) {
let data = over.element.data.iter().zip(under.element.data.iter()).map(|(a, b)| blend_fn(*a, *b)).collect();
*over.element = Raster::new_cpu(Image {
@ -203,7 +203,7 @@ mod test {
let opacity = 100_f64;
let result = super::color_overlay((), Table::new_from_element(Raster::new_cpu(image.clone())), overlay_color, BlendMode::Multiply, opacity);
let result = result.iter_ref().next().unwrap().element;
let result = result.iter().next().unwrap().element;
// The output should just be the original green and alpha channels (as we multiply them by 1 and other channels by 0)
assert_eq!(result.data[0], Color::from_rgbaf32_unchecked(0., image_color.g(), 0., image_color.a()));

View file

@ -10,7 +10,7 @@ use std::cmp::{max, min};
#[node_macro::node(category("Raster: Filter"))]
async fn dehaze(_: impl Ctx, image_frame: Table<Raster<CPU>>, strength: Percentage) -> Table<Raster<CPU>> {
image_frame
.iter()
.into_iter()
.map(|mut row| {
let image = row.element;
// Prepare the image data for processing

View file

@ -22,7 +22,7 @@ async fn blur(
gamma: bool,
) -> Table<Raster<CPU>> {
image_frame
.iter()
.into_iter()
.map(|mut row| {
let image = row.element.clone();

View file

@ -18,7 +18,7 @@ async fn image_color_palette(
let mut histogram: Vec<usize> = vec![0; (bins + 1.) as usize];
let mut colors: Vec<Vec<Color>> = vec![vec![]; (bins + 1.) as usize];
for row in image.iter_ref() {
for row in image.iter() {
for pixel in row.element.data.iter() {
let r = pixel.r() * GRID;
let g = pixel.g() * GRID;

View file

@ -32,7 +32,7 @@ impl From<std::io::Error> for Error {
#[node_macro::node(category("Debug: Raster"))]
pub fn sample_image(ctx: impl ExtractFootprint + Clone + Send, image_frame: Table<Raster<CPU>>) -> Table<Raster<CPU>> {
image_frame
.iter()
.into_iter()
.filter_map(|mut row| {
let image_frame_transform = row.transform;
let image = row.element;
@ -104,10 +104,10 @@ pub fn combine_channels(
#[expose] alpha: Table<Raster<CPU>>,
) -> Table<Raster<CPU>> {
let max_len = red.len().max(green.len()).max(blue.len()).max(alpha.len());
let red = red.iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
let green = green.iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
let blue = blue.iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
let alpha = alpha.iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
let red = red.into_iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
let green = green.into_iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
let blue = blue.into_iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
let alpha = alpha.into_iter().map(Some).chain(std::iter::repeat(None)).take(max_len);
red.zip(green)
.zip(blue)
@ -193,14 +193,14 @@ pub fn mask(
stencil: Table<Raster<CPU>>,
) -> Table<Raster<CPU>> {
// TODO: Figure out what it means to support multiple stencil rows?
let Some(stencil) = stencil.iter().next() else {
let Some(stencil) = stencil.into_iter().next() else {
// No stencil provided so we return the original image
return image;
};
let stencil_size = DVec2::new(stencil.element.width as f64, stencil.element.height as f64);
image
.iter()
.into_iter()
.filter_map(|mut row| {
let image_size = DVec2::new(row.element.width as f64, row.element.height as f64);
let mask_size = stencil.transform.decompose_scale();
@ -235,7 +235,7 @@ pub fn mask(
#[node_macro::node(category(""))]
pub fn extend_image_to_bounds(_: impl Ctx, image: Table<Raster<CPU>>, bounds: DAffine2) -> Table<Raster<CPU>> {
image
.iter()
.into_iter()
.map(|mut row| {
let image_aabb = Bbox::unit().affine_transform(row.transform).to_axis_aligned_bbox();
let bounds_aabb = Bbox::unit().affine_transform(bounds.transform()).to_axis_aligned_bbox();
@ -246,7 +246,7 @@ pub fn extend_image_to_bounds(_: impl Ctx, image: Table<Raster<CPU>>, bounds: DA
let image_data = &row.element.data;
let (image_width, image_height) = (row.element.width, row.element.height);
if image_width == 0 || image_height == 0 {
return empty_image((), bounds, Color::TRANSPARENT).iter().next().unwrap();
return empty_image((), bounds, Color::TRANSPARENT).into_iter().next().unwrap();
}
let orig_image_scale = DVec2::new(image_width as f64, image_height as f64);

View file

@ -1,6 +1,6 @@
use graph_craft::wasm_application_io::WasmEditorApi;
pub use graphene_core::text::*;
use graphene_core::{Ctx, table::Table, vector::VectorData};
use graphene_core::{Ctx, table::Table, vector::Vector};
#[node_macro::node(category(""))]
fn text<'i: 'n>(
@ -28,10 +28,10 @@ fn text<'i: 'n>(
#[default(0.)]
tilt: f64,
align: TextAlign,
/// Splits each text glyph into its own row in the table of vector data.
/// Splits each text glyph into its own row in the table of vector geometry.
#[default(false)]
per_glyph_instances: bool,
) -> Table<VectorData> {
) -> Table<Vector> {
let typesetting = TypesettingConfig {
font_size,
line_height_ratio,

View file

@ -9,7 +9,7 @@ use graphene_core::raster::image::Image;
use graphene_core::raster_types::{CPU, Raster};
use graphene_core::table::Table;
use graphene_core::transform::Footprint;
use graphene_core::vector::VectorData;
use graphene_core::vector::Vector;
use graphene_core::{Color, Context, Ctx, ExtractFootprint, Graphic, OwnedContextImpl, WasmNotSend};
use graphene_svg_renderer::RenderMetadata;
use graphene_svg_renderer::{Render, RenderParams, RenderSvgSegmentList, SvgRender, format_transform_matrix};
@ -101,7 +101,7 @@ fn string_to_bytes(_: impl Ctx, string: String) -> Vec<u8> {
#[node_macro::node(category("Web Request"), name("Image to Bytes"))]
fn image_to_bytes(_: impl Ctx, image: Table<Raster<CPU>>) -> Vec<u8> {
let Some(image) = image.iter_ref().next() else { return vec![] };
let Some(image) = image.iter().next() else { return vec![] };
image.element.data.iter().flat_map(|color| color.to_rgb8_srgb().into_iter()).collect::<Vec<u8>>()
}
@ -215,7 +215,7 @@ async fn render_canvas(render_config: RenderConfig, data: impl Render, editor: &
async fn rasterize<T: WasmNotSend + 'n>(
_: impl Ctx,
#[implementations(
Table<VectorData>,
Table<Vector>,
Table<Raster<CPU>>,
Table<Graphic>,
)]
@ -283,7 +283,7 @@ async fn render<'a: 'n, T: 'n + Render + WasmNotSend>(
render_config: RenderConfig,
editor_api: impl Node<Context<'static>, Output = &'a WasmEditorApi>,
#[implementations(
Context -> Table<VectorData>,
Context -> Table<Vector>,
Context -> Table<Raster<CPU>>,
Context -> Table<Graphic>,
Context -> Table<Artboard>,

View file

@ -13,7 +13,7 @@ use graphene_core::render_complexity::RenderComplexity;
use graphene_core::table::{Table, TableRow};
use graphene_core::transform::{Footprint, Transform};
use graphene_core::uuid::{NodeId, generate_uuid};
use graphene_core::vector::VectorData;
use graphene_core::vector::Vector;
use graphene_core::vector::click_target::{ClickTarget, FreePoint};
use graphene_core::vector::style::{Fill, Stroke, StrokeAlign, ViewMode};
use graphene_core::{Artboard, Graphic};
@ -236,7 +236,7 @@ pub trait Render: BoundingBox + RenderComplexity {
impl Render for Table<Graphic> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
let mut iter = self.iter_ref().peekable();
let mut iter = self.iter().peekable();
let mut mask_state = None;
while let Some(row) = iter.next() {
@ -288,7 +288,7 @@ impl Render for Table<Graphic> {
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
let mut iter = self.iter_ref().peekable();
let mut iter = self.iter().peekable();
let mut mask_element_and_transform = None;
while let Some(row) = iter.next() {
@ -356,7 +356,7 @@ impl Render for Table<Graphic> {
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
for row in self.iter_ref() {
for row in self.iter() {
if let Some(element_id) = row.source_node_id {
let mut footprint = footprint;
footprint.transform *= *row.transform;
@ -365,10 +365,10 @@ impl Render for Table<Graphic> {
}
}
if let Some(graphic_group_id) = element_id {
if let Some(group_id) = element_id {
let mut all_upstream_click_targets = Vec::new();
for row in self.iter_ref() {
for row in self.iter() {
let mut new_click_targets = Vec::new();
row.element.add_upstream_click_targets(&mut new_click_targets);
@ -379,12 +379,12 @@ impl Render for Table<Graphic> {
all_upstream_click_targets.extend(new_click_targets);
}
metadata.click_targets.insert(graphic_group_id, all_upstream_click_targets);
metadata.click_targets.insert(group_id, all_upstream_click_targets);
}
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for row in self.iter_ref() {
for row in self.iter() {
let mut new_click_targets = Vec::new();
row.element.add_upstream_click_targets(&mut new_click_targets);
@ -398,7 +398,7 @@ impl Render for Table<Graphic> {
}
fn contains_artboard(&self) -> bool {
self.iter_ref().any(|row| row.element.contains_artboard())
self.iter().any(|row| row.element.contains_artboard())
}
fn new_ids_from_hash(&mut self, _reference: Option<NodeId>) {
@ -408,20 +408,20 @@ impl Render for Table<Graphic> {
}
}
impl Render for Table<VectorData> {
impl Render for Table<Vector> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for row in self.iter_ref() {
for row in self.iter() {
let multiplied_transform = *row.transform;
let vector_data = &row.element;
let vector = &row.element;
// Only consider strokes with non-zero weight, since default strokes with zero weight would prevent assigning the correct stroke transform
let has_real_stroke = vector_data.style.stroke().filter(|stroke| stroke.weight() > 0.);
let has_real_stroke = vector.style.stroke().filter(|stroke| stroke.weight() > 0.);
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
let applied_stroke_transform = set_stroke_transform.unwrap_or(*row.transform);
let applied_stroke_transform = render_params.alignment_parent_transform.unwrap_or(applied_stroke_transform);
let element_transform = set_stroke_transform.map(|stroke_transform| multiplied_transform * stroke_transform.inverse());
let element_transform = element_transform.unwrap_or(DAffine2::IDENTITY);
let layer_bounds = vector_data.bounding_box().unwrap_or_default();
let transformed_bounds = vector_data.bounding_box_with_transform(applied_stroke_transform).unwrap_or_default();
let layer_bounds = vector.bounding_box().unwrap_or_default();
let transformed_bounds = vector.bounding_box_with_transform(applied_stroke_transform).unwrap_or_default();
let mut path = String::new();
@ -429,12 +429,12 @@ impl Render for Table<VectorData> {
let _ = subpath.subpath_to_svg(&mut path, applied_stroke_transform);
}
let connected = vector_data.stroke_bezier_paths().all(|path| path.closed());
let can_draw_aligned_stroke = vector_data.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered()) && connected;
let connected = vector.stroke_bezier_paths().all(|path| path.closed());
let can_draw_aligned_stroke = vector.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered()) && connected;
let mut push_id = None;
if can_draw_aligned_stroke {
let mask_type = if vector_data.style.stroke().unwrap().align == StrokeAlign::Inside {
let mask_type = if vector.style.stroke().unwrap().align == StrokeAlign::Inside {
MaskType::Clip
} else {
MaskType::Mask
@ -519,7 +519,7 @@ impl Render for Table<VectorData> {
use vello::kurbo::{Cap, Join};
use vello::peniko;
for row in self.iter_ref() {
for row in self.iter() {
let multiplied_transform = parent_transform * *row.transform;
let has_real_stroke = row.element.style.stroke().filter(|stroke| stroke.weight() > 0.);
let set_stroke_transform = has_real_stroke.map(|stroke| stroke.transform).filter(|transform| transform.matrix2.determinant() != 0.);
@ -566,7 +566,7 @@ impl Render for Table<VectorData> {
element.style.clear_stroke();
element.style.set_fill(Fill::solid(Color::BLACK));
let vector_data = Table::new_from_row(TableRow {
let vector_table = Table::new_from_row(TableRow {
element,
alpha_blending: *row.alpha_blending,
transform: *row.transform,
@ -580,7 +580,7 @@ impl Render for Table<VectorData> {
let rect = kurbo::Rect::new(bounds[0].x, bounds[0].y, bounds[1].x, bounds[1].y);
scene.push_layer(peniko::Mix::Normal, 1., kurbo::Affine::IDENTITY, &rect);
vector_data.render_to_vello(scene, parent_transform, _context, &render_params.for_alignment(applied_stroke_transform));
vector_table.render_to_vello(scene, parent_transform, _context, &render_params.for_alignment(applied_stroke_transform));
scene.push_layer(peniko::BlendMode::new(peniko::Mix::Clip, peniko::Compose::SrcIn), 1., kurbo::Affine::IDENTITY, &rect);
}
@ -728,13 +728,13 @@ impl Render for Table<VectorData> {
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, mut footprint: Footprint, element_id: Option<NodeId>) {
for row in self.iter_ref() {
for row in self.iter() {
let transform = *row.transform;
let vector_data = row.element;
let vector = row.element;
if let Some(element_id) = element_id {
let stroke_width = vector_data.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = vector_data.style.fill() != &Fill::None;
let stroke_width = vector.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = vector.style.fill() != &Fill::None;
let fill = |mut subpath: Subpath<_>| {
if filled {
subpath.set_closed(true);
@ -743,9 +743,9 @@ impl Render for Table<VectorData> {
};
// For free-floating anchors, we need to add a click target for each
let single_anchors_targets = vector_data.point_domain.ids().iter().filter_map(|&point_id| {
if vector_data.connected_count(point_id) == 0 {
let anchor = vector_data.point_domain.position_from_id(point_id).unwrap_or_default();
let single_anchors_targets = vector.point_domain.ids().iter().filter_map(|&point_id| {
if vector.connected_count(point_id) == 0 {
let anchor = vector.point_domain.position_from_id(point_id).unwrap_or_default();
let point = FreePoint::new(point_id, anchor);
Some(ClickTarget::new_with_free_point(point))
@ -754,7 +754,7 @@ impl Render for Table<VectorData> {
}
});
let click_targets = vector_data
let click_targets = vector
.stroke_bezier_paths()
.map(fill)
.map(|subpath| ClickTarget::new_with_subpath(subpath, stroke_width))
@ -764,15 +764,15 @@ impl Render for Table<VectorData> {
metadata.click_targets.entry(element_id).or_insert(click_targets);
}
if let Some(upstream_graphic_group) = &vector_data.upstream_graphic_group {
if let Some(upstream_group) = &vector.upstream_group {
footprint.transform *= transform;
upstream_graphic_group.collect_metadata(metadata, footprint, None);
upstream_group.collect_metadata(metadata, footprint, None);
}
}
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for row in self.iter_ref() {
for row in self.iter() {
let stroke_width = row.element.style.stroke().as_ref().map_or(0., Stroke::weight);
let filled = row.element.style.fill() != &Fill::None;
let fill = |mut subpath: Subpath<_>| {
@ -853,7 +853,7 @@ impl Render for Artboard {
},
// Artboard contents
|render| {
self.graphic_group.render_svg(render, render_params);
self.group.render_svg(render, render_params);
},
);
}
@ -875,9 +875,9 @@ impl Render for Artboard {
let blend_mode = peniko::BlendMode::new(peniko::Mix::Clip, peniko::Compose::SrcOver);
scene.push_layer(blend_mode, 1., kurbo::Affine::new(transform.to_cols_array()), &rect);
}
// Since the graphic group's transform is right multiplied in when rendering the graphic group, we just need to right multiply by the offset here.
// Since the group's transform is right multiplied in when rendering the group, we just need to right multiply by the offset here.
let child_transform = transform * DAffine2::from_translation(self.location.as_dvec2());
self.graphic_group.render_to_vello(scene, child_transform, context, render_params);
self.group.render_to_vello(scene, child_transform, context, render_params);
if self.clip {
scene.pop_layer();
}
@ -894,7 +894,7 @@ impl Render for Artboard {
}
}
footprint.transform *= self.transform();
self.graphic_group.collect_metadata(metadata, footprint, None);
self.group.collect_metadata(metadata, footprint, None);
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
@ -909,38 +909,38 @@ impl Render for Artboard {
impl Render for Table<Artboard> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for artboard in self.iter_ref() {
for artboard in self.iter() {
artboard.element.render_svg(render, render_params);
}
}
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
for row in self.iter_ref() {
for row in self.iter() {
row.element.render_to_vello(scene, transform, context, render_params);
}
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, _element_id: Option<NodeId>) {
for row in self.iter_ref() {
for row in self.iter() {
row.element.collect_metadata(metadata, footprint, *row.source_node_id);
}
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
for row in self.iter_ref() {
for row in self.iter() {
row.element.add_upstream_click_targets(click_targets);
}
}
fn contains_artboard(&self) -> bool {
self.iter_ref().count() > 0
self.iter().count() > 0
}
}
impl Render for Table<Raster<CPU>> {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
for row in self.iter_ref() {
for row in self.iter() {
let image = row.element;
let transform = *row.transform;
@ -1029,7 +1029,7 @@ impl Render for Table<Raster<CPU>> {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, _: &mut RenderContext, render_params: &RenderParams) {
use vello::peniko;
for row in self.iter_ref() {
for row in self.iter() {
let image = &row.element;
if image.data.is_empty() {
continue;
@ -1068,7 +1068,7 @@ impl Render for Table<Raster<CPU>> {
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.)]);
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of the graphical data table
if let Some(image) = self.iter_ref().next() {
if let Some(image) = self.iter().next() {
metadata.local_transforms.insert(element_id, *image.transform);
}
}
@ -1090,7 +1090,7 @@ impl Render for Table<Raster<GPU>> {
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, _render_params: &RenderParams) {
use vello::peniko;
for row in self.iter_ref() {
for row in self.iter() {
let blend_mode = *row.alpha_blending;
let mut layer = false;
if blend_mode != Default::default() {
@ -1126,7 +1126,7 @@ impl Render for Table<Raster<GPU>> {
metadata.click_targets.insert(element_id, vec![ClickTarget::new_with_subpath(subpath, 0.)]);
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of the graphical data table
if let Some(image) = self.iter_ref().next() {
if let Some(image) = self.iter().next() {
metadata.local_transforms.insert(element_id, *image.transform);
}
}
@ -1140,50 +1140,50 @@ impl Render for Table<Raster<GPU>> {
impl Render for Graphic {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams) {
match self {
Graphic::VectorData(vector_data) => vector_data.render_svg(render, render_params),
Graphic::RasterDataCPU(raster) => raster.render_svg(render, render_params),
Graphic::RasterDataGPU(_raster) => (),
Graphic::GraphicGroup(graphic_group) => graphic_group.render_svg(render, render_params),
Graphic::Vector(vector) => vector.render_svg(render, render_params),
Graphic::RasterCPU(raster) => raster.render_svg(render, render_params),
Graphic::RasterGPU(_) => (),
Graphic::Group(group) => group.render_svg(render, render_params),
}
}
#[cfg(feature = "vello")]
fn render_to_vello(&self, scene: &mut Scene, transform: DAffine2, context: &mut RenderContext, render_params: &RenderParams) {
match self {
Graphic::VectorData(vector_data) => vector_data.render_to_vello(scene, transform, context, render_params),
Graphic::RasterDataCPU(raster) => raster.render_to_vello(scene, transform, context, render_params),
Graphic::RasterDataGPU(raster) => raster.render_to_vello(scene, transform, context, render_params),
Graphic::GraphicGroup(graphic_group) => graphic_group.render_to_vello(scene, transform, context, render_params),
Graphic::Vector(vector) => vector.render_to_vello(scene, transform, context, render_params),
Graphic::RasterCPU(raster) => raster.render_to_vello(scene, transform, context, render_params),
Graphic::RasterGPU(raster) => raster.render_to_vello(scene, transform, context, render_params),
Graphic::Group(group) => group.render_to_vello(scene, transform, context, render_params),
}
}
fn collect_metadata(&self, metadata: &mut RenderMetadata, footprint: Footprint, element_id: Option<NodeId>) {
if let Some(element_id) = element_id {
match self {
Graphic::GraphicGroup(_) => {
Graphic::Group(_) => {
metadata.upstream_footprints.insert(element_id, footprint);
}
Graphic::VectorData(vector_data) => {
Graphic::Vector(vector) => {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of the graphical data table
if let Some(vector_data) = vector_data.iter_ref().next() {
metadata.first_element_source_id.insert(element_id, *vector_data.source_node_id);
metadata.local_transforms.insert(element_id, *vector_data.transform);
if let Some(vector) = vector.iter().next() {
metadata.first_element_source_id.insert(element_id, *vector.source_node_id);
metadata.local_transforms.insert(element_id, *vector.transform);
}
}
Graphic::RasterDataCPU(raster_frame) => {
Graphic::RasterCPU(raster_frame) => {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of images
if let Some(image) = raster_frame.iter_ref().next() {
if let Some(image) = raster_frame.iter().next() {
metadata.local_transforms.insert(element_id, *image.transform);
}
}
Graphic::RasterDataGPU(raster_frame) => {
Graphic::RasterGPU(raster_frame) => {
metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of images
if let Some(image) = raster_frame.iter_ref().next() {
if let Some(image) = raster_frame.iter().next() {
metadata.local_transforms.insert(element_id, *image.transform);
}
}
@ -1191,37 +1191,37 @@ impl Render for Graphic {
}
match self {
Graphic::VectorData(vector_data) => vector_data.collect_metadata(metadata, footprint, element_id),
Graphic::RasterDataCPU(raster) => raster.collect_metadata(metadata, footprint, element_id),
Graphic::RasterDataGPU(raster) => raster.collect_metadata(metadata, footprint, element_id),
Graphic::GraphicGroup(graphic_group) => graphic_group.collect_metadata(metadata, footprint, element_id),
Graphic::Vector(vector) => vector.collect_metadata(metadata, footprint, element_id),
Graphic::RasterCPU(raster) => raster.collect_metadata(metadata, footprint, element_id),
Graphic::RasterGPU(raster) => raster.collect_metadata(metadata, footprint, element_id),
Graphic::Group(group) => group.collect_metadata(metadata, footprint, element_id),
}
}
fn add_upstream_click_targets(&self, click_targets: &mut Vec<ClickTarget>) {
match self {
Graphic::VectorData(vector_data) => vector_data.add_upstream_click_targets(click_targets),
Graphic::RasterDataCPU(raster) => raster.add_upstream_click_targets(click_targets),
Graphic::RasterDataGPU(raster) => raster.add_upstream_click_targets(click_targets),
Graphic::GraphicGroup(graphic_group) => graphic_group.add_upstream_click_targets(click_targets),
Graphic::Vector(vector) => vector.add_upstream_click_targets(click_targets),
Graphic::RasterCPU(raster) => raster.add_upstream_click_targets(click_targets),
Graphic::RasterGPU(raster) => raster.add_upstream_click_targets(click_targets),
Graphic::Group(group) => group.add_upstream_click_targets(click_targets),
}
}
fn contains_artboard(&self) -> bool {
match self {
Graphic::VectorData(vector_data) => vector_data.contains_artboard(),
Graphic::GraphicGroup(graphic_group) => graphic_group.contains_artboard(),
Graphic::RasterDataCPU(raster) => raster.contains_artboard(),
Graphic::RasterDataGPU(raster) => raster.contains_artboard(),
Graphic::Vector(vector) => vector.contains_artboard(),
Graphic::Group(group) => group.contains_artboard(),
Graphic::RasterCPU(raster) => raster.contains_artboard(),
Graphic::RasterGPU(raster) => raster.contains_artboard(),
}
}
fn new_ids_from_hash(&mut self, reference: Option<NodeId>) {
match self {
Graphic::VectorData(vector_data) => vector_data.new_ids_from_hash(reference),
Graphic::GraphicGroup(graphic_group) => graphic_group.new_ids_from_hash(reference),
Graphic::RasterDataCPU(_) => (),
Graphic::RasterDataGPU(_) => (),
Graphic::Vector(vector) => vector.new_ids_from_hash(reference),
Graphic::Group(group) => group.new_ids_from_hash(reference),
Graphic::RasterCPU(_) => (),
Graphic::RasterGPU(_) => (),
}
}
}

View file

@ -16,7 +16,7 @@ use graphene_std::any::DowncastBothNode;
use graphene_std::any::{ComposeTypeErased, DynAnyNode, IntoTypeErasedNode};
use graphene_std::application_io::{ImageTexture, SurfaceFrame};
use graphene_std::table::Table;
use graphene_std::vector::VectorData;
use graphene_std::vector::Vector;
#[cfg(feature = "gpu")]
use graphene_std::wasm_application_io::{WasmEditorApi, WasmSurfaceHandle};
use node_registry_macros::{async_node, convert_node, into_node};
@ -31,9 +31,9 @@ use wgpu_executor::{WgpuSurface, WindowHandle};
// TODO: turn into hashmap
fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> {
let mut node_types: Vec<(ProtoNodeIdentifier, NodeConstructor, NodeIOTypes)> = vec![
into_node!(from: Table<VectorData>, to: Table<VectorData>),
into_node!(from: Table<VectorData>, to: Graphic),
into_node!(from: Table<VectorData>, to: Table<Graphic>),
into_node!(from: Table<Vector>, to: Table<Vector>),
into_node!(from: Table<Vector>, to: Graphic),
into_node!(from: Table<Vector>, to: Table<Graphic>),
into_node!(from: Table<Graphic>, to: Table<Graphic>),
into_node!(from: Table<Graphic>, to: Graphic),
into_node!(from: Table<Raster<CPU>>, to: Table<Raster<CPU>>),
@ -43,7 +43,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
into_node!(from: Table<Raster<CPU>>, to: Table<Graphic>),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => ImageTexture]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<VectorData>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Vector>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Table<Graphic>]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Graphic]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => Artboard]),
@ -78,7 +78,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::CentroidType]),
async_node!(graphene_core::memo::MonitorNode<_, _, _>, input: Context, fn_params: [Context => graphene_std::vector::misc::PointSpacingType]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Image<Color>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<VectorData>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Vector>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Raster<CPU>>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Table<Graphic>]),
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => Vec<DVec2>]),
@ -94,7 +94,7 @@ fn node_registry() -> HashMap<ProtoNodeIdentifier, HashMap<NodeIOTypes, NodeCons
async_node!(graphene_core::memo::MemoNode<_, _>, input: Context, fn_params: [Context => RenderOutput]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Graphic]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Table<Graphic>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Table<VectorData>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Table<Vector>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Table<Graphic>]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => WgpuSurface]),
async_node!(graphene_core::memo::ImpureMemoNode<_, _, _>, input: Context, fn_params: [Context => Option<WgpuSurface>]),

View file

@ -910,7 +910,7 @@ mod tests {
let attr = quote!(category("Vector: Shape"));
let input = quote!(
/// Test
fn circle(_: impl Ctx, #[default(50.)] radius: f64) -> VectorData {
fn circle(_: impl Ctx, #[default(50.)] radius: f64) -> Vector {
// Implementation details...
}
);
@ -937,7 +937,7 @@ mod tests {
ty: parse_quote!(impl Ctx),
implementations: Punctuated::new(),
},
output_type: parse_quote!(VectorData),
output_type: parse_quote!(Vector),
is_async: false,
fields: vec![ParsedField::Regular {
pat_ident: pat_ident("radius"),

View file

@ -11,7 +11,7 @@ pub async fn upload_texture<'a: 'n>(_: impl ExtractFootprint + Ctx, input: Table
let device = &executor.context.device;
let queue = &executor.context.queue;
let table = input
.iter_ref()
.iter()
.map(|row| {
let image = row.element;
let rgba8_data: Vec<SRGBA8> = image.data.iter().map(|x| (*x).into()).collect();