mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-01 07:08:12 +08:00
Merge branch 'master' into merge_point
This commit is contained in:
@@ -63,14 +63,14 @@ jobs:
|
|||||||
mkdir artifacts
|
mkdir artifacts
|
||||||
mv hierarchical_message_system_tree.txt artifacts/hierarchical_message_system_tree.txt
|
mv hierarchical_message_system_tree.txt artifacts/hierarchical_message_system_tree.txt
|
||||||
|
|
||||||
- name: 🚚 Move `artifacts` contents to `website/other/editor-structure`
|
- name: 🚚 Move `artifacts` contents to the project root
|
||||||
run: |
|
run: |
|
||||||
mv artifacts/* website/other/editor-structure
|
mv artifacts/* .
|
||||||
|
|
||||||
- name: 🔧 Build auto-generated code docs artifacts into HTML
|
- name: 🔧 Build auto-generated code docs artifacts into HTML
|
||||||
run: |
|
run: |
|
||||||
cd website/other/editor-structure
|
cd website
|
||||||
node generate.js hierarchical_message_system_tree.txt replacement.html
|
npm run generate-editor-structure
|
||||||
|
|
||||||
- name: 🌐 Build Graphite website with Zola
|
- name: 🌐 Build Graphite website with Zola
|
||||||
env:
|
env:
|
||||||
@@ -80,38 +80,6 @@ jobs:
|
|||||||
npm run install-fonts
|
npm run install-fonts
|
||||||
zola --config config.toml build --minify
|
zola --config config.toml build --minify
|
||||||
|
|
||||||
- name: 💿 Restore cache of `website/other/dist` directory, if available and `website/other` didn't change
|
|
||||||
if: steps.changes.outputs.website-other != 'true'
|
|
||||||
id: cache-website-other-dist
|
|
||||||
uses: actions/cache/restore@v3
|
|
||||||
with:
|
|
||||||
path: website/other/dist
|
|
||||||
key: website-other-dist-${{ runner.os }}
|
|
||||||
|
|
||||||
- name: 🟢 Set up Node only if we are going to build in the next step
|
|
||||||
if: steps.cache-website-other-dist.outputs.cache-hit != 'true'
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: "latest"
|
|
||||||
|
|
||||||
- name: 📁 Build `website/other` directory only if changed or not cached
|
|
||||||
if: steps.cache-website-other-dist.outputs.cache-hit != 'true'
|
|
||||||
id: build-website-other
|
|
||||||
run: |
|
|
||||||
sh website/other/build.sh
|
|
||||||
|
|
||||||
- name: 💾 Save cache of `website/other/dist` directory if it was built above
|
|
||||||
if: steps.cache-website-other-dist.outputs.cache-hit != 'true'
|
|
||||||
uses: actions/cache/save@v3
|
|
||||||
with:
|
|
||||||
path: website/other/dist
|
|
||||||
key: ${{ steps.cache-website-other-dist.outputs.cache-primary-key }}
|
|
||||||
|
|
||||||
- name: 🚚 Move `website/other/dist` contents to `website/public`
|
|
||||||
run: |
|
|
||||||
mkdir -p website/public
|
|
||||||
mv website/other/dist/* website/public
|
|
||||||
|
|
||||||
- name: 📤 Publish to Cloudflare Pages
|
- name: 📤 Publish to Cloudflare Pages
|
||||||
id: cloudflare
|
id: cloudflare
|
||||||
uses: cloudflare/pages-action@1
|
uses: cloudflare/pages-action@1
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ flamegraph.svg
|
|||||||
.idea/
|
.idea/
|
||||||
.direnv
|
.direnv
|
||||||
hierarchical_message_system_tree.txt
|
hierarchical_message_system_tree.txt
|
||||||
|
hierarchical_message_system_tree.html
|
||||||
|
|||||||
+10
-4
@@ -40,10 +40,10 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
libcef = pkgs.libcef.overrideAttrs (finalAttrs: previousAttrs: {
|
libcef = pkgs.libcef.overrideAttrs (finalAttrs: previousAttrs: {
|
||||||
version = "138.0.26";
|
version = "139.0.17";
|
||||||
gitRevision = "84f2d27";
|
gitRevision = "6c347eb";
|
||||||
chromiumVersion = "138.0.7204.158";
|
chromiumVersion = "139.0.7258.31";
|
||||||
srcHash = "sha256-d9jQJX7rgdoHfROD3zmOdMSesRdKE3slB5ZV+U2wlbQ=";
|
srcHash = "sha256-kRMO8DP4El1qytDsAZBdHvR9AAHXce90nPdyfJailBg=";
|
||||||
|
|
||||||
__intentionallyOverridingVersion = true;
|
__intentionallyOverridingVersion = true;
|
||||||
|
|
||||||
@@ -75,6 +75,12 @@
|
|||||||
vulkan-loader
|
vulkan-loader
|
||||||
libraw
|
libraw
|
||||||
libGL
|
libGL
|
||||||
|
|
||||||
|
# X11 libraries, not needed on wayland! Remove when x11 is finally dead
|
||||||
|
libxkbcommon
|
||||||
|
xorg.libXcursor
|
||||||
|
xorg.libxcb
|
||||||
|
xorg.libX11
|
||||||
];
|
];
|
||||||
|
|
||||||
# Development tools that don't need to be in LD_LIBRARY_PATH
|
# Development tools that don't need to be in LD_LIBRARY_PATH
|
||||||
|
|||||||
Vendored
+1
-1
@@ -35,7 +35,7 @@
|
|||||||
"rust-analyzer.cargo.allTargets": false,
|
"rust-analyzer.cargo.allTargets": false,
|
||||||
// ESLint config
|
// ESLint config
|
||||||
"eslint.format.enable": true,
|
"eslint.format.enable": true,
|
||||||
"eslint.workingDirectories": ["./frontend", "./website/other/bezier-rs-demos", "./website"],
|
"eslint.workingDirectories": ["./frontend", "./website"],
|
||||||
"eslint.validate": ["javascript", "typescript", "svelte"],
|
"eslint.validate": ["javascript", "typescript", "svelte"],
|
||||||
// Svelte config
|
// Svelte config
|
||||||
"svelte.plugin.svelte.compilerWarnings": {
|
"svelte.plugin.svelte.compilerWarnings": {
|
||||||
|
|||||||
Generated
+19
-41
@@ -461,27 +461,6 @@ version = "0.22.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bezier-rs"
|
|
||||||
version = "0.4.0"
|
|
||||||
dependencies = [
|
|
||||||
"dyn-any",
|
|
||||||
"glam",
|
|
||||||
"kurbo",
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bezier-rs-wasm"
|
|
||||||
version = "0.0.0"
|
|
||||||
dependencies = [
|
|
||||||
"bezier-rs",
|
|
||||||
"glam",
|
|
||||||
"js-sys",
|
|
||||||
"log",
|
|
||||||
"wasm-bindgen",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bincode"
|
name = "bincode"
|
||||||
version = "1.3.3"
|
version = "1.3.3"
|
||||||
@@ -683,19 +662,20 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cef"
|
name = "cef"
|
||||||
version = "138.5.0+138.0.26"
|
version = "139.0.1+139.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7bfa138b538b29584b02e990a631ef44261d9a70d35a77ce4ef624410046fe91"
|
checksum = "39b749cfc4124f9505b3fbe32279c0e93f30831f1ecf3c2cf85863179319cd7b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cef-dll-sys",
|
"cef-dll-sys",
|
||||||
|
"libloading",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cef-dll-sys"
|
name = "cef-dll-sys"
|
||||||
version = "138.5.0+138.0.26"
|
version = "139.0.1+139.0.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "658450855d0ef25af50420b95fbdfacb5df17c7eb20a1615ec995470c26ed643"
|
checksum = "fc42adb0adc477860b705e967d9f899be05ba3775fe4d6dc4d844a1391ffa3dd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cmake",
|
"cmake",
|
||||||
@@ -1883,7 +1863,6 @@ dependencies = [
|
|||||||
name = "graph-craft"
|
name = "graph-craft"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bezier-rs",
|
|
||||||
"criterion",
|
"criterion",
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
"glam",
|
"glam",
|
||||||
@@ -1961,7 +1940,6 @@ name = "graphene-core"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bezier-rs",
|
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"ctor",
|
"ctor",
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
@@ -1974,6 +1952,7 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
"parley",
|
"parley",
|
||||||
"petgraph 0.7.1",
|
"petgraph 0.7.1",
|
||||||
|
"poly-cool",
|
||||||
"rand 0.9.1",
|
"rand 0.9.1",
|
||||||
"rand_chacha 0.9.0",
|
"rand_chacha 0.9.0",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
@@ -2018,7 +1997,6 @@ dependencies = [
|
|||||||
name = "graphene-path-bool"
|
name = "graphene-path-bool"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bezier-rs",
|
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
"glam",
|
"glam",
|
||||||
"graphene-core",
|
"graphene-core",
|
||||||
@@ -2033,7 +2011,6 @@ dependencies = [
|
|||||||
name = "graphene-raster-nodes"
|
name = "graphene-raster-nodes"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bezier-rs",
|
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
"fastnoise-lite",
|
"fastnoise-lite",
|
||||||
@@ -2042,6 +2019,7 @@ dependencies = [
|
|||||||
"graphene-core",
|
"graphene-core",
|
||||||
"graphene-core-shaders",
|
"graphene-core-shaders",
|
||||||
"image",
|
"image",
|
||||||
|
"kurbo",
|
||||||
"ndarray",
|
"ndarray",
|
||||||
"node-macro",
|
"node-macro",
|
||||||
"rand 0.9.1",
|
"rand 0.9.1",
|
||||||
@@ -2056,10 +2034,7 @@ name = "graphene-std"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bytemuck",
|
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
"fastnoise-lite",
|
|
||||||
"futures",
|
|
||||||
"glam",
|
"glam",
|
||||||
"graph-craft",
|
"graph-craft",
|
||||||
"graphene-application-io",
|
"graphene-application-io",
|
||||||
@@ -2071,10 +2046,7 @@ dependencies = [
|
|||||||
"graphene-svg-renderer",
|
"graphene-svg-renderer",
|
||||||
"image",
|
"image",
|
||||||
"log",
|
"log",
|
||||||
"ndarray",
|
|
||||||
"node-macro",
|
"node-macro",
|
||||||
"rand 0.9.1",
|
|
||||||
"rand_chacha 0.9.0",
|
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"tokio",
|
"tokio",
|
||||||
"vello",
|
"vello",
|
||||||
@@ -2089,10 +2061,10 @@ name = "graphene-svg-renderer"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bezier-rs",
|
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
"glam",
|
"glam",
|
||||||
"graphene-core",
|
"graphene-core",
|
||||||
|
"kurbo",
|
||||||
"log",
|
"log",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -2113,6 +2085,7 @@ dependencies = [
|
|||||||
"graph-craft",
|
"graph-craft",
|
||||||
"graphene-std",
|
"graphene-std",
|
||||||
"graphite-editor",
|
"graphite-editor",
|
||||||
|
"image",
|
||||||
"include_dir",
|
"include_dir",
|
||||||
"open",
|
"open",
|
||||||
"rfd",
|
"rfd",
|
||||||
@@ -2130,9 +2103,8 @@ dependencies = [
|
|||||||
name = "graphite-editor"
|
name = "graphite-editor"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bezier-rs",
|
"base64 0.22.1",
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
"bytemuck",
|
|
||||||
"derivative",
|
"derivative",
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
@@ -2148,18 +2120,15 @@ dependencies = [
|
|||||||
"num_enum",
|
"num_enum",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"preprocessor",
|
"preprocessor",
|
||||||
"ron",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"specta",
|
"specta",
|
||||||
"spin",
|
"spin",
|
||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
|
||||||
"usvg",
|
"usvg",
|
||||||
"vello",
|
"vello",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"wgpu-executor",
|
"wgpu-executor",
|
||||||
]
|
]
|
||||||
@@ -4040,6 +4009,15 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
|
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "poly-cool"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6113ca52ade3ae52044cf0519e2c78ffa82120f6fa82f5099c8a4fd3ec8de43"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "portable-atomic"
|
name = "portable-atomic"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
|
|||||||
+21
-10
@@ -20,9 +20,7 @@ members = [
|
|||||||
"node-graph/preprocessor",
|
"node-graph/preprocessor",
|
||||||
"libraries/dyn-any",
|
"libraries/dyn-any",
|
||||||
"libraries/path-bool",
|
"libraries/path-bool",
|
||||||
"libraries/bezier-rs",
|
|
||||||
"libraries/math-parser",
|
"libraries/math-parser",
|
||||||
"website/other/bezier-rs-demos/wasm",
|
|
||||||
]
|
]
|
||||||
default-members = [
|
default-members = [
|
||||||
"editor",
|
"editor",
|
||||||
@@ -44,9 +42,14 @@ resolver = "2"
|
|||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
# Local dependencies
|
# Local dependencies
|
||||||
bezier-rs = { path = "libraries/bezier-rs", features = ["dyn-any", "serde"] }
|
dyn-any = { path = "libraries/dyn-any", features = [
|
||||||
dyn-any = { path = "libraries/dyn-any", features = ["derive", "glam", "reqwest", "log-bad-types", "rc"] }
|
"derive",
|
||||||
preprocessor = { path = "node-graph/preprocessor"}
|
"glam",
|
||||||
|
"reqwest",
|
||||||
|
"log-bad-types",
|
||||||
|
"rc",
|
||||||
|
] }
|
||||||
|
preprocessor = { path = "node-graph/preprocessor" }
|
||||||
math-parser = { path = "libraries/math-parser" }
|
math-parser = { path = "libraries/math-parser" }
|
||||||
path-bool = { path = "libraries/path-bool" }
|
path-bool = { path = "libraries/path-bool" }
|
||||||
graphene-application-io = { path = "node-graph/gapplication-io" }
|
graphene-application-io = { path = "node-graph/gapplication-io" }
|
||||||
@@ -80,9 +83,8 @@ convert_case = "0.7"
|
|||||||
derivative = "2.2"
|
derivative = "2.2"
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
proc-macro2 = { version = "1", features = [ "span-locations" ] }
|
proc-macro2 = { version = "1", features = ["span-locations"] }
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
axum = "0.8"
|
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
ron = "0.8"
|
ron = "0.8"
|
||||||
fastnoise-lite = "1.1"
|
fastnoise-lite = "1.1"
|
||||||
@@ -122,9 +124,17 @@ resvg = "0.44"
|
|||||||
usvg = "0.44"
|
usvg = "0.44"
|
||||||
rand = { version = "0.9", default-features = false, features = ["std_rng"] }
|
rand = { version = "0.9", default-features = false, features = ["std_rng"] }
|
||||||
rand_chacha = "0.9"
|
rand_chacha = "0.9"
|
||||||
glam = { version = "0.29", default-features = false, features = ["serde", "scalar-math", "debug-glam-assert"] }
|
glam = { version = "0.29", default-features = false, features = [
|
||||||
|
"serde",
|
||||||
|
"scalar-math",
|
||||||
|
"debug-glam-assert",
|
||||||
|
] }
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp"] }
|
image = { version = "0.25", default-features = false, features = [
|
||||||
|
"png",
|
||||||
|
"jpeg",
|
||||||
|
"bmp",
|
||||||
|
] }
|
||||||
parley = "0.5.0"
|
parley = "0.5.0"
|
||||||
skrifa = "0.32.0"
|
skrifa = "0.32.0"
|
||||||
pretty_assertions = "1.4.1"
|
pretty_assertions = "1.4.1"
|
||||||
@@ -159,12 +169,13 @@ iai-callgrind = { version = "0.12.3" }
|
|||||||
ndarray = "0.16.1"
|
ndarray = "0.16.1"
|
||||||
strum = { version = "0.26.3", features = ["derive"] }
|
strum = { version = "0.26.3", features = ["derive"] }
|
||||||
dirs = "6.0"
|
dirs = "6.0"
|
||||||
cef = "138.5.0"
|
cef = "139.0.1"
|
||||||
include_dir = "0.7.4"
|
include_dir = "0.7.4"
|
||||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
rfd = "0.15.4"
|
rfd = "0.15.4"
|
||||||
open = "5.3.2"
|
open = "5.3.2"
|
||||||
|
poly-cool = "0.2.0"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 1
|
opt-level = 1
|
||||||
|
|||||||
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
@@ -39,3 +39,4 @@ vello = { workspace = true }
|
|||||||
derivative = { workspace = true }
|
derivative = { workspace = true }
|
||||||
rfd = { workspace = true }
|
rfd = { workspace = true }
|
||||||
open = { workspace = true }
|
open = { workspace = true }
|
||||||
|
image = { workspace = true }
|
||||||
|
|||||||
+74
-1
@@ -7,8 +7,12 @@ use crate::dialogs::dialog_save_graphite_file;
|
|||||||
use crate::render::GraphicsState;
|
use crate::render::GraphicsState;
|
||||||
use crate::render::WgpuContext;
|
use crate::render::WgpuContext;
|
||||||
use graph_craft::wasm_application_io::WasmApplicationIo;
|
use graph_craft::wasm_application_io::WasmApplicationIo;
|
||||||
|
use graphene_std::Color;
|
||||||
|
use graphene_std::raster::Image;
|
||||||
use graphite_editor::application::Editor;
|
use graphite_editor::application::Editor;
|
||||||
|
use graphite_editor::consts::DEFAULT_DOCUMENT_NAME;
|
||||||
use graphite_editor::messages::prelude::*;
|
use graphite_editor::messages::prelude::*;
|
||||||
|
use std::fs;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
@@ -75,7 +79,7 @@ impl WinitApp {
|
|||||||
String::new()
|
String::new()
|
||||||
});
|
});
|
||||||
let message = PortfolioMessage::OpenDocumentFile {
|
let message = PortfolioMessage::OpenDocumentFile {
|
||||||
document_name: path.file_name().and_then(|s| s.to_str()).unwrap_or("unknown").to_string(),
|
document_name: path.file_stem().and_then(|s| s.to_str()).unwrap_or("unknown").to_string(),
|
||||||
document_serialized_content: content,
|
document_serialized_content: content,
|
||||||
};
|
};
|
||||||
let _ = event_loop_proxy.send_event(CustomEvent::DispatchMessage(message.into()));
|
let _ = event_loop_proxy.send_event(CustomEvent::DispatchMessage(message.into()));
|
||||||
@@ -264,6 +268,75 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
|||||||
let Some(event) = self.cef_context.handle_window_event(event) else { return };
|
let Some(event) = self.cef_context.handle_window_event(event) else { return };
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
|
// Currently not supported on wayland see https://github.com/rust-windowing/winit/issues/1881
|
||||||
|
WindowEvent::DroppedFile(path) => {
|
||||||
|
let name = path.file_stem().and_then(|s| s.to_str()).map(|s| s.to_string());
|
||||||
|
let Some(extension) = path.extension().and_then(|s| s.to_str()) else {
|
||||||
|
tracing::warn!("Unsupported file dropped: {}", path.display());
|
||||||
|
// Fine to early return since we don't need to do cef work in this case
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let load_string = |path: &std::path::PathBuf| {
|
||||||
|
let Ok(content) = fs::read_to_string(path) else {
|
||||||
|
tracing::error!("Failed to read file: {}", path.display());
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
if content.is_empty() {
|
||||||
|
tracing::warn!("Dropped file is empty: {}", path.display());
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(content)
|
||||||
|
};
|
||||||
|
// TODO: Consider moving this logic to the editor so we have one message to load data which is then demultiplexed in the portfolio message handler
|
||||||
|
match extension {
|
||||||
|
"graphite" => {
|
||||||
|
let Some(content) = load_string(&path) else { return };
|
||||||
|
|
||||||
|
let message = PortfolioMessage::OpenDocumentFile {
|
||||||
|
document_name: name.unwrap_or(DEFAULT_DOCUMENT_NAME.to_string()),
|
||||||
|
document_serialized_content: content,
|
||||||
|
};
|
||||||
|
self.dispatch_message(message.into());
|
||||||
|
}
|
||||||
|
"svg" => {
|
||||||
|
let Some(content) = load_string(&path) else { return };
|
||||||
|
|
||||||
|
let message = PortfolioMessage::PasteSvg {
|
||||||
|
name: path.file_stem().map(|s| s.to_string_lossy().to_string()),
|
||||||
|
svg: content,
|
||||||
|
mouse: None,
|
||||||
|
parent_and_insert_index: None,
|
||||||
|
};
|
||||||
|
self.dispatch_message(message.into());
|
||||||
|
}
|
||||||
|
_ => match image::ImageReader::open(&path) {
|
||||||
|
Ok(reader) => match reader.decode() {
|
||||||
|
Ok(image) => {
|
||||||
|
let width = image.width();
|
||||||
|
let height = image.height();
|
||||||
|
// TODO: support loading images with more than 8 bits per channel
|
||||||
|
let image_data = image.to_rgba8();
|
||||||
|
let image = Image::<Color>::from_image_data(image_data.as_raw(), width, height);
|
||||||
|
|
||||||
|
let message = PortfolioMessage::PasteImage {
|
||||||
|
name,
|
||||||
|
image,
|
||||||
|
mouse: None,
|
||||||
|
parent_and_insert_index: None,
|
||||||
|
};
|
||||||
|
self.dispatch_message(message.into());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to decode image: {}: {}", path.display(), e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to open image file: {}: {}", path.display(), e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
WindowEvent::CloseRequested => {
|
WindowEvent::CloseRequested => {
|
||||||
tracing::info!("The close button was pressed; stopping");
|
tracing::info!("The close button was pressed; stopping");
|
||||||
event_loop.exit();
|
event_loop.exit();
|
||||||
|
|||||||
@@ -44,34 +44,34 @@ var s_diffuse: sampler;
|
|||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
let ui = textureSample(t_ui, s_diffuse, in.tex_coords);
|
let ui_linear = textureSample(t_ui, s_diffuse, in.tex_coords);
|
||||||
if (ui.a >= 0.999) {
|
if (ui_linear.a >= 0.999) {
|
||||||
return ui;
|
return ui_linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
let viewport_coordinate = (in.tex_coords - constants.viewport_offset) * constants.viewport_scale;
|
let viewport_coordinate = (in.tex_coords - constants.viewport_offset) * constants.viewport_scale;
|
||||||
|
|
||||||
// Vello renders its values to an `RgbaUnorm` texture, but if we try to use this in the main rendering pipeline
|
let overlay_srgb = textureSample(t_overlays, s_diffuse, viewport_coordinate);
|
||||||
// which renders to an `Srgb` surface, gamma mapping is applied twice. This converts back to linear to compensate.
|
let viewport_srgb = textureSample(t_viewport, s_diffuse, viewport_coordinate);
|
||||||
let overlay_raw = textureSample(t_overlays, s_diffuse, viewport_coordinate);
|
|
||||||
let overlay = vec4<f32>(srgb_to_linear(overlay_raw.rgb), overlay_raw.a);
|
|
||||||
let viewport_raw = textureSample(t_viewport, s_diffuse, viewport_coordinate);
|
|
||||||
let viewport = vec4<f32>(srgb_to_linear(viewport_raw.rgb), viewport_raw.a);
|
|
||||||
|
|
||||||
if (overlay.a < 0.001) {
|
// UI texture is premultiplied, we need to unpremultiply before blending
|
||||||
return blend(ui, viewport);
|
let ui_srgb = linear_to_srgb(unpremultiply(ui_linear));
|
||||||
|
|
||||||
|
if (overlay_srgb.a < 0.001) {
|
||||||
|
if (ui_srgb.a < 0.001) {
|
||||||
|
return srgb_to_linear(viewport_srgb);
|
||||||
|
} else {
|
||||||
|
return srgb_to_linear(blend(ui_srgb, viewport_srgb));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let composite = blend(overlay, viewport);
|
let composite_linear = blend(srgb_to_linear(overlay_srgb), srgb_to_linear(viewport_srgb));
|
||||||
return blend(ui, composite);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn srgb_to_linear(srgb: vec3<f32>) -> vec3<f32> {
|
if (ui_srgb.a < 0.001) {
|
||||||
return select(
|
return composite_linear;
|
||||||
pow((srgb + 0.055) / 1.055, vec3<f32>(2.4)),
|
}
|
||||||
srgb / 12.92,
|
|
||||||
srgb <= vec3<f32>(0.04045)
|
return srgb_to_linear(blend(ui_srgb, linear_to_srgb(composite_linear)));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn blend(fg: vec4<f32>, bg: vec4<f32>) -> vec4<f32> {
|
fn blend(fg: vec4<f32>, bg: vec4<f32>) -> vec4<f32> {
|
||||||
@@ -79,3 +79,25 @@ fn blend(fg: vec4<f32>, bg: vec4<f32>) -> vec4<f32> {
|
|||||||
let rgb = fg.rgb * fg.a + bg.rgb * bg.a * (1.0 - fg.a);
|
let rgb = fg.rgb * fg.a + bg.rgb * bg.a * (1.0 - fg.a);
|
||||||
return vec4<f32>(rgb, a);
|
return vec4<f32>(rgb, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn linear_to_srgb(in: vec4<f32>) -> vec4<f32> {
|
||||||
|
let cutoff = vec3<f32>(0.0031308);
|
||||||
|
let lo = in.rgb * 12.92;
|
||||||
|
let hi = 1.055 * pow(max(in.rgb, vec3<f32>(0.0)), vec3<f32>(1.0/2.4)) - 0.055;
|
||||||
|
return vec4<f32>(select(lo, hi, in.rgb > cutoff), in.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn srgb_to_linear(in: vec4<f32>) -> vec4<f32> {
|
||||||
|
let cutoff = vec3<f32>(0.04045);
|
||||||
|
let lo = in.rgb / 12.92;
|
||||||
|
let hi = pow((in.rgb + 0.055) / 1.055, vec3<f32>(2.4));
|
||||||
|
return vec4<f32>(select(lo, hi, in.rgb > cutoff), in.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpremultiply(in: vec4<f32>) -> vec4<f32> {
|
||||||
|
if (in.a > 0.0) {
|
||||||
|
return vec4<f32>((in.rgb / in.a), in.a);
|
||||||
|
} else {
|
||||||
|
return vec4<f32>(0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+4
-8
@@ -12,18 +12,18 @@ license = "Apache-2.0"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["wasm"]
|
default = ["wasm"]
|
||||||
wasm = ["wasm-bindgen", "graphene-std/wasm", "wasm-bindgen-futures"]
|
wasm = ["wasm-bindgen", "graphene-std/wasm"]
|
||||||
gpu = ["interpreted-executor/gpu", "wgpu-executor"]
|
gpu = ["interpreted-executor/gpu", "wgpu-executor"]
|
||||||
resvg = ["graphene-std/resvg"]
|
resvg = ["graphene-std/resvg"]
|
||||||
vello = ["graphene-std/vello", "resvg"]
|
vello = ["graphene-std/vello", "resvg"]
|
||||||
ron = ["dep:ron"]
|
ron = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Local dependencies
|
# Local dependencies
|
||||||
graphite-proc-macros = { workspace = true }
|
graphite-proc-macros = { workspace = true }
|
||||||
graph-craft = { workspace = true }
|
graph-craft = { workspace = true }
|
||||||
interpreted-executor = { workspace = true }
|
interpreted-executor = { workspace = true }
|
||||||
graphene-std = { workspace = true }
|
graphene-std = { workspace = true } # NOTE: `graphene-core` should not be added here because `graphene-std` re-exports its contents
|
||||||
preprocessor = { workspace = true }
|
preprocessor = { workspace = true }
|
||||||
|
|
||||||
# Workspace dependencies
|
# Workspace dependencies
|
||||||
@@ -33,7 +33,6 @@ bitflags = { workspace = true }
|
|||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
bezier-rs = { workspace = true }
|
|
||||||
kurbo = { workspace = true }
|
kurbo = { workspace = true }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
glam = { workspace = true }
|
glam = { workspace = true }
|
||||||
@@ -44,9 +43,8 @@ num_enum = { workspace = true }
|
|||||||
usvg = { workspace = true }
|
usvg = { workspace = true }
|
||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
web-sys = { workspace = true }
|
web-sys = { workspace = true }
|
||||||
bytemuck = { workspace = true }
|
|
||||||
vello = { workspace = true }
|
vello = { workspace = true }
|
||||||
tracing = { workspace = true }
|
base64 = { workspace = true }
|
||||||
|
|
||||||
# Required dependencies
|
# Required dependencies
|
||||||
spin = "0.9.8"
|
spin = "0.9.8"
|
||||||
@@ -56,8 +54,6 @@ wgpu-executor = { workspace = true, optional = true }
|
|||||||
|
|
||||||
# Optional workspace dependencies
|
# Optional workspace dependencies
|
||||||
wasm-bindgen = { workspace = true, optional = true }
|
wasm-bindgen = { workspace = true, optional = true }
|
||||||
wasm-bindgen-futures = { workspace = true, optional = true }
|
|
||||||
ron = { workspace = true, optional = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
# Workspace dependencies
|
# Workspace dependencies
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ pub struct DispatcherMessageHandlers {
|
|||||||
pub portfolio_message_handler: PortfolioMessageHandler,
|
pub portfolio_message_handler: PortfolioMessageHandler,
|
||||||
preferences_message_handler: PreferencesMessageHandler,
|
preferences_message_handler: PreferencesMessageHandler,
|
||||||
tool_message_handler: ToolMessageHandler,
|
tool_message_handler: ToolMessageHandler,
|
||||||
workspace_message_handler: WorkspaceMessageHandler,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DispatcherMessageHandlers {
|
impl DispatcherMessageHandlers {
|
||||||
@@ -231,9 +230,6 @@ impl Dispatcher {
|
|||||||
|
|
||||||
self.message_handlers.tool_message_handler.process_message(message, &mut queue, context);
|
self.message_handlers.tool_message_handler.process_message(message, &mut queue, context);
|
||||||
}
|
}
|
||||||
Message::Workspace(message) => {
|
|
||||||
self.message_handlers.workspace_message_handler.process_message(message, &mut queue, ());
|
|
||||||
}
|
|
||||||
Message::NoOp => {}
|
Message::NoOp => {}
|
||||||
Message::Batched { messages } => {
|
Message::Batched { messages } => {
|
||||||
messages.iter().for_each(|message| self.handle_message(message.to_owned(), false));
|
messages.iter().for_each(|message| self.handle_message(message.to_owned(), false));
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ impl MessageHandler<DeferMessage, DeferMessageContext<'_>> for DeferMessageHandl
|
|||||||
for (_, message) in elements.rev() {
|
for (_, message) in elements.rev() {
|
||||||
responses.add_front(message);
|
responses.add_front(message);
|
||||||
}
|
}
|
||||||
for (id, messages) in self.after_graph_run.iter() {
|
for (&document_id, messages) in self.after_graph_run.iter() {
|
||||||
if !messages.is_empty() {
|
if !messages.is_empty() {
|
||||||
responses.add(PortfolioMessage::SubmitGraphRender { document_id: *id, ignore_hash: false });
|
responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,11 +149,16 @@ pub enum FrontendMessage {
|
|||||||
UpdateGraphViewOverlay {
|
UpdateGraphViewOverlay {
|
||||||
open: bool,
|
open: bool,
|
||||||
},
|
},
|
||||||
UpdateSpreadsheetState {
|
UpdateDataPanelState {
|
||||||
open: bool,
|
open: bool,
|
||||||
node: Option<NodeId>,
|
|
||||||
},
|
},
|
||||||
UpdateSpreadsheetLayout {
|
UpdatePropertiesPanelState {
|
||||||
|
open: bool,
|
||||||
|
},
|
||||||
|
UpdateLayersPanelState {
|
||||||
|
open: bool,
|
||||||
|
},
|
||||||
|
UpdateDataPanelLayout {
|
||||||
#[serde(rename = "layoutTarget")]
|
#[serde(rename = "layoutTarget")]
|
||||||
layout_target: LayoutTarget,
|
layout_target: LayoutTarget,
|
||||||
diff: Vec<WidgetDiff>,
|
diff: Vec<WidgetDiff>,
|
||||||
@@ -296,7 +301,7 @@ pub enum FrontendMessage {
|
|||||||
#[serde(rename = "openDocuments")]
|
#[serde(rename = "openDocuments")]
|
||||||
open_documents: Vec<FrontendDocumentDetails>,
|
open_documents: Vec<FrontendDocumentDetails>,
|
||||||
},
|
},
|
||||||
UpdatePropertyPanelSectionsLayout {
|
UpdatePropertiesPanelLayout {
|
||||||
#[serde(rename = "layoutTarget")]
|
#[serde(rename = "layoutTarget")]
|
||||||
layout_target: LayoutTarget,
|
layout_target: LayoutTarget,
|
||||||
diff: Vec<WidgetDiff>,
|
diff: Vec<WidgetDiff>,
|
||||||
|
|||||||
@@ -427,6 +427,7 @@ pub fn input_mappings() -> Mapping {
|
|||||||
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }),
|
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }),
|
||||||
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=PortfolioMessage::Copy { clipboard: Clipboard::Device }),
|
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=PortfolioMessage::Copy { clipboard: Clipboard::Device }),
|
||||||
entry!(KeyDown(KeyR); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleRulers),
|
entry!(KeyDown(KeyR); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleRulers),
|
||||||
|
entry!(KeyDown(KeyD); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleDataPanelOpen),
|
||||||
//
|
//
|
||||||
// FrontendMessage
|
// FrontendMessage
|
||||||
entry!(KeyDown(KeyV); modifiers=[Accel], action_dispatch=FrontendMessage::TriggerPaste),
|
entry!(KeyDown(KeyV); modifiers=[Accel], action_dispatch=FrontendMessage::TriggerPaste),
|
||||||
|
|||||||
@@ -308,6 +308,7 @@ impl LayoutMessageHandler {
|
|||||||
|
|
||||||
responses.add(callback_message);
|
responses.add(callback_message);
|
||||||
}
|
}
|
||||||
|
Widget::ImageLabel(_) => {}
|
||||||
Widget::IconLabel(_) => {}
|
Widget::IconLabel(_) => {}
|
||||||
Widget::InvisibleStandinInput(invisible) => {
|
Widget::InvisibleStandinInput(invisible) => {
|
||||||
let callback_message = match action {
|
let callback_message = match action {
|
||||||
@@ -481,18 +482,18 @@ impl LayoutMessageHandler {
|
|||||||
diff.iter_mut().for_each(|diff| diff.new_value.apply_keyboard_shortcut(action_input_mapping));
|
diff.iter_mut().for_each(|diff| diff.new_value.apply_keyboard_shortcut(action_input_mapping));
|
||||||
|
|
||||||
let message = match layout_target {
|
let message = match layout_target {
|
||||||
|
LayoutTarget::MenuBar => unreachable!("Menu bar is not diffed"),
|
||||||
LayoutTarget::DialogButtons => FrontendMessage::UpdateDialogButtons { layout_target, diff },
|
LayoutTarget::DialogButtons => FrontendMessage::UpdateDialogButtons { layout_target, diff },
|
||||||
LayoutTarget::DialogColumn1 => FrontendMessage::UpdateDialogColumn1 { layout_target, diff },
|
LayoutTarget::DialogColumn1 => FrontendMessage::UpdateDialogColumn1 { layout_target, diff },
|
||||||
LayoutTarget::DialogColumn2 => FrontendMessage::UpdateDialogColumn2 { layout_target, diff },
|
LayoutTarget::DialogColumn2 => FrontendMessage::UpdateDialogColumn2 { layout_target, diff },
|
||||||
LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout { layout_target, diff },
|
LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout { layout_target, diff },
|
||||||
LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout { layout_target, diff },
|
LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout { layout_target, diff },
|
||||||
|
LayoutTarget::DataPanel => FrontendMessage::UpdateDataPanelLayout { layout_target, diff },
|
||||||
LayoutTarget::LayersPanelControlLeftBar => FrontendMessage::UpdateLayersPanelControlBarLeftLayout { layout_target, diff },
|
LayoutTarget::LayersPanelControlLeftBar => FrontendMessage::UpdateLayersPanelControlBarLeftLayout { layout_target, diff },
|
||||||
LayoutTarget::LayersPanelControlRightBar => FrontendMessage::UpdateLayersPanelControlBarRightLayout { layout_target, diff },
|
LayoutTarget::LayersPanelControlRightBar => FrontendMessage::UpdateLayersPanelControlBarRightLayout { layout_target, diff },
|
||||||
LayoutTarget::LayersPanelBottomBar => FrontendMessage::UpdateLayersPanelBottomBarLayout { layout_target, diff },
|
LayoutTarget::LayersPanelBottomBar => FrontendMessage::UpdateLayersPanelBottomBarLayout { layout_target, diff },
|
||||||
LayoutTarget::MenuBar => unreachable!("Menu bar is not diffed"),
|
LayoutTarget::PropertiesPanel => FrontendMessage::UpdatePropertiesPanelLayout { layout_target, diff },
|
||||||
LayoutTarget::NodeGraphControlBar => FrontendMessage::UpdateNodeGraphControlBarLayout { layout_target, diff },
|
LayoutTarget::NodeGraphControlBar => FrontendMessage::UpdateNodeGraphControlBarLayout { layout_target, diff },
|
||||||
LayoutTarget::PropertiesSections => FrontendMessage::UpdatePropertyPanelSectionsLayout { layout_target, diff },
|
|
||||||
LayoutTarget::Spreadsheet => FrontendMessage::UpdateSpreadsheetLayout { layout_target, diff },
|
|
||||||
LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout { layout_target, diff },
|
LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout { layout_target, diff },
|
||||||
LayoutTarget::ToolShelf => FrontendMessage::UpdateToolShelfLayout { layout_target, diff },
|
LayoutTarget::ToolShelf => FrontendMessage::UpdateToolShelfLayout { layout_target, diff },
|
||||||
LayoutTarget::WorkingColors => FrontendMessage::UpdateWorkingColorsLayout { layout_target, diff },
|
LayoutTarget::WorkingColors => FrontendMessage::UpdateWorkingColorsLayout { layout_target, diff },
|
||||||
|
|||||||
@@ -42,9 +42,9 @@ pub enum LayoutTarget {
|
|||||||
/// Bar at the top of the node graph containing the location and the "Preview" and "Hide" buttons.
|
/// Bar at the top of the node graph containing the location and the "Preview" and "Hide" buttons.
|
||||||
NodeGraphControlBar,
|
NodeGraphControlBar,
|
||||||
/// The body of the Properties panel containing many collapsable sections.
|
/// The body of the Properties panel containing many collapsable sections.
|
||||||
PropertiesSections,
|
PropertiesPanel,
|
||||||
/// The spredsheet panel allows for the visualisation of data in the graph.
|
/// The spredsheet panel allows for the visualisation of data in the graph.
|
||||||
Spreadsheet,
|
DataPanel,
|
||||||
/// The bar directly above the canvas, left-aligned and to the right of the document mode dropdown.
|
/// The bar directly above the canvas, left-aligned and to the right of the document mode dropdown.
|
||||||
ToolOptions,
|
ToolOptions,
|
||||||
/// The vertical buttons for all of the tools on the left of the canvas.
|
/// The vertical buttons for all of the tools on the left of the canvas.
|
||||||
@@ -369,6 +369,7 @@ impl LayoutGroup {
|
|||||||
Widget::IconButton(x) => &mut x.tooltip,
|
Widget::IconButton(x) => &mut x.tooltip,
|
||||||
Widget::IconLabel(x) => &mut x.tooltip,
|
Widget::IconLabel(x) => &mut x.tooltip,
|
||||||
Widget::ImageButton(x) => &mut x.tooltip,
|
Widget::ImageButton(x) => &mut x.tooltip,
|
||||||
|
Widget::ImageLabel(x) => &mut x.tooltip,
|
||||||
Widget::NumberInput(x) => &mut x.tooltip,
|
Widget::NumberInput(x) => &mut x.tooltip,
|
||||||
Widget::ParameterExposeButton(x) => &mut x.tooltip,
|
Widget::ParameterExposeButton(x) => &mut x.tooltip,
|
||||||
Widget::PopoverButton(x) => &mut x.tooltip,
|
Widget::PopoverButton(x) => &mut x.tooltip,
|
||||||
@@ -546,6 +547,7 @@ pub enum Widget {
|
|||||||
IconButton(IconButton),
|
IconButton(IconButton),
|
||||||
IconLabel(IconLabel),
|
IconLabel(IconLabel),
|
||||||
ImageButton(ImageButton),
|
ImageButton(ImageButton),
|
||||||
|
ImageLabel(ImageLabel),
|
||||||
InvisibleStandinInput(InvisibleStandinInput),
|
InvisibleStandinInput(InvisibleStandinInput),
|
||||||
NodeCatalog(NodeCatalog),
|
NodeCatalog(NodeCatalog),
|
||||||
NumberInput(NumberInput),
|
NumberInput(NumberInput),
|
||||||
@@ -622,6 +624,7 @@ impl DiffUpdate {
|
|||||||
Widget::TextButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
|
Widget::TextButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
|
||||||
Widget::ImageButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
|
Widget::ImageButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
|
||||||
Widget::IconLabel(_)
|
Widget::IconLabel(_)
|
||||||
|
| Widget::ImageLabel(_)
|
||||||
| Widget::CurveInput(_)
|
| Widget::CurveInput(_)
|
||||||
| Widget::InvisibleStandinInput(_)
|
| Widget::InvisibleStandinInput(_)
|
||||||
| Widget::NodeCatalog(_)
|
| Widget::NodeCatalog(_)
|
||||||
|
|||||||
@@ -168,8 +168,6 @@ pub struct ColorInput {
|
|||||||
#[widget_builder(constructor)]
|
#[widget_builder(constructor)]
|
||||||
pub value: FillChoice,
|
pub value: FillChoice,
|
||||||
|
|
||||||
pub disabled: bool,
|
|
||||||
|
|
||||||
// TODO: Implement
|
// TODO: Implement
|
||||||
// #[serde(rename = "allowTransparency")]
|
// #[serde(rename = "allowTransparency")]
|
||||||
// #[derivative(Default(value = "false"))]
|
// #[derivative(Default(value = "false"))]
|
||||||
@@ -179,9 +177,11 @@ pub struct ColorInput {
|
|||||||
#[derivative(Default(value = "true"))]
|
#[derivative(Default(value = "true"))]
|
||||||
pub allow_none: bool,
|
pub allow_none: bool,
|
||||||
|
|
||||||
// TODO: Implement
|
pub disabled: bool,
|
||||||
// pub disabled: bool,
|
|
||||||
//
|
#[serde(rename = "menuDirection")]
|
||||||
|
pub menu_direction: Option<MenuDirection>,
|
||||||
|
|
||||||
pub tooltip: String,
|
pub tooltip: String,
|
||||||
|
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
|
|||||||
@@ -416,6 +416,9 @@ pub struct TextInput {
|
|||||||
#[serde(rename = "minWidth")]
|
#[serde(rename = "minWidth")]
|
||||||
pub min_width: u32,
|
pub min_width: u32,
|
||||||
|
|
||||||
|
#[serde(rename = "maxWidth")]
|
||||||
|
pub max_width: u32,
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||||
|
|||||||
@@ -65,4 +65,17 @@ pub struct TextLabel {
|
|||||||
pub value: String,
|
pub value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
|
||||||
|
#[derivative(Debug, PartialEq)]
|
||||||
|
pub struct ImageLabel {
|
||||||
|
#[widget_builder(constructor)]
|
||||||
|
pub url: String,
|
||||||
|
|
||||||
|
pub width: Option<String>,
|
||||||
|
|
||||||
|
pub height: Option<String>,
|
||||||
|
|
||||||
|
pub tooltip: String,
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Add UserInputLabel
|
// TODO: Add UserInputLabel
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ pub enum Message {
|
|||||||
Preferences(PreferencesMessage),
|
Preferences(PreferencesMessage),
|
||||||
#[child]
|
#[child]
|
||||||
Tool(ToolMessage),
|
Tool(ToolMessage),
|
||||||
#[child]
|
|
||||||
Workspace(WorkspaceMessage),
|
|
||||||
|
|
||||||
// Messages
|
// Messages
|
||||||
NoOp,
|
NoOp,
|
||||||
@@ -71,7 +69,7 @@ mod test {
|
|||||||
|
|
||||||
fn print_tree_node(tree: &DebugMessageTree, prefix: &str, is_last: bool, file: &mut std::fs::File) {
|
fn print_tree_node(tree: &DebugMessageTree, prefix: &str, is_last: bool, file: &mut std::fs::File) {
|
||||||
// Print the current node
|
// Print the current node
|
||||||
let (branch, child_prefix) = if tree.has_message_handler_data_fields() || tree.has_message_handler_fields() {
|
let (branch, child_prefix) = if tree.message_handler_data_fields().is_some() || tree.message_handler_fields().is_some() {
|
||||||
("├── ", format!("{}│ ", prefix))
|
("├── ", format!("{}│ ", prefix))
|
||||||
} else {
|
} else {
|
||||||
if is_last {
|
if is_last {
|
||||||
@@ -99,7 +97,7 @@ mod test {
|
|||||||
// Print handler field if any
|
// Print handler field if any
|
||||||
if let Some(data) = tree.message_handler_fields() {
|
if let Some(data) = tree.message_handler_fields() {
|
||||||
let len = data.fields().len();
|
let len = data.fields().len();
|
||||||
let (branch, child_prefix) = if tree.has_message_handler_data_fields() {
|
let (branch, child_prefix) = if tree.message_handler_data_fields().is_some() {
|
||||||
("├── ", format!("{}│ ", prefix))
|
("├── ", format!("{}│ ", prefix))
|
||||||
} else {
|
} else {
|
||||||
("└── ", format!("{} ", prefix))
|
("└── ", format!("{} ", prefix))
|
||||||
|
|||||||
@@ -16,4 +16,3 @@ pub mod portfolio;
|
|||||||
pub mod preferences;
|
pub mod preferences;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
pub mod tool;
|
pub mod tool;
|
||||||
pub mod workspace;
|
|
||||||
|
|||||||
+8
-8
@@ -1,16 +1,15 @@
|
|||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::node_graph_executor::InspectResult;
|
use crate::node_graph_executor::InspectResult;
|
||||||
|
|
||||||
/// The spreadsheet UI allows for graph data to be previewed.
|
/// The Data panel UI allows the user to visualize the output data of the selected node.
|
||||||
#[impl_message(Message, PortfolioMessage, Spreadsheet)]
|
#[impl_message(Message, DocumentMessage, DataPanel)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum SpreadsheetMessage {
|
pub enum DataPanelMessage {
|
||||||
ToggleOpen,
|
|
||||||
|
|
||||||
UpdateLayout {
|
UpdateLayout {
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
inspect_result: InspectResult,
|
inspect_result: InspectResult,
|
||||||
},
|
},
|
||||||
|
ClearLayout,
|
||||||
|
|
||||||
PushToElementPath {
|
PushToElementPath {
|
||||||
index: usize,
|
index: usize,
|
||||||
@@ -19,14 +18,15 @@ pub enum SpreadsheetMessage {
|
|||||||
len: usize,
|
len: usize,
|
||||||
},
|
},
|
||||||
|
|
||||||
ViewVectorDomain {
|
ViewVectorTableTab {
|
||||||
domain: VectorDomain,
|
tab: VectorTableTab,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum VectorDomain {
|
pub enum VectorTableTab {
|
||||||
#[default]
|
#[default]
|
||||||
|
Properties,
|
||||||
Points,
|
Points,
|
||||||
Segments,
|
Segments,
|
||||||
Regions,
|
Regions,
|
||||||
@@ -0,0 +1,627 @@
|
|||||||
|
use super::VectorTableTab;
|
||||||
|
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, LayoutTarget, WidgetLayout};
|
||||||
|
use crate::messages::portfolio::document::data_panel::DataPanelMessage;
|
||||||
|
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||||
|
use crate::messages::prelude::*;
|
||||||
|
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||||
|
use graph_craft::document::NodeId;
|
||||||
|
use graphene_std::Color;
|
||||||
|
use graphene_std::Context;
|
||||||
|
use graphene_std::gradient::GradientStops;
|
||||||
|
use graphene_std::memo::IORecord;
|
||||||
|
use graphene_std::raster_types::{CPU, GPU, Raster};
|
||||||
|
use graphene_std::table::Table;
|
||||||
|
use graphene_std::vector::Vector;
|
||||||
|
use graphene_std::vector::style::{Fill, FillChoice};
|
||||||
|
use graphene_std::{Artboard, Graphic};
|
||||||
|
use std::any::Any;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(ExtractField)]
|
||||||
|
pub struct DataPanelMessageContext<'a> {
|
||||||
|
pub network_interface: &'a mut NodeNetworkInterface,
|
||||||
|
pub data_panel_open: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The data panel allows for graph data to be previewed.
|
||||||
|
#[derive(Default, Debug, Clone, ExtractField)]
|
||||||
|
pub struct DataPanelMessageHandler {
|
||||||
|
introspected_node: Option<NodeId>,
|
||||||
|
introspected_data: Option<Arc<dyn Any + Send + Sync>>,
|
||||||
|
element_path: Vec<usize>,
|
||||||
|
active_vector_table_tab: VectorTableTab,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
|
impl MessageHandler<DataPanelMessage, DataPanelMessageContext<'_>> for DataPanelMessageHandler {
|
||||||
|
fn process_message(&mut self, message: DataPanelMessage, responses: &mut VecDeque<Message>, context: DataPanelMessageContext) {
|
||||||
|
match message {
|
||||||
|
DataPanelMessage::UpdateLayout { mut inspect_result } => {
|
||||||
|
self.introspected_node = Some(inspect_result.inspect_node);
|
||||||
|
self.introspected_data = inspect_result.take_data();
|
||||||
|
self.update_layout(responses, context);
|
||||||
|
}
|
||||||
|
DataPanelMessage::ClearLayout => {
|
||||||
|
self.introspected_node = None;
|
||||||
|
self.introspected_data = None;
|
||||||
|
self.element_path.clear();
|
||||||
|
self.active_vector_table_tab = VectorTableTab::default();
|
||||||
|
self.update_layout(responses, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
DataPanelMessage::PushToElementPath { index } => {
|
||||||
|
self.element_path.push(index);
|
||||||
|
self.update_layout(responses, context);
|
||||||
|
}
|
||||||
|
DataPanelMessage::TruncateElementPath { len } => {
|
||||||
|
self.element_path.truncate(len);
|
||||||
|
self.update_layout(responses, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
DataPanelMessage::ViewVectorTableTab { tab } => {
|
||||||
|
self.active_vector_table_tab = tab;
|
||||||
|
self.update_layout(responses, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn actions(&self) -> ActionList {
|
||||||
|
actions!(DataPanelMessage;)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DataPanelMessageHandler {
|
||||||
|
fn update_layout(&mut self, responses: &mut VecDeque<Message>, context: DataPanelMessageContext<'_>) {
|
||||||
|
let DataPanelMessageContext { network_interface, .. } = context;
|
||||||
|
|
||||||
|
let mut layout_data = LayoutData {
|
||||||
|
current_depth: 0,
|
||||||
|
desired_path: &mut self.element_path,
|
||||||
|
breadcrumbs: Vec::new(),
|
||||||
|
vector_table_tab: self.active_vector_table_tab,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Main data visualization
|
||||||
|
let mut layout = self
|
||||||
|
.introspected_data
|
||||||
|
.as_ref()
|
||||||
|
.map(|instrospected_data| generate_layout(instrospected_data, &mut layout_data).unwrap_or_else(|| label("Visualization of this data type is not yet supported")))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let mut widgets = Vec::new();
|
||||||
|
|
||||||
|
// Selected layer/node name
|
||||||
|
if let Some(node_id) = self.introspected_node {
|
||||||
|
let is_layer = network_interface.is_layer(&node_id, &[]);
|
||||||
|
|
||||||
|
widgets.extend([
|
||||||
|
if is_layer {
|
||||||
|
IconLabel::new("Layer").tooltip("Name of the selected layer").widget_holder()
|
||||||
|
} else {
|
||||||
|
IconLabel::new("Node").tooltip("Name of the selected node").widget_holder()
|
||||||
|
},
|
||||||
|
Separator::new(SeparatorType::Related).widget_holder(),
|
||||||
|
TextInput::new(network_interface.display_name(&node_id, &[]))
|
||||||
|
.tooltip(if is_layer { "Name of the selected layer" } else { "Name of the selected node" })
|
||||||
|
.on_update(move |text_input| {
|
||||||
|
NodeGraphMessage::SetDisplayName {
|
||||||
|
node_id,
|
||||||
|
alias: text_input.value.clone(),
|
||||||
|
skip_adding_history_step: false,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
})
|
||||||
|
.max_width(200)
|
||||||
|
.widget_holder(),
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Element path breadcrumbs
|
||||||
|
if !layout_data.breadcrumbs.is_empty() {
|
||||||
|
let breadcrumb = BreadcrumbTrailButtons::new(layout_data.breadcrumbs)
|
||||||
|
.on_update(|&len| DataPanelMessage::TruncateElementPath { len: len as usize }.into())
|
||||||
|
.widget_holder();
|
||||||
|
widgets.push(breadcrumb);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !widgets.is_empty() {
|
||||||
|
layout.insert(0, LayoutGroup::Row { widgets });
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.add(LayoutMessage::SendLayout {
|
||||||
|
layout: Layout::WidgetLayout(WidgetLayout { layout }),
|
||||||
|
layout_target: LayoutTarget::DataPanel,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LayoutData<'a> {
|
||||||
|
current_depth: usize,
|
||||||
|
desired_path: &'a mut Vec<usize>,
|
||||||
|
breadcrumbs: Vec<String>,
|
||||||
|
vector_table_tab: VectorTableTab,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! generate_layout_downcast {
|
||||||
|
($introspected_data:expr, $data:expr, [ $($ty:ty),* $(,)? ]) => {
|
||||||
|
if false { None }
|
||||||
|
$(
|
||||||
|
else if let Some(io) = $introspected_data.downcast_ref::<IORecord<Context, $ty>>() {
|
||||||
|
Some(io.output.layout_with_breadcrumb($data))
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
else { None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: We simply try all these types sequentially. Find a better strategy.
|
||||||
|
fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
|
||||||
|
generate_layout_downcast!(introspected_data, data, [
|
||||||
|
Table<Artboard>,
|
||||||
|
Table<Graphic>,
|
||||||
|
Table<Vector>,
|
||||||
|
Table<Raster<CPU>>,
|
||||||
|
Table<Raster<GPU>>,
|
||||||
|
Table<Color>,
|
||||||
|
Table<GradientStops>,
|
||||||
|
f64,
|
||||||
|
u32,
|
||||||
|
u64,
|
||||||
|
bool,
|
||||||
|
String,
|
||||||
|
Option<f64>,
|
||||||
|
DVec2,
|
||||||
|
DAffine2,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn column_headings(value: &[&str]) -> Vec<WidgetHolder> {
|
||||||
|
value.iter().map(|text| TextLabel::new(*text).widget_holder()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn label(x: impl Into<String>) -> Vec<LayoutGroup> {
|
||||||
|
let error = vec![TextLabel::new(x).widget_holder()];
|
||||||
|
vec![LayoutGroup::Row { widgets: error }]
|
||||||
|
}
|
||||||
|
|
||||||
|
trait TableRowLayout {
|
||||||
|
fn type_name() -> &'static str;
|
||||||
|
fn identifier(&self) -> String;
|
||||||
|
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
data.breadcrumbs.push(self.identifier());
|
||||||
|
self.element_page(data)
|
||||||
|
}
|
||||||
|
fn element_widget(&self, index: usize) -> WidgetHolder {
|
||||||
|
TextButton::new(self.identifier())
|
||||||
|
.on_update(move |_| DataPanelMessage::PushToElementPath { index }.into())
|
||||||
|
.widget_holder()
|
||||||
|
}
|
||||||
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: TableRowLayout> TableRowLayout for Table<T> {
|
||||||
|
fn type_name() -> &'static str {
|
||||||
|
"Table"
|
||||||
|
}
|
||||||
|
fn identifier(&self) -> String {
|
||||||
|
format!("Table<{}> ({} row{})", T::type_name(), self.len(), if self.len() == 1 { "" } else { "s" })
|
||||||
|
}
|
||||||
|
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
if let Some(index) = data.desired_path.get(data.current_depth).copied() {
|
||||||
|
if let Some(row) = self.get(index) {
|
||||||
|
data.current_depth += 1;
|
||||||
|
let result = row.element.layout_with_breadcrumb(data);
|
||||||
|
data.current_depth -= 1;
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
warn!("Desired path truncated");
|
||||||
|
data.desired_path.truncate(data.current_depth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rows = self
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, row)| {
|
||||||
|
vec![
|
||||||
|
TextLabel::new(format!("{index}")).widget_holder(),
|
||||||
|
row.element.element_widget(index),
|
||||||
|
TextLabel::new(format_transform_matrix(row.transform)).widget_holder(),
|
||||||
|
TextLabel::new(format!("{}", row.alpha_blending)).widget_holder(),
|
||||||
|
TextLabel::new(row.source_node_id.map_or_else(|| "-".to_string(), |id| format!("{}", id.0))).widget_holder(),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
rows.insert(0, column_headings(&["", "element", "transform", "alpha_blending", "source_node_id"]));
|
||||||
|
|
||||||
|
vec![LayoutGroup::Table { rows }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableRowLayout for Artboard {
|
||||||
|
fn type_name() -> &'static str {
|
||||||
|
"Artboard"
|
||||||
|
}
|
||||||
|
fn identifier(&self) -> String {
|
||||||
|
self.label.clone()
|
||||||
|
}
|
||||||
|
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
self.content.element_page(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableRowLayout for Graphic {
|
||||||
|
fn type_name() -> &'static str {
|
||||||
|
"Graphic"
|
||||||
|
}
|
||||||
|
fn identifier(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Graphic(table) => table.identifier(),
|
||||||
|
Self::Vector(table) => table.identifier(),
|
||||||
|
Self::RasterCPU(table) => table.identifier(),
|
||||||
|
Self::RasterGPU(table) => table.identifier(),
|
||||||
|
Self::Color(table) => table.identifier(),
|
||||||
|
Self::Gradient(table) => table.identifier(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Don't put a breadcrumb for Graphic
|
||||||
|
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
self.element_page(data)
|
||||||
|
}
|
||||||
|
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
match self {
|
||||||
|
Self::Graphic(table) => table.layout_with_breadcrumb(data),
|
||||||
|
Self::Vector(table) => table.layout_with_breadcrumb(data),
|
||||||
|
Self::RasterCPU(table) => table.layout_with_breadcrumb(data),
|
||||||
|
Self::RasterGPU(table) => table.layout_with_breadcrumb(data),
|
||||||
|
Self::Color(table) => table.layout_with_breadcrumb(data),
|
||||||
|
Self::Gradient(table) => table.layout_with_breadcrumb(data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableRowLayout for Vector {
|
||||||
|
fn type_name() -> &'static str {
|
||||||
|
"Vector"
|
||||||
|
}
|
||||||
|
fn identifier(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"Vector ({} point{}, {} segment{})",
|
||||||
|
self.point_domain.ids().len(),
|
||||||
|
if self.point_domain.ids().len() == 1 { "" } else { "s" },
|
||||||
|
self.segment_domain.ids().len(),
|
||||||
|
if self.segment_domain.ids().len() == 1 { "" } else { "s" }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
let table_tab_entries = [VectorTableTab::Properties, VectorTableTab::Points, VectorTableTab::Segments, VectorTableTab::Regions]
|
||||||
|
.into_iter()
|
||||||
|
.map(|tab| {
|
||||||
|
RadioEntryData::new(format!("{tab:?}"))
|
||||||
|
.label(format!("{tab:?}"))
|
||||||
|
.on_update(move |_| DataPanelMessage::ViewVectorTableTab { tab }.into())
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let table_tabs = vec![RadioInput::new(table_tab_entries).selected_index(Some(data.vector_table_tab as u32)).widget_holder()];
|
||||||
|
|
||||||
|
let mut table_rows = Vec::new();
|
||||||
|
match data.vector_table_tab {
|
||||||
|
VectorTableTab::Properties => {
|
||||||
|
table_rows.push(column_headings(&["property", "value"]));
|
||||||
|
|
||||||
|
match self.style.fill.clone() {
|
||||||
|
Fill::None => table_rows.push(vec![
|
||||||
|
TextLabel::new("Fill").widget_holder(),
|
||||||
|
ColorInput::new(FillChoice::None).disabled(true).menu_direction(Some(MenuDirection::Top)).widget_holder(),
|
||||||
|
]),
|
||||||
|
Fill::Solid(color) => table_rows.push(vec![
|
||||||
|
TextLabel::new("Fill").widget_holder(),
|
||||||
|
ColorInput::new(FillChoice::Solid(color)).disabled(true).menu_direction(Some(MenuDirection::Top)).widget_holder(),
|
||||||
|
]),
|
||||||
|
Fill::Gradient(gradient) => {
|
||||||
|
table_rows.push(vec![
|
||||||
|
TextLabel::new("Fill").widget_holder(),
|
||||||
|
ColorInput::new(FillChoice::Gradient(gradient.stops))
|
||||||
|
.disabled(true)
|
||||||
|
.menu_direction(Some(MenuDirection::Top))
|
||||||
|
.widget_holder(),
|
||||||
|
]);
|
||||||
|
table_rows.push(vec![
|
||||||
|
TextLabel::new("Fill Gradient Type").widget_holder(),
|
||||||
|
TextLabel::new(gradient.gradient_type.to_string()).widget_holder(),
|
||||||
|
]);
|
||||||
|
table_rows.push(vec![
|
||||||
|
TextLabel::new("Fill Gradient Start").widget_holder(),
|
||||||
|
TextLabel::new(format_dvec2(gradient.start)).widget_holder(),
|
||||||
|
]);
|
||||||
|
table_rows.push(vec![TextLabel::new("Fill Gradient End").widget_holder(), TextLabel::new(format_dvec2(gradient.end)).widget_holder()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(stroke) = self.style.stroke.clone() {
|
||||||
|
let color = if let Some(color) = stroke.color { FillChoice::Solid(color) } else { FillChoice::None };
|
||||||
|
table_rows.push(vec![
|
||||||
|
TextLabel::new("Stroke").widget_holder(),
|
||||||
|
ColorInput::new(color).disabled(true).menu_direction(Some(MenuDirection::Top)).widget_holder(),
|
||||||
|
]);
|
||||||
|
table_rows.push(vec![TextLabel::new("Stroke Weight").widget_holder(), TextLabel::new(format!("{} px", stroke.weight)).widget_holder()]);
|
||||||
|
table_rows.push(vec![
|
||||||
|
TextLabel::new("Stroke Dash Lengths").widget_holder(),
|
||||||
|
TextLabel::new(if stroke.dash_lengths.is_empty() {
|
||||||
|
"-".to_string()
|
||||||
|
} else {
|
||||||
|
format!("[{}]", stroke.dash_lengths.iter().map(|x| format!("{x} px")).collect::<Vec<_>>().join(", "))
|
||||||
|
})
|
||||||
|
.widget_holder(),
|
||||||
|
]);
|
||||||
|
table_rows.push(vec![
|
||||||
|
TextLabel::new("Stroke Dash Offset").widget_holder(),
|
||||||
|
TextLabel::new(format!("{}", stroke.dash_offset)).widget_holder(),
|
||||||
|
]);
|
||||||
|
table_rows.push(vec![TextLabel::new("Stroke Cap").widget_holder(), TextLabel::new(stroke.cap.to_string()).widget_holder()]);
|
||||||
|
table_rows.push(vec![TextLabel::new("Stroke Join").widget_holder(), TextLabel::new(stroke.join.to_string()).widget_holder()]);
|
||||||
|
table_rows.push(vec![
|
||||||
|
TextLabel::new("Stroke Join Miter Limit").widget_holder(),
|
||||||
|
TextLabel::new(format!("{}", stroke.join_miter_limit)).widget_holder(),
|
||||||
|
]);
|
||||||
|
table_rows.push(vec![TextLabel::new("Stroke Align").widget_holder(), TextLabel::new(stroke.align.to_string()).widget_holder()]);
|
||||||
|
table_rows.push(vec![
|
||||||
|
TextLabel::new("Stroke Transform").widget_holder(),
|
||||||
|
TextLabel::new(format_transform_matrix(&stroke.transform)).widget_holder(),
|
||||||
|
]);
|
||||||
|
table_rows.push(vec![
|
||||||
|
TextLabel::new("Stroke Non-Scaling").widget_holder(),
|
||||||
|
TextLabel::new((if stroke.non_scaling { "Yes" } else { "No" }).to_string()).widget_holder(),
|
||||||
|
]);
|
||||||
|
table_rows.push(vec![
|
||||||
|
TextLabel::new("Stroke Paint Order").widget_holder(),
|
||||||
|
TextLabel::new(stroke.paint_order.to_string()).widget_holder(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let colinear = self.colinear_manipulators.iter().map(|[a, b]| format!("[{a} / {b}]")).collect::<Vec<_>>().join(", ");
|
||||||
|
let colinear = if colinear.is_empty() { "-".to_string() } else { colinear };
|
||||||
|
table_rows.push(vec![TextLabel::new("Colinear Handle IDs").widget_holder(), TextLabel::new(colinear).widget_holder()]);
|
||||||
|
|
||||||
|
table_rows.push(vec![
|
||||||
|
TextLabel::new("Upstream Nested Layers").widget_holder(),
|
||||||
|
TextLabel::new(if self.upstream_nested_layers.is_some() {
|
||||||
|
"Yes (this preserves references to its upstream nested layers for editing by tools)"
|
||||||
|
} else {
|
||||||
|
"No (this doesn't preserve references to its upstream nested layers for editing by tools)"
|
||||||
|
})
|
||||||
|
.widget_holder(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
VectorTableTab::Points => {
|
||||||
|
table_rows.push(column_headings(&["", "position"]));
|
||||||
|
table_rows.extend(
|
||||||
|
self.point_domain
|
||||||
|
.iter()
|
||||||
|
.map(|(id, position)| vec![TextLabel::new(format!("{}", id.inner())).widget_holder(), TextLabel::new(format!("{position}")).widget_holder()]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
VectorTableTab::Segments => {
|
||||||
|
table_rows.push(column_headings(&["", "start_index", "end_index", "handles"]));
|
||||||
|
table_rows.extend(self.segment_domain.iter().map(|(id, start, end, handles)| {
|
||||||
|
vec![
|
||||||
|
TextLabel::new(format!("{}", id.inner())).widget_holder(),
|
||||||
|
TextLabel::new(format!("{start}")).widget_holder(),
|
||||||
|
TextLabel::new(format!("{end}")).widget_holder(),
|
||||||
|
TextLabel::new(format!("{handles:?}")).widget_holder(),
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
VectorTableTab::Regions => {
|
||||||
|
table_rows.push(column_headings(&["", "segment_range", "fill"]));
|
||||||
|
table_rows.extend(self.region_domain.iter().map(|(id, segment_range, fill)| {
|
||||||
|
vec![
|
||||||
|
TextLabel::new(format!("{}", id.inner())).widget_holder(),
|
||||||
|
TextLabel::new(format!("{segment_range:?}")).widget_holder(),
|
||||||
|
TextLabel::new(format!("{}", fill.inner())).widget_holder(),
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vec![LayoutGroup::Row { widgets: table_tabs }, LayoutGroup::Table { rows: table_rows }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableRowLayout for Raster<CPU> {
|
||||||
|
fn type_name() -> &'static str {
|
||||||
|
"Raster"
|
||||||
|
}
|
||||||
|
fn identifier(&self) -> String {
|
||||||
|
format!("Raster ({}x{})", self.width, self.height)
|
||||||
|
}
|
||||||
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
let base64_string = self.data().base64_string.clone().unwrap_or_else(|| {
|
||||||
|
use base64::Engine;
|
||||||
|
|
||||||
|
let output = self.data().to_png();
|
||||||
|
let preamble = "data:image/png;base64,";
|
||||||
|
let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4);
|
||||||
|
base64_string.push_str(preamble);
|
||||||
|
base64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string);
|
||||||
|
base64_string
|
||||||
|
});
|
||||||
|
|
||||||
|
let widgets = vec![ImageLabel::new(base64_string).widget_holder()];
|
||||||
|
vec![LayoutGroup::Row { widgets }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableRowLayout for Raster<GPU> {
|
||||||
|
fn type_name() -> &'static str {
|
||||||
|
"Raster"
|
||||||
|
}
|
||||||
|
fn identifier(&self) -> String {
|
||||||
|
format!("Raster ({}x{})", self.data().width(), self.data().height())
|
||||||
|
}
|
||||||
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
let widgets = vec![TextLabel::new("Raster is a texture on the GPU and cannot currently be displayed here").widget_holder()];
|
||||||
|
vec![LayoutGroup::Row { widgets }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableRowLayout for Color {
|
||||||
|
fn type_name() -> &'static str {
|
||||||
|
"Color"
|
||||||
|
}
|
||||||
|
fn identifier(&self) -> String {
|
||||||
|
format!("Color (#{})", self.to_gamma_srgb().to_rgba_hex_srgb())
|
||||||
|
}
|
||||||
|
fn element_widget(&self, _index: usize) -> WidgetHolder {
|
||||||
|
ColorInput::new(FillChoice::Solid(*self)).disabled(true).menu_direction(Some(MenuDirection::Top)).widget_holder()
|
||||||
|
}
|
||||||
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
let widgets = vec![self.element_widget(0)];
|
||||||
|
vec![LayoutGroup::Row { widgets }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableRowLayout for GradientStops {
|
||||||
|
fn type_name() -> &'static str {
|
||||||
|
"Gradient"
|
||||||
|
}
|
||||||
|
fn identifier(&self) -> String {
|
||||||
|
format!("Gradient ({} stops)", self.0.len())
|
||||||
|
}
|
||||||
|
fn element_widget(&self, _index: usize) -> WidgetHolder {
|
||||||
|
ColorInput::new(FillChoice::Gradient(self.clone()))
|
||||||
|
.disabled(true)
|
||||||
|
.menu_direction(Some(MenuDirection::Top))
|
||||||
|
.widget_holder()
|
||||||
|
}
|
||||||
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
let widgets = vec![self.element_widget(0)];
|
||||||
|
vec![LayoutGroup::Row { widgets }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableRowLayout for f64 {
|
||||||
|
fn type_name() -> &'static str {
|
||||||
|
"Number (f64)"
|
||||||
|
}
|
||||||
|
fn identifier(&self) -> String {
|
||||||
|
"Number (f64)".to_string()
|
||||||
|
}
|
||||||
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
let widgets = vec![TextLabel::new(self.to_string()).widget_holder()];
|
||||||
|
vec![LayoutGroup::Row { widgets }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableRowLayout for u32 {
|
||||||
|
fn type_name() -> &'static str {
|
||||||
|
"Number (u32)"
|
||||||
|
}
|
||||||
|
fn identifier(&self) -> String {
|
||||||
|
"Number (u32)".to_string()
|
||||||
|
}
|
||||||
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
let widgets = vec![TextLabel::new(self.to_string()).widget_holder()];
|
||||||
|
vec![LayoutGroup::Row { widgets }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableRowLayout for u64 {
|
||||||
|
fn type_name() -> &'static str {
|
||||||
|
"Number (u64)"
|
||||||
|
}
|
||||||
|
fn identifier(&self) -> String {
|
||||||
|
"Number (u64)".to_string()
|
||||||
|
}
|
||||||
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
let widgets = vec![TextLabel::new(self.to_string()).widget_holder()];
|
||||||
|
vec![LayoutGroup::Row { widgets }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableRowLayout for bool {
|
||||||
|
fn type_name() -> &'static str {
|
||||||
|
"Bool"
|
||||||
|
}
|
||||||
|
fn identifier(&self) -> String {
|
||||||
|
"Bool".to_string()
|
||||||
|
}
|
||||||
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
let widgets = vec![TextLabel::new(self.to_string()).widget_holder()];
|
||||||
|
vec![LayoutGroup::Row { widgets }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableRowLayout for String {
|
||||||
|
fn type_name() -> &'static str {
|
||||||
|
"String"
|
||||||
|
}
|
||||||
|
fn identifier(&self) -> String {
|
||||||
|
"String".to_string()
|
||||||
|
}
|
||||||
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
let widgets = vec![TextAreaInput::new(self.to_string()).disabled(true).widget_holder()];
|
||||||
|
vec![LayoutGroup::Row { widgets }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableRowLayout for Option<f64> {
|
||||||
|
fn type_name() -> &'static str {
|
||||||
|
"Option<f64>"
|
||||||
|
}
|
||||||
|
fn identifier(&self) -> String {
|
||||||
|
"Option<f64>".to_string()
|
||||||
|
}
|
||||||
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
let widgets = vec![TextLabel::new(format!("{self:?}")).widget_holder()];
|
||||||
|
vec![LayoutGroup::Row { widgets }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableRowLayout for DVec2 {
|
||||||
|
fn type_name() -> &'static str {
|
||||||
|
"Vec2"
|
||||||
|
}
|
||||||
|
fn identifier(&self) -> String {
|
||||||
|
"Vec2".to_string()
|
||||||
|
}
|
||||||
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
let widgets = vec![TextLabel::new(format!("({}, {})", self.x, self.y)).widget_holder()];
|
||||||
|
vec![LayoutGroup::Row { widgets }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TableRowLayout for DAffine2 {
|
||||||
|
fn type_name() -> &'static str {
|
||||||
|
"Transform"
|
||||||
|
}
|
||||||
|
fn identifier(&self) -> String {
|
||||||
|
"Transform".to_string()
|
||||||
|
}
|
||||||
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
let widgets = vec![TextLabel::new(format_transform_matrix(self)).widget_holder()];
|
||||||
|
vec![LayoutGroup::Row { widgets }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_transform_matrix(transform: &DAffine2) -> String {
|
||||||
|
let (scale, angle, translation) = transform.to_scale_angle_translation();
|
||||||
|
let rotation = if angle == -0. { 0. } else { angle.to_degrees() };
|
||||||
|
let round = |x: f64| (x * 1e3).round() / 1e3;
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"Location: ({} px, {} px) — Rotation: {rotation:2}° — Scale: ({}x, {}x)",
|
||||||
|
round(translation.x),
|
||||||
|
round(translation.y),
|
||||||
|
round(scale.x),
|
||||||
|
round(scale.y)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_dvec2(value: DVec2) -> String {
|
||||||
|
let round = |x: f64| (x * 1e3).round() / 1e3;
|
||||||
|
format!("({} px, {} px)", round(value.x), round(value.y))
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
mod data_panel_message;
|
||||||
|
mod data_panel_message_handler;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use data_panel_message::*;
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use data_panel_message_handler::*;
|
||||||
@@ -2,6 +2,7 @@ use std::path::PathBuf;
|
|||||||
|
|
||||||
use super::utility_types::misc::{GroupFolderType, SnappingState};
|
use super::utility_types::misc::{GroupFolderType, SnappingState};
|
||||||
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
||||||
|
use crate::messages::portfolio::document::data_panel::DataPanelMessage;
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlaysType;
|
use crate::messages::portfolio::document::overlays::utility_types::OverlaysType;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
@@ -33,6 +34,8 @@ pub enum DocumentMessage {
|
|||||||
Overlays(OverlaysMessage),
|
Overlays(OverlaysMessage),
|
||||||
#[child]
|
#[child]
|
||||||
PropertiesPanel(PropertiesPanelMessage),
|
PropertiesPanel(PropertiesPanelMessage),
|
||||||
|
#[child]
|
||||||
|
DataPanel(DataPanelMessage),
|
||||||
|
|
||||||
// Messages
|
// Messages
|
||||||
AlignSelectedLayers {
|
AlignSelectedLayers {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use crate::application::{GRAPHITE_GIT_COMMIT_HASH, generate_uuid};
|
|||||||
use crate::consts::{ASYMPTOTIC_EFFECT, COLOR_OVERLAY_GRAY, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ROTATE_SNAP_INTERVAL};
|
use crate::consts::{ASYMPTOTIC_EFFECT, COLOR_OVERLAY_GRAY, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ROTATE_SNAP_INTERVAL};
|
||||||
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
|
use crate::messages::portfolio::document::data_panel::{DataPanelMessageContext, DataPanelMessageHandler};
|
||||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||||
use crate::messages::portfolio::document::node_graph::NodeGraphMessageContext;
|
use crate::messages::portfolio::document::node_graph::NodeGraphMessageContext;
|
||||||
use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options};
|
use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options};
|
||||||
@@ -18,6 +19,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::{Doc
|
|||||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ};
|
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ};
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate};
|
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate};
|
||||||
use crate::messages::portfolio::document::utility_types::nodes::RawBuffer;
|
use crate::messages::portfolio::document::utility_types::nodes::RawBuffer;
|
||||||
|
use crate::messages::portfolio::utility_types::PanelType;
|
||||||
use crate::messages::portfolio::utility_types::PersistentData;
|
use crate::messages::portfolio::utility_types::PersistentData;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_blend_mode, get_fill, get_opacity};
|
use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_blend_mode, get_fill, get_opacity};
|
||||||
@@ -25,7 +27,6 @@ use crate::messages::tool::tool_messages::select_tool::SelectToolPointerKeys;
|
|||||||
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
||||||
use crate::messages::tool::utility_types::ToolType;
|
use crate::messages::tool::utility_types::ToolType;
|
||||||
use crate::node_graph_executor::NodeGraphExecutor;
|
use crate::node_graph_executor::NodeGraphExecutor;
|
||||||
use bezier_rs::Subpath;
|
|
||||||
use glam::{DAffine2, DVec2, IVec2};
|
use glam::{DAffine2, DVec2, IVec2};
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork};
|
use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork};
|
||||||
@@ -33,10 +34,13 @@ use graphene_std::math::quad::Quad;
|
|||||||
use graphene_std::path_bool::{boolean_intersect, path_bool_lib};
|
use graphene_std::path_bool::{boolean_intersect, path_bool_lib};
|
||||||
use graphene_std::raster::BlendMode;
|
use graphene_std::raster::BlendMode;
|
||||||
use graphene_std::raster_types::Raster;
|
use graphene_std::raster_types::Raster;
|
||||||
|
use graphene_std::subpath::Subpath;
|
||||||
use graphene_std::table::Table;
|
use graphene_std::table::Table;
|
||||||
use graphene_std::vector::PointId;
|
use graphene_std::vector::PointId;
|
||||||
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||||
|
use graphene_std::vector::misc::{dvec2_to_point, point_to_dvec2};
|
||||||
use graphene_std::vector::style::ViewMode;
|
use graphene_std::vector::style::ViewMode;
|
||||||
|
use kurbo::{Affine, CubicBez, Line, ParamCurve, PathSeg, QuadBez};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
@@ -49,6 +53,9 @@ pub struct DocumentMessageContext<'a> {
|
|||||||
pub current_tool: &'a ToolType,
|
pub current_tool: &'a ToolType,
|
||||||
pub preferences: &'a PreferencesMessageHandler,
|
pub preferences: &'a PreferencesMessageHandler,
|
||||||
pub device_pixel_ratio: f64,
|
pub device_pixel_ratio: f64,
|
||||||
|
pub data_panel_open: bool,
|
||||||
|
pub layers_panel_open: bool,
|
||||||
|
pub properties_panel_open: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)]
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)]
|
||||||
@@ -63,9 +70,11 @@ pub struct DocumentMessageHandler {
|
|||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub node_graph_handler: NodeGraphMessageHandler,
|
pub node_graph_handler: NodeGraphMessageHandler,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
overlays_message_handler: OverlaysMessageHandler,
|
pub overlays_message_handler: OverlaysMessageHandler,
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
properties_panel_message_handler: PropertiesPanelMessageHandler,
|
pub properties_panel_message_handler: PropertiesPanelMessageHandler,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub data_panel_message_handler: DataPanelMessageHandler,
|
||||||
|
|
||||||
// ============================================
|
// ============================================
|
||||||
// Fields that are saved in the document format
|
// Fields that are saved in the document format
|
||||||
@@ -144,6 +153,7 @@ impl Default for DocumentMessageHandler {
|
|||||||
node_graph_handler: NodeGraphMessageHandler::default(),
|
node_graph_handler: NodeGraphMessageHandler::default(),
|
||||||
overlays_message_handler: OverlaysMessageHandler::default(),
|
overlays_message_handler: OverlaysMessageHandler::default(),
|
||||||
properties_panel_message_handler: PropertiesPanelMessageHandler::default(),
|
properties_panel_message_handler: PropertiesPanelMessageHandler::default(),
|
||||||
|
data_panel_message_handler: DataPanelMessageHandler::default(),
|
||||||
// ============================================
|
// ============================================
|
||||||
// Fields that are saved in the document format
|
// Fields that are saved in the document format
|
||||||
// ============================================
|
// ============================================
|
||||||
@@ -186,6 +196,9 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
current_tool,
|
current_tool,
|
||||||
preferences,
|
preferences,
|
||||||
device_pixel_ratio,
|
device_pixel_ratio,
|
||||||
|
data_panel_open,
|
||||||
|
layers_panel_open,
|
||||||
|
properties_panel_open,
|
||||||
} = context;
|
} = context;
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
@@ -223,9 +236,20 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
document_name: self.name.as_str(),
|
document_name: self.name.as_str(),
|
||||||
executor,
|
executor,
|
||||||
persistent_data,
|
persistent_data,
|
||||||
|
properties_panel_open,
|
||||||
};
|
};
|
||||||
self.properties_panel_message_handler.process_message(message, responses, context);
|
self.properties_panel_message_handler.process_message(message, responses, context);
|
||||||
}
|
}
|
||||||
|
DocumentMessage::DataPanel(message) => {
|
||||||
|
self.data_panel_message_handler.process_message(
|
||||||
|
message,
|
||||||
|
responses,
|
||||||
|
DataPanelMessageContext {
|
||||||
|
network_interface: &mut self.network_interface,
|
||||||
|
data_panel_open,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
DocumentMessage::NodeGraph(message) => {
|
DocumentMessage::NodeGraph(message) => {
|
||||||
self.node_graph_handler.process_message(
|
self.node_graph_handler.process_message(
|
||||||
message,
|
message,
|
||||||
@@ -241,6 +265,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
graph_fade_artwork_percentage: self.graph_fade_artwork_percentage,
|
graph_fade_artwork_percentage: self.graph_fade_artwork_percentage,
|
||||||
navigation_handler: &self.navigation_handler,
|
navigation_handler: &self.navigation_handler,
|
||||||
preferences,
|
preferences,
|
||||||
|
layers_panel_open,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -356,12 +381,15 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
DocumentMessage::DocumentHistoryBackward => self.undo_with_history(ipp, responses),
|
DocumentMessage::DocumentHistoryBackward => self.undo_with_history(ipp, responses),
|
||||||
DocumentMessage::DocumentHistoryForward => self.redo_with_history(ipp, responses),
|
DocumentMessage::DocumentHistoryForward => self.redo_with_history(ipp, responses),
|
||||||
DocumentMessage::DocumentStructureChanged => {
|
DocumentMessage::DocumentStructureChanged => {
|
||||||
self.update_layers_panel_control_bar_widgets(responses);
|
if layers_panel_open {
|
||||||
self.update_layers_panel_bottom_bar_widgets(responses);
|
self.network_interface.load_structure();
|
||||||
|
let data_buffer: RawBuffer = self.serialize_root();
|
||||||
|
|
||||||
self.network_interface.load_structure();
|
self.update_layers_panel_control_bar_widgets(layers_panel_open, responses);
|
||||||
let data_buffer: RawBuffer = self.serialize_root();
|
self.update_layers_panel_bottom_bar_widgets(layers_panel_open, responses);
|
||||||
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
|
|
||||||
|
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DocumentMessage::DrawArtboardOverlays(overlay_context) => {
|
DocumentMessage::DrawArtboardOverlays(overlay_context) => {
|
||||||
if !overlay_context.visibility_settings.artboard_name() {
|
if !overlay_context.visibility_settings.artboard_name() {
|
||||||
@@ -554,6 +582,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
responses.add(NodeGraphMessage::UpdateHints);
|
responses.add(NodeGraphMessage::UpdateHints);
|
||||||
} else {
|
} else {
|
||||||
responses.add(ToolMessage::ActivateTool { tool_type: *current_tool });
|
responses.add(ToolMessage::ActivateTool { tool_type: *current_tool });
|
||||||
|
responses.add(OverlaysMessage::Draw); // Redraw overlays when graph is closed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DocumentMessage::GraphViewOverlayToggle => {
|
DocumentMessage::GraphViewOverlayToggle => {
|
||||||
@@ -1128,7 +1157,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
DocumentMessage::SetActivePanel { active_panel: panel } => {
|
DocumentMessage::SetActivePanel { active_panel: panel } => {
|
||||||
use crate::messages::portfolio::utility_types::PanelType;
|
|
||||||
match panel {
|
match panel {
|
||||||
PanelType::Document => {
|
PanelType::Document => {
|
||||||
if self.graph_view_overlay_open {
|
if self.graph_view_overlay_open {
|
||||||
@@ -2549,7 +2577,11 @@ impl DocumentMessageHandler {
|
|||||||
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
|
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_layers_panel_control_bar_widgets(&self, responses: &mut VecDeque<Message>) {
|
pub fn update_layers_panel_control_bar_widgets(&self, layers_panel_open: bool, responses: &mut VecDeque<Message>) {
|
||||||
|
if !layers_panel_open {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Get an iterator over the selected layers (excluding artboards which don't have an opacity or blend mode).
|
// Get an iterator over the selected layers (excluding artboards which don't have an opacity or blend mode).
|
||||||
let selected_nodes = self.network_interface.selected_nodes();
|
let selected_nodes = self.network_interface.selected_nodes();
|
||||||
let selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&self.network_interface);
|
let selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&self.network_interface);
|
||||||
@@ -2707,7 +2739,11 @@ impl DocumentMessageHandler {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_layers_panel_bottom_bar_widgets(&self, responses: &mut VecDeque<Message>) {
|
pub fn update_layers_panel_bottom_bar_widgets(&self, layers_panel_open: bool, responses: &mut VecDeque<Message>) {
|
||||||
|
if !layers_panel_open {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let selected_nodes = self.network_interface.selected_nodes();
|
let selected_nodes = self.network_interface.selected_nodes();
|
||||||
let mut selected_layers = selected_nodes.selected_layers(self.metadata());
|
let mut selected_layers = selected_nodes.selected_layers(self.metadata());
|
||||||
let selected_layer = selected_layers.next();
|
let selected_layer = selected_layers.next();
|
||||||
@@ -2949,10 +2985,10 @@ fn quad_to_path_lib_segments(quad: Quad) -> Vec<path_bool_lib::PathSegment> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn click_targets_to_path_lib_segments<'a>(click_targets: impl Iterator<Item = &'a ClickTarget>, transform: DAffine2) -> Vec<path_bool_lib::PathSegment> {
|
fn click_targets_to_path_lib_segments<'a>(click_targets: impl Iterator<Item = &'a ClickTarget>, transform: DAffine2) -> Vec<path_bool_lib::PathSegment> {
|
||||||
let segment = |bezier: bezier_rs::Bezier| match bezier.handles {
|
let segment = |bezier: PathSeg| match bezier {
|
||||||
bezier_rs::BezierHandles::Linear => path_bool_lib::PathSegment::Line(bezier.start, bezier.end),
|
PathSeg::Line(line) => path_bool_lib::PathSegment::Line(point_to_dvec2(line.p0), point_to_dvec2(line.p1)),
|
||||||
bezier_rs::BezierHandles::Quadratic { handle } => path_bool_lib::PathSegment::Quadratic(bezier.start, handle, bezier.end),
|
PathSeg::Quad(quad_bez) => path_bool_lib::PathSegment::Quadratic(point_to_dvec2(quad_bez.p0), point_to_dvec2(quad_bez.p1), point_to_dvec2(quad_bez.p2)),
|
||||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => path_bool_lib::PathSegment::Cubic(bezier.start, handle_start, handle_end, bezier.end),
|
PathSeg::Cubic(cubic_bez) => path_bool_lib::PathSegment::Cubic(point_to_dvec2(cubic_bez.p0), point_to_dvec2(cubic_bez.p1), point_to_dvec2(cubic_bez.p2), point_to_dvec2(cubic_bez.p3)),
|
||||||
};
|
};
|
||||||
click_targets
|
click_targets
|
||||||
.filter_map(|target| {
|
.filter_map(|target| {
|
||||||
@@ -2963,7 +2999,7 @@ fn click_targets_to_path_lib_segments<'a>(click_targets: impl Iterator<Item = &'
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(|bezier| segment(bezier.apply_transformation(|x| transform.transform_point2(x))))
|
.map(|bezier| segment(Affine::new(transform.to_cols_array()) * bezier))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2986,11 +3022,11 @@ impl<'a> ClickXRayIter<'a> {
|
|||||||
|
|
||||||
/// Handles the checking of the layer where the target is a rect or path
|
/// Handles the checking of the layer where the target is a rect or path
|
||||||
fn check_layer_area_target(&mut self, click_targets: Option<&Vec<ClickTarget>>, clip: bool, layer: LayerNodeIdentifier, path: Vec<path_bool_lib::PathSegment>, transform: DAffine2) -> XRayResult {
|
fn check_layer_area_target(&mut self, click_targets: Option<&Vec<ClickTarget>>, clip: bool, layer: LayerNodeIdentifier, path: Vec<path_bool_lib::PathSegment>, transform: DAffine2) -> XRayResult {
|
||||||
// Convert back to Bezier-rs types for intersections
|
// Convert back to Kurbo types for intersections
|
||||||
let segment = |bezier: &path_bool_lib::PathSegment| match *bezier {
|
let segment = |bezier: &path_bool_lib::PathSegment| match *bezier {
|
||||||
path_bool_lib::PathSegment::Line(start, end) => bezier_rs::Bezier::from_linear_dvec2(start, end),
|
path_bool_lib::PathSegment::Line(start, end) => PathSeg::Line(Line::new(dvec2_to_point(start), dvec2_to_point(end))),
|
||||||
path_bool_lib::PathSegment::Cubic(start, h1, h2, end) => bezier_rs::Bezier::from_cubic_dvec2(start, h1, h2, end),
|
path_bool_lib::PathSegment::Cubic(start, h1, h2, end) => PathSeg::Cubic(CubicBez::new(dvec2_to_point(start), dvec2_to_point(h1), dvec2_to_point(h2), dvec2_to_point(end))),
|
||||||
path_bool_lib::PathSegment::Quadratic(start, h1, end) => bezier_rs::Bezier::from_quadratic_dvec2(start, h1, end),
|
path_bool_lib::PathSegment::Quadratic(start, h1, end) => PathSeg::Quad(QuadBez::new(dvec2_to_point(start), dvec2_to_point(h1), dvec2_to_point(end))),
|
||||||
path_bool_lib::PathSegment::Arc(_, _, _, _, _, _, _) => unimplemented!(),
|
path_bool_lib::PathSegment::Arc(_, _, _, _, _, _, _) => unimplemented!(),
|
||||||
};
|
};
|
||||||
let get_clip = || path.iter().map(segment);
|
let get_clip = || path.iter().map(segment);
|
||||||
@@ -3039,7 +3075,10 @@ impl<'a> ClickXRayIter<'a> {
|
|||||||
XRayTarget::Quad(quad) => self.check_layer_area_target(click_targets, clip, layer, quad_to_path_lib_segments(*quad), transform),
|
XRayTarget::Quad(quad) => self.check_layer_area_target(click_targets, clip, layer, quad_to_path_lib_segments(*quad), transform),
|
||||||
XRayTarget::Path(path) => self.check_layer_area_target(click_targets, clip, layer, path.clone(), transform),
|
XRayTarget::Path(path) => self.check_layer_area_target(click_targets, clip, layer, path.clone(), transform),
|
||||||
XRayTarget::Polygon(polygon) => {
|
XRayTarget::Polygon(polygon) => {
|
||||||
let polygon = polygon.iter_closed().map(|line| path_bool_lib::PathSegment::Line(line.start, line.end)).collect();
|
let polygon = polygon
|
||||||
|
.iter_closed()
|
||||||
|
.map(|line| path_bool_lib::PathSegment::Line(point_to_dvec2(line.start()), point_to_dvec2(line.end())))
|
||||||
|
.collect();
|
||||||
self.check_layer_area_target(click_targets, clip, layer, polygon, transform)
|
self.check_layer_area_target(click_targets, clip, layer, polygon, transform)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ use super::utility_types::TransformIn;
|
|||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
|
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use bezier_rs::Subpath;
|
|
||||||
use glam::{DAffine2, IVec2};
|
use glam::{DAffine2, IVec2};
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_std::Artboard;
|
use graphene_std::Artboard;
|
||||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||||
use graphene_std::raster::BlendMode;
|
use graphene_std::raster::BlendMode;
|
||||||
use graphene_std::raster_types::{CPU, Raster};
|
use graphene_std::raster_types::{CPU, Raster};
|
||||||
|
use graphene_std::subpath::Subpath;
|
||||||
use graphene_std::table::Table;
|
use graphene_std::table::Table;
|
||||||
use graphene_std::text::{Font, TypesettingConfig};
|
use graphene_std::text::{Font, TypesettingConfig};
|
||||||
use graphene_std::vector::PointId;
|
use graphene_std::vector::PointId;
|
||||||
|
|||||||
-2
@@ -426,7 +426,6 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, t
|
|||||||
Fill::Gradient(Gradient {
|
Fill::Gradient(Gradient {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
transform: DAffine2::IDENTITY,
|
|
||||||
gradient_type: GradientType::Linear,
|
gradient_type: GradientType::Linear,
|
||||||
stops,
|
stops,
|
||||||
})
|
})
|
||||||
@@ -453,7 +452,6 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, t
|
|||||||
Fill::Gradient(Gradient {
|
Fill::Gradient(Gradient {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
transform: DAffine2::IDENTITY,
|
|
||||||
gradient_type: GradientType::Radial,
|
gradient_type: GradientType::Radial,
|
||||||
stops,
|
stops,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface};
|
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface};
|
||||||
use bezier_rs::Subpath;
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{NodeId, NodeInput};
|
use graph_craft::document::{NodeId, NodeInput};
|
||||||
|
use graphene_std::subpath::Subpath;
|
||||||
use graphene_std::vector::PointId;
|
use graphene_std::vector::PointId;
|
||||||
|
|
||||||
/// Convert an affine transform into the tuple `(scale, angle, translation, shear)` assuming `shear.y = 0`.
|
/// Convert an affine transform into the tuple `(scale, angle, translation, shear)` assuming `shear.y = 0`.
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions:
|
|||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector};
|
use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use bezier_rs::Subpath;
|
|
||||||
use glam::{DAffine2, IVec2};
|
use glam::{DAffine2, IVec2};
|
||||||
use graph_craft::concrete;
|
use graph_craft::concrete;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
@@ -12,6 +11,7 @@ use graphene_std::Artboard;
|
|||||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||||
use graphene_std::raster::BlendMode;
|
use graphene_std::raster::BlendMode;
|
||||||
use graphene_std::raster_types::{CPU, Raster};
|
use graphene_std::raster_types::{CPU, Raster};
|
||||||
|
use graphene_std::subpath::Subpath;
|
||||||
use graphene_std::table::Table;
|
use graphene_std::table::Table;
|
||||||
use graphene_std::text::{Font, TypesettingConfig};
|
use graphene_std::text::{Font, TypesettingConfig};
|
||||||
use graphene_std::vector::Vector;
|
use graphene_std::vector::Vector;
|
||||||
@@ -135,7 +135,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
Some(NodeInput::value(TaggedValue::Graphic(Default::default()), true)),
|
Some(NodeInput::value(TaggedValue::Graphic(Default::default()), true)),
|
||||||
Some(NodeInput::value(TaggedValue::DVec2(artboard.location.into()), false)),
|
Some(NodeInput::value(TaggedValue::DVec2(artboard.location.into()), false)),
|
||||||
Some(NodeInput::value(TaggedValue::DVec2(artboard.dimensions.into()), false)),
|
Some(NodeInput::value(TaggedValue::DVec2(artboard.dimensions.into()), false)),
|
||||||
Some(NodeInput::value(TaggedValue::Color(artboard.background), false)),
|
Some(NodeInput::value(TaggedValue::Color(Table::new_from_element(artboard.background)), false)),
|
||||||
Some(NodeInput::value(TaggedValue::Bool(artboard.clip), false)),
|
Some(NodeInput::value(TaggedValue::Bool(artboard.clip), false)),
|
||||||
]);
|
]);
|
||||||
self.network_interface.insert_node(new_id, artboard_node_template, &[]);
|
self.network_interface.insert_node(new_id, artboard_node_template, &[]);
|
||||||
@@ -329,11 +329,11 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
match &fill {
|
match &fill {
|
||||||
Fill::None => {
|
Fill::None => {
|
||||||
let input_connector = InputConnector::node(fill_node_id, backup_color_index);
|
let input_connector = InputConnector::node(fill_node_id, backup_color_index);
|
||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::OptionalColor(None), false), true);
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(Table::new()), false), true);
|
||||||
}
|
}
|
||||||
Fill::Solid(color) => {
|
Fill::Solid(color) => {
|
||||||
let input_connector = InputConnector::node(fill_node_id, backup_color_index);
|
let input_connector = InputConnector::node(fill_node_id, backup_color_index);
|
||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::OptionalColor(Some(*color)), false), true);
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(Table::new_from_element(*color)), false), true);
|
||||||
}
|
}
|
||||||
Fill::Gradient(gradient) => {
|
Fill::Gradient(gradient) => {
|
||||||
let input_connector = InputConnector::node(fill_node_id, backup_gradient_index);
|
let input_connector = InputConnector::node(fill_node_id, backup_gradient_index);
|
||||||
@@ -372,8 +372,10 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
pub fn stroke_set(&mut self, stroke: Stroke) {
|
pub fn stroke_set(&mut self, stroke: Stroke) {
|
||||||
let Some(stroke_node_id) = self.existing_node_id("Stroke", true) else { return };
|
let Some(stroke_node_id) = self.existing_node_id("Stroke", true) else { return };
|
||||||
|
|
||||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::ColorInput::<Option<graphene_std::Color>>::INDEX);
|
let stroke_color = if let Some(color) = stroke.color { Table::new_from_element(color) } else { Table::new() };
|
||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::OptionalColor(stroke.color), false), true);
|
|
||||||
|
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::ColorInput::INDEX);
|
||||||
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(stroke_color), false), true);
|
||||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::WeightInput::INDEX);
|
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::WeightInput::INDEX);
|
||||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.weight), false), true);
|
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.weight), false), true);
|
||||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::AlignInput::INDEX);
|
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::AlignInput::INDEX);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
mod document_message;
|
mod document_message;
|
||||||
mod document_message_handler;
|
mod document_message_handler;
|
||||||
|
|
||||||
|
pub mod data_panel;
|
||||||
pub mod graph_operation;
|
pub mod graph_operation;
|
||||||
pub mod navigation;
|
pub mod navigation;
|
||||||
pub mod node_graph;
|
pub mod node_graph;
|
||||||
|
|||||||
@@ -399,7 +399,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
NodeInput::value(TaggedValue::Graphic(Default::default()), true),
|
NodeInput::value(TaggedValue::Graphic(Default::default()), true),
|
||||||
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
||||||
NodeInput::value(TaggedValue::DVec2(DVec2::new(1920., 1080.)), false),
|
NodeInput::value(TaggedValue::DVec2(DVec2::new(1920., 1080.)), false),
|
||||||
NodeInput::value(TaggedValue::Color(Color::WHITE), false),
|
NodeInput::value(TaggedValue::Color(Table::new_from_element(Color::WHITE)), false),
|
||||||
NodeInput::value(TaggedValue::Bool(false), false),
|
NodeInput::value(TaggedValue::Bool(false), false),
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ pub struct NodeGraphMessageContext<'a> {
|
|||||||
pub graph_fade_artwork_percentage: f64,
|
pub graph_fade_artwork_percentage: f64,
|
||||||
pub navigation_handler: &'a NavigationMessageHandler,
|
pub navigation_handler: &'a NavigationMessageHandler,
|
||||||
pub preferences: &'a PreferencesMessageHandler,
|
pub preferences: &'a PreferencesMessageHandler,
|
||||||
|
pub layers_panel_open: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, ExtractField)]
|
#[derive(Debug, Clone, ExtractField)]
|
||||||
@@ -88,7 +89,7 @@ pub struct NodeGraphMessageHandler {
|
|||||||
reordering_import: Option<usize>,
|
reordering_import: Option<usize>,
|
||||||
/// The index of the export that is being moved
|
/// The index of the export that is being moved
|
||||||
reordering_export: Option<usize>,
|
reordering_export: Option<usize>,
|
||||||
/// The end index of the moved port
|
/// The end index of the moved connector
|
||||||
end_index: Option<usize>,
|
end_index: Option<usize>,
|
||||||
/// Used to keep track of what nodes are sent to the front end so that only visible ones are sent to the frontend
|
/// Used to keep track of what nodes are sent to the front end so that only visible ones are sent to the frontend
|
||||||
frontend_nodes: Vec<NodeId>,
|
frontend_nodes: Vec<NodeId>,
|
||||||
@@ -111,6 +112,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
graph_fade_artwork_percentage,
|
graph_fade_artwork_percentage,
|
||||||
navigation_handler,
|
navigation_handler,
|
||||||
preferences,
|
preferences,
|
||||||
|
layers_panel_open,
|
||||||
} = context;
|
} = context;
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
@@ -155,11 +157,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
}
|
}
|
||||||
responses.add(MenuBarMessage::SendLayout);
|
responses.add(MenuBarMessage::SendLayout);
|
||||||
responses.add(NodeGraphMessage::UpdateLayerPanel);
|
responses.add(NodeGraphMessage::UpdateLayerPanel);
|
||||||
|
responses.add(PropertiesPanelMessage::Refresh);
|
||||||
responses.add(NodeGraphMessage::SendSelectedNodes);
|
responses.add(NodeGraphMessage::SendSelectedNodes);
|
||||||
responses.add(ArtboardToolMessage::UpdateSelectedArtboard);
|
responses.add(ArtboardToolMessage::UpdateSelectedArtboard);
|
||||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
|
responses.add(PortfolioMessage::SubmitActiveGraphRender);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::CreateWire { output_connector, input_connector } => {
|
NodeGraphMessage::CreateWire { output_connector, input_connector } => {
|
||||||
// TODO: Add support for flattening NodeInput::Network exports in flatten_with_fns https://github.com/GraphiteEditor/Graphite/issues/1762
|
// TODO: Add support for flattening NodeInput::Network exports in flatten_with_fns https://github.com/GraphiteEditor/Graphite/issues/1762
|
||||||
@@ -1215,7 +1219,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
log::debug!("preferences.graph_wire_style: {:?}", preferences.graph_wire_style);
|
|
||||||
let (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?;
|
let (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?;
|
||||||
|
|
||||||
let node_bbox = kurbo::Rect::new(node_bbox[0].x, node_bbox[0].y, node_bbox[1].x, node_bbox[1].y).to_path(DEFAULT_ACCURACY);
|
let node_bbox = kurbo::Rect::new(node_bbox[0].x, node_bbox[0].y, node_bbox[1].x, node_bbox[1].y).to_path(DEFAULT_ACCURACY);
|
||||||
@@ -1485,7 +1489,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
};
|
};
|
||||||
selected_nodes.set_selected_nodes(nodes);
|
selected_nodes.set_selected_nodes(nodes);
|
||||||
responses.add(BroadcastEvent::SelectionChanged);
|
responses.add(BroadcastEvent::SelectionChanged);
|
||||||
responses.add(PropertiesPanelMessage::Refresh);
|
|
||||||
}
|
}
|
||||||
NodeGraphMessage::SendClickTargets => responses.add(FrontendMessage::UpdateClickTargets {
|
NodeGraphMessage::SendClickTargets => responses.add(FrontendMessage::UpdateClickTargets {
|
||||||
click_targets: Some(network_interface.collect_frontend_click_targets(breadcrumb_network_path)),
|
click_targets: Some(network_interface.collect_frontend_click_targets(breadcrumb_network_path)),
|
||||||
@@ -1677,6 +1680,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
responses.add(DocumentMessage::RenderRulers);
|
responses.add(DocumentMessage::RenderRulers);
|
||||||
responses.add(DocumentMessage::RenderScrollbars);
|
responses.add(DocumentMessage::RenderScrollbars);
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
|
responses.add(OverlaysMessage::Draw); // Redraw overlays to update artboard names
|
||||||
}
|
}
|
||||||
NodeGraphMessage::SetDisplayNameImpl { node_id, alias } => {
|
NodeGraphMessage::SetDisplayNameImpl { node_id, alias } => {
|
||||||
network_interface.set_display_name(&node_id, alias, selection_network_path);
|
network_interface.set_display_name(&node_id, alias, selection_network_path);
|
||||||
@@ -1833,7 +1837,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
let quad = Quad::from_box([box_selection_start, box_selection_end_graph]);
|
let quad = Quad::from_box([box_selection_start, box_selection_end_graph]);
|
||||||
if click_targets.node_click_target.intersect_path(|| quad.bezier_lines(), DAffine2::IDENTITY) {
|
if click_targets.node_click_target.intersect_path(|| quad.to_lines(), DAffine2::IDENTITY) {
|
||||||
nodes.insert(node_id);
|
nodes.insert(node_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1873,7 +1877,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
}
|
}
|
||||||
|
|
||||||
NodeGraphMessage::UpdateLayerPanel => {
|
NodeGraphMessage::UpdateLayerPanel => {
|
||||||
Self::update_layer_panel(network_interface, selection_network_path, collapsed, responses);
|
Self::update_layer_panel(network_interface, selection_network_path, collapsed, layers_panel_open, responses);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::UpdateEdges => {
|
NodeGraphMessage::UpdateEdges => {
|
||||||
// Update the import/export UI edges whenever the PTZ changes or the bounding box of all nodes changes
|
// Update the import/export UI edges whenever the PTZ changes or the bounding box of all nodes changes
|
||||||
@@ -2329,9 +2333,9 @@ impl NodeGraphMessageHandler {
|
|||||||
.icon(Some("Node".to_string()))
|
.icon(Some("Node".to_string()))
|
||||||
.tooltip("Add an operation to the end of this layer's chain of nodes")
|
.tooltip("Add an operation to the end of this layer's chain of nodes")
|
||||||
.popover_layout({
|
.popover_layout({
|
||||||
let layer_identifier = LayerNodeIdentifier::new(layer, &context.network_interface);
|
let layer_identifier = LayerNodeIdentifier::new(layer, context.network_interface);
|
||||||
let compatible_type = {
|
let compatible_type = {
|
||||||
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer_identifier, &context.network_interface);
|
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer_identifier, context.network_interface);
|
||||||
let node_type = graph_layer.horizontal_layer_flow().nth(1);
|
let node_type = graph_layer.horizontal_layer_flow().nth(1);
|
||||||
if let Some(node_id) = node_type {
|
if let Some(node_id) = node_type {
|
||||||
let (output_type, _) = context.network_interface.output_type(&node_id, 0, &[]);
|
let (output_type, _) = context.network_interface.output_type(&node_id, 0, &[]);
|
||||||
@@ -2413,7 +2417,7 @@ impl NodeGraphMessageHandler {
|
|||||||
} else {
|
} else {
|
||||||
added_wires.push(WirePathUpdate {
|
added_wires.push(WirePathUpdate {
|
||||||
id: NodeId(u64::MAX),
|
id: NodeId(u64::MAX),
|
||||||
input_index: usize::MAX,
|
input_index: u32::MAX as usize,
|
||||||
wire_path_update: None,
|
wire_path_update: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -2585,7 +2589,11 @@ impl NodeGraphMessageHandler {
|
|||||||
Some(subgraph_names)
|
Some(subgraph_names)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_layer_panel(network_interface: &NodeNetworkInterface, selection_network_path: &[NodeId], collapsed: &CollapsedLayers, responses: &mut VecDeque<Message>) {
|
fn update_layer_panel(network_interface: &NodeNetworkInterface, selection_network_path: &[NodeId], collapsed: &CollapsedLayers, layers_panel_open: bool, responses: &mut VecDeque<Message>) {
|
||||||
|
if !layers_panel_open {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let selected_layers = network_interface
|
let selected_layers = network_interface
|
||||||
.selected_nodes()
|
.selected_nodes()
|
||||||
.selected_layers(network_interface.document_metadata())
|
.selected_layers(network_interface.document_metadata())
|
||||||
@@ -2668,7 +2676,7 @@ impl NodeGraphMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_node_graph_hints(&self, responses: &mut VecDeque<Message>) {
|
fn update_node_graph_hints(&self, responses: &mut VecDeque<Message>) {
|
||||||
// A wire is in progress and its start and end connectors are set
|
// A wire is in progress and its start and end connectors are set
|
||||||
let wiring = self.wire_in_progress_from_connector.is_some();
|
let wiring = self.wire_in_progress_from_connector.is_some();
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use graphene_std::raster::{
|
|||||||
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
|
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
|
||||||
SelectiveColorChoice,
|
SelectiveColorChoice,
|
||||||
};
|
};
|
||||||
|
use graphene_std::table::{Table, TableRow};
|
||||||
use graphene_std::text::{Font, TextAlign};
|
use graphene_std::text::{Font, TextAlign};
|
||||||
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
|
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
|
||||||
use graphene_std::vector::misc::{ArcType, CentroidType, GridType, MergeByDistanceAlgorithm, PointSpacingType};
|
use graphene_std::vector::misc::{ArcType, CentroidType, GridType, MergeByDistanceAlgorithm, PointSpacingType};
|
||||||
@@ -180,8 +181,8 @@ pub(crate) fn property_from_type(
|
|||||||
// ============
|
// ============
|
||||||
// STRUCT TYPES
|
// STRUCT TYPES
|
||||||
// ============
|
// ============
|
||||||
Some(x) if x == TypeId::of::<Color>() => color_widget(default_info, ColorInput::default().allow_none(false)),
|
Some(x) if x == TypeId::of::<Table<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)),
|
||||||
Some(x) if x == TypeId::of::<Option<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)),
|
Some(x) if x == TypeId::of::<Table<GradientStops>>() => color_widget(default_info, ColorInput::default().allow_none(false)),
|
||||||
Some(x) if x == TypeId::of::<GradientStops>() => color_widget(default_info, ColorInput::default().allow_none(false)),
|
Some(x) if x == TypeId::of::<GradientStops>() => color_widget(default_info, ColorInput::default().allow_none(false)),
|
||||||
Some(x) if x == TypeId::of::<Font>() => font_widget(default_info),
|
Some(x) if x == TypeId::of::<Font>() => font_widget(default_info),
|
||||||
Some(x) if x == TypeId::of::<Curve>() => curve_widget(default_info),
|
Some(x) if x == TypeId::of::<Curve>() => curve_widget(default_info),
|
||||||
@@ -908,28 +909,39 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
|
|||||||
|
|
||||||
// Add the color input
|
// Add the color input
|
||||||
match &**tagged_value {
|
match &**tagged_value {
|
||||||
TaggedValue::Color(color) => widgets.push(
|
TaggedValue::Color(color_table) => widgets.push(
|
||||||
color_button
|
color_button
|
||||||
.value(FillChoice::Solid(*color))
|
.value(match color_table.iter().next() {
|
||||||
.on_update(update_value(|x: &ColorInput| TaggedValue::Color(x.value.as_solid().unwrap_or_default()), node_id, index))
|
Some(color) => FillChoice::Solid(*color.element),
|
||||||
.on_commit(commit_value)
|
|
||||||
.widget_holder(),
|
|
||||||
),
|
|
||||||
TaggedValue::OptionalColor(color) => widgets.push(
|
|
||||||
color_button
|
|
||||||
.value(match color {
|
|
||||||
Some(color) => FillChoice::Solid(*color),
|
|
||||||
None => FillChoice::None,
|
None => FillChoice::None,
|
||||||
})
|
})
|
||||||
.on_update(update_value(|x: &ColorInput| TaggedValue::OptionalColor(x.value.as_solid()), node_id, index))
|
.on_update(update_value(
|
||||||
|
|input: &ColorInput| TaggedValue::Color(input.value.as_solid().iter().map(|&color| TableRow::new_from_element(color)).collect()),
|
||||||
|
node_id,
|
||||||
|
index,
|
||||||
|
))
|
||||||
.on_commit(commit_value)
|
.on_commit(commit_value)
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
),
|
),
|
||||||
TaggedValue::GradientStops(x) => widgets.push(
|
TaggedValue::GradientTable(gradient_table) => widgets.push(
|
||||||
color_button
|
color_button
|
||||||
.value(FillChoice::Gradient(x.clone()))
|
.value(match gradient_table.iter().next() {
|
||||||
|
Some(row) => FillChoice::Gradient(row.element.clone()),
|
||||||
|
None => FillChoice::None,
|
||||||
|
})
|
||||||
.on_update(update_value(
|
.on_update(update_value(
|
||||||
|x: &ColorInput| TaggedValue::GradientStops(x.value.as_gradient().cloned().unwrap_or_default()),
|
|input: &ColorInput| TaggedValue::GradientTable(input.value.as_gradient().iter().map(|&gradient| TableRow::new_from_element(gradient.clone())).collect()),
|
||||||
|
node_id,
|
||||||
|
index,
|
||||||
|
))
|
||||||
|
.on_commit(commit_value)
|
||||||
|
.widget_holder(),
|
||||||
|
),
|
||||||
|
TaggedValue::GradientStops(gradient_stops) => widgets.push(
|
||||||
|
color_button
|
||||||
|
.value(FillChoice::Gradient(gradient_stops.clone()))
|
||||||
|
.on_update(update_value(
|
||||||
|
|input: &ColorInput| TaggedValue::GradientStops(input.value.as_gradient().cloned().unwrap_or_default()),
|
||||||
node_id,
|
node_id,
|
||||||
index,
|
index,
|
||||||
))
|
))
|
||||||
@@ -1559,7 +1571,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), &Some(&TaggedValue::OptionalColor(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = (
|
let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), Some(TaggedValue::Color(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = (
|
||||||
&document_node.inputs[FillInput::<Color>::INDEX].as_value(),
|
&document_node.inputs[FillInput::<Color>::INDEX].as_value(),
|
||||||
&document_node.inputs[BackupColorInput::INDEX].as_value(),
|
&document_node.inputs[BackupColorInput::INDEX].as_value(),
|
||||||
&document_node.inputs[BackupGradientInput::INDEX].as_value(),
|
&document_node.inputs[BackupGradientInput::INDEX].as_value(),
|
||||||
@@ -1569,7 +1581,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
return vec![LayoutGroup::Row { widgets: widgets_first_row }];
|
return vec![LayoutGroup::Row { widgets: widgets_first_row }];
|
||||||
};
|
};
|
||||||
let fill2 = fill.clone();
|
let fill2 = fill.clone();
|
||||||
let backup_color_fill: Fill = backup_color.into();
|
let backup_color_fill: Fill = backup_color.clone().into();
|
||||||
let backup_gradient_fill: Fill = backup_gradient.clone().into();
|
let backup_gradient_fill: Fill = backup_gradient.clone().into();
|
||||||
|
|
||||||
widgets_first_row.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
widgets_first_row.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
@@ -1582,13 +1594,13 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
Fill::None => NodeGraphMessage::SetInputValue {
|
Fill::None => NodeGraphMessage::SetInputValue {
|
||||||
node_id,
|
node_id,
|
||||||
input_index: BackupColorInput::INDEX,
|
input_index: BackupColorInput::INDEX,
|
||||||
value: TaggedValue::OptionalColor(None),
|
value: TaggedValue::Color(Table::new()),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
Fill::Solid(color) => NodeGraphMessage::SetInputValue {
|
Fill::Solid(color) => NodeGraphMessage::SetInputValue {
|
||||||
node_id,
|
node_id,
|
||||||
input_index: BackupColorInput::INDEX,
|
input_index: BackupColorInput::INDEX,
|
||||||
value: TaggedValue::OptionalColor(Some(*color)),
|
value: TaggedValue::Color(Table::new_from_element(*color)),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
Fill::Gradient(gradient) => NodeGraphMessage::SetInputValue {
|
Fill::Gradient(gradient) => NodeGraphMessage::SetInputValue {
|
||||||
@@ -1750,7 +1762,7 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
|
|||||||
let miter_limit_disabled = join_value != &StrokeJoin::Miter;
|
let miter_limit_disabled = join_value != &StrokeJoin::Miter;
|
||||||
|
|
||||||
let color = color_widget(
|
let color = color_widget(
|
||||||
ParameterWidgetsInfo::new(node_id, ColorInput::<Option<Color>>::INDEX, true, context),
|
ParameterWidgetsInfo::new(node_id, ColorInput::INDEX, true, context),
|
||||||
crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(),
|
crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(),
|
||||||
);
|
);
|
||||||
let weight = number_widget(ParameterWidgetsInfo::new(node_id, WeightInput::INDEX, true, context), NumberInput::default().unit(" px").min(0.));
|
let weight = number_widget(ParameterWidgetsInfo::new(node_id, WeightInput::INDEX, true, context), NumberInput::default().unit(" px").min(0.));
|
||||||
|
|||||||
@@ -8,18 +8,19 @@ use std::borrow::Cow;
|
|||||||
pub enum FrontendGraphDataType {
|
pub enum FrontendGraphDataType {
|
||||||
#[default]
|
#[default]
|
||||||
General,
|
General,
|
||||||
|
Number,
|
||||||
|
Artboard,
|
||||||
|
Graphic,
|
||||||
Raster,
|
Raster,
|
||||||
Vector,
|
Vector,
|
||||||
Number,
|
Color,
|
||||||
Graphic,
|
Gradient,
|
||||||
Artboard,
|
Typography,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FrontendGraphDataType {
|
impl FrontendGraphDataType {
|
||||||
pub fn from_type(input: &Type) -> Self {
|
pub fn from_type(input: &Type) -> Self {
|
||||||
match TaggedValue::from_type_or_none(input) {
|
match TaggedValue::from_type_or_none(input) {
|
||||||
TaggedValue::Raster(_) => Self::Raster,
|
|
||||||
TaggedValue::Vector(_) => Self::Vector,
|
|
||||||
TaggedValue::U32(_)
|
TaggedValue::U32(_)
|
||||||
| TaggedValue::U64(_)
|
| TaggedValue::U64(_)
|
||||||
| TaggedValue::F64(_)
|
| TaggedValue::F64(_)
|
||||||
@@ -28,8 +29,13 @@ impl FrontendGraphDataType {
|
|||||||
| TaggedValue::VecF64(_)
|
| TaggedValue::VecF64(_)
|
||||||
| TaggedValue::VecDVec2(_)
|
| TaggedValue::VecDVec2(_)
|
||||||
| TaggedValue::DAffine2(_) => Self::Number,
|
| TaggedValue::DAffine2(_) => Self::Number,
|
||||||
TaggedValue::Graphic(_) => Self::Graphic,
|
|
||||||
TaggedValue::Artboard(_) => Self::Artboard,
|
TaggedValue::Artboard(_) => Self::Artboard,
|
||||||
|
TaggedValue::Graphic(_) => Self::Graphic,
|
||||||
|
TaggedValue::Raster(_) => Self::Raster,
|
||||||
|
TaggedValue::Vector(_) => Self::Vector,
|
||||||
|
TaggedValue::Color(_) => Self::Color,
|
||||||
|
TaggedValue::Gradient(_) | TaggedValue::GradientStops(_) | TaggedValue::GradientTable(_) => Self::Gradient,
|
||||||
|
TaggedValue::String(_) => Self::Typography,
|
||||||
_ => Self::General,
|
_ => Self::General,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,8 +183,8 @@ pub struct FrontendClickTargets {
|
|||||||
pub node_click_targets: Vec<String>,
|
pub node_click_targets: Vec<String>,
|
||||||
#[serde(rename = "layerClickTargets")]
|
#[serde(rename = "layerClickTargets")]
|
||||||
pub layer_click_targets: Vec<String>,
|
pub layer_click_targets: Vec<String>,
|
||||||
#[serde(rename = "portClickTargets")]
|
#[serde(rename = "connectorClickTargets")]
|
||||||
pub port_click_targets: Vec<String>,
|
pub connector_click_targets: Vec<String>,
|
||||||
#[serde(rename = "iconClickTargets")]
|
#[serde(rename = "iconClickTargets")]
|
||||||
pub icon_click_targets: Vec<String>,
|
pub icon_click_targets: Vec<String>,
|
||||||
#[serde(rename = "allNodesBoundingBox")]
|
#[serde(rename = "allNodesBoundingBox")]
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use crate::consts::HIDE_HANDLE_DISTANCE;
|
|||||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||||
use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState};
|
use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState};
|
||||||
use crate::messages::tool::tool_messages::tool_prelude::{DocumentMessageHandler, PreferencesMessageHandler};
|
use crate::messages::tool::tool_messages::tool_prelude::{DocumentMessageHandler, PreferencesMessageHandler};
|
||||||
use bezier_rs::{Bezier, BezierHandles};
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
|
use graphene_std::subpath::{Bezier, BezierHandles};
|
||||||
use graphene_std::vector::misc::ManipulatorPointId;
|
use graphene_std::vector::misc::ManipulatorPointId;
|
||||||
use graphene_std::vector::{PointId, SegmentId};
|
use graphene_std::vector::{PointId, SegmentId};
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
@@ -125,7 +125,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the selected segments and then add a bold line overlay on them
|
// Get the selected segments and then add a bold line overlay on them
|
||||||
for (segment_id, bezier, _, _) in vector.segment_bezier_iter() {
|
for (segment_id, bezier, _, _) in vector.segment_iter() {
|
||||||
let Some(selected_shape_state) = shape_editor.selected_shape_state.get_mut(&layer) else {
|
let Some(selected_shape_state) = shape_editor.selected_shape_state.get_mut(&layer) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,14 +5,16 @@ use crate::consts::{
|
|||||||
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, SEGMENT_SELECTED_THICKNESS,
|
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, SEGMENT_SELECTED_THICKNESS,
|
||||||
};
|
};
|
||||||
use crate::messages::prelude::Message;
|
use crate::messages::prelude::Message;
|
||||||
use bezier_rs::{Bezier, Subpath};
|
|
||||||
use core::borrow::Borrow;
|
use core::borrow::Borrow;
|
||||||
use core::f64::consts::{FRAC_PI_2, PI, TAU};
|
use core::f64::consts::{FRAC_PI_2, PI, TAU};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphene_std::Color;
|
use graphene_std::Color;
|
||||||
use graphene_std::math::quad::Quad;
|
use graphene_std::math::quad::Quad;
|
||||||
|
use graphene_std::subpath::Subpath;
|
||||||
use graphene_std::vector::click_target::ClickTargetType;
|
use graphene_std::vector::click_target::ClickTargetType;
|
||||||
|
use graphene_std::vector::misc::{dvec2_to_point, point_to_dvec2};
|
||||||
use graphene_std::vector::{PointId, SegmentId, Vector};
|
use graphene_std::vector::{PointId, SegmentId, Vector};
|
||||||
|
use kurbo::{self, Affine, CubicBez, ParamCurve, PathSeg};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use wasm_bindgen::{JsCast, JsValue};
|
use wasm_bindgen::{JsCast, JsValue};
|
||||||
use web_sys::{OffscreenCanvas, OffscreenCanvasRenderingContext2d};
|
use web_sys::{OffscreenCanvas, OffscreenCanvasRenderingContext2d};
|
||||||
@@ -571,11 +573,7 @@ impl OverlayContext {
|
|||||||
let handle_start = start + start_vec.perp() * radius * factor;
|
let handle_start = start + start_vec.perp() * radius * factor;
|
||||||
let handle_end = end - end_vec.perp() * radius * factor;
|
let handle_end = end - end_vec.perp() * radius * factor;
|
||||||
|
|
||||||
let bezier = Bezier {
|
let bezier = PathSeg::Cubic(CubicBez::new(dvec2_to_point(start), dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(end)));
|
||||||
start,
|
|
||||||
end,
|
|
||||||
handles: bezier_rs::BezierHandles::Cubic { handle_start, handle_end },
|
|
||||||
};
|
|
||||||
|
|
||||||
self.bezier_command(bezier, DAffine2::IDENTITY, i == 0);
|
self.bezier_command(bezier, DAffine2::IDENTITY, i == 0);
|
||||||
}
|
}
|
||||||
@@ -762,7 +760,7 @@ impl OverlayContext {
|
|||||||
|
|
||||||
self.render_context.begin_path();
|
self.render_context.begin_path();
|
||||||
let mut last_point = None;
|
let mut last_point = None;
|
||||||
for (_, bezier, start_id, end_id) in vector.segment_bezier_iter() {
|
for (_, bezier, start_id, end_id) in vector.segment_iter() {
|
||||||
let move_to = last_point != Some(start_id);
|
let move_to = last_point != Some(start_id);
|
||||||
last_point = Some(end_id);
|
last_point = Some(end_id);
|
||||||
|
|
||||||
@@ -776,7 +774,7 @@ impl OverlayContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
||||||
pub fn outline_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
pub fn outline_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||||
self.start_dpi_aware_transform();
|
self.start_dpi_aware_transform();
|
||||||
|
|
||||||
self.render_context.begin_path();
|
self.render_context.begin_path();
|
||||||
@@ -788,7 +786,7 @@ impl OverlayContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Used by the path tool segment mode in order to show the selected segments.
|
/// Used by the path tool segment mode in order to show the selected segments.
|
||||||
pub fn outline_select_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
pub fn outline_select_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||||
self.start_dpi_aware_transform();
|
self.start_dpi_aware_transform();
|
||||||
|
|
||||||
self.render_context.begin_path();
|
self.render_context.begin_path();
|
||||||
@@ -802,7 +800,7 @@ impl OverlayContext {
|
|||||||
self.end_dpi_aware_transform();
|
self.end_dpi_aware_transform();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
pub fn outline_overlay_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||||
self.start_dpi_aware_transform();
|
self.start_dpi_aware_transform();
|
||||||
|
|
||||||
self.render_context.begin_path();
|
self.render_context.begin_path();
|
||||||
@@ -816,18 +814,18 @@ impl OverlayContext {
|
|||||||
self.end_dpi_aware_transform();
|
self.end_dpi_aware_transform();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bezier_command(&self, bezier: Bezier, transform: DAffine2, move_to: bool) {
|
fn bezier_command(&self, bezier: PathSeg, transform: DAffine2, move_to: bool) {
|
||||||
self.start_dpi_aware_transform();
|
self.start_dpi_aware_transform();
|
||||||
|
|
||||||
let Bezier { start, end, handles } = bezier.apply_transformation(|point| transform.transform_point2(point));
|
let bezier = Affine::new(transform.to_cols_array()) * bezier;
|
||||||
if move_to {
|
if move_to {
|
||||||
self.render_context.move_to(start.x, start.y);
|
self.render_context.move_to(bezier.start().x, bezier.start().y);
|
||||||
}
|
}
|
||||||
|
match bezier.as_path_el() {
|
||||||
match handles {
|
kurbo::PathEl::LineTo(point) => self.render_context.line_to(point.x, point.y),
|
||||||
bezier_rs::BezierHandles::Linear => self.render_context.line_to(end.x, end.y),
|
kurbo::PathEl::QuadTo(point, point1) => self.render_context.quadratic_curve_to(point.x, point.y, point1.x, point1.y),
|
||||||
bezier_rs::BezierHandles::Quadratic { handle } => self.render_context.quadratic_curve_to(handle.x, handle.y, end.x, end.y),
|
kurbo::PathEl::CurveTo(point, point1, point2) => self.render_context.bezier_curve_to(point.x, point.y, point1.x, point1.y, point2.x, point2.y),
|
||||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => self.render_context.bezier_curve_to(handle_start.x, handle_start.y, handle_end.x, handle_end.y, end.x, end.y),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.end_dpi_aware_transform();
|
self.end_dpi_aware_transform();
|
||||||
@@ -841,36 +839,35 @@ impl OverlayContext {
|
|||||||
let subpath = subpath.borrow();
|
let subpath = subpath.borrow();
|
||||||
let mut curves = subpath.iter().peekable();
|
let mut curves = subpath.iter().peekable();
|
||||||
|
|
||||||
let Some(first) = curves.peek() else {
|
let Some(&first) = curves.peek() else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
self.render_context.move_to(transform.transform_point2(first.start()).x, transform.transform_point2(first.start()).y);
|
let start_point = transform.transform_point2(point_to_dvec2(first.start()));
|
||||||
for curve in curves {
|
self.render_context.move_to(start_point.x, start_point.y);
|
||||||
match curve.handles {
|
|
||||||
bezier_rs::BezierHandles::Linear => {
|
|
||||||
let a = transform.transform_point2(curve.end());
|
|
||||||
let a = a.round() - DVec2::splat(0.5);
|
|
||||||
|
|
||||||
self.render_context.line_to(a.x, a.y)
|
for curve in curves {
|
||||||
|
match curve {
|
||||||
|
PathSeg::Line(line) => {
|
||||||
|
let a = transform.transform_point2(point_to_dvec2(line.p1));
|
||||||
|
let a = a.round() - DVec2::splat(0.5);
|
||||||
|
self.render_context.line_to(a.x, a.y);
|
||||||
}
|
}
|
||||||
bezier_rs::BezierHandles::Quadratic { handle } => {
|
PathSeg::Quad(quad_bez) => {
|
||||||
let a = transform.transform_point2(handle);
|
let a = transform.transform_point2(point_to_dvec2(quad_bez.p1));
|
||||||
let b = transform.transform_point2(curve.end());
|
let b = transform.transform_point2(point_to_dvec2(quad_bez.p2));
|
||||||
let a = a.round() - DVec2::splat(0.5);
|
let a = a.round() - DVec2::splat(0.5);
|
||||||
let b = b.round() - DVec2::splat(0.5);
|
let b = b.round() - DVec2::splat(0.5);
|
||||||
|
self.render_context.quadratic_curve_to(a.x, a.y, b.x, b.y);
|
||||||
self.render_context.quadratic_curve_to(a.x, a.y, b.x, b.y)
|
|
||||||
}
|
}
|
||||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
PathSeg::Cubic(cubic_bez) => {
|
||||||
let a = transform.transform_point2(handle_start);
|
let a = transform.transform_point2(point_to_dvec2(cubic_bez.p1));
|
||||||
let b = transform.transform_point2(handle_end);
|
let b = transform.transform_point2(point_to_dvec2(cubic_bez.p2));
|
||||||
let c = transform.transform_point2(curve.end());
|
let c = transform.transform_point2(point_to_dvec2(cubic_bez.p3));
|
||||||
let a = a.round() - DVec2::splat(0.5);
|
let a = a.round() - DVec2::splat(0.5);
|
||||||
let b = b.round() - DVec2::splat(0.5);
|
let b = b.round() - DVec2::splat(0.5);
|
||||||
let c = c.round() - DVec2::splat(0.5);
|
let c = c.round() - DVec2::splat(0.5);
|
||||||
|
self.render_context.bezier_curve_to(a.x, a.y, b.x, b.y, c.x, c.y);
|
||||||
self.render_context.bezier_curve_to(a.x, a.y, b.x, b.y, c.x, c.y)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -885,7 +882,7 @@ impl OverlayContext {
|
|||||||
|
|
||||||
/// Used by the Select tool to outline a path or a free point when selected or hovered.
|
/// Used by the Select tool to outline a path or a free point when selected or hovered.
|
||||||
pub fn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {
|
pub fn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {
|
||||||
let mut subpaths: Vec<bezier_rs::Subpath<PointId>> = vec![];
|
let mut subpaths: Vec<Subpath<PointId>> = vec![];
|
||||||
|
|
||||||
target_types.for_each(|target_type| match target_type.borrow() {
|
target_types.for_each(|target_type| match target_type.borrow() {
|
||||||
ClickTargetType::FreePoint(point) => {
|
ClickTargetType::FreePoint(point) => {
|
||||||
|
|||||||
@@ -4,20 +4,22 @@ use crate::consts::{
|
|||||||
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
|
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
|
||||||
};
|
};
|
||||||
use crate::messages::prelude::Message;
|
use crate::messages::prelude::Message;
|
||||||
use bezier_rs::{Bezier, Subpath};
|
|
||||||
use core::borrow::Borrow;
|
use core::borrow::Borrow;
|
||||||
use core::f64::consts::{FRAC_PI_2, PI, TAU};
|
use core::f64::consts::{FRAC_PI_2, PI, TAU};
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphene_std::Color;
|
use graphene_std::Color;
|
||||||
use graphene_std::math::quad::Quad;
|
use graphene_std::math::quad::Quad;
|
||||||
|
use graphene_std::subpath::{self, Subpath};
|
||||||
use graphene_std::table::Table;
|
use graphene_std::table::Table;
|
||||||
use graphene_std::text::{TextAlign, TypesettingConfig, load_font, to_path};
|
use graphene_std::text::{TextAlign, TypesettingConfig, load_font, to_path};
|
||||||
use graphene_std::vector::click_target::ClickTargetType;
|
use graphene_std::vector::click_target::ClickTargetType;
|
||||||
|
use graphene_std::vector::misc::point_to_dvec2;
|
||||||
use graphene_std::vector::{PointId, SegmentId, Vector};
|
use graphene_std::vector::{PointId, SegmentId, Vector};
|
||||||
|
use kurbo::{self, BezPath, ParamCurve};
|
||||||
|
use kurbo::{Affine, PathSeg};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex, MutexGuard};
|
use std::sync::{Arc, Mutex, MutexGuard};
|
||||||
use vello::Scene;
|
use vello::Scene;
|
||||||
use vello::kurbo::{self, BezPath};
|
|
||||||
use vello::peniko;
|
use vello::peniko;
|
||||||
|
|
||||||
pub type OverlayProvider = fn(OverlayContext) -> Message;
|
pub type OverlayProvider = fn(OverlayContext) -> Message;
|
||||||
@@ -200,6 +202,7 @@ impl core::hash::Hash for OverlayContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl OverlayContext {
|
impl OverlayContext {
|
||||||
|
#[allow(dead_code)]
|
||||||
pub(super) fn new(size: DVec2, device_pixel_ratio: f64, visibility_settings: OverlaysVisibilitySettings) -> Self {
|
pub(super) fn new(size: DVec2, device_pixel_ratio: f64, visibility_settings: OverlaysVisibilitySettings) -> Self {
|
||||||
Self {
|
Self {
|
||||||
internal: Arc::new(Mutex::new(OverlayContextInternal::new(size, device_pixel_ratio, visibility_settings))),
|
internal: Arc::new(Mutex::new(OverlayContextInternal::new(size, device_pixel_ratio, visibility_settings))),
|
||||||
@@ -345,16 +348,16 @@ impl OverlayContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
||||||
pub fn outline_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
pub fn outline_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||||
self.internal().outline_bezier(bezier, transform);
|
self.internal().outline_bezier(bezier, transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Used by the path tool segment mode in order to show the selected segments.
|
/// Used by the path tool segment mode in order to show the selected segments.
|
||||||
pub fn outline_select_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
pub fn outline_select_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||||
self.internal().outline_select_bezier(bezier, transform);
|
self.internal().outline_select_bezier(bezier, transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
pub fn outline_overlay_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||||
self.internal().outline_overlay_bezier(bezier, transform);
|
self.internal().outline_overlay_bezier(bezier, transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -842,7 +845,7 @@ impl OverlayContextInternal {
|
|||||||
let mut path = BezPath::new();
|
let mut path = BezPath::new();
|
||||||
|
|
||||||
let mut last_point = None;
|
let mut last_point = None;
|
||||||
for (_, bezier, start_id, end_id) in vector.segment_bezier_iter() {
|
for (_, bezier, start_id, end_id) in vector.segment_iter() {
|
||||||
let move_to = last_point != Some(start_id);
|
let move_to = last_point != Some(start_id);
|
||||||
last_point = Some(end_id);
|
last_point = Some(end_id);
|
||||||
|
|
||||||
@@ -853,7 +856,7 @@ impl OverlayContextInternal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
||||||
fn outline_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
fn outline_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||||
let vello_transform = self.get_transform();
|
let vello_transform = self.get_transform();
|
||||||
let mut path = BezPath::new();
|
let mut path = BezPath::new();
|
||||||
self.bezier_to_path(bezier, transform, true, &mut path);
|
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||||
@@ -862,7 +865,7 @@ impl OverlayContextInternal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Used by the path tool segment mode in order to show the selected segments.
|
/// Used by the path tool segment mode in order to show the selected segments.
|
||||||
fn outline_select_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
fn outline_select_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||||
let vello_transform = self.get_transform();
|
let vello_transform = self.get_transform();
|
||||||
let mut path = BezPath::new();
|
let mut path = BezPath::new();
|
||||||
self.bezier_to_path(bezier, transform, true, &mut path);
|
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||||
@@ -870,7 +873,7 @@ impl OverlayContextInternal {
|
|||||||
self.scene.stroke(&kurbo::Stroke::new(4.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
self.scene.stroke(&kurbo::Stroke::new(4.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
fn outline_overlay_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||||
let vello_transform = self.get_transform();
|
let vello_transform = self.get_transform();
|
||||||
let mut path = BezPath::new();
|
let mut path = BezPath::new();
|
||||||
self.bezier_to_path(bezier, transform, true, &mut path);
|
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||||
@@ -878,21 +881,12 @@ impl OverlayContextInternal {
|
|||||||
self.scene.stroke(&kurbo::Stroke::new(4.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &path);
|
self.scene.stroke(&kurbo::Stroke::new(4.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &path);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bezier_to_path(&self, bezier: Bezier, transform: DAffine2, move_to: bool, path: &mut BezPath) {
|
fn bezier_to_path(&self, bezier: PathSeg, transform: DAffine2, move_to: bool, path: &mut BezPath) {
|
||||||
let Bezier { start, end, handles } = bezier.apply_transformation(|point| transform.transform_point2(point));
|
let bezier = Affine::new(transform.to_cols_array()) * bezier;
|
||||||
if move_to {
|
if move_to {
|
||||||
path.move_to(kurbo::Point::new(start.x, start.y));
|
path.move_to(bezier.start());
|
||||||
}
|
|
||||||
|
|
||||||
match handles {
|
|
||||||
bezier_rs::BezierHandles::Linear => path.line_to(kurbo::Point::new(end.x, end.y)),
|
|
||||||
bezier_rs::BezierHandles::Quadratic { handle } => path.quad_to(kurbo::Point::new(handle.x, handle.y), kurbo::Point::new(end.x, end.y)),
|
|
||||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => path.curve_to(
|
|
||||||
kurbo::Point::new(handle_start.x, handle_start.y),
|
|
||||||
kurbo::Point::new(handle_end.x, handle_end.y),
|
|
||||||
kurbo::Point::new(end.x, end.y),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
path.push(bezier.as_path_el());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2) -> BezPath {
|
fn push_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2) -> BezPath {
|
||||||
@@ -906,27 +900,27 @@ impl OverlayContextInternal {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let start_point = transform.transform_point2(first.start());
|
let start_point = transform.transform_point2(point_to_dvec2(first.start()));
|
||||||
path.move_to(kurbo::Point::new(start_point.x, start_point.y));
|
path.move_to(kurbo::Point::new(start_point.x, start_point.y));
|
||||||
|
|
||||||
for curve in curves {
|
for curve in curves {
|
||||||
match curve.handles {
|
match curve {
|
||||||
bezier_rs::BezierHandles::Linear => {
|
PathSeg::Line(line) => {
|
||||||
let a = transform.transform_point2(curve.end());
|
let a = transform.transform_point2(point_to_dvec2(line.p1));
|
||||||
let a = a.round() - DVec2::splat(0.5);
|
let a = a.round() - DVec2::splat(0.5);
|
||||||
path.line_to(kurbo::Point::new(a.x, a.y));
|
path.line_to(kurbo::Point::new(a.x, a.y));
|
||||||
}
|
}
|
||||||
bezier_rs::BezierHandles::Quadratic { handle } => {
|
PathSeg::Quad(quad_bez) => {
|
||||||
let a = transform.transform_point2(handle);
|
let a = transform.transform_point2(point_to_dvec2(quad_bez.p1));
|
||||||
let b = transform.transform_point2(curve.end());
|
let b = transform.transform_point2(point_to_dvec2(quad_bez.p2));
|
||||||
let a = a.round() - DVec2::splat(0.5);
|
let a = a.round() - DVec2::splat(0.5);
|
||||||
let b = b.round() - DVec2::splat(0.5);
|
let b = b.round() - DVec2::splat(0.5);
|
||||||
path.quad_to(kurbo::Point::new(a.x, a.y), kurbo::Point::new(b.x, b.y));
|
path.quad_to(kurbo::Point::new(a.x, a.y), kurbo::Point::new(b.x, b.y));
|
||||||
}
|
}
|
||||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
PathSeg::Cubic(cubic_bez) => {
|
||||||
let a = transform.transform_point2(handle_start);
|
let a = transform.transform_point2(point_to_dvec2(cubic_bez.p1));
|
||||||
let b = transform.transform_point2(handle_end);
|
let b = transform.transform_point2(point_to_dvec2(cubic_bez.p2));
|
||||||
let c = transform.transform_point2(curve.end());
|
let c = transform.transform_point2(point_to_dvec2(cubic_bez.p3));
|
||||||
let a = a.round() - DVec2::splat(0.5);
|
let a = a.round() - DVec2::splat(0.5);
|
||||||
let b = b.round() - DVec2::splat(0.5);
|
let b = b.round() - DVec2::splat(0.5);
|
||||||
let c = c.round() - DVec2::splat(0.5);
|
let c = c.round() - DVec2::splat(0.5);
|
||||||
@@ -945,7 +939,7 @@ impl OverlayContextInternal {
|
|||||||
|
|
||||||
/// Used by the Select tool to outline a path or a free point when selected or hovered.
|
/// Used by the Select tool to outline a path or a free point when selected or hovered.
|
||||||
fn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {
|
fn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {
|
||||||
let mut subpaths: Vec<bezier_rs::Subpath<PointId>> = vec![];
|
let mut subpaths: Vec<subpath::Subpath<PointId>> = vec![];
|
||||||
|
|
||||||
for target_type in target_types {
|
for target_type in target_types {
|
||||||
match target_type.borrow() {
|
match target_type.borrow() {
|
||||||
@@ -1118,13 +1112,13 @@ impl OverlayContextInternal {
|
|||||||
|
|
||||||
// Add handle points if they exist
|
// Add handle points if they exist
|
||||||
match transformed_bezier.handles {
|
match transformed_bezier.handles {
|
||||||
bezier_rs::BezierHandles::Quadratic { handle } => {
|
subpath::BezierHandles::Quadratic { handle } => {
|
||||||
min_x = min_x.min(handle.x);
|
min_x = min_x.min(handle.x);
|
||||||
min_y = min_y.min(handle.y);
|
min_y = min_y.min(handle.y);
|
||||||
max_x = max_x.max(handle.x);
|
max_x = max_x.max(handle.x);
|
||||||
max_y = max_y.max(handle.y);
|
max_y = max_y.max(handle.y);
|
||||||
}
|
}
|
||||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
subpath::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||||
for handle in [handle_start, handle_end] {
|
for handle in [handle_start, handle_end] {
|
||||||
min_x = min_x.min(handle.x);
|
min_x = min_x.min(handle.x);
|
||||||
min_y = min_y.min(handle.y);
|
min_y = min_y.min(handle.y);
|
||||||
@@ -1154,7 +1148,7 @@ impl OverlayContextInternal {
|
|||||||
let mut path = BezPath::new();
|
let mut path = BezPath::new();
|
||||||
let mut last_point = None;
|
let mut last_point = None;
|
||||||
|
|
||||||
for (_, bezier, start_id, end_id) in row.element.segment_bezier_iter() {
|
for (_, bezier, start_id, end_id) in row.element.segment_iter() {
|
||||||
let move_to = last_point != Some(start_id);
|
let move_to = last_point != Some(start_id);
|
||||||
last_point = Some(end_id);
|
last_point = Some(end_id);
|
||||||
|
|
||||||
|
|||||||
+9
-2
@@ -14,6 +14,7 @@ pub struct PropertiesPanelMessageContext<'a> {
|
|||||||
pub document_name: &'a str,
|
pub document_name: &'a str,
|
||||||
pub executor: &'a mut NodeGraphExecutor,
|
pub executor: &'a mut NodeGraphExecutor,
|
||||||
pub persistent_data: &'a PersistentData,
|
pub persistent_data: &'a PersistentData,
|
||||||
|
pub properties_panel_open: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, ExtractField)]
|
#[derive(Debug, Clone, Default, ExtractField)]
|
||||||
@@ -28,16 +29,22 @@ impl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> f
|
|||||||
document_name,
|
document_name,
|
||||||
executor,
|
executor,
|
||||||
persistent_data,
|
persistent_data,
|
||||||
|
properties_panel_open,
|
||||||
} = context;
|
} = context;
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
PropertiesPanelMessage::Clear => {
|
PropertiesPanelMessage::Clear => {
|
||||||
responses.add(LayoutMessage::SendLayout {
|
responses.add(LayoutMessage::SendLayout {
|
||||||
layout: Layout::WidgetLayout(WidgetLayout::new(vec![])),
|
layout: Layout::WidgetLayout(WidgetLayout::new(vec![])),
|
||||||
layout_target: LayoutTarget::PropertiesSections,
|
layout_target: LayoutTarget::PropertiesPanel,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
PropertiesPanelMessage::Refresh => {
|
PropertiesPanelMessage::Refresh => {
|
||||||
|
if !properties_panel_open {
|
||||||
|
responses.add(PropertiesPanelMessage::Clear);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let mut node_properties_context = NodePropertiesContext {
|
let mut node_properties_context = NodePropertiesContext {
|
||||||
persistent_data,
|
persistent_data,
|
||||||
responses,
|
responses,
|
||||||
@@ -50,7 +57,7 @@ impl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> f
|
|||||||
|
|
||||||
node_properties_context.responses.add(LayoutMessage::SendLayout {
|
node_properties_context.responses.add(LayoutMessage::SendLayout {
|
||||||
layout: Layout::WidgetLayout(WidgetLayout::new(properties_sections)),
|
layout: Layout::WidgetLayout(WidgetLayout::new(properties_sections)),
|
||||||
layout_target: LayoutTarget::PropertiesSections,
|
layout_target: LayoutTarget::PropertiesPanel,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::messages::tool::common_functionality::graph_modification_utils;
|
|||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_std::math::quad::Quad;
|
use graphene_std::math::quad::Quad;
|
||||||
|
use graphene_std::subpath;
|
||||||
use graphene_std::transform::Footprint;
|
use graphene_std::transform::Footprint;
|
||||||
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||||
use graphene_std::vector::{PointId, Vector};
|
use graphene_std::vector::{PointId, Vector};
|
||||||
@@ -196,7 +197,7 @@ impl DocumentMetadata {
|
|||||||
self.all_layers().filter_map(|layer| self.bounding_box_viewport(layer)).reduce(Quad::combine_bounds)
|
self.all_layers().filter_map(|layer| self.bounding_box_viewport(layer)).reduce(Quad::combine_bounds)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn layer_outline(&self, layer: LayerNodeIdentifier) -> impl Iterator<Item = &bezier_rs::Subpath<PointId>> {
|
pub fn layer_outline(&self, layer: LayerNodeIdentifier) -> impl Iterator<Item = &subpath::Subpath<PointId>> {
|
||||||
static EMPTY: Vec<ClickTarget> = Vec::new();
|
static EMPTY: Vec<ClickTarget> = Vec::new();
|
||||||
let click_targets = self.click_targets.get(&layer).unwrap_or(&EMPTY);
|
let click_targets = self.click_targets.get(&layer).unwrap_or(&EMPTY);
|
||||||
click_targets.iter().filter_map(|target| match target.target_type() {
|
click_targets.iter().filter_map(|target| match target.target_type() {
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ use crate::messages::portfolio::document::node_graph::utility_types::{Direction,
|
|||||||
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire};
|
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire};
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
use crate::messages::tool::tool_messages::tool_prelude::NumberInputMode;
|
use crate::messages::tool::tool_messages::tool_prelude::NumberInputMode;
|
||||||
use bezier_rs::Subpath;
|
|
||||||
use glam::{DAffine2, DVec2, IVec2};
|
use glam::{DAffine2, DVec2, IVec2};
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork};
|
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork};
|
||||||
use graph_craft::{Type, concrete};
|
use graph_craft::{Type, concrete};
|
||||||
use graphene_std::Artboard;
|
use graphene_std::Artboard;
|
||||||
use graphene_std::math::quad::Quad;
|
use graphene_std::math::quad::Quad;
|
||||||
|
use graphene_std::subpath::Subpath;
|
||||||
use graphene_std::table::Table;
|
use graphene_std::table::Table;
|
||||||
use graphene_std::transform::Footprint;
|
use graphene_std::transform::Footprint;
|
||||||
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||||
@@ -1287,7 +1287,7 @@ impl NodeNetworkInterface {
|
|||||||
let artboard = self.document_node(&artboard_node_identifier.to_node(), &[]);
|
let artboard = self.document_node(&artboard_node_identifier.to_node(), &[]);
|
||||||
let clip_input = artboard.unwrap().inputs.get(5).unwrap();
|
let clip_input = artboard.unwrap().inputs.get(5).unwrap();
|
||||||
if let NodeInput::Value { tagged_value, .. } = clip_input {
|
if let NodeInput::Value { tagged_value, .. } = clip_input {
|
||||||
if tagged_value.to_primitive_string() == "true" {
|
if tagged_value.clone().into_inner() == TaggedValue::Bool(true) {
|
||||||
return Some(Quad::clip(
|
return Some(Quad::clip(
|
||||||
self.document_metadata.bounding_box_document(layer).unwrap_or_default(),
|
self.document_metadata.bounding_box_document(layer).unwrap_or_default(),
|
||||||
self.document_metadata.bounding_box_document(artboard_node_identifier).unwrap_or_default(),
|
self.document_metadata.bounding_box_document(artboard_node_identifier).unwrap_or_default(),
|
||||||
@@ -2635,7 +2635,7 @@ impl NodeNetworkInterface {
|
|||||||
InputConnector::Node { node_id, input_index } => (*node_id, *input_index),
|
InputConnector::Node { node_id, input_index } => (*node_id, *input_index),
|
||||||
InputConnector::Export(export_index) => (NodeId(u64::MAX), *export_index),
|
InputConnector::Export(export_index) => (NodeId(u64::MAX), *export_index),
|
||||||
})
|
})
|
||||||
.chain(std::iter::once((NodeId(u64::MAX), usize::MAX)))
|
.chain(std::iter::once((NodeId(u64::MAX), u32::MAX as usize)))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2725,7 +2725,7 @@ impl NodeNetworkInterface {
|
|||||||
|
|
||||||
Some(WirePathUpdate {
|
Some(WirePathUpdate {
|
||||||
id: NodeId(u64::MAX),
|
id: NodeId(u64::MAX),
|
||||||
input_index: usize::MAX,
|
input_index: u32::MAX as usize,
|
||||||
wire_path_update,
|
wire_path_update,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -2832,7 +2832,7 @@ impl NodeNetworkInterface {
|
|||||||
let node_click_target_bottom_right = node_click_target_top_left + DVec2::new(width as f64, height as f64);
|
let node_click_target_bottom_right = node_click_target_top_left + DVec2::new(width as f64, height as f64);
|
||||||
|
|
||||||
let radius = 3.;
|
let radius = 3.;
|
||||||
let subpath = bezier_rs::Subpath::new_rounded_rect(node_click_target_top_left, node_click_target_bottom_right, [radius; 4]);
|
let subpath = Subpath::new_rounded_rect(node_click_target_top_left, node_click_target_bottom_right, [radius; 4]);
|
||||||
let node_click_target = ClickTarget::new_with_subpath(subpath, 0.);
|
let node_click_target = ClickTarget::new_with_subpath(subpath, 0.);
|
||||||
|
|
||||||
DocumentNodeClickTargets {
|
DocumentNodeClickTargets {
|
||||||
@@ -2871,7 +2871,7 @@ impl NodeNetworkInterface {
|
|||||||
let node_bottom_right = node_top_left + DVec2::new(width as f64, height as f64);
|
let node_bottom_right = node_top_left + DVec2::new(width as f64, height as f64);
|
||||||
let chain_top_left = node_top_left - DVec2::new((chain_width_grid_spaces * crate::consts::GRID_SIZE) as f64, 0.);
|
let chain_top_left = node_top_left - DVec2::new((chain_width_grid_spaces * crate::consts::GRID_SIZE) as f64, 0.);
|
||||||
let radius = 10.;
|
let radius = 10.;
|
||||||
let subpath = bezier_rs::Subpath::new_rounded_rect(chain_top_left, node_bottom_right, [radius; 4]);
|
let subpath = Subpath::new_rounded_rect(chain_top_left, node_bottom_right, [radius; 4]);
|
||||||
let node_click_target = ClickTarget::new_with_subpath(subpath, 0.);
|
let node_click_target = ClickTarget::new_with_subpath(subpath, 0.);
|
||||||
|
|
||||||
DocumentNodeClickTargets {
|
DocumentNodeClickTargets {
|
||||||
@@ -3048,7 +3048,7 @@ impl NodeNetworkInterface {
|
|||||||
|
|
||||||
pub fn collect_frontend_click_targets(&mut self, network_path: &[NodeId]) -> FrontendClickTargets {
|
pub fn collect_frontend_click_targets(&mut self, network_path: &[NodeId]) -> FrontendClickTargets {
|
||||||
let mut all_node_click_targets = Vec::new();
|
let mut all_node_click_targets = Vec::new();
|
||||||
let mut port_click_targets = Vec::new();
|
let mut connector_click_targets = Vec::new();
|
||||||
let mut icon_click_targets = Vec::new();
|
let mut icon_click_targets = Vec::new();
|
||||||
let Some(network_metadata) = self.network_metadata(network_path) else {
|
let Some(network_metadata) = self.network_metadata(network_path) else {
|
||||||
log::error!("Could not get nested network_metadata in collect_frontend_click_targets");
|
log::error!("Could not get nested network_metadata in collect_frontend_click_targets");
|
||||||
@@ -3059,27 +3059,21 @@ impl NodeNetworkInterface {
|
|||||||
let mut node_path = String::new();
|
let mut node_path = String::new();
|
||||||
|
|
||||||
if let ClickTargetType::Subpath(subpath) = node_click_targets.node_click_target.target_type() {
|
if let ClickTargetType::Subpath(subpath) = node_click_targets.node_click_target.target_type() {
|
||||||
let _ = subpath.subpath_to_svg(&mut node_path, DAffine2::IDENTITY);
|
node_path.push_str(subpath.to_bezpath().to_svg().as_str())
|
||||||
}
|
}
|
||||||
all_node_click_targets.push((node_id, node_path));
|
all_node_click_targets.push((node_id, node_path));
|
||||||
for port in node_click_targets.port_click_targets.click_targets().chain(import_export_click_targets.click_targets()) {
|
for port in node_click_targets.port_click_targets.click_targets().chain(import_export_click_targets.click_targets()) {
|
||||||
if let ClickTargetType::Subpath(subpath) = port.target_type() {
|
if let ClickTargetType::Subpath(subpath) = port.target_type() {
|
||||||
let mut port_path = String::new();
|
connector_click_targets.push(subpath.to_bezpath().to_svg());
|
||||||
let _ = subpath.subpath_to_svg(&mut port_path, DAffine2::IDENTITY);
|
|
||||||
port_click_targets.push(port_path);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let NodeTypeClickTargets::Layer(layer_metadata) = &node_click_targets.node_type_metadata {
|
if let NodeTypeClickTargets::Layer(layer_metadata) = &node_click_targets.node_type_metadata {
|
||||||
if let ClickTargetType::Subpath(subpath) = layer_metadata.visibility_click_target.target_type() {
|
if let ClickTargetType::Subpath(subpath) = layer_metadata.visibility_click_target.target_type() {
|
||||||
let mut port_path = String::new();
|
icon_click_targets.push(subpath.to_bezpath().to_svg());
|
||||||
let _ = subpath.subpath_to_svg(&mut port_path, DAffine2::IDENTITY);
|
|
||||||
icon_click_targets.push(port_path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let ClickTargetType::Subpath(subpath) = layer_metadata.grip_click_target.target_type() {
|
if let ClickTargetType::Subpath(subpath) = layer_metadata.grip_click_target.target_type() {
|
||||||
let mut port_path = String::new();
|
icon_click_targets.push(subpath.to_bezpath().to_svg());
|
||||||
let _ = subpath.subpath_to_svg(&mut port_path, DAffine2::IDENTITY);
|
|
||||||
icon_click_targets.push(port_path);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3095,9 +3089,8 @@ impl NodeNetworkInterface {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let bounds = self.all_nodes_bounding_box(network_path).cloned().unwrap_or([DVec2::ZERO, DVec2::ZERO]);
|
let bounds = self.all_nodes_bounding_box(network_path).cloned().unwrap_or([DVec2::ZERO, DVec2::ZERO]);
|
||||||
let rect = bezier_rs::Subpath::<PointId>::new_rect(bounds[0], bounds[1]);
|
let rect = Subpath::<PointId>::new_rect(bounds[0], bounds[1]);
|
||||||
let mut all_nodes_bounding_box = String::new();
|
let all_nodes_bounding_box = rect.to_bezpath().to_svg();
|
||||||
let _ = rect.subpath_to_svg(&mut all_nodes_bounding_box, DAffine2::IDENTITY);
|
|
||||||
|
|
||||||
let Some(rounded_network_edge_distance) = self.rounded_network_edge_distance(network_path).cloned() else {
|
let Some(rounded_network_edge_distance) = self.rounded_network_edge_distance(network_path).cloned() else {
|
||||||
log::error!("Could not get rounded_network_edge_distance in collect_frontend_click_targets");
|
log::error!("Could not get rounded_network_edge_distance in collect_frontend_click_targets");
|
||||||
@@ -3123,9 +3116,8 @@ impl NodeNetworkInterface {
|
|||||||
.inverse()
|
.inverse()
|
||||||
.transform_point2(import_exports_viewport_bottom_right);
|
.transform_point2(import_exports_viewport_bottom_right);
|
||||||
|
|
||||||
let import_exports_target = bezier_rs::Subpath::<PointId>::new_rect(node_graph_top_left, node_graph_bottom_right);
|
let import_exports_target = Subpath::<PointId>::new_rect(node_graph_top_left, node_graph_bottom_right);
|
||||||
let mut import_exports_bounding_box = String::new();
|
let import_exports_bounding_box = import_exports_target.to_bezpath().to_svg();
|
||||||
let _ = import_exports_target.subpath_to_svg(&mut import_exports_bounding_box, DAffine2::IDENTITY);
|
|
||||||
|
|
||||||
let mut modify_import_export = Vec::new();
|
let mut modify_import_export = Vec::new();
|
||||||
if let Some(modify_import_export_click_targets) = self.modify_import_export(network_path) {
|
if let Some(modify_import_export_click_targets) = self.modify_import_export(network_path) {
|
||||||
@@ -3136,16 +3128,14 @@ impl NodeNetworkInterface {
|
|||||||
.chain(modify_import_export_click_targets.reorder_imports_exports.click_targets())
|
.chain(modify_import_export_click_targets.reorder_imports_exports.click_targets())
|
||||||
{
|
{
|
||||||
if let ClickTargetType::Subpath(subpath) = click_target.target_type() {
|
if let ClickTargetType::Subpath(subpath) = click_target.target_type() {
|
||||||
let mut remove_string = String::new();
|
modify_import_export.push(subpath.to_bezpath().to_svg());
|
||||||
let _ = subpath.subpath_to_svg(&mut remove_string, DAffine2::IDENTITY);
|
|
||||||
modify_import_export.push(remove_string);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
FrontendClickTargets {
|
FrontendClickTargets {
|
||||||
node_click_targets,
|
node_click_targets,
|
||||||
layer_click_targets,
|
layer_click_targets,
|
||||||
port_click_targets,
|
connector_click_targets,
|
||||||
icon_click_targets,
|
icon_click_targets,
|
||||||
all_nodes_bounding_box,
|
all_nodes_bounding_box,
|
||||||
import_exports_bounding_box,
|
import_exports_bounding_box,
|
||||||
@@ -3407,7 +3397,7 @@ impl NodeNetworkInterface {
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let bounding_box_subpath = bezier_rs::Subpath::<PointId>::new_rect(bounds[0], bounds[1]);
|
let bounding_box_subpath = Subpath::<PointId>::new_rect(bounds[0], bounds[1]);
|
||||||
bounding_box_subpath.bounding_box_with_transform(network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport)
|
bounding_box_subpath.bounding_box_with_transform(network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6047,7 +6037,7 @@ impl Iterator for FlowIter<'_> {
|
|||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
let take = if self.flow_type == FlowType::UpstreamFlow { usize::MAX } else { 1 };
|
let take = if self.flow_type == FlowType::UpstreamFlow { u32::MAX as usize } else { 1 };
|
||||||
let inputs = document_node.inputs.iter().skip(skip).take(take);
|
let inputs = document_node.inputs.iter().skip(skip).take(take);
|
||||||
|
|
||||||
let node_ids = inputs.filter_map(|input| match input {
|
let node_ids = inputs.filter_map(|input| match input {
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions:
|
|||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};
|
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};
|
||||||
use crate::messages::prelude::DocumentMessageHandler;
|
use crate::messages::prelude::DocumentMessageHandler;
|
||||||
use bezier_rs::Subpath;
|
|
||||||
use glam::IVec2;
|
use glam::IVec2;
|
||||||
use graph_craft::document::DocumentNode;
|
use graph_craft::document::DocumentNode;
|
||||||
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
|
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
|
||||||
use graphene_std::ProtoNodeIdentifier;
|
use graphene_std::ProtoNodeIdentifier;
|
||||||
|
use graphene_std::subpath::Subpath;
|
||||||
use graphene_std::table::Table;
|
use graphene_std::table::Table;
|
||||||
use graphene_std::text::{TextAlign, TypesettingConfig};
|
use graphene_std::text::{TextAlign, TypesettingConfig};
|
||||||
use graphene_std::uuid::NodeId;
|
use graphene_std::uuid::NodeId;
|
||||||
@@ -300,10 +300,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
|||||||
"graphene_core::raster::BlendNode",
|
"graphene_core::raster::BlendNode",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
NodeReplacement {
|
|
||||||
node: graphene_std::raster_nodes::blending_nodes::blend_color_pair::IDENTIFIER,
|
|
||||||
aliases: &["graphene_raster_nodes::adjustments::BlendColorPairNode", "graphene_core::raster::BlendColorPairNode"],
|
|
||||||
},
|
|
||||||
NodeReplacement {
|
NodeReplacement {
|
||||||
node: graphene_std::raster_nodes::blending_nodes::color_overlay::IDENTIFIER,
|
node: graphene_std::raster_nodes::blending_nodes::color_overlay::IDENTIFIER,
|
||||||
aliases: &[
|
aliases: &[
|
||||||
@@ -637,9 +633,9 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||||||
};
|
};
|
||||||
let vector = Vector::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
|
// Retrieve the output connectors linked to the "Spline" node's output connector
|
||||||
let Some(spline_outputs) = document.network_interface.outward_wires(network_path)?.get(&OutputConnector::node(*node_id, 0)).cloned() else {
|
let Some(spline_outputs) = document.network_interface.outward_wires(network_path)?.get(&OutputConnector::node(*node_id, 0)).cloned() else {
|
||||||
log::error!("Vec of InputConnector Spline node is connected to its output port 0.");
|
log::error!("Vec of InputConnector Spline node is connected to its output connector 0.");
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -684,7 +680,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||||||
// Reposition the new "Path" node with an offset relative to the original "Spline" node's position
|
// Reposition the new "Path" node with an offset relative to the original "Spline" node's position
|
||||||
document.network_interface.shift_node(&new_path_id, node_position + IVec2::new(-7, 0), network_path);
|
document.network_interface.shift_node(&new_path_id, node_position + IVec2::new(-7, 0), network_path);
|
||||||
|
|
||||||
// Redirect each output connection from the old node to the new "Spline" node's output port
|
// Redirect each output connection from the old node to the new "Spline" node's output connector
|
||||||
for input_connector in spline_outputs {
|
for input_connector in spline_outputs {
|
||||||
document.network_interface.set_input(&input_connector, NodeInput::node(new_spline_id, 0), network_path);
|
document.network_interface.set_input(&input_connector, NodeInput::node(new_spline_id, 0), network_path);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,10 +16,12 @@ pub struct MenuBarMessageHandler {
|
|||||||
pub has_selected_nodes: bool,
|
pub has_selected_nodes: bool,
|
||||||
pub has_selected_layers: bool,
|
pub has_selected_layers: bool,
|
||||||
pub has_selection_history: (bool, bool),
|
pub has_selection_history: (bool, bool),
|
||||||
pub spreadsheet_view_open: bool,
|
|
||||||
pub message_logging_verbosity: MessageLoggingVerbosity,
|
pub message_logging_verbosity: MessageLoggingVerbosity,
|
||||||
pub reset_node_definitions_on_open: bool,
|
pub reset_node_definitions_on_open: bool,
|
||||||
pub make_path_editable_is_allowed: bool,
|
pub make_path_editable_is_allowed: bool,
|
||||||
|
pub data_panel_open: bool,
|
||||||
|
pub layers_panel_open: bool,
|
||||||
|
pub properties_panel_open: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
@@ -585,18 +587,40 @@ impl LayoutHolder for MenuBarMessageHandler {
|
|||||||
disabled: no_active_document,
|
disabled: no_active_document,
|
||||||
..MenuBarEntry::default()
|
..MenuBarEntry::default()
|
||||||
}],
|
}],
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
MenuBarEntry::new_root(
|
||||||
|
"Window".into(),
|
||||||
|
false,
|
||||||
|
MenuBarEntryChildren(vec![
|
||||||
|
vec![
|
||||||
|
MenuBarEntry {
|
||||||
|
label: "Properties".into(),
|
||||||
|
icon: Some(if self.properties_panel_open { "CheckboxChecked" } else { "CheckboxUnchecked" }.into()),
|
||||||
|
shortcut: action_keys!(PortfolioMessageDiscriminant::TogglePropertiesPanelOpen),
|
||||||
|
action: MenuBarEntry::create_action(|_| PortfolioMessage::TogglePropertiesPanelOpen.into()),
|
||||||
|
..MenuBarEntry::default()
|
||||||
|
},
|
||||||
|
MenuBarEntry {
|
||||||
|
label: "Layers".into(),
|
||||||
|
icon: Some(if self.layers_panel_open { "CheckboxChecked" } else { "CheckboxUnchecked" }.into()),
|
||||||
|
shortcut: action_keys!(PortfolioMessageDiscriminant::ToggleLayersPanelOpen),
|
||||||
|
action: MenuBarEntry::create_action(|_| PortfolioMessage::ToggleLayersPanelOpen.into()),
|
||||||
|
..MenuBarEntry::default()
|
||||||
|
},
|
||||||
|
],
|
||||||
vec![MenuBarEntry {
|
vec![MenuBarEntry {
|
||||||
label: "Window: Spreadsheet".into(),
|
label: "Data".into(),
|
||||||
icon: Some(if self.spreadsheet_view_open { "CheckboxChecked" } else { "CheckboxUnchecked" }.into()),
|
icon: Some(if self.data_panel_open { "CheckboxChecked" } else { "CheckboxUnchecked" }.into()),
|
||||||
action: MenuBarEntry::create_action(|_| SpreadsheetMessage::ToggleOpen.into()),
|
shortcut: action_keys!(PortfolioMessageDiscriminant::ToggleDataPanelOpen),
|
||||||
disabled: no_active_document,
|
action: MenuBarEntry::create_action(|_| PortfolioMessage::ToggleDataPanelOpen.into()),
|
||||||
..MenuBarEntry::default()
|
..MenuBarEntry::default()
|
||||||
}],
|
}],
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
MenuBarEntry::new_root(
|
MenuBarEntry::new_root(
|
||||||
"Help".into(),
|
"Help".into(),
|
||||||
true,
|
false,
|
||||||
MenuBarEntryChildren(vec![
|
MenuBarEntryChildren(vec![
|
||||||
vec![MenuBarEntry {
|
vec![MenuBarEntry {
|
||||||
label: "About Graphite…".into(),
|
label: "About Graphite…".into(),
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ mod portfolio_message_handler;
|
|||||||
pub mod document;
|
pub mod document;
|
||||||
pub mod document_migration;
|
pub mod document_migration;
|
||||||
pub mod menu_bar;
|
pub mod menu_bar;
|
||||||
pub mod spreadsheet;
|
|
||||||
pub mod utility_types;
|
pub mod utility_types;
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ pub enum PortfolioMessage {
|
|||||||
MenuBar(MenuBarMessage),
|
MenuBar(MenuBarMessage),
|
||||||
#[child]
|
#[child]
|
||||||
Document(DocumentMessage),
|
Document(DocumentMessage),
|
||||||
#[child]
|
|
||||||
Spreadsheet(SpreadsheetMessage),
|
|
||||||
|
|
||||||
// Messages
|
// Messages
|
||||||
Init,
|
Init,
|
||||||
@@ -128,6 +126,9 @@ pub enum PortfolioMessage {
|
|||||||
document_id: DocumentId,
|
document_id: DocumentId,
|
||||||
ignore_hash: bool,
|
ignore_hash: bool,
|
||||||
},
|
},
|
||||||
|
ToggleDataPanelOpen,
|
||||||
|
TogglePropertiesPanelOpen,
|
||||||
|
ToggleLayersPanelOpen,
|
||||||
ToggleRulers,
|
ToggleRulers,
|
||||||
UpdateDocumentWidgets,
|
UpdateDocumentWidgets,
|
||||||
UpdateOpenDocumentsList,
|
UpdateOpenDocumentsList,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use super::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use super::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use super::document::utility_types::network_interface;
|
use super::document::utility_types::network_interface;
|
||||||
use super::spreadsheet::SpreadsheetMessageHandler;
|
|
||||||
use super::utility_types::{PanelType, PersistentData};
|
use super::utility_types::{PanelType, PersistentData};
|
||||||
use crate::application::generate_uuid;
|
use crate::application::generate_uuid;
|
||||||
use crate::consts::{DEFAULT_DOCUMENT_NAME, DEFAULT_STROKE_WIDTH};
|
use crate::consts::{DEFAULT_DOCUMENT_NAME, DEFAULT_STROKE_WIDTH};
|
||||||
@@ -23,11 +22,12 @@ use crate::messages::tool::common_functionality::graph_modification_utils;
|
|||||||
use crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed;
|
use crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed;
|
||||||
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
|
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
|
||||||
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
|
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
|
||||||
use bezier_rs::BezierHandles;
|
use derivative::*;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_std::Color;
|
use graphene_std::Color;
|
||||||
use graphene_std::renderer::Quad;
|
use graphene_std::renderer::Quad;
|
||||||
|
use graphene_std::subpath::BezierHandles;
|
||||||
use graphene_std::text::Font;
|
use graphene_std::text::Font;
|
||||||
use graphene_std::vector::misc::HandleId;
|
use graphene_std::vector::misc::HandleId;
|
||||||
use graphene_std::vector::{PointId, SegmentId, Vector, VectorModificationType};
|
use graphene_std::vector::{PointId, SegmentId, Vector, VectorModificationType};
|
||||||
@@ -37,14 +37,15 @@ use std::vec;
|
|||||||
pub struct PortfolioMessageContext<'a> {
|
pub struct PortfolioMessageContext<'a> {
|
||||||
pub ipp: &'a InputPreprocessorMessageHandler,
|
pub ipp: &'a InputPreprocessorMessageHandler,
|
||||||
pub preferences: &'a PreferencesMessageHandler,
|
pub preferences: &'a PreferencesMessageHandler,
|
||||||
|
pub animation: &'a AnimationMessageHandler,
|
||||||
pub current_tool: &'a ToolType,
|
pub current_tool: &'a ToolType,
|
||||||
pub message_logging_verbosity: MessageLoggingVerbosity,
|
pub message_logging_verbosity: MessageLoggingVerbosity,
|
||||||
pub reset_node_definitions_on_open: bool,
|
pub reset_node_definitions_on_open: bool,
|
||||||
pub timing_information: TimingInformation,
|
pub timing_information: TimingInformation,
|
||||||
pub animation: &'a AnimationMessageHandler,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, ExtractField)]
|
#[derive(Debug, Derivative, ExtractField)]
|
||||||
|
#[derivative(Default)]
|
||||||
pub struct PortfolioMessageHandler {
|
pub struct PortfolioMessageHandler {
|
||||||
menu_bar_message_handler: MenuBarMessageHandler,
|
menu_bar_message_handler: MenuBarMessageHandler,
|
||||||
pub documents: HashMap<DocumentId, DocumentMessageHandler>,
|
pub documents: HashMap<DocumentId, DocumentMessageHandler>,
|
||||||
@@ -55,10 +56,13 @@ pub struct PortfolioMessageHandler {
|
|||||||
pub persistent_data: PersistentData,
|
pub persistent_data: PersistentData,
|
||||||
pub executor: NodeGraphExecutor,
|
pub executor: NodeGraphExecutor,
|
||||||
pub selection_mode: SelectionMode,
|
pub selection_mode: SelectionMode,
|
||||||
/// The spreadsheet UI allows for graph data to be previewed.
|
|
||||||
pub spreadsheet: SpreadsheetMessageHandler,
|
|
||||||
device_pixel_ratio: Option<f64>,
|
device_pixel_ratio: Option<f64>,
|
||||||
pub reset_node_definitions_on_open: bool,
|
pub reset_node_definitions_on_open: bool,
|
||||||
|
pub data_panel_open: bool,
|
||||||
|
#[derivative(Default(value = "true"))]
|
||||||
|
pub layers_panel_open: bool,
|
||||||
|
#[derivative(Default(value = "true"))]
|
||||||
|
pub properties_panel_open: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
@@ -67,11 +71,11 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
let PortfolioMessageContext {
|
let PortfolioMessageContext {
|
||||||
ipp,
|
ipp,
|
||||||
preferences,
|
preferences,
|
||||||
|
animation,
|
||||||
current_tool,
|
current_tool,
|
||||||
message_logging_verbosity,
|
message_logging_verbosity,
|
||||||
reset_node_definitions_on_open,
|
reset_node_definitions_on_open,
|
||||||
timing_information,
|
timing_information,
|
||||||
animation,
|
|
||||||
} = context;
|
} = context;
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
@@ -86,7 +90,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
self.menu_bar_message_handler.has_selected_layers = false;
|
self.menu_bar_message_handler.has_selected_layers = false;
|
||||||
self.menu_bar_message_handler.has_selection_history = (false, false);
|
self.menu_bar_message_handler.has_selection_history = (false, false);
|
||||||
self.menu_bar_message_handler.make_path_editable_is_allowed = false;
|
self.menu_bar_message_handler.make_path_editable_is_allowed = false;
|
||||||
self.menu_bar_message_handler.spreadsheet_view_open = self.spreadsheet.spreadsheet_view_open;
|
self.menu_bar_message_handler.data_panel_open = self.data_panel_open;
|
||||||
|
self.menu_bar_message_handler.layers_panel_open = self.layers_panel_open;
|
||||||
|
self.menu_bar_message_handler.properties_panel_open = self.properties_panel_open;
|
||||||
self.menu_bar_message_handler.message_logging_verbosity = message_logging_verbosity;
|
self.menu_bar_message_handler.message_logging_verbosity = message_logging_verbosity;
|
||||||
self.menu_bar_message_handler.reset_node_definitions_on_open = reset_node_definitions_on_open;
|
self.menu_bar_message_handler.reset_node_definitions_on_open = reset_node_definitions_on_open;
|
||||||
|
|
||||||
@@ -108,9 +114,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
|
|
||||||
self.menu_bar_message_handler.process_message(message, responses, ());
|
self.menu_bar_message_handler.process_message(message, responses, ());
|
||||||
}
|
}
|
||||||
PortfolioMessage::Spreadsheet(message) => {
|
|
||||||
self.spreadsheet.process_message(message, responses, ());
|
|
||||||
}
|
|
||||||
PortfolioMessage::Document(message) => {
|
PortfolioMessage::Document(message) => {
|
||||||
if let Some(document_id) = self.active_document_id {
|
if let Some(document_id) = self.active_document_id {
|
||||||
if let Some(document) = self.documents.get_mut(&document_id) {
|
if let Some(document) = self.documents.get_mut(&document_id) {
|
||||||
@@ -122,6 +125,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
current_tool,
|
current_tool,
|
||||||
preferences,
|
preferences,
|
||||||
device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.),
|
device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.),
|
||||||
|
data_panel_open: self.data_panel_open,
|
||||||
|
layers_panel_open: self.layers_panel_open,
|
||||||
|
properties_panel_open: self.properties_panel_open,
|
||||||
};
|
};
|
||||||
document.process_message(message, responses, document_inputs)
|
document.process_message(message, responses, document_inputs)
|
||||||
}
|
}
|
||||||
@@ -156,6 +162,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
current_tool,
|
current_tool,
|
||||||
preferences,
|
preferences,
|
||||||
device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.),
|
device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.),
|
||||||
|
data_panel_open: self.data_panel_open,
|
||||||
|
layers_panel_open: self.layers_panel_open,
|
||||||
|
properties_panel_open: self.properties_panel_open,
|
||||||
};
|
};
|
||||||
document.process_message(message, responses, document_inputs)
|
document.process_message(message, responses, document_inputs)
|
||||||
}
|
}
|
||||||
@@ -201,6 +210,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
// Clear relevant UI layouts if there are no documents
|
// Clear relevant UI layouts if there are no documents
|
||||||
responses.add(PropertiesPanelMessage::Clear);
|
responses.add(PropertiesPanelMessage::Clear);
|
||||||
responses.add(DocumentMessage::ClearLayersPanel);
|
responses.add(DocumentMessage::ClearLayersPanel);
|
||||||
|
responses.add(DataPanelMessage::ClearLayout);
|
||||||
let hint_data = HintData(vec![HintGroup(vec![])]);
|
let hint_data = HintData(vec![HintGroup(vec![])]);
|
||||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||||
}
|
}
|
||||||
@@ -225,6 +235,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
// Clear UI layouts that assume the existence of a document
|
// Clear UI layouts that assume the existence of a document
|
||||||
responses.add(PropertiesPanelMessage::Clear);
|
responses.add(PropertiesPanelMessage::Clear);
|
||||||
responses.add(DocumentMessage::ClearLayersPanel);
|
responses.add(DocumentMessage::ClearLayersPanel);
|
||||||
|
responses.add(DataPanelMessage::ClearLayout);
|
||||||
let hint_data = HintData(vec![HintGroup(vec![])]);
|
let hint_data = HintData(vec![HintGroup(vec![])]);
|
||||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||||
}
|
}
|
||||||
@@ -344,13 +355,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
self.persistent_data.font_cache.insert(font, preview_url, data);
|
self.persistent_data.font_cache.insert(font, preview_url, data);
|
||||||
self.executor.update_font_cache(self.persistent_data.font_cache.clone());
|
self.executor.update_font_cache(self.persistent_data.font_cache.clone());
|
||||||
for document_id in self.document_ids.iter() {
|
for document_id in self.document_ids.iter() {
|
||||||
let inspect_node = self.inspect_node_id();
|
let node_to_inspect = self.node_to_inspect();
|
||||||
if let Ok(message) = self.executor.submit_node_graph_evaluation(
|
if let Ok(message) = self.executor.submit_node_graph_evaluation(
|
||||||
self.documents.get_mut(document_id).expect("Tried to render non-existent document"),
|
self.documents.get_mut(document_id).expect("Tried to render non-existent document"),
|
||||||
*document_id,
|
*document_id,
|
||||||
ipp.viewport_bounds.size().as_uvec2(),
|
ipp.viewport_bounds.size().as_uvec2(),
|
||||||
timing_information,
|
timing_information,
|
||||||
inspect_node,
|
node_to_inspect,
|
||||||
true,
|
true,
|
||||||
) {
|
) {
|
||||||
responses.add_front(message);
|
responses.add_front(message);
|
||||||
@@ -388,7 +399,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
new_responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });
|
new_responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });
|
||||||
}
|
}
|
||||||
|
|
||||||
self.load_document(new_document, document_id, &mut new_responses, false);
|
self.load_document(new_document, document_id, self.layers_panel_open, &mut new_responses, false);
|
||||||
new_responses.add(PortfolioMessage::SelectDocument { document_id });
|
new_responses.add(PortfolioMessage::SelectDocument { document_id });
|
||||||
new_responses.extend(responses.drain(..));
|
new_responses.extend(responses.drain(..));
|
||||||
*responses = new_responses;
|
*responses = new_responses;
|
||||||
@@ -504,7 +515,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
document.set_save_state(document_is_saved);
|
document.set_save_state(document_is_saved);
|
||||||
|
|
||||||
// Load the document into the portfolio so it opens in the editor
|
// Load the document into the portfolio so it opens in the editor
|
||||||
self.load_document(document, document_id, responses, to_front);
|
self.load_document(document, document_id, self.layers_panel_open, responses, to_front);
|
||||||
}
|
}
|
||||||
PortfolioMessage::PasteIntoFolder { clipboard, parent, insert_index } => {
|
PortfolioMessage::PasteIntoFolder { clipboard, parent, insert_index } => {
|
||||||
let mut all_new_ids = Vec::new();
|
let mut all_new_ids = Vec::new();
|
||||||
@@ -770,7 +781,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
|
|
||||||
if create_document {
|
if create_document {
|
||||||
responses.add(PortfolioMessage::NewDocumentWithName {
|
responses.add(PortfolioMessage::NewDocumentWithName {
|
||||||
name: name.clone().unwrap_or("Untitled Document".into()),
|
name: name.clone().unwrap_or(DEFAULT_DOCUMENT_NAME.into()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -801,7 +812,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
|
|
||||||
if create_document {
|
if create_document {
|
||||||
responses.add(PortfolioMessage::NewDocumentWithName {
|
responses.add(PortfolioMessage::NewDocumentWithName {
|
||||||
name: name.clone().unwrap_or("Untitled Document".into()),
|
name: name.clone().unwrap_or(DEFAULT_DOCUMENT_NAME.into()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -918,13 +929,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
PortfolioMessage::SubmitGraphRender { document_id, ignore_hash } => {
|
PortfolioMessage::SubmitGraphRender { document_id, ignore_hash } => {
|
||||||
let inspect_node = self.inspect_node_id();
|
let node_to_inspect = self.node_to_inspect();
|
||||||
let result = self.executor.submit_node_graph_evaluation(
|
let result = self.executor.submit_node_graph_evaluation(
|
||||||
self.documents.get_mut(&document_id).expect("Tried to render non-existent document"),
|
self.documents.get_mut(&document_id).expect("Tried to render non-existent document"),
|
||||||
document_id,
|
document_id,
|
||||||
ipp.viewport_bounds.size().as_uvec2(),
|
ipp.viewport_bounds.size().as_uvec2(),
|
||||||
timing_information,
|
timing_information,
|
||||||
inspect_node,
|
node_to_inspect,
|
||||||
ignore_hash,
|
ignore_hash,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -938,6 +949,58 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
Ok(message) => responses.add_front(message),
|
Ok(message) => responses.add_front(message),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
PortfolioMessage::ToggleDataPanelOpen => {
|
||||||
|
self.data_panel_open = !self.data_panel_open;
|
||||||
|
responses.add(MenuBarMessage::SendLayout);
|
||||||
|
|
||||||
|
// Run the graph to grab the data
|
||||||
|
if self.data_panel_open {
|
||||||
|
// When opening, we make the frontend show the panel first so it can start receiving its message subscriptions for the data it will display
|
||||||
|
responses.add(FrontendMessage::UpdateDataPanelState { open: self.data_panel_open });
|
||||||
|
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
} else {
|
||||||
|
// If we don't clear the panel, the layout diffing system will assume widgets still exist when it attempts to update the data panel next time it is opened
|
||||||
|
responses.add(DataPanelMessage::ClearLayout);
|
||||||
|
|
||||||
|
// When closing, we make the frontend hide the panel last so it can finish receiving its message subscriptions before it is destroyed
|
||||||
|
responses.add(FrontendMessage::UpdateDataPanelState { open: self.data_panel_open });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PortfolioMessage::TogglePropertiesPanelOpen => {
|
||||||
|
self.properties_panel_open = !self.properties_panel_open;
|
||||||
|
responses.add(MenuBarMessage::SendLayout);
|
||||||
|
|
||||||
|
responses.add(FrontendMessage::UpdatePropertiesPanelState { open: self.properties_panel_open });
|
||||||
|
|
||||||
|
// Run the graph to grab the data
|
||||||
|
if self.properties_panel_open {
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.add(PropertiesPanelMessage::Refresh);
|
||||||
|
}
|
||||||
|
PortfolioMessage::ToggleLayersPanelOpen => {
|
||||||
|
self.layers_panel_open = !self.layers_panel_open;
|
||||||
|
responses.add(MenuBarMessage::SendLayout);
|
||||||
|
|
||||||
|
// Run the graph to grab the data
|
||||||
|
if self.layers_panel_open {
|
||||||
|
// When opening, we make the frontend show the panel first so it can start receiving its message subscriptions for the data it will display
|
||||||
|
responses.add(FrontendMessage::UpdateLayersPanelState { open: self.layers_panel_open });
|
||||||
|
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
responses.add(DeferMessage::AfterGraphRun {
|
||||||
|
messages: vec![NodeGraphMessage::UpdateLayerPanel.into(), DocumentMessage::DocumentStructureChanged.into()],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// If we don't clear the panel, the layout diffing system will assume widgets still exist when it attempts to update the layers panel next time it is opened
|
||||||
|
responses.add(DocumentMessage::ClearLayersPanel);
|
||||||
|
|
||||||
|
// When closing, we make the frontend hide the panel last so it can finish receiving its message subscriptions before it is destroyed
|
||||||
|
responses.add(FrontendMessage::UpdateLayersPanelState { open: self.layers_panel_open });
|
||||||
|
}
|
||||||
|
}
|
||||||
PortfolioMessage::ToggleRulers => {
|
PortfolioMessage::ToggleRulers => {
|
||||||
if let Some(document) = self.active_document_mut() {
|
if let Some(document) = self.active_document_mut() {
|
||||||
document.rulers_visible = !document.rulers_visible;
|
document.rulers_visible = !document.rulers_visible;
|
||||||
@@ -987,6 +1050,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
PasteIntoFolder,
|
PasteIntoFolder,
|
||||||
PrevDocument,
|
PrevDocument,
|
||||||
ToggleRulers,
|
ToggleRulers,
|
||||||
|
ToggleDataPanelOpen,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Extend with actions that require an active document
|
// Extend with actions that require an active document
|
||||||
@@ -1056,14 +1120,14 @@ impl PortfolioMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: DocumentId, responses: &mut VecDeque<Message>, to_front: bool) {
|
fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: DocumentId, layers_panel_open: bool, responses: &mut VecDeque<Message>, to_front: bool) {
|
||||||
if to_front {
|
if to_front {
|
||||||
self.document_ids.push_front(document_id);
|
self.document_ids.push_front(document_id);
|
||||||
} else {
|
} else {
|
||||||
self.document_ids.push_back(document_id);
|
self.document_ids.push_back(document_id);
|
||||||
}
|
}
|
||||||
new_document.update_layers_panel_control_bar_widgets(responses);
|
new_document.update_layers_panel_control_bar_widgets(layers_panel_open, responses);
|
||||||
new_document.update_layers_panel_bottom_bar_widgets(responses);
|
new_document.update_layers_panel_bottom_bar_widgets(layers_panel_open, responses);
|
||||||
|
|
||||||
self.documents.insert(document_id, new_document);
|
self.documents.insert(document_id, new_document);
|
||||||
|
|
||||||
@@ -1111,17 +1175,17 @@ impl PortfolioMessageHandler {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the id of the node that should be used as the target for the spreadsheet
|
/// Get the ID of the selected node that should be used as the current source for the Data panel.
|
||||||
pub fn inspect_node_id(&self) -> Option<NodeId> {
|
pub fn node_to_inspect(&self) -> Option<NodeId> {
|
||||||
// Spreadsheet not open, skipping
|
// Skip if the Data panel is not open
|
||||||
if !self.spreadsheet.spreadsheet_view_open {
|
if !self.data_panel_open {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let document = self.documents.get(&self.active_document_id?)?;
|
let document = self.documents.get(&self.active_document_id?)?;
|
||||||
let selected_nodes = document.network_interface.selected_nodes().0;
|
let selected_nodes = document.network_interface.selected_nodes().0;
|
||||||
|
|
||||||
// Selected nodes != 1, skipping
|
// Skip if there is not exactly one selected node
|
||||||
if selected_nodes.len() != 1 {
|
if selected_nodes.len() != 1 {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
mod spreadsheet_message;
|
|
||||||
mod spreadsheet_message_handler;
|
|
||||||
|
|
||||||
#[doc(inline)]
|
|
||||||
pub use spreadsheet_message::*;
|
|
||||||
#[doc(inline)]
|
|
||||||
pub use spreadsheet_message_handler::*;
|
|
||||||
@@ -1,326 +0,0 @@
|
|||||||
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::*;
|
|
||||||
use graph_craft::document::NodeId;
|
|
||||||
use graphene_std::Color;
|
|
||||||
use graphene_std::Context;
|
|
||||||
use graphene_std::memo::IORecord;
|
|
||||||
use graphene_std::raster::Image;
|
|
||||||
use graphene_std::table::Table;
|
|
||||||
use graphene_std::vector::Vector;
|
|
||||||
use graphene_std::{Artboard, Graphic};
|
|
||||||
use std::any::Any;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
/// The spreadsheet UI allows for graph data to be previewed.
|
|
||||||
#[derive(Default, Debug, Clone, ExtractField)]
|
|
||||||
pub struct SpreadsheetMessageHandler {
|
|
||||||
/// Sets whether or not the spreadsheet is drawn.
|
|
||||||
pub spreadsheet_view_open: bool,
|
|
||||||
inspect_node: Option<NodeId>,
|
|
||||||
introspected_data: Option<Arc<dyn Any + Send + Sync>>,
|
|
||||||
element_path: Vec<usize>,
|
|
||||||
viewing_vector_domain: VectorDomain,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[message_handler_data]
|
|
||||||
impl MessageHandler<SpreadsheetMessage, ()> for SpreadsheetMessageHandler {
|
|
||||||
fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque<Message>, _: ()) {
|
|
||||||
match message {
|
|
||||||
SpreadsheetMessage::ToggleOpen => {
|
|
||||||
self.spreadsheet_view_open = !self.spreadsheet_view_open;
|
|
||||||
// Run the graph to grab the data
|
|
||||||
if self.spreadsheet_view_open {
|
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
|
||||||
}
|
|
||||||
// Update checked UI state for open
|
|
||||||
responses.add(MenuBarMessage::SendLayout);
|
|
||||||
self.update_layout(responses);
|
|
||||||
}
|
|
||||||
|
|
||||||
SpreadsheetMessage::UpdateLayout { mut inspect_result } => {
|
|
||||||
self.inspect_node = Some(inspect_result.inspect_node);
|
|
||||||
self.introspected_data = inspect_result.take_data();
|
|
||||||
self.update_layout(responses)
|
|
||||||
}
|
|
||||||
|
|
||||||
SpreadsheetMessage::PushToElementPath { index } => {
|
|
||||||
self.element_path.push(index);
|
|
||||||
self.update_layout(responses);
|
|
||||||
}
|
|
||||||
SpreadsheetMessage::TruncateElementPath { len } => {
|
|
||||||
self.element_path.truncate(len);
|
|
||||||
self.update_layout(responses);
|
|
||||||
}
|
|
||||||
|
|
||||||
SpreadsheetMessage::ViewVectorDomain { domain } => {
|
|
||||||
self.viewing_vector_domain = domain;
|
|
||||||
self.update_layout(responses);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn actions(&self) -> ActionList {
|
|
||||||
actions!(SpreadsheetMessage;)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SpreadsheetMessageHandler {
|
|
||||||
fn update_layout(&mut self, responses: &mut VecDeque<Message>) {
|
|
||||||
responses.add(FrontendMessage::UpdateSpreadsheetState {
|
|
||||||
node: self.inspect_node,
|
|
||||||
open: self.spreadsheet_view_open,
|
|
||||||
});
|
|
||||||
if !self.spreadsheet_view_open {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let mut layout_data = LayoutData {
|
|
||||||
current_depth: 0,
|
|
||||||
desired_path: &mut self.element_path,
|
|
||||||
breadcrumbs: Vec::new(),
|
|
||||||
vector_domain: self.viewing_vector_domain,
|
|
||||||
};
|
|
||||||
let mut layout = self
|
|
||||||
.introspected_data
|
|
||||||
.as_ref()
|
|
||||||
.map(|instrospected_data| generate_layout(instrospected_data, &mut layout_data))
|
|
||||||
.unwrap_or_else(|| Some(label("No data")))
|
|
||||||
.unwrap_or_else(|| label("Failed to downcast data"));
|
|
||||||
|
|
||||||
if layout_data.breadcrumbs.len() > 1 {
|
|
||||||
let breadcrumb = BreadcrumbTrailButtons::new(layout_data.breadcrumbs)
|
|
||||||
.on_update(|&len| SpreadsheetMessage::TruncateElementPath { len: len as usize }.into())
|
|
||||||
.widget_holder();
|
|
||||||
layout.insert(0, LayoutGroup::Row { widgets: vec![breadcrumb] });
|
|
||||||
}
|
|
||||||
|
|
||||||
responses.add(LayoutMessage::SendLayout {
|
|
||||||
layout: Layout::WidgetLayout(WidgetLayout { layout }),
|
|
||||||
layout_target: LayoutTarget::Spreadsheet,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LayoutData<'a> {
|
|
||||||
current_depth: usize,
|
|
||||||
desired_path: &'a mut Vec<usize>,
|
|
||||||
breadcrumbs: Vec<String>,
|
|
||||||
vector_domain: VectorDomain,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
|
|
||||||
// We simply try random types. TODO: better strategy.
|
|
||||||
#[allow(clippy::manual_map)]
|
|
||||||
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Artboard>>>() {
|
|
||||||
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<Vector>>>() {
|
|
||||||
Some(io.output.layout_with_breadcrumb(data))
|
|
||||||
} 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))
|
|
||||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), Table<Graphic>>>() {
|
|
||||||
Some(io.output.layout_with_breadcrumb(data))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn column_headings(value: &[&str]) -> Vec<WidgetHolder> {
|
|
||||||
value.iter().map(|text| TextLabel::new(*text).widget_holder()).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn label(x: impl Into<String>) -> Vec<LayoutGroup> {
|
|
||||||
let error = vec![TextLabel::new(x).widget_holder()];
|
|
||||||
vec![LayoutGroup::Row { widgets: error }]
|
|
||||||
}
|
|
||||||
|
|
||||||
trait TableRowLayout {
|
|
||||||
fn type_name() -> &'static str;
|
|
||||||
fn identifier(&self) -> String;
|
|
||||||
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
|
||||||
data.breadcrumbs.push(self.identifier());
|
|
||||||
self.compute_layout(data)
|
|
||||||
}
|
|
||||||
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TableRowLayout for Graphic {
|
|
||||||
fn type_name() -> &'static str {
|
|
||||||
"Graphic"
|
|
||||||
}
|
|
||||||
fn identifier(&self) -> String {
|
|
||||||
match self {
|
|
||||||
Self::Graphic(graphic) => graphic.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
|
|
||||||
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
|
||||||
self.compute_layout(data)
|
|
||||||
}
|
|
||||||
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
|
||||||
match self {
|
|
||||||
Self::Graphic(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 Vector {
|
|
||||||
fn type_name() -> &'static str {
|
|
||||||
"Vector"
|
|
||||||
}
|
|
||||||
fn identifier(&self) -> String {
|
|
||||||
format!(
|
|
||||||
"Vector ({} point{}, {} segment{})",
|
|
||||||
self.point_domain.ids().len(),
|
|
||||||
if self.point_domain.ids().len() == 1 { "" } else { "s" },
|
|
||||||
self.segment_domain.ids().len(),
|
|
||||||
if self.segment_domain.ids().len() == 1 { "" } else { "s" }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
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: {}\nPreserves Reference to Upstream Nested Layers for Editing by Tools: {}",
|
|
||||||
self.style,
|
|
||||||
colinear,
|
|
||||||
if self.upstream_nested_layers.is_some() { "Yes" } else { "No" }
|
|
||||||
))
|
|
||||||
.multiline(true)
|
|
||||||
.widget_holder(),
|
|
||||||
];
|
|
||||||
|
|
||||||
let domain_entries = [VectorDomain::Points, VectorDomain::Segments, VectorDomain::Regions]
|
|
||||||
.into_iter()
|
|
||||||
.map(|domain| {
|
|
||||||
RadioEntryData::new(format!("{domain:?}"))
|
|
||||||
.label(format!("{domain:?}"))
|
|
||||||
.on_update(move |_| SpreadsheetMessage::ViewVectorDomain { domain }.into())
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
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_domain {
|
|
||||||
VectorDomain::Points => {
|
|
||||||
table_rows.push(column_headings(&["", "position"]));
|
|
||||||
table_rows.extend(
|
|
||||||
self.point_domain
|
|
||||||
.iter()
|
|
||||||
.map(|(id, position)| vec![TextLabel::new(format!("{}", id.inner())).widget_holder(), TextLabel::new(format!("{}", position)).widget_holder()]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
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![
|
|
||||||
TextLabel::new(format!("{}", id.inner())).widget_holder(),
|
|
||||||
TextLabel::new(format!("{}", start)).widget_holder(),
|
|
||||||
TextLabel::new(format!("{}", end)).widget_holder(),
|
|
||||||
TextLabel::new(format!("{:?}", handles)).widget_holder(),
|
|
||||||
]
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
VectorDomain::Regions => {
|
|
||||||
table_rows.push(column_headings(&["", "segment_range", "fill"]));
|
|
||||||
table_rows.extend(self.region_domain.iter().map(|(id, segment_range, fill)| {
|
|
||||||
vec![
|
|
||||||
TextLabel::new(format!("{}", id.inner())).widget_holder(),
|
|
||||||
TextLabel::new(format!("{:?}", segment_range)).widget_holder(),
|
|
||||||
TextLabel::new(format!("{}", fill.inner())).widget_holder(),
|
|
||||||
]
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vec![LayoutGroup::Row { widgets: style }, LayoutGroup::Row { widgets: domain }, LayoutGroup::Table { rows: table_rows }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TableRowLayout for Image<Color> {
|
|
||||||
fn type_name() -> &'static str {
|
|
||||||
"Image"
|
|
||||||
}
|
|
||||||
fn identifier(&self) -> String {
|
|
||||||
format!("Image ({}x{})", self.width, self.height)
|
|
||||||
}
|
|
||||||
fn compute_layout(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
|
||||||
let rows = vec![vec![TextLabel::new(format!("Image ({}x{})", self.width, self.height)).widget_holder()]];
|
|
||||||
vec![LayoutGroup::Table { rows }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TableRowLayout for Artboard {
|
|
||||||
fn type_name() -> &'static str {
|
|
||||||
"Artboard"
|
|
||||||
}
|
|
||||||
fn identifier(&self) -> String {
|
|
||||||
self.label.clone()
|
|
||||||
}
|
|
||||||
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
|
||||||
self.content.compute_layout(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: TableRowLayout> TableRowLayout for Table<T> {
|
|
||||||
fn type_name() -> &'static str {
|
|
||||||
"Table"
|
|
||||||
}
|
|
||||||
fn identifier(&self) -> String {
|
|
||||||
format!("Table<{}> ({} row{})", T::type_name(), self.len(), if self.len() == 1 { "" } else { "s" })
|
|
||||||
}
|
|
||||||
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
|
||||||
if let Some(index) = data.desired_path.get(data.current_depth).copied() {
|
|
||||||
if let Some(row) = self.get(index) {
|
|
||||||
data.current_depth += 1;
|
|
||||||
let result = row.element.layout_with_breadcrumb(data);
|
|
||||||
data.current_depth -= 1;
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
warn!("Desired path truncated");
|
|
||||||
data.desired_path.truncate(data.current_depth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut rows = self
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(index, row)| {
|
|
||||||
let (scale, angle, translation) = row.transform.to_scale_angle_translation();
|
|
||||||
let rotation = if angle == -0. { 0. } else { angle.to_degrees() };
|
|
||||||
let round = |x: f64| (x * 1e3).round() / 1e3;
|
|
||||||
vec![
|
|
||||||
TextLabel::new(format!("{index}")).widget_holder(),
|
|
||||||
TextButton::new(row.element.identifier())
|
|
||||||
.on_update(move |_| SpreadsheetMessage::PushToElementPath { index }.into())
|
|
||||||
.widget_holder(),
|
|
||||||
TextLabel::new(format!(
|
|
||||||
"Location: ({} px, {} px) — Rotation: {rotation:2}° — Scale: ({}x, {}x)",
|
|
||||||
round(translation.x),
|
|
||||||
round(translation.y),
|
|
||||||
round(scale.x),
|
|
||||||
round(scale.y)
|
|
||||||
))
|
|
||||||
.widget_holder(),
|
|
||||||
TextLabel::new(format!("{}", row.alpha_blending)).widget_holder(),
|
|
||||||
TextLabel::new(row.source_node_id.map_or_else(|| "-".to_string(), |id| format!("{}", id.0))).widget_holder(),
|
|
||||||
]
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
rows.insert(0, column_headings(&["", "element", "transform", "alpha_blending", "source_node_id"]));
|
|
||||||
|
|
||||||
vec![LayoutGroup::Table { rows }]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -43,7 +43,7 @@ pub enum PanelType {
|
|||||||
Document,
|
Document,
|
||||||
Layers,
|
Layers,
|
||||||
Properties,
|
Properties,
|
||||||
Spreadsheet,
|
DataPanel,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<String> for PanelType {
|
impl From<String> for PanelType {
|
||||||
@@ -52,7 +52,7 @@ impl From<String> for PanelType {
|
|||||||
"Document" => PanelType::Document,
|
"Document" => PanelType::Document,
|
||||||
"Layers" => PanelType::Layers,
|
"Layers" => PanelType::Layers,
|
||||||
"Properties" => PanelType::Properties,
|
"Properties" => PanelType::Properties,
|
||||||
"Spreadsheet" => PanelType::Spreadsheet,
|
"Data" => PanelType::DataPanel,
|
||||||
_ => panic!("Unknown panel type: {value}"),
|
_ => panic!("Unknown panel type: {value}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ pub use crate::messages::input_mapper::key_mapping::{KeyMappingMessage, KeyMappi
|
|||||||
pub use crate::messages::input_mapper::{InputMapperMessage, InputMapperMessageContext, InputMapperMessageDiscriminant, InputMapperMessageHandler};
|
pub use crate::messages::input_mapper::{InputMapperMessage, InputMapperMessageContext, InputMapperMessageDiscriminant, InputMapperMessageHandler};
|
||||||
pub use crate::messages::input_preprocessor::{InputPreprocessorMessage, InputPreprocessorMessageContext, InputPreprocessorMessageDiscriminant, InputPreprocessorMessageHandler};
|
pub use crate::messages::input_preprocessor::{InputPreprocessorMessage, InputPreprocessorMessageContext, InputPreprocessorMessageDiscriminant, InputPreprocessorMessageHandler};
|
||||||
pub use crate::messages::layout::{LayoutMessage, LayoutMessageDiscriminant, LayoutMessageHandler};
|
pub use crate::messages::layout::{LayoutMessage, LayoutMessageDiscriminant, LayoutMessageHandler};
|
||||||
|
pub use crate::messages::portfolio::document::data_panel::{DataPanelMessage, DataPanelMessageDiscriminant};
|
||||||
pub use crate::messages::portfolio::document::graph_operation::{GraphOperationMessage, GraphOperationMessageContext, GraphOperationMessageDiscriminant, GraphOperationMessageHandler};
|
pub use crate::messages::portfolio::document::graph_operation::{GraphOperationMessage, GraphOperationMessageContext, GraphOperationMessageDiscriminant, GraphOperationMessageHandler};
|
||||||
pub use crate::messages::portfolio::document::navigation::{NavigationMessage, NavigationMessageContext, NavigationMessageDiscriminant, NavigationMessageHandler};
|
pub use crate::messages::portfolio::document::navigation::{NavigationMessage, NavigationMessageContext, NavigationMessageDiscriminant, NavigationMessageHandler};
|
||||||
pub use crate::messages::portfolio::document::node_graph::{NodeGraphMessage, NodeGraphMessageDiscriminant, NodeGraphMessageHandler};
|
pub use crate::messages::portfolio::document::node_graph::{NodeGraphMessage, NodeGraphMessageDiscriminant, NodeGraphMessageHandler};
|
||||||
@@ -24,12 +25,10 @@ pub use crate::messages::portfolio::document::overlays::{OverlaysMessage, Overla
|
|||||||
pub use crate::messages::portfolio::document::properties_panel::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant, PropertiesPanelMessageHandler};
|
pub use crate::messages::portfolio::document::properties_panel::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant, PropertiesPanelMessageHandler};
|
||||||
pub use crate::messages::portfolio::document::{DocumentMessage, DocumentMessageContext, DocumentMessageDiscriminant, DocumentMessageHandler};
|
pub use crate::messages::portfolio::document::{DocumentMessage, DocumentMessageContext, DocumentMessageDiscriminant, DocumentMessageHandler};
|
||||||
pub use crate::messages::portfolio::menu_bar::{MenuBarMessage, MenuBarMessageDiscriminant, MenuBarMessageHandler};
|
pub use crate::messages::portfolio::menu_bar::{MenuBarMessage, MenuBarMessageDiscriminant, MenuBarMessageHandler};
|
||||||
pub use crate::messages::portfolio::spreadsheet::{SpreadsheetMessage, SpreadsheetMessageDiscriminant};
|
|
||||||
pub use crate::messages::portfolio::{PortfolioMessage, PortfolioMessageContext, PortfolioMessageDiscriminant, PortfolioMessageHandler};
|
pub use crate::messages::portfolio::{PortfolioMessage, PortfolioMessageContext, PortfolioMessageDiscriminant, PortfolioMessageHandler};
|
||||||
pub use crate::messages::preferences::{PreferencesMessage, PreferencesMessageDiscriminant, PreferencesMessageHandler};
|
pub use crate::messages::preferences::{PreferencesMessage, PreferencesMessageDiscriminant, PreferencesMessageHandler};
|
||||||
pub use crate::messages::tool::transform_layer::{TransformLayerMessage, TransformLayerMessageDiscriminant, TransformLayerMessageHandler};
|
pub use crate::messages::tool::transform_layer::{TransformLayerMessage, TransformLayerMessageDiscriminant, TransformLayerMessageHandler};
|
||||||
pub use crate::messages::tool::{ToolMessage, ToolMessageContext, ToolMessageDiscriminant, ToolMessageHandler};
|
pub use crate::messages::tool::{ToolMessage, ToolMessageContext, ToolMessageDiscriminant, ToolMessageHandler};
|
||||||
pub use crate::messages::workspace::{WorkspaceMessage, WorkspaceMessageDiscriminant, WorkspaceMessageHandler};
|
|
||||||
|
|
||||||
// Message, MessageDiscriminant
|
// Message, MessageDiscriminant
|
||||||
pub use crate::messages::broadcast::broadcast_event::{BroadcastEvent, BroadcastEventDiscriminant};
|
pub use crate::messages::broadcast::broadcast_event::{BroadcastEvent, BroadcastEventDiscriminant};
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions;
|
|||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeNetworkInterface, NodeTemplate};
|
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeNetworkInterface, NodeTemplate};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use bezier_rs::Subpath;
|
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{NodeId, NodeInput};
|
use graph_craft::document::{NodeId, NodeInput};
|
||||||
@@ -12,10 +11,11 @@ use graphene_std::Color;
|
|||||||
use graphene_std::NodeInputDecleration;
|
use graphene_std::NodeInputDecleration;
|
||||||
use graphene_std::raster::BlendMode;
|
use graphene_std::raster::BlendMode;
|
||||||
use graphene_std::raster_types::{CPU, GPU, Raster};
|
use graphene_std::raster_types::{CPU, GPU, Raster};
|
||||||
|
use graphene_std::subpath::Subpath;
|
||||||
use graphene_std::table::Table;
|
use graphene_std::table::Table;
|
||||||
use graphene_std::text::{Font, TypesettingConfig};
|
use graphene_std::text::{Font, TypesettingConfig};
|
||||||
use graphene_std::vector::misc::ManipulatorPointId;
|
use graphene_std::vector::misc::ManipulatorPointId;
|
||||||
use graphene_std::vector::style::Gradient;
|
use graphene_std::vector::style::{Fill, Gradient};
|
||||||
use graphene_std::vector::{PointId, SegmentId, VectorModificationType};
|
use graphene_std::vector::{PointId, SegmentId, VectorModificationType};
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
@@ -273,7 +273,7 @@ pub fn get_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkI
|
|||||||
let fill_index = 1;
|
let fill_index = 1;
|
||||||
|
|
||||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Fill")?;
|
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Fill")?;
|
||||||
let TaggedValue::Fill(graphene_std::vector::style::Fill::Gradient(gradient)) = inputs.get(fill_index)?.as_value()? else {
|
let TaggedValue::Fill(Fill::Gradient(gradient)) = inputs.get(fill_index)?.as_value()? else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
Some(gradient.clone())
|
Some(gradient.clone())
|
||||||
@@ -284,7 +284,7 @@ pub fn get_fill_color(layer: LayerNodeIdentifier, network_interface: &NodeNetwor
|
|||||||
let fill_index = 1;
|
let fill_index = 1;
|
||||||
|
|
||||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Fill")?;
|
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Fill")?;
|
||||||
let TaggedValue::Fill(graphene_std::vector::style::Fill::Solid(color)) = inputs.get(fill_index)?.as_value()? else {
|
let TaggedValue::Fill(Fill::Solid(color)) = inputs.get(fill_index)?.as_value()? else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
Some(color.to_linear_srgb())
|
Some(color.to_linear_srgb())
|
||||||
|
|||||||
@@ -43,18 +43,19 @@ fn draw_line_with_length(line_start: DVec2, line_end: DVec2, transform: DAffine2
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws a dashed outline around a rectangle to visualize the AABB
|
/// Draws a dashed outline around the given rectangle (assumed to be in document space).
|
||||||
|
/// The provided transform is applied to convert coordinates (e.g., to viewport space) during rendering.
|
||||||
fn draw_dashed_rect_outline(rect: Rect, transform: DAffine2, overlay_context: &mut OverlayContext) {
|
fn draw_dashed_rect_outline(rect: Rect, transform: DAffine2, overlay_context: &mut OverlayContext) {
|
||||||
let min = rect.min();
|
let min = rect.min();
|
||||||
let max = rect.max();
|
let max = rect.max();
|
||||||
|
|
||||||
// Create the four corners of the rectangle
|
// Define corners in document space
|
||||||
let top_left = transform.transform_point2(DVec2::new(min.x, min.y));
|
let top_left = DVec2::new(min.x, min.y);
|
||||||
let top_right = transform.transform_point2(DVec2::new(max.x, min.y));
|
let top_right = DVec2::new(max.x, min.y);
|
||||||
let bottom_right = transform.transform_point2(DVec2::new(max.x, max.y));
|
let bottom_right = DVec2::new(max.x, max.y);
|
||||||
let bottom_left = transform.transform_point2(DVec2::new(min.x, max.y));
|
let bottom_left = DVec2::new(min.x, max.y);
|
||||||
|
|
||||||
// Draw the four sides as dashed lines
|
// Draw each edge using document-space coordinates; transform is applied inside draw_dashed_line
|
||||||
draw_dashed_line(top_left, top_right, transform, overlay_context);
|
draw_dashed_line(top_left, top_right, transform, overlay_context);
|
||||||
draw_dashed_line(top_right, bottom_right, transform, overlay_context);
|
draw_dashed_line(top_right, bottom_right, transform, overlay_context);
|
||||||
draw_dashed_line(bottom_right, bottom_left, transform, overlay_context);
|
draw_dashed_line(bottom_right, bottom_left, transform, overlay_context);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use super::graph_modification_utils::merge_layers;
|
use super::graph_modification_utils::merge_layers;
|
||||||
use super::snapping::{SnapCache, SnapCandidatePoint, SnapData, SnapManager, SnappedPoint};
|
use super::snapping::{SnapCache, SnapCandidatePoint, SnapData, SnapManager, SnappedPoint};
|
||||||
use super::utility_functions::{adjust_handle_colinearity, calculate_bezier_bbox, calculate_segment_angle, restore_g1_continuity, restore_previous_handle_position};
|
use super::utility_functions::{adjust_handle_colinearity, calculate_segment_angle, restore_g1_continuity, restore_previous_handle_position};
|
||||||
use crate::consts::HANDLE_LENGTH_FACTOR;
|
use crate::consts::HANDLE_LENGTH_FACTOR;
|
||||||
use crate::messages::portfolio::document::overlays::utility_functions::selected_segments;
|
use crate::messages::portfolio::document::overlays::utility_functions::selected_segments;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||||
@@ -9,12 +9,15 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node
|
|||||||
use crate::messages::preferences::SelectionMode;
|
use crate::messages::preferences::SelectionMode;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::common_functionality::snapping::SnapTypeConfiguration;
|
use crate::messages::tool::common_functionality::snapping::SnapTypeConfiguration;
|
||||||
use crate::messages::tool::common_functionality::utility_functions::{is_intersecting, is_visible_point};
|
use crate::messages::tool::common_functionality::utility_functions::is_visible_point;
|
||||||
use crate::messages::tool::tool_messages::path_tool::{PathOverlayMode, PointSelectState};
|
use crate::messages::tool::tool_messages::path_tool::{PathOverlayMode, PointSelectState};
|
||||||
use bezier_rs::{Bezier, BezierHandles, Subpath, TValue};
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
|
use graphene_std::subpath::{BezierHandles, Subpath};
|
||||||
|
use graphene_std::subpath::{PathSegPoints, pathseg_points};
|
||||||
|
use graphene_std::vector::algorithms::bezpath_algorithms::pathseg_compute_lookup_table;
|
||||||
|
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point, point_to_dvec2};
|
||||||
use graphene_std::vector::{HandleExt, PointId, SegmentId, Vector, VectorModificationType};
|
use graphene_std::vector::{HandleExt, PointId, SegmentId, Vector, VectorModificationType};
|
||||||
|
use kurbo::{Affine, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveNearest, PathSeg, Rect, Shape};
|
||||||
use std::f64::consts::TAU;
|
use std::f64::consts::TAU;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
@@ -26,7 +29,7 @@ pub enum SelectionChange {
|
|||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum SelectionShape<'a> {
|
pub enum SelectionShape<'a> {
|
||||||
Box([DVec2; 2]),
|
Box(Rect),
|
||||||
Lasso(&'a Vec<DVec2>),
|
Lasso(&'a Vec<DVec2>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +188,7 @@ pub type OpposingHandleLengths = HashMap<LayerNodeIdentifier, HashMap<HandleId,
|
|||||||
pub struct ClosestSegment {
|
pub struct ClosestSegment {
|
||||||
layer: LayerNodeIdentifier,
|
layer: LayerNodeIdentifier,
|
||||||
segment: SegmentId,
|
segment: SegmentId,
|
||||||
bezier: Bezier,
|
bezier: PathSeg,
|
||||||
points: [PointId; 2],
|
points: [PointId; 2],
|
||||||
colinear: [Option<HandleId>; 2],
|
colinear: [Option<HandleId>; 2],
|
||||||
t: f64,
|
t: f64,
|
||||||
@@ -205,12 +208,12 @@ impl ClosestSegment {
|
|||||||
self.points
|
self.points
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bezier(&self) -> Bezier {
|
pub fn pathseg(&self) -> PathSeg {
|
||||||
self.bezier
|
self.bezier
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn closest_point_document(&self) -> DVec2 {
|
pub fn closest_point_document(&self) -> DVec2 {
|
||||||
self.bezier.evaluate(TValue::Parametric(self.t))
|
point_to_dvec2(self.bezier.eval(self.t))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn closest_point_to_viewport(&self) -> DVec2 {
|
pub fn closest_point_to_viewport(&self) -> DVec2 {
|
||||||
@@ -219,7 +222,7 @@ impl ClosestSegment {
|
|||||||
|
|
||||||
pub fn closest_point(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> DVec2 {
|
pub fn closest_point(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> DVec2 {
|
||||||
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
||||||
let bezier_point = self.bezier.evaluate(TValue::Parametric(self.t));
|
let bezier_point = point_to_dvec2(self.bezier.eval(self.t));
|
||||||
transform.transform_point2(bezier_point)
|
transform.transform_point2(bezier_point)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,10 +231,10 @@ impl ClosestSegment {
|
|||||||
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
||||||
let layer_mouse_pos = transform.inverse().transform_point2(mouse_position);
|
let layer_mouse_pos = transform.inverse().transform_point2(mouse_position);
|
||||||
|
|
||||||
let t = self.bezier.project(layer_mouse_pos).clamp(0., 1.);
|
let t = self.bezier.nearest(dvec2_to_point(layer_mouse_pos), DEFAULT_ACCURACY).t.clamp(0., 1.);
|
||||||
self.t = t;
|
self.t = t;
|
||||||
|
|
||||||
let bezier_point = self.bezier.evaluate(TValue::Parametric(t));
|
let bezier_point = point_to_dvec2(self.bezier.eval(t));
|
||||||
let bezier_point = transform.transform_point2(bezier_point);
|
let bezier_point = transform.transform_point2(bezier_point);
|
||||||
self.bezier_point_to_viewport = bezier_point;
|
self.bezier_point_to_viewport = bezier_point;
|
||||||
}
|
}
|
||||||
@@ -249,22 +252,24 @@ impl ClosestSegment {
|
|||||||
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
||||||
|
|
||||||
// Split the Bezier at the parameter `t`
|
// Split the Bezier at the parameter `t`
|
||||||
let [first, second] = self.bezier.split(TValue::Parametric(self.t));
|
let first = self.bezier.subsegment(0_f64..self.t);
|
||||||
|
let second = self.bezier.subsegment(self.t..1.);
|
||||||
|
|
||||||
// Transform the handle positions to viewport space
|
// Transform the handle positions to viewport space
|
||||||
let first_handle = first.handle_end().map(|handle| transform.transform_point2(handle));
|
let first_handle = pathseg_points(first).p2.map(|handle| transform.transform_point2(handle));
|
||||||
let second_handle = second.handle_start().map(|handle| transform.transform_point2(handle));
|
let second_handle = pathseg_points(second).p1.map(|handle| transform.transform_point2(handle));
|
||||||
|
|
||||||
(first_handle, second_handle)
|
(first_handle, second_handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn adjusted_insert(&self, responses: &mut VecDeque<Message>) -> (PointId, [SegmentId; 2]) {
|
pub fn adjusted_insert(&self, responses: &mut VecDeque<Message>) -> (PointId, [SegmentId; 2]) {
|
||||||
let layer = self.layer;
|
let layer = self.layer;
|
||||||
let [first, second] = self.bezier.split(TValue::Parametric(self.t));
|
let first = pathseg_points(self.bezier.subsegment(0_f64..self.t));
|
||||||
|
let second = pathseg_points(self.bezier.subsegment(self.t..1.));
|
||||||
|
|
||||||
// Point
|
// Point
|
||||||
let midpoint = PointId::generate();
|
let midpoint = PointId::generate();
|
||||||
let modification_type = VectorModificationType::InsertPoint { id: midpoint, position: first.end };
|
let modification_type = VectorModificationType::InsertPoint { id: midpoint, position: first.p3 };
|
||||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||||
|
|
||||||
// First segment
|
// First segment
|
||||||
@@ -272,7 +277,7 @@ impl ClosestSegment {
|
|||||||
let modification_type = VectorModificationType::InsertSegment {
|
let modification_type = VectorModificationType::InsertSegment {
|
||||||
id: segment_ids[0],
|
id: segment_ids[0],
|
||||||
points: [self.points[0], midpoint],
|
points: [self.points[0], midpoint],
|
||||||
handles: [first.handle_start().map(|handle| handle - first.start), first.handle_end().map(|handle| handle - first.end)],
|
handles: [first.p1.map(|handle| handle - first.p0), first.p2.map(|handle| handle - first.p3)],
|
||||||
};
|
};
|
||||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||||
|
|
||||||
@@ -280,12 +285,12 @@ impl ClosestSegment {
|
|||||||
let modification_type = VectorModificationType::InsertSegment {
|
let modification_type = VectorModificationType::InsertSegment {
|
||||||
id: segment_ids[1],
|
id: segment_ids[1],
|
||||||
points: [midpoint, self.points[1]],
|
points: [midpoint, self.points[1]],
|
||||||
handles: [second.handle_start().map(|handle| handle - second.start), second.handle_end().map(|handle| handle - second.end)],
|
handles: [second.p1.map(|handle| handle - second.p0), second.p2.map(|handle| handle - second.p3)],
|
||||||
};
|
};
|
||||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||||
|
|
||||||
// G1 continuous on new handles
|
// G1 continuous on new handles
|
||||||
if self.bezier.handle_end().is_some() {
|
if pathseg_points(self.bezier).p2.is_some() {
|
||||||
let handles = [HandleId::end(segment_ids[0]), HandleId::primary(segment_ids[1])];
|
let handles = [HandleId::end(segment_ids[0]), HandleId::primary(segment_ids[1])];
|
||||||
let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: true };
|
let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: true };
|
||||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||||
@@ -353,8 +358,8 @@ impl ClosestSegment {
|
|||||||
) -> Option<[Option<HandleId>; 2]> {
|
) -> Option<[Option<HandleId>; 2]> {
|
||||||
let transform = document.metadata().transform_to_viewport_if_feeds(self.layer, &document.network_interface);
|
let transform = document.metadata().transform_to_viewport_if_feeds(self.layer, &document.network_interface);
|
||||||
|
|
||||||
let start = self.bezier.start;
|
let start = point_to_dvec2(self.bezier.start());
|
||||||
let end = self.bezier.end;
|
let end = point_to_dvec2(self.bezier.end());
|
||||||
|
|
||||||
// Apply the drag delta to the segment's handles
|
// Apply the drag delta to the segment's handles
|
||||||
let b = self.bezier_point_to_viewport;
|
let b = self.bezier_point_to_viewport;
|
||||||
@@ -1686,9 +1691,9 @@ impl ShapeState {
|
|||||||
|
|
||||||
let vector = network_interface.compute_modified_vector(layer)?;
|
let vector = network_interface.compute_modified_vector(layer)?;
|
||||||
|
|
||||||
for (segment, mut bezier, start, end) in vector.segment_bezier_iter() {
|
for (segment_id, mut segment, start, end) in vector.segment_iter() {
|
||||||
let t = bezier.project(layer_pos);
|
let t = segment.nearest(dvec2_to_point(layer_pos), DEFAULT_ACCURACY).t;
|
||||||
let layerspace = bezier.evaluate(TValue::Parametric(t));
|
let layerspace = point_to_dvec2(segment.eval(t));
|
||||||
|
|
||||||
let screenspace = transform.transform_point2(layerspace);
|
let screenspace = transform.transform_point2(layerspace);
|
||||||
let distance_squared = screenspace.distance_squared(position);
|
let distance_squared = screenspace.distance_squared(position);
|
||||||
@@ -1697,20 +1702,22 @@ impl ShapeState {
|
|||||||
closest_distance_squared = distance_squared;
|
closest_distance_squared = distance_squared;
|
||||||
|
|
||||||
// Convert to linear if handes are on top of control points
|
// Convert to linear if handes are on top of control points
|
||||||
if let bezier_rs::BezierHandles::Cubic { handle_start, handle_end } = bezier.handles {
|
let PathSegPoints { p0: _, p1, p2, p3: _ } = pathseg_points(segment);
|
||||||
if handle_start.abs_diff_eq(bezier.start(), f64::EPSILON * 100.) && handle_end.abs_diff_eq(bezier.end(), f64::EPSILON * 100.) {
|
if let (Some(p1), Some(p2)) = (p1, p2) {
|
||||||
bezier = Bezier::from_linear_dvec2(bezier.start, bezier.end);
|
let segment_points = pathseg_points(segment);
|
||||||
|
if p1.abs_diff_eq(segment_points.p0, f64::EPSILON * 100.) && p2.abs_diff_eq(segment_points.p3, f64::EPSILON * 100.) {
|
||||||
|
segment = PathSeg::Line(Line::new(segment.start(), segment.end()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let primary_handle = vector.colinear_manipulators.iter().find(|handles| handles.contains(&HandleId::primary(segment)));
|
let primary_handle = vector.colinear_manipulators.iter().find(|handles| handles.contains(&HandleId::primary(segment_id)));
|
||||||
let end_handle = vector.colinear_manipulators.iter().find(|handles| handles.contains(&HandleId::end(segment)));
|
let end_handle = vector.colinear_manipulators.iter().find(|handles| handles.contains(&HandleId::end(segment_id)));
|
||||||
let primary_handle = primary_handle.and_then(|&handles| handles.into_iter().find(|handle| handle.segment != segment));
|
let primary_handle = primary_handle.and_then(|&handles| handles.into_iter().find(|handle| handle.segment != segment_id));
|
||||||
let end_handle = end_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_id));
|
||||||
|
|
||||||
closest = Some(ClosestSegment {
|
closest = Some(ClosestSegment {
|
||||||
segment,
|
segment: segment_id,
|
||||||
bezier,
|
bezier: segment,
|
||||||
points: [start, end],
|
points: [start, end],
|
||||||
colinear: [primary_handle, end_handle],
|
colinear: [primary_handle, end_handle],
|
||||||
t,
|
t,
|
||||||
@@ -2076,21 +2083,24 @@ impl ShapeState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Selection segments
|
// Selection segments
|
||||||
for (id, bezier, _, _) in vector.segment_bezier_iter() {
|
for (id, segment, _, _) in vector.segment_iter() {
|
||||||
if select_segments {
|
if select_segments {
|
||||||
// Select segments if they lie inside the bounding box or lasso polygon
|
// Select segments if they lie inside the bounding box or lasso polygon
|
||||||
let segment_bbox = calculate_bezier_bbox(bezier);
|
let transformed_segment = Affine::new(transform.to_cols_array()) * segment;
|
||||||
let bottom_left = transform.transform_point2(segment_bbox[0]);
|
let segment_bbox = transformed_segment.bounding_box();
|
||||||
let top_right = transform.transform_point2(segment_bbox[1]);
|
|
||||||
|
|
||||||
let select = match selection_shape {
|
let select = match selection_shape {
|
||||||
SelectionShape::Box(quad) => {
|
SelectionShape::Box(rect) => {
|
||||||
let enclosed = quad[0].min(quad[1]).cmple(bottom_left).all() && quad[0].max(quad[1]).cmpge(top_right).all();
|
let enclosed = segment_bbox.contains_rect(rect);
|
||||||
match selection_mode {
|
match selection_mode {
|
||||||
SelectionMode::Enclosed => enclosed,
|
SelectionMode::Enclosed => enclosed,
|
||||||
_ => {
|
_ => {
|
||||||
// Check for intersection with the segment
|
// Check for intersection with the segment
|
||||||
enclosed || is_intersecting(bezier, quad, transform)
|
enclosed
|
||||||
|
|| rect
|
||||||
|
.path_segments(DEFAULT_ACCURACY)
|
||||||
|
.map(|seg| seg.as_line().unwrap())
|
||||||
|
.any(|line| !transformed_segment.intersect_line(line).is_empty())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2098,7 +2108,7 @@ impl ShapeState {
|
|||||||
let polygon = polygon_subpath.as_ref().expect("If `selection_shape` is a polygon then subpath is constructed beforehand.");
|
let polygon = polygon_subpath.as_ref().expect("If `selection_shape` is a polygon then subpath is constructed beforehand.");
|
||||||
|
|
||||||
// Sample 10 points on the bezier and check if all or some lie inside the polygon
|
// Sample 10 points on the bezier and check if all or some lie inside the polygon
|
||||||
let points = bezier.compute_lookup_table(Some(10), None);
|
let points = pathseg_compute_lookup_table(segment, Some(10), false);
|
||||||
match selection_mode {
|
match selection_mode {
|
||||||
SelectionMode::Enclosed => points.map(|p| transform.transform_point2(p)).all(|p| polygon.contains_point(p)),
|
SelectionMode::Enclosed => points.map(|p| transform.transform_point2(p)).all(|p| polygon.contains_point(p)),
|
||||||
_ => points.map(|p| transform.transform_point2(p)).any(|p| polygon.contains_point(p)),
|
_ => points.map(|p| transform.transform_point2(p)).any(|p| polygon.contains_point(p)),
|
||||||
@@ -2111,13 +2121,15 @@ impl ShapeState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let segment_points = pathseg_points(segment);
|
||||||
|
|
||||||
// Selecting handles
|
// Selecting handles
|
||||||
for (position, id) in [(bezier.handle_start(), ManipulatorPointId::PrimaryHandle(id)), (bezier.handle_end(), ManipulatorPointId::EndHandle(id))] {
|
for (position, id) in [(segment_points.p1, ManipulatorPointId::PrimaryHandle(id)), (segment_points.p2, ManipulatorPointId::EndHandle(id))] {
|
||||||
let Some(position) = position else { continue };
|
let Some(position) = position else { continue };
|
||||||
let transformed_position = transform.transform_point2(position);
|
let transformed_position = transform.transform_point2(position);
|
||||||
|
|
||||||
let select = match selection_shape {
|
let select = match selection_shape {
|
||||||
SelectionShape::Box(quad) => quad[0].min(quad[1]).cmple(transformed_position).all() && quad[0].max(quad[1]).cmpge(transformed_position).all(),
|
SelectionShape::Box(rect) => rect.contains(dvec2_to_point(transformed_position)),
|
||||||
SelectionShape::Lasso(_) => polygon_subpath
|
SelectionShape::Lasso(_) => polygon_subpath
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("If `selection_shape` is a polygon then subpath is constructed beforehand.")
|
.expect("If `selection_shape` is a polygon then subpath is constructed beforehand.")
|
||||||
@@ -2139,7 +2151,7 @@ impl ShapeState {
|
|||||||
let transformed_position = transform.transform_point2(position);
|
let transformed_position = transform.transform_point2(position);
|
||||||
|
|
||||||
let select = match selection_shape {
|
let select = match selection_shape {
|
||||||
SelectionShape::Box(quad) => quad[0].min(quad[1]).cmple(transformed_position).all() && quad[0].max(quad[1]).cmpge(transformed_position).all(),
|
SelectionShape::Box(rect) => rect.contains(dvec2_to_point(transformed_position)),
|
||||||
SelectionShape::Lasso(_) => polygon_subpath
|
SelectionShape::Lasso(_) => polygon_subpath
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.expect("If `selection_shape` is a polygon then subpath is constructed beforehand.")
|
.expect("If `selection_shape` is a polygon then subpath is constructed beforehand.")
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
|||||||
use crate::messages::tool::common_functionality::transformation_cage::BoundingBoxManager;
|
use crate::messages::tool::common_functionality::transformation_cage::BoundingBoxManager;
|
||||||
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
||||||
use crate::messages::tool::utility_types::*;
|
use crate::messages::tool::utility_types::*;
|
||||||
use bezier_rs::Subpath;
|
|
||||||
use glam::{DAffine2, DMat2, DVec2};
|
use glam::{DAffine2, DMat2, DVec2};
|
||||||
use graph_craft::document::NodeInput;
|
use graph_craft::document::NodeInput;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
|
use graphene_std::subpath::{self, Subpath};
|
||||||
use graphene_std::vector::click_target::ClickTargetType;
|
use graphene_std::vector::click_target::ClickTargetType;
|
||||||
use graphene_std::vector::misc::{ArcType, dvec2_to_point};
|
use graphene_std::vector::misc::{ArcType, dvec2_to_point};
|
||||||
use kurbo::{BezPath, PathEl, Shape};
|
use kurbo::{BezPath, PathEl, Shape};
|
||||||
@@ -363,9 +363,9 @@ pub fn arc_outline(layer: Option<LayerNodeIdentifier>, document: &DocumentMessag
|
|||||||
start_angle / 360. * std::f64::consts::TAU,
|
start_angle / 360. * std::f64::consts::TAU,
|
||||||
sweep_angle / 360. * std::f64::consts::TAU,
|
sweep_angle / 360. * std::f64::consts::TAU,
|
||||||
match arc_type {
|
match arc_type {
|
||||||
ArcType::Open => bezier_rs::ArcType::Open,
|
ArcType::Open => subpath::ArcType::Open,
|
||||||
ArcType::Closed => bezier_rs::ArcType::Closed,
|
ArcType::Closed => subpath::ArcType::Closed,
|
||||||
ArcType::PieSlice => bezier_rs::ArcType::PieSlice,
|
ArcType::PieSlice => subpath::ArcType::PieSlice,
|
||||||
},
|
},
|
||||||
))];
|
))];
|
||||||
let viewport = document.metadata().transform_to_viewport(layer);
|
let viewport = document.metadata().transform_to_viewport(layer);
|
||||||
|
|||||||
@@ -10,14 +10,16 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
|||||||
use crate::messages::portfolio::document::utility_types::misc::{GridSnapTarget, PathSnapTarget, SnapTarget};
|
use crate::messages::portfolio::document::utility_types::misc::{GridSnapTarget, PathSnapTarget, SnapTarget};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
pub use alignment_snapper::*;
|
pub use alignment_snapper::*;
|
||||||
use bezier_rs::TValue;
|
|
||||||
pub use distribution_snapper::*;
|
pub use distribution_snapper::*;
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphene_std::renderer::Quad;
|
use graphene_std::renderer::Quad;
|
||||||
use graphene_std::renderer::Rect;
|
use graphene_std::renderer::Rect;
|
||||||
use graphene_std::vector::NoHashBuilder;
|
use graphene_std::vector::NoHashBuilder;
|
||||||
use graphene_std::vector::PointId;
|
use graphene_std::vector::PointId;
|
||||||
|
use graphene_std::vector::algorithms::intersection::filtered_segment_intersections;
|
||||||
|
use graphene_std::vector::misc::point_to_dvec2;
|
||||||
pub use grid_snapper::*;
|
pub use grid_snapper::*;
|
||||||
|
use kurbo::ParamCurve;
|
||||||
pub use layer_snapper::*;
|
pub use layer_snapper::*;
|
||||||
pub use snap_results::*;
|
pub use snap_results::*;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
@@ -81,6 +83,7 @@ impl SnapConstraint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn snap_tolerance(document: &DocumentMessageHandler) -> f64 {
|
pub fn snap_tolerance(document: &DocumentMessageHandler) -> f64 {
|
||||||
document.snapping_state.tolerance / document.document_ptz.zoom()
|
document.snapping_state.tolerance / document.document_ptz.zoom()
|
||||||
}
|
}
|
||||||
@@ -127,13 +130,16 @@ fn get_closest_point(points: Vec<SnappedPoint>) -> Option<SnappedPoint> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_closest_curve(curves: &[SnappedCurve], exclude_paths: bool) -> Option<&SnappedPoint> {
|
fn get_closest_curve(curves: &[SnappedCurve], exclude_paths: bool) -> Option<&SnappedPoint> {
|
||||||
let keep_curve = |curve: &&SnappedCurve| !exclude_paths || curve.point.target != SnapTarget::Path(PathSnapTarget::AlongPath);
|
let keep_curve = |curve: &&SnappedCurve| !exclude_paths || curve.point.target != SnapTarget::Path(PathSnapTarget::AlongPath);
|
||||||
curves.iter().filter(keep_curve).map(|curve| &curve.point).min_by(compare_points)
|
curves.iter().filter(keep_curve).map(|curve| &curve.point).min_by(compare_points)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_closest_line(lines: &[SnappedLine]) -> Option<&SnappedPoint> {
|
fn get_closest_line(lines: &[SnappedLine]) -> Option<&SnappedPoint> {
|
||||||
lines.iter().map(|curve| &curve.point).min_by(compare_points)
|
lines.iter().map(|curve| &curve.point).min_by(compare_points)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_closest_intersection(snap_to: DVec2, curves: &[SnappedCurve]) -> Option<SnappedPoint> {
|
fn get_closest_intersection(snap_to: DVec2, curves: &[SnappedCurve]) -> Option<SnappedPoint> {
|
||||||
let mut best = None;
|
let mut best = None;
|
||||||
for curve_i in curves {
|
for curve_i in curves {
|
||||||
@@ -141,8 +147,8 @@ fn get_closest_intersection(snap_to: DVec2, curves: &[SnappedCurve]) -> Option<S
|
|||||||
if curve_i.start == curve_j.start && curve_i.layer == curve_j.layer {
|
if curve_i.start == curve_j.start && curve_i.layer == curve_j.layer {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for curve_i_t in curve_i.document_curve.intersections(&curve_j.document_curve, None, None) {
|
for curve_i_t in filtered_segment_intersections(curve_i.document_curve, curve_j.document_curve, None, None) {
|
||||||
let snapped_point_document = curve_i.document_curve.evaluate(TValue::Parametric(curve_i_t));
|
let snapped_point_document = point_to_dvec2(curve_i.document_curve.eval(curve_i_t));
|
||||||
let distance = snap_to.distance(snapped_point_document);
|
let distance = snap_to.distance(snapped_point_document);
|
||||||
let i_closer = curve_i.point.distance < curve_j.point.distance;
|
let i_closer = curve_i.point.distance < curve_j.point.distance;
|
||||||
let close = if i_closer { curve_i } else { curve_j };
|
let close = if i_closer { curve_i } else { curve_j };
|
||||||
@@ -165,6 +171,7 @@ fn get_closest_intersection(snap_to: DVec2, curves: &[SnappedCurve]) -> Option<S
|
|||||||
}
|
}
|
||||||
best
|
best
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_grid_intersection(snap_to: DVec2, lines: &[SnappedLine]) -> Option<SnappedPoint> {
|
fn get_grid_intersection(snap_to: DVec2, lines: &[SnappedLine]) -> Option<SnappedPoint> {
|
||||||
let mut best = None;
|
let mut best = None;
|
||||||
for line_i in lines {
|
for line_i in lines {
|
||||||
@@ -237,6 +244,7 @@ impl<'a> SnapData<'a> {
|
|||||||
self.node_snap_cache.is_some_and(|cache| !cache.manipulators.is_empty())
|
self.node_snap_cache.is_some_and(|cache| !cache.manipulators.is_empty())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SnapManager {
|
impl SnapManager {
|
||||||
pub fn update_indicator(&mut self, snapped_point: SnappedPoint) {
|
pub fn update_indicator(&mut self, snapped_point: SnappedPoint) {
|
||||||
self.indicator = snapped_point.is_snapped().then_some(snapped_point);
|
self.indicator = snapped_point.is_snapped().then_some(snapped_point);
|
||||||
|
|||||||
@@ -3,11 +3,17 @@ use crate::consts::HIDE_HANDLE_DISTANCE;
|
|||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::misc::*;
|
use crate::messages::portfolio::document::utility_types::misc::*;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use bezier_rs::{Bezier, Identifier, Subpath, TValue};
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graphene_std::math::math_ext::QuadExt;
|
use graphene_std::math::math_ext::QuadExt;
|
||||||
use graphene_std::renderer::Quad;
|
use graphene_std::renderer::Quad;
|
||||||
|
use graphene_std::subpath::pathseg_points;
|
||||||
|
use graphene_std::subpath::{Identifier, ManipulatorGroup, Subpath};
|
||||||
use graphene_std::vector::PointId;
|
use graphene_std::vector::PointId;
|
||||||
|
use graphene_std::vector::algorithms::bezpath_algorithms::{pathseg_normals_to_point, pathseg_tangents_to_point};
|
||||||
|
use graphene_std::vector::algorithms::intersection::filtered_segment_intersections;
|
||||||
|
use graphene_std::vector::misc::dvec2_to_point;
|
||||||
|
use graphene_std::vector::misc::point_to_dvec2;
|
||||||
|
use kurbo::{Affine, DEFAULT_ACCURACY, Nearest, ParamCurve, ParamCurveNearest, PathSeg};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct LayerSnapper {
|
pub struct LayerSnapper {
|
||||||
@@ -37,7 +43,7 @@ impl LayerSnapper {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for document_curve in bounds.bezier_lines() {
|
for document_curve in bounds.to_lines() {
|
||||||
self.paths_to_snap.push(SnapCandidatePath {
|
self.paths_to_snap.push(SnapCandidatePath {
|
||||||
document_curve,
|
document_curve,
|
||||||
layer,
|
layer,
|
||||||
@@ -70,7 +76,7 @@ impl LayerSnapper {
|
|||||||
if document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::IntersectionPoint)) || document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::AlongPath)) {
|
if document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::IntersectionPoint)) || document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::AlongPath)) {
|
||||||
for subpath in document.metadata().layer_outline(layer) {
|
for subpath in document.metadata().layer_outline(layer) {
|
||||||
for (start_index, curve) in subpath.iter().enumerate() {
|
for (start_index, curve) in subpath.iter().enumerate() {
|
||||||
let document_curve = curve.apply_transformation(|p| transform.transform_point2(p));
|
let document_curve = Affine::new(transform.to_cols_array()) * curve;
|
||||||
let start = subpath.manipulator_groups()[start_index].id;
|
let start = subpath.manipulator_groups()[start_index].id;
|
||||||
if snap_data.ignore_manipulator(layer, start) || snap_data.ignore_manipulator(layer, subpath.manipulator_groups()[(start_index + 1) % subpath.len()].id) {
|
if snap_data.ignore_manipulator(layer, start) || snap_data.ignore_manipulator(layer, subpath.manipulator_groups()[(start_index + 1) % subpath.len()].id) {
|
||||||
continue;
|
continue;
|
||||||
@@ -98,13 +104,12 @@ impl LayerSnapper {
|
|||||||
|
|
||||||
for path in &self.paths_to_snap {
|
for path in &self.paths_to_snap {
|
||||||
// Skip very short paths
|
// Skip very short paths
|
||||||
if path.document_curve.start.distance_squared(path.document_curve.end) < tolerance * tolerance * 2. {
|
if path.document_curve.start().distance_squared(path.document_curve.end()) < tolerance * tolerance * 2. {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let time = path.document_curve.project(point.document_point);
|
let Nearest { distance_sq, t } = path.document_curve.nearest(dvec2_to_point(point.document_point), DEFAULT_ACCURACY);
|
||||||
let snapped_point_document = path.document_curve.evaluate(bezier_rs::TValue::Parametric(time));
|
let snapped_point_document = point_to_dvec2(path.document_curve.eval(t));
|
||||||
|
let distance = distance_sq.sqrt();
|
||||||
let distance = snapped_point_document.distance(point.document_point);
|
|
||||||
|
|
||||||
if distance < tolerance {
|
if distance < tolerance {
|
||||||
snap_results.curves.push(SnappedCurve {
|
snap_results.curves.push(SnappedCurve {
|
||||||
@@ -144,8 +149,8 @@ impl LayerSnapper {
|
|||||||
|
|
||||||
for path in &self.paths_to_snap {
|
for path in &self.paths_to_snap {
|
||||||
for constraint_path in constraint_path.iter() {
|
for constraint_path in constraint_path.iter() {
|
||||||
for time in path.document_curve.intersections(&constraint_path, None, None) {
|
for time in filtered_segment_intersections(path.document_curve, constraint_path, None, None) {
|
||||||
let snapped_point_document = path.document_curve.evaluate(bezier_rs::TValue::Parametric(time));
|
let snapped_point_document = point_to_dvec2(path.document_curve.eval(time));
|
||||||
|
|
||||||
let distance = snapped_point_document.distance(point.document_point);
|
let distance = snapped_point_document.distance(point.document_point);
|
||||||
|
|
||||||
@@ -266,8 +271,8 @@ impl LayerSnapper {
|
|||||||
fn normals_and_tangents(path: &SnapCandidatePath, normals: bool, tangents: bool, point: &SnapCandidatePoint, tolerance: f64, snap_results: &mut SnapResults) {
|
fn normals_and_tangents(path: &SnapCandidatePath, normals: bool, tangents: bool, point: &SnapCandidatePoint, tolerance: f64, snap_results: &mut SnapResults) {
|
||||||
if normals && path.bounds.is_none() {
|
if normals && path.bounds.is_none() {
|
||||||
for &neighbor in &point.neighbors {
|
for &neighbor in &point.neighbors {
|
||||||
for t in path.document_curve.normals_to_point(neighbor) {
|
for t in pathseg_normals_to_point(path.document_curve, dvec2_to_point(neighbor)) {
|
||||||
let normal_point = path.document_curve.evaluate(TValue::Parametric(t));
|
let normal_point = point_to_dvec2(path.document_curve.eval(t));
|
||||||
let distance = normal_point.distance(point.document_point);
|
let distance = normal_point.distance(point.document_point);
|
||||||
if distance > tolerance {
|
if distance > tolerance {
|
||||||
continue;
|
continue;
|
||||||
@@ -287,8 +292,8 @@ fn normals_and_tangents(path: &SnapCandidatePath, normals: bool, tangents: bool,
|
|||||||
}
|
}
|
||||||
if tangents && path.bounds.is_none() {
|
if tangents && path.bounds.is_none() {
|
||||||
for &neighbor in &point.neighbors {
|
for &neighbor in &point.neighbors {
|
||||||
for t in path.document_curve.tangents_to_point(neighbor) {
|
for t in pathseg_tangents_to_point(path.document_curve, dvec2_to_point(neighbor)) {
|
||||||
let tangent_point = path.document_curve.evaluate(TValue::Parametric(t));
|
let tangent_point = point_to_dvec2(path.document_curve.eval(t));
|
||||||
let distance = tangent_point.distance(point.document_point);
|
let distance = tangent_point.distance(point.document_point);
|
||||||
if distance > tolerance {
|
if distance > tolerance {
|
||||||
continue;
|
continue;
|
||||||
@@ -310,7 +315,7 @@ fn normals_and_tangents(path: &SnapCandidatePath, normals: bool, tangents: bool,
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct SnapCandidatePath {
|
struct SnapCandidatePath {
|
||||||
document_curve: Bezier,
|
document_curve: PathSeg,
|
||||||
layer: LayerNodeIdentifier,
|
layer: LayerNodeIdentifier,
|
||||||
start: PointId,
|
start: PointId,
|
||||||
target: SnapTarget,
|
target: SnapTarget,
|
||||||
@@ -440,12 +445,13 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<Poin
|
|||||||
if points.len() >= crate::consts::MAX_LAYER_SNAP_POINTS {
|
if points.len() >= crate::consts::MAX_LAYER_SNAP_POINTS {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let curve = pathseg_points(curve);
|
||||||
|
|
||||||
let in_handle = curve.handle_start().map(|handle| handle - curve.start).filter(handle_not_under(to_document));
|
let in_handle = curve.p1.map(|handle| handle - curve.p0).filter(handle_not_under(to_document));
|
||||||
let out_handle = curve.handle_end().map(|handle| handle - curve.end).filter(handle_not_under(to_document));
|
let out_handle = curve.p2.map(|handle| handle - curve.p3).filter(handle_not_under(to_document));
|
||||||
if in_handle.is_none() && out_handle.is_none() {
|
if in_handle.is_none() && out_handle.is_none() {
|
||||||
points.push(SnapCandidatePoint::new(
|
points.push(SnapCandidatePoint::new(
|
||||||
to_document.transform_point2(curve.start() * 0.5 + curve.end * 0.5),
|
to_document.transform_point2(curve.p0 * 0.5 + curve.p3 * 0.5),
|
||||||
SnapSource::Path(PathSnapSource::LineMidpoint),
|
SnapSource::Path(PathSnapSource::LineMidpoint),
|
||||||
SnapTarget::Path(PathSnapTarget::LineMidpoint),
|
SnapTarget::Path(PathSnapTarget::LineMidpoint),
|
||||||
Some(layer),
|
Some(layer),
|
||||||
@@ -487,7 +493,7 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<Poin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn are_manipulator_handles_colinear(manipulators: &bezier_rs::ManipulatorGroup<PointId>, to_document: DAffine2, subpath: &Subpath<PointId>, index: usize) -> bool {
|
pub fn are_manipulator_handles_colinear(manipulators: &ManipulatorGroup<PointId>, to_document: DAffine2, subpath: &Subpath<PointId>, index: usize) -> bool {
|
||||||
let anchor = manipulators.anchor;
|
let anchor = manipulators.anchor;
|
||||||
let handle_in = manipulators.in_handle.map(|handle| anchor - handle).filter(handle_not_under(to_document));
|
let handle_in = manipulators.in_handle.map(|handle| anchor - handle).filter(handle_not_under(to_document));
|
||||||
let handle_out = manipulators.out_handle.map(|handle| handle - anchor).filter(handle_not_under(to_document));
|
let handle_out = manipulators.out_handle.map(|handle| handle - anchor).filter(handle_not_under(to_document));
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ use super::DistributionMatch;
|
|||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::misc::{DistributionSnapTarget, SnapSource, SnapTarget};
|
use crate::messages::portfolio::document::utility_types::misc::{DistributionSnapTarget, SnapSource, SnapTarget};
|
||||||
use crate::messages::tool::common_functionality::snapping::SnapCandidatePoint;
|
use crate::messages::tool::common_functionality::snapping::SnapCandidatePoint;
|
||||||
use bezier_rs::Bezier;
|
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use graphene_std::renderer::Quad;
|
use graphene_std::renderer::Quad;
|
||||||
use graphene_std::renderer::Rect;
|
use graphene_std::renderer::Rect;
|
||||||
use graphene_std::vector::PointId;
|
use graphene_std::vector::PointId;
|
||||||
|
use kurbo::PathSeg;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
@@ -120,5 +120,5 @@ pub struct SnappedCurve {
|
|||||||
pub layer: LayerNodeIdentifier,
|
pub layer: LayerNodeIdentifier,
|
||||||
pub start: PointId,
|
pub start: PointId,
|
||||||
pub point: SnappedPoint,
|
pub point: SnappedPoint,
|
||||||
pub document_curve: Bezier,
|
pub document_curve: PathSeg,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,16 +9,17 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{Node
|
|||||||
use crate::messages::tool::common_functionality::transformation_cage::SelectedEdges;
|
use crate::messages::tool::common_functionality::transformation_cage::SelectedEdges;
|
||||||
use crate::messages::tool::tool_messages::path_tool::PathOverlayMode;
|
use crate::messages::tool::tool_messages::path_tool::PathOverlayMode;
|
||||||
use crate::messages::tool::utility_types::ToolType;
|
use crate::messages::tool::utility_types::ToolType;
|
||||||
use bezier_rs::{Bezier, BezierHandles};
|
|
||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graph_craft::concrete;
|
use graph_craft::concrete;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graphene_std::renderer::Quad;
|
use graphene_std::renderer::Quad;
|
||||||
|
use graphene_std::subpath::{Bezier, BezierHandles};
|
||||||
use graphene_std::table::Table;
|
use graphene_std::table::Table;
|
||||||
use graphene_std::text::{FontCache, load_font};
|
use graphene_std::text::{FontCache, load_font};
|
||||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
|
use graphene_std::vector::algorithms::bezpath_algorithms::pathseg_compute_lookup_table;
|
||||||
|
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point};
|
||||||
use graphene_std::vector::{HandleExt, PointId, SegmentId, Vector, VectorModification, VectorModificationType};
|
use graphene_std::vector::{HandleExt, PointId, SegmentId, Vector, VectorModification, VectorModificationType};
|
||||||
use kurbo::{CubicBez, Line, ParamCurveExtrema, PathSeg, Point, QuadBez};
|
use kurbo::{CubicBez, DEFAULT_ACCURACY, Line, ParamCurve, PathSeg, Point, QuadBez, Shape};
|
||||||
|
|
||||||
/// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable.
|
/// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable.
|
||||||
pub fn should_extend(
|
pub fn should_extend(
|
||||||
@@ -208,25 +209,6 @@ pub fn is_visible_point(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Function to find the bounding box of bezier (uses method from kurbo)
|
|
||||||
pub fn calculate_bezier_bbox(bezier: Bezier) -> [DVec2; 2] {
|
|
||||||
let start = Point::new(bezier.start.x, bezier.start.y);
|
|
||||||
let end = Point::new(bezier.end.x, bezier.end.y);
|
|
||||||
let bbox = match bezier.handles {
|
|
||||||
BezierHandles::Cubic { handle_start, handle_end } => {
|
|
||||||
let p1 = Point::new(handle_start.x, handle_start.y);
|
|
||||||
let p2 = Point::new(handle_end.x, handle_end.y);
|
|
||||||
CubicBez::new(start, p1, p2, end).bounding_box()
|
|
||||||
}
|
|
||||||
BezierHandles::Quadratic { handle } => {
|
|
||||||
let p1 = Point::new(handle.x, handle.y);
|
|
||||||
QuadBez::new(start, p1, end).bounding_box()
|
|
||||||
}
|
|
||||||
BezierHandles::Linear => Line::new(start, end).bounding_box(),
|
|
||||||
};
|
|
||||||
[DVec2::new(bbox.x0, bbox.y0), DVec2::new(bbox.x1, bbox.y1)]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_intersecting(bezier: Bezier, quad: [DVec2; 2], transform: DAffine2) -> bool {
|
pub fn is_intersecting(bezier: Bezier, quad: [DVec2; 2], transform: DAffine2) -> bool {
|
||||||
let to_layerspace = transform.inverse();
|
let to_layerspace = transform.inverse();
|
||||||
let quad = [to_layerspace.transform_point2(quad[0]), to_layerspace.transform_point2(quad[1])];
|
let quad = [to_layerspace.transform_point2(quad[0]), to_layerspace.transform_point2(quad[1])];
|
||||||
@@ -496,19 +478,19 @@ pub fn log_optimization(a: f64, b: f64, p1: DVec2, p3: DVec2, d1: DVec2, d2: DVe
|
|||||||
let c1 = p1 + d1 * start_handle_length;
|
let c1 = p1 + d1 * start_handle_length;
|
||||||
let c2 = p3 + d2 * end_handle_length;
|
let c2 = p3 + d2 * end_handle_length;
|
||||||
|
|
||||||
let new_curve = Bezier::from_cubic_coordinates(p1.x, p1.y, c1.x, c1.y, c2.x, c2.y, p3.x, p3.y);
|
let new_curve = PathSeg::Cubic(CubicBez::new(Point::new(p1.x, p1.y), Point::new(c1.x, c1.y), Point::new(c2.x, c2.y), Point::new(p3.x, p3.y)));
|
||||||
|
|
||||||
// Sample 2*n points from new curve and get the L2 metric between all of points
|
// Sample 2*n points from new curve and get the L2 metric between all of points
|
||||||
let points = new_curve.compute_lookup_table(Some(2 * n), None).collect::<Vec<_>>();
|
let points = pathseg_compute_lookup_table(new_curve, Some(2 * n), false);
|
||||||
|
|
||||||
let dist = points1.iter().zip(points.iter()).map(|(p1, p2)| (p1.x - p2.x).powi(2) + (p1.y - p2.y).powi(2)).sum::<f64>();
|
let dist = points1.iter().zip(points).map(|(p1, p2)| (p1.x - p2.x).powi(2) + (p1.y - p2.y).powi(2)).sum::<f64>();
|
||||||
|
|
||||||
dist / (2 * n) as f64
|
dist / (2 * n) as f64
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calculates optimal handle lengths with adam optimization.
|
/// Calculates optimal handle lengths with adam optimization.
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn find_two_param_best_approximate(p1: DVec2, p3: DVec2, d1: DVec2, d2: DVec2, min_len1: f64, min_len2: f64, farther_segment: Bezier, other_segment: Bezier) -> (DVec2, DVec2) {
|
pub fn find_two_param_best_approximate(p1: DVec2, p3: DVec2, d1: DVec2, d2: DVec2, min_len1: f64, min_len2: f64, further_segment: PathSeg, other_segment: PathSeg) -> (DVec2, DVec2) {
|
||||||
let h = 1e-6;
|
let h = 1e-6;
|
||||||
let tol = 1e-6;
|
let tol = 1e-6;
|
||||||
let max_iter = 200;
|
let max_iter = 200;
|
||||||
@@ -530,21 +512,25 @@ pub fn find_two_param_best_approximate(p1: DVec2, p3: DVec2, d1: DVec2, d2: DVec
|
|||||||
|
|
||||||
let n = 20;
|
let n = 20;
|
||||||
|
|
||||||
let farther_segment = if farther_segment.start.distance(p1) >= f64::EPSILON {
|
let further_segment = if further_segment.start().distance(dvec2_to_point(p1)) >= f64::EPSILON {
|
||||||
farther_segment.reverse()
|
further_segment.reverse()
|
||||||
} else {
|
} else {
|
||||||
farther_segment
|
further_segment
|
||||||
};
|
};
|
||||||
|
|
||||||
let other_segment = if other_segment.end.distance(p3) >= f64::EPSILON { other_segment.reverse() } else { other_segment };
|
let other_segment = if other_segment.end().distance(dvec2_to_point(p3)) >= f64::EPSILON {
|
||||||
|
other_segment.reverse()
|
||||||
|
} else {
|
||||||
|
other_segment
|
||||||
|
};
|
||||||
|
|
||||||
// Now we sample points proportional to the lengths of the beziers
|
// Now we sample points proportional to the lengths of the beziers
|
||||||
let l1 = farther_segment.length(None);
|
let l1 = further_segment.perimeter(DEFAULT_ACCURACY);
|
||||||
let l2 = other_segment.length(None);
|
let l2 = other_segment.perimeter(DEFAULT_ACCURACY);
|
||||||
let ratio = l1 / (l1 + l2);
|
let ratio = l1 / (l1 + l2);
|
||||||
let n_points1 = ((2 * n) as f64 * ratio).floor() as usize;
|
let n_points1 = ((2 * n) as f64 * ratio).floor() as usize;
|
||||||
let mut points1 = farther_segment.compute_lookup_table(Some(n_points1), None).collect::<Vec<_>>();
|
let mut points1 = pathseg_compute_lookup_table(further_segment, Some(n_points1), false).collect::<Vec<_>>();
|
||||||
let mut points2 = other_segment.compute_lookup_table(Some(n), None).collect::<Vec<_>>();
|
let mut points2 = pathseg_compute_lookup_table(other_segment, Some(n), false).collect::<Vec<_>>();
|
||||||
points1.append(&mut points2);
|
points1.append(&mut points2);
|
||||||
|
|
||||||
let f = |a: f64, b: f64| -> f64 { log_optimization(a, b, p1, p3, d1, d2, &points1, n) };
|
let f = |a: f64, b: f64| -> f64 { log_optimization(a, b, p1, p3, d1, d2, &points1, n) };
|
||||||
|
|||||||
@@ -156,6 +156,9 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
|
|||||||
// Subscribe new tool
|
// Subscribe new tool
|
||||||
tool_data.tools.get(&tool_type).unwrap().activate(responses);
|
tool_data.tools.get(&tool_type).unwrap().activate(responses);
|
||||||
|
|
||||||
|
// Re-add the artboard overlay provider when tools are reactivated
|
||||||
|
responses.add(OverlaysMessage::AddProvider(ARTBOARD_OVERLAY_PROVIDER));
|
||||||
|
|
||||||
// Send the SelectionChanged message to the active tool, this will ensure the selection is updated
|
// Send the SelectionChanged message to the active tool, this will ensure the selection is updated
|
||||||
responses.add(BroadcastEvent::SelectionChanged);
|
responses.add(BroadcastEvent::SelectionChanged);
|
||||||
|
|
||||||
|
|||||||
@@ -204,7 +204,6 @@ impl SelectedGradient {
|
|||||||
|
|
||||||
/// Update the layer fill to the current gradient
|
/// Update the layer fill to the current gradient
|
||||||
pub fn render_gradient(&mut self, responses: &mut VecDeque<Message>) {
|
pub fn render_gradient(&mut self, responses: &mut VecDeque<Message>) {
|
||||||
self.gradient.transform = self.transform;
|
|
||||||
if let Some(layer) = self.layer {
|
if let Some(layer) = self.layer {
|
||||||
responses.add(GraphOperationMessage::FillSet {
|
responses.add(GraphOperationMessage::FillSet {
|
||||||
layer,
|
layer,
|
||||||
@@ -436,14 +435,7 @@ impl Fsm for GradientToolFsmState {
|
|||||||
gradient.clone()
|
gradient.clone()
|
||||||
} else {
|
} else {
|
||||||
// Generate a new gradient
|
// Generate a new gradient
|
||||||
Gradient::new(
|
Gradient::new(DVec2::ZERO, global_tool_data.secondary_color, DVec2::ONE, global_tool_data.primary_color, tool_options.gradient_type)
|
||||||
DVec2::ZERO,
|
|
||||||
global_tool_data.secondary_color,
|
|
||||||
DVec2::ONE,
|
|
||||||
global_tool_data.primary_color,
|
|
||||||
DAffine2::IDENTITY,
|
|
||||||
tool_options.gradient_type,
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
let selected_gradient = SelectedGradient::new(gradient, layer, document).with_gradient_start(input.mouse.position);
|
let selected_gradient = SelectedGradient::new(gradient, layer, document).with_gradient_start(input.mouse.position);
|
||||||
|
|
||||||
|
|||||||
@@ -21,14 +21,16 @@ use crate::messages::tool::common_functionality::shape_editor::{
|
|||||||
};
|
};
|
||||||
use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
|
use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
|
||||||
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate, make_path_editable_is_allowed};
|
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate, make_path_editable_is_allowed};
|
||||||
use bezier_rs::{Bezier, BezierHandles, TValue};
|
|
||||||
use graphene_std::Color;
|
use graphene_std::Color;
|
||||||
use graphene_std::renderer::Quad;
|
use graphene_std::renderer::Quad;
|
||||||
|
use graphene_std::subpath::pathseg_points;
|
||||||
use graphene_std::transform::ReferencePoint;
|
use graphene_std::transform::ReferencePoint;
|
||||||
use graphene_std::uuid::NodeId;
|
use graphene_std::uuid::NodeId;
|
||||||
|
use graphene_std::vector::algorithms::util::pathseg_tangent;
|
||||||
use graphene_std::vector::click_target::ClickTargetType;
|
use graphene_std::vector::click_target::ClickTargetType;
|
||||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
|
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point, point_to_dvec2};
|
||||||
use graphene_std::vector::{HandleExt, NoHashBuilder, PointId, SegmentId, Vector, VectorModificationType};
|
use graphene_std::vector::{HandleExt, NoHashBuilder, PointId, SegmentId, Vector, VectorModificationType};
|
||||||
|
use kurbo::{DEFAULT_ACCURACY, ParamCurve, ParamCurveNearest, PathSeg, Rect};
|
||||||
use std::vec;
|
use std::vec;
|
||||||
|
|
||||||
#[derive(Default, ExtractField)]
|
#[derive(Default, ExtractField)]
|
||||||
@@ -126,6 +128,7 @@ pub enum PathToolMessage {
|
|||||||
overlay_context: OverlayContext,
|
overlay_context: OverlayContext,
|
||||||
},
|
},
|
||||||
MergeSelectedPoints,
|
MergeSelectedPoints,
|
||||||
|
StartSlidingPoint,
|
||||||
Copy {
|
Copy {
|
||||||
clipboard: Clipboard,
|
clipboard: Clipboard,
|
||||||
},
|
},
|
||||||
@@ -429,6 +432,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Path
|
|||||||
ClosePath,
|
ClosePath,
|
||||||
PointerMove,
|
PointerMove,
|
||||||
MergeSelectedPoints,
|
MergeSelectedPoints,
|
||||||
|
StartSlidingPoint,
|
||||||
Copy,
|
Copy,
|
||||||
Cut,
|
Cut,
|
||||||
DeleteSelected,
|
DeleteSelected,
|
||||||
@@ -447,6 +451,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Path
|
|||||||
BreakPath,
|
BreakPath,
|
||||||
DeleteAndBreakPath,
|
DeleteAndBreakPath,
|
||||||
SwapSelectedHandles,
|
SwapSelectedHandles,
|
||||||
|
StartSlidingPoint,
|
||||||
Copy,
|
Copy,
|
||||||
Cut,
|
Cut,
|
||||||
DeleteSelected,
|
DeleteSelected,
|
||||||
@@ -465,6 +470,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Path
|
|||||||
DeleteAndBreakPath,
|
DeleteAndBreakPath,
|
||||||
Escape,
|
Escape,
|
||||||
RightClick,
|
RightClick,
|
||||||
|
StartSlidingPoint,
|
||||||
TogglePointEditing,
|
TogglePointEditing,
|
||||||
ToggleSegmentEditing
|
ToggleSegmentEditing
|
||||||
),
|
),
|
||||||
@@ -505,7 +511,7 @@ pub enum PointSelectState {
|
|||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct SlidingSegmentData {
|
pub struct SlidingSegmentData {
|
||||||
segment_id: SegmentId,
|
segment_id: SegmentId,
|
||||||
bezier: Bezier,
|
bezier: PathSeg,
|
||||||
start: PointId,
|
start: PointId,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -910,13 +916,12 @@ impl PathToolData {
|
|||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
PathToolFsmState::Dragging(self.dragging_state)
|
PathToolFsmState::Dragging(self.dragging_state)
|
||||||
} else {
|
} else {
|
||||||
let start_pos = segment.bezier().start;
|
let points = pathseg_points(segment.pathseg());
|
||||||
let end_pos = segment.bezier().end;
|
|
||||||
|
|
||||||
let [pos1, pos2] = match segment.bezier().handles {
|
let [pos1, pos2] = match (points.p1, points.p2) {
|
||||||
BezierHandles::Cubic { handle_start, handle_end } => [handle_start, handle_end],
|
(Some(p1), Some(p2)) => [p1, p2],
|
||||||
BezierHandles::Quadratic { handle } => [handle, end_pos],
|
(Some(p1), None) | (None, Some(p1)) => [p1, points.p3],
|
||||||
BezierHandles::Linear => [start_pos + (end_pos - start_pos) / 3., end_pos + (start_pos - end_pos) / 3.],
|
(None, None) => [points.p0 + (points.p3 - points.p0) / 3., points.p3 + (points.p0 - points.p3) / 3.],
|
||||||
};
|
};
|
||||||
self.molding_info = Some((pos1, pos2));
|
self.molding_info = Some((pos1, pos2));
|
||||||
PathToolFsmState::Dragging(self.dragging_state)
|
PathToolFsmState::Dragging(self.dragging_state)
|
||||||
@@ -1277,15 +1282,10 @@ impl PathToolData {
|
|||||||
};
|
};
|
||||||
let Some(vector) = 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.colinear(*anchor) {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(point_id) = anchor.as_anchor() else { return false };
|
let Some(point_id) = anchor.as_anchor() else { return false };
|
||||||
|
|
||||||
let mut connected_segments = [None, None];
|
let mut connected_segments = [None, None];
|
||||||
for (segment, bezier, start, end) in vector.segment_bezier_iter() {
|
for (segment, bezier, start, end) in vector.segment_iter() {
|
||||||
if start == point_id || end == point_id {
|
if start == point_id || end == point_id {
|
||||||
match (connected_segments[0], connected_segments[1]) {
|
match (connected_segments[0], connected_segments[1]) {
|
||||||
(None, None) => connected_segments[0] = Some(SlidingSegmentData { segment_id: segment, bezier, start }),
|
(None, None) => connected_segments[0] = Some(SlidingSegmentData { segment_id: segment, bezier, start }),
|
||||||
@@ -1325,13 +1325,13 @@ impl PathToolData {
|
|||||||
|
|
||||||
let segments = sliding_point_info.connected_segments;
|
let segments = sliding_point_info.connected_segments;
|
||||||
|
|
||||||
let t1 = segments[0].bezier.project(layer_pos);
|
let t1 = segments[0].bezier.nearest(dvec2_to_point(layer_pos), DEFAULT_ACCURACY).t;
|
||||||
let position1 = segments[0].bezier.evaluate(TValue::Parametric(t1));
|
let position1 = point_to_dvec2(segments[0].bezier.eval(t1));
|
||||||
|
|
||||||
let t2 = segments[1].bezier.project(layer_pos);
|
let t2 = segments[1].bezier.nearest(dvec2_to_point(layer_pos), DEFAULT_ACCURACY).t;
|
||||||
let position2 = segments[1].bezier.evaluate(TValue::Parametric(t2));
|
let position2 = point_to_dvec2(segments[1].bezier.eval(t2));
|
||||||
|
|
||||||
let (closer_segment, farther_segment, t_value, new_position) = if position2.distance(layer_pos) < position1.distance(layer_pos) {
|
let (closer_segment, further_segment, t_value, new_position) = if position2.distance(layer_pos) < position1.distance(layer_pos) {
|
||||||
(segments[1], segments[0], t2, position2)
|
(segments[1], segments[0], t2, position2)
|
||||||
} else {
|
} else {
|
||||||
(segments[0], segments[1], t1, position1)
|
(segments[0], segments[1], t1, position1)
|
||||||
@@ -1346,50 +1346,59 @@ impl PathToolData {
|
|||||||
shape_editor.move_anchor(anchor, &vector, delta, layer, None, responses);
|
shape_editor.move_anchor(anchor, &vector, delta, layer, None, responses);
|
||||||
|
|
||||||
// Make a split at the t_value
|
// Make a split at the t_value
|
||||||
let [first, second] = closer_segment.bezier.split(TValue::Parametric(t_value));
|
let first = closer_segment.bezier.subsegment(0_f64..t_value);
|
||||||
let closer_segment_other_point = if anchor == closer_segment.start { closer_segment.bezier.end } else { closer_segment.bezier.start };
|
let second = closer_segment.bezier.subsegment(t_value..1.);
|
||||||
|
|
||||||
let (split_segment, other_segment) = if first.start == closer_segment_other_point { (first, second) } else { (second, first) };
|
let closer_segment_other_point = if anchor == closer_segment.start {
|
||||||
|
closer_segment.bezier.end()
|
||||||
|
} else {
|
||||||
|
closer_segment.bezier.start()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (split_segment, other_segment) = if first.start() == closer_segment_other_point { (first, second) } else { (second, first) };
|
||||||
|
let split_segment_points = pathseg_points(split_segment);
|
||||||
|
|
||||||
// Primary handle maps to primary handle and secondary maps to secondary
|
// Primary handle maps to primary handle and secondary maps to secondary
|
||||||
let closer_primary_handle = HandleId::primary(closer_segment.segment_id);
|
let closer_primary_handle = HandleId::primary(closer_segment.segment_id);
|
||||||
let Some(handle_position) = split_segment.handle_start() else { return };
|
let Some(handle_position) = split_segment_points.p1 else { return };
|
||||||
let relative_position1 = handle_position - split_segment.start;
|
let relative_position1 = handle_position - split_segment_points.p0;
|
||||||
let modification_type = closer_primary_handle.set_relative_position(relative_position1);
|
let modification_type = closer_primary_handle.set_relative_position(relative_position1);
|
||||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||||
|
|
||||||
let closer_secondary_handle = HandleId::end(closer_segment.segment_id);
|
let closer_secondary_handle = HandleId::end(closer_segment.segment_id);
|
||||||
let Some(handle_position) = split_segment.handle_end() else { return };
|
let Some(handle_position) = split_segment_points.p2 else { return };
|
||||||
let relative_position2 = handle_position - split_segment.end;
|
let relative_position2 = handle_position - split_segment_points.p3;
|
||||||
let modification_type = closer_secondary_handle.set_relative_position(relative_position2);
|
let modification_type = closer_secondary_handle.set_relative_position(relative_position2);
|
||||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||||
|
|
||||||
let end_handle_direction = if anchor == closer_segment.start { -relative_position1 } else { -relative_position2 };
|
let end_handle_direction = if anchor == closer_segment.start { -relative_position1 } else { -relative_position2 };
|
||||||
|
|
||||||
let (farther_other_point, start_handle, end_handle, start_handle_pos) = if anchor == farther_segment.start {
|
let further_segment_points = pathseg_points(further_segment.bezier);
|
||||||
|
|
||||||
|
let (further_other_point, start_handle, end_handle, start_handle_pos) = if anchor == further_segment.start {
|
||||||
(
|
(
|
||||||
farther_segment.bezier.end,
|
further_segment_points.p3,
|
||||||
HandleId::end(farther_segment.segment_id),
|
HandleId::end(further_segment.segment_id),
|
||||||
HandleId::primary(farther_segment.segment_id),
|
HandleId::primary(further_segment.segment_id),
|
||||||
farther_segment.bezier.handle_end(),
|
further_segment_points.p2,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
farther_segment.bezier.start,
|
further_segment_points.p0,
|
||||||
HandleId::primary(farther_segment.segment_id),
|
HandleId::primary(further_segment.segment_id),
|
||||||
HandleId::end(farther_segment.segment_id),
|
HandleId::end(further_segment.segment_id),
|
||||||
farther_segment.bezier.handle_start(),
|
further_segment_points.p1,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let Some(start_handle_position) = start_handle_pos else { return };
|
let Some(start_handle_position) = start_handle_pos else { return };
|
||||||
let start_handle_direction = start_handle_position - farther_other_point;
|
let start_handle_direction = start_handle_position - further_other_point;
|
||||||
|
|
||||||
// Get normalized direction vectors, if cubic handle is zero then we consider corresponding tangent
|
// Get normalized direction vectors, if cubic handle is zero then we consider corresponding tangent
|
||||||
let d1 = start_handle_direction.try_normalize().unwrap_or({
|
let d1 = start_handle_direction.try_normalize().unwrap_or({
|
||||||
if anchor == farther_segment.start {
|
if anchor == further_segment.start {
|
||||||
-farther_segment.bezier.tangent(TValue::Parametric(0.99))
|
-pathseg_tangent(further_segment.bezier, 1.)
|
||||||
} else {
|
} else {
|
||||||
farther_segment.bezier.tangent(TValue::Parametric(0.01))
|
pathseg_tangent(further_segment.bezier, 0.)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1398,7 +1407,7 @@ impl PathToolData {
|
|||||||
let min_len1 = start_handle_direction.length() * 0.4;
|
let min_len1 = start_handle_direction.length() * 0.4;
|
||||||
let min_len2 = end_handle_direction.length() * 0.4;
|
let min_len2 = end_handle_direction.length() * 0.4;
|
||||||
|
|
||||||
let (relative_pos1, relative_pos2) = find_two_param_best_approximate(farther_other_point, new_position, d1, d2, min_len1, min_len2, farther_segment.bezier, other_segment);
|
let (relative_pos1, relative_pos2) = find_two_param_best_approximate(further_other_point, new_position, d1, d2, min_len1, min_len2, further_segment.bezier, other_segment);
|
||||||
|
|
||||||
// Now set those handles to these handle lengths keeping the directions d1, d2
|
// Now set those handles to these handle lengths keeping the directions d1, d2
|
||||||
let modification_type = start_handle.set_relative_position(relative_pos1);
|
let modification_type = start_handle.set_relative_position(relative_pos1);
|
||||||
@@ -1807,13 +1816,13 @@ impl Fsm for PathToolFsmState {
|
|||||||
if tool_options.path_editing_mode.segment_editing_mode && !tool_data.segment_editing_modifier {
|
if tool_options.path_editing_mode.segment_editing_mode && !tool_data.segment_editing_modifier {
|
||||||
let transform = document.metadata().transform_to_viewport_if_feeds(closest_segment.layer(), &document.network_interface);
|
let transform = document.metadata().transform_to_viewport_if_feeds(closest_segment.layer(), &document.network_interface);
|
||||||
|
|
||||||
overlay_context.outline_overlay_bezier(closest_segment.bezier(), transform);
|
overlay_context.outline_overlay_bezier(closest_segment.pathseg(), transform);
|
||||||
|
|
||||||
// Draw the anchors again
|
// Draw the anchors again
|
||||||
let display_anchors = overlay_context.visibility_settings.anchors();
|
let display_anchors = overlay_context.visibility_settings.anchors();
|
||||||
if display_anchors {
|
if display_anchors {
|
||||||
let start_pos = transform.transform_point2(closest_segment.bezier().start);
|
let start_pos = transform.transform_point2(point_to_dvec2(closest_segment.pathseg().start()));
|
||||||
let end_pos = transform.transform_point2(closest_segment.bezier().end);
|
let end_pos = transform.transform_point2(point_to_dvec2(closest_segment.pathseg().end()));
|
||||||
let start_id = closest_segment.points()[0];
|
let start_id = closest_segment.points()[0];
|
||||||
let end_id = closest_segment.points()[1];
|
let end_id = closest_segment.points()[1];
|
||||||
if let Some(shape_state) = shape_editor.selected_shape_state.get_mut(&closest_segment.layer()) {
|
if let Some(shape_state) = shape_editor.selected_shape_state.get_mut(&closest_segment.layer()) {
|
||||||
@@ -1890,7 +1899,7 @@ impl Fsm for PathToolFsmState {
|
|||||||
let (points_inside, segments_inside) = match selection_shape {
|
let (points_inside, segments_inside) = match selection_shape {
|
||||||
SelectionShapeType::Box => {
|
SelectionShapeType::Box => {
|
||||||
let previous_mouse = document.metadata().document_to_viewport.transform_point2(tool_data.previous_mouse_position);
|
let previous_mouse = document.metadata().document_to_viewport.transform_point2(tool_data.previous_mouse_position);
|
||||||
let bbox = [tool_data.drag_start_pos, previous_mouse];
|
let bbox = Rect::new(tool_data.drag_start_pos.x, tool_data.drag_start_pos.y, previous_mouse.x, previous_mouse.y);
|
||||||
shape_editor.get_inside_points_and_segments(
|
shape_editor.get_inside_points_and_segments(
|
||||||
&document.network_interface,
|
&document.network_interface,
|
||||||
SelectionShape::Box(bbox),
|
SelectionShape::Box(bbox),
|
||||||
@@ -1935,7 +1944,7 @@ impl Fsm for PathToolFsmState {
|
|||||||
|
|
||||||
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
||||||
|
|
||||||
for (segment, bezier, _, _) in vector.segment_bezier_iter() {
|
for (segment, bezier, _, _) in vector.segment_iter() {
|
||||||
if segments.contains(&segment) {
|
if segments.contains(&segment) {
|
||||||
overlay_context.outline_overlay_bezier(bezier, transform);
|
overlay_context.outline_overlay_bezier(bezier, transform);
|
||||||
}
|
}
|
||||||
@@ -2143,10 +2152,6 @@ impl Fsm for PathToolFsmState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !tool_data.update_colinear(equidistant_state, toggle_colinear_state, tool_action_data.shape_editor, tool_action_data.document, responses) {
|
if !tool_data.update_colinear(equidistant_state, toggle_colinear_state, tool_action_data.shape_editor, tool_action_data.document, responses) {
|
||||||
if snap_angle_state && lock_angle_state && tool_data.start_sliding_point(tool_action_data.shape_editor, tool_action_data.document) {
|
|
||||||
return PathToolFsmState::SlidingPoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
tool_data.drag(
|
tool_data.drag(
|
||||||
equidistant_state,
|
equidistant_state,
|
||||||
lock_angle_state,
|
lock_angle_state,
|
||||||
@@ -2311,7 +2316,8 @@ impl Fsm for PathToolFsmState {
|
|||||||
|
|
||||||
match selection_shape {
|
match selection_shape {
|
||||||
SelectionShapeType::Box => {
|
SelectionShapeType::Box => {
|
||||||
let bbox = [tool_data.drag_start_pos, previous_mouse];
|
let bbox = Rect::new(tool_data.drag_start_pos.x, tool_data.drag_start_pos.y, previous_mouse.x, previous_mouse.y);
|
||||||
|
|
||||||
shape_editor.select_all_in_shape(
|
shape_editor.select_all_in_shape(
|
||||||
&document.network_interface,
|
&document.network_interface,
|
||||||
SelectionShape::Box(bbox),
|
SelectionShape::Box(bbox),
|
||||||
@@ -2407,7 +2413,8 @@ impl Fsm for PathToolFsmState {
|
|||||||
} else {
|
} else {
|
||||||
match selection_shape {
|
match selection_shape {
|
||||||
SelectionShapeType::Box => {
|
SelectionShapeType::Box => {
|
||||||
let bbox = [tool_data.drag_start_pos, previous_mouse];
|
let bbox = Rect::new(tool_data.drag_start_pos.x, tool_data.drag_start_pos.y, previous_mouse.x, previous_mouse.y);
|
||||||
|
|
||||||
shape_editor.select_all_in_shape(
|
shape_editor.select_all_in_shape(
|
||||||
&document.network_interface,
|
&document.network_interface,
|
||||||
SelectionShape::Box(bbox),
|
SelectionShape::Box(bbox),
|
||||||
@@ -2440,6 +2447,8 @@ impl Fsm for PathToolFsmState {
|
|||||||
tool_data.ghost_outline.clear();
|
tool_data.ghost_outline.clear();
|
||||||
let extend_selection = input.keyboard.get(extend_selection as usize);
|
let extend_selection = input.keyboard.get(extend_selection as usize);
|
||||||
let drag_occurred = tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD;
|
let drag_occurred = tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD;
|
||||||
|
let mut segment_dissolved = false;
|
||||||
|
let mut point_inserted = false;
|
||||||
|
|
||||||
let nearest_point = shape_editor.find_nearest_visible_point_indices(
|
let nearest_point = shape_editor.find_nearest_visible_point_indices(
|
||||||
&document.network_interface,
|
&document.network_interface,
|
||||||
@@ -2460,6 +2469,7 @@ impl Fsm for PathToolFsmState {
|
|||||||
if tool_data.delete_segment_pressed {
|
if tool_data.delete_segment_pressed {
|
||||||
if let Some(vector) = document.network_interface.compute_modified_vector(segment.layer()) {
|
if let Some(vector) = document.network_interface.compute_modified_vector(segment.layer()) {
|
||||||
shape_editor.dissolve_segment(responses, segment.layer(), &vector, segment.segment(), segment.points());
|
shape_editor.dissolve_segment(responses, segment.layer(), &vector, segment.segment(), segment.points());
|
||||||
|
segment_dissolved = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let is_segment_selected = shape_editor
|
let is_segment_selected = shape_editor
|
||||||
@@ -2468,11 +2478,7 @@ impl Fsm for PathToolFsmState {
|
|||||||
.is_some_and(|state| state.is_segment_selected(segment.segment()));
|
.is_some_and(|state| state.is_segment_selected(segment.segment()));
|
||||||
|
|
||||||
segment.adjusted_insert_and_select(shape_editor, responses, extend_selection, point_mode, is_segment_selected);
|
segment.adjusted_insert_and_select(shape_editor, responses, extend_selection, point_mode, is_segment_selected);
|
||||||
tool_data.segment = None;
|
point_inserted = true;
|
||||||
tool_data.molding_info = None;
|
|
||||||
tool_data.molding_segment = false;
|
|
||||||
tool_data.temporary_adjacent_handles_while_molding = None;
|
|
||||||
return PathToolFsmState::Ready;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2480,6 +2486,11 @@ impl Fsm for PathToolFsmState {
|
|||||||
tool_data.molding_info = None;
|
tool_data.molding_info = None;
|
||||||
tool_data.molding_segment = false;
|
tool_data.molding_segment = false;
|
||||||
tool_data.temporary_adjacent_handles_while_molding = None;
|
tool_data.temporary_adjacent_handles_while_molding = None;
|
||||||
|
|
||||||
|
if segment_dissolved || point_inserted {
|
||||||
|
responses.add(DocumentMessage::EndTransaction);
|
||||||
|
return PathToolFsmState::Ready;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let segment_mode = tool_options.path_editing_mode.segment_editing_mode;
|
let segment_mode = tool_options.path_editing_mode.segment_editing_mode;
|
||||||
@@ -2636,6 +2647,14 @@ impl Fsm for PathToolFsmState {
|
|||||||
shape_editor.delete_point_and_break_path(document, responses);
|
shape_editor.delete_point_and_break_path(document, responses);
|
||||||
PathToolFsmState::Ready
|
PathToolFsmState::Ready
|
||||||
}
|
}
|
||||||
|
(_, PathToolMessage::StartSlidingPoint) => {
|
||||||
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
if tool_data.start_sliding_point(shape_editor, document) {
|
||||||
|
PathToolFsmState::SlidingPoint
|
||||||
|
} else {
|
||||||
|
PathToolFsmState::Ready
|
||||||
|
}
|
||||||
|
}
|
||||||
(_, PathToolMessage::Copy { clipboard }) => {
|
(_, PathToolMessage::Copy { clipboard }) => {
|
||||||
// TODO: Add support for selected segments
|
// TODO: Add support for selected segments
|
||||||
|
|
||||||
@@ -2767,16 +2786,13 @@ impl Fsm for PathToolFsmState {
|
|||||||
|
|
||||||
// Create new segment ids and add the segments into the existing vector content
|
// Create new segment ids and add the segments into the existing vector content
|
||||||
let mut segments_map = HashMap::new();
|
let mut segments_map = HashMap::new();
|
||||||
for (segment_id, bezier, start, end) in new_vector.segment_bezier_iter() {
|
for (segment_id, bezier, start, end) in new_vector.segment_iter() {
|
||||||
let new_segment_id = SegmentId::generate();
|
let new_segment_id = SegmentId::generate();
|
||||||
|
|
||||||
segments_map.insert(segment_id, new_segment_id);
|
segments_map.insert(segment_id, new_segment_id);
|
||||||
|
|
||||||
let handles = match bezier.handles {
|
let points = pathseg_points(bezier);
|
||||||
BezierHandles::Linear => [None, None],
|
let handles = [points.p1, points.p2];
|
||||||
BezierHandles::Quadratic { handle } => [Some(handle - bezier.start), None],
|
|
||||||
BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start), Some(handle_end - bezier.end)],
|
|
||||||
};
|
|
||||||
|
|
||||||
let points = [points_map[&start], points_map[&end]];
|
let points = [points_map[&start], points_map[&end]];
|
||||||
let modification_type = VectorModificationType::InsertSegment { id: new_segment_id, points, handles };
|
let modification_type = VectorModificationType::InsertSegment { id: new_segment_id, points, handles };
|
||||||
@@ -2872,7 +2888,7 @@ impl Fsm for PathToolFsmState {
|
|||||||
|
|
||||||
let mut segments_map = HashMap::new();
|
let mut segments_map = HashMap::new();
|
||||||
|
|
||||||
for (segment_id, bezier, start, end) in old_vector.segment_bezier_iter() {
|
for (segment_id, bezier, start, end) in old_vector.segment_iter() {
|
||||||
let both_ends_selected = layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(start)) && layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(end));
|
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);
|
let segment_selected = layer_selection_state.is_segment_selected(segment_id);
|
||||||
@@ -2881,11 +2897,8 @@ impl Fsm for PathToolFsmState {
|
|||||||
let new_id = SegmentId::generate();
|
let new_id = SegmentId::generate();
|
||||||
segments_map.insert(segment_id, new_id);
|
segments_map.insert(segment_id, new_id);
|
||||||
|
|
||||||
let handles = match bezier.handles {
|
let points = pathseg_points(bezier);
|
||||||
BezierHandles::Linear => [None, None],
|
let handles = [points.p1, points.p2];
|
||||||
BezierHandles::Quadratic { handle } => [Some(handle - bezier.start), None],
|
|
||||||
BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start), Some(handle_end - bezier.end)],
|
|
||||||
};
|
|
||||||
|
|
||||||
let points = [points_map[&start], points_map[&end]];
|
let points = [points_map[&start], points_map[&end]];
|
||||||
let modification_type = VectorModificationType::InsertSegment { id: new_id, points, handles };
|
let modification_type = VectorModificationType::InsertSegment { id: new_id, points, handles };
|
||||||
@@ -3449,7 +3462,7 @@ fn update_dynamic_hints(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut drag_selected_hints = vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")];
|
let drag_selected_hints = vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")];
|
||||||
let mut delete_selected_hints = vec![HintInfo::keys([Key::Delete], "Delete Selected")];
|
let mut delete_selected_hints = vec![HintInfo::keys([Key::Delete], "Delete Selected")];
|
||||||
|
|
||||||
if at_least_one_anchor_selected {
|
if at_least_one_anchor_selected {
|
||||||
@@ -3457,10 +3470,6 @@ fn update_dynamic_hints(
|
|||||||
delete_selected_hints.push(HintInfo::keys([Key::Shift], "Cut Anchor").prepend_plus());
|
delete_selected_hints.push(HintInfo::keys([Key::Shift], "Cut Anchor").prepend_plus());
|
||||||
}
|
}
|
||||||
|
|
||||||
if single_colinear_anchor_selected {
|
|
||||||
drag_selected_hints.push(HintInfo::multi_keys([[Key::Control], [Key::Shift]], "Slide").prepend_plus());
|
|
||||||
}
|
|
||||||
|
|
||||||
let segment_edit = tool_options.path_editing_mode.segment_editing_mode;
|
let segment_edit = tool_options.path_editing_mode.segment_editing_mode;
|
||||||
let point_edit = tool_options.path_editing_mode.point_editing_mode;
|
let point_edit = tool_options.path_editing_mode.point_editing_mode;
|
||||||
|
|
||||||
@@ -3525,9 +3534,15 @@ fn update_dynamic_hints(
|
|||||||
let mut groups = vec![
|
let mut groups = vec![
|
||||||
HintGroup(drag_selected_hints),
|
HintGroup(drag_selected_hints),
|
||||||
HintGroup(vec![HintInfo::multi_keys([[Key::KeyG], [Key::KeyR], [Key::KeyS]], "Grab/Rotate/Scale Selected")]),
|
HintGroup(vec![HintInfo::multi_keys([[Key::KeyG], [Key::KeyR], [Key::KeyS]], "Grab/Rotate/Scale Selected")]),
|
||||||
HintGroup(vec![HintInfo::arrow_keys("Nudge Selected"), HintInfo::keys([Key::Shift], "10x").prepend_plus()]),
|
|
||||||
HintGroup(delete_selected_hints),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if single_colinear_anchor_selected {
|
||||||
|
groups.push(HintGroup(vec![HintInfo::multi_keys([[Key::KeyG], [Key::KeyG]], "Slide")]));
|
||||||
|
}
|
||||||
|
|
||||||
|
groups.push(HintGroup(vec![HintInfo::arrow_keys("Nudge Selected"), HintInfo::keys([Key::Shift], "10x").prepend_plus()]));
|
||||||
|
groups.push(HintGroup(delete_selected_hints));
|
||||||
|
|
||||||
hint_data.append(&mut groups);
|
hint_data.append(&mut groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,12 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{self
|
|||||||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||||
use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
|
use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
|
||||||
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, closest_point, should_extend};
|
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, closest_point, should_extend};
|
||||||
use bezier_rs::{Bezier, BezierHandles};
|
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_std::Color;
|
use graphene_std::Color;
|
||||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
|
use graphene_std::subpath::pathseg_points;
|
||||||
|
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point};
|
||||||
use graphene_std::vector::{NoHashBuilder, PointId, SegmentId, StrokeId, Vector, VectorModificationType};
|
use graphene_std::vector::{NoHashBuilder, PointId, SegmentId, StrokeId, Vector, VectorModificationType};
|
||||||
|
use kurbo::{CubicBez, PathSeg};
|
||||||
|
|
||||||
#[derive(Default, ExtractField)]
|
#[derive(Default, ExtractField)]
|
||||||
pub struct PenTool {
|
pub struct PenTool {
|
||||||
@@ -1257,7 +1258,7 @@ impl PenToolData {
|
|||||||
let vector = document.network_interface.compute_modified_vector(layer);
|
let vector = document.network_interface.compute_modified_vector(layer);
|
||||||
let (handle_start, in_segment) = if let Some(vector) = &vector {
|
let (handle_start, in_segment) = if let Some(vector) = &vector {
|
||||||
vector
|
vector
|
||||||
.segment_bezier_iter()
|
.segment_iter()
|
||||||
.find_map(|(segment_id, bezier, start, end)| {
|
.find_map(|(segment_id, bezier, start, end)| {
|
||||||
let is_end = point == end;
|
let is_end = point == end;
|
||||||
let is_start = point == start;
|
let is_start = point == start;
|
||||||
@@ -1265,15 +1266,11 @@ impl PenToolData {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let handle = match bezier.handles {
|
let points = pathseg_points(bezier);
|
||||||
BezierHandles::Cubic { handle_start, handle_end, .. } => {
|
let handle = match (points.p1, points.p2) {
|
||||||
if is_start {
|
(Some(p1), Some(_)) if is_start => p1,
|
||||||
handle_start
|
(Some(_), Some(p2)) if !is_start => p2,
|
||||||
} else {
|
(Some(p1), None) | (None, Some(p1)) => p1,
|
||||||
handle_end
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BezierHandles::Quadratic { handle } => handle,
|
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
Some((segment_id, is_end, handle))
|
Some((segment_id, is_end, handle))
|
||||||
@@ -1599,9 +1596,8 @@ impl Fsm for PenToolFsmState {
|
|||||||
let handle_start = tool_data.latest_point().map(|point| transform.transform_point2(point.handle_start));
|
let handle_start = tool_data.latest_point().map(|point| transform.transform_point2(point.handle_start));
|
||||||
|
|
||||||
if let (Some((start, handle_start)), Some(handle_end)) = (tool_data.latest_point().map(|point| (point.pos, point.handle_start)), tool_data.handle_end) {
|
if let (Some((start, handle_start)), Some(handle_end)) = (tool_data.latest_point().map(|point| (point.pos, point.handle_start)), tool_data.handle_end) {
|
||||||
let handles = BezierHandles::Cubic { handle_start, handle_end };
|
|
||||||
let end = tool_data.next_point;
|
let end = tool_data.next_point;
|
||||||
let bezier = Bezier { start, handles, end };
|
let bezier = PathSeg::Cubic(CubicBez::new(dvec2_to_point(start), dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(end)));
|
||||||
if (end - start).length_squared() > f64::EPSILON {
|
if (end - start).length_squared() > f64::EPSILON {
|
||||||
// Draw the curve for the currently-being-placed segment
|
// Draw the curve for the currently-being-placed segment
|
||||||
overlay_context.outline_bezier(bezier, transform);
|
overlay_context.outline_bezier(bezier, transform);
|
||||||
@@ -1723,7 +1719,7 @@ impl Fsm for PenToolFsmState {
|
|||||||
// We have the point. Join the 2 vertices and check if any path is closed.
|
// We have the point. Join the 2 vertices and check if any path is closed.
|
||||||
if let Some(end) = closest_point {
|
if let Some(end) = closest_point {
|
||||||
let segment_id = SegmentId::generate();
|
let segment_id = SegmentId::generate();
|
||||||
vector.push(segment_id, start, end, BezierHandles::Cubic { handle_start, handle_end }, StrokeId::ZERO);
|
vector.push(segment_id, start, end, (Some(handle_start), Some(handle_end)), StrokeId::ZERO);
|
||||||
|
|
||||||
let grouped_segments = vector.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 closed_paths = grouped_segments.iter().filter(|path| path.is_closed() && path.contains(segment_id));
|
||||||
|
|||||||
@@ -20,12 +20,12 @@ use crate::messages::tool::common_functionality::shape_editor::SelectionShapeTyp
|
|||||||
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager};
|
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager};
|
||||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||||
use crate::messages::tool::common_functionality::utility_functions::{resize_bounds, rotate_bounds, skew_bounds, text_bounding_box, transforming_transform_cage};
|
use crate::messages::tool::common_functionality::utility_functions::{resize_bounds, rotate_bounds, skew_bounds, text_bounding_box, transforming_transform_cage};
|
||||||
use bezier_rs::Subpath;
|
|
||||||
use glam::DMat2;
|
use glam::DMat2;
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_std::path_bool::BooleanOperation;
|
use graphene_std::path_bool::BooleanOperation;
|
||||||
use graphene_std::renderer::Quad;
|
use graphene_std::renderer::Quad;
|
||||||
use graphene_std::renderer::Rect;
|
use graphene_std::renderer::Rect;
|
||||||
|
use graphene_std::subpath::Subpath;
|
||||||
use graphene_std::transform::ReferencePoint;
|
use graphene_std::transform::ReferencePoint;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
@@ -92,6 +92,7 @@ pub enum SelectToolMessage {
|
|||||||
remove_from_selection: Key,
|
remove_from_selection: Key,
|
||||||
},
|
},
|
||||||
EditLayer,
|
EditLayer,
|
||||||
|
EditLayerExec,
|
||||||
Enter,
|
Enter,
|
||||||
PointerMove(SelectToolPointerKeys),
|
PointerMove(SelectToolPointerKeys),
|
||||||
PointerOutsideViewport(SelectToolPointerKeys),
|
PointerOutsideViewport(SelectToolPointerKeys),
|
||||||
@@ -323,6 +324,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Sele
|
|||||||
PointerMove,
|
PointerMove,
|
||||||
Abort,
|
Abort,
|
||||||
EditLayer,
|
EditLayer,
|
||||||
|
EditLayerExec,
|
||||||
Enter,
|
Enter,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -689,32 +691,28 @@ impl Fsm for SelectToolFsmState {
|
|||||||
// Measure with Alt held down
|
// Measure with Alt held down
|
||||||
// TODO: Don't use `Key::Alt` directly, instead take it as a variable from the input mappings list like in all other places
|
// TODO: Don't use `Key::Alt` directly, instead take it as a variable from the input mappings list like in all other places
|
||||||
if overlay_context.visibility_settings.quick_measurement() && !matches!(self, Self::ResizingBounds { .. }) && input.keyboard.get(Key::Alt as usize) {
|
if overlay_context.visibility_settings.quick_measurement() && !matches!(self, Self::ResizingBounds { .. }) && input.keyboard.get(Key::Alt as usize) {
|
||||||
// Get all selected layers and compute their viewport-aligned AABB
|
// Compute document-space bounding box (AABB) of all selected visible & unlocked layers
|
||||||
let selected_bounds_viewport = document
|
let selected_bounds_doc_space = document
|
||||||
.network_interface
|
.network_interface
|
||||||
.selected_nodes()
|
.selected_nodes()
|
||||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||||
|
// Exclude layers that are artboards
|
||||||
.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))
|
.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||||
.filter_map(|layer| {
|
// For each remaining layer, try to get its document-space bounding box and convert it to a Rect
|
||||||
// Get the layer's bounding box in its local space
|
.filter_map(|layer| document.metadata().bounding_box_document(layer).map(Rect::from_box))
|
||||||
let local_bounds = document.metadata().bounding_box_with_transform(layer, DAffine2::IDENTITY)?;
|
// Combine all individual bounding boxes into one overall bounding box that contains all selected layers
|
||||||
// Transform the bounds directly to viewport space
|
|
||||||
let viewport_quad = document.metadata().transform_to_viewport(layer) * Quad::from_box(local_bounds);
|
|
||||||
// Convert the quad to an AABB in viewport space
|
|
||||||
Some(Rect::from_box(viewport_quad.bounding_box()))
|
|
||||||
})
|
|
||||||
.reduce(Rect::combine_bounds);
|
.reduce(Rect::combine_bounds);
|
||||||
|
|
||||||
// Get the hovered layer's viewport-aligned AABB
|
// Compute document-space bounding box (AABB) of the currently hovered layer
|
||||||
let hovered_bounds_viewport = document.metadata().bounding_box_with_transform(layer, DAffine2::IDENTITY).map(|bounds| {
|
let hovered_bounds_doc_space = document.metadata().bounding_box_document(layer);
|
||||||
let viewport_quad = document.metadata().transform_to_viewport(layer) * Quad::from_box(bounds);
|
|
||||||
Rect::from_box(viewport_quad.bounding_box())
|
|
||||||
});
|
|
||||||
|
|
||||||
// Use the viewport-aligned AABBs for measurement
|
// If both selected and hovered bounds exist, overlay measurement lines
|
||||||
if let (Some(selected_bounds), Some(hovered_bounds)) = (selected_bounds_viewport, hovered_bounds_viewport) {
|
if let (Some(selected_bounds), Some(hovered_bounds)) = (selected_bounds_doc_space, hovered_bounds_doc_space.map(Rect::from_box)) {
|
||||||
// Since we're already in viewport space, use identity transform
|
// Both `selected_bounds` and `hovered_bounds` are in document space.
|
||||||
measure::overlay(selected_bounds, hovered_bounds, DAffine2::IDENTITY, DAffine2::IDENTITY, &mut overlay_context);
|
// To correctly render overlay lines in the UI (which is in viewport space), we need to transform both rectangles from document to viewport space.
|
||||||
|
// Therefore, we pass `document_to_viewport` as both the `transform` and `document_to_viewport` parameters.
|
||||||
|
let document_to_viewport = document.metadata().document_to_viewport;
|
||||||
|
measure::overlay(selected_bounds, hovered_bounds, document_to_viewport, document_to_viewport, &mut overlay_context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -985,14 +983,19 @@ impl Fsm for SelectToolFsmState {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
(_, SelectToolMessage::EditLayer) => {
|
(_, SelectToolMessage::EditLayer) => {
|
||||||
// Edit the clicked layer
|
responses.add(DeferMessage::AfterGraphRun {
|
||||||
|
messages: vec![SelectToolMessage::EditLayerExec.into()],
|
||||||
|
});
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
(_, SelectToolMessage::EditLayerExec) => {
|
||||||
if let Some(intersect) = document.click(input) {
|
if let Some(intersect) = document.click(input) {
|
||||||
match tool_data.nested_selection_behavior {
|
match tool_data.nested_selection_behavior {
|
||||||
NestedSelectionBehavior::Shallowest => edit_layer_shallowest_manipulation(document, intersect, responses),
|
NestedSelectionBehavior::Shallowest => edit_layer_shallowest_manipulation(document, intersect, responses),
|
||||||
NestedSelectionBehavior::Deepest => edit_layer_deepest_manipulation(intersect, &document.network_interface, responses),
|
NestedSelectionBehavior::Deepest => edit_layer_deepest_manipulation(intersect, &document.network_interface, responses),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ pub struct TransformLayerMessageHandler {
|
|||||||
|
|
||||||
// Ghost outlines for Path Tool
|
// Ghost outlines for Path Tool
|
||||||
ghost_outline: Vec<(Vec<ClickTargetType>, DAffine2)>,
|
ghost_outline: Vec<(Vec<ClickTargetType>, DAffine2)>,
|
||||||
|
|
||||||
|
was_grabbing: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for TransformLayerMessageHandler {
|
impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for TransformLayerMessageHandler {
|
||||||
@@ -122,7 +124,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
|
|||||||
self.local_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot);
|
self.local_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot);
|
||||||
self.grab_target = self.local_pivot;
|
self.grab_target = self.local_pivot;
|
||||||
}
|
}
|
||||||
// Here vector data from all layers is not considered which can be a problem in pivot calculation
|
// TODO: Here vector data from all layers is not considered which can be a problem in pivot calculation
|
||||||
else if let Some(vector) = 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();
|
*selected.original_transforms = OriginalTransforms::default();
|
||||||
|
|
||||||
@@ -301,6 +303,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
|
|||||||
responses.add(SelectToolMessage::PivotShift { offset: None, flush: true });
|
responses.add(SelectToolMessage::PivotShift { offset: None, flush: true });
|
||||||
|
|
||||||
if final_transform {
|
if final_transform {
|
||||||
|
self.was_grabbing = false;
|
||||||
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
|
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,34 +359,64 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
|
|||||||
if (selected_points.is_empty() && selected_segments.is_empty())
|
if (selected_points.is_empty() && selected_segments.is_empty())
|
||||||
|| (!using_path_tool && !using_select_tool && !using_pen_tool && !using_shape_tool)
|
|| (!using_path_tool && !using_select_tool && !using_pen_tool && !using_shape_tool)
|
||||||
|| selected_layers.is_empty()
|
|| selected_layers.is_empty()
|
||||||
|| transform_type.equivalent_to(self.transform_operation)
|
|| (transform_type.equivalent_to(self.transform_operation) && !self.was_grabbing)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if using_path_tool && transform_type == TransformType::Grab {
|
||||||
|
// Check if a single point is selected and it's a colinear point
|
||||||
|
let single_anchor_selected = shape_editor.selected_points().count() == 1 && shape_editor.selected_points().any(|point| point.as_anchor().is_some());
|
||||||
|
|
||||||
|
if single_anchor_selected && transform_type.equivalent_to(self.transform_operation) && self.was_grabbing {
|
||||||
|
let selected_nodes = &document.network_interface.selected_nodes();
|
||||||
|
|
||||||
|
let Some(layer) = selected_nodes.selected_layers(document.metadata()).next() else { return };
|
||||||
|
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { return };
|
||||||
|
let Some(anchor) = shape_editor.selected_points().next() else { return };
|
||||||
|
|
||||||
|
if vector_data.colinear(*anchor) {
|
||||||
|
responses.add(TransformLayerMessage::CancelTransformOperation);
|
||||||
|
|
||||||
|
// Start sliding point
|
||||||
|
responses.add(DeferMessage::AfterGraphRun {
|
||||||
|
messages: vec![PathToolMessage::StartSlidingPoint.into()],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if transform_type.equivalent_to(self.transform_operation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.was_grabbing = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(vector) = 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() {
|
&& let [point] = selected_points.as_slice()
|
||||||
if matches!(point, ManipulatorPointId::Anchor(_)) {
|
{
|
||||||
if let Some([handle1, handle2]) = point.get_handle_pair(&vector) {
|
if matches!(point, ManipulatorPointId::Anchor(_)) {
|
||||||
let handle1_length = handle1.length(&vector);
|
if let Some([handle1, handle2]) = point.get_handle_pair(&vector) {
|
||||||
let handle2_length = handle2.length(&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) {
|
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
|
// G should work for this point but not R and S
|
||||||
if matches!(transform_type, TransformType::Rotate | TransformType::Scale) {
|
if matches!(transform_type, TransformType::Rotate | TransformType::Scale) {
|
||||||
selected.original_transforms.clear();
|
selected.original_transforms.clear();
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
let handle_length = point.as_handle().map(|handle| handle.length(&vector));
|
} else {
|
||||||
|
let handle_length = point.as_handle().map(|handle| handle.length(&vector));
|
||||||
|
|
||||||
if handle_length == Some(0.) {
|
if handle_length == Some(0.) {
|
||||||
selected.original_transforms.clear();
|
selected.original_transforms.clear();
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -409,6 +442,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
|
|||||||
TransformLayerMessage::CancelTransformOperation => {
|
TransformLayerMessage::CancelTransformOperation => {
|
||||||
if using_path_tool {
|
if using_path_tool {
|
||||||
self.ghost_outline.clear();
|
self.ghost_outline.clear();
|
||||||
|
self.was_grabbing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if using_pen_tool {
|
if using_pen_tool {
|
||||||
@@ -499,7 +533,7 @@ impl MessageHandler<TransformLayerMessage, TransformLayerMessageContext<'_>> for
|
|||||||
|
|
||||||
if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
if self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
|
||||||
match self.transform_operation {
|
match self.transform_operation {
|
||||||
TransformOperation::None => unreachable!(),
|
TransformOperation::None => {}
|
||||||
TransformOperation::Grabbing(translation) => {
|
TransformOperation::Grabbing(translation) => {
|
||||||
let delta_pos = input.mouse.position - self.mouse_position;
|
let delta_pos = input.mouse.position - self.mouse_position;
|
||||||
let delta_pos = (self.initial_transform * document_to_viewport.inverse()).transform_vector2(delta_pos);
|
let delta_pos = (self.initial_transform * document_to_viewport.inverse()).transform_vector2(delta_pos);
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
mod workspace_message;
|
|
||||||
mod workspace_message_handler;
|
|
||||||
|
|
||||||
#[doc(inline)]
|
|
||||||
pub use workspace_message::{WorkspaceMessage, WorkspaceMessageDiscriminant};
|
|
||||||
#[doc(inline)]
|
|
||||||
pub use workspace_message_handler::WorkspaceMessageHandler;
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
use crate::messages::prelude::*;
|
|
||||||
|
|
||||||
#[impl_message(Message, Workspace)]
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
|
||||||
pub enum WorkspaceMessage {
|
|
||||||
// Messages
|
|
||||||
NodeGraphToggleVisibility,
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
use crate::messages::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, ExtractField)]
|
|
||||||
pub struct WorkspaceMessageHandler {
|
|
||||||
node_graph_visible: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[message_handler_data]
|
|
||||||
impl MessageHandler<WorkspaceMessage, ()> for WorkspaceMessageHandler {
|
|
||||||
fn process_message(&mut self, message: WorkspaceMessage, _responses: &mut VecDeque<Message>, _: ()) {
|
|
||||||
match message {
|
|
||||||
// Messages
|
|
||||||
WorkspaceMessage::NodeGraphToggleVisibility => {
|
|
||||||
self.node_graph_visible = !self.node_graph_visible;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn actions(&self) -> ActionList {
|
|
||||||
actions!(WorkspaceMessageDiscriminant;
|
|
||||||
NodeGraphToggleVisibility,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,13 +8,11 @@ use graph_craft::proto::GraphErrors;
|
|||||||
use graph_craft::wasm_application_io::EditorPreferences;
|
use graph_craft::wasm_application_io::EditorPreferences;
|
||||||
use graphene_std::application_io::TimingInformation;
|
use graphene_std::application_io::TimingInformation;
|
||||||
use graphene_std::application_io::{NodeGraphUpdateMessage, RenderConfig};
|
use graphene_std::application_io::{NodeGraphUpdateMessage, RenderConfig};
|
||||||
use graphene_std::renderer::RenderSvgSegmentList;
|
|
||||||
use graphene_std::renderer::{Render, RenderParams, SvgRender};
|
|
||||||
use graphene_std::renderer::{RenderMetadata, format_transform_matrix};
|
use graphene_std::renderer::{RenderMetadata, format_transform_matrix};
|
||||||
use graphene_std::text::FontCache;
|
use graphene_std::text::FontCache;
|
||||||
use graphene_std::transform::Footprint;
|
use graphene_std::transform::Footprint;
|
||||||
use graphene_std::vector::Vector;
|
use graphene_std::vector::Vector;
|
||||||
use graphene_std::vector::style::ViewMode;
|
use graphene_std::wasm_application_io::RenderOutputType;
|
||||||
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;
|
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypesDelta;
|
||||||
|
|
||||||
mod runtime_io;
|
mod runtime_io;
|
||||||
@@ -33,7 +31,6 @@ pub struct ExecutionResponse {
|
|||||||
execution_id: u64,
|
execution_id: u64,
|
||||||
result: Result<TaggedValue, String>,
|
result: Result<TaggedValue, String>,
|
||||||
responses: VecDeque<FrontendMessage>,
|
responses: VecDeque<FrontendMessage>,
|
||||||
transform: DAffine2,
|
|
||||||
vector_modify: HashMap<NodeId, Vector>,
|
vector_modify: HashMap<NodeId, Vector>,
|
||||||
/// The resulting value from the temporary inspected during execution
|
/// The resulting value from the temporary inspected during execution
|
||||||
inspect_result: Option<InspectResult>,
|
inspect_result: Option<InspectResult>,
|
||||||
@@ -57,7 +54,7 @@ pub struct NodeGraphExecutor {
|
|||||||
current_execution_id: u64,
|
current_execution_id: u64,
|
||||||
futures: HashMap<u64, ExecutionContext>,
|
futures: HashMap<u64, ExecutionContext>,
|
||||||
node_graph_hash: u64,
|
node_graph_hash: u64,
|
||||||
old_inspect_node: Option<NodeId>,
|
previous_node_to_inspect: Option<NodeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -79,7 +76,7 @@ impl NodeGraphExecutor {
|
|||||||
runtime_io: NodeRuntimeIO::with_channels(request_sender, response_receiver),
|
runtime_io: NodeRuntimeIO::with_channels(request_sender, response_receiver),
|
||||||
node_graph_hash: 0,
|
node_graph_hash: 0,
|
||||||
current_execution_id: 0,
|
current_execution_id: 0,
|
||||||
old_inspect_node: None,
|
previous_node_to_inspect: None,
|
||||||
};
|
};
|
||||||
(node_runtime, node_executor)
|
(node_runtime, node_executor)
|
||||||
}
|
}
|
||||||
@@ -112,24 +109,25 @@ impl NodeGraphExecutor {
|
|||||||
let instrumented = Instrumented::new(&mut network);
|
let instrumented = Instrumented::new(&mut network);
|
||||||
|
|
||||||
self.runtime_io
|
self.runtime_io
|
||||||
.send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, inspect_node: None }))
|
.send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, node_to_inspect: None }))
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
Ok(instrumented)
|
Ok(instrumented)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the cached network if necessary.
|
/// Update the cached network if necessary.
|
||||||
fn update_node_graph(&mut self, document: &mut DocumentMessageHandler, inspect_node: Option<NodeId>, ignore_hash: bool) -> Result<(), String> {
|
fn update_node_graph(&mut self, document: &mut DocumentMessageHandler, node_to_inspect: Option<NodeId>, ignore_hash: bool) -> Result<(), String> {
|
||||||
let network_hash = document.network_interface.document_network().current_hash();
|
let network_hash = document.network_interface.document_network().current_hash();
|
||||||
// Refresh the graph when it changes or the inspect node changes
|
// Refresh the graph when it changes or the inspect node changes
|
||||||
if network_hash != self.node_graph_hash || self.old_inspect_node != inspect_node || ignore_hash {
|
if network_hash != self.node_graph_hash || self.previous_node_to_inspect != node_to_inspect || ignore_hash {
|
||||||
let network = document.network_interface.document_network().clone();
|
let network = document.network_interface.document_network().clone();
|
||||||
self.old_inspect_node = inspect_node;
|
self.previous_node_to_inspect = node_to_inspect;
|
||||||
self.node_graph_hash = network_hash;
|
self.node_graph_hash = network_hash;
|
||||||
|
|
||||||
self.runtime_io
|
self.runtime_io
|
||||||
.send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, inspect_node }))
|
.send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, node_to_inspect }))
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,10 +170,10 @@ impl NodeGraphExecutor {
|
|||||||
document_id: DocumentId,
|
document_id: DocumentId,
|
||||||
viewport_resolution: UVec2,
|
viewport_resolution: UVec2,
|
||||||
time: TimingInformation,
|
time: TimingInformation,
|
||||||
inspect_node: Option<NodeId>,
|
node_to_inspect: Option<NodeId>,
|
||||||
ignore_hash: bool,
|
ignore_hash: bool,
|
||||||
) -> Result<Message, String> {
|
) -> Result<Message, String> {
|
||||||
self.update_node_graph(document, inspect_node, ignore_hash)?;
|
self.update_node_graph(document, node_to_inspect, ignore_hash)?;
|
||||||
self.submit_current_node_graph_evaluation(document, document_id, viewport_resolution, time)
|
self.submit_current_node_graph_evaluation(document, document_id, viewport_resolution, time)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -209,7 +207,7 @@ impl NodeGraphExecutor {
|
|||||||
|
|
||||||
// Execute the node graph
|
// Execute the node graph
|
||||||
self.runtime_io
|
self.runtime_io
|
||||||
.send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, inspect_node: None }))
|
.send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, node_to_inspect: None }))
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
let execution_id = self.queue_execution(render_config);
|
let execution_id = self.queue_execution(render_config);
|
||||||
let execution_context = ExecutionContext {
|
let execution_context = ExecutionContext {
|
||||||
@@ -223,7 +221,7 @@ impl NodeGraphExecutor {
|
|||||||
|
|
||||||
fn export(&self, node_graph_output: TaggedValue, export_config: ExportConfig, responses: &mut VecDeque<Message>) -> Result<(), String> {
|
fn export(&self, node_graph_output: TaggedValue, export_config: ExportConfig, responses: &mut VecDeque<Message>) -> Result<(), String> {
|
||||||
let TaggedValue::RenderOutput(RenderOutput {
|
let TaggedValue::RenderOutput(RenderOutput {
|
||||||
data: graphene_std::wasm_application_io::RenderOutputType::Svg { svg, .. },
|
data: RenderOutputType::Svg { svg, .. },
|
||||||
..
|
..
|
||||||
}) = node_graph_output
|
}) = node_graph_output
|
||||||
else {
|
else {
|
||||||
@@ -251,6 +249,7 @@ impl NodeGraphExecutor {
|
|||||||
let size = (size * scale_factor).into();
|
let size = (size * scale_factor).into();
|
||||||
responses.add(FrontendMessage::TriggerExportImage { svg, name, mime, size });
|
responses.add(FrontendMessage::TriggerExportImage { svg, name, mime, size });
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,7 +262,6 @@ impl NodeGraphExecutor {
|
|||||||
execution_id,
|
execution_id,
|
||||||
result,
|
result,
|
||||||
responses: existing_responses,
|
responses: existing_responses,
|
||||||
transform,
|
|
||||||
vector_modify,
|
vector_modify,
|
||||||
inspect_result,
|
inspect_result,
|
||||||
} = execution_response;
|
} = execution_response;
|
||||||
@@ -286,17 +284,17 @@ impl NodeGraphExecutor {
|
|||||||
let execution_context = self.futures.remove(&execution_id).ok_or_else(|| "Invalid generation ID".to_string())?;
|
let execution_context = self.futures.remove(&execution_id).ok_or_else(|| "Invalid generation ID".to_string())?;
|
||||||
if let Some(export_config) = execution_context.export_config {
|
if let Some(export_config) = execution_context.export_config {
|
||||||
// Special handling for exporting the artwork
|
// Special handling for exporting the artwork
|
||||||
self.export(node_graph_output, export_config, responses)?
|
self.export(node_graph_output, export_config, responses)?;
|
||||||
} else {
|
} else {
|
||||||
self.process_node_graph_output(node_graph_output, transform, responses)?
|
self.process_node_graph_output(node_graph_output, responses)?;
|
||||||
}
|
}
|
||||||
responses.add_front(DeferMessage::TriggerGraphRun(execution_id, execution_context.document_id));
|
responses.add(DeferMessage::TriggerGraphRun(execution_id, execution_context.document_id));
|
||||||
|
|
||||||
// Update the spreadsheet on the frontend using the value of the inspect result.
|
// Update the Data panel on the frontend using the value of the inspect result.
|
||||||
if self.old_inspect_node.is_some() {
|
if let Some(inspect_result) = (self.previous_node_to_inspect.is_some()).then_some(inspect_result).flatten() {
|
||||||
if let Some(inspect_result) = inspect_result {
|
responses.add(DataPanelMessage::UpdateLayout { inspect_result });
|
||||||
responses.add(SpreadsheetMessage::UpdateLayout { inspect_result });
|
} else {
|
||||||
}
|
responses.add(DataPanelMessage::ClearLayout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NodeGraphUpdate::CompilationResponse(execution_response) => {
|
NodeGraphUpdate::CompilationResponse(execution_response) => {
|
||||||
@@ -329,84 +327,45 @@ impl NodeGraphExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn debug_render(render_object: impl Render, transform: DAffine2, responses: &mut VecDeque<Message>) {
|
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, responses: &mut VecDeque<Message>) -> Result<(), String> {
|
||||||
// Setup rendering
|
let TaggedValue::RenderOutput(render_output) = node_graph_output else {
|
||||||
let mut render = SvgRender::new();
|
return Err(format!("Invalid node graph output type: {node_graph_output:#?}"));
|
||||||
let render_params = RenderParams {
|
|
||||||
view_mode: ViewMode::Normal,
|
|
||||||
culling_bounds: None,
|
|
||||||
thumbnail: false,
|
|
||||||
hide_artboards: false,
|
|
||||||
for_export: false,
|
|
||||||
for_mask: false,
|
|
||||||
alignment_parent_transform: None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Render SVG
|
match render_output.data {
|
||||||
render_object.render_svg(&mut render, &render_params);
|
RenderOutputType::Svg { svg, image_data } => {
|
||||||
|
// Send to frontend
|
||||||
// Concatenate the defs and the SVG into one string
|
responses.add(FrontendMessage::UpdateImageData { image_data });
|
||||||
render.wrap_with_transform(transform, None);
|
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
|
||||||
let svg = render.svg.to_svg_string();
|
|
||||||
|
|
||||||
// Send to frontend
|
|
||||||
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, transform: DAffine2, responses: &mut VecDeque<Message>) -> Result<(), String> {
|
|
||||||
let mut render_output_metadata = RenderMetadata::default();
|
|
||||||
match node_graph_output {
|
|
||||||
TaggedValue::RenderOutput(render_output) => {
|
|
||||||
match render_output.data {
|
|
||||||
graphene_std::wasm_application_io::RenderOutputType::Svg { svg, image_data } => {
|
|
||||||
// Send to frontend
|
|
||||||
responses.add(FrontendMessage::UpdateImageData { image_data });
|
|
||||||
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
|
|
||||||
}
|
|
||||||
graphene_std::wasm_application_io::RenderOutputType::CanvasFrame(frame) => {
|
|
||||||
let matrix = format_transform_matrix(frame.transform);
|
|
||||||
let transform = if matrix.is_empty() { String::new() } else { format!(" transform=\"{matrix}\"") };
|
|
||||||
let svg = format!(
|
|
||||||
r#"<svg><foreignObject width="{}" height="{}"{transform}><div data-canvas-placeholder="{}"></div></foreignObject></svg>"#,
|
|
||||||
frame.resolution.x, frame.resolution.y, frame.surface_id.0
|
|
||||||
);
|
|
||||||
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
|
|
||||||
}
|
|
||||||
graphene_std::wasm_application_io::RenderOutputType::Texture { .. } => {}
|
|
||||||
_ => {
|
|
||||||
return Err(format!("Invalid node graph output type: {:#?}", render_output.data));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render_output_metadata = render_output.metadata;
|
|
||||||
}
|
}
|
||||||
TaggedValue::Bool(render_object) => Self::debug_render(render_object, transform, responses),
|
RenderOutputType::CanvasFrame(frame) => {
|
||||||
TaggedValue::String(render_object) => Self::debug_render(render_object, transform, responses),
|
let matrix = format_transform_matrix(frame.transform);
|
||||||
TaggedValue::F64(render_object) => Self::debug_render(render_object, transform, responses),
|
let transform = if matrix.is_empty() { String::new() } else { format!(" transform=\"{matrix}\"") };
|
||||||
TaggedValue::DVec2(render_object) => Self::debug_render(render_object, transform, responses),
|
let svg = format!(
|
||||||
TaggedValue::OptionalColor(render_object) => Self::debug_render(render_object, transform, responses),
|
r#"<svg><foreignObject width="{}" height="{}"{transform}><div data-canvas-placeholder="{}"></div></foreignObject></svg>"#,
|
||||||
TaggedValue::Vector(render_object) => Self::debug_render(render_object, transform, responses),
|
frame.resolution.x, frame.resolution.y, frame.surface_id.0
|
||||||
TaggedValue::Graphic(render_object) => Self::debug_render(render_object, transform, responses),
|
);
|
||||||
TaggedValue::Raster(render_object) => Self::debug_render(render_object, transform, responses),
|
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
|
||||||
TaggedValue::Palette(render_object) => Self::debug_render(render_object, transform, responses),
|
|
||||||
_ => {
|
|
||||||
return Err(format!("Invalid node graph output type: {node_graph_output:#?}"));
|
|
||||||
}
|
}
|
||||||
};
|
RenderOutputType::Texture { .. } => {}
|
||||||
let graphene_std::renderer::RenderMetadata {
|
_ => return Err(format!("Invalid node graph output type: {:#?}", render_output.data)),
|
||||||
upstream_footprints: footprints,
|
}
|
||||||
|
|
||||||
|
let RenderMetadata {
|
||||||
|
upstream_footprints,
|
||||||
local_transforms,
|
local_transforms,
|
||||||
first_element_source_id,
|
first_element_source_id,
|
||||||
click_targets,
|
click_targets,
|
||||||
clip_targets,
|
clip_targets,
|
||||||
} = render_output_metadata;
|
} = render_output.metadata;
|
||||||
|
|
||||||
// Run these update state messages immediately
|
// Run these update state messages immediately
|
||||||
responses.add(DocumentMessage::UpdateUpstreamTransforms {
|
responses.add(DocumentMessage::UpdateUpstreamTransforms {
|
||||||
upstream_footprints: footprints,
|
upstream_footprints,
|
||||||
local_transforms,
|
local_transforms,
|
||||||
first_element_source_id,
|
first_element_source_id,
|
||||||
});
|
});
|
||||||
@@ -415,6 +374,7 @@ impl NodeGraphExecutor {
|
|||||||
responses.add(DocumentMessage::RenderScrollbars);
|
responses.add(DocumentMessage::RenderScrollbars);
|
||||||
responses.add(DocumentMessage::RenderRulers);
|
responses.add(DocumentMessage::RenderRulers);
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ use graph_craft::proto::GraphErrors;
|
|||||||
use graph_craft::wasm_application_io::EditorPreferences;
|
use graph_craft::wasm_application_io::EditorPreferences;
|
||||||
use graph_craft::{ProtoNodeIdentifier, concrete};
|
use graph_craft::{ProtoNodeIdentifier, concrete};
|
||||||
use graphene_std::application_io::{ImageTexture, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
|
use graphene_std::application_io::{ImageTexture, NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
|
||||||
|
use graphene_std::bounds::RenderBoundingBox;
|
||||||
use graphene_std::memo::IORecord;
|
use graphene_std::memo::IORecord;
|
||||||
use graphene_std::renderer::{Render, RenderParams, SvgRender};
|
use graphene_std::renderer::{Render, RenderParams, SvgRender};
|
||||||
use graphene_std::renderer::{RenderSvgSegmentList, SvgSegment};
|
use graphene_std::renderer::{RenderSvgSegmentList, SvgSegment};
|
||||||
use graphene_std::table::{Table, TableRow};
|
use graphene_std::table::{Table, TableRow};
|
||||||
use graphene_std::text::FontCache;
|
use graphene_std::text::FontCache;
|
||||||
|
use graphene_std::transform::RenderQuality;
|
||||||
use graphene_std::vector::Vector;
|
use graphene_std::vector::Vector;
|
||||||
use graphene_std::vector::style::ViewMode;
|
|
||||||
use graphene_std::wasm_application_io::{RenderOutputType, WasmApplicationIo, WasmEditorApi};
|
use graphene_std::wasm_application_io::{RenderOutputType, WasmApplicationIo, WasmEditorApi};
|
||||||
use graphene_std::{Artboard, Context, Graphic};
|
use graphene_std::{Artboard, Context, Graphic};
|
||||||
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
|
use interpreted_executor::dynamic_executor::{DynamicExecutor, IntrospectError, ResolvedDocumentNodeTypesDelta};
|
||||||
@@ -42,7 +43,7 @@ pub struct NodeRuntime {
|
|||||||
node_graph_errors: GraphErrors,
|
node_graph_errors: GraphErrors,
|
||||||
monitor_nodes: Vec<Vec<NodeId>>,
|
monitor_nodes: Vec<Vec<NodeId>>,
|
||||||
|
|
||||||
/// Which node is inspected and which monitor node is used (if any) for the current execution
|
/// Which node is inspected and which monitor node is used (if any) for the current execution.
|
||||||
inspect_state: Option<InspectState>,
|
inspect_state: Option<InspectState>,
|
||||||
|
|
||||||
/// Mapping of the fully-qualified node paths to their preprocessor substitutions.
|
/// Mapping of the fully-qualified node paths to their preprocessor substitutions.
|
||||||
@@ -67,7 +68,7 @@ pub enum GraphRuntimeRequest {
|
|||||||
pub struct GraphUpdate {
|
pub struct GraphUpdate {
|
||||||
pub(super) network: NodeNetwork,
|
pub(super) network: NodeNetwork,
|
||||||
/// The node that should be temporary inspected during execution
|
/// The node that should be temporary inspected during execution
|
||||||
pub(super) inspect_node: Option<NodeId>,
|
pub(super) node_to_inspect: Option<NodeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
@@ -188,22 +189,21 @@ impl NodeRuntime {
|
|||||||
let _ = self.update_network(graph).await;
|
let _ = self.update_network(graph).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GraphRuntimeRequest::GraphUpdate(GraphUpdate { mut network, inspect_node }) => {
|
GraphRuntimeRequest::GraphUpdate(GraphUpdate { mut network, node_to_inspect }) => {
|
||||||
// Insert the monitor node to manage the inspection
|
// Insert the monitor node to manage the inspection
|
||||||
self.inspect_state = inspect_node.map(|inspect| InspectState::monitor_inspect_node(&mut network, inspect));
|
self.inspect_state = node_to_inspect.map(|inspect| InspectState::monitor_inspect_node(&mut network, inspect));
|
||||||
|
|
||||||
self.old_graph = Some(network.clone());
|
self.old_graph = Some(network.clone());
|
||||||
|
|
||||||
self.node_graph_errors.clear();
|
self.node_graph_errors.clear();
|
||||||
let result = self.update_network(network).await;
|
let result = self.update_network(network).await;
|
||||||
|
let node_graph_errors = self.node_graph_errors.clone();
|
||||||
|
|
||||||
self.update_thumbnails = true;
|
self.update_thumbnails = true;
|
||||||
self.sender.send_generation_response(CompilationResponse {
|
|
||||||
result,
|
self.sender.send_generation_response(CompilationResponse { result, node_graph_errors });
|
||||||
node_graph_errors: self.node_graph_errors.clone(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
GraphRuntimeRequest::ExecutionRequest(ExecutionRequest { execution_id, render_config, .. }) => {
|
GraphRuntimeRequest::ExecutionRequest(ExecutionRequest { execution_id, render_config, .. }) => {
|
||||||
let transform = render_config.viewport.transform;
|
|
||||||
|
|
||||||
let result = self.execute_network(render_config).await;
|
let result = self.execute_network(render_config).await;
|
||||||
let mut responses = VecDeque::new();
|
let mut responses = VecDeque::new();
|
||||||
// TODO: Only process monitor nodes if the graph has changed, not when only the Footprint changes
|
// TODO: Only process monitor nodes if the graph has changed, not when only the Footprint changes
|
||||||
@@ -227,7 +227,6 @@ impl NodeRuntime {
|
|||||||
execution_id,
|
execution_id,
|
||||||
result,
|
result,
|
||||||
responses,
|
responses,
|
||||||
transform,
|
|
||||||
vector_modify: self.vector_modify.clone(),
|
vector_modify: self.vector_modify.clone(),
|
||||||
inspect_result,
|
inspect_result,
|
||||||
});
|
});
|
||||||
@@ -292,51 +291,49 @@ impl NodeRuntime {
|
|||||||
if self.inspect_state.is_some_and(|inspect_state| monitor_node_path.last().copied() == Some(inspect_state.monitor_node)) {
|
if self.inspect_state.is_some_and(|inspect_state| monitor_node_path.last().copied() == Some(inspect_state.monitor_node)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The monitor nodes are located within a document node, and are thus children in that network, so this gets the parent document node's ID
|
// The monitor nodes are located within a document node, and are thus children in that network, so this gets the parent document node's ID
|
||||||
let Some(parent_network_node_id) = monitor_node_path.len().checked_sub(2).and_then(|index| monitor_node_path.get(index)).copied() else {
|
let Some(parent_network_node_id) = monitor_node_path.len().checked_sub(2).and_then(|index| monitor_node_path.get(index)).copied() else {
|
||||||
warn!("Monitor node has invalid node id");
|
warn!("Monitor node has invalid node id");
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Extract the monitor node's stored `Graphic` data.
|
// Extract the monitor node's stored `Graphic` data
|
||||||
let Ok(introspected_data) = self.executor.introspect(monitor_node_path) else {
|
let Ok(introspected_data) = self.executor.introspect(monitor_node_path) else {
|
||||||
// TODO: Fix the root of the issue causing the spam of this warning (this at least temporarily disables it in release builds)
|
// TODO: Fix the root of the issue causing the spam of this warning (this at least temporarily disables it in release builds)
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
warn!("Failed to introspect monitor node {}", self.executor.introspect(monitor_node_path).unwrap_err());
|
warn!("Failed to introspect monitor node {}", self.executor.introspect(monitor_node_path).unwrap_err());
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Graphic table: thumbnail
|
||||||
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Graphic>>>() {
|
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Graphic>>>() {
|
||||||
Self::process_graphic(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses, update_thumbnails)
|
if update_thumbnails {
|
||||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Artboard>>>() {
|
Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses)
|
||||||
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<Vector>>>() {
|
// Artboard table: thumbnail
|
||||||
|
else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Artboard>>>() {
|
||||||
|
if update_thumbnails {
|
||||||
|
Self::render_thumbnail(&mut self.thumbnail_renders, parent_network_node_id, &io.output, responses)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Vector table: vector modifications
|
||||||
|
else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Vector>>>() {
|
||||||
|
// Insert the vector modify
|
||||||
let default = TableRow::default();
|
let default = TableRow::default();
|
||||||
self.vector_modify
|
self.vector_modify
|
||||||
.insert(parent_network_node_id, record.output.iter().next().unwrap_or_else(|| default.as_ref()).element.clone());
|
.insert(parent_network_node_id, io.output.iter().next().unwrap_or_else(|| default.as_ref()).element.clone());
|
||||||
} else {
|
}
|
||||||
|
// Other
|
||||||
|
else {
|
||||||
log::warn!("Failed to downcast monitor node output {parent_network_node_id:?}");
|
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.
|
/// 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(
|
fn render_thumbnail(thumbnail_renders: &mut HashMap<NodeId, Vec<SvgSegment>>, parent_network_node_id: NodeId, graphic: &impl Render, responses: &mut VecDeque<FrontendMessage>) {
|
||||||
thumbnail_renders: &mut HashMap<NodeId, Vec<SvgSegment>>,
|
|
||||||
parent_network_node_id: NodeId,
|
|
||||||
graphic: &impl Render,
|
|
||||||
responses: &mut VecDeque<FrontendMessage>,
|
|
||||||
update_thumbnails: bool,
|
|
||||||
) {
|
|
||||||
// RENDER THUMBNAIL
|
|
||||||
|
|
||||||
if !update_thumbnails {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip thumbnails if the layer is too complex (for performance)
|
// Skip thumbnails if the layer is too complex (for performance)
|
||||||
if graphic.render_complexity() > 1000 {
|
if graphic.render_complexity() > 1000 {
|
||||||
let old = thumbnail_renders.insert(parent_network_node_id, Vec::new());
|
let old = thumbnail_renders.insert(parent_network_node_id, Vec::new());
|
||||||
@@ -349,24 +346,28 @@ impl NodeRuntime {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let bounds = graphic.bounding_box(DAffine2::IDENTITY, true);
|
let bounds = match graphic.bounding_box(DAffine2::IDENTITY, true) {
|
||||||
|
RenderBoundingBox::None => return,
|
||||||
|
RenderBoundingBox::Infinite => [DVec2::ZERO, DVec2::new(300., 200.)],
|
||||||
|
RenderBoundingBox::Rectangle(bounds) => bounds,
|
||||||
|
};
|
||||||
|
let footprint = Footprint {
|
||||||
|
transform: DAffine2::from_translation(DVec2::new(bounds[0].x, bounds[0].y)),
|
||||||
|
resolution: UVec2::new((bounds[1].x - bounds[0].x).abs() as u32, (bounds[1].y - bounds[0].y).abs() as u32),
|
||||||
|
quality: RenderQuality::Full,
|
||||||
|
};
|
||||||
|
|
||||||
// Render the thumbnail from a `Graphic` into an SVG string
|
// Render the thumbnail from a `Graphic` into an SVG string
|
||||||
let render_params = RenderParams {
|
let render_params = RenderParams {
|
||||||
view_mode: ViewMode::Normal,
|
footprint,
|
||||||
culling_bounds: bounds,
|
|
||||||
thumbnail: true,
|
thumbnail: true,
|
||||||
hide_artboards: false,
|
..Default::default()
|
||||||
for_export: false,
|
|
||||||
for_mask: false,
|
|
||||||
alignment_parent_transform: None,
|
|
||||||
};
|
};
|
||||||
let mut render = SvgRender::new();
|
let mut render = SvgRender::new();
|
||||||
graphic.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
|
// And give the SVG a viewbox and outer <svg>...</svg> wrapper tag
|
||||||
let [min, max] = bounds.unwrap_or_default();
|
render.format_svg(bounds[0], bounds[1]);
|
||||||
render.format_svg(min, max);
|
|
||||||
|
|
||||||
// UPDATE FRONTEND THUMBNAIL
|
// UPDATE FRONTEND THUMBNAIL
|
||||||
|
|
||||||
|
|||||||
@@ -82,18 +82,4 @@ impl DebugMessageTree {
|
|||||||
pub fn message_handler_fields(&self) -> Option<&MessageData> {
|
pub fn message_handler_fields(&self) -> Option<&MessageData> {
|
||||||
self.message_handler.as_ref()
|
self.message_handler.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_message_handler_data_fields(&self) -> bool {
|
|
||||||
match self.message_handler_data_fields() {
|
|
||||||
Some(_) => true,
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_message_handler_fields(&self) -> bool {
|
|
||||||
match self.message_handler_fields() {
|
|
||||||
Some(_) => true,
|
|
||||||
None => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,16 +113,22 @@
|
|||||||
|
|
||||||
--color-data-general: #cfcfcf;
|
--color-data-general: #cfcfcf;
|
||||||
--color-data-general-dim: #8a8a8a;
|
--color-data-general-dim: #8a8a8a;
|
||||||
--color-data-raster: #e4bb72;
|
|
||||||
--color-data-raster-dim: #8b7752;
|
|
||||||
--color-data-vector: #65bbe5;
|
|
||||||
--color-data-vector-dim: #4b778c;
|
|
||||||
--color-data-graphic: #66b195;
|
|
||||||
--color-data-graphic-dim: #3d725e;
|
|
||||||
--color-data-artboard: #fbf9eb;
|
|
||||||
--color-data-artboard-dim: #b9b9a9;
|
|
||||||
--color-data-number: #c9a699;
|
--color-data-number: #c9a699;
|
||||||
--color-data-number-dim: #886b60;
|
--color-data-number-dim: #886b60;
|
||||||
|
--color-data-artboard: #fbf9eb;
|
||||||
|
--color-data-artboard-dim: #b9b9a9;
|
||||||
|
--color-data-graphic: #68c587;
|
||||||
|
--color-data-graphic-dim: #37754c;
|
||||||
|
--color-data-raster: #e4bb72;
|
||||||
|
--color-data-raster-dim: #9a7b43;
|
||||||
|
--color-data-vector: #65bbe5;
|
||||||
|
--color-data-vector-dim: #417892;
|
||||||
|
--color-data-color: #ce6ea7;
|
||||||
|
--color-data-color-dim: #924071;
|
||||||
|
--color-data-gradient: #af81eb;
|
||||||
|
--color-data-gradient-dim: #6c489b;
|
||||||
|
--color-data-typography: #eea7a7;
|
||||||
|
--color-data-typography-dim: #955252;
|
||||||
|
|
||||||
--color-none: white;
|
--color-none: white;
|
||||||
--color-none-repeat: no-repeat;
|
--color-none-repeat: no-repeat;
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
export let colorOrGradient: FillChoice;
|
export let colorOrGradient: FillChoice;
|
||||||
export let allowNone = false;
|
export let allowNone = false;
|
||||||
// export let allowTransparency = false; // TODO: Implement
|
// export let allowTransparency = false; // TODO: Implement
|
||||||
|
export let disabled = false;
|
||||||
export let direction: MenuDirection = "Bottom";
|
export let direction: MenuDirection = "Bottom";
|
||||||
// TODO: See if this should be made to follow the pattern of DropdownInput.svelte so this could be removed
|
// TODO: See if this should be made to follow the pattern of DropdownInput.svelte so this could be removed
|
||||||
export let open: boolean;
|
export let open: boolean;
|
||||||
@@ -133,6 +134,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onPointerDown(e: PointerEvent) {
|
function onPointerDown(e: PointerEvent) {
|
||||||
|
if (disabled) return;
|
||||||
|
|
||||||
const target = (e.target || undefined) as HTMLElement | undefined;
|
const target = (e.target || undefined) as HTMLElement | undefined;
|
||||||
draggingPickerTrack = target?.closest("[data-saturation-value-picker], [data-hue-picker], [data-alpha-picker]") || undefined;
|
draggingPickerTrack = target?.closest("[data-saturation-value-picker], [data-hue-picker], [data-alpha-picker]") || undefined;
|
||||||
|
|
||||||
@@ -403,7 +406,7 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FloatingMenu class="color-picker" {open} on:open {strayCloses} escapeCloses={strayCloses && !gradientSpectrumDragging} {direction} type="Popover" bind:this={self}>
|
<FloatingMenu class="color-picker" classes={{ disabled }} {open} on:open {strayCloses} escapeCloses={strayCloses && !gradientSpectrumDragging} {direction} type="Popover" bind:this={self}>
|
||||||
<LayoutRow
|
<LayoutRow
|
||||||
styles={{
|
styles={{
|
||||||
"--new-color": newColor.toHexOptionalAlpha(),
|
"--new-color": newColor.toHexOptionalAlpha(),
|
||||||
@@ -418,7 +421,7 @@
|
|||||||
>
|
>
|
||||||
<LayoutCol class="pickers-and-gradient">
|
<LayoutCol class="pickers-and-gradient">
|
||||||
<LayoutRow class="pickers">
|
<LayoutRow class="pickers">
|
||||||
<LayoutCol class="saturation-value-picker" on:pointerdown={onPointerDown} data-saturation-value-picker>
|
<LayoutCol class="saturation-value-picker" title={disabled ? "Saturation and value (disabled)" : "Saturation and value"} on:pointerdown={onPointerDown} data-saturation-value-picker>
|
||||||
{#if !isNone}
|
{#if !isNone}
|
||||||
<div class="selection-circle" style:top={`${(1 - value) * 100}%`} style:left={`${saturation * 100}%`} />
|
<div class="selection-circle" style:top={`${(1 - value) * 100}%`} style:left={`${saturation * 100}%`} />
|
||||||
{/if}
|
{/if}
|
||||||
@@ -432,12 +435,12 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
<LayoutCol class="hue-picker" on:pointerdown={onPointerDown} data-hue-picker>
|
<LayoutCol class="hue-picker" title={disabled ? "Hue (disabled)" : "Hue"} on:pointerdown={onPointerDown} data-hue-picker>
|
||||||
{#if !isNone}
|
{#if !isNone}
|
||||||
<div class="selection-needle" style:top={`${(1 - hue) * 100}%`} />
|
<div class="selection-needle" style:top={`${(1 - hue) * 100}%`} />
|
||||||
{/if}
|
{/if}
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
<LayoutCol class="alpha-picker" on:pointerdown={onPointerDown} data-alpha-picker>
|
<LayoutCol class="alpha-picker" title={disabled ? "Alpha (disabled)" : "Alpha"} on:pointerdown={onPointerDown} data-alpha-picker>
|
||||||
{#if !isNone}
|
{#if !isNone}
|
||||||
<div class="selection-needle" style:top={`${(1 - alpha) * 100}%`} />
|
<div class="selection-needle" style:top={`${(1 - alpha) * 100}%`} />
|
||||||
{/if}
|
{/if}
|
||||||
@@ -447,6 +450,7 @@
|
|||||||
<LayoutRow class="gradient">
|
<LayoutRow class="gradient">
|
||||||
<SpectrumInput
|
<SpectrumInput
|
||||||
{gradient}
|
{gradient}
|
||||||
|
{disabled}
|
||||||
on:gradient={() => {
|
on:gradient={() => {
|
||||||
gradient = gradient;
|
gradient = gradient;
|
||||||
if (gradient) dispatch("colorOrGradient", gradient);
|
if (gradient) dispatch("colorOrGradient", gradient);
|
||||||
@@ -459,6 +463,7 @@
|
|||||||
{#if gradientSpectrumInputWidget && activeIndex !== undefined}
|
{#if gradientSpectrumInputWidget && activeIndex !== undefined}
|
||||||
<NumberInput
|
<NumberInput
|
||||||
value={(gradient.positionAtIndex(activeIndex) || 0) * 100}
|
value={(gradient.positionAtIndex(activeIndex) || 0) * 100}
|
||||||
|
{disabled}
|
||||||
on:value={({ detail }) => {
|
on:value={({ detail }) => {
|
||||||
if (gradientSpectrumInputWidget && activeIndex !== undefined && detail !== undefined) gradientSpectrumInputWidget.setPosition(activeIndex, detail / 100);
|
if (gradientSpectrumInputWidget && activeIndex !== undefined && detail !== undefined) gradientSpectrumInputWidget.setPosition(activeIndex, detail / 100);
|
||||||
}}
|
}}
|
||||||
@@ -478,7 +483,7 @@
|
|||||||
styles={{ "--outline-amount": outlineFactor }}
|
styles={{ "--outline-amount": outlineFactor }}
|
||||||
tooltip={!newColor.equals(oldColor) ? "Comparison between the present color choice (left) and the color before any change was made (right)" : "The present color choice"}
|
tooltip={!newColor.equals(oldColor) ? "Comparison between the present color choice (left) and the color before any change was made (right)" : "The present color choice"}
|
||||||
>
|
>
|
||||||
{#if !newColor.equals(oldColor)}
|
{#if !newColor.equals(oldColor) && !disabled}
|
||||||
<div class="swap-button-background"></div>
|
<div class="swap-button-background"></div>
|
||||||
<IconButton class="swap-button" icon="SwapHorizontal" size={16} action={swapNewWithOld} tooltip="Swap" />
|
<IconButton class="swap-button" icon="SwapHorizontal" size={16} action={swapNewWithOld} tooltip="Swap" />
|
||||||
{/if}
|
{/if}
|
||||||
@@ -500,6 +505,7 @@
|
|||||||
<LayoutRow>
|
<LayoutRow>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={newColor.toHexOptionalAlpha() || "-"}
|
value={newColor.toHexOptionalAlpha() || "-"}
|
||||||
|
{disabled}
|
||||||
on:commitText={({ detail }) => {
|
on:commitText={({ detail }) => {
|
||||||
dispatch("startHistoryTransaction");
|
dispatch("startHistoryTransaction");
|
||||||
setColorCode(detail);
|
setColorCode(detail);
|
||||||
@@ -520,6 +526,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<NumberInput
|
<NumberInput
|
||||||
value={strength}
|
value={strength}
|
||||||
|
{disabled}
|
||||||
on:value={({ detail }) => {
|
on:value={({ detail }) => {
|
||||||
strength = detail;
|
strength = detail;
|
||||||
setColorRGB(channel, detail);
|
setColorRGB(channel, detail);
|
||||||
@@ -547,6 +554,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<NumberInput
|
<NumberInput
|
||||||
value={strength}
|
value={strength}
|
||||||
|
{disabled}
|
||||||
on:value={({ detail }) => {
|
on:value={({ detail }) => {
|
||||||
strength = detail;
|
strength = detail;
|
||||||
setColorHSV(channel, detail);
|
setColorHSV(channel, detail);
|
||||||
@@ -573,6 +581,7 @@
|
|||||||
<Separator type="Related" />
|
<Separator type="Related" />
|
||||||
<NumberInput
|
<NumberInput
|
||||||
value={!isNone ? alpha * 100 : undefined}
|
value={!isNone ? alpha * 100 : undefined}
|
||||||
|
{disabled}
|
||||||
on:value={({ detail }) => {
|
on:value={({ detail }) => {
|
||||||
if (detail !== undefined) alpha = detail / 100;
|
if (detail !== undefined) alpha = detail / 100;
|
||||||
setColorAlphaPercent(detail);
|
setColorAlphaPercent(detail);
|
||||||
@@ -593,14 +602,14 @@
|
|||||||
<LayoutRow class="leftover-space" />
|
<LayoutRow class="leftover-space" />
|
||||||
<LayoutRow>
|
<LayoutRow>
|
||||||
{#if allowNone && !gradient}
|
{#if allowNone && !gradient}
|
||||||
<button class="preset-color none" on:click={() => setColorPreset("none")} title="Set to no color" tabindex="0"></button>
|
<button class="preset-color none" {disabled} on:click={() => setColorPreset("none")} title="Set to no color" tabindex="0"></button>
|
||||||
<Separator type="Related" />
|
<Separator type="Related" />
|
||||||
{/if}
|
{/if}
|
||||||
<button class="preset-color black" on:click={() => setColorPreset("black")} title="Set to black" tabindex="0"></button>
|
<button class="preset-color black" {disabled} on:click={() => setColorPreset("black")} title="Set to black" tabindex="0"></button>
|
||||||
<Separator type="Related" />
|
<Separator type="Related" />
|
||||||
<button class="preset-color white" on:click={() => setColorPreset("white")} title="Set to white" tabindex="0"></button>
|
<button class="preset-color white" {disabled} on:click={() => setColorPreset("white")} title="Set to white" tabindex="0"></button>
|
||||||
<Separator type="Related" />
|
<Separator type="Related" />
|
||||||
<button class="preset-color pure" on:click={setColorPresetSubtile} tabindex="-1">
|
<button class="preset-color pure" {disabled} on:click={setColorPresetSubtile} tabindex="-1">
|
||||||
<div data-pure-tile="red" style="--pure-color: #ff0000; --pure-color-gray: #4c4c4c" title="Set to red" />
|
<div data-pure-tile="red" style="--pure-color: #ff0000; --pure-color-gray: #4c4c4c" title="Set to red" />
|
||||||
<div data-pure-tile="yellow" style="--pure-color: #ffff00; --pure-color-gray: #e3e3e3" title="Set to yellow" />
|
<div data-pure-tile="yellow" style="--pure-color: #ffff00; --pure-color-gray: #e3e3e3" title="Set to yellow" />
|
||||||
<div data-pure-tile="green" style="--pure-color: #00ff00; --pure-color-gray: #969696" title="Set to green" />
|
<div data-pure-tile="green" style="--pure-color: #00ff00; --pure-color-gray: #969696" title="Set to green" />
|
||||||
@@ -609,7 +618,7 @@
|
|||||||
<div data-pure-tile="magenta" style="--pure-color: #ff00ff; --pure-color-gray: #696969" title="Set to magenta" />
|
<div data-pure-tile="magenta" style="--pure-color: #ff00ff; --pure-color-gray: #696969" title="Set to magenta" />
|
||||||
</button>
|
</button>
|
||||||
<Separator type="Related" />
|
<Separator type="Related" />
|
||||||
<IconButton icon="Eyedropper" size={24} action={activateEyedropperSample} tooltip="Sample a pixel color from the document" />
|
<IconButton icon="Eyedropper" size={24} {disabled} action={activateEyedropperSample} tooltip="Sample a pixel color from the document" />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
@@ -954,7 +963,7 @@
|
|||||||
// For the least jarring luminance conversion, these colors are derived by placing a black layer with the "desaturate" blend mode over the colors.
|
// For the least jarring luminance conversion, these colors are derived by placing a black layer with the "desaturate" blend mode over the colors.
|
||||||
// We don't use the CSS `filter: grayscale(1);` property because it produces overly dark tones for bright colors with a noticeable jump on hover.
|
// We don't use the CSS `filter: grayscale(1);` property because it produces overly dark tones for bright colors with a noticeable jump on hover.
|
||||||
background: var(--pure-color-gray);
|
background: var(--pure-color-gray);
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.1s;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover div {
|
&:hover div {
|
||||||
@@ -963,5 +972,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.disabled .pickers-and-gradient .pickers :is(.saturation-value-picker, .hue-picker, .alpha-picker),
|
||||||
|
&.disabled .details .preset-color,
|
||||||
|
&.disabled .details .choice-preview {
|
||||||
|
transition: opacity 0.1s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled .details .preset-color.pure:hover div {
|
||||||
|
background: var(--pure-color-gray);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// paddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpaddingpadding
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { getContext, onMount, onDestroy } from "svelte";
|
||||||
|
|
||||||
|
import type { Editor } from "@graphite/editor";
|
||||||
|
import { defaultWidgetLayout, patchWidgetLayout, UpdateDataPanelLayout } from "@graphite/messages";
|
||||||
|
|
||||||
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
|
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||||
|
|
||||||
|
const editor = getContext<Editor>("editor");
|
||||||
|
|
||||||
|
let dataPanelLayout = defaultWidgetLayout();
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
editor.subscriptions.subscribeJsMessage(UpdateDataPanelLayout, (updateDataPanelLayout) => {
|
||||||
|
patchWidgetLayout(dataPanelLayout, updateDataPanelLayout);
|
||||||
|
dataPanelLayout = dataPanelLayout;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
editor.subscriptions.unsubscribeJsMessage(UpdateDataPanelLayout);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<LayoutCol class="data-panel">
|
||||||
|
<LayoutCol class="body" scrollableY={true}>
|
||||||
|
<WidgetLayout layout={dataPanelLayout} />
|
||||||
|
</LayoutCol>
|
||||||
|
</LayoutCol>
|
||||||
|
|
||||||
|
<style lang="scss" global>
|
||||||
|
.data-panel {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 4px;
|
||||||
|
|
||||||
|
table {
|
||||||
|
margin: -4px;
|
||||||
|
width: calc(100% + 2 * 4px);
|
||||||
|
|
||||||
|
.text-label {
|
||||||
|
white-space: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -675,7 +675,7 @@
|
|||||||
|
|
||||||
&[title^="Coming Soon"] {
|
&[title^="Coming Soon"] {
|
||||||
opacity: 0.25;
|
opacity: 0.25;
|
||||||
transition: opacity 0.2s;
|
transition: opacity 0.1s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@@ -825,7 +825,7 @@
|
|||||||
|
|
||||||
.graph-view {
|
.graph-view {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: opacity 0.2s ease-in-out;
|
transition: opacity 0.2s;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
||||||
&.open {
|
&.open {
|
||||||
|
|||||||
@@ -98,6 +98,12 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
|
editor.subscriptions.unsubscribeJsMessage(UpdateLayersPanelControlBarLeftLayout);
|
||||||
|
editor.subscriptions.unsubscribeJsMessage(UpdateLayersPanelControlBarRightLayout);
|
||||||
|
editor.subscriptions.unsubscribeJsMessage(UpdateLayersPanelBottomBarLayout);
|
||||||
|
editor.subscriptions.unsubscribeJsMessage(UpdateDocumentLayerStructureJs);
|
||||||
|
editor.subscriptions.unsubscribeJsMessage(UpdateDocumentLayerDetails);
|
||||||
|
|
||||||
removeEventListener("pointermove", clippingHover);
|
removeEventListener("pointermove", clippingHover);
|
||||||
removeEventListener("keydown", clippingKeyPress);
|
removeEventListener("keydown", clippingKeyPress);
|
||||||
removeEventListener("keyup", clippingKeyPress);
|
removeEventListener("keyup", clippingKeyPress);
|
||||||
@@ -489,7 +495,9 @@
|
|||||||
<LayoutCol class="layers" on:dragleave={() => (dragInPanel = false)}>
|
<LayoutCol class="layers" on:dragleave={() => (dragInPanel = false)}>
|
||||||
<LayoutRow class="control-bar" scrollableX={true}>
|
<LayoutRow class="control-bar" scrollableX={true}>
|
||||||
<WidgetLayout layout={layersPanelControlBarLeftLayout} />
|
<WidgetLayout layout={layersPanelControlBarLeftLayout} />
|
||||||
<Separator />
|
{#if layersPanelControlBarLeftLayout?.layout?.length > 0 && layersPanelControlBarRightLayout?.layout?.length > 0}
|
||||||
|
<Separator />
|
||||||
|
{/if}
|
||||||
<WidgetLayout layout={layersPanelControlBarRightLayout} />
|
<WidgetLayout layout={layersPanelControlBarRightLayout} />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<LayoutRow class="list-area" scrollableY={true}>
|
<LayoutRow class="list-area" scrollableY={true}>
|
||||||
@@ -605,6 +613,10 @@
|
|||||||
.widget-span:first-child {
|
.widget-span:first-child {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:not(:has(*)) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bottom bar
|
// Bottom bar
|
||||||
@@ -619,6 +631,10 @@
|
|||||||
.widget-span > * {
|
.widget-span > * {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:not(:has(*)) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layer hierarchy
|
// Layer hierarchy
|
||||||
@@ -714,6 +730,7 @@
|
|||||||
width: 36px;
|
width: 36px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
|
overflow: hidden;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
background-image: var(--color-transparent-checkered-background);
|
background-image: var(--color-transparent-checkered-background);
|
||||||
background-size: var(--color-transparent-checkered-background-size-mini);
|
background-size: var(--color-transparent-checkered-background-size-mini);
|
||||||
@@ -721,9 +738,8 @@
|
|||||||
background-repeat: var(--color-transparent-checkered-background-repeat);
|
background-repeat: var(--color-transparent-checkered-background-repeat);
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: calc(100% - 4px);
|
width: 100%;
|
||||||
height: calc(100% - 4px);
|
height: 100%;
|
||||||
margin: 2px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +1,31 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, onMount } from "svelte";
|
import { getContext, onMount, onDestroy } from "svelte";
|
||||||
|
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { Editor } from "@graphite/editor";
|
||||||
import { defaultWidgetLayout, patchWidgetLayout, UpdatePropertyPanelSectionsLayout } from "@graphite/messages";
|
import { defaultWidgetLayout, patchWidgetLayout, UpdatePropertiesPanelLayout } from "@graphite/messages";
|
||||||
|
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||||
|
|
||||||
const editor = getContext<Editor>("editor");
|
const editor = getContext<Editor>("editor");
|
||||||
|
|
||||||
let propertiesSectionsLayout = defaultWidgetLayout();
|
let propertiesPanelLayout = defaultWidgetLayout();
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
editor.subscriptions.subscribeJsMessage(UpdatePropertyPanelSectionsLayout, (updatePropertyPanelSectionsLayout) => {
|
editor.subscriptions.subscribeJsMessage(UpdatePropertiesPanelLayout, (updatePropertiesPanelLayout) => {
|
||||||
patchWidgetLayout(propertiesSectionsLayout, updatePropertyPanelSectionsLayout);
|
patchWidgetLayout(propertiesPanelLayout, updatePropertiesPanelLayout);
|
||||||
propertiesSectionsLayout = propertiesSectionsLayout;
|
propertiesPanelLayout = propertiesPanelLayout;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
editor.subscriptions.unsubscribeJsMessage(UpdatePropertiesPanelLayout);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LayoutCol class="properties">
|
<LayoutCol class="properties">
|
||||||
<LayoutCol class="sections" scrollableY={true}>
|
<LayoutCol class="sections" scrollableY={true}>
|
||||||
<WidgetLayout layout={propertiesSectionsLayout} />
|
<WidgetLayout layout={propertiesPanelLayout} />
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
|
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
|
||||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
|
||||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
|
||||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
|
||||||
|
|
||||||
import { getContext } from "svelte";
|
|
||||||
|
|
||||||
import type { PortfolioState } from "/src/state-providers/portfolio";
|
|
||||||
|
|
||||||
const portfolio = getContext<PortfolioState>("portfolio");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<LayoutCol class="spreadsheet">
|
|
||||||
<LayoutRow class="control-bar">
|
|
||||||
<TextLabel>Spreadsheet Data for Node ID {$portfolio.spreadsheetNode}:</TextLabel>
|
|
||||||
</LayoutRow>
|
|
||||||
<LayoutCol class="body" scrollableY={true}>
|
|
||||||
<WidgetLayout layout={$portfolio.spreadsheetWidgets} />
|
|
||||||
</LayoutCol>
|
|
||||||
</LayoutCol>
|
|
||||||
|
|
||||||
<style lang="scss" global>
|
|
||||||
.spreadsheet {
|
|
||||||
flex-grow: 1;
|
|
||||||
padding: 4px;
|
|
||||||
|
|
||||||
.control-bar {
|
|
||||||
height: 32px;
|
|
||||||
--widget-height: 24px;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
|
|
||||||
.text-label {
|
|
||||||
margin: calc((24px - var(--widget-height)) / 2 + 4px) 0;
|
|
||||||
min-height: var(--widget-height);
|
|
||||||
line-height: var(--widget-height);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
table {
|
|
||||||
margin: 0 -4px;
|
|
||||||
width: calc(100% + 2 * 4px);
|
|
||||||
margin-top: 8px;
|
|
||||||
|
|
||||||
.text-label {
|
|
||||||
white-space: wrap;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
// Imports/Export are stored at a key value of 0
|
// Imports/Export are stored at a key value of 0
|
||||||
|
|
||||||
$: gridSpacing = calculateGridSpacing($nodeGraph.transform.scale);
|
$: gridSpacing = calculateGridSpacing($nodeGraph.transform.scale);
|
||||||
$: dotRadius = 1 + Math.floor($nodeGraph.transform.scale - 0.5 + 0.001) / 2;
|
$: gridDotRadius = 1 + Math.floor($nodeGraph.transform.scale - 0.5 + 0.001) / 2;
|
||||||
|
|
||||||
let inputElement: HTMLInputElement;
|
let inputElement: HTMLInputElement;
|
||||||
let hoveringImportIndex: number | undefined = undefined;
|
let hoveringImportIndex: number | undefined = undefined;
|
||||||
@@ -192,7 +192,7 @@
|
|||||||
return output.connectedTo
|
return output.connectedTo
|
||||||
.map((inputConnector) => {
|
.map((inputConnector) => {
|
||||||
if ((inputConnector as Node).nodeId === undefined) return `Connected to export index ${inputConnector.index}`;
|
if ((inputConnector as Node).nodeId === undefined) return `Connected to export index ${inputConnector.index}`;
|
||||||
return `Connected to ${(inputConnector as Node).nodeId}, port index ${inputConnector.index}`;
|
return `Connected to ${(inputConnector as Node).nodeId}, connector index ${inputConnector.index}`;
|
||||||
})
|
})
|
||||||
.join("\n");
|
.join("\n");
|
||||||
}
|
}
|
||||||
@@ -200,7 +200,7 @@
|
|||||||
function inputConnectedToText(input: FrontendGraphInput): string {
|
function inputConnectedToText(input: FrontendGraphInput): string {
|
||||||
if (input.connectedTo === undefined) return "Connected to nothing";
|
if (input.connectedTo === undefined) return "Connected to nothing";
|
||||||
if ((input.connectedTo as Node).nodeId === undefined) return `Connected to import index ${input.connectedTo.index}`;
|
if ((input.connectedTo as Node).nodeId === undefined) return `Connected to import index ${input.connectedTo.index}`;
|
||||||
return `Connected to ${(input.connectedTo as Node).nodeId}, port index ${input.connectedTo.index}`;
|
return `Connected to ${(input.connectedTo as Node).nodeId}, connector index ${input.connectedTo.index}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function primaryOutputConnectedToLayer(node: FrontendNode): boolean {
|
function primaryOutputConnectedToLayer(node: FrontendNode): boolean {
|
||||||
@@ -238,7 +238,7 @@
|
|||||||
style:--grid-spacing={`${gridSpacing}px`}
|
style:--grid-spacing={`${gridSpacing}px`}
|
||||||
style:--grid-offset-x={`${$nodeGraph.transform.x}px`}
|
style:--grid-offset-x={`${$nodeGraph.transform.x}px`}
|
||||||
style:--grid-offset-y={`${$nodeGraph.transform.y}px`}
|
style:--grid-offset-y={`${$nodeGraph.transform.y}px`}
|
||||||
style:--dot-radius={`${dotRadius}px`}
|
style:--grid-dot-radius={`${gridDotRadius}px`}
|
||||||
data-node-graph
|
data-node-graph
|
||||||
>
|
>
|
||||||
<!-- Right click menu for adding nodes -->
|
<!-- Right click menu for adding nodes -->
|
||||||
@@ -298,8 +298,8 @@
|
|||||||
{#each $nodeGraph.clickTargets.layerClickTargets as pathString}
|
{#each $nodeGraph.clickTargets.layerClickTargets as pathString}
|
||||||
<path class="layer" d={pathString} />
|
<path class="layer" d={pathString} />
|
||||||
{/each}
|
{/each}
|
||||||
{#each $nodeGraph.clickTargets.portClickTargets as pathString}
|
{#each $nodeGraph.clickTargets.connectorClickTargets as pathString}
|
||||||
<path class="port" d={pathString} />
|
<path class="connector" d={pathString} />
|
||||||
{/each}
|
{/each}
|
||||||
{#each $nodeGraph.clickTargets.iconClickTargets as pathString}
|
{#each $nodeGraph.clickTargets.iconClickTargets as pathString}
|
||||||
<path class="visibility" d={pathString} />
|
<path class="visibility" d={pathString} />
|
||||||
@@ -332,14 +332,14 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Import and Export ports -->
|
<!-- Import and Export connectors -->
|
||||||
<div class="imports-and-exports" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
|
<div class="imports-and-exports" style:transform-origin={`0 0`} style:transform={`translate(${$nodeGraph.transform.x}px, ${$nodeGraph.transform.y}px) scale(${$nodeGraph.transform.scale})`}>
|
||||||
{#each $nodeGraph.imports as { outputMetadata, position }, index}
|
{#each $nodeGraph.imports as { outputMetadata, position }, index}
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 8 8"
|
viewBox="0 0 8 8"
|
||||||
class="port"
|
class="connector"
|
||||||
data-port="output"
|
data-connector="output"
|
||||||
data-datatype={outputMetadata.dataType}
|
data-datatype={outputMetadata.dataType}
|
||||||
style:--data-color={`var(--color-data-${outputMetadata.dataType.toLowerCase()})`}
|
style:--data-color={`var(--color-data-${outputMetadata.dataType.toLowerCase()})`}
|
||||||
style:--data-color-dim={`var(--color-data-${outputMetadata.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${outputMetadata.dataType.toLowerCase()}-dim)`}
|
||||||
@@ -411,8 +411,8 @@
|
|||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 8 8"
|
viewBox="0 0 8 8"
|
||||||
class="port"
|
class="connector"
|
||||||
data-port="input"
|
data-connector="input"
|
||||||
data-datatype={inputMetadata.dataType}
|
data-datatype={inputMetadata.dataType}
|
||||||
style:--data-color={`var(--color-data-${inputMetadata.dataType.toLowerCase()})`}
|
style:--data-color={`var(--color-data-${inputMetadata.dataType.toLowerCase()})`}
|
||||||
style:--data-color-dim={`var(--color-data-${inputMetadata.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${inputMetadata.dataType.toLowerCase()}-dim)`}
|
||||||
@@ -521,8 +521,8 @@
|
|||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 8 12"
|
viewBox="0 0 8 12"
|
||||||
class="port top"
|
class="connector top"
|
||||||
data-port="output"
|
data-connector="output"
|
||||||
data-datatype={node.primaryOutput.dataType}
|
data-datatype={node.primaryOutput.dataType}
|
||||||
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
|
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
|
||||||
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
|
||||||
@@ -542,8 +542,8 @@
|
|||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 8 12"
|
viewBox="0 0 8 12"
|
||||||
class="port bottom"
|
class="connector bottom"
|
||||||
data-port="input"
|
data-connector="input"
|
||||||
data-datatype={node.primaryInput?.dataType}
|
data-datatype={node.primaryInput?.dataType}
|
||||||
style:--data-color={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()})`}
|
style:--data-color={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()})`}
|
||||||
style:--data-color-dim={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${(node.primaryInput?.dataType || "General").toLowerCase()}-dim)`}
|
||||||
@@ -561,14 +561,14 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<!-- Layer input port (from left) -->
|
<!-- Layer input connector (from left) -->
|
||||||
{#if node.exposedInputs.length > 0}
|
{#if node.exposedInputs.length > 0}
|
||||||
<div class="input ports">
|
<div class="input connectors">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 8 8"
|
viewBox="0 0 8 8"
|
||||||
class="port"
|
class="connector"
|
||||||
data-port="input"
|
data-connector="input"
|
||||||
data-datatype={stackDataInput.dataType}
|
data-datatype={stackDataInput.dataType}
|
||||||
style:--data-color={`var(--color-data-${stackDataInput.dataType.toLowerCase()})`}
|
style:--data-color={`var(--color-data-${stackDataInput.dataType.toLowerCase()})`}
|
||||||
style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${stackDataInput.dataType.toLowerCase()}-dim)`}
|
||||||
@@ -679,14 +679,14 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<!-- Input ports -->
|
<!-- Input connectors -->
|
||||||
<div class="input ports">
|
<div class="input connectors">
|
||||||
{#if node.primaryInput?.dataType}
|
{#if node.primaryInput?.dataType}
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 8 8"
|
viewBox="0 0 8 8"
|
||||||
class="port primary-port"
|
class="connector primary-connector"
|
||||||
data-port="input"
|
data-connector="input"
|
||||||
data-datatype={node.primaryInput?.dataType}
|
data-datatype={node.primaryInput?.dataType}
|
||||||
style:--data-color={`var(--color-data-${node.primaryInput.dataType.toLowerCase()})`}
|
style:--data-color={`var(--color-data-${node.primaryInput.dataType.toLowerCase()})`}
|
||||||
style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${node.primaryInput.dataType.toLowerCase()}-dim)`}
|
||||||
@@ -704,8 +704,8 @@
|
|||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 8 8"
|
viewBox="0 0 8 8"
|
||||||
class="port"
|
class="connector"
|
||||||
data-port="input"
|
data-connector="input"
|
||||||
data-datatype={secondary.dataType}
|
data-datatype={secondary.dataType}
|
||||||
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
|
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
|
||||||
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
|
||||||
@@ -720,14 +720,14 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<!-- Output ports -->
|
<!-- Output connectors -->
|
||||||
<div class="output ports">
|
<div class="output connectors">
|
||||||
{#if node.primaryOutput}
|
{#if node.primaryOutput}
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 8 8"
|
viewBox="0 0 8 8"
|
||||||
class="port primary-port"
|
class="connector primary-connector"
|
||||||
data-port="output"
|
data-connector="output"
|
||||||
data-datatype={node.primaryOutput.dataType}
|
data-datatype={node.primaryOutput.dataType}
|
||||||
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
|
style:--data-color={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()})`}
|
||||||
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${node.primaryOutput.dataType.toLowerCase()}-dim)`}
|
||||||
@@ -744,8 +744,8 @@
|
|||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 8 8"
|
viewBox="0 0 8 8"
|
||||||
class="port"
|
class="connector"
|
||||||
data-port="output"
|
data-connector="output"
|
||||||
data-datatype={secondary.dataType}
|
data-datatype={secondary.dataType}
|
||||||
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
|
style:--data-color={`var(--color-data-${secondary.dataType.toLowerCase()})`}
|
||||||
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
|
style:--data-color-dim={`var(--color-data-${secondary.dataType.toLowerCase()}-dim)`}
|
||||||
@@ -801,8 +801,8 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-size: var(--grid-spacing) var(--grid-spacing);
|
background-size: var(--grid-spacing) var(--grid-spacing);
|
||||||
background-position: calc(var(--grid-offset-x) - var(--dot-radius)) calc(var(--grid-offset-y) - var(--dot-radius));
|
background-position: calc(var(--grid-offset-x) - var(--grid-dot-radius)) calc(var(--grid-offset-y) - var(--grid-dot-radius));
|
||||||
background-image: radial-gradient(circle at var(--dot-radius) var(--dot-radius), var(--color-3-darkgray) var(--dot-radius), transparent 0);
|
background-image: radial-gradient(circle at var(--grid-dot-radius) var(--grid-dot-radius), var(--color-3-darkgray) var(--grid-dot-radius), transparent 0);
|
||||||
background-repeat: repeat;
|
background-repeat: repeat;
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
mix-blend-mode: screen;
|
mix-blend-mode: screen;
|
||||||
@@ -859,7 +859,7 @@
|
|||||||
stroke: blue;
|
stroke: blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
.port {
|
.connector {
|
||||||
stroke: green;
|
stroke: green;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -898,11 +898,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.imports-and-exports {
|
.imports-and-exports {
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
// Keeps the connectors above the wires
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
.port {
|
.connector {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
@@ -1008,7 +1010,7 @@
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
bottom: calc(100% + 12px);
|
bottom: calc(100% + 12px);
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
transition: opacity 0.2s ease-in-out;
|
transition: opacity 0.2s;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
|
||||||
// Tail
|
// Tail
|
||||||
@@ -1040,7 +1042,7 @@
|
|||||||
-webkit-user-select: text;
|
-webkit-user-select: text;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
transition:
|
transition:
|
||||||
opacity 0.2s ease-in-out,
|
opacity 0.2s,
|
||||||
z-index 0s 0.2s;
|
z-index 0s 0.2s;
|
||||||
|
|
||||||
&::selection {
|
&::selection {
|
||||||
@@ -1088,8 +1090,10 @@
|
|||||||
border: 1px dashed var(--data-color);
|
border: 1px dashed var(--data-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ports {
|
.connectors {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
// Keeps the connectors above the wires
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
&.input {
|
&.input {
|
||||||
left: -3px;
|
left: -3px;
|
||||||
@@ -1100,7 +1104,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.port {
|
.connector {
|
||||||
// Double the intended value because of margin collapsing, but for the first and last we divide it by two as intended
|
// Double the intended value because of margin collapsing, but for the first and last we divide it by two as intended
|
||||||
margin: calc(24px - 8px) 0;
|
margin: calc(24px - 8px) 0;
|
||||||
width: 8px;
|
width: 8px;
|
||||||
@@ -1117,7 +1121,7 @@
|
|||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
--extra-width-to-reach-grid-multiple: 8px;
|
--extra-width-to-reach-grid-multiple: 8px;
|
||||||
--node-chain-area-left-extension: 0;
|
--node-chain-area-left-extension: 0;
|
||||||
// Keep this equation in sync with the equivalent one in the Svelte template `<clipPath><path d="layerBorderMask(...)" /></clipPath>` above, as well as the `left` port offset CSS rule above in `.ports.input` above.
|
// Keep this equation in sync with the equivalent one in the Svelte template `<clipPath><path d="layerBorderMask(...)" /></clipPath>` above, as well as the `left` connector offset CSS rule above in `.connectors.input` above.
|
||||||
width: calc((var(--layer-area-width) - 0.5) * 24px);
|
width: calc((var(--layer-area-width) - 0.5) * 24px);
|
||||||
padding-left: calc(var(--node-chain-area-left-extension) * 24px);
|
padding-left: calc(var(--node-chain-area-left-extension) * 24px);
|
||||||
margin-left: calc((0.5 - var(--node-chain-area-left-extension)) * 24px);
|
margin-left: calc((0.5 - var(--node-chain-area-left-extension)) * 24px);
|
||||||
@@ -1141,8 +1145,10 @@
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
position: relative;
|
position: relative;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 72px;
|
|
||||||
height: 48px;
|
height: 48px;
|
||||||
|
// We shorten the width by 1px on the left and right so the inner thumbnail graphic maintains a perfect 3:2 aspect ratio
|
||||||
|
width: calc(72px - 2px);
|
||||||
|
margin: 0 1px;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: "";
|
content: "";
|
||||||
@@ -1153,7 +1159,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&::before,
|
&::before,
|
||||||
svg:not(.port) {
|
svg:not(.connector) {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
@@ -1163,7 +1169,7 @@
|
|||||||
height: calc(100% - 2px);
|
height: calc(100% - 2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.port {
|
.connector {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
left: 0;
|
left: 0;
|
||||||
@@ -1211,21 +1217,21 @@
|
|||||||
right: -12px;
|
right: -12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input.ports {
|
.input.connectors {
|
||||||
left: calc(-3px + var(--node-chain-area-left-extension) * 24px - 36px);
|
left: calc(-3px + var(--node-chain-area-left-extension) * 24px - 36px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.solo-drag-grip,
|
.solo-drag-grip,
|
||||||
.visibility,
|
.visibility,
|
||||||
.input.ports,
|
.input.connectors,
|
||||||
.input.ports .port {
|
.input.connectors .connector {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin: auto 0;
|
margin: auto 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input.ports .port {
|
.input.connectors .connector {
|
||||||
left: 24px;
|
left: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1259,11 +1265,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.port {
|
.connector {
|
||||||
&:first-of-type {
|
&:first-of-type {
|
||||||
margin-top: calc((24px - 8px) / 2);
|
margin-top: calc((24px - 8px) / 2);
|
||||||
|
|
||||||
&:not(.primary-port) {
|
&:not(.primary-connector) {
|
||||||
margin-top: calc((24px - 8px) / 2 + 24px);
|
margin-top: calc((24px - 8px) / 2 + 24px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte";
|
import TextInput from "@graphite/components/widgets/inputs/TextInput.svelte";
|
||||||
import WorkingColorsInput from "@graphite/components/widgets/inputs/WorkingColorsInput.svelte";
|
import WorkingColorsInput from "@graphite/components/widgets/inputs/WorkingColorsInput.svelte";
|
||||||
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte";
|
||||||
|
import ImageLabel from "@graphite/components/widgets/labels/ImageLabel.svelte";
|
||||||
import Separator from "@graphite/components/widgets/labels/Separator.svelte";
|
import Separator from "@graphite/components/widgets/labels/Separator.svelte";
|
||||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||||
@@ -124,6 +125,10 @@
|
|||||||
{#if iconLabel}
|
{#if iconLabel}
|
||||||
<IconLabel {...exclude(iconLabel)} />
|
<IconLabel {...exclude(iconLabel)} />
|
||||||
{/if}
|
{/if}
|
||||||
|
{@const imageLabel = narrowWidgetProps(component.props, "ImageLabel")}
|
||||||
|
{#if imageLabel}
|
||||||
|
<ImageLabel {...exclude(imageLabel)} />
|
||||||
|
{/if}
|
||||||
{@const imageButton = narrowWidgetProps(component.props, "ImageButton")}
|
{@const imageButton = narrowWidgetProps(component.props, "ImageButton")}
|
||||||
{#if imageButton}
|
{#if imageButton}
|
||||||
<ImageButton {...exclude(imageButton)} action={() => widgetValueCommitAndUpdate(index, undefined)} />
|
<ImageButton {...exclude(imageButton)} action={() => widgetValueCommitAndUpdate(index, undefined)} />
|
||||||
|
|||||||
@@ -17,14 +17,15 @@
|
|||||||
.join(" ");
|
.join(" ");
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<img src={IMAGE_BASE64_STRINGS[image]} style:width style:height class={`image-label ${className} ${extraClasses}`.trim()} title={tooltip} alt="" on:click={action} />
|
<img src={IMAGE_BASE64_STRINGS[image]} style:width style:height class={`image-button ${className} ${extraClasses}`.trim()} title={tooltip} alt="" on:click={action} />
|
||||||
|
|
||||||
<style lang="scss" global>
|
<style lang="scss" global>
|
||||||
.image-label {
|
.image-button {
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
border-radius: 2px;
|
||||||
|
|
||||||
+ .image-label.image-label {
|
+ .image-button.image-button {
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from "svelte";
|
import { createEventDispatcher } from "svelte";
|
||||||
|
|
||||||
import type { FillChoice } from "@graphite/messages";
|
import type { FillChoice, MenuDirection } from "@graphite/messages";
|
||||||
import { Color, contrastingOutlineFactor, Gradient } from "@graphite/messages";
|
import { Color, contrastingOutlineFactor, Gradient } from "@graphite/messages";
|
||||||
|
|
||||||
import ColorPicker from "@graphite/components/floating-menus/ColorPicker.svelte";
|
import ColorPicker from "@graphite/components/floating-menus/ColorPicker.svelte";
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{ value: FillChoice; startHistoryTransaction: undefined }>();
|
const dispatch = createEventDispatcher<{ value: FillChoice; startHistoryTransaction: undefined }>();
|
||||||
|
|
||||||
@@ -15,6 +14,7 @@
|
|||||||
export let value: FillChoice;
|
export let value: FillChoice;
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
export let allowNone = false;
|
export let allowNone = false;
|
||||||
|
export let menuDirection: MenuDirection = "Bottom";
|
||||||
// export let allowTransparency = false; // TODO: Implement
|
// export let allowTransparency = false; // TODO: Implement
|
||||||
export let tooltip: string | undefined = undefined;
|
export let tooltip: string | undefined = undefined;
|
||||||
|
|
||||||
@@ -25,16 +25,18 @@
|
|||||||
$: transparency = value instanceof Gradient ? value.stops.some((stop) => stop.color.alpha < 1) : value.alpha < 1;
|
$: transparency = value instanceof Gradient ? value.stops.some((stop) => stop.color.alpha < 1) : value.alpha < 1;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LayoutCol class="color-button" classes={{ open, disabled, none, transparency, outlined }} {tooltip}>
|
<LayoutCol class="color-button" classes={{ open, disabled, none, transparency, outlined, "direction-top": menuDirection === "Top" }} {tooltip}>
|
||||||
<button {disabled} style:--chosen-gradient={chosenGradient} style:--outline-amount={outlineFactor} on:click={() => (open = true)} tabindex="0" data-floating-menu-spawner>
|
<button style:--chosen-gradient={chosenGradient} style:--outline-amount={outlineFactor} on:click={() => (open = true)} tabindex="0" data-floating-menu-spawner>
|
||||||
{#if disabled && value instanceof Color && !value.none}
|
<!-- {#if disabled && value instanceof Color && !value.none}
|
||||||
<TextLabel>sRGB</TextLabel>
|
<TextLabel>sRGB</TextLabel>
|
||||||
{/if}
|
{/if} -->
|
||||||
</button>
|
</button>
|
||||||
<ColorPicker
|
<ColorPicker
|
||||||
{open}
|
{open}
|
||||||
on:open={({ detail }) => (open = detail)}
|
{disabled}
|
||||||
colorOrGradient={value}
|
colorOrGradient={value}
|
||||||
|
direction={menuDirection || "Bottom"}
|
||||||
|
on:open={({ detail }) => (open = detail)}
|
||||||
on:colorOrGradient={({ detail }) => {
|
on:colorOrGradient={({ detail }) => {
|
||||||
value = detail;
|
value = detail;
|
||||||
dispatch("value", detail);
|
dispatch("value", detail);
|
||||||
@@ -137,5 +139,9 @@
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.direction-top > .floating-menu {
|
||||||
|
bottom: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
const dispatch = createEventDispatcher<{ activeMarkerIndexChange: number | undefined; gradient: Gradient; dragging: boolean }>();
|
const dispatch = createEventDispatcher<{ activeMarkerIndexChange: number | undefined; gradient: Gradient; dragging: boolean }>();
|
||||||
|
|
||||||
export let gradient: Gradient;
|
export let gradient: Gradient;
|
||||||
|
export let disabled = false;
|
||||||
export let activeMarkerIndex = 0 as number | undefined;
|
export let activeMarkerIndex = 0 as number | undefined;
|
||||||
// export let disabled = false;
|
// export let disabled = false;
|
||||||
// export let tooltip: string | undefined = undefined;
|
// export let tooltip: string | undefined = undefined;
|
||||||
@@ -22,6 +23,8 @@
|
|||||||
let deletionRestore: boolean | undefined = undefined;
|
let deletionRestore: boolean | undefined = undefined;
|
||||||
|
|
||||||
function markerPointerDown(e: PointerEvent, index: number) {
|
function markerPointerDown(e: PointerEvent, index: number) {
|
||||||
|
if (disabled) return;
|
||||||
|
|
||||||
// Left-click to select and begin potentially dragging
|
// Left-click to select and begin potentially dragging
|
||||||
if (e.button === BUTTON_LEFT) {
|
if (e.button === BUTTON_LEFT) {
|
||||||
activeMarkerIndex = index;
|
activeMarkerIndex = index;
|
||||||
@@ -47,6 +50,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function insertStop(e: MouseEvent) {
|
function insertStop(e: MouseEvent) {
|
||||||
|
if (disabled) return;
|
||||||
|
|
||||||
if (e.button !== BUTTON_LEFT) return;
|
if (e.button !== BUTTON_LEFT) return;
|
||||||
|
|
||||||
let position = markerPosition(e);
|
let position = markerPosition(e);
|
||||||
@@ -79,6 +84,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function deleteStop(e: KeyboardEvent) {
|
function deleteStop(e: KeyboardEvent) {
|
||||||
|
if (disabled) return;
|
||||||
|
|
||||||
if (e.key !== "Delete" && e.key !== "Backspace") return;
|
if (e.key !== "Delete" && e.key !== "Backspace") return;
|
||||||
if (activeMarkerIndex === undefined) return;
|
if (activeMarkerIndex === undefined) return;
|
||||||
|
|
||||||
@@ -88,6 +95,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function deleteStopByIndex(index: number) {
|
function deleteStopByIndex(index: number) {
|
||||||
|
if (disabled) return;
|
||||||
|
|
||||||
if (gradient.stops.length <= 2) return;
|
if (gradient.stops.length <= 2) return;
|
||||||
|
|
||||||
gradient.stops.splice(index, 1);
|
gradient.stops.splice(index, 1);
|
||||||
@@ -103,6 +112,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function moveMarker(e: PointerEvent, index: number) {
|
function moveMarker(e: PointerEvent, index: number) {
|
||||||
|
if (disabled) return;
|
||||||
|
|
||||||
// Just in case the mouseup event is lost
|
// Just in case the mouseup event is lost
|
||||||
if (e.buttons === 0) stopDrag();
|
if (e.buttons === 0) stopDrag();
|
||||||
|
|
||||||
@@ -120,6 +131,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function setPosition(index: number, position: number) {
|
export function setPosition(index: number, position: number) {
|
||||||
|
if (disabled) return;
|
||||||
|
|
||||||
const active = gradient.stops[index];
|
const active = gradient.stops[index];
|
||||||
active.position = position;
|
active.position = position;
|
||||||
gradient.stops.sort((a, b) => a.position - b.position);
|
gradient.stops.sort((a, b) => a.position - b.position);
|
||||||
@@ -131,6 +144,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function abortDrag() {
|
function abortDrag() {
|
||||||
|
if (disabled) return;
|
||||||
|
|
||||||
if (activeMarkerIndex === undefined) return;
|
if (activeMarkerIndex === undefined) return;
|
||||||
|
|
||||||
if (deletionRestore) {
|
if (deletionRestore) {
|
||||||
@@ -143,6 +158,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function stopDrag() {
|
function stopDrag() {
|
||||||
|
if (disabled) return;
|
||||||
|
|
||||||
removeEvents();
|
removeEvents();
|
||||||
|
|
||||||
positionRestore = undefined;
|
positionRestore = undefined;
|
||||||
@@ -152,19 +169,27 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onPointerMove(e: PointerEvent) {
|
function onPointerMove(e: PointerEvent) {
|
||||||
|
if (disabled) return;
|
||||||
|
|
||||||
if (activeMarkerIndex !== undefined) moveMarker(e, activeMarkerIndex);
|
if (activeMarkerIndex !== undefined) moveMarker(e, activeMarkerIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPointerUp() {
|
function onPointerUp() {
|
||||||
|
if (disabled) return;
|
||||||
|
|
||||||
stopDrag();
|
stopDrag();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMouseDown(e: MouseEvent) {
|
function onMouseDown(e: MouseEvent) {
|
||||||
|
if (disabled) return;
|
||||||
|
|
||||||
const BUTTONS_RIGHT = 0b0000_0010;
|
const BUTTONS_RIGHT = 0b0000_0010;
|
||||||
if (e.buttons & BUTTONS_RIGHT) abortDrag();
|
if (e.buttons & BUTTONS_RIGHT) abortDrag();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onKeyDown(e: KeyboardEvent) {
|
function onKeyDown(e: KeyboardEvent) {
|
||||||
|
if (disabled) return;
|
||||||
|
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
const element = markerTrack?.div();
|
const element = markerTrack?.div();
|
||||||
if (element) preventEscapeClosingParentFloatingMenu(element);
|
if (element) preventEscapeClosingParentFloatingMenu(element);
|
||||||
@@ -193,25 +218,28 @@
|
|||||||
document.removeEventListener("keydown", deleteStop);
|
document.removeEventListener("keydown", deleteStop);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Future design notes:
|
||||||
|
//
|
||||||
// # Backend -> Frontend
|
// # Backend -> Frontend
|
||||||
// Populate(gradient, { position, color }[], active) // The only way indexes get changed. Frontend drops marker if it's being dragged.
|
// Populate(gradient, { position, color }[], active) // The only way indexes get changed. Frontend drops marker if it's being dragged.
|
||||||
// UpdateGradient(gradient)
|
// UpdateGradient(gradient)
|
||||||
// UpdateMarkers({ index, position, color }[])
|
// UpdateMarkers({ index, position, color }[])
|
||||||
|
//
|
||||||
// # Frontend -> Backend
|
// # Frontend -> Backend
|
||||||
// SendNewActive(index)
|
// SendNewActive(index)
|
||||||
// SendPositions({ index, position }[])
|
// SendPositions({ index, position }[])
|
||||||
// AddMarker(position)
|
// AddMarker(position)
|
||||||
// RemoveMarkers(index[])
|
// RemoveMarkers(index[])
|
||||||
// ResetMarkerToDefault(index)
|
// ResetMarkerToDefault(index)
|
||||||
|
//
|
||||||
// // We need a way to encode constraints on some markers, like locking them in place or preventing reordering
|
// We need a way to encode constraints on some markers, like locking them in place or preventing reordering
|
||||||
// // We need a way to encode the allowability of adding new markers between certain markers, or preventing the deletion of certain markers
|
// We need a way to encode the allowability of adding new markers between certain markers, or preventing the deletion of certain markers
|
||||||
// // We need the ability to multi-select markers and move them all at once
|
// We need the ability to multi-select markers and move them all at once
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<LayoutCol
|
<LayoutCol
|
||||||
class="spectrum-input"
|
class="spectrum-input"
|
||||||
|
classes={{ disabled }}
|
||||||
styles={{
|
styles={{
|
||||||
"--gradient-start": gradient.firstColor()?.toHexOptionalAlpha() || "black",
|
"--gradient-start": gradient.firstColor()?.toHexOptionalAlpha() || "black",
|
||||||
"--gradient-end": gradient.lastColor()?.toHexOptionalAlpha() || "black",
|
"--gradient-end": gradient.lastColor()?.toHexOptionalAlpha() || "black",
|
||||||
@@ -232,6 +260,9 @@
|
|||||||
viewBox="0 0 12 12"
|
viewBox="0 0 12 12"
|
||||||
>
|
>
|
||||||
<path class="inner-fill" d="M10,11.5H2c-0.8,0-1.5-0.7-1.5-1.5V6.8c0-0.4,0.2-0.8,0.4-1.1L6,0.7l5.1,5.1c0.3,0.3,0.4,0.7,0.4,1.1V10C11.5,10.8,10.8,11.5,10,11.5z" />
|
<path class="inner-fill" d="M10,11.5H2c-0.8,0-1.5-0.7-1.5-1.5V6.8c0-0.4,0.2-0.8,0.4-1.1L6,0.7l5.1,5.1c0.3,0.3,0.4,0.7,0.4,1.1V10C11.5,10.8,10.8,11.5,10,11.5z" />
|
||||||
|
{#if disabled}
|
||||||
|
<path class="disabled-fill" d="M10,11.5H2c-0.8,0-1.5-0.7-1.5-1.5V6.8c0-0.4,0.2-0.8,0.4-1.1L6,0.7l5.1,5.1c0.3,0.3,0.4,0.7,0.4,1.1V10C11.5,10.8,10.8,11.5,10,11.5z" />
|
||||||
|
{/if}
|
||||||
<path
|
<path
|
||||||
class="outer-border"
|
class="outer-border"
|
||||||
d="M6,1.4L1.3,6.1C1.1,6.3,1,6.6,1,6.8V10c0,0.6,0.4,1,1,1h8c0.6,0,1-0.4,1-1V6.8c0-0.3-0.1-0.5-0.3-0.7L6,1.4M6,0l5.4,5.4C11.8,5.8,12,6.3,12,6.8V10c0,1.1-0.9,2-2,2H2c-1.1,0-2-0.9-2-2V6.8c0-0.5,0.2-1,0.6-1.4L6,0z"
|
d="M6,1.4L1.3,6.1C1.1,6.3,1,6.6,1,6.8V10c0,0.6,0.4,1,1,1h8c0.6,0,1-0.4,1-1V6.8c0-0.3-0.1-0.5-0.3-0.7L6,1.4M6,0l5.4,5.4C11.8,5.8,12,6.3,12,6.8V10c0,1.1-0.9,2-2,2H2c-1.1,0-2-0.9-2-2V6.8c0-0.5,0.2-1,0.6-1.4L6,0z"
|
||||||
@@ -269,6 +300,14 @@
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.disabled .gradient-strip {
|
||||||
|
transition: opacity 0.1s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.marker-track {
|
.marker-track {
|
||||||
margin-top: calc(24px - 16px - 12px);
|
margin-top: calc(24px - 16px - 12px);
|
||||||
margin-left: var(--marker-half-width);
|
margin-left: var(--marker-half-width);
|
||||||
@@ -294,28 +333,40 @@
|
|||||||
.outer-border {
|
.outer-border {
|
||||||
fill: var(--color-5-dullgray);
|
fill: var(--color-5-dullgray);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:not(.active) {
|
&.disabled .marker-track .marker {
|
||||||
.inner-fill:hover + .outer-border,
|
.disabled-fill {
|
||||||
.outer-border:hover {
|
opacity: 0.5;
|
||||||
fill: var(--color-6-lowergray);
|
}
|
||||||
}
|
|
||||||
|
.outer-border {
|
||||||
|
fill: var(--color-4-dimgray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.disabled) .marker-track .marker {
|
||||||
|
&:not(.active) {
|
||||||
|
.inner-fill:hover + .outer-border,
|
||||||
|
.outer-border:hover {
|
||||||
|
fill: var(--color-6-lowergray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
.inner-fill {
|
||||||
|
filter: drop-shadow(0 0 1px var(--color-2-mildblack)) drop-shadow(0 0 1px var(--color-2-mildblack));
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active {
|
// Outer border when active
|
||||||
.inner-fill {
|
.outer-border {
|
||||||
filter: drop-shadow(0 0 1px var(--color-2-mildblack)) drop-shadow(0 0 1px var(--color-2-mildblack));
|
fill: var(--color-e-nearwhite);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Outer border when active
|
.inner-fill:hover + .outer-border,
|
||||||
.outer-border {
|
.outer-border:hover {
|
||||||
fill: var(--color-e-nearwhite);
|
fill: var(--color-f-white);
|
||||||
}
|
|
||||||
|
|
||||||
.inner-fill:hover + .outer-border,
|
|
||||||
.outer-border:hover {
|
|
||||||
fill: var(--color-f-white);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
// Styling
|
// Styling
|
||||||
export let centered = false;
|
export let centered = false;
|
||||||
export let minWidth = 0;
|
export let minWidth = 0;
|
||||||
|
export let maxWidth = 0;
|
||||||
|
|
||||||
let className = "";
|
let className = "";
|
||||||
export { className as class };
|
export { className as class };
|
||||||
@@ -63,7 +64,10 @@
|
|||||||
<FieldInput
|
<FieldInput
|
||||||
class={`text-input ${className}`.trim()}
|
class={`text-input ${className}`.trim()}
|
||||||
classes={{ centered, ...classes }}
|
classes={{ centered, ...classes }}
|
||||||
styles={{ ...(minWidth > 0 ? { "min-width": `${minWidth}px` } : {}) }}
|
styles={{
|
||||||
|
...(minWidth > 0 ? { "min-width": `${minWidth}px` } : {}),
|
||||||
|
...(maxWidth > 0 ? { "max-width": `${maxWidth}px` } : {}),
|
||||||
|
}}
|
||||||
{value}
|
{value}
|
||||||
on:value
|
on:value
|
||||||
on:textFocused={onTextFocused}
|
on:textFocused={onTextFocused}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let className = "";
|
||||||
|
export { className as class };
|
||||||
|
export let classes: Record<string, boolean> = {};
|
||||||
|
|
||||||
|
export let url: string;
|
||||||
|
export let width: string | undefined;
|
||||||
|
export let height: string | undefined;
|
||||||
|
export let tooltip: string | undefined = undefined;
|
||||||
|
|
||||||
|
$: extraClasses = Object.entries(classes)
|
||||||
|
.flatMap(([className, stateName]) => (stateName ? [className] : []))
|
||||||
|
.join(" ");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<img src={url} style:width style:height class={`image-label ${className} ${extraClasses}`.trim()} title={tooltip} alt="" />
|
||||||
|
|
||||||
|
<style lang="scss" global>
|
||||||
|
.image-label {
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-image: var(--color-transparent-checkered-background);
|
||||||
|
background-size: var(--color-transparent-checkered-background-size);
|
||||||
|
background-position: var(--color-transparent-checkered-background-position);
|
||||||
|
background-repeat: var(--color-transparent-checkered-background-repeat);
|
||||||
|
|
||||||
|
+ .image-label.image-label {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
|
import Data from "@graphite/components/panels/Data.svelte";
|
||||||
import Document from "@graphite/components/panels/Document.svelte";
|
import Document from "@graphite/components/panels/Document.svelte";
|
||||||
import Layers from "@graphite/components/panels/Layers.svelte";
|
import Layers from "@graphite/components/panels/Layers.svelte";
|
||||||
import Properties from "@graphite/components/panels/Properties.svelte";
|
import Properties from "@graphite/components/panels/Properties.svelte";
|
||||||
import Spreadsheet from "@graphite/components/panels/Spreadsheet.svelte";
|
|
||||||
|
|
||||||
const PANEL_COMPONENTS = {
|
const PANEL_COMPONENTS = {
|
||||||
Document,
|
Document,
|
||||||
Layers,
|
Layers,
|
||||||
Properties,
|
Properties,
|
||||||
Spreadsheet,
|
Data,
|
||||||
};
|
};
|
||||||
type PanelType = keyof typeof PANEL_COMPONENTS;
|
type PanelType = keyof typeof PANEL_COMPONENTS;
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
/**/ root: 100,
|
/**/ root: 100,
|
||||||
/* ├─ */ content: 80,
|
/* ├─ */ content: 80,
|
||||||
/* │ ├─ */ document: 70,
|
/* │ ├─ */ document: 70,
|
||||||
/* │ └─ */ spreadsheet: 30,
|
/* │ └─ */ data: 30,
|
||||||
/* └─ */ details: 20,
|
/* └─ */ details: 20,
|
||||||
/* ├─ */ properties: 45,
|
/* ├─ */ properties: 45,
|
||||||
/* └─ */ layers: 55,
|
/* └─ */ layers: 55,
|
||||||
@@ -148,23 +148,31 @@
|
|||||||
bind:this={documentPanel}
|
bind:this={documentPanel}
|
||||||
/>
|
/>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
{#if $portfolio.spreadsheetOpen}
|
{#if $portfolio.dataPanelOpen}
|
||||||
<LayoutRow class="workspace-grid-resize-gutter" data-gutter-vertical on:pointerdown={(e) => resizePanel(e)} />
|
<LayoutRow class="workspace-grid-resize-gutter" data-gutter-vertical on:pointerdown={(e) => resizePanel(e)} />
|
||||||
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["spreadsheet"] }} data-subdivision-name="spreadsheet">
|
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["data"] }} data-subdivision-name="data">
|
||||||
<Panel panelType="Spreadsheet" tabLabels={[{ name: "Spreadsheet" }]} tabActiveIndex={0} />
|
<Panel panelType="Data" tabLabels={[{ name: "Data" }]} tabActiveIndex={0} />
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
{/if}
|
{/if}
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
<LayoutCol class="workspace-grid-resize-gutter" data-gutter-horizontal on:pointerdown={(e) => resizePanel(e)} />
|
{#if $portfolio.propertiesPanelOpen || $portfolio.layersPanelOpen}
|
||||||
<LayoutCol class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["details"] }} data-subdivision-name="details">
|
<LayoutCol class="workspace-grid-resize-gutter" data-gutter-horizontal on:pointerdown={(e) => resizePanel(e)} />
|
||||||
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["properties"] }} data-subdivision-name="properties">
|
<LayoutCol class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["details"] }} data-subdivision-name="details">
|
||||||
<Panel panelType="Properties" tabLabels={[{ name: "Properties" }]} tabActiveIndex={0} />
|
{#if $portfolio.propertiesPanelOpen}
|
||||||
</LayoutRow>
|
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["properties"] }} data-subdivision-name="properties">
|
||||||
<LayoutRow class="workspace-grid-resize-gutter" data-gutter-vertical on:pointerdown={(e) => resizePanel(e)} />
|
<Panel panelType="Properties" tabLabels={[{ name: "Properties" }]} tabActiveIndex={0} />
|
||||||
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["layers"] }} data-subdivision-name="layers">
|
</LayoutRow>
|
||||||
<Panel panelType="Layers" tabLabels={[{ name: "Layers" }]} tabActiveIndex={0} />
|
{/if}
|
||||||
</LayoutRow>
|
{#if $portfolio.propertiesPanelOpen && $portfolio.layersPanelOpen}
|
||||||
</LayoutCol>
|
<LayoutRow class="workspace-grid-resize-gutter" data-gutter-vertical on:pointerdown={(e) => resizePanel(e)} />
|
||||||
|
{/if}
|
||||||
|
{#if $portfolio.layersPanelOpen}
|
||||||
|
<LayoutRow class="workspace-grid-subdivision" styles={{ "flex-grow": panelSizes["layers"] }} data-subdivision-name="layers">
|
||||||
|
<Panel panelType="Layers" tabLabels={[{ name: "Layers" }]} tabActiveIndex={0} />
|
||||||
|
</LayoutRow>
|
||||||
|
{/if}
|
||||||
|
</LayoutCol>
|
||||||
|
{/if}
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
{#if $dialog.visible}
|
{#if $dialog.visible}
|
||||||
<Dialog />
|
<Dialog />
|
||||||
|
|||||||
+54
-17
@@ -180,7 +180,7 @@ export class Box {
|
|||||||
export type FrontendClickTargets = {
|
export type FrontendClickTargets = {
|
||||||
readonly nodeClickTargets: string[];
|
readonly nodeClickTargets: string[];
|
||||||
readonly layerClickTargets: string[];
|
readonly layerClickTargets: string[];
|
||||||
readonly portClickTargets: string[];
|
readonly connectorClickTargets: string[];
|
||||||
readonly iconClickTargets: string[];
|
readonly iconClickTargets: string[];
|
||||||
readonly allNodesBoundingBox: string;
|
readonly allNodesBoundingBox: string;
|
||||||
readonly importExportsBoundingBox: string;
|
readonly importExportsBoundingBox: string;
|
||||||
@@ -192,7 +192,7 @@ export type ContextMenuInformation = {
|
|||||||
contextMenuData: "CreateNode" | { type: "CreateNode"; compatibleType: string } | { nodeId: bigint; currentlyIsNode: boolean };
|
contextMenuData: "CreateNode" | { type: "CreateNode"; compatibleType: string } | { nodeId: bigint; currentlyIsNode: boolean };
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FrontendGraphDataType = "General" | "Raster" | "Vector" | "Number" | "Graphic" | "Artboard";
|
export type FrontendGraphDataType = "General" | "Number" | "Artboard" | "Graphic" | "Raster" | "Vector" | "Color";
|
||||||
|
|
||||||
export class Node {
|
export class Node {
|
||||||
readonly index!: bigint;
|
readonly index!: bigint;
|
||||||
@@ -763,10 +763,16 @@ export class UpdateGraphFadeArtwork extends JsMessage {
|
|||||||
readonly percentage!: number;
|
readonly percentage!: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UpdateSpreadsheetState extends JsMessage {
|
export class UpdateDataPanelState extends JsMessage {
|
||||||
readonly open!: boolean;
|
readonly open!: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
readonly node!: bigint | undefined;
|
export class UpdatePropertiesPanelState extends JsMessage {
|
||||||
|
readonly open!: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateLayersPanelState extends JsMessage {
|
||||||
|
readonly open!: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UpdateMouseCursor extends JsMessage {
|
export class UpdateMouseCursor extends JsMessage {
|
||||||
@@ -981,9 +987,11 @@ export class ColorInput extends WidgetProps {
|
|||||||
})
|
})
|
||||||
value!: FillChoice;
|
value!: FillChoice;
|
||||||
|
|
||||||
|
allowNone!: boolean;
|
||||||
|
|
||||||
disabled!: boolean;
|
disabled!: boolean;
|
||||||
|
|
||||||
allowNone!: boolean;
|
menuDirection!: MenuDirection | undefined;
|
||||||
|
|
||||||
// allowTransparency!: boolean; // TODO: Implement
|
// allowTransparency!: boolean; // TODO: Implement
|
||||||
|
|
||||||
@@ -1140,6 +1148,19 @@ export class ImageButton extends WidgetProps {
|
|||||||
tooltip!: string | undefined;
|
tooltip!: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ImageLabel extends WidgetProps {
|
||||||
|
url!: string;
|
||||||
|
|
||||||
|
@Transform(({ value }: { value: string }) => value || undefined)
|
||||||
|
width!: string | undefined;
|
||||||
|
|
||||||
|
@Transform(({ value }: { value: string }) => value || undefined)
|
||||||
|
height!: string | undefined;
|
||||||
|
|
||||||
|
@Transform(({ value }: { value: string }) => value || undefined)
|
||||||
|
tooltip!: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export type NumberInputIncrementBehavior = "Add" | "Multiply" | "Callback" | "None";
|
export type NumberInputIncrementBehavior = "Add" | "Multiply" | "Callback" | "None";
|
||||||
export type NumberInputMode = "Increment" | "Range";
|
export type NumberInputMode = "Increment" | "Range";
|
||||||
|
|
||||||
@@ -1332,6 +1353,8 @@ export class TextInput extends WidgetProps {
|
|||||||
|
|
||||||
minWidth!: number;
|
minWidth!: number;
|
||||||
|
|
||||||
|
maxWidth!: number;
|
||||||
|
|
||||||
@Transform(({ value }: { value: string }) => value || undefined)
|
@Transform(({ value }: { value: string }) => value || undefined)
|
||||||
tooltip!: string | undefined;
|
tooltip!: string | undefined;
|
||||||
}
|
}
|
||||||
@@ -1383,6 +1406,7 @@ const widgetSubTypes = [
|
|||||||
{ value: FontInput, name: "FontInput" },
|
{ value: FontInput, name: "FontInput" },
|
||||||
{ value: IconButton, name: "IconButton" },
|
{ value: IconButton, name: "IconButton" },
|
||||||
{ value: ImageButton, name: "ImageButton" },
|
{ value: ImageButton, name: "ImageButton" },
|
||||||
|
{ value: ImageLabel, name: "ImageLabel" },
|
||||||
{ value: IconLabel, name: "IconLabel" },
|
{ value: IconLabel, name: "IconLabel" },
|
||||||
{ value: NodeCatalog, name: "NodeCatalog" },
|
{ value: NodeCatalog, name: "NodeCatalog" },
|
||||||
{ value: NumberInput, name: "NumberInput" },
|
{ value: NumberInput, name: "NumberInput" },
|
||||||
@@ -1472,11 +1496,11 @@ export function patchWidgetLayout(layout: /* &mut */ WidgetLayout, updates: Widg
|
|||||||
|
|
||||||
updates.diff.forEach((update) => {
|
updates.diff.forEach((update) => {
|
||||||
// Find the object where the diff applies to
|
// Find the object where the diff applies to
|
||||||
const diffObject = update.widgetPath.reduce((targetLayout, index) => {
|
const diffObject = update.widgetPath.reduce((targetLayout: UIItem | undefined, index: number): UIItem | undefined => {
|
||||||
if ("columnWidgets" in targetLayout) return targetLayout.columnWidgets[index];
|
if (targetLayout && "columnWidgets" in targetLayout) return targetLayout.columnWidgets[index];
|
||||||
if ("rowWidgets" in targetLayout) return targetLayout.rowWidgets[index];
|
if (targetLayout && "rowWidgets" in targetLayout) return targetLayout.rowWidgets[index];
|
||||||
if ("tableWidgets" in targetLayout) return targetLayout.tableWidgets[index];
|
if (targetLayout && "tableWidgets" in targetLayout) return targetLayout.tableWidgets[index];
|
||||||
if ("layout" in targetLayout) return targetLayout.layout[index];
|
if (targetLayout && "layout" in targetLayout) return targetLayout.layout[index];
|
||||||
if (targetLayout instanceof Widget) {
|
if (targetLayout instanceof Widget) {
|
||||||
if (targetLayout.props.kind === "PopoverButton" && targetLayout.props instanceof PopoverButton && targetLayout.props.popoverLayout) {
|
if (targetLayout.props.kind === "PopoverButton" && targetLayout.props instanceof PopoverButton && targetLayout.props.popoverLayout) {
|
||||||
return targetLayout.props.popoverLayout[index];
|
return targetLayout.props.popoverLayout[index];
|
||||||
@@ -1487,10 +1511,21 @@ export function patchWidgetLayout(layout: /* &mut */ WidgetLayout, updates: Widg
|
|||||||
}
|
}
|
||||||
// This is a path traversal so we can assume from the backend that it exists
|
// This is a path traversal so we can assume from the backend that it exists
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
if ("action" in targetLayout) return targetLayout.children![index];
|
if (targetLayout && "action" in targetLayout) return targetLayout.children![index];
|
||||||
return targetLayout[index];
|
|
||||||
|
return targetLayout?.[index];
|
||||||
}, layout.layout as UIItem);
|
}, layout.layout as UIItem);
|
||||||
|
|
||||||
|
// Exit if we failed to produce a valid patch for the existing layout.
|
||||||
|
// This means that the backend assumed an existing layout that doesn't exist in the frontend. This can happen, for
|
||||||
|
// example, if a panel is destroyed in the frontend but was never cleared in the backend, so the next time the backend
|
||||||
|
// tries to update the layout, it attempts to insert only the changes against the old layout that no longer exists.
|
||||||
|
if (diffObject === undefined) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error("In `patchWidgetLayout`, the `diffObject` is undefined. The layout has not been updated. See the source code comment above this error for hints.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If this is a list with a length, then set the length to 0 to clear the list
|
// If this is a list with a length, then set the length to 0 to clear the list
|
||||||
if ("length" in diffObject) {
|
if ("length" in diffObject) {
|
||||||
diffObject.length = 0;
|
diffObject.length = 0;
|
||||||
@@ -1613,9 +1648,9 @@ export class UpdateMenuBarLayout extends JsMessage {
|
|||||||
|
|
||||||
export class UpdateNodeGraphControlBarLayout extends WidgetDiffUpdate {}
|
export class UpdateNodeGraphControlBarLayout extends WidgetDiffUpdate {}
|
||||||
|
|
||||||
export class UpdatePropertyPanelSectionsLayout extends WidgetDiffUpdate {}
|
export class UpdatePropertiesPanelLayout extends WidgetDiffUpdate {}
|
||||||
|
|
||||||
export class UpdateSpreadsheetLayout extends WidgetDiffUpdate {}
|
export class UpdateDataPanelLayout extends WidgetDiffUpdate {}
|
||||||
|
|
||||||
export class UpdateToolOptionsLayout extends WidgetDiffUpdate {}
|
export class UpdateToolOptionsLayout extends WidgetDiffUpdate {}
|
||||||
|
|
||||||
@@ -1712,9 +1747,11 @@ export const messageMakers: Record<string, MessageMaker> = {
|
|||||||
UpdateNodeThumbnail,
|
UpdateNodeThumbnail,
|
||||||
UpdateOpenDocumentsList,
|
UpdateOpenDocumentsList,
|
||||||
UpdatePlatform,
|
UpdatePlatform,
|
||||||
UpdatePropertyPanelSectionsLayout,
|
UpdatePropertiesPanelLayout,
|
||||||
UpdateSpreadsheetLayout,
|
UpdateDataPanelLayout,
|
||||||
UpdateSpreadsheetState,
|
UpdateDataPanelState,
|
||||||
|
UpdatePropertiesPanelState,
|
||||||
|
UpdateLayersPanelState,
|
||||||
UpdateToolOptionsLayout,
|
UpdateToolOptionsLayout,
|
||||||
UpdateToolShelfLayout,
|
UpdateToolShelfLayout,
|
||||||
UpdateViewportHolePunch,
|
UpdateViewportHolePunch,
|
||||||
|
|||||||
@@ -13,10 +13,9 @@ import {
|
|||||||
TriggerOpenDocument,
|
TriggerOpenDocument,
|
||||||
UpdateActiveDocument,
|
UpdateActiveDocument,
|
||||||
UpdateOpenDocumentsList,
|
UpdateOpenDocumentsList,
|
||||||
UpdateSpreadsheetState,
|
UpdateDataPanelState,
|
||||||
defaultWidgetLayout,
|
UpdatePropertiesPanelState,
|
||||||
patchWidgetLayout,
|
UpdateLayersPanelState,
|
||||||
UpdateSpreadsheetLayout,
|
|
||||||
} from "@graphite/messages";
|
} from "@graphite/messages";
|
||||||
import { downloadFile, downloadFileBlob, upload } from "@graphite/utility-functions/files";
|
import { downloadFile, downloadFileBlob, upload } from "@graphite/utility-functions/files";
|
||||||
import { extractPixelData, rasterizeSVG } from "@graphite/utility-functions/rasterization";
|
import { extractPixelData, rasterizeSVG } from "@graphite/utility-functions/rasterization";
|
||||||
@@ -27,9 +26,9 @@ export function createPortfolioState(editor: Editor) {
|
|||||||
unsaved: false,
|
unsaved: false,
|
||||||
documents: [] as FrontendDocumentDetails[],
|
documents: [] as FrontendDocumentDetails[],
|
||||||
activeDocumentIndex: 0,
|
activeDocumentIndex: 0,
|
||||||
spreadsheetOpen: false,
|
dataPanelOpen: false,
|
||||||
spreadsheetNode: BigInt(0) as bigint | undefined,
|
propertiesPanelOpen: true,
|
||||||
spreadsheetWidgets: defaultWidgetLayout(),
|
layersPanelOpen: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set up message subscriptions on creation
|
// Set up message subscriptions on creation
|
||||||
@@ -107,16 +106,21 @@ export function createPortfolioState(editor: Editor) {
|
|||||||
// Fail silently if there's an error rasterizing the SVG, such as a zero-sized image
|
// Fail silently if there's an error rasterizing the SVG, such as a zero-sized image
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
editor.subscriptions.subscribeJsMessage(UpdateSpreadsheetState, async (updateSpreadsheetState) => {
|
editor.subscriptions.subscribeJsMessage(UpdateDataPanelState, async (updateDataPanelState) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
state.spreadsheetOpen = updateSpreadsheetState.open;
|
state.dataPanelOpen = updateDataPanelState.open;
|
||||||
state.spreadsheetNode = updateSpreadsheetState.node;
|
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
editor.subscriptions.subscribeJsMessage(UpdateSpreadsheetLayout, (updateSpreadsheetLayout) => {
|
editor.subscriptions.subscribeJsMessage(UpdatePropertiesPanelState, async (updatePropertiesPanelState) => {
|
||||||
update((state) => {
|
update((state) => {
|
||||||
patchWidgetLayout(state.spreadsheetWidgets, updateSpreadsheetLayout);
|
state.propertiesPanelOpen = updatePropertiesPanelState.open;
|
||||||
|
return state;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
editor.subscriptions.subscribeJsMessage(UpdateLayersPanelState, async (updateLayersPanelState) => {
|
||||||
|
update((state) => {
|
||||||
|
state.layersPanelOpen = updateLayersPanelState.open;
|
||||||
return state;
|
return state;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ export function createSubscriptionRouter() {
|
|||||||
subscriptions[messageType.name] = callback;
|
subscriptions[messageType.name] = callback;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const unsubscribeJsMessage = <T extends JsMessage>(messageType: new () => T) => {
|
||||||
|
delete subscriptions[messageType.name];
|
||||||
|
};
|
||||||
|
|
||||||
const handleJsMessage = (messageType: JsMessageType, messageData: Record<string, unknown>, wasm: WebAssembly.Memory, handle: EditorHandle) => {
|
const handleJsMessage = (messageType: JsMessageType, messageData: Record<string, unknown>, wasm: WebAssembly.Memory, handle: EditorHandle) => {
|
||||||
// Find the message maker for the message type, which can either be a JS class constructor or a function that returns an instance of the JS class
|
// Find the message maker for the message type, which can either be a JS class constructor or a function that returns an instance of the JS class
|
||||||
const messageMaker = messageMakers[messageType];
|
const messageMaker = messageMakers[messageType];
|
||||||
@@ -68,6 +72,7 @@ export function createSubscriptionRouter() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
subscribeJsMessage,
|
subscribeJsMessage,
|
||||||
|
unsubscribeJsMessage,
|
||||||
handleJsMessage,
|
handleJsMessage,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -262,7 +262,6 @@ function generateRustLicenses(): LicenseInfo[] | undefined {
|
|||||||
const { stdout, stderr, status } = spawnSync("cargo", ["about", "generate", "about.hbs"], {
|
const { stdout, stderr, status } = spawnSync("cargo", ["about", "generate", "about.hbs"], {
|
||||||
cwd: path.join(__dirname, ".."),
|
cwd: path.join(__dirname, ".."),
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
timeout: 60000, // One minute
|
|
||||||
shell: true,
|
shell: true,
|
||||||
windowsHide: true, // Hide the terminal on Windows
|
windowsHide: true, // Hide the terminal on Windows
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -231,11 +231,7 @@ impl EditorHandle {
|
|||||||
let js_return_value = self.frontend_message_handler_callback.call2(&JsValue::null(), &JsValue::from(message_type), &message_data);
|
let js_return_value = self.frontend_message_handler_callback.call2(&JsValue::null(), &JsValue::from(message_type), &message_data);
|
||||||
|
|
||||||
if let Err(error) = js_return_value {
|
if let Err(error) = js_return_value {
|
||||||
error!(
|
error!("While handling FrontendMessage {:?}, JavaScript threw an error:\n{:?}", message.to_discriminant().local_name(), error,)
|
||||||
"While handling FrontendMessage \"{:?}\", JavaScript threw an error: {:?}",
|
|
||||||
message.to_discriminant().local_name(),
|
|
||||||
error,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "bezier-rs"
|
|
||||||
version = "0.4.0"
|
|
||||||
rust-version = "1.85"
|
|
||||||
edition = "2024"
|
|
||||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
|
||||||
description = "Computational geometry algorithms for Bézier segments and shapes useful in the context of 2D graphics"
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
readme = "README.md"
|
|
||||||
keywords = ["bezier", "curve", "geometry", "2d", "graphics"]
|
|
||||||
categories = ["graphics", "mathematics"]
|
|
||||||
homepage = "https://github.com/GraphiteEditor/Graphite/tree/master/libraries/bezier-rs"
|
|
||||||
repository = "https://github.com/GraphiteEditor/Graphite/tree/master/libraries/bezier-rs"
|
|
||||||
documentation = "https://graphite.rs/libraries/bezier-rs/"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
# Required dependencies
|
|
||||||
glam = { workspace = true }
|
|
||||||
|
|
||||||
# Optional local dependencies
|
|
||||||
dyn-any = { version = "0.3.0", path = "../dyn-any", optional = true }
|
|
||||||
|
|
||||||
# Optional workspace dependencies
|
|
||||||
kurbo = { workspace = true, optional = true }
|
|
||||||
serde = { workspace = true, optional = true }
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user