mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-12-23 10:11:54 +00:00
Merge branch 'master' into ir/ref-centroid
This commit is contained in:
commit
20afb25698
98 changed files with 2696 additions and 2151 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -3,6 +3,7 @@ target/
|
|||
*.exrc
|
||||
perf.data*
|
||||
profile.json
|
||||
profile.json.gz
|
||||
flamegraph.svg
|
||||
.idea/
|
||||
.direnv
|
||||
|
|
|
|||
23
.nix/flake.lock
generated
23
.nix/flake.lock
generated
|
|
@ -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"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
543
Cargo.lock
generated
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
2
demo-artwork/changing-seasons.graphite
generated
2
demo-artwork/changing-seasons.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/isometric-fountain.graphite
generated
2
demo-artwork/isometric-fountain.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/marbled-mandelbrot.graphite
generated
2
demo-artwork/marbled-mandelbrot.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/painted-dreams.graphite
generated
2
demo-artwork/painted-dreams.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/parametric-dunescape.graphite
generated
2
demo-artwork/parametric-dunescape.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/procedural-string-lights.graphite
generated
2
demo-artwork/procedural-string-lights.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/red-dress.graphite
generated
2
demo-artwork/red-dress.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/valley-of-spires.graphite
generated
2
demo-artwork/valley-of-spires.graphite
generated
File diff suppressed because one or more lines are too long
3
desktop/.gitignore
vendored
3
desktop/.gitignore
vendored
|
|
@ -1,3 +0,0 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
|
@ -37,3 +37,4 @@ bytemuck = { workspace = true }
|
|||
glam = { workspace = true }
|
||||
vello = { workspace = true }
|
||||
derivative = { workspace = true }
|
||||
rfd = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
22
desktop/src/dialogs.rs
Normal 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())
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
|
|
|
|||
|
|
@ -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>) {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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() };
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)?;
|
||||
|
|
|
|||
|
|
@ -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 }))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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)),
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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:#?}"));
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -679,7 +679,7 @@
|
|||
}
|
||||
|
||||
.color-vector {
|
||||
fill: var(--color-data-vectordata);
|
||||
fill: var(--color-data-vector);
|
||||
}
|
||||
|
||||
.color-raster {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>>,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>>,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.;
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
|
|
|
|||
|
|
@ -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}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
@ -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]
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
@ -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>;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// ============
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
|
|
|
|||
|
|
@ -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(_) => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>]),
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue