mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-29 07:48:11 +08:00
Merge branch 'master' into separate_segments
This commit is contained in:
@@ -47,6 +47,11 @@ jobs:
|
|||||||
echo "Latest updated version:"
|
echo "Latest updated version:"
|
||||||
rustc --version
|
rustc --version
|
||||||
|
|
||||||
|
- name: 🦀 Fetch Rust dependencies
|
||||||
|
run: |
|
||||||
|
echo "If it fails here, the committed Cargo.lock may be out of date"
|
||||||
|
cargo fetch --locked
|
||||||
|
|
||||||
- name: ✂ Replace template in <head> of index.html
|
- name: ✂ Replace template in <head> of index.html
|
||||||
run: |
|
run: |
|
||||||
# Remove the INDEX_HTML_HEAD_REPLACEMENT environment variable for build links (not master deploys)
|
# Remove the INDEX_HTML_HEAD_REPLACEMENT environment variable for build links (not master deploys)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Vendored
+6
-7
@@ -35,16 +35,15 @@
|
|||||||
"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": {
|
||||||
// NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
"css-unused-selector": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
||||||
"css-unused-selector": "ignore",
|
"vite-plugin-svelte-css-no-scopable-elements": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
||||||
"vite-plugin-svelte-css-no-scopable-elements": "ignore",
|
"a11y-no-static-element-interactions": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
||||||
"a11y-no-static-element-interactions": "ignore",
|
"a11y-no-noninteractive-element-interactions": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
||||||
"a11y-no-noninteractive-element-interactions": "ignore",
|
"a11y-click-events-have-key-events": "ignore" // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
||||||
"a11y-click-events-have-key-events": "ignore"
|
|
||||||
},
|
},
|
||||||
// VS Code config
|
// VS Code config
|
||||||
"html.format.wrapLineLength": 200,
|
"html.format.wrapLineLength": 200,
|
||||||
|
|||||||
Generated
+81
-45
@@ -461,28 +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",
|
|
||||||
"poly-cool",
|
|
||||||
"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"
|
||||||
@@ -1799,6 +1777,7 @@ version = "0.29.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee"
|
checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
"libm",
|
"libm",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@@ -1885,7 +1864,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",
|
||||||
@@ -1963,7 +1941,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",
|
||||||
@@ -1972,10 +1949,12 @@ dependencies = [
|
|||||||
"image",
|
"image",
|
||||||
"kurbo",
|
"kurbo",
|
||||||
"log",
|
"log",
|
||||||
|
"lyon_geom",
|
||||||
"node-macro",
|
"node-macro",
|
||||||
"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",
|
||||||
@@ -2020,7 +1999,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",
|
||||||
@@ -2035,7 +2013,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",
|
||||||
@@ -2044,8 +2021,10 @@ dependencies = [
|
|||||||
"graphene-core",
|
"graphene-core",
|
||||||
"graphene-core-shaders",
|
"graphene-core-shaders",
|
||||||
"image",
|
"image",
|
||||||
|
"kurbo",
|
||||||
"ndarray",
|
"ndarray",
|
||||||
"node-macro",
|
"node-macro",
|
||||||
|
"num-traits",
|
||||||
"rand 0.9.1",
|
"rand 0.9.1",
|
||||||
"rand_chacha 0.9.0",
|
"rand_chacha 0.9.0",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -2058,10 +2037,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",
|
||||||
@@ -2073,10 +2049,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",
|
||||||
@@ -2091,10 +2064,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",
|
||||||
@@ -2106,17 +2079,19 @@ dependencies = [
|
|||||||
name = "graphite-desktop"
|
name = "graphite-desktop"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"ash",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cef",
|
"cef",
|
||||||
|
"core-foundation",
|
||||||
"derivative",
|
"derivative",
|
||||||
"dirs",
|
"dirs",
|
||||||
"futures",
|
"futures",
|
||||||
"glam",
|
"glam",
|
||||||
"graph-craft",
|
"graphite-desktop-wrapper",
|
||||||
"graphene-std",
|
|
||||||
"graphite-editor",
|
|
||||||
"image",
|
|
||||||
"include_dir",
|
"include_dir",
|
||||||
|
"libc",
|
||||||
|
"objc2-io-surface",
|
||||||
|
"objc2-metal 0.3.1",
|
||||||
"open",
|
"open",
|
||||||
"rfd",
|
"rfd",
|
||||||
"ron",
|
"ron",
|
||||||
@@ -2125,18 +2100,34 @@ dependencies = [
|
|||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"vello",
|
"vello",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"wgpu-executor",
|
"windows",
|
||||||
"winit",
|
"winit",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "graphite-desktop-wrapper"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"dirs",
|
||||||
|
"futures",
|
||||||
|
"graph-craft",
|
||||||
|
"graphene-std",
|
||||||
|
"graphite-editor",
|
||||||
|
"image",
|
||||||
|
"ron",
|
||||||
|
"thiserror 2.0.12",
|
||||||
|
"tracing",
|
||||||
|
"vello",
|
||||||
|
"wgpu",
|
||||||
|
"wgpu-executor",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "graphite-editor"
|
name = "graphite-editor"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bezier-rs",
|
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
"bytemuck",
|
|
||||||
"derivative",
|
"derivative",
|
||||||
"dyn-any",
|
"dyn-any",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
@@ -2152,18 +2143,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",
|
||||||
]
|
]
|
||||||
@@ -3013,6 +3001,17 @@ version = "0.1.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lyon_geom"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8af69edc087272df438b3ee436c4bb6d7c04aa8af665cfd398feae627dbd8570"
|
||||||
|
dependencies = [
|
||||||
|
"arrayvec",
|
||||||
|
"euclid",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "malloc_buf"
|
name = "malloc_buf"
|
||||||
version = "0.0.6"
|
version = "0.0.6"
|
||||||
@@ -3498,7 +3497,7 @@ dependencies = [
|
|||||||
"block2 0.5.1",
|
"block2 0.5.1",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
"objc2-metal",
|
"objc2-metal 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3553,6 +3552,19 @@ dependencies = [
|
|||||||
"objc2-core-foundation",
|
"objc2-core-foundation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-io-surface"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7282e9ac92529fa3457ce90ebb15f4ecbc383e8338060960760fa2cf75420c3c"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.1",
|
||||||
|
"libc",
|
||||||
|
"objc2 0.6.1",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-foundation 0.3.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-link-presentation"
|
name = "objc2-link-presentation"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -3577,6 +3589,20 @@ dependencies = [
|
|||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "objc2-metal"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f246c183239540aab1782457b35ab2040d4259175bd1d0c58e46ada7b47a874"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.9.1",
|
||||||
|
"block2 0.6.1",
|
||||||
|
"dispatch2",
|
||||||
|
"objc2 0.6.1",
|
||||||
|
"objc2-core-foundation",
|
||||||
|
"objc2-foundation 0.3.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc2-quartz-core"
|
name = "objc2-quartz-core"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -3587,7 +3613,7 @@ dependencies = [
|
|||||||
"block2 0.5.1",
|
"block2 0.5.1",
|
||||||
"objc2 0.5.2",
|
"objc2 0.5.2",
|
||||||
"objc2-foundation 0.2.2",
|
"objc2-foundation 0.2.2",
|
||||||
"objc2-metal",
|
"objc2-metal 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3832,9 +3858,13 @@ dependencies = [
|
|||||||
"glam",
|
"glam",
|
||||||
"glob",
|
"glob",
|
||||||
"image",
|
"image",
|
||||||
|
"lyon_geom",
|
||||||
"regex",
|
"regex",
|
||||||
"resvg",
|
"resvg",
|
||||||
|
"roots",
|
||||||
|
"rustc-hash 2.1.1",
|
||||||
"slotmap",
|
"slotmap",
|
||||||
|
"smallvec",
|
||||||
"svg",
|
"svg",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4662,6 +4692,12 @@ dependencies = [
|
|||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "roots"
|
||||||
|
version = "0.0.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "082f11ffa03bbef6c2c6ea6bea1acafaade2fd9050ae0234ab44a2153742b058"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "roxmltree"
|
name = "roxmltree"
|
||||||
version = "0.20.0"
|
version = "0.20.0"
|
||||||
|
|||||||
+22
-10
@@ -2,6 +2,7 @@
|
|||||||
members = [
|
members = [
|
||||||
"editor",
|
"editor",
|
||||||
"desktop",
|
"desktop",
|
||||||
|
"desktop/wrapper",
|
||||||
"proc-macros",
|
"proc-macros",
|
||||||
"frontend/wasm",
|
"frontend/wasm",
|
||||||
"node-graph/gapplication-io",
|
"node-graph/gapplication-io",
|
||||||
@@ -20,9 +21,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 +43,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 +84,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,16 +125,24 @@ 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 = [
|
||||||
|
"nostd-libm",
|
||||||
|
"scalar-math",
|
||||||
|
"bytemuck",
|
||||||
|
] }
|
||||||
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"
|
||||||
fern = { version = "0.7", features = ["colored"] }
|
fern = { version = "0.7", features = ["colored"] }
|
||||||
num_enum = "0.7"
|
num_enum = "0.7"
|
||||||
num-derive = "0.4"
|
num-derive = "0.4"
|
||||||
num-traits = { version = "0.2", default-features = false, features = ["i128"] }
|
num-traits = { version = "0.2", default-features = false, features = ["libm"] }
|
||||||
specta = { version = "2.0.0-rc.22", features = [
|
specta = { version = "2.0.0-rc.22", features = [
|
||||||
"glam",
|
"glam",
|
||||||
"derive",
|
"derive",
|
||||||
@@ -149,6 +160,7 @@ syn = { version = "2.0", default-features = false, features = [
|
|||||||
"proc-macro",
|
"proc-macro",
|
||||||
] }
|
] }
|
||||||
kurbo = { version = "0.11.0", features = ["serde"] }
|
kurbo = { version = "0.11.0", features = ["serde"] }
|
||||||
|
lyon_geom = "1.0"
|
||||||
petgraph = { version = "0.7.1", default-features = false, features = [
|
petgraph = { version = "0.7.1", default-features = false, features = [
|
||||||
"graphmap",
|
"graphmap",
|
||||||
] }
|
] }
|
||||||
|
|||||||
+18
-18
@@ -1,22 +1,22 @@
|
|||||||
# Keep this list in sync with those in `/deny.toml` and `/frontend/vite.config.ts`.
|
|
||||||
accepted = [
|
accepted = [
|
||||||
"Apache-2.0 WITH LLVM-exception",
|
"Apache-2.0 WITH LLVM-exception", # Keep this list in sync with those in `/deny.toml`
|
||||||
"Apache-2.0",
|
"Apache-2.0", # Keep this list in sync with those in `/deny.toml`
|
||||||
"BSD-2-Clause",
|
"BSD-2-Clause", # Keep this list in sync with those in `/deny.toml`
|
||||||
"BSD-3-Clause",
|
"BSD-3-Clause", # Keep this list in sync with those in `/deny.toml`
|
||||||
"BSL-1.0",
|
"BSL-1.0", # Keep this list in sync with those in `/deny.toml`
|
||||||
"CC0-1.0",
|
"CC0-1.0", # Keep this list in sync with those in `/deny.toml`
|
||||||
"CDLA-Permissive-2.0",
|
"CDLA-Permissive-2.0", # Keep this list in sync with those in `/deny.toml`
|
||||||
"ISC",
|
"ISC", # Keep this list in sync with those in `/deny.toml`
|
||||||
"MIT-0",
|
"MIT-0", # Keep this list in sync with those in `/deny.toml`
|
||||||
"MIT",
|
"MIT", # Keep this list in sync with those in `/deny.toml`
|
||||||
"MPL-2.0",
|
"MPL-2.0", # Keep this list in sync with those in `/deny.toml`
|
||||||
"OpenSSL",
|
"OpenSSL", # Keep this list in sync with those in `/deny.toml`
|
||||||
"Unicode-3.0",
|
"Unicode-3.0", # Keep this list in sync with those in `/deny.toml`
|
||||||
"Unicode-DFS-2016",
|
"Unicode-DFS-2016", # Keep this list in sync with those in `/deny.toml`
|
||||||
"Zlib",
|
"Zlib", # Keep this list in sync with those in `/deny.toml`
|
||||||
"NCSA",
|
"NCSA", # Keep this list in sync with those in `/deny.toml`
|
||||||
"bzip2-1.0.6",
|
"bzip2-1.0.6", # Keep this list in sync with those in `/deny.toml`
|
||||||
|
"OFL-1.1", # Keep this list in sync with those in `/deny.toml`
|
||||||
]
|
]
|
||||||
workarounds = ["ring"]
|
workarounds = ["ring"]
|
||||||
ignore-build-dependencies = true
|
ignore-build-dependencies = true
|
||||||
|
|||||||
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
@@ -63,25 +63,25 @@ ignore = [
|
|||||||
# See https://spdx.org/licenses/ for list of possible licenses
|
# See https://spdx.org/licenses/ for list of possible licenses
|
||||||
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
|
# [possible values: any SPDX 3.11 short identifier (+ optional exception)].
|
||||||
#
|
#
|
||||||
# Keep this list in sync with those in `/about.toml` and `/frontend/vite.config.ts`.
|
|
||||||
allow = [
|
allow = [
|
||||||
"Apache-2.0 WITH LLVM-exception",
|
"Apache-2.0 WITH LLVM-exception", # Keep this list in sync with those in `/about.toml`
|
||||||
"Apache-2.0",
|
"Apache-2.0", # Keep this list in sync with those in `/about.toml`
|
||||||
"BSD-2-Clause",
|
"BSD-2-Clause", # Keep this list in sync with those in `/about.toml`
|
||||||
"BSD-3-Clause",
|
"BSD-3-Clause", # Keep this list in sync with those in `/about.toml`
|
||||||
"BSL-1.0",
|
"BSL-1.0", # Keep this list in sync with those in `/about.toml`
|
||||||
"CC0-1.0",
|
"CC0-1.0", # Keep this list in sync with those in `/about.toml`
|
||||||
"CDLA-Permissive-2.0",
|
"CDLA-Permissive-2.0", # Keep this list in sync with those in `/about.toml`
|
||||||
"ISC",
|
"ISC", # Keep this list in sync with those in `/about.toml`
|
||||||
"MIT-0",
|
"MIT-0", # Keep this list in sync with those in `/about.toml`
|
||||||
"MIT",
|
"MIT", # Keep this list in sync with those in `/about.toml`
|
||||||
"MPL-2.0",
|
"MPL-2.0", # Keep this list in sync with those in `/about.toml`
|
||||||
"OpenSSL",
|
"OpenSSL", # Keep this list in sync with those in `/about.toml`
|
||||||
"Unicode-3.0",
|
"Unicode-3.0", # Keep this list in sync with those in `/about.toml`
|
||||||
"Unicode-DFS-2016",
|
"Unicode-DFS-2016", # Keep this list in sync with those in `/about.toml`
|
||||||
"Zlib",
|
"Zlib", # Keep this list in sync with those in `/about.toml`
|
||||||
"NCSA",
|
"NCSA", # Keep this list in sync with those in `/about.toml`
|
||||||
"bzip2-1.0.6",
|
"bzip2-1.0.6", # Keep this list in sync with those in `/about.toml`
|
||||||
|
"OFL-1.1", # Keep this list in sync with those in `/about.toml`
|
||||||
]
|
]
|
||||||
# The confidence threshold for detecting a license from license text.
|
# The confidence threshold for detecting a license from license text.
|
||||||
# The higher the value, the more closely the license text must be to the
|
# The higher the value, the more closely the license text must be to the
|
||||||
|
|||||||
+32
-11
@@ -9,19 +9,18 @@ edition = "2024"
|
|||||||
rust-version = "1.87"
|
rust-version = "1.87"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["gpu"]
|
default = ["gpu", "accelerated_paint"]
|
||||||
gpu = ["graphite-editor/gpu"]
|
gpu = ["graphite-desktop-wrapper/gpu"]
|
||||||
|
|
||||||
|
# Hardware acceleration features
|
||||||
|
accelerated_paint = ["accelerated_paint_dmabuf", "accelerated_paint_d3d11", "accelerated_paint_iosurface"]
|
||||||
|
accelerated_paint_dmabuf = ["libc", "ash"]
|
||||||
|
accelerated_paint_d3d11 = ["windows", "ash"]
|
||||||
|
accelerated_paint_iosurface = ["objc2-io-surface", "objc2-metal", "core-foundation"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# # Local dependencies
|
# # Local dependencies
|
||||||
graphite-editor = { path = "../editor", features = [
|
graphite-desktop-wrapper = { path = "wrapper" }
|
||||||
"gpu",
|
|
||||||
"ron",
|
|
||||||
"vello",
|
|
||||||
] }
|
|
||||||
graphene-std = { workspace = true }
|
|
||||||
graph-craft = { workspace = true }
|
|
||||||
wgpu-executor = { workspace = true }
|
|
||||||
|
|
||||||
wgpu = { workspace = true }
|
wgpu = { workspace = true }
|
||||||
winit = { workspace = true, features = ["serde"] }
|
winit = { workspace = true, features = ["serde"] }
|
||||||
@@ -39,4 +38,26 @@ vello = { workspace = true }
|
|||||||
derivative = { workspace = true }
|
derivative = { workspace = true }
|
||||||
rfd = { workspace = true }
|
rfd = { workspace = true }
|
||||||
open = { workspace = true }
|
open = { workspace = true }
|
||||||
image = { workspace = true }
|
|
||||||
|
# Hardware acceleration dependencies
|
||||||
|
ash = { version = "0.38", optional = true }
|
||||||
|
|
||||||
|
# Windows-specific dependencies
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
windows = { version = "0.58", features = [
|
||||||
|
"Win32_Graphics_Direct3D11",
|
||||||
|
"Win32_Graphics_Direct3D12",
|
||||||
|
"Win32_Graphics_Dxgi",
|
||||||
|
"Win32_Graphics_Dxgi_Common",
|
||||||
|
"Win32_Foundation"
|
||||||
|
], optional = true }
|
||||||
|
|
||||||
|
# macOS-specific dependencies
|
||||||
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
objc2-io-surface = { version = "0.3", optional = true }
|
||||||
|
objc2-metal = { version = "0.3", optional = true }
|
||||||
|
core-foundation = { version = "0.9", optional = true }
|
||||||
|
|
||||||
|
# Linux-specific dependencies
|
||||||
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
|
libc = { version = "0.2", optional = true }
|
||||||
|
|||||||
+207
-233
@@ -1,26 +1,19 @@
|
|||||||
use crate::CustomEvent;
|
use crate::CustomEvent;
|
||||||
use crate::WindowSize;
|
use crate::cef::WindowSize;
|
||||||
use crate::consts::APP_NAME;
|
use crate::consts::{APP_NAME, CEF_MESSAGE_LOOP_MAX_ITERATIONS};
|
||||||
use crate::dialogs::dialog_open_graphite_file;
|
|
||||||
use crate::dialogs::dialog_save_file;
|
|
||||||
use crate::dialogs::dialog_save_graphite_file;
|
|
||||||
use crate::render::GraphicsState;
|
use crate::render::GraphicsState;
|
||||||
use crate::render::WgpuContext;
|
use graphite_desktop_wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, Platform};
|
||||||
use graph_craft::wasm_application_io::WasmApplicationIo;
|
use graphite_desktop_wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages};
|
||||||
use graphene_std::Color;
|
|
||||||
use graphene_std::raster::Image;
|
use rfd::AsyncFileDialog;
|
||||||
use graphite_editor::application::Editor;
|
|
||||||
use graphite_editor::consts::DEFAULT_DOCUMENT_NAME;
|
|
||||||
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::sync::mpsc::SyncSender;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use winit::application::ApplicationHandler;
|
use winit::application::ApplicationHandler;
|
||||||
use winit::dpi::PhysicalSize;
|
use winit::dpi::PhysicalSize;
|
||||||
use winit::event::StartCause;
|
|
||||||
use winit::event::WindowEvent;
|
use winit::event::WindowEvent;
|
||||||
use winit::event_loop::ActiveEventLoop;
|
use winit::event_loop::ActiveEventLoop;
|
||||||
use winit::event_loop::ControlFlow;
|
use winit::event_loop::ControlFlow;
|
||||||
@@ -31,18 +24,33 @@ use winit::window::WindowId;
|
|||||||
use crate::cef;
|
use crate::cef;
|
||||||
|
|
||||||
pub(crate) struct WinitApp {
|
pub(crate) struct WinitApp {
|
||||||
cef_context: cef::Context<cef::Initialized>,
|
cef_context: Box<dyn cef::CefContext>,
|
||||||
window: Option<Arc<Window>>,
|
window: Option<Arc<Window>>,
|
||||||
cef_schedule: Option<Instant>,
|
cef_schedule: Option<Instant>,
|
||||||
window_size_sender: Sender<WindowSize>,
|
window_size_sender: Sender<WindowSize>,
|
||||||
graphics_state: Option<GraphicsState>,
|
graphics_state: Option<GraphicsState>,
|
||||||
wgpu_context: WgpuContext,
|
wgpu_context: WgpuContext,
|
||||||
event_loop_proxy: EventLoopProxy<CustomEvent>,
|
event_loop_proxy: EventLoopProxy<CustomEvent>,
|
||||||
editor: Editor,
|
desktop_wrapper: DesktopWrapper,
|
||||||
|
last_ui_update: Instant,
|
||||||
|
avg_frame_time: f32,
|
||||||
|
start_render_sender: SyncSender<()>,
|
||||||
|
web_communication_initialized: bool,
|
||||||
|
web_communication_startup_buffer: Vec<Vec<u8>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WinitApp {
|
impl WinitApp {
|
||||||
pub(crate) fn new(cef_context: cef::Context<cef::Initialized>, window_size_sender: Sender<WindowSize>, wgpu_context: WgpuContext, event_loop_proxy: EventLoopProxy<CustomEvent>) -> Self {
|
pub(crate) fn new(cef_context: Box<dyn cef::CefContext>, window_size_sender: Sender<WindowSize>, wgpu_context: WgpuContext, event_loop_proxy: EventLoopProxy<CustomEvent>) -> Self {
|
||||||
|
let rendering_loop_proxy = event_loop_proxy.clone();
|
||||||
|
let (start_render_sender, start_render_receiver) = std::sync::mpsc::sync_channel(1);
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
loop {
|
||||||
|
let result = futures::executor::block_on(DesktopWrapper::execute_node_graph());
|
||||||
|
let _ = rendering_loop_proxy.send_event(CustomEvent::NodeGraphExecutionResult(result));
|
||||||
|
let _ = start_render_receiver.recv();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
cef_context,
|
cef_context,
|
||||||
window: None,
|
window: None,
|
||||||
@@ -51,96 +59,128 @@ impl WinitApp {
|
|||||||
window_size_sender,
|
window_size_sender,
|
||||||
wgpu_context,
|
wgpu_context,
|
||||||
event_loop_proxy,
|
event_loop_proxy,
|
||||||
editor: Editor::new(),
|
desktop_wrapper: DesktopWrapper::new(),
|
||||||
|
last_ui_update: Instant::now(),
|
||||||
|
avg_frame_time: 0.,
|
||||||
|
start_render_sender,
|
||||||
|
web_communication_initialized: false,
|
||||||
|
web_communication_startup_buffer: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch_message(&mut self, message: Message) {
|
fn handle_desktop_frontend_message(&mut self, message: DesktopFrontendMessage) {
|
||||||
let responses = self.editor.handle_message(message);
|
match message {
|
||||||
self.send_messages_to_editor(responses);
|
DesktopFrontendMessage::ToWeb(messages) => {
|
||||||
}
|
let Some(bytes) = serialize_frontend_messages(messages) else {
|
||||||
|
tracing::error!("Failed to serialize frontend messages");
|
||||||
fn send_messages_to_editor(&mut self, mut responses: Vec<FrontendMessage>) {
|
return;
|
||||||
for message in responses.extract_if(.., |m| matches!(m, FrontendMessage::RenderOverlays(_))) {
|
};
|
||||||
let FrontendMessage::RenderOverlays(overlay_context) = message else { unreachable!() };
|
self.send_or_queue_web_message(bytes);
|
||||||
if let Some(graphics_state) = &mut self.graphics_state {
|
|
||||||
let scene = overlay_context.take_scene();
|
|
||||||
graphics_state.set_overlays_scene(scene);
|
|
||||||
}
|
}
|
||||||
}
|
DesktopFrontendMessage::OpenFileDialog { title, filters, context } => {
|
||||||
|
|
||||||
for _ in responses.extract_if(.., |m| matches!(m, FrontendMessage::TriggerOpenDocument)) {
|
|
||||||
let event_loop_proxy = self.event_loop_proxy.clone();
|
|
||||||
let _ = thread::spawn(move || {
|
|
||||||
let path = futures::executor::block_on(dialog_open_graphite_file());
|
|
||||||
if let Some(path) = path {
|
|
||||||
let content = std::fs::read_to_string(&path).unwrap_or_else(|_| {
|
|
||||||
tracing::error!("Failed to read file: {}", path.display());
|
|
||||||
String::new()
|
|
||||||
});
|
|
||||||
let message = PortfolioMessage::OpenDocumentFile {
|
|
||||||
document_name: path.file_stem().and_then(|s| s.to_str()).unwrap_or("unknown").to_string(),
|
|
||||||
document_serialized_content: content,
|
|
||||||
};
|
|
||||||
let _ = event_loop_proxy.send_event(CustomEvent::DispatchMessage(message.into()));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for message in responses.extract_if(.., |m| matches!(m, FrontendMessage::TriggerSaveDocument { .. })) {
|
|
||||||
let FrontendMessage::TriggerSaveDocument { document_id, name, path, content } = message else {
|
|
||||||
unreachable!()
|
|
||||||
};
|
|
||||||
if let Some(path) = path {
|
|
||||||
let _ = std::fs::write(&path, content);
|
|
||||||
} else {
|
|
||||||
let event_loop_proxy = self.event_loop_proxy.clone();
|
let event_loop_proxy = self.event_loop_proxy.clone();
|
||||||
let _ = thread::spawn(move || {
|
let _ = thread::spawn(move || {
|
||||||
let path = futures::executor::block_on(dialog_save_graphite_file(name));
|
let mut dialog = AsyncFileDialog::new().set_title(title);
|
||||||
if let Some(path) = path {
|
for filter in filters {
|
||||||
if let Err(e) = std::fs::write(&path, content) {
|
dialog = dialog.add_filter(filter.name, &filter.extensions);
|
||||||
tracing::error!("Failed to save file: {}: {}", path.display(), e);
|
}
|
||||||
} else {
|
|
||||||
let message = Message::Portfolio(PortfolioMessage::DocumentPassMessage {
|
let show_dialog = async move { dialog.pick_file().await.map(|f| f.path().to_path_buf()) };
|
||||||
document_id,
|
|
||||||
message: DocumentMessage::SavedDocument { path: Some(path) },
|
if let Some(path) = futures::executor::block_on(show_dialog)
|
||||||
});
|
&& let Ok(content) = std::fs::read(&path)
|
||||||
let _ = event_loop_proxy.send_event(CustomEvent::DispatchMessage(message));
|
{
|
||||||
}
|
let message = DesktopWrapperMessage::OpenFileDialogResult { path, content, context };
|
||||||
|
let _ = event_loop_proxy.send_event(CustomEvent::DesktopWrapperMessage(message));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
DesktopFrontendMessage::SaveFileDialog {
|
||||||
|
title,
|
||||||
for message in responses.extract_if(.., |m| matches!(m, FrontendMessage::TriggerSaveFile { .. })) {
|
default_filename,
|
||||||
let FrontendMessage::TriggerSaveFile { name, content } = message else { unreachable!() };
|
default_folder,
|
||||||
let _ = thread::spawn(move || {
|
filters,
|
||||||
let path = futures::executor::block_on(dialog_save_file(name));
|
context,
|
||||||
if let Some(path) = path {
|
} => {
|
||||||
if let Err(e) = std::fs::write(&path, content) {
|
let event_loop_proxy = self.event_loop_proxy.clone();
|
||||||
tracing::error!("Failed to save file: {}: {}", path.display(), e);
|
let _ = thread::spawn(move || {
|
||||||
|
let mut dialog = AsyncFileDialog::new().set_title(title).set_file_name(default_filename);
|
||||||
|
if let Some(folder) = default_folder {
|
||||||
|
dialog = dialog.set_directory(folder);
|
||||||
|
}
|
||||||
|
for filter in filters {
|
||||||
|
dialog = dialog.add_filter(filter.name, &filter.extensions);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for message in responses.extract_if(.., |m| matches!(m, FrontendMessage::TriggerVisitLink { .. })) {
|
let show_dialog = async move { dialog.save_file().await.map(|f| f.path().to_path_buf()) };
|
||||||
let _ = thread::spawn(move || {
|
|
||||||
let FrontendMessage::TriggerVisitLink { url } = message else { unreachable!() };
|
|
||||||
if let Err(e) = open::that(&url) {
|
|
||||||
tracing::error!("Failed to open URL: {}: {}", url, e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if responses.is_empty() {
|
if let Some(path) = futures::executor::block_on(show_dialog) {
|
||||||
return;
|
let message = DesktopWrapperMessage::SaveFileDialogResult { path, context };
|
||||||
|
let _ = event_loop_proxy.send_event(CustomEvent::DesktopWrapperMessage(message));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
DesktopFrontendMessage::WriteFile { path, content } => {
|
||||||
|
if let Err(e) = std::fs::write(&path, content) {
|
||||||
|
tracing::error!("Failed to write file {}: {}", path.display(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DesktopFrontendMessage::OpenUrl(url) => {
|
||||||
|
let _ = thread::spawn(move || {
|
||||||
|
if let Err(e) = open::that(&url) {
|
||||||
|
tracing::error!("Failed to open URL: {}: {}", url, e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
DesktopFrontendMessage::UpdateViewportBounds { x, y, width, height } => {
|
||||||
|
if let Some(graphics_state) = &mut self.graphics_state
|
||||||
|
&& let Some(window) = &self.window
|
||||||
|
{
|
||||||
|
let window_size = window.inner_size();
|
||||||
|
|
||||||
|
let viewport_offset_x = x / window_size.width as f32;
|
||||||
|
let viewport_offset_y = y / window_size.height as f32;
|
||||||
|
graphics_state.set_viewport_offset([viewport_offset_x, viewport_offset_y]);
|
||||||
|
|
||||||
|
let viewport_scale_x = if width != 0.0 { window_size.width as f32 / width } else { 1.0 };
|
||||||
|
let viewport_scale_y = if height != 0.0 { window_size.height as f32 / height } else { 1.0 };
|
||||||
|
graphics_state.set_viewport_scale([viewport_scale_x, viewport_scale_y]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DesktopFrontendMessage::UpdateOverlays(scene) => {
|
||||||
|
if let Some(graphics_state) = &mut self.graphics_state {
|
||||||
|
graphics_state.set_overlays_scene(scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DesktopFrontendMessage::UpdateWindowState { maximized, minimized } => {
|
||||||
|
if let Some(window) = &self.window {
|
||||||
|
window.set_maximized(maximized);
|
||||||
|
window.set_minimized(minimized);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DesktopFrontendMessage::CloseWindow => {
|
||||||
|
let _ = self.event_loop_proxy.send_event(CustomEvent::CloseWindow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_desktop_frontend_messages(&mut self, messages: Vec<DesktopFrontendMessage>) {
|
||||||
|
for message in messages {
|
||||||
|
self.handle_desktop_frontend_message(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatch_desktop_wrapper_message(&mut self, message: DesktopWrapperMessage) {
|
||||||
|
let responses = self.desktop_wrapper.dispatch(message);
|
||||||
|
self.handle_desktop_frontend_messages(responses);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_or_queue_web_message(&mut self, message: Vec<u8>) {
|
||||||
|
if self.web_communication_initialized {
|
||||||
|
self.cef_context.send_web_message(message);
|
||||||
|
} else {
|
||||||
|
self.web_communication_startup_buffer.push(message);
|
||||||
}
|
}
|
||||||
let Ok(message) = ron::to_string(&responses) else {
|
|
||||||
tracing::error!("Failed to serialize Messages");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
self.cef_context.send_web_message(message.as_bytes());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,23 +189,20 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
|||||||
// Set a timeout in case we miss any cef schedule requests
|
// Set a timeout in case we miss any cef schedule requests
|
||||||
let timeout = Instant::now() + Duration::from_millis(10);
|
let timeout = Instant::now() + Duration::from_millis(10);
|
||||||
let wait_until = timeout.min(self.cef_schedule.unwrap_or(timeout));
|
let wait_until = timeout.min(self.cef_schedule.unwrap_or(timeout));
|
||||||
self.cef_context.work();
|
|
||||||
|
|
||||||
event_loop.set_control_flow(ControlFlow::WaitUntil(wait_until));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) {
|
|
||||||
if let Some(schedule) = self.cef_schedule
|
if let Some(schedule) = self.cef_schedule
|
||||||
&& schedule < Instant::now()
|
&& schedule < Instant::now()
|
||||||
{
|
{
|
||||||
self.cef_schedule = None;
|
self.cef_schedule = None;
|
||||||
self.cef_context.work();
|
// Poll cef message loop multiple times to avoid message loop starvation
|
||||||
}
|
for _ in 0..CEF_MESSAGE_LOOP_MAX_ITERATIONS {
|
||||||
if let StartCause::ResumeTimeReached { .. } = cause {
|
self.cef_context.work();
|
||||||
if let Some(window) = &self.window {
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(window) = &self.window.as_ref() {
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
event_loop.set_control_flow(ControlFlow::WaitUntil(wait_until));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
@@ -174,7 +211,7 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
|||||||
.with_min_inner_size(winit::dpi::LogicalSize::new(400, 300))
|
.with_min_inner_size(winit::dpi::LogicalSize::new(400, 300))
|
||||||
.with_inner_size(winit::dpi::LogicalSize::new(1200, 800));
|
.with_inner_size(winit::dpi::LogicalSize::new(1200, 800));
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
use crate::consts::APP_ID;
|
use crate::consts::APP_ID;
|
||||||
use winit::platform::wayland::ActiveEventLoopExtWayland;
|
use winit::platform::wayland::ActiveEventLoopExtWayland;
|
||||||
@@ -194,17 +231,48 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
|||||||
|
|
||||||
tracing::info!("Winit window created and ready");
|
tracing::info!("Winit window created and ready");
|
||||||
|
|
||||||
let application_io = WasmApplicationIo::new_with_context(self.wgpu_context.clone());
|
self.desktop_wrapper.init(self.wgpu_context.clone());
|
||||||
|
|
||||||
futures::executor::block_on(graphite_editor::node_graph_executor::replace_application_io(application_io));
|
#[cfg(target_os = "windows")]
|
||||||
|
let platform = Platform::Windows;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let platform = Platform::Mac;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
let platform = Platform::Linux;
|
||||||
|
self.dispatch_desktop_wrapper_message(DesktopWrapperMessage::UpdatePlatform(platform));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn user_event(&mut self, _: &ActiveEventLoop, event: CustomEvent) {
|
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: CustomEvent) {
|
||||||
match event {
|
match event {
|
||||||
|
CustomEvent::WebCommunicationInitialized => {
|
||||||
|
self.web_communication_initialized = true;
|
||||||
|
for message in self.web_communication_startup_buffer.drain(..) {
|
||||||
|
self.cef_context.send_web_message(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CustomEvent::DesktopWrapperMessage(message) => self.dispatch_desktop_wrapper_message(message),
|
||||||
|
CustomEvent::NodeGraphExecutionResult(result) => match result {
|
||||||
|
NodeGraphExecutionResult::HasRun(texture) => {
|
||||||
|
self.dispatch_desktop_wrapper_message(DesktopWrapperMessage::PollNodeGraphEvaluation);
|
||||||
|
if let Some(texture) = texture
|
||||||
|
&& let Some(graphics_state) = self.graphics_state.as_mut()
|
||||||
|
&& let Some(window) = self.window.as_ref()
|
||||||
|
{
|
||||||
|
graphics_state.bind_viewport_texture(texture);
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NodeGraphExecutionResult::NotRun => {}
|
||||||
|
},
|
||||||
CustomEvent::UiUpdate(texture) => {
|
CustomEvent::UiUpdate(texture) => {
|
||||||
if let Some(graphics_state) = self.graphics_state.as_mut() {
|
if let Some(graphics_state) = self.graphics_state.as_mut() {
|
||||||
graphics_state.resize(texture.width(), texture.height());
|
graphics_state.resize(texture.width(), texture.height());
|
||||||
graphics_state.bind_ui_texture(texture);
|
graphics_state.bind_ui_texture(texture);
|
||||||
|
let elapsed = self.last_ui_update.elapsed().as_secs_f32();
|
||||||
|
self.last_ui_update = Instant::now();
|
||||||
|
if elapsed < 0.5 {
|
||||||
|
self.avg_frame_time = (self.avg_frame_time * 3. + elapsed) / 4.;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let Some(window) = &self.window {
|
if let Some(window) = &self.window {
|
||||||
window.request_redraw();
|
window.request_redraw();
|
||||||
@@ -217,150 +285,56 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
|||||||
self.cef_schedule = Some(instant);
|
self.cef_schedule = Some(instant);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CustomEvent::DispatchMessage(message) => {
|
CustomEvent::CloseWindow => {
|
||||||
self.dispatch_message(message);
|
// TODO: Implement graceful shutdown
|
||||||
}
|
|
||||||
CustomEvent::MessageReceived(message) => {
|
|
||||||
if let Message::InputPreprocessor(_) = &message {
|
|
||||||
if let Some(window) = &self.window {
|
|
||||||
window.request_redraw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Message::InputPreprocessor(InputPreprocessorMessage::BoundsOfViewports { bounds_of_viewports }) = &message {
|
|
||||||
if let Some(graphic_state) = &mut self.graphics_state {
|
|
||||||
let window_size = self.window.as_ref().unwrap().inner_size();
|
|
||||||
let window_size = glam::Vec2::new(window_size.width as f32, window_size.height as f32);
|
|
||||||
let top_left = bounds_of_viewports[0].top_left.as_vec2() / window_size;
|
|
||||||
let bottom_right = bounds_of_viewports[0].bottom_right.as_vec2() / window_size;
|
|
||||||
let offset = top_left.to_array();
|
|
||||||
let scale = (bottom_right - top_left).recip();
|
|
||||||
graphic_state.set_viewport_offset(offset);
|
|
||||||
graphic_state.set_viewport_scale(scale.to_array());
|
|
||||||
} else {
|
|
||||||
panic!("graphics state not intialized, viewport offset might be lost");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.dispatch_message(message);
|
tracing::info!("Exiting main event loop");
|
||||||
}
|
event_loop.exit();
|
||||||
CustomEvent::NodeGraphRan(texture) => {
|
|
||||||
if let Some(texture) = texture
|
|
||||||
&& let Some(graphics_state) = &mut self.graphics_state
|
|
||||||
{
|
|
||||||
graphics_state.bind_viewport_texture(texture);
|
|
||||||
}
|
|
||||||
let mut responses = VecDeque::new();
|
|
||||||
let err = self.editor.poll_node_graph_evaluation(&mut responses);
|
|
||||||
if let Err(e) = err {
|
|
||||||
if e != "No active document" {
|
|
||||||
tracing::error!("Error poling node graph: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for message in responses {
|
|
||||||
self.dispatch_message(message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn window_event(&mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {
|
fn window_event(&mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {
|
||||||
let Some(event) = self.cef_context.handle_window_event(event) else { return };
|
self.cef_context.handle_window_event(&event);
|
||||||
|
|
||||||
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");
|
let _ = self.event_loop_proxy.send_event(CustomEvent::CloseWindow);
|
||||||
event_loop.exit();
|
|
||||||
}
|
}
|
||||||
WindowEvent::Resized(PhysicalSize { width, height }) => {
|
WindowEvent::Resized(PhysicalSize { width, height }) => {
|
||||||
let _ = self.window_size_sender.send(WindowSize::new(width as usize, height as usize));
|
let _ = self.window_size_sender.send(WindowSize::new(width as usize, height as usize));
|
||||||
self.cef_context.notify_of_resize();
|
self.cef_context.notify_of_resize();
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowEvent::RedrawRequested => {
|
WindowEvent::RedrawRequested => {
|
||||||
let Some(ref mut graphics_state) = self.graphics_state else { return };
|
let Some(ref mut graphics_state) = self.graphics_state else { return };
|
||||||
// Only rerender once we have a new ui texture to display
|
// Only rerender once we have a new ui texture to display
|
||||||
|
if let Some(window) = &self.window {
|
||||||
match graphics_state.render() {
|
match graphics_state.render(window.as_ref()) {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(wgpu::SurfaceError::Lost) => {
|
Err(wgpu::SurfaceError::Lost) => {
|
||||||
tracing::warn!("lost surface");
|
tracing::warn!("lost surface");
|
||||||
|
}
|
||||||
|
Err(wgpu::SurfaceError::OutOfMemory) => {
|
||||||
|
event_loop.exit();
|
||||||
|
}
|
||||||
|
Err(e) => tracing::error!("{:?}", e),
|
||||||
}
|
}
|
||||||
Err(wgpu::SurfaceError::OutOfMemory) => {
|
let _ = self.start_render_sender.try_send(());
|
||||||
event_loop.exit();
|
|
||||||
}
|
|
||||||
Err(e) => tracing::error!("{:?}", e),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Currently not supported on wayland see https://github.com/rust-windowing/winit/issues/1881
|
||||||
|
WindowEvent::DroppedFile(path) => {
|
||||||
|
match std::fs::read(&path) {
|
||||||
|
Ok(content) => {
|
||||||
|
let message = DesktopWrapperMessage::OpenFile { path, content };
|
||||||
|
let _ = self.event_loop_proxy.send_event(CustomEvent::DesktopWrapperMessage(message));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to read dropped file {}: {}", path.display(), e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+49
-11
@@ -1,26 +1,52 @@
|
|||||||
use crate::{CustomEvent, WgpuContext, render::FrameBufferRef};
|
//! CEF (Chromium Embedded Framework) integration for Graphite Desktop
|
||||||
use std::{
|
//!
|
||||||
sync::{Arc, Mutex, mpsc::Receiver},
|
//! This module provides CEF browser integration with hardware-accelerated texture sharing.
|
||||||
time::Instant,
|
//!
|
||||||
};
|
//! # Hardware Acceleration
|
||||||
|
//!
|
||||||
|
//! The texture import system supports platform-specific hardware acceleration:
|
||||||
|
//!
|
||||||
|
//! - **Linux**: DMA-BUF via Vulkan external memory (`accelerated_paint_dmabuf` feature)
|
||||||
|
//! - **Windows**: D3D11 shared textures via either Vulkan or D3D12 interop (`accelerated_paint_d3d11` feature)
|
||||||
|
//! - **macOS**: IOSurface via Metal/Vulkan interop (`accelerated_paint_iosurface` feature)
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! The system gracefully falls back to CPU textures when hardware acceleration is unavailable.
|
||||||
|
|
||||||
|
use crate::CustomEvent;
|
||||||
|
use crate::render::FrameBufferRef;
|
||||||
|
use graphite_desktop_wrapper::{WgpuContext, deserialize_editor_message};
|
||||||
|
use std::sync::mpsc::Receiver;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
mod consts;
|
||||||
mod context;
|
mod context;
|
||||||
mod dirs;
|
mod dirs;
|
||||||
mod input;
|
mod input;
|
||||||
mod internal;
|
mod internal;
|
||||||
mod ipc;
|
mod ipc;
|
||||||
|
mod platform;
|
||||||
mod scheme_handler;
|
mod scheme_handler;
|
||||||
mod utility;
|
mod utility;
|
||||||
|
|
||||||
pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError};
|
#[cfg(feature = "accelerated_paint")]
|
||||||
|
mod texture_import;
|
||||||
|
#[cfg(feature = "accelerated_paint")]
|
||||||
|
use texture_import::SharedTextureHandle;
|
||||||
|
|
||||||
|
pub(crate) use context::{CefContext, CefContextBuilder, InitError};
|
||||||
use winit::event_loop::EventLoopProxy;
|
use winit::event_loop::EventLoopProxy;
|
||||||
|
|
||||||
pub(crate) trait CefEventHandler: Clone {
|
pub(crate) trait CefEventHandler: Clone {
|
||||||
fn window_size(&self) -> WindowSize;
|
fn window_size(&self) -> WindowSize;
|
||||||
fn draw<'a>(&self, frame_buffer: FrameBufferRef<'a>);
|
fn draw<'a>(&self, frame_buffer: FrameBufferRef<'a>);
|
||||||
|
#[cfg(feature = "accelerated_paint")]
|
||||||
|
fn draw_gpu(&self, shared_texture: SharedTextureHandle);
|
||||||
/// Scheudule the main event loop to run the cef event loop after the timeout
|
/// Scheudule the main event loop to run the cef event loop after the timeout
|
||||||
/// [`_cef_browser_process_handler_t::on_schedule_message_pump_work`] for more documentation.
|
/// [`_cef_browser_process_handler_t::on_schedule_message_pump_work`] for more documentation.
|
||||||
fn schedule_cef_message_loop_work(&self, scheduled_time: Instant);
|
fn schedule_cef_message_loop_work(&self, scheduled_time: Instant);
|
||||||
|
fn initialized_web_communication(&self);
|
||||||
fn receive_web_message(&self, message: &[u8]);
|
fn receive_web_message(&self, message: &[u8]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,14 +146,26 @@ impl CefEventHandler for CefHandler {
|
|||||||
let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time));
|
let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn initialized_web_communication(&self) {
|
||||||
|
let _ = self.event_loop_proxy.send_event(CustomEvent::WebCommunicationInitialized);
|
||||||
|
}
|
||||||
|
|
||||||
fn receive_web_message(&self, message: &[u8]) {
|
fn receive_web_message(&self, message: &[u8]) {
|
||||||
let str = std::str::from_utf8(message).unwrap();
|
let Some(desktop_wrapper_message) = deserialize_editor_message(message) else {
|
||||||
match ron::from_str(str) {
|
tracing::error!("Failed to deserialize web message");
|
||||||
Ok(message) => {
|
return;
|
||||||
let _ = self.event_loop_proxy.send_event(CustomEvent::MessageReceived(message));
|
};
|
||||||
|
let _ = self.event_loop_proxy.send_event(CustomEvent::DesktopWrapperMessage(desktop_wrapper_message));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "accelerated_paint")]
|
||||||
|
fn draw_gpu(&self, shared_texture: SharedTextureHandle) {
|
||||||
|
match shared_texture.import_texture(&self.wgpu_context.device) {
|
||||||
|
Ok(texture) => {
|
||||||
|
let _ = self.event_loop_proxy.send_event(CustomEvent::UiUpdate(texture));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("Failed to deserialize message {:?}", e)
|
tracing::error!("Failed to import shared texture: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
pub(crate) const GRAPHITE_SCHEME: &str = "graphite-static";
|
||||||
|
pub(crate) const FRONTEND_DOMAIN: &str = "frontend";
|
||||||
+9
-155
@@ -1,161 +1,15 @@
|
|||||||
use cef::sys::{CEF_API_VERSION_LAST, cef_resultcode_t};
|
mod multithreaded;
|
||||||
use cef::{App, BrowserSettings, Client, DictionaryValue, ImplBrowser, ImplBrowserHost, ImplCommandLine, RenderHandler, RequestContext, WindowInfo, browser_host_create_browser_sync, initialize};
|
mod singlethreaded;
|
||||||
use cef::{Browser, CefString, Settings, api_hash, args::Args, execute_process};
|
|
||||||
use thiserror::Error;
|
|
||||||
use winit::event::WindowEvent;
|
|
||||||
|
|
||||||
use crate::cef::dirs::{cef_cache_dir, cef_data_dir};
|
mod builder;
|
||||||
|
pub(crate) use builder::{CefContextBuilder, InitError};
|
||||||
|
|
||||||
use super::input::InputState;
|
pub(crate) trait CefContext {
|
||||||
use super::ipc::{MessageType, SendMessage};
|
fn work(&mut self);
|
||||||
use super::scheme_handler::{FRONTEND_DOMAIN, GRAPHITE_SCHEME};
|
|
||||||
use super::{CefEventHandler, input};
|
|
||||||
|
|
||||||
use super::internal::{BrowserProcessAppImpl, BrowserProcessClientImpl, RenderHandlerImpl, RenderProcessAppImpl};
|
fn handle_window_event(&mut self, event: &winit::event::WindowEvent);
|
||||||
|
|
||||||
pub(crate) struct Setup {}
|
fn notify_of_resize(&self);
|
||||||
pub(crate) struct Initialized {}
|
|
||||||
pub(crate) trait ContextState {}
|
|
||||||
impl ContextState for Setup {}
|
|
||||||
impl ContextState for Initialized {}
|
|
||||||
|
|
||||||
pub(crate) struct Context<S: ContextState> {
|
fn send_web_message(&self, message: Vec<u8>);
|
||||||
args: Args,
|
|
||||||
pub(crate) browser: Option<Browser>,
|
|
||||||
pub(crate) input_state: InputState,
|
|
||||||
marker: std::marker::PhantomData<S>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Context<Setup> {
|
|
||||||
pub(crate) fn new() -> Result<Context<Setup>, SetupError> {
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
let _loader = {
|
|
||||||
let loader = library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), false);
|
|
||||||
assert!(loader.load());
|
|
||||||
loader
|
|
||||||
};
|
|
||||||
let _ = api_hash(CEF_API_VERSION_LAST, 0);
|
|
||||||
|
|
||||||
let args = Args::new();
|
|
||||||
let cmd = args.as_cmd_line().unwrap();
|
|
||||||
let switch = CefString::from("type");
|
|
||||||
let is_browser_process = cmd.has_switch(Some(&switch)) != 1;
|
|
||||||
|
|
||||||
if !is_browser_process {
|
|
||||||
let process_type = CefString::from(&cmd.switch_value(Some(&switch)));
|
|
||||||
let mut app = RenderProcessAppImpl::app();
|
|
||||||
let ret = execute_process(Some(args.as_main_args()), Some(&mut app), std::ptr::null_mut());
|
|
||||||
if ret >= 0 {
|
|
||||||
return Err(SetupError::SubprocessFailed(process_type.to_string()));
|
|
||||||
} else {
|
|
||||||
return Err(SetupError::Subprocess);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Context {
|
|
||||||
args,
|
|
||||||
browser: None,
|
|
||||||
input_state: InputState::default(),
|
|
||||||
marker: std::marker::PhantomData::<Setup>,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn init(self, event_handler: impl CefEventHandler) -> Result<Context<Initialized>, InitError> {
|
|
||||||
let settings = Settings {
|
|
||||||
windowless_rendering_enabled: 1,
|
|
||||||
multi_threaded_message_loop: 0,
|
|
||||||
external_message_pump: 1,
|
|
||||||
root_cache_path: cef_data_dir().to_str().map(CefString::from).unwrap(),
|
|
||||||
cache_path: cef_cache_dir().to_str().map(CefString::from).unwrap(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Attention! Wrapping this in an extra App is necessary, otherwise the program still compiles but segfaults
|
|
||||||
let mut cef_app = App::new(BrowserProcessAppImpl::new(event_handler.clone()));
|
|
||||||
|
|
||||||
let result = initialize(Some(self.args.as_main_args()), Some(&settings), Some(&mut cef_app), std::ptr::null_mut());
|
|
||||||
if result != 1 {
|
|
||||||
let cef_exit_code = cef::get_exit_code() as u32;
|
|
||||||
if cef_exit_code == cef_resultcode_t::CEF_RESULT_CODE_NORMAL_EXIT_PROCESS_NOTIFIED as u32 {
|
|
||||||
return Err(InitError::AlreadyRunning);
|
|
||||||
}
|
|
||||||
return Err(InitError::InitializationFailed(cef_exit_code));
|
|
||||||
}
|
|
||||||
|
|
||||||
let render_handler = RenderHandler::new(RenderHandlerImpl::new(event_handler.clone()));
|
|
||||||
let mut client = Client::new(BrowserProcessClientImpl::new(render_handler, event_handler.clone()));
|
|
||||||
|
|
||||||
let url = CefString::from(format!("{GRAPHITE_SCHEME}://{FRONTEND_DOMAIN}/").as_str());
|
|
||||||
|
|
||||||
let window_info = WindowInfo {
|
|
||||||
windowless_rendering_enabled: 1,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let settings = BrowserSettings {
|
|
||||||
windowless_frame_rate: 60,
|
|
||||||
background_color: 0x0,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let browser = browser_host_create_browser_sync(
|
|
||||||
Some(&window_info),
|
|
||||||
Some(&mut client),
|
|
||||||
Some(&url),
|
|
||||||
Some(&settings),
|
|
||||||
Option::<&mut DictionaryValue>::None,
|
|
||||||
Option::<&mut RequestContext>::None,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(Context {
|
|
||||||
args: self.args.clone(),
|
|
||||||
browser,
|
|
||||||
input_state: self.input_state.clone(),
|
|
||||||
marker: std::marker::PhantomData::<Initialized>,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Context<Initialized> {
|
|
||||||
pub(crate) fn work(&mut self) {
|
|
||||||
cef::do_message_loop_work();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn handle_window_event(&mut self, event: WindowEvent) -> Option<WindowEvent> {
|
|
||||||
input::handle_window_event(self, event)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn notify_of_resize(&self) {
|
|
||||||
if let Some(browser) = &self.browser {
|
|
||||||
browser.host().unwrap().was_resized();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn send_web_message(&self, message: &[u8]) {
|
|
||||||
self.send_message(MessageType::SendToJS, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: ContextState> Drop for Context<S> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if self.browser.is_some() {
|
|
||||||
cef::shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub(crate) enum SetupError {
|
|
||||||
#[error("this is the sub process should exit immediately")]
|
|
||||||
Subprocess,
|
|
||||||
#[error("subprocess returned non zero exit code")]
|
|
||||||
SubprocessFailed(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub(crate) enum InitError {
|
|
||||||
#[error("initialization failed")]
|
|
||||||
InitializationFailed(u32),
|
|
||||||
#[error("Another instance is already running")]
|
|
||||||
AlreadyRunning,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
use cef::args::Args;
|
||||||
|
use cef::sys::{CEF_API_VERSION_LAST, cef_resultcode_t};
|
||||||
|
use cef::{
|
||||||
|
App, BrowserSettings, CefString, Client, DictionaryValue, ImplCommandLine, RenderHandler, RequestContext, Settings, WindowInfo, api_hash, browser_host_create_browser_sync, execute_process,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::CefContext;
|
||||||
|
use super::singlethreaded::SingleThreadedCefContext;
|
||||||
|
use crate::cef::CefHandler;
|
||||||
|
use crate::cef::consts::{FRONTEND_DOMAIN, GRAPHITE_SCHEME};
|
||||||
|
use crate::cef::dirs::{cef_cache_dir, cef_data_dir};
|
||||||
|
use crate::cef::input::InputState;
|
||||||
|
use crate::cef::internal::{BrowserProcessAppImpl, BrowserProcessClientImpl, RenderHandlerImpl, RenderProcessAppImpl};
|
||||||
|
|
||||||
|
pub(crate) struct CefContextBuilder {
|
||||||
|
pub(crate) args: Args,
|
||||||
|
pub(crate) is_sub_process: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for CefContextBuilder {}
|
||||||
|
|
||||||
|
impl CefContextBuilder {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let _loader = {
|
||||||
|
let loader = library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), false);
|
||||||
|
assert!(loader.load());
|
||||||
|
loader
|
||||||
|
};
|
||||||
|
let _ = api_hash(CEF_API_VERSION_LAST, 0);
|
||||||
|
|
||||||
|
let args = Args::new();
|
||||||
|
let cmd = args.as_cmd_line().unwrap();
|
||||||
|
let switch = CefString::from("type");
|
||||||
|
let is_sub_process = cmd.has_switch(Some(&switch)) == 1;
|
||||||
|
|
||||||
|
Self { args, is_sub_process }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_sub_process(&self) -> bool {
|
||||||
|
self.is_sub_process
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn execute_sub_process(&self) -> SetupError {
|
||||||
|
let cmd = self.args.as_cmd_line().unwrap();
|
||||||
|
let switch = CefString::from("type");
|
||||||
|
let process_type = CefString::from(&cmd.switch_value(Some(&switch)));
|
||||||
|
let mut app = RenderProcessAppImpl::app();
|
||||||
|
let ret = execute_process(Some(self.args.as_main_args()), Some(&mut app), std::ptr::null_mut());
|
||||||
|
if ret >= 0 {
|
||||||
|
SetupError::SubprocessFailed(process_type.to_string())
|
||||||
|
} else {
|
||||||
|
SetupError::Subprocess
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub(crate) fn initialize(self, event_handler: CefHandler) -> Result<impl CefContext, InitError> {
|
||||||
|
let settings = Settings {
|
||||||
|
windowless_rendering_enabled: 1,
|
||||||
|
multi_threaded_message_loop: 0,
|
||||||
|
external_message_pump: 1,
|
||||||
|
root_cache_path: cef_data_dir().to_str().map(CefString::from).unwrap(),
|
||||||
|
cache_path: cef_cache_dir().to_str().map(CefString::from).unwrap(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.initialize_inner(&event_handler, settings)?;
|
||||||
|
|
||||||
|
create_browser(event_handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
pub(crate) fn initialize(self, event_handler: CefHandler) -> Result<impl CefContext, InitError> {
|
||||||
|
let settings = Settings {
|
||||||
|
windowless_rendering_enabled: 1,
|
||||||
|
multi_threaded_message_loop: 1,
|
||||||
|
root_cache_path: cef_data_dir().to_str().map(CefString::from).unwrap(),
|
||||||
|
cache_path: cef_cache_dir().to_str().map(CefString::from).unwrap(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
self.initialize_inner(&event_handler, settings)?;
|
||||||
|
|
||||||
|
super::multithreaded::run_on_ui_thread(move || match create_browser(event_handler) {
|
||||||
|
Ok(context) => {
|
||||||
|
super::multithreaded::CONTEXT.with(|b| {
|
||||||
|
*b.borrow_mut() = Some(context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to initialize CEF context: {:?}", e);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(super::multithreaded::MultiThreadedCefContextProxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initialize_inner(self, event_handler: &CefHandler, settings: Settings) -> Result<(), InitError> {
|
||||||
|
let mut cef_app = App::new(BrowserProcessAppImpl::new(event_handler.clone()));
|
||||||
|
let result = cef::initialize(Some(self.args.as_main_args()), Some(&settings), Some(&mut cef_app), std::ptr::null_mut());
|
||||||
|
// Attention! Wrapping this in an extra App is necessary, otherwise the program still compiles but segfaults
|
||||||
|
|
||||||
|
if result != 1 {
|
||||||
|
let cef_exit_code = cef::get_exit_code() as u32;
|
||||||
|
if cef_exit_code == cef_resultcode_t::CEF_RESULT_CODE_NORMAL_EXIT_PROCESS_NOTIFIED as u32 {
|
||||||
|
return Err(InitError::AlreadyRunning);
|
||||||
|
}
|
||||||
|
return Err(InitError::InitializationFailed(cef_exit_code));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_browser(event_handler: CefHandler) -> Result<SingleThreadedCefContext, InitError> {
|
||||||
|
let render_handler = RenderHandler::new(RenderHandlerImpl::new(event_handler.clone()));
|
||||||
|
let mut client = Client::new(BrowserProcessClientImpl::new(render_handler, event_handler.clone()));
|
||||||
|
|
||||||
|
let url = CefString::from(format!("{GRAPHITE_SCHEME}://{FRONTEND_DOMAIN}/").as_str());
|
||||||
|
|
||||||
|
let window_info = WindowInfo {
|
||||||
|
windowless_rendering_enabled: 1,
|
||||||
|
#[cfg(feature = "accelerated_paint")]
|
||||||
|
shared_texture_enabled: if crate::cef::platform::should_enable_hardware_acceleration() { 1 } else { 0 },
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let settings = BrowserSettings {
|
||||||
|
windowless_frame_rate: crate::consts::CEF_WINDOWLESS_FRAME_RATE,
|
||||||
|
background_color: 0x0,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let browser = browser_host_create_browser_sync(
|
||||||
|
Some(&window_info),
|
||||||
|
Some(&mut client),
|
||||||
|
Some(&url),
|
||||||
|
Some(&settings),
|
||||||
|
Option::<&mut DictionaryValue>::None,
|
||||||
|
Option::<&mut RequestContext>::None,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(browser) = browser {
|
||||||
|
Ok(SingleThreadedCefContext {
|
||||||
|
browser,
|
||||||
|
input_state: InputState::default(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
tracing::error!("Failed to create browser");
|
||||||
|
Err(InitError::BrowserCreationFailed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub(crate) enum SetupError {
|
||||||
|
#[error("This is the sub process should exit immediately")]
|
||||||
|
Subprocess,
|
||||||
|
#[error("Subprocess returned non zero exit code")]
|
||||||
|
SubprocessFailed(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub(crate) enum InitError {
|
||||||
|
#[error("Initialization failed")]
|
||||||
|
InitializationFailed(u32),
|
||||||
|
#[error("Browser creation failed")]
|
||||||
|
BrowserCreationFailed,
|
||||||
|
#[error("Another instance is already running")]
|
||||||
|
AlreadyRunning,
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
use cef::sys::cef_thread_id_t;
|
||||||
|
use cef::{Task, ThreadId, post_task};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use winit::event::WindowEvent;
|
||||||
|
|
||||||
|
use crate::cef::internal::task::ClosureTask;
|
||||||
|
|
||||||
|
use super::CefContext;
|
||||||
|
use super::singlethreaded::SingleThreadedCefContext;
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
pub(super) static CONTEXT: RefCell<Option<SingleThreadedCefContext>> = const { RefCell::new(None) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct MultiThreadedCefContextProxy;
|
||||||
|
|
||||||
|
impl CefContext for MultiThreadedCefContextProxy {
|
||||||
|
fn work(&mut self) {
|
||||||
|
// CEF handles its own message loop in multi-threaded mode
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_window_event(&mut self, event: &WindowEvent) {
|
||||||
|
let event_clone = event.clone();
|
||||||
|
run_on_ui_thread(move || {
|
||||||
|
CONTEXT.with(|b| {
|
||||||
|
if let Some(context) = b.borrow_mut().as_mut() {
|
||||||
|
context.handle_window_event(&event_clone);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn notify_of_resize(&self) {
|
||||||
|
run_on_ui_thread(move || {
|
||||||
|
CONTEXT.with(|b| {
|
||||||
|
if let Some(context) = b.borrow_mut().as_mut() {
|
||||||
|
context.notify_of_resize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_web_message(&self, message: Vec<u8>) {
|
||||||
|
run_on_ui_thread(move || {
|
||||||
|
CONTEXT.with(|b| {
|
||||||
|
if let Some(context) = b.borrow_mut().as_mut() {
|
||||||
|
context.send_web_message(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for MultiThreadedCefContextProxy {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
cef::shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn run_on_ui_thread<F>(closure: F)
|
||||||
|
where
|
||||||
|
F: FnOnce() + Send + 'static,
|
||||||
|
{
|
||||||
|
let closure_task = ClosureTask::new(closure);
|
||||||
|
let mut task = Task::new(closure_task);
|
||||||
|
post_task(ThreadId::from(cef_thread_id_t::TID_UI), Some(&mut task));
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
use cef::{Browser, ImplBrowser, ImplBrowserHost};
|
||||||
|
use winit::event::WindowEvent;
|
||||||
|
|
||||||
|
use crate::cef::input;
|
||||||
|
use crate::cef::input::InputState;
|
||||||
|
use crate::cef::ipc::{MessageType, SendMessage};
|
||||||
|
|
||||||
|
use super::CefContext;
|
||||||
|
|
||||||
|
pub(super) struct SingleThreadedCefContext {
|
||||||
|
pub(super) browser: Browser,
|
||||||
|
pub(super) input_state: InputState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CefContext for SingleThreadedCefContext {
|
||||||
|
fn work(&mut self) {
|
||||||
|
cef::do_message_loop_work();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_window_event(&mut self, event: &WindowEvent) {
|
||||||
|
input::handle_window_event(&self.browser, &mut self.input_state, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn notify_of_resize(&self) {
|
||||||
|
self.browser.host().unwrap().was_resized();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_web_message(&self, message: Vec<u8>) {
|
||||||
|
self.send_message(MessageType::SendToJS, &message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for SingleThreadedCefContext {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
cef::shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SendMessage for SingleThreadedCefContext {
|
||||||
|
fn send_message(&self, message_type: MessageType, message: &[u8]) {
|
||||||
|
let Some(frame) = self.browser.main_frame() else {
|
||||||
|
tracing::error!("Main frame is not available, cannot send message");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
frame.send_message(message_type, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
+125
-127
@@ -1,164 +1,153 @@
|
|||||||
use cef::sys::{cef_event_flags_t, cef_key_event_type_t, cef_mouse_button_type_t};
|
use cef::sys::{cef_event_flags_t, cef_key_event_type_t, cef_mouse_button_type_t};
|
||||||
use cef::{ImplBrowser, ImplBrowserHost, KeyEvent, KeyEventType, MouseEvent};
|
use cef::{Browser, ImplBrowser, ImplBrowserHost, KeyEvent, KeyEventType, MouseEvent};
|
||||||
use winit::dpi::PhysicalPosition;
|
use winit::dpi::PhysicalPosition;
|
||||||
use winit::event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent};
|
use winit::event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent};
|
||||||
|
|
||||||
use super::context::{Context, Initialized};
|
|
||||||
|
|
||||||
mod keymap;
|
mod keymap;
|
||||||
use keymap::{ToDomBits, ToVKBits};
|
use keymap::{ToDomBits, ToVKBits};
|
||||||
|
|
||||||
pub(crate) fn handle_window_event(context: &mut Context<Initialized>, event: WindowEvent) -> Option<WindowEvent> {
|
pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputState, event: &WindowEvent) {
|
||||||
match event {
|
match event {
|
||||||
WindowEvent::CursorMoved { position, .. } => {
|
WindowEvent::CursorMoved { position, .. } => {
|
||||||
if let Some(browser) = &context.browser {
|
if let Some(host) = browser.host() {
|
||||||
if let Some(host) = browser.host() {
|
host.set_focus(1);
|
||||||
host.set_focus(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.input_state.update_mouse_position(&position);
|
|
||||||
let mouse_event: MouseEvent = (&context.input_state).into();
|
|
||||||
browser.host().unwrap().send_mouse_move_event(Some(&mouse_event), 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input_state.update_mouse_position(position);
|
||||||
|
let mouse_event: MouseEvent = (input_state).into();
|
||||||
|
browser.host().unwrap().send_mouse_move_event(Some(&mouse_event), 0);
|
||||||
}
|
}
|
||||||
WindowEvent::MouseInput { state, button, .. } => {
|
WindowEvent::MouseInput { state, button, .. } => {
|
||||||
if let Some(browser) = &context.browser {
|
if let Some(host) = browser.host() {
|
||||||
if let Some(host) = browser.host() {
|
host.set_focus(1);
|
||||||
host.set_focus(1);
|
|
||||||
|
|
||||||
let mouse_up = match state {
|
let mouse_up = match state {
|
||||||
ElementState::Pressed => 0,
|
ElementState::Pressed => 0,
|
||||||
ElementState::Released => 1,
|
ElementState::Released => 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let cef_button = match button {
|
let cef_button = match button {
|
||||||
MouseButton::Left => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_LEFT)),
|
MouseButton::Left => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_LEFT)),
|
||||||
MouseButton::Right => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_RIGHT)),
|
MouseButton::Right => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_RIGHT)),
|
||||||
MouseButton::Middle => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_MIDDLE)),
|
MouseButton::Middle => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_MIDDLE)),
|
||||||
MouseButton::Forward => None, //TODO: Handle Forward button
|
MouseButton::Forward => None, //TODO: Handle Forward button
|
||||||
MouseButton::Back => None, //TODO: Handle Back button
|
MouseButton::Back => None, //TODO: Handle Back button
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut mouse_state = context.input_state.mouse_state.clone();
|
let mut mouse_state = input_state.mouse_state.clone();
|
||||||
match button {
|
match button {
|
||||||
MouseButton::Left => {
|
MouseButton::Left => {
|
||||||
mouse_state.left = match state {
|
mouse_state.left = match state {
|
||||||
ElementState::Pressed => true,
|
ElementState::Pressed => true,
|
||||||
ElementState::Released => false,
|
ElementState::Released => false,
|
||||||
}
|
|
||||||
}
|
}
|
||||||
MouseButton::Right => {
|
|
||||||
mouse_state.right = match state {
|
|
||||||
ElementState::Pressed => true,
|
|
||||||
ElementState::Released => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MouseButton::Middle => {
|
|
||||||
mouse_state.middle = match state {
|
|
||||||
ElementState::Pressed => true,
|
|
||||||
ElementState::Released => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
context.input_state.update_mouse_state(mouse_state);
|
|
||||||
|
|
||||||
let mouse_event: MouseEvent = (&context.input_state).into();
|
|
||||||
|
|
||||||
if let Some(button) = cef_button {
|
|
||||||
host.send_mouse_click_event(
|
|
||||||
Some(&mouse_event),
|
|
||||||
button,
|
|
||||||
mouse_up,
|
|
||||||
1, // click count
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
MouseButton::Right => {
|
||||||
|
mouse_state.right = match state {
|
||||||
|
ElementState::Pressed => true,
|
||||||
|
ElementState::Released => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseButton::Middle => {
|
||||||
|
mouse_state.middle = match state {
|
||||||
|
ElementState::Pressed => true,
|
||||||
|
ElementState::Released => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
input_state.update_mouse_state(mouse_state);
|
||||||
|
|
||||||
|
let mouse_event: MouseEvent = input_state.into();
|
||||||
|
|
||||||
|
if let Some(button) = cef_button {
|
||||||
|
host.send_mouse_click_event(
|
||||||
|
Some(&mouse_event),
|
||||||
|
button,
|
||||||
|
mouse_up,
|
||||||
|
1, // click count
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowEvent::MouseWheel { delta, phase: _, device_id: _, .. } => {
|
WindowEvent::MouseWheel { delta, phase: _, device_id: _, .. } => {
|
||||||
if let Some(browser) = &context.browser {
|
if let Some(host) = browser.host() {
|
||||||
if let Some(host) = browser.host() {
|
let mouse_event = input_state.into();
|
||||||
let mouse_event = (&context.input_state).into();
|
let line_width = 40; //feels about right, TODO: replace with correct value
|
||||||
let line_width = 40; //feels about right, TODO: replace with correct value
|
let line_height = 30; //feels about right, TODO: replace with correct value
|
||||||
let line_height = 30; //feels about right, TODO: replace with correct value
|
let (delta_x, delta_y) = match delta {
|
||||||
let (delta_x, delta_y) = match delta {
|
MouseScrollDelta::LineDelta(x, y) => (x * line_width as f32, y * line_height as f32),
|
||||||
MouseScrollDelta::LineDelta(x, y) => (x * line_width as f32, y * line_height as f32),
|
MouseScrollDelta::PixelDelta(physical_position) => (physical_position.x as f32, physical_position.y as f32),
|
||||||
MouseScrollDelta::PixelDelta(physical_position) => (physical_position.x as f32, physical_position.y as f32),
|
};
|
||||||
};
|
host.send_mouse_wheel_event(Some(&mouse_event), delta_x as i32, delta_y as i32);
|
||||||
host.send_mouse_wheel_event(Some(&mouse_event), delta_x as i32, delta_y as i32);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WindowEvent::ModifiersChanged(modifiers) => {
|
WindowEvent::ModifiersChanged(modifiers) => {
|
||||||
context.input_state.update_modifiers(&modifiers.state());
|
input_state.update_modifiers(&modifiers.state());
|
||||||
}
|
}
|
||||||
WindowEvent::KeyboardInput { device_id: _, event, is_synthetic: _ } => {
|
WindowEvent::KeyboardInput { device_id: _, event, is_synthetic: _ } => {
|
||||||
if let Some(browser) = &context.browser {
|
if let Some(host) = browser.host() {
|
||||||
if let Some(host) = browser.host() {
|
host.set_focus(1);
|
||||||
host.set_focus(1);
|
|
||||||
|
|
||||||
let (named_key, character) = match &event.logical_key {
|
let (named_key, character) = match &event.logical_key {
|
||||||
winit::keyboard::Key::Named(named_key) => (
|
winit::keyboard::Key::Named(named_key) => (
|
||||||
Some(named_key),
|
Some(named_key),
|
||||||
match named_key {
|
match named_key {
|
||||||
winit::keyboard::NamedKey::Space => Some(' '),
|
winit::keyboard::NamedKey::Space => Some(' '),
|
||||||
winit::keyboard::NamedKey::Enter => Some('\u{000d}'),
|
winit::keyboard::NamedKey::Enter => Some('\u{000d}'),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
winit::keyboard::Key::Character(str) => {
|
winit::keyboard::Key::Character(str) => {
|
||||||
let char = str.chars().next().unwrap_or('\0');
|
let char = str.chars().next().unwrap_or('\0');
|
||||||
(None, Some(char))
|
(None, Some(char))
|
||||||
}
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut key_event = KeyEvent {
|
|
||||||
size: size_of::<KeyEvent>(),
|
|
||||||
focus_on_editable_field: 1,
|
|
||||||
modifiers: context.input_state.cef_modifiers(&event.location, event.repeat).raw(),
|
|
||||||
is_system_key: 0,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(named_key) = named_key {
|
|
||||||
key_event.native_key_code = named_key.to_dom_bits();
|
|
||||||
key_event.windows_key_code = named_key.to_vk_bits();
|
|
||||||
} else if let Some(char) = character {
|
|
||||||
key_event.native_key_code = char.to_dom_bits();
|
|
||||||
key_event.windows_key_code = char.to_vk_bits();
|
|
||||||
}
|
}
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
match event.state {
|
let mut key_event = KeyEvent {
|
||||||
ElementState::Pressed => {
|
size: size_of::<KeyEvent>(),
|
||||||
key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_RAWKEYDOWN);
|
focus_on_editable_field: 1,
|
||||||
host.send_key_event(Some(&key_event));
|
modifiers: input_state.cef_modifiers(&event.location, event.repeat).raw(),
|
||||||
|
is_system_key: 0,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(char) = character {
|
if let Some(named_key) = named_key {
|
||||||
let mut buf = [0; 2];
|
key_event.native_key_code = named_key.to_dom_bits();
|
||||||
char.encode_utf16(&mut buf);
|
key_event.windows_key_code = named_key.to_vk_bits();
|
||||||
key_event.character = buf[0];
|
} else if let Some(char) = character {
|
||||||
let mut buf = [0; 2];
|
key_event.native_key_code = char.to_dom_bits();
|
||||||
char.to_lowercase().next().unwrap().encode_utf16(&mut buf);
|
key_event.windows_key_code = char.to_vk_bits();
|
||||||
key_event.unmodified_character = buf[0];
|
|
||||||
|
|
||||||
key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_CHAR);
|
|
||||||
host.send_key_event(Some(&key_event));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ElementState::Released => {
|
|
||||||
key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_KEYUP);
|
|
||||||
host.send_key_event(Some(&key_event));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match event.state {
|
||||||
|
ElementState::Pressed => {
|
||||||
|
key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_RAWKEYDOWN);
|
||||||
|
host.send_key_event(Some(&key_event));
|
||||||
|
|
||||||
|
if let Some(char) = character {
|
||||||
|
let mut buf = [0; 2];
|
||||||
|
char.encode_utf16(&mut buf);
|
||||||
|
key_event.character = buf[0];
|
||||||
|
let mut buf = [0; 2];
|
||||||
|
char.to_lowercase().next().unwrap().encode_utf16(&mut buf);
|
||||||
|
key_event.unmodified_character = buf[0];
|
||||||
|
|
||||||
|
key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_CHAR);
|
||||||
|
host.send_key_event(Some(&key_event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ElementState::Released => {
|
||||||
|
key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_KEYUP);
|
||||||
|
host.send_key_event(Some(&key_event));
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
e => return Some(e),
|
_ => {}
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
@@ -227,6 +216,15 @@ impl From<&InputState> for MouseEvent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl From<&mut InputState> for MouseEvent {
|
||||||
|
fn from(val: &mut InputState) -> Self {
|
||||||
|
MouseEvent {
|
||||||
|
x: val.mouse_position.x as i32,
|
||||||
|
y: val.mouse_position.y as i32,
|
||||||
|
modifiers: val.cef_modifiers_mouse_event().raw(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct CefModifiers(u32);
|
struct CefModifiers(u32);
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ mod browser_process_app;
|
|||||||
mod browser_process_client;
|
mod browser_process_client;
|
||||||
mod browser_process_handler;
|
mod browser_process_handler;
|
||||||
mod browser_process_life_span_handler;
|
mod browser_process_life_span_handler;
|
||||||
mod render_handler;
|
|
||||||
mod render_process_app;
|
mod render_process_app;
|
||||||
mod render_process_handler;
|
mod render_process_handler;
|
||||||
mod render_process_v8_handler;
|
mod render_process_v8_handler;
|
||||||
|
|
||||||
|
pub(super) mod render_handler;
|
||||||
|
pub(super) mod task;
|
||||||
|
|
||||||
pub(super) use browser_process_app::BrowserProcessAppImpl;
|
pub(super) use browser_process_app::BrowserProcessAppImpl;
|
||||||
pub(super) use browser_process_client::BrowserProcessClientImpl;
|
pub(super) use browser_process_client::BrowserProcessClientImpl;
|
||||||
pub(super) use render_handler::RenderHandlerImpl;
|
pub(super) use render_handler::RenderHandlerImpl;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#[cfg(target_os = "linux")]
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use cef::rc::{Rc, RcImpl};
|
use cef::rc::{Rc, RcImpl};
|
||||||
@@ -34,12 +35,28 @@ impl<H: CefEventHandler + Clone> ImplApp for BrowserProcessAppImpl<H> {
|
|||||||
|
|
||||||
fn on_before_command_line_processing(&self, _process_type: Option<&cef::CefString>, command_line: Option<&mut cef::CommandLine>) {
|
fn on_before_command_line_processing(&self, _process_type: Option<&cef::CefString>, command_line: Option<&mut cef::CommandLine>) {
|
||||||
if let Some(cmd) = command_line {
|
if let Some(cmd) = command_line {
|
||||||
// Disable GPU acceleration, because it is not supported for Offscreen Rendering and can cause crashes.
|
#[cfg(not(feature = "accelerated_paint"))]
|
||||||
cmd.append_switch(Some(&CefString::from("disable-gpu")));
|
{
|
||||||
cmd.append_switch(Some(&CefString::from("disable-gpu-compositing")));
|
// Disable GPU acceleration when accelerated_paint feature is not enabled
|
||||||
|
cmd.append_switch(Some(&CefString::from("disable-gpu")));
|
||||||
|
cmd.append_switch(Some(&CefString::from("disable-gpu-compositing")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "accelerated_paint")]
|
||||||
|
{
|
||||||
|
// Enable GPU acceleration switches for better performance
|
||||||
|
cmd.append_switch(Some(&CefString::from("enable-gpu-rasterization")));
|
||||||
|
cmd.append_switch(Some(&CefString::from("enable-accelerated-2d-canvas")));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "accelerated_paint", target_os = "linux"))]
|
||||||
|
{
|
||||||
|
// Use Vulkan for accelerated painting
|
||||||
|
cmd.append_switch_with_value(Some(&CefString::from("use-angle")), Some(&CefString::from("vulkan")));
|
||||||
|
}
|
||||||
|
|
||||||
// Tell CEF to use Wayland if available
|
// Tell CEF to use Wayland if available
|
||||||
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
let use_wayland = env::var("WAYLAND_DISPLAY")
|
let use_wayland = env::var("WAYLAND_DISPLAY")
|
||||||
.ok()
|
.ok()
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ impl<H: CefEventHandler> ImplClient for BrowserProcessClientImpl<H> {
|
|||||||
) -> ::std::os::raw::c_int {
|
) -> ::std::os::raw::c_int {
|
||||||
let unpacked_message = unsafe { message.and_then(|m| m.unpack()) };
|
let unpacked_message = unsafe { message.and_then(|m| m.unpack()) };
|
||||||
match unpacked_message {
|
match unpacked_message {
|
||||||
|
Some(UnpackedMessage {
|
||||||
|
message_type: MessageType::Initialized,
|
||||||
|
data: _,
|
||||||
|
}) => self.event_handler.initialized_web_communication(),
|
||||||
Some(UnpackedMessage {
|
Some(UnpackedMessage {
|
||||||
message_type: MessageType::SendToNative,
|
message_type: MessageType::SendToNative,
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ use cef::sys::{_cef_browser_process_handler_t, cef_base_ref_counted_t, cef_brows
|
|||||||
use cef::{CefString, ImplBrowserProcessHandler, SchemeHandlerFactory, WrapBrowserProcessHandler};
|
use cef::{CefString, ImplBrowserProcessHandler, SchemeHandlerFactory, WrapBrowserProcessHandler};
|
||||||
|
|
||||||
use crate::cef::CefEventHandler;
|
use crate::cef::CefEventHandler;
|
||||||
use crate::cef::scheme_handler::{GRAPHITE_SCHEME, GraphiteSchemeHandlerFactory};
|
use crate::cef::consts::GRAPHITE_SCHEME;
|
||||||
|
use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory;
|
||||||
|
|
||||||
pub(crate) struct BrowserProcessHandlerImpl<H: CefEventHandler> {
|
pub(crate) struct BrowserProcessHandlerImpl<H: CefEventHandler> {
|
||||||
object: *mut RcImpl<cef_browser_process_handler_t, Self>,
|
object: *mut RcImpl<cef_browser_process_handler_t, Self>,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ pub(crate) struct RenderHandlerImpl<H: CefEventHandler> {
|
|||||||
object: *mut RcImpl<_cef_render_handler_t, Self>,
|
object: *mut RcImpl<_cef_render_handler_t, Self>,
|
||||||
event_handler: H,
|
event_handler: H,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<H: CefEventHandler> RenderHandlerImpl<H> {
|
impl<H: CefEventHandler> RenderHandlerImpl<H> {
|
||||||
pub(crate) fn new(event_handler: H) -> Self {
|
pub(crate) fn new(event_handler: H) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -47,6 +48,23 @@ impl<H: CefEventHandler> ImplRenderHandler for RenderHandlerImpl<H> {
|
|||||||
self.event_handler.draw(frame_buffer)
|
self.event_handler.draw(frame_buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "accelerated_paint")]
|
||||||
|
fn on_accelerated_paint(&self, _browser: Option<&mut Browser>, type_: PaintElementType, _dirty_rect_count: usize, _dirty_rects: Option<&Rect>, info: Option<&cef::AcceleratedPaintInfo>) {
|
||||||
|
use crate::cef::texture_import::shared_texture_handle::SharedTextureHandle;
|
||||||
|
|
||||||
|
if type_ != PaintElementType::default() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let shared_handle = SharedTextureHandle::new(info.unwrap());
|
||||||
|
if let SharedTextureHandle::Unsupported = shared_handle {
|
||||||
|
tracing::error!("Platform does not support accelerated painting");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.event_handler.draw_gpu(shared_handle);
|
||||||
|
}
|
||||||
|
|
||||||
fn get_raw(&self) -> *mut _cef_render_handler_t {
|
fn get_raw(&self) -> *mut _cef_render_handler_t {
|
||||||
self.object.cast()
|
self.object.cast()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,24 +76,30 @@ impl ImplRenderProcessHandler for RenderProcessHandlerImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_context_created(&self, _browser: Option<&mut cef::Browser>, _frame: Option<&mut cef::Frame>, context: Option<&mut cef::V8Context>) {
|
fn on_context_created(&self, _browser: Option<&mut cef::Browser>, _frame: Option<&mut cef::Frame>, context: Option<&mut cef::V8Context>) {
|
||||||
let function_name = "sendNativeMessage";
|
let register_js_function = |context: &mut cef::V8Context, name: &'static str| {
|
||||||
|
let mut v8_handler = V8Handler::new(BrowserProcessV8HandlerImpl::new());
|
||||||
|
let Some(mut function) = v8_value_create_function(Some(&CefString::from(name)), Some(&mut v8_handler)) else {
|
||||||
|
tracing::error!("Failed to create V8 function {name}");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(global) = context.global() else {
|
||||||
|
tracing::error!("Global object is not available in V8 context");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
global.set_value_bykey(Some(&CefString::from(name)), Some(&mut function), V8Propertyattribute::default());
|
||||||
|
};
|
||||||
|
|
||||||
let Some(context) = context else {
|
let Some(context) = context else {
|
||||||
tracing::error!("V8 context is not available");
|
tracing::error!("V8 context is not available");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut v8_handler = V8Handler::new(BrowserProcessV8HandlerImpl::new());
|
let initialized_function_name = "initializeNativeCommunication";
|
||||||
let Some(mut function) = v8_value_create_function(Some(&CefString::from(function_name)), Some(&mut v8_handler)) else {
|
let send_function_name = "sendNativeMessage";
|
||||||
tracing::error!("Failed to create V8 function {function_name}");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(global) = context.global() else {
|
register_js_function(context, initialized_function_name);
|
||||||
tracing::error!("Global object is not available in V8 context");
|
register_js_function(context, send_function_name);
|
||||||
return;
|
|
||||||
};
|
|
||||||
global.set_value_bykey(Some(&CefString::from(function_name)), Some(&mut function), V8Propertyattribute::default());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_raw(&self) -> *mut _cef_render_process_handler_t {
|
fn get_raw(&self) -> *mut _cef_render_process_handler_t {
|
||||||
|
|||||||
@@ -21,8 +21,11 @@ impl ImplV8Handler for BrowserProcessV8HandlerImpl {
|
|||||||
_retval: Option<&mut Option<V8Value>>,
|
_retval: Option<&mut Option<V8Value>>,
|
||||||
_exception: Option<&mut cef::CefString>,
|
_exception: Option<&mut cef::CefString>,
|
||||||
) -> ::std::os::raw::c_int {
|
) -> ::std::os::raw::c_int {
|
||||||
if let Some(name) = name {
|
match name.map(|s| s.to_string()).unwrap_or_default().as_str() {
|
||||||
if name.to_string() == "sendNativeMessage" {
|
"initializeNativeCommunication" => {
|
||||||
|
v8_context_get_current_context().send_message(MessageType::Initialized, vec![0u8].as_slice());
|
||||||
|
}
|
||||||
|
"sendNativeMessage" => {
|
||||||
let Some(args) = arguments else {
|
let Some(args) = arguments else {
|
||||||
tracing::error!("No arguments provided to sendNativeMessage");
|
tracing::error!("No arguments provided to sendNativeMessage");
|
||||||
return 0;
|
return 0;
|
||||||
@@ -48,6 +51,9 @@ impl ImplV8Handler for BrowserProcessV8HandlerImpl {
|
|||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
name => {
|
||||||
|
tracing::error!("Unknown V8 function called: {}", name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
1
|
1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
use cef::rc::{Rc, RcImpl};
|
||||||
|
use cef::sys::{_cef_task_t, cef_base_ref_counted_t};
|
||||||
|
use cef::{ImplTask, WrapTask};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
// Closure-based task wrapper following CEF patterns
|
||||||
|
pub struct ClosureTask<F> {
|
||||||
|
pub(crate) object: *mut RcImpl<_cef_task_t, Self>,
|
||||||
|
pub(crate) closure: RefCell<Option<F>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: FnOnce() + Send + 'static> ClosureTask<F> {
|
||||||
|
pub fn new(closure: F) -> Self {
|
||||||
|
Self {
|
||||||
|
object: std::ptr::null_mut(),
|
||||||
|
closure: RefCell::new(Some(closure)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: FnOnce() + Send + 'static> ImplTask for ClosureTask<F> {
|
||||||
|
fn execute(&self) {
|
||||||
|
if let Some(closure) = self.closure.borrow_mut().take() {
|
||||||
|
closure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_raw(&self) -> *mut _cef_task_t {
|
||||||
|
self.object.cast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: FnOnce() + Send + 'static> Clone for ClosureTask<F> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
unsafe {
|
||||||
|
if !self.object.is_null() {
|
||||||
|
let rc_impl = &mut *self.object;
|
||||||
|
rc_impl.interface.add_ref();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
object: self.object,
|
||||||
|
closure: RefCell::new(None), // Closure can only be executed once
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: FnOnce() + Send + 'static> Rc for ClosureTask<F> {
|
||||||
|
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||||
|
unsafe {
|
||||||
|
let base = &*self.object;
|
||||||
|
std::mem::transmute(&base.cef_object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: FnOnce() + Send + 'static> WrapTask for ClosureTask<F> {
|
||||||
|
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_task_t, Self>) {
|
||||||
|
self.object = object;
|
||||||
|
}
|
||||||
|
}
|
||||||
+7
-18
@@ -1,14 +1,17 @@
|
|||||||
use cef::{CefString, Frame, ImplBinaryValue, ImplBrowser, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ProcessId, V8Context, sys::cef_process_id_t};
|
use cef::{CefString, Frame, ImplBinaryValue, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ProcessId, V8Context, sys::cef_process_id_t};
|
||||||
|
|
||||||
use super::{Context, Initialized};
|
|
||||||
|
|
||||||
pub(crate) enum MessageType {
|
pub(crate) enum MessageType {
|
||||||
|
Initialized,
|
||||||
SendToJS,
|
SendToJS,
|
||||||
SendToNative,
|
SendToNative,
|
||||||
}
|
}
|
||||||
impl From<MessageType> for MessageInfo {
|
impl From<MessageType> for MessageInfo {
|
||||||
fn from(val: MessageType) -> Self {
|
fn from(val: MessageType) -> Self {
|
||||||
match val {
|
match val {
|
||||||
|
MessageType::Initialized => MessageInfo {
|
||||||
|
name: "initialized".to_string(),
|
||||||
|
target: cef_process_id_t::PID_BROWSER.into(),
|
||||||
|
},
|
||||||
MessageType::SendToJS => MessageInfo {
|
MessageType::SendToJS => MessageInfo {
|
||||||
name: "send_to_js".to_string(),
|
name: "send_to_js".to_string(),
|
||||||
target: cef_process_id_t::PID_RENDERER.into(),
|
target: cef_process_id_t::PID_RENDERER.into(),
|
||||||
@@ -24,6 +27,7 @@ impl TryFrom<String> for MessageType {
|
|||||||
type Error = ();
|
type Error = ();
|
||||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
match value.as_str() {
|
match value.as_str() {
|
||||||
|
"initialized" => Ok(MessageType::Initialized),
|
||||||
"send_to_js" => Ok(MessageType::SendToJS),
|
"send_to_js" => Ok(MessageType::SendToJS),
|
||||||
"send_to_native" => Ok(MessageType::SendToNative),
|
"send_to_native" => Ok(MessageType::SendToNative),
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
@@ -39,21 +43,6 @@ pub(crate) struct MessageInfo {
|
|||||||
pub(crate) trait SendMessage {
|
pub(crate) trait SendMessage {
|
||||||
fn send_message(&self, message_type: MessageType, message: &[u8]);
|
fn send_message(&self, message_type: MessageType, message: &[u8]);
|
||||||
}
|
}
|
||||||
impl SendMessage for Context<Initialized> {
|
|
||||||
fn send_message(&self, message_type: MessageType, message: &[u8]) {
|
|
||||||
let Some(browser) = &self.browser else {
|
|
||||||
tracing::error!("Browser is not initialized, cannot send message");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let Some(frame) = browser.main_frame() else {
|
|
||||||
tracing::error!("Main frame is not available, cannot send message");
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
frame.send_message(message_type, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl SendMessage for Option<V8Context> {
|
impl SendMessage for Option<V8Context> {
|
||||||
fn send_message(&self, message_type: MessageType, message: &[u8]) {
|
fn send_message(&self, message_type: MessageType, message: &[u8]) {
|
||||||
let Some(context) = self else {
|
let Some(context) = self else {
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#[cfg(feature = "accelerated_paint")]
|
||||||
|
pub fn should_enable_hardware_acceleration() -> bool {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
// Check if running on Wayland or X11
|
||||||
|
let has_wayland = std::env::var("WAYLAND_DISPLAY")
|
||||||
|
.ok()
|
||||||
|
.filter(|var| !var.is_empty())
|
||||||
|
.or_else(|| std::env::var("WAYLAND_SOCKET").ok())
|
||||||
|
.filter(|var| !var.is_empty())
|
||||||
|
.is_some();
|
||||||
|
|
||||||
|
let has_x11 = std::env::var("DISPLAY").ok().filter(|var| !var.is_empty()).is_some();
|
||||||
|
|
||||||
|
if !has_wayland && !has_x11 {
|
||||||
|
tracing::warn!("No display server detected, disabling hardware acceleration");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for NVIDIA proprietary driver (known to have issues)
|
||||||
|
if let Ok(driver_info) = std::fs::read_to_string("/proc/driver/nvidia/version") {
|
||||||
|
if driver_info.contains("NVIDIA") {
|
||||||
|
tracing::warn!("NVIDIA proprietary driver detected, hardware acceleration may be unstable");
|
||||||
|
// Still return true but with warning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for basic GPU capabilities
|
||||||
|
if has_wayland {
|
||||||
|
tracing::info!("Wayland detected, enabling hardware acceleration");
|
||||||
|
true
|
||||||
|
} else if has_x11 {
|
||||||
|
tracing::info!("X11 detected, enabling hardware acceleration");
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
// Windows generally has good D3D11 support
|
||||||
|
tracing::info!("Windows detected, enabling hardware acceleration");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
// macOS has good Metal/IOSurface support
|
||||||
|
tracing::info!("macOS detected, enabling hardware acceleration");
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
|
||||||
|
{
|
||||||
|
tracing::warn!("Unsupported platform for hardware acceleration");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,8 +11,7 @@ use cef::{
|
|||||||
};
|
};
|
||||||
use include_dir::{Dir, include_dir};
|
use include_dir::{Dir, include_dir};
|
||||||
|
|
||||||
pub(crate) const GRAPHITE_SCHEME: &str = "graphite-static";
|
use super::consts::{FRONTEND_DOMAIN, GRAPHITE_SCHEME};
|
||||||
pub(crate) const FRONTEND_DOMAIN: &str = "frontend";
|
|
||||||
|
|
||||||
pub(crate) struct GraphiteSchemeHandlerFactory {
|
pub(crate) struct GraphiteSchemeHandlerFactory {
|
||||||
object: *mut RcImpl<_cef_scheme_handler_factory_t, Self>,
|
object: *mut RcImpl<_cef_scheme_handler_factory_t, Self>,
|
||||||
@@ -88,6 +87,7 @@ impl<'a> GraphiteFrontendResourceHandler<'a> {
|
|||||||
match ext {
|
match ext {
|
||||||
"html" => Some("text/html".to_string()),
|
"html" => Some("text/html".to_string()),
|
||||||
"css" => Some("text/css".to_string()),
|
"css" => Some("text/css".to_string()),
|
||||||
|
"txt" => Some("text/plain".to_string()),
|
||||||
"wasm" => Some("application/wasm".to_string()),
|
"wasm" => Some("application/wasm".to_string()),
|
||||||
"js" => Some("application/javascript".to_string()),
|
"js" => Some("application/javascript".to_string()),
|
||||||
"png" => Some("image/png".to_string()),
|
"png" => Some("image/png".to_string()),
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
//! Common utilities and traits for texture import across platforms
|
||||||
|
|
||||||
|
use crate::cef::texture_import::*;
|
||||||
|
use ash::vk;
|
||||||
|
use cef::sys::cef_color_type_t;
|
||||||
|
use wgpu::Device;
|
||||||
|
|
||||||
|
/// Common format conversion utilities
|
||||||
|
pub mod format {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Convert CEF color type to wgpu texture format
|
||||||
|
pub fn cef_to_wgpu(format: cef_color_type_t) -> Result<wgpu::TextureFormat, TextureImportError> {
|
||||||
|
match format {
|
||||||
|
cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(wgpu::TextureFormat::Bgra8UnormSrgb),
|
||||||
|
cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(wgpu::TextureFormat::Rgba8UnormSrgb),
|
||||||
|
_ => Err(TextureImportError::UnsupportedFormat { format }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
/// Convert CEF color type to Vulkan format
|
||||||
|
pub fn cef_to_vulkan(format: cef_color_type_t) -> Result<vk::Format, TextureImportError> {
|
||||||
|
match format {
|
||||||
|
cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(vk::Format::B8G8R8A8_UNORM),
|
||||||
|
cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(vk::Format::R8G8B8A8_UNORM),
|
||||||
|
_ => Err(TextureImportError::UnsupportedFormat { format }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Common texture creation utilities
|
||||||
|
pub mod texture {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Create a fallback CPU texture with the given dimensions and format
|
||||||
|
pub fn create_fallback(device: &Device, width: u32, height: u32, format: cef_color_type_t, label: &str) -> TextureImportResult {
|
||||||
|
let wgpu_format = format::cef_to_wgpu(format)?;
|
||||||
|
|
||||||
|
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: Some(label),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu_format,
|
||||||
|
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||||
|
view_formats: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
tracing::warn!(
|
||||||
|
"Using fallback CPU texture for CEF rendering ({}x{}, {:?}) - hardware acceleration failed or unavailable. Consider checking GPU driver support.",
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
format
|
||||||
|
);
|
||||||
|
Ok(texture)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Common Vulkan utilities
|
||||||
|
pub mod vulkan {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Find a suitable memory type index for Vulkan allocation
|
||||||
|
pub fn find_memory_type_index(type_filter: u32, properties: vk::MemoryPropertyFlags, mem_properties: &vk::PhysicalDeviceMemoryProperties) -> Option<u32> {
|
||||||
|
(0..mem_properties.memory_type_count).find(|&i| (type_filter & (1 << i)) != 0 && mem_properties.memory_types[i as usize].property_flags.contains(properties))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the wgpu device is using Vulkan backend
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
pub fn is_vulkan_backend(device: &Device) -> bool {
|
||||||
|
use wgpu::hal::api;
|
||||||
|
let mut is_vulkan = false;
|
||||||
|
unsafe {
|
||||||
|
device.as_hal::<api::Vulkan, _, _>(|device| {
|
||||||
|
is_vulkan = device.is_some();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
is_vulkan
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the wgpu device is using D3D12 backend
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
pub fn is_d3d12_backend(device: &Device) -> bool {
|
||||||
|
use wgpu::hal::api;
|
||||||
|
let mut is_d3d12 = false;
|
||||||
|
unsafe {
|
||||||
|
device.as_hal::<api::Dx12, _, _>(|device| {
|
||||||
|
is_d3d12 = device.is_some();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
is_d3d12
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,290 @@
|
|||||||
|
//! Windows D3D11 shared texture import implementation
|
||||||
|
|
||||||
|
use super::common::{format, texture, vulkan};
|
||||||
|
use super::{TextureImportError, TextureImportResult, TextureImporter};
|
||||||
|
use ash::vk;
|
||||||
|
use cef::{AcceleratedPaintInfo, sys::cef_color_type_t};
|
||||||
|
use std::os::raw::c_void;
|
||||||
|
use wgpu::hal::api;
|
||||||
|
|
||||||
|
pub struct D3D11Importer {
|
||||||
|
pub handle: *mut c_void,
|
||||||
|
pub format: cef_color_type_t,
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextureImporter for D3D11Importer {
|
||||||
|
fn new(info: &AcceleratedPaintInfo) -> Self {
|
||||||
|
Self {
|
||||||
|
handle: info.shared_texture_handle,
|
||||||
|
format: *info.format.as_ref(),
|
||||||
|
width: info.extra.coded_size.width as u32,
|
||||||
|
height: info.extra.coded_size.height as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_to_wgpu(&self, device: &wgpu::Device) -> TextureImportResult {
|
||||||
|
// Try hardware acceleration first
|
||||||
|
if self.supports_hardware_acceleration(device) {
|
||||||
|
// Try D3D12 first (most efficient on Windows)
|
||||||
|
if vulkan::is_d3d12_backend(device) {
|
||||||
|
match self.import_via_d3d12(device) {
|
||||||
|
Ok(texture) => {
|
||||||
|
tracing::info!("Successfully imported D3D11 shared texture via D3D12");
|
||||||
|
return Ok(texture);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("Failed to import D3D11 via D3D12: {}, trying Vulkan fallback", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try Vulkan as fallback
|
||||||
|
if vulkan::is_vulkan_backend(device) {
|
||||||
|
match self.import_via_vulkan(device) {
|
||||||
|
Ok(texture) => {
|
||||||
|
tracing::info!("Successfully imported D3D11 shared texture via Vulkan");
|
||||||
|
return Ok(texture);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("Failed to import D3D11 via Vulkan: {}, falling back to CPU texture", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to CPU texture
|
||||||
|
texture::create_fallback(device, self.width, self.height, self.format, "CEF D3D11 Texture (fallback)")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supports_hardware_acceleration(&self, device: &wgpu::Device) -> bool {
|
||||||
|
// Check if handle is valid
|
||||||
|
if self.handle.is_null() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if wgpu is using D3D12 or Vulkan backend
|
||||||
|
vulkan::is_d3d12_backend(device) || vulkan::is_vulkan_backend(device)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl D3D11Importer {
|
||||||
|
fn import_via_d3d12(&self, device: &wgpu::Device) -> TextureImportResult {
|
||||||
|
// Get wgpu's D3D12 device
|
||||||
|
use wgpu::hal::api;
|
||||||
|
let hal_texture = unsafe {
|
||||||
|
device.as_hal::<api::Dx12, _, _>(|device| {
|
||||||
|
let Some(device) = device else {
|
||||||
|
return Err(TextureImportError::HardwareUnavailable {
|
||||||
|
reason: "Device is not using D3D12 backend".to_string(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Import D3D11 shared handle directly into D3D12 resource
|
||||||
|
let d3d12_resource = self.import_d3d11_handle_to_d3d12(device)?;
|
||||||
|
|
||||||
|
// Wrap D3D12 resource in wgpu-hal texture
|
||||||
|
let hal_texture = <api::Dx12 as wgpu::hal::Api>::Device::texture_from_raw(
|
||||||
|
d3d12_resource,
|
||||||
|
format::cef_to_wgpu(self.format)?,
|
||||||
|
wgpu::TextureDimension::D2,
|
||||||
|
wgpu::Extent3d {
|
||||||
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
1, // mip_level_count
|
||||||
|
1, // sample_count
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(hal_texture)
|
||||||
|
})
|
||||||
|
}?;
|
||||||
|
|
||||||
|
// Import hal texture into wgpu
|
||||||
|
let texture = unsafe {
|
||||||
|
device.create_texture_from_hal::<api::Dx12>(
|
||||||
|
hal_texture,
|
||||||
|
&wgpu::TextureDescriptor {
|
||||||
|
label: Some("CEF D3D11→D3D12 Shared Texture"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: format::cef_to_wgpu(self.format)?,
|
||||||
|
usage: wgpu::TextureUsages::TEXTURE_BINDING,
|
||||||
|
view_formats: &[],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(texture)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_via_vulkan(&self, device: &wgpu::Device) -> TextureImportResult {
|
||||||
|
// Get wgpu's Vulkan instance and device
|
||||||
|
use wgpu::{TextureUses, wgc::api::Vulkan};
|
||||||
|
let hal_texture = unsafe {
|
||||||
|
device.as_hal::<api::Vulkan, _, _>(|device| {
|
||||||
|
let Some(device) = device else {
|
||||||
|
return Err(TextureImportError::HardwareUnavailable {
|
||||||
|
reason: "Device is not using Vulkan backend".to_string(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Import D3D11 shared handle into Vulkan
|
||||||
|
let vk_image = self.import_d3d11_handle_to_vulkan(device)?;
|
||||||
|
|
||||||
|
// Wrap VkImage in wgpu-hal texture
|
||||||
|
let hal_texture = <api::Vulkan as wgpu::hal::Api>::Device::texture_from_raw(
|
||||||
|
vk_image,
|
||||||
|
&wgpu::hal::TextureDescriptor {
|
||||||
|
label: Some("CEF D3D11 Shared Texture"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: format::cef_to_wgpu(self.format)?,
|
||||||
|
usage: TextureUses::COPY_DST | TextureUses::RESOURCE,
|
||||||
|
memory_flags: wgpu::hal::MemoryFlags::empty(),
|
||||||
|
view_formats: vec![],
|
||||||
|
},
|
||||||
|
None, // drop_callback
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(hal_texture)
|
||||||
|
})
|
||||||
|
}?;
|
||||||
|
|
||||||
|
// Import hal texture into wgpu
|
||||||
|
let texture = unsafe {
|
||||||
|
device.create_texture_from_hal::<Vulkan>(
|
||||||
|
hal_texture,
|
||||||
|
&wgpu::TextureDescriptor {
|
||||||
|
label: Some("CEF D3D11 Shared Texture"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: format::cef_to_wgpu(self.format)?,
|
||||||
|
usage: wgpu::TextureUsages::TEXTURE_BINDING,
|
||||||
|
view_formats: &[],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(texture)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_d3d11_handle_to_vulkan(&self, hal_device: &<api::Vulkan as wgpu::hal::Api>::Device) -> Result<vk::Image, TextureImportError> {
|
||||||
|
// Get raw Vulkan handles
|
||||||
|
let device = hal_device.raw_device();
|
||||||
|
let _instance = hal_device.shared_instance().raw_instance();
|
||||||
|
|
||||||
|
// Validate dimensions
|
||||||
|
if self.width == 0 || self.height == 0 {
|
||||||
|
return Err(TextureImportError::InvalidHandle("Invalid D3D11 texture dimensions".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create external memory image info
|
||||||
|
let mut external_memory_info = vk::ExternalMemoryImageCreateInfo::default().handle_types(vk::ExternalMemoryHandleTypeFlags::D3D11_TEXTURE);
|
||||||
|
|
||||||
|
// Create image create info
|
||||||
|
let image_create_info = vk::ImageCreateInfo::default()
|
||||||
|
.image_type(vk::ImageType::TYPE_2D)
|
||||||
|
.format(format::cef_to_vulkan(self.format)?)
|
||||||
|
.extent(vk::Extent3D {
|
||||||
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
|
depth: 1,
|
||||||
|
})
|
||||||
|
.mip_levels(1)
|
||||||
|
.array_layers(1)
|
||||||
|
.samples(vk::SampleCountFlags::TYPE_1)
|
||||||
|
.tiling(vk::ImageTiling::OPTIMAL)
|
||||||
|
.usage(vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::COLOR_ATTACHMENT)
|
||||||
|
.sharing_mode(vk::SharingMode::EXCLUSIVE)
|
||||||
|
.push_next(&mut external_memory_info);
|
||||||
|
|
||||||
|
// Create the image
|
||||||
|
let image = unsafe {
|
||||||
|
device.create_image(&image_create_info, None).map_err(|e| TextureImportError::VulkanError {
|
||||||
|
operation: format!("Failed to create Vulkan image: {:?}", e),
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get memory requirements
|
||||||
|
let memory_requirements = unsafe { device.get_image_memory_requirements(image) };
|
||||||
|
|
||||||
|
// Import D3D11 handle
|
||||||
|
let mut import_memory_win32 = vk::ImportMemoryWin32HandleInfoKHR::default()
|
||||||
|
.handle_type(vk::ExternalMemoryHandleTypeFlags::D3D11_TEXTURE)
|
||||||
|
.handle(self.handle as isize);
|
||||||
|
|
||||||
|
// Find a suitable memory type
|
||||||
|
let memory_properties = unsafe { hal_device.shared_instance().raw_instance().get_physical_device_memory_properties(hal_device.raw_physical_device()) };
|
||||||
|
|
||||||
|
let memory_type_index =
|
||||||
|
vulkan::find_memory_type_index(memory_requirements.memory_type_bits, vk::MemoryPropertyFlags::empty(), &memory_properties).ok_or_else(|| TextureImportError::VulkanError {
|
||||||
|
operation: "Failed to find suitable memory type for D3D11 texture".to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let allocate_info = vk::MemoryAllocateInfo::default()
|
||||||
|
.allocation_size(memory_requirements.size)
|
||||||
|
.memory_type_index(memory_type_index)
|
||||||
|
.push_next(&mut import_memory_win32);
|
||||||
|
|
||||||
|
let device_memory = unsafe {
|
||||||
|
device.allocate_memory(&allocate_info, None).map_err(|e| TextureImportError::VulkanError {
|
||||||
|
operation: format!("Failed to allocate memory for D3D11 texture: {:?}", e),
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bind memory to image
|
||||||
|
unsafe {
|
||||||
|
device.bind_image_memory(image, device_memory, 0).map_err(|e| TextureImportError::VulkanError {
|
||||||
|
operation: format!("Failed to bind memory to image: {:?}", e),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(image)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_d3d11_handle_to_d3d12(&self, hal_device: &<wgpu::hal::api::Dx12 as wgpu::hal::Api>::Device) -> Result<windows::Win32::Graphics::Direct3D12::ID3D12Resource, TextureImportError> {
|
||||||
|
use windows::Win32::Graphics::Direct3D12::*;
|
||||||
|
use windows::core::*;
|
||||||
|
|
||||||
|
// Get D3D12 device from wgpu-hal
|
||||||
|
let d3d12_device = hal_device.raw_device();
|
||||||
|
|
||||||
|
// Validate dimensions
|
||||||
|
if self.width == 0 || self.height == 0 {
|
||||||
|
return Err(TextureImportError::InvalidHandle("Invalid D3D11 texture dimensions".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open D3D11 shared handle on D3D12 device
|
||||||
|
unsafe {
|
||||||
|
let mut shared_resource: Option<ID3D12Resource> = None;
|
||||||
|
d3d12_device
|
||||||
|
.OpenSharedHandle(windows::Win32::Foundation::HANDLE(self.handle), &mut shared_resource)
|
||||||
|
.map_err(|e| TextureImportError::PlatformError {
|
||||||
|
message: format!("Failed to open D3D11 shared handle on D3D12: {:?}", e),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
shared_resource.ok_or_else(|| TextureImportError::InvalidHandle("Failed to get D3D12 resource from shared handle".to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,273 @@
|
|||||||
|
//! Linux DMA-BUF texture import implementation
|
||||||
|
|
||||||
|
use super::common::{format, texture, vulkan};
|
||||||
|
use super::{TextureImportError, TextureImportResult, TextureImporter};
|
||||||
|
use ash::vk;
|
||||||
|
use cef::{AcceleratedPaintInfo, sys::cef_color_type_t};
|
||||||
|
use wgpu::hal::api;
|
||||||
|
|
||||||
|
pub(crate) struct DmaBufImporter {
|
||||||
|
fds: Vec<std::os::fd::RawFd>,
|
||||||
|
format: cef_color_type_t,
|
||||||
|
modifier: u64,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
strides: Vec<u32>,
|
||||||
|
offsets: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextureImporter for DmaBufImporter {
|
||||||
|
fn new(info: &AcceleratedPaintInfo) -> Self {
|
||||||
|
Self {
|
||||||
|
fds: extract_fds_from_info(info),
|
||||||
|
format: *info.format.as_ref(),
|
||||||
|
modifier: info.modifier,
|
||||||
|
width: info.extra.coded_size.width as u32,
|
||||||
|
height: info.extra.coded_size.height as u32,
|
||||||
|
strides: extract_strides_from_info(info),
|
||||||
|
offsets: extract_offsets_from_info(info),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_to_wgpu(&self, device: &wgpu::Device) -> TextureImportResult {
|
||||||
|
// Try hardware acceleration first
|
||||||
|
if self.supports_hardware_acceleration(device) {
|
||||||
|
match self.import_via_vulkan(device) {
|
||||||
|
Ok(texture) => {
|
||||||
|
tracing::info!("Successfully imported DMA-BUF texture via Vulkan");
|
||||||
|
return Ok(texture);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("Failed to import DMA-BUF via Vulkan: {}, falling back to CPU texture", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to CPU texture
|
||||||
|
texture::create_fallback(device, self.width, self.height, self.format, "CEF DMA-BUF Texture (fallback)")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supports_hardware_acceleration(&self, device: &wgpu::Device) -> bool {
|
||||||
|
// Check if we have valid file descriptors
|
||||||
|
if self.fds.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for &fd in &self.fds {
|
||||||
|
if fd < 0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Check if file descriptor is valid
|
||||||
|
let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
|
||||||
|
if flags == -1 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if wgpu is using Vulkan backend
|
||||||
|
vulkan::is_vulkan_backend(device)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DmaBufImporter {
|
||||||
|
fn import_via_vulkan(&self, device: &wgpu::Device) -> TextureImportResult {
|
||||||
|
// Get wgpu's Vulkan instance and device
|
||||||
|
use wgpu::{TextureUses, wgc::api::Vulkan};
|
||||||
|
let hal_texture = unsafe {
|
||||||
|
device.as_hal::<api::Vulkan, _, _>(|device| {
|
||||||
|
let Some(device) = device else {
|
||||||
|
return Err(TextureImportError::HardwareUnavailable {
|
||||||
|
reason: "Device is not using Vulkan backend".to_string(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create VkImage from DMA-BUF using external memory
|
||||||
|
let vk_image = self.create_vulkan_image_from_dmabuf(device)?;
|
||||||
|
|
||||||
|
// Wrap VkImage in wgpu-hal texture
|
||||||
|
let hal_texture = <api::Vulkan as wgpu::hal::Api>::Device::texture_from_raw(
|
||||||
|
vk_image,
|
||||||
|
&wgpu::hal::TextureDescriptor {
|
||||||
|
label: Some("CEF DMA-BUF Texture"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: format::cef_to_wgpu(self.format)?,
|
||||||
|
usage: TextureUses::COPY_DST | TextureUses::RESOURCE,
|
||||||
|
memory_flags: wgpu::hal::MemoryFlags::empty(),
|
||||||
|
view_formats: vec![],
|
||||||
|
},
|
||||||
|
None, // drop_callback
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(hal_texture)
|
||||||
|
})
|
||||||
|
}?;
|
||||||
|
|
||||||
|
// Import hal texture into wgpu
|
||||||
|
let texture = unsafe {
|
||||||
|
device.create_texture_from_hal::<Vulkan>(
|
||||||
|
hal_texture,
|
||||||
|
&wgpu::TextureDescriptor {
|
||||||
|
label: Some("CEF DMA-BUF Texture"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: format::cef_to_wgpu(self.format)?,
|
||||||
|
usage: wgpu::TextureUsages::TEXTURE_BINDING,
|
||||||
|
view_formats: &[],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(texture)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_vulkan_image_from_dmabuf(&self, hal_device: &<api::Vulkan as wgpu::hal::Api>::Device) -> Result<vk::Image, TextureImportError> {
|
||||||
|
// Get raw Vulkan handles
|
||||||
|
let device = hal_device.raw_device();
|
||||||
|
let _instance = hal_device.shared_instance().raw_instance();
|
||||||
|
|
||||||
|
// Validate dimensions
|
||||||
|
if self.width == 0 || self.height == 0 {
|
||||||
|
return Err(TextureImportError::InvalidHandle("Invalid DMA-BUF dimensions".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create external memory image
|
||||||
|
let image_create_info = vk::ImageCreateInfo::default()
|
||||||
|
.image_type(vk::ImageType::TYPE_2D)
|
||||||
|
.format(format::cef_to_vulkan(self.format)?)
|
||||||
|
.extent(vk::Extent3D {
|
||||||
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
|
depth: 1,
|
||||||
|
})
|
||||||
|
.mip_levels(1)
|
||||||
|
.array_layers(1)
|
||||||
|
.samples(vk::SampleCountFlags::TYPE_1)
|
||||||
|
.tiling(vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT)
|
||||||
|
.usage(vk::ImageUsageFlags::SAMPLED | vk::ImageUsageFlags::COLOR_ATTACHMENT)
|
||||||
|
.sharing_mode(vk::SharingMode::EXCLUSIVE);
|
||||||
|
|
||||||
|
// Set up DRM format modifier
|
||||||
|
let plane_layouts = self.create_subresource_layouts()?;
|
||||||
|
let mut drm_format_modifier = vk::ImageDrmFormatModifierExplicitCreateInfoEXT::default()
|
||||||
|
.drm_format_modifier(self.modifier)
|
||||||
|
.plane_layouts(&plane_layouts);
|
||||||
|
|
||||||
|
let image_create_info = image_create_info.push_next(&mut drm_format_modifier);
|
||||||
|
|
||||||
|
// Create the image
|
||||||
|
let image = unsafe {
|
||||||
|
device.create_image(&image_create_info, None).map_err(|e| TextureImportError::VulkanError {
|
||||||
|
operation: format!("Failed to create Vulkan image: {e:?}"),
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Import memory from DMA-BUF
|
||||||
|
let memory_requirements = unsafe { device.get_image_memory_requirements(image) };
|
||||||
|
|
||||||
|
// Duplicate the file descriptor to avoid ownership issues
|
||||||
|
let dup_fd = unsafe { libc::dup(self.fds[0]) };
|
||||||
|
if dup_fd == -1 {
|
||||||
|
return Err(TextureImportError::PlatformError {
|
||||||
|
message: "Failed to duplicate DMA-BUF file descriptor".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut import_memory_fd = vk::ImportMemoryFdInfoKHR::default().handle_type(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT).fd(dup_fd);
|
||||||
|
|
||||||
|
// Find a suitable memory type
|
||||||
|
let memory_properties = unsafe { hal_device.shared_instance().raw_instance().get_physical_device_memory_properties(hal_device.raw_physical_device()) };
|
||||||
|
|
||||||
|
let memory_type_index =
|
||||||
|
vulkan::find_memory_type_index(memory_requirements.memory_type_bits, vk::MemoryPropertyFlags::empty(), &memory_properties).ok_or_else(|| TextureImportError::VulkanError {
|
||||||
|
operation: "Failed to find suitable memory type for DMA-BUF".to_string(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let allocate_info = vk::MemoryAllocateInfo::default()
|
||||||
|
.allocation_size(memory_requirements.size)
|
||||||
|
.memory_type_index(memory_type_index)
|
||||||
|
.push_next(&mut import_memory_fd);
|
||||||
|
|
||||||
|
let device_memory = unsafe {
|
||||||
|
device.allocate_memory(&allocate_info, None).map_err(|e| TextureImportError::VulkanError {
|
||||||
|
operation: format!("Failed to allocate memory for DMA-BUF: {e:?}"),
|
||||||
|
})?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bind memory to image
|
||||||
|
unsafe {
|
||||||
|
device.bind_image_memory(image, device_memory, 0).map_err(|e| TextureImportError::VulkanError {
|
||||||
|
operation: format!("Failed to bind memory to image: {e:?}"),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(image)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_subresource_layouts(&self) -> Result<Vec<vk::SubresourceLayout>, TextureImportError> {
|
||||||
|
let mut layouts = Vec::new();
|
||||||
|
|
||||||
|
for i in 0..self.fds.len() {
|
||||||
|
layouts.push(vk::SubresourceLayout {
|
||||||
|
offset: self.offsets.get(i).copied().unwrap_or(0) as u64,
|
||||||
|
size: 0, // Will be calculated by driver
|
||||||
|
row_pitch: self.strides.get(i).copied().unwrap_or(0) as u64,
|
||||||
|
array_pitch: 0,
|
||||||
|
depth_pitch: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(layouts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_fds_from_info(info: &cef::AcceleratedPaintInfo) -> Vec<std::os::fd::RawFd> {
|
||||||
|
let plane_count = info.plane_count as usize;
|
||||||
|
let mut fds = Vec::with_capacity(plane_count);
|
||||||
|
|
||||||
|
for i in 0..plane_count {
|
||||||
|
if let Some(plane) = info.planes.get(i) {
|
||||||
|
fds.push(plane.fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fds
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_strides_from_info(info: &cef::AcceleratedPaintInfo) -> Vec<u32> {
|
||||||
|
let plane_count = info.plane_count as usize;
|
||||||
|
let mut strides = Vec::with_capacity(plane_count);
|
||||||
|
|
||||||
|
for i in 0..plane_count {
|
||||||
|
if let Some(plane) = info.planes.get(i) {
|
||||||
|
strides.push(plane.stride);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
strides
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_offsets_from_info(info: &cef::AcceleratedPaintInfo) -> Vec<u32> {
|
||||||
|
let plane_count = info.plane_count as usize;
|
||||||
|
let mut offsets = Vec::with_capacity(plane_count);
|
||||||
|
|
||||||
|
for i in 0..plane_count {
|
||||||
|
if let Some(plane) = info.planes.get(i) {
|
||||||
|
offsets.push(plane.offset as u32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
offsets
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
//! macOS IOSurface texture import implementation
|
||||||
|
|
||||||
|
use super::common::{format, texture};
|
||||||
|
use super::{TextureImportError, TextureImportResult, TextureImporter};
|
||||||
|
use cef::{AcceleratedPaintInfo, sys::cef_color_type_t};
|
||||||
|
use core_foundation::base::{CFType, TCFType};
|
||||||
|
use objc2_io_surface::{IOSurface, IOSurfaceRef};
|
||||||
|
use objc2_metal::{MTLDevice, MTLPixelFormat, MTLTexture, MTLTextureDescriptor, MTLTextureType, MTLTextureUsage};
|
||||||
|
use std::os::raw::c_void;
|
||||||
|
use wgpu::hal::api;
|
||||||
|
|
||||||
|
pub struct IOSurfaceImporter {
|
||||||
|
pub handle: *mut c_void,
|
||||||
|
pub format: cef_color_type_t,
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextureImporter for IOSurfaceImporter {
|
||||||
|
fn new(info: &AcceleratedPaintInfo) -> Self {
|
||||||
|
Self {
|
||||||
|
handle: info.shared_texture_handle,
|
||||||
|
format: *info.format.as_ref(),
|
||||||
|
width: info.extra.coded_size.width as u32,
|
||||||
|
height: info.extra.coded_size.height as u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_to_wgpu(&self, device: &wgpu::Device) -> TextureImportResult {
|
||||||
|
// Try hardware acceleration first
|
||||||
|
if self.supports_hardware_acceleration(device) {
|
||||||
|
match self.import_via_metal(device) {
|
||||||
|
Ok(texture) => {
|
||||||
|
tracing::trace!("Successfully imported IOSurface texture via Metal");
|
||||||
|
return Ok(texture);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("Failed to import IOSurface via Metal: {}, falling back to CPU texture", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to CPU texture
|
||||||
|
texture::create_fallback(device, self.width, self.height, self.format, "CEF IOSurface Texture (fallback)")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn supports_hardware_acceleration(&self, device: &wgpu::Device) -> bool {
|
||||||
|
// Check if handle is valid
|
||||||
|
if self.handle.is_null() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if wgpu is using Metal backend
|
||||||
|
self.is_metal_backend(device)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IOSurfaceImporter {
|
||||||
|
fn import_via_metal(&self, device: &wgpu::Device) -> TextureImportResult {
|
||||||
|
// Get wgpu's Metal device
|
||||||
|
use wgpu::{hal::Api, wgc::api::Metal};
|
||||||
|
let hal_texture = unsafe {
|
||||||
|
device.as_hal::<api::Metal, _, _>(|device| {
|
||||||
|
let Some(device) = device else {
|
||||||
|
return Err(TextureImportError::HardwareUnavailable {
|
||||||
|
reason: "Device is not using Metal backend".to_string(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Import IOSurface handle into Metal texture
|
||||||
|
let metal_texture = self.import_iosurface_to_metal(device)?;
|
||||||
|
|
||||||
|
// Wrap Metal texture in wgpu-hal texture
|
||||||
|
let hal_texture = <api::Metal as wgpu::hal::Api>::Device::texture_from_raw(
|
||||||
|
metal_texture,
|
||||||
|
&wgpu::hal::TextureDescriptor {
|
||||||
|
label: Some("CEF IOSurface Texture"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: format::cef_to_wgpu(self.format)?,
|
||||||
|
usage: wgpu::hal::TextureUses::RESOURCE,
|
||||||
|
memory_flags: wgpu::hal::MemoryFlags::empty(),
|
||||||
|
view_formats: vec![],
|
||||||
|
},
|
||||||
|
None, // drop_callback
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(hal_texture)
|
||||||
|
})
|
||||||
|
}?;
|
||||||
|
|
||||||
|
// Import hal texture into wgpu
|
||||||
|
let texture = unsafe {
|
||||||
|
device.create_texture_from_hal::<Metal>(
|
||||||
|
hal_texture,
|
||||||
|
&wgpu::TextureDescriptor {
|
||||||
|
label: Some("CEF IOSurface Texture"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: format::cef_to_wgpu(self.format)?,
|
||||||
|
usage: wgpu::TextureUsages::TEXTURE_BINDING,
|
||||||
|
view_formats: &[],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(texture)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_iosurface_to_metal(&self, hal_device: &<api::Metal as wgpu::hal::Api>::Device) -> Result<<api::Metal as wgpu::hal::Api>::Texture, TextureImportError> {
|
||||||
|
// Validate dimensions
|
||||||
|
if self.width == 0 || self.height == 0 {
|
||||||
|
return Err(TextureImportError::InvalidHandle("Invalid IOSurface texture dimensions".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert handle to IOSurface
|
||||||
|
let iosurface = unsafe {
|
||||||
|
let cf_type = CFType::wrap_under_get_rule(self.handle as IOSurfaceRef);
|
||||||
|
IOSurface::from(cf_type)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the Metal device from wgpu-hal
|
||||||
|
let metal_device = hal_device.raw_device();
|
||||||
|
|
||||||
|
// Convert CEF format to Metal pixel format
|
||||||
|
let metal_format = self.cef_to_metal_format(self.format)?;
|
||||||
|
|
||||||
|
// Create Metal texture descriptor
|
||||||
|
let texture_descriptor = MTLTextureDescriptor::new();
|
||||||
|
texture_descriptor.setTextureType(MTLTextureType::Type2D);
|
||||||
|
texture_descriptor.setPixelFormat(metal_format);
|
||||||
|
texture_descriptor.setWidth(self.width as usize);
|
||||||
|
texture_descriptor.setHeight(self.height as usize);
|
||||||
|
texture_descriptor.setDepth(1);
|
||||||
|
texture_descriptor.setMipmapLevelCount(1);
|
||||||
|
texture_descriptor.setSampleCount(1);
|
||||||
|
texture_descriptor.setUsage(MTLTextureUsage::ShaderRead);
|
||||||
|
|
||||||
|
// Create Metal texture from IOSurface
|
||||||
|
let metal_texture = unsafe { metal_device.newTextureWithDescriptor_iosurface_plane(&texture_descriptor, &iosurface, 0) };
|
||||||
|
|
||||||
|
let Some(metal_texture) = metal_texture else {
|
||||||
|
return Err(TextureImportError::PlatformError {
|
||||||
|
message: "Failed to create Metal texture from IOSurface".to_string(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
tracing::trace!("Successfully created Metal texture from IOSurface");
|
||||||
|
Ok(metal_texture)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cef_to_metal_format(&self, format: cef_color_type_t) -> Result<MTLPixelFormat, TextureImportError> {
|
||||||
|
match format {
|
||||||
|
cef_color_type_t::CEF_COLOR_TYPE_BGRA_8888 => Ok(MTLPixelFormat::BGRA8Unorm_sRGB),
|
||||||
|
cef_color_type_t::CEF_COLOR_TYPE_RGBA_8888 => Ok(MTLPixelFormat::RGBA8Unorm_sRGB),
|
||||||
|
_ => Err(TextureImportError::UnsupportedFormat { format }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_metal_backend(&self, device: &wgpu::Device) -> bool {
|
||||||
|
use wgpu::hal::api;
|
||||||
|
let mut is_metal = false;
|
||||||
|
unsafe {
|
||||||
|
device.as_hal::<api::Metal, _, _>(|device| {
|
||||||
|
is_metal = device.is_some();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
is_metal
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
//! Unified texture import system for CEF hardware acceleration
|
||||||
|
//!
|
||||||
|
//! This module provides a platform-agnostic interface for importing shared textures
|
||||||
|
//! from CEF into wgpu, with automatic fallback to CPU textures when hardware
|
||||||
|
//! acceleration is not available.
|
||||||
|
//!
|
||||||
|
//! # Supported Platforms
|
||||||
|
//!
|
||||||
|
//! - **Linux**: DMA-BUF via Vulkan external memory
|
||||||
|
//! - **Windows**: D3D11 shared textures via Vulkan interop
|
||||||
|
//! - **macOS**: IOSurface via Metal native API
|
||||||
|
//!
|
||||||
|
//! # Usage
|
||||||
|
//!
|
||||||
|
//! ```no_run
|
||||||
|
//! // Import texture with automatic platform detection
|
||||||
|
//! let texture = shared_handle.import_texture(&device)?;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Features
|
||||||
|
//!
|
||||||
|
//! - `accelerated_paint` - Base feature for texture import
|
||||||
|
//! - `accelerated_paint_dmabuf` - Linux DMA-BUF support
|
||||||
|
//! - `accelerated_paint_d3d11` - Windows D3D11 support
|
||||||
|
//! - `accelerated_paint_iosurface` - macOS IOSurface support
|
||||||
|
|
||||||
|
pub(crate) mod common;
|
||||||
|
|
||||||
|
pub(crate) mod shared_texture_handle;
|
||||||
|
pub(crate) use shared_texture_handle::SharedTextureHandle;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub(crate) mod dmabuf;
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
pub(crate) mod d3d11;
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub(crate) mod iosurface;
|
||||||
|
|
||||||
|
/// Result type for texture import operations
|
||||||
|
pub type TextureImportResult = Result<wgpu::Texture, TextureImportError>;
|
||||||
|
|
||||||
|
/// Errors that can occur during texture import
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum TextureImportError {
|
||||||
|
#[error("Invalid texture handle: {0}")]
|
||||||
|
InvalidHandle(String),
|
||||||
|
|
||||||
|
#[error("Unsupported texture format: {format:?}")]
|
||||||
|
UnsupportedFormat { format: cef::sys::cef_color_type_t },
|
||||||
|
|
||||||
|
#[error("Hardware acceleration not available: {reason}")]
|
||||||
|
HardwareUnavailable { reason: String },
|
||||||
|
|
||||||
|
#[error("Vulkan operation failed: {operation}")]
|
||||||
|
VulkanError { operation: String },
|
||||||
|
|
||||||
|
#[error("Platform-specific error: {message}")]
|
||||||
|
PlatformError { message: String },
|
||||||
|
|
||||||
|
#[error("Unsupported platform for texture import")]
|
||||||
|
UnsupportedPlatform,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait for platform-specific texture importers
|
||||||
|
pub trait TextureImporter {
|
||||||
|
fn new(info: &cef::AcceleratedPaintInfo) -> Self;
|
||||||
|
|
||||||
|
/// Import the texture into wgpu, with automatic fallback to CPU texture
|
||||||
|
fn import_to_wgpu(&self, device: &wgpu::Device) -> TextureImportResult;
|
||||||
|
|
||||||
|
/// Check if hardware acceleration is available for this texture
|
||||||
|
fn supports_hardware_acceleration(&self, device: &wgpu::Device) -> bool;
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
use cef::AcceleratedPaintInfo;
|
||||||
|
|
||||||
|
use super::{TextureImportError, TextureImportResult, TextureImporter};
|
||||||
|
|
||||||
|
pub(crate) enum SharedTextureHandle {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
DmaBuf(super::dmabuf::DmaBufImporter),
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
D3D11(super::d3d11::D3D11Importer),
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
IOSurface(super::iosurface::IOSurfaceImporter),
|
||||||
|
Unsupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SharedTextureHandle {
|
||||||
|
pub(crate) fn new(info: &AcceleratedPaintInfo) -> Self {
|
||||||
|
// Extract DMA-BUF information
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
return Self::DmaBuf(super::dmabuf::DmaBufImporter::new(info));
|
||||||
|
|
||||||
|
// Extract D3D11 shared handle with texture metadata
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
return Self::D3D11(super::d3d11::D3D11Importer::new(info));
|
||||||
|
|
||||||
|
// Extract IOSurface handle with texture metadata
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
return Self::IOSurface(super::iosurface::IOSurfaceImporter::new(info));
|
||||||
|
|
||||||
|
#[allow(unreachable_code)]
|
||||||
|
Self::Unsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Import a texture using the appropriate platform-specific importer
|
||||||
|
pub(crate) fn import_texture(self, device: &wgpu::Device) -> TextureImportResult {
|
||||||
|
match self {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
SharedTextureHandle::DmaBuf(importer) => importer.import_to_wgpu(device),
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
SharedTextureHandle::D3D11(importer) => importer.import_to_wgpu(device),
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
SharedTextureHandle::IOSurface(importer) => importer.import_to_wgpu(device),
|
||||||
|
SharedTextureHandle::Unsupported => Err(TextureImportError::UnsupportedPlatform),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
pub(crate) static APP_NAME: &str = "Graphite";
|
pub(crate) static APP_NAME: &str = "Graphite";
|
||||||
pub(crate) static APP_ID: &str = "rs.graphite.GraphiteEditor";
|
pub(crate) static APP_ID: &str = "rs.graphite.GraphiteEditor";
|
||||||
pub(crate) static APP_DIRECTORY_NAME: &str = "graphite-editor";
|
pub(crate) static APP_DIRECTORY_NAME: &str = "graphite-editor";
|
||||||
|
|
||||||
|
// CEF configuration constants
|
||||||
|
pub(crate) const CEF_WINDOWLESS_FRAME_RATE: i32 = 60;
|
||||||
|
pub(crate) const CEF_MESSAGE_LOOP_MAX_ITERATIONS: usize = 10;
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use rfd::AsyncFileDialog;
|
|
||||||
|
|
||||||
pub(crate) async fn dialog_open_graphite_file() -> Option<PathBuf> {
|
|
||||||
AsyncFileDialog::new()
|
|
||||||
.add_filter("Graphite", &["graphite"])
|
|
||||||
.set_title("Open Graphite Document")
|
|
||||||
.pick_file()
|
|
||||||
.await
|
|
||||||
.map(|f| f.path().to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn dialog_save_graphite_file(name: String) -> Option<PathBuf> {
|
|
||||||
AsyncFileDialog::new()
|
|
||||||
.add_filter("Graphite", &["graphite"])
|
|
||||||
.set_title("Save Graphite Document")
|
|
||||||
.set_file_name(name)
|
|
||||||
.save_file()
|
|
||||||
.await
|
|
||||||
.map(|f| f.path().to_path_buf())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn dialog_save_file(name: String) -> Option<PathBuf> {
|
|
||||||
AsyncFileDialog::new().set_title("Save File").set_file_name(name).save_file().await.map(|f| f.path().to_path_buf())
|
|
||||||
}
|
|
||||||
+25
-36
@@ -1,53 +1,53 @@
|
|||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use std::{fmt::Debug, time::Duration};
|
|
||||||
|
|
||||||
use graphite_editor::messages::prelude::Message;
|
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use winit::event_loop::EventLoop;
|
use winit::event_loop::EventLoop;
|
||||||
|
|
||||||
pub(crate) mod consts;
|
pub(crate) mod consts;
|
||||||
|
|
||||||
mod cef;
|
mod cef;
|
||||||
use cef::{Setup, WindowSize};
|
|
||||||
|
|
||||||
mod render;
|
mod render;
|
||||||
use render::WgpuContext;
|
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
use app::WinitApp;
|
use app::WinitApp;
|
||||||
|
|
||||||
mod dirs;
|
mod dirs;
|
||||||
|
|
||||||
mod dialogs;
|
use graphite_desktop_wrapper::messages::DesktopWrapperMessage;
|
||||||
|
use graphite_desktop_wrapper::{NodeGraphExecutionResult, WgpuContext};
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub(crate) enum CustomEvent {
|
pub(crate) enum CustomEvent {
|
||||||
UiUpdate(wgpu::Texture),
|
UiUpdate(wgpu::Texture),
|
||||||
ScheduleBrowserWork(Instant),
|
ScheduleBrowserWork(Instant),
|
||||||
DispatchMessage(Message),
|
WebCommunicationInitialized,
|
||||||
MessageReceived(Message),
|
DesktopWrapperMessage(DesktopWrapperMessage),
|
||||||
NodeGraphRan(Option<wgpu::Texture>),
|
NodeGraphExecutionResult(NodeGraphExecutionResult),
|
||||||
|
CloseWindow,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init();
|
tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init();
|
||||||
|
|
||||||
let cef_context = match cef::Context::<Setup>::new() {
|
let cef_context_builder = cef::CefContextBuilder::new();
|
||||||
Ok(c) => c,
|
|
||||||
Err(cef::SetupError::Subprocess) => exit(0),
|
if cef_context_builder.is_sub_process() {
|
||||||
Err(cef::SetupError::SubprocessFailed(t)) => {
|
// We are in a CEF subprocess
|
||||||
tracing::error!("Subprocess of type {t} failed");
|
// This will block until the CEF subprocess quits
|
||||||
exit(1);
|
let error = cef_context_builder.execute_sub_process();
|
||||||
}
|
tracing::error!("Cef subprocess failed with error: {error}");
|
||||||
};
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let wgpu_context = futures::executor::block_on(WgpuContext::new()).unwrap();
|
||||||
|
|
||||||
let event_loop = EventLoop::<CustomEvent>::with_user_event().build().unwrap();
|
let event_loop = EventLoop::<CustomEvent>::with_user_event().build().unwrap();
|
||||||
|
|
||||||
let (window_size_sender, window_size_receiver) = std::sync::mpsc::channel();
|
let (window_size_sender, window_size_receiver) = std::sync::mpsc::channel();
|
||||||
|
|
||||||
let wgpu_context = futures::executor::block_on(WgpuContext::new()).unwrap();
|
let cef_handler = cef::CefHandler::new(window_size_receiver, event_loop.create_proxy(), wgpu_context.clone());
|
||||||
let cef_context = match cef_context.init(cef::CefHandler::new(window_size_receiver, event_loop.create_proxy(), wgpu_context.clone())) {
|
let cef_context = match cef_context_builder.initialize(cef_handler) {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(cef::InitError::AlreadyRunning) => {
|
Err(cef::InitError::AlreadyRunning) => {
|
||||||
tracing::error!("Another instance is already running, Exiting.");
|
tracing::error!("Another instance is already running, Exiting.");
|
||||||
@@ -57,26 +57,15 @@ fn main() {
|
|||||||
tracing::error!("Cef initialization failed with code: {code}");
|
tracing::error!("Cef initialization failed with code: {code}");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
Err(cef::InitError::BrowserCreationFailed) => {
|
||||||
|
tracing::error!("Failed to create CEF browser");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::info!("Cef initialized successfully");
|
tracing::info!("CEF initialized successfully");
|
||||||
|
|
||||||
let rendering_loop_proxy = event_loop.create_proxy();
|
let mut winit_app = WinitApp::new(Box::new(cef_context), window_size_sender, wgpu_context, event_loop.create_proxy());
|
||||||
let target_fps = 60;
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
loop {
|
|
||||||
let last_render = Instant::now();
|
|
||||||
let (has_run, texture) = futures::executor::block_on(graphite_editor::node_graph_executor::run_node_graph());
|
|
||||||
if has_run {
|
|
||||||
let _ = rendering_loop_proxy.send_event(CustomEvent::NodeGraphRan(texture.map(|t| (*t.texture).clone())));
|
|
||||||
}
|
|
||||||
let frame_time = Duration::from_secs_f32((target_fps as f32).recip());
|
|
||||||
let sleep = last_render + frame_time - Instant::now();
|
|
||||||
std::thread::sleep(sleep);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut winit_app = WinitApp::new(cef_context, window_size_sender, wgpu_context, event_loop.create_proxy());
|
|
||||||
|
|
||||||
event_loop.run_app(&mut winit_app).unwrap();
|
event_loop.run_app(&mut winit_app).unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ mod frame_buffer_ref;
|
|||||||
pub(crate) use frame_buffer_ref::FrameBufferRef;
|
pub(crate) use frame_buffer_ref::FrameBufferRef;
|
||||||
|
|
||||||
mod graphics_state;
|
mod graphics_state;
|
||||||
pub(crate) use graphics_state::{GraphicsState, WgpuContext};
|
pub(crate) use graphics_state::GraphicsState;
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
use graphene_std::Color;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wgpu_executor::WgpuExecutor;
|
|
||||||
use winit::window::Window;
|
use winit::window::Window;
|
||||||
|
|
||||||
pub(crate) use wgpu_executor::Context as WgpuContext;
|
use graphite_desktop_wrapper::{Color, WgpuContext, WgpuExecutor};
|
||||||
|
|
||||||
#[derive(derivative::Derivative)]
|
#[derive(derivative::Derivative)]
|
||||||
#[derivative(Debug)]
|
#[derivative(Debug)]
|
||||||
@@ -234,7 +232,7 @@ impl GraphicsState {
|
|||||||
self.bind_overlays_texture(texture);
|
self.bind_overlays_texture(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
|
pub(crate) fn render(&mut self, window: &Window) -> Result<(), wgpu::SurfaceError> {
|
||||||
if let Some(scene) = self.overlays_scene.take() {
|
if let Some(scene) = self.overlays_scene.take() {
|
||||||
self.render_overlays(scene);
|
self.render_overlays(scene);
|
||||||
}
|
}
|
||||||
@@ -277,6 +275,7 @@ impl GraphicsState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.context.queue.submit(std::iter::once(encoder.finish()));
|
self.context.queue.submit(std::iter::once(encoder.finish()));
|
||||||
|
window.pre_present_notify();
|
||||||
output.present();
|
output.present();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
[package]
|
||||||
|
name = "graphite-desktop-wrapper"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Graphite Desktop Wrapper"
|
||||||
|
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||||
|
license = "Apache-2.0"
|
||||||
|
repository = ""
|
||||||
|
edition = "2024"
|
||||||
|
rust-version = "1.87"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["gpu"]
|
||||||
|
gpu = ["graphite-editor/gpu"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Local dependencies
|
||||||
|
graphite-editor = { path = "../../editor", features = [
|
||||||
|
"gpu",
|
||||||
|
"ron",
|
||||||
|
"vello",
|
||||||
|
] }
|
||||||
|
graphene-std = { workspace = true }
|
||||||
|
graph-craft = { workspace = true }
|
||||||
|
wgpu-executor = { workspace = true }
|
||||||
|
|
||||||
|
wgpu = { workspace = true }
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
futures = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
dirs = { workspace = true }
|
||||||
|
ron = { workspace = true}
|
||||||
|
vello = { workspace = true }
|
||||||
|
image = { workspace = true }
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
use graphene_std::Color;
|
||||||
|
use graphene_std::raster::Image;
|
||||||
|
use graphite_editor::messages::app_window::app_window_message_handler::AppWindowPlatform;
|
||||||
|
use graphite_editor::messages::prelude::{AppWindowMessage, DocumentMessage, PortfolioMessage};
|
||||||
|
|
||||||
|
use crate::messages::Platform;
|
||||||
|
|
||||||
|
use super::DesktopWrapperMessageDispatcher;
|
||||||
|
use super::messages::{DesktopFrontendMessage, DesktopWrapperMessage, EditorMessage, OpenFileDialogContext, SaveFileDialogContext};
|
||||||
|
|
||||||
|
pub(super) fn handle_desktop_wrapper_message(dispatcher: &mut DesktopWrapperMessageDispatcher, message: DesktopWrapperMessage) {
|
||||||
|
match message {
|
||||||
|
DesktopWrapperMessage::FromWeb(message) => {
|
||||||
|
dispatcher.queue_editor_message(*message);
|
||||||
|
}
|
||||||
|
DesktopWrapperMessage::OpenFileDialogResult { path, content, context } => match context {
|
||||||
|
OpenFileDialogContext::Document => {
|
||||||
|
dispatcher.queue_desktop_wrapper_message(DesktopWrapperMessage::OpenDocument { path, content });
|
||||||
|
}
|
||||||
|
OpenFileDialogContext::Import => {
|
||||||
|
dispatcher.queue_desktop_wrapper_message(DesktopWrapperMessage::ImportFile { path, content });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DesktopWrapperMessage::SaveFileDialogResult { path, context } => match context {
|
||||||
|
SaveFileDialogContext::Document { document_id, content } => {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::WriteFile { path: path.clone(), content });
|
||||||
|
dispatcher.queue_editor_message(EditorMessage::Portfolio(PortfolioMessage::DocumentPassMessage {
|
||||||
|
document_id,
|
||||||
|
message: DocumentMessage::SavedDocument { path: Some(path) },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
SaveFileDialogContext::File { content } => {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::WriteFile { path, content });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DesktopWrapperMessage::OpenFile { path, content } => {
|
||||||
|
let extension = path.extension().and_then(|s| s.to_str()).unwrap_or_default().to_lowercase();
|
||||||
|
match extension.as_str() {
|
||||||
|
"graphite" => {
|
||||||
|
dispatcher.queue_desktop_wrapper_message(DesktopWrapperMessage::OpenDocument { path, content });
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
dispatcher.queue_desktop_wrapper_message(DesktopWrapperMessage::ImportFile { path, content });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DesktopWrapperMessage::OpenDocument { path, content } => {
|
||||||
|
let Ok(content) = String::from_utf8(content) else {
|
||||||
|
tracing::warn!("Document file is invalid: {}", path.display());
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let message = PortfolioMessage::OpenDocumentFile {
|
||||||
|
document_name: None,
|
||||||
|
document_path: Some(path),
|
||||||
|
document_serialized_content: content,
|
||||||
|
};
|
||||||
|
dispatcher.queue_editor_message(message.into());
|
||||||
|
}
|
||||||
|
DesktopWrapperMessage::ImportFile { path, content } => {
|
||||||
|
let extension = path.extension().and_then(|s| s.to_str()).unwrap_or_default().to_lowercase();
|
||||||
|
match extension.as_str() {
|
||||||
|
"svg" => {
|
||||||
|
dispatcher.queue_desktop_wrapper_message(DesktopWrapperMessage::ImportSvg { path, content });
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
dispatcher.queue_desktop_wrapper_message(DesktopWrapperMessage::ImportImage { path, content });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DesktopWrapperMessage::ImportSvg { path, content } => {
|
||||||
|
let Ok(content) = String::from_utf8(content) else {
|
||||||
|
tracing::warn!("Svg file is invalid: {}", path.display());
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
dispatcher.queue_editor_message(message.into());
|
||||||
|
}
|
||||||
|
DesktopWrapperMessage::ImportImage { path, content } => {
|
||||||
|
let name = path.file_stem().and_then(|s| s.to_str()).map(|s| s.to_string());
|
||||||
|
let extension = path.extension().and_then(|s| s.to_str()).unwrap_or_default().to_lowercase();
|
||||||
|
let Some(image_format) = image::ImageFormat::from_extension(&extension) else {
|
||||||
|
tracing::warn!("Unsupported file type: {}", path.display());
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let reader = image::ImageReader::with_format(std::io::Cursor::new(content), image_format);
|
||||||
|
let Ok(image) = reader.decode() else {
|
||||||
|
tracing::error!("Failed to decode image: {}", path.display());
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let width = image.width();
|
||||||
|
let height = image.height();
|
||||||
|
|
||||||
|
// TODO: Handle Image formats 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,
|
||||||
|
};
|
||||||
|
dispatcher.queue_editor_message(message.into());
|
||||||
|
}
|
||||||
|
DesktopWrapperMessage::PollNodeGraphEvaluation => dispatcher.poll_node_graph_evaluation(),
|
||||||
|
DesktopWrapperMessage::UpdatePlatform(platform) => {
|
||||||
|
let platform = match platform {
|
||||||
|
Platform::Windows => AppWindowPlatform::Windows,
|
||||||
|
Platform::Mac => AppWindowPlatform::Mac,
|
||||||
|
Platform::Linux => AppWindowPlatform::Linux,
|
||||||
|
};
|
||||||
|
let message = AppWindowMessage::AppWindowUpdatePlatform { platform };
|
||||||
|
dispatcher.queue_editor_message(message.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
use graphite_editor::messages::prelude::InputPreprocessorMessage;
|
||||||
|
|
||||||
|
use super::DesktopWrapperMessageDispatcher;
|
||||||
|
use super::messages::{DesktopFrontendMessage, EditorMessage};
|
||||||
|
|
||||||
|
pub(super) fn intercept_editor_message(dispatcher: &mut DesktopWrapperMessageDispatcher, message: EditorMessage) -> Option<EditorMessage> {
|
||||||
|
match message {
|
||||||
|
EditorMessage::InputPreprocessor(message) => {
|
||||||
|
if let InputPreprocessorMessage::BoundsOfViewports { bounds_of_viewports } = &message {
|
||||||
|
let top_left = bounds_of_viewports[0].top_left;
|
||||||
|
let bottom_right = bounds_of_viewports[0].bottom_right;
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::UpdateViewportBounds {
|
||||||
|
x: top_left.x as f32,
|
||||||
|
y: top_left.y as f32,
|
||||||
|
width: (bottom_right.x - top_left.x) as f32,
|
||||||
|
height: (bottom_right.y - top_left.y) as f32,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some(EditorMessage::InputPreprocessor(message))
|
||||||
|
}
|
||||||
|
m => Some(m),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use graphite_editor::messages::prelude::FrontendMessage;
|
||||||
|
|
||||||
|
use super::DesktopWrapperMessageDispatcher;
|
||||||
|
use super::messages::{DesktopFrontendMessage, FileFilter, OpenFileDialogContext, SaveFileDialogContext};
|
||||||
|
|
||||||
|
pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageDispatcher, message: FrontendMessage) -> Option<FrontendMessage> {
|
||||||
|
match message {
|
||||||
|
FrontendMessage::RenderOverlays { context } => {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::UpdateOverlays(context.take_scene()));
|
||||||
|
}
|
||||||
|
FrontendMessage::TriggerOpenDocument => {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::OpenFileDialog {
|
||||||
|
title: "Open Document".to_string(),
|
||||||
|
filters: vec![FileFilter {
|
||||||
|
name: "Graphite".to_string(),
|
||||||
|
extensions: vec!["graphite".to_string()],
|
||||||
|
}],
|
||||||
|
context: OpenFileDialogContext::Document,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
FrontendMessage::TriggerImport => {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::OpenFileDialog {
|
||||||
|
title: "Import File".to_string(),
|
||||||
|
filters: vec![
|
||||||
|
FileFilter {
|
||||||
|
name: "Svg".to_string(),
|
||||||
|
extensions: vec!["svg".to_string()],
|
||||||
|
},
|
||||||
|
FileFilter {
|
||||||
|
name: "Image".to_string(),
|
||||||
|
extensions: vec!["png".to_string(), "jpg".to_string(), "jpeg".to_string(), "bmp".to_string()],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
context: OpenFileDialogContext::Import,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
FrontendMessage::TriggerSaveDocument { document_id, name, path, content } => {
|
||||||
|
if let Some(path) = path {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::WriteFile { path, content });
|
||||||
|
} else {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::SaveFileDialog {
|
||||||
|
title: "Save Document".to_string(),
|
||||||
|
default_filename: name,
|
||||||
|
default_folder: path.and_then(|p| p.parent().map(PathBuf::from)),
|
||||||
|
filters: vec![FileFilter {
|
||||||
|
name: "Graphite".to_string(),
|
||||||
|
extensions: vec!["graphite".to_string()],
|
||||||
|
}],
|
||||||
|
context: SaveFileDialogContext::Document { document_id, content },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FrontendMessage::TriggerSaveFile { name, content } => {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::SaveFileDialog {
|
||||||
|
title: "Save File".to_string(),
|
||||||
|
default_filename: name,
|
||||||
|
default_folder: None,
|
||||||
|
filters: Vec::new(),
|
||||||
|
context: SaveFileDialogContext::File { content },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
FrontendMessage::TriggerVisitLink { url } => {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::OpenUrl(url));
|
||||||
|
}
|
||||||
|
FrontendMessage::UpdateWindowState { maximized, minimized } => {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::UpdateWindowState { maximized, minimized });
|
||||||
|
|
||||||
|
// Forward this to update the ui
|
||||||
|
return Some(message);
|
||||||
|
}
|
||||||
|
FrontendMessage::CloseWindow => {
|
||||||
|
dispatcher.respond(DesktopFrontendMessage::CloseWindow);
|
||||||
|
}
|
||||||
|
m => return Some(m),
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
use graph_craft::wasm_application_io::WasmApplicationIo;
|
||||||
|
use graphite_editor::application::Editor;
|
||||||
|
use graphite_editor::messages::prelude::{FrontendMessage, Message};
|
||||||
|
|
||||||
|
// TODO: Remove usage of this reexport in desktop create and remove this line
|
||||||
|
pub use graphene_std::Color;
|
||||||
|
|
||||||
|
pub use wgpu_executor::Context as WgpuContext;
|
||||||
|
pub use wgpu_executor::WgpuExecutor;
|
||||||
|
|
||||||
|
pub mod messages;
|
||||||
|
use messages::{DesktopFrontendMessage, DesktopWrapperMessage};
|
||||||
|
|
||||||
|
mod message_dispatcher;
|
||||||
|
use message_dispatcher::DesktopWrapperMessageDispatcher;
|
||||||
|
|
||||||
|
mod handle_desktop_wrapper_message;
|
||||||
|
mod intercept_editor_message;
|
||||||
|
mod intercept_frontend_message;
|
||||||
|
|
||||||
|
pub struct DesktopWrapper {
|
||||||
|
editor: Editor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DesktopWrapper {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { editor: Editor::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(&self, wgpu_context: WgpuContext) {
|
||||||
|
let application_io = WasmApplicationIo::new_with_context(wgpu_context);
|
||||||
|
futures::executor::block_on(graphite_editor::node_graph_executor::replace_application_io(application_io));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dispatch(&mut self, message: DesktopWrapperMessage) -> Vec<DesktopFrontendMessage> {
|
||||||
|
let mut executor = DesktopWrapperMessageDispatcher::new(&mut self.editor);
|
||||||
|
executor.queue_desktop_wrapper_message(message);
|
||||||
|
executor.execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn execute_node_graph() -> NodeGraphExecutionResult {
|
||||||
|
let result = graphite_editor::node_graph_executor::run_node_graph().await;
|
||||||
|
match result {
|
||||||
|
(true, texture) => NodeGraphExecutionResult::HasRun(texture.map(|t| t.texture)),
|
||||||
|
(false, _) => NodeGraphExecutionResult::NotRun,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DesktopWrapper {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum NodeGraphExecutionResult {
|
||||||
|
HasRun(Option<wgpu::Texture>),
|
||||||
|
NotRun,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize_editor_message(data: &[u8]) -> Option<DesktopWrapperMessage> {
|
||||||
|
if let Ok(string) = std::str::from_utf8(data) {
|
||||||
|
if let Ok(message) = ron::de::from_str::<Message>(string) {
|
||||||
|
Some(DesktopWrapperMessage::FromWeb(message.into()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn serialize_frontend_messages(messages: Vec<FrontendMessage>) -> Option<Vec<u8>> {
|
||||||
|
if let Ok(serialized) = ron::ser::to_string(&messages) {
|
||||||
|
Some(serialized.into_bytes())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
use graphite_editor::application::Editor;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
use super::handle_desktop_wrapper_message::handle_desktop_wrapper_message;
|
||||||
|
use super::intercept_editor_message::intercept_editor_message;
|
||||||
|
use super::intercept_frontend_message::intercept_frontend_message;
|
||||||
|
use super::messages::{DesktopFrontendMessage, DesktopWrapperMessage, EditorMessage};
|
||||||
|
|
||||||
|
pub(crate) struct DesktopWrapperMessageDispatcher<'a> {
|
||||||
|
editor: &'a mut Editor,
|
||||||
|
desktop_wrapper_message_queue: VecDeque<DesktopWrapperMessage>,
|
||||||
|
editor_message_queue: Vec<EditorMessage>,
|
||||||
|
responses: Vec<DesktopFrontendMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> DesktopWrapperMessageDispatcher<'a> {
|
||||||
|
pub(crate) fn new(editor: &'a mut Editor) -> Self {
|
||||||
|
Self {
|
||||||
|
editor,
|
||||||
|
desktop_wrapper_message_queue: VecDeque::new(),
|
||||||
|
editor_message_queue: Vec::new(),
|
||||||
|
responses: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn execute(mut self) -> Vec<DesktopFrontendMessage> {
|
||||||
|
self.process_queue();
|
||||||
|
self.responses
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn queue_desktop_wrapper_message(&mut self, message: DesktopWrapperMessage) {
|
||||||
|
self.desktop_wrapper_message_queue.push_back(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn queue_editor_message(&mut self, message: EditorMessage) {
|
||||||
|
if let Some(message) = intercept_editor_message(self, message) {
|
||||||
|
self.editor_message_queue.push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn respond(&mut self, response: DesktopFrontendMessage) {
|
||||||
|
self.responses.push(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn poll_node_graph_evaluation(&mut self) {
|
||||||
|
let mut responses = VecDeque::new();
|
||||||
|
if let Err(e) = self.editor.poll_node_graph_evaluation(&mut responses) {
|
||||||
|
if e != "No active document" {
|
||||||
|
tracing::error!("Error poling node graph: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while let Some(message) = responses.pop_front() {
|
||||||
|
self.queue_editor_message(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_queue(&mut self) {
|
||||||
|
let mut frontend_messages = Vec::new();
|
||||||
|
|
||||||
|
while !self.desktop_wrapper_message_queue.is_empty() || !self.editor_message_queue.is_empty() {
|
||||||
|
while let Some(message) = self.desktop_wrapper_message_queue.pop_front() {
|
||||||
|
handle_desktop_wrapper_message(self, message);
|
||||||
|
}
|
||||||
|
let current_frontend_messages = self
|
||||||
|
.editor
|
||||||
|
.handle_message(EditorMessage::Batched {
|
||||||
|
messages: std::mem::take(&mut self.editor_message_queue).into_boxed_slice(),
|
||||||
|
})
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|m| intercept_frontend_message(self, m));
|
||||||
|
frontend_messages.extend(current_frontend_messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.respond(DesktopFrontendMessage::ToWeb(frontend_messages));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use graphite_editor::messages::prelude::{DocumentId, FrontendMessage};
|
||||||
|
|
||||||
|
pub(crate) use graphite_editor::messages::prelude::Message as EditorMessage;
|
||||||
|
|
||||||
|
pub enum DesktopFrontendMessage {
|
||||||
|
ToWeb(Vec<FrontendMessage>),
|
||||||
|
OpenFileDialog {
|
||||||
|
title: String,
|
||||||
|
filters: Vec<FileFilter>,
|
||||||
|
context: OpenFileDialogContext,
|
||||||
|
},
|
||||||
|
SaveFileDialog {
|
||||||
|
title: String,
|
||||||
|
default_filename: String,
|
||||||
|
default_folder: Option<PathBuf>,
|
||||||
|
filters: Vec<FileFilter>,
|
||||||
|
context: SaveFileDialogContext,
|
||||||
|
},
|
||||||
|
WriteFile {
|
||||||
|
path: PathBuf,
|
||||||
|
content: Vec<u8>,
|
||||||
|
},
|
||||||
|
OpenUrl(String),
|
||||||
|
UpdateViewportBounds {
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
width: f32,
|
||||||
|
height: f32,
|
||||||
|
},
|
||||||
|
UpdateOverlays(vello::Scene),
|
||||||
|
UpdateWindowState {
|
||||||
|
maximized: bool,
|
||||||
|
minimized: bool,
|
||||||
|
},
|
||||||
|
CloseWindow,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FileFilter {
|
||||||
|
pub name: String,
|
||||||
|
pub extensions: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum DesktopWrapperMessage {
|
||||||
|
FromWeb(Box<EditorMessage>),
|
||||||
|
OpenFileDialogResult { path: PathBuf, content: Vec<u8>, context: OpenFileDialogContext },
|
||||||
|
SaveFileDialogResult { path: PathBuf, context: SaveFileDialogContext },
|
||||||
|
OpenDocument { path: PathBuf, content: Vec<u8> },
|
||||||
|
OpenFile { path: PathBuf, content: Vec<u8> },
|
||||||
|
ImportFile { path: PathBuf, content: Vec<u8> },
|
||||||
|
ImportSvg { path: PathBuf, content: Vec<u8> },
|
||||||
|
ImportImage { path: PathBuf, content: Vec<u8> },
|
||||||
|
PollNodeGraphEvaluation,
|
||||||
|
UpdatePlatform(Platform),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum OpenFileDialogContext {
|
||||||
|
Document,
|
||||||
|
Import,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum SaveFileDialogContext {
|
||||||
|
Document { document_id: DocumentId, content: Vec<u8> },
|
||||||
|
File { content: Vec<u8> },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Platform {
|
||||||
|
Windows,
|
||||||
|
Mac,
|
||||||
|
Linux,
|
||||||
|
}
|
||||||
+3
-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,7 @@ 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 }
|
base64 = { workspace = true }
|
||||||
|
|
||||||
# Required dependencies
|
# Required dependencies
|
||||||
@@ -57,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
|
||||||
|
|||||||
@@ -152,8 +152,8 @@ pub const COLOR_OVERLAY_WHITE: &str = "#ffffff";
|
|||||||
pub const COLOR_OVERLAY_BLACK_75: &str = "#000000bf";
|
pub const COLOR_OVERLAY_BLACK_75: &str = "#000000bf";
|
||||||
|
|
||||||
// DOCUMENT
|
// DOCUMENT
|
||||||
|
pub const FILE_EXTENSION: &str = "graphite";
|
||||||
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
|
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
|
||||||
pub const FILE_SAVE_SUFFIX: &str = ".graphite";
|
|
||||||
pub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences
|
pub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences
|
||||||
pub const AUTO_SAVE_TIMEOUT_SECONDS: u64 = 1;
|
pub const AUTO_SAVE_TIMEOUT_SECONDS: u64 = 1;
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
|
|||||||
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
|
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
|
||||||
];
|
];
|
||||||
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[
|
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[
|
||||||
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::AnimationFrame)),
|
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(EventMessageDiscriminant::AnimationFrame)),
|
||||||
MessageDiscriminant::Animation(AnimationMessageDiscriminant::IncrementFrameCounter),
|
MessageDiscriminant::Animation(AnimationMessageDiscriminant::IncrementFrameCounter),
|
||||||
];
|
];
|
||||||
// TODO: Find a way to combine these with the list above. We use strings for now since these are the standard variant names used by multiple messages. But having these also type-checked would be best.
|
// TODO: Find a way to combine these with the list above. We use strings for now since these are the standard variant names used by multiple messages. But having these also type-checked would be best.
|
||||||
@@ -442,7 +442,7 @@ mod test {
|
|||||||
assert_eq!(layers_before_copy.len(), 3);
|
assert_eq!(layers_before_copy.len(), 3);
|
||||||
assert_eq!(layers_after_copy.len(), 6);
|
assert_eq!(layers_after_copy.len(), 6);
|
||||||
|
|
||||||
println!("{:?} {:?}", layers_after_copy, layers_before_copy);
|
println!("{layers_after_copy:?} {layers_before_copy:?}");
|
||||||
|
|
||||||
assert_eq!(layers_after_copy[5], shape_id);
|
assert_eq!(layers_after_copy[5], shape_id);
|
||||||
}
|
}
|
||||||
@@ -497,7 +497,8 @@ mod test {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let responses = editor.editor.handle_message(PortfolioMessage::OpenDocumentFile {
|
let responses = editor.editor.handle_message(PortfolioMessage::OpenDocumentFile {
|
||||||
document_name: document_name.into(),
|
document_name: Some(document_name.to_string()),
|
||||||
|
document_path: None,
|
||||||
document_serialized_content,
|
document_serialized_content,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use crate::messages::prelude::*;
|
|
||||||
|
|
||||||
use super::animation_message_handler::AnimationTimeMode;
|
use super::animation_message_handler::AnimationTimeMode;
|
||||||
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
#[impl_message(Message, Animation)]
|
#[impl_message(Message, Animation)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
|
use super::app_window_message_handler::AppWindowPlatform;
|
||||||
|
|
||||||
#[impl_message(Message, AppWindow)]
|
#[impl_message(Message, AppWindow)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum AppWindowMessage {
|
pub enum AppWindowMessage {
|
||||||
AppWindowMinimize,
|
AppWindowMinimize,
|
||||||
AppWindowMaximize,
|
AppWindowMaximize,
|
||||||
|
AppWindowUpdatePlatform { platform: AppWindowPlatform },
|
||||||
AppWindowClose,
|
AppWindowClose,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,34 +6,34 @@ use graphite_proc_macros::{ExtractField, message_handler_data};
|
|||||||
pub struct AppWindowMessageHandler {
|
pub struct AppWindowMessageHandler {
|
||||||
platform: AppWindowPlatform,
|
platform: AppWindowPlatform,
|
||||||
maximized: bool,
|
maximized: bool,
|
||||||
viewport_hole_punch_active: bool,
|
minimized: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl MessageHandler<AppWindowMessage, ()> for AppWindowMessageHandler {
|
impl MessageHandler<AppWindowMessage, ()> for AppWindowMessageHandler {
|
||||||
fn process_message(&mut self, message: AppWindowMessage, responses: &mut std::collections::VecDeque<Message>, _: ()) {
|
fn process_message(&mut self, message: AppWindowMessage, responses: &mut std::collections::VecDeque<Message>, _: ()) {
|
||||||
match message {
|
match message {
|
||||||
AppWindowMessage::AppWindowMinimize => {
|
|
||||||
self.platform = if self.platform == AppWindowPlatform::Mac {
|
|
||||||
AppWindowPlatform::Windows
|
|
||||||
} else {
|
|
||||||
AppWindowPlatform::Mac
|
|
||||||
};
|
|
||||||
responses.add(FrontendMessage::UpdatePlatform { platform: self.platform });
|
|
||||||
}
|
|
||||||
AppWindowMessage::AppWindowMaximize => {
|
AppWindowMessage::AppWindowMaximize => {
|
||||||
self.maximized = !self.maximized;
|
self.maximized = !self.maximized;
|
||||||
responses.add(FrontendMessage::UpdateMaximized { maximized: self.maximized });
|
responses.add(FrontendMessage::UpdateWindowState {
|
||||||
|
maximized: self.maximized,
|
||||||
self.viewport_hole_punch_active = !self.viewport_hole_punch_active;
|
minimized: self.minimized,
|
||||||
responses.add(FrontendMessage::UpdateViewportHolePunch {
|
|
||||||
active: self.viewport_hole_punch_active,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
AppWindowMessage::AppWindowClose => {
|
AppWindowMessage::AppWindowMinimize => {
|
||||||
self.platform = AppWindowPlatform::Web;
|
self.minimized = !self.minimized;
|
||||||
|
responses.add(FrontendMessage::UpdateWindowState {
|
||||||
|
maximized: self.maximized,
|
||||||
|
minimized: self.minimized,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
AppWindowMessage::AppWindowUpdatePlatform { platform } => {
|
||||||
|
self.platform = platform;
|
||||||
responses.add(FrontendMessage::UpdatePlatform { platform: self.platform });
|
responses.add(FrontendMessage::UpdatePlatform { platform: self.platform });
|
||||||
}
|
}
|
||||||
|
AppWindowMessage::AppWindowClose => {
|
||||||
|
responses.add(FrontendMessage::CloseWindow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ use crate::messages::prelude::*;
|
|||||||
pub enum BroadcastMessage {
|
pub enum BroadcastMessage {
|
||||||
// Sub-messages
|
// Sub-messages
|
||||||
#[child]
|
#[child]
|
||||||
TriggerEvent(BroadcastEvent),
|
TriggerEvent(EventMessage),
|
||||||
|
|
||||||
// Messages
|
// Messages
|
||||||
SubscribeEvent {
|
SubscribeEvent {
|
||||||
on: BroadcastEvent,
|
on: EventMessage,
|
||||||
send: Box<Message>,
|
send: Box<Message>,
|
||||||
},
|
},
|
||||||
UnsubscribeEvent {
|
UnsubscribeEvent {
|
||||||
on: BroadcastEvent,
|
on: EventMessage,
|
||||||
message: Box<Message>,
|
send: Box<Message>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ use crate::messages::prelude::*;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Default, ExtractField)]
|
#[derive(Debug, Clone, Default, ExtractField)]
|
||||||
pub struct BroadcastMessageHandler {
|
pub struct BroadcastMessageHandler {
|
||||||
listeners: HashMap<BroadcastEvent, Vec<Message>>,
|
event: EventMessageHandler,
|
||||||
|
listeners: HashMap<EventMessage, Vec<Message>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
@@ -10,19 +11,15 @@ impl MessageHandler<BroadcastMessage, ()> for BroadcastMessageHandler {
|
|||||||
fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque<Message>, _: ()) {
|
fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque<Message>, _: ()) {
|
||||||
match message {
|
match message {
|
||||||
// Sub-messages
|
// Sub-messages
|
||||||
BroadcastMessage::TriggerEvent(event) => {
|
BroadcastMessage::TriggerEvent(message) => self.event.process_message(message, responses, EventMessageContext { listeners: &mut self.listeners }),
|
||||||
for message in self.listeners.entry(event).or_default() {
|
|
||||||
responses.add_front(message.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Messages
|
// Messages
|
||||||
BroadcastMessage::SubscribeEvent { on, send } => self.listeners.entry(on).or_default().push(*send),
|
BroadcastMessage::SubscribeEvent { on, send } => self.listeners.entry(on).or_default().push(*send),
|
||||||
BroadcastMessage::UnsubscribeEvent { on, message } => self.listeners.entry(on).or_default().retain(|msg| *msg != *message),
|
BroadcastMessage::UnsubscribeEvent { on, send } => self.listeners.entry(on).or_default().retain(|msg| *msg != *send),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn actions(&self) -> ActionList {
|
fn actions(&self) -> ActionList {
|
||||||
actions!(BroadcastEventDiscriminant;)
|
actions!(EventMessageDiscriminant;)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, Hash)]
|
|
||||||
#[impl_message(Message, BroadcastMessage, TriggerEvent)]
|
#[impl_message(Message, BroadcastMessage, TriggerEvent)]
|
||||||
pub enum BroadcastEvent {
|
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, Hash)]
|
||||||
|
pub enum EventMessage {
|
||||||
/// Triggered by requestAnimationFrame in JS
|
/// Triggered by requestAnimationFrame in JS
|
||||||
AnimationFrame,
|
AnimationFrame,
|
||||||
CanvasTransformed,
|
CanvasTransformed,
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
|
#[derive(ExtractField)]
|
||||||
|
pub struct EventMessageContext<'a> {
|
||||||
|
pub listeners: &'a mut HashMap<EventMessage, Vec<Message>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, ExtractField)]
|
||||||
|
pub struct EventMessageHandler {}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
|
impl MessageHandler<EventMessage, EventMessageContext<'_>> for EventMessageHandler {
|
||||||
|
fn process_message(&mut self, message: EventMessage, responses: &mut VecDeque<Message>, context: EventMessageContext) {
|
||||||
|
for message in context.listeners.entry(message).or_default() {
|
||||||
|
responses.add_front(message.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn actions(&self) -> ActionList {
|
||||||
|
actions!(EventMessageDiscriminant;)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
mod event_message;
|
||||||
|
mod event_message_handler;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use event_message::{EventMessage, EventMessageDiscriminant};
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use event_message_handler::{EventMessageContext, EventMessageHandler};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
mod broadcast_message;
|
mod broadcast_message;
|
||||||
mod broadcast_message_handler;
|
mod broadcast_message_handler;
|
||||||
|
|
||||||
pub mod broadcast_event;
|
pub mod event;
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use broadcast_message::{BroadcastMessage, BroadcastMessageDiscriminant};
|
pub use broadcast_message::{BroadcastMessage, BroadcastMessageDiscriminant};
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ use crate::messages::prelude::*;
|
|||||||
#[impl_message(Message, Defer)]
|
#[impl_message(Message, Defer)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum DeferMessage {
|
pub enum DeferMessage {
|
||||||
SetGraphSubmissionIndex(u64),
|
SetGraphSubmissionIndex { execution_id: u64 },
|
||||||
TriggerGraphRun(u64, DocumentId),
|
TriggerGraphRun { execution_id: u64, document_id: DocumentId },
|
||||||
AfterGraphRun { messages: Vec<Message> },
|
AfterGraphRun { messages: Vec<Message> },
|
||||||
TriggerNavigationReady,
|
TriggerNavigationReady,
|
||||||
AfterNavigationReady { messages: Vec<Message> },
|
AfterNavigationReady { messages: Vec<Message> },
|
||||||
|
|||||||
@@ -24,10 +24,10 @@ impl MessageHandler<DeferMessage, DeferMessageContext<'_>> for DeferMessageHandl
|
|||||||
DeferMessage::AfterNavigationReady { messages } => {
|
DeferMessage::AfterNavigationReady { messages } => {
|
||||||
self.after_viewport_resize.extend_from_slice(&messages);
|
self.after_viewport_resize.extend_from_slice(&messages);
|
||||||
}
|
}
|
||||||
DeferMessage::SetGraphSubmissionIndex(execution_id) => {
|
DeferMessage::SetGraphSubmissionIndex { execution_id } => {
|
||||||
self.current_graph_submission_id = execution_id + 1;
|
self.current_graph_submission_id = execution_id + 1;
|
||||||
}
|
}
|
||||||
DeferMessage::TriggerGraphRun(execution_id, document_id) => {
|
DeferMessage::TriggerGraphRun { execution_id, document_id } => {
|
||||||
let after_graph_run = self.after_graph_run.entry(document_id).or_default();
|
let after_graph_run = self.after_graph_run.entry(document_id).or_default();
|
||||||
if after_graph_run.is_empty() {
|
if after_graph_run.is_empty() {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ pub enum DialogMessage {
|
|||||||
RequestLicensesDialogWithLocalizedCommitDate {
|
RequestLicensesDialogWithLocalizedCommitDate {
|
||||||
localized_commit_year: String,
|
localized_commit_year: String,
|
||||||
},
|
},
|
||||||
|
RequestLicensesThirdPartyDialogWithLicenseText {
|
||||||
|
license_text: String,
|
||||||
|
},
|
||||||
RequestNewDocumentDialog,
|
RequestNewDocumentDialog,
|
||||||
RequestPreferencesDialog,
|
RequestPreferencesDialog,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use super::new_document_dialog::NewDocumentDialogMessageContext;
|
use super::new_document_dialog::NewDocumentDialogMessageContext;
|
||||||
use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArtworkDialog, LicensesDialog};
|
use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArtworkDialog, LicensesDialog};
|
||||||
|
use crate::messages::dialog::simple_dialogs::LicensesThirdPartyDialog;
|
||||||
use crate::messages::input_mapper::utility_types::input_mouse::ViewportBounds;
|
use crate::messages::input_mapper::utility_types::input_mouse::ViewportBounds;
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
@@ -103,6 +104,10 @@ impl MessageHandler<DialogMessage, DialogMessageContext<'_>> for DialogMessageHa
|
|||||||
|
|
||||||
dialog.send_dialog_to_frontend(responses);
|
dialog.send_dialog_to_frontend(responses);
|
||||||
}
|
}
|
||||||
|
DialogMessage::RequestLicensesThirdPartyDialogWithLicenseText { license_text } => {
|
||||||
|
let dialog = LicensesThirdPartyDialog { license_text };
|
||||||
|
dialog.send_dialog_to_frontend(responses);
|
||||||
|
}
|
||||||
DialogMessage::RequestNewDocumentDialog => {
|
DialogMessage::RequestNewDocumentDialog => {
|
||||||
self.new_document_dialog = NewDocumentDialogMessageHandler {
|
self.new_document_dialog = NewDocumentDialogMessageHandler {
|
||||||
name: portfolio.generate_new_document_name(),
|
name: portfolio.generate_new_document_name(),
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ use crate::messages::prelude::*;
|
|||||||
#[impl_message(Message, DialogMessage, ExportDialog)]
|
#[impl_message(Message, DialogMessage, ExportDialog)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum ExportDialogMessage {
|
pub enum ExportDialogMessage {
|
||||||
FileType(FileType),
|
FileType { file_type: FileType },
|
||||||
ScaleFactor(f64),
|
ScaleFactor { factor: f64 },
|
||||||
TransparentBackground(bool),
|
TransparentBackground { transparent: bool },
|
||||||
ExportBounds(ExportBounds),
|
ExportBounds { bounds: ExportBounds },
|
||||||
|
|
||||||
Submit,
|
Submit,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,13 +38,13 @@ impl MessageHandler<ExportDialogMessage, ExportDialogMessageContext<'_>> for Exp
|
|||||||
let ExportDialogMessageContext { portfolio } = context;
|
let ExportDialogMessageContext { portfolio } = context;
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
ExportDialogMessage::FileType(export_type) => self.file_type = export_type,
|
ExportDialogMessage::FileType { file_type } => self.file_type = file_type,
|
||||||
ExportDialogMessage::ScaleFactor(factor) => self.scale_factor = factor,
|
ExportDialogMessage::ScaleFactor { factor } => self.scale_factor = factor,
|
||||||
ExportDialogMessage::TransparentBackground(transparent_background) => self.transparent_background = transparent_background,
|
ExportDialogMessage::TransparentBackground { transparent } => self.transparent_background = transparent,
|
||||||
ExportDialogMessage::ExportBounds(export_area) => self.bounds = export_area,
|
ExportDialogMessage::ExportBounds { bounds } => self.bounds = bounds,
|
||||||
|
|
||||||
ExportDialogMessage::Submit => responses.add_front(PortfolioMessage::SubmitDocumentExport {
|
ExportDialogMessage::Submit => responses.add_front(PortfolioMessage::SubmitDocumentExport {
|
||||||
file_name: portfolio.active_document().map(|document| document.name.clone()).unwrap_or_default(),
|
name: portfolio.active_document().map(|document| document.name.clone()).unwrap_or_default(),
|
||||||
file_type: self.file_type,
|
file_type: self.file_type,
|
||||||
scale_factor: self.scale_factor,
|
scale_factor: self.scale_factor,
|
||||||
bounds: self.bounds,
|
bounds: self.bounds,
|
||||||
@@ -84,24 +84,28 @@ impl LayoutHolder for ExportDialogMessageHandler {
|
|||||||
fn layout(&self) -> Layout {
|
fn layout(&self) -> Layout {
|
||||||
let entries = [(FileType::Png, "PNG"), (FileType::Jpg, "JPG"), (FileType::Svg, "SVG")]
|
let entries = [(FileType::Png, "PNG"), (FileType::Jpg, "JPG"), (FileType::Svg, "SVG")]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(val, name)| RadioEntryData::new(format!("{val:?}")).label(name).on_update(move |_| ExportDialogMessage::FileType(val).into()))
|
.map(|(file_type, name)| {
|
||||||
|
RadioEntryData::new(format!("{file_type:?}"))
|
||||||
|
.label(name)
|
||||||
|
.on_update(move |_| ExportDialogMessage::FileType { file_type }.into())
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let export_type = vec![
|
let export_type = vec![
|
||||||
TextLabel::new("File Type").table_align(true).min_width(100).widget_holder(),
|
TextLabel::new("File Type").table_align(true).min_width("100px").widget_holder(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
RadioInput::new(entries).selected_index(Some(self.file_type as u32)).widget_holder(),
|
RadioInput::new(entries).selected_index(Some(self.file_type as u32)).widget_holder(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let resolution = vec![
|
let resolution = vec![
|
||||||
TextLabel::new("Scale Factor").table_align(true).min_width(100).widget_holder(),
|
TextLabel::new("Scale Factor").table_align(true).min_width("100px").widget_holder(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
NumberInput::new(Some(self.scale_factor))
|
NumberInput::new(Some(self.scale_factor))
|
||||||
.unit("")
|
.unit("")
|
||||||
.min(0.)
|
.min(0.)
|
||||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||||
.disabled(self.file_type == FileType::Svg)
|
.disabled(self.file_type == FileType::Svg)
|
||||||
.on_update(|number_input: &NumberInput| ExportDialogMessage::ScaleFactor(number_input.value.unwrap()).into())
|
.on_update(|number_input: &NumberInput| ExportDialogMessage::ScaleFactor { factor: number_input.value.unwrap() }.into())
|
||||||
.min_width(200)
|
.min_width(200)
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
];
|
];
|
||||||
@@ -125,10 +129,10 @@ impl LayoutHolder for ExportDialogMessageHandler {
|
|||||||
.map(|choice| {
|
.map(|choice| {
|
||||||
choice
|
choice
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(val, name, disabled)| {
|
.map(|(bounds, name, disabled)| {
|
||||||
MenuListEntry::new(format!("{val:?}"))
|
MenuListEntry::new(format!("{bounds:?}"))
|
||||||
.label(name)
|
.label(name)
|
||||||
.on_commit(move |_| ExportDialogMessage::ExportBounds(val).into())
|
.on_commit(move |_| ExportDialogMessage::ExportBounds { bounds }.into())
|
||||||
.disabled(disabled)
|
.disabled(disabled)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
@@ -140,18 +144,18 @@ impl LayoutHolder for ExportDialogMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let export_area = vec![
|
let export_area = vec![
|
||||||
TextLabel::new("Bounds").table_align(true).min_width(100).widget_holder(),
|
TextLabel::new("Bounds").table_align(true).min_width("100px").widget_holder(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
DropdownInput::new(entries).selected_index(Some(index as u32)).widget_holder(),
|
DropdownInput::new(entries).selected_index(Some(index as u32)).widget_holder(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let checkbox_id = CheckboxId::new();
|
let checkbox_id = CheckboxId::new();
|
||||||
let transparent_background = vec![
|
let transparent_background = vec![
|
||||||
TextLabel::new("Transparency").table_align(true).min_width(100).for_checkbox(checkbox_id).widget_holder(),
|
TextLabel::new("Transparency").table_align(true).min_width("100px").for_checkbox(checkbox_id).widget_holder(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
CheckboxInput::new(self.transparent_background)
|
CheckboxInput::new(self.transparent_background)
|
||||||
.disabled(self.file_type == FileType::Jpg)
|
.disabled(self.file_type == FileType::Jpg)
|
||||||
.on_update(move |value: &CheckboxInput| ExportDialogMessage::TransparentBackground(value.checked).into())
|
.on_update(move |value: &CheckboxInput| ExportDialogMessage::TransparentBackground { transparent: value.checked }.into())
|
||||||
.for_label(checkbox_id)
|
.for_label(checkbox_id)
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ use crate::messages::prelude::*;
|
|||||||
#[impl_message(Message, DialogMessage, NewDocumentDialog)]
|
#[impl_message(Message, DialogMessage, NewDocumentDialog)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum NewDocumentDialogMessage {
|
pub enum NewDocumentDialogMessage {
|
||||||
Name(String),
|
Name { name: String },
|
||||||
Infinite(bool),
|
Infinite { infinite: bool },
|
||||||
DimensionsX(f64),
|
DimensionsX { width: f64 },
|
||||||
DimensionsY(f64),
|
DimensionsY { height: f64 },
|
||||||
|
|
||||||
Submit,
|
Submit,
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-11
@@ -20,10 +20,10 @@ pub struct NewDocumentDialogMessageHandler {
|
|||||||
impl<'a> MessageHandler<NewDocumentDialogMessage, NewDocumentDialogMessageContext<'a>> for NewDocumentDialogMessageHandler {
|
impl<'a> MessageHandler<NewDocumentDialogMessage, NewDocumentDialogMessageContext<'a>> for NewDocumentDialogMessageHandler {
|
||||||
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, context: NewDocumentDialogMessageContext<'a>) {
|
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, context: NewDocumentDialogMessageContext<'a>) {
|
||||||
match message {
|
match message {
|
||||||
NewDocumentDialogMessage::Name(name) => self.name = name,
|
NewDocumentDialogMessage::Name { name } => self.name = name,
|
||||||
NewDocumentDialogMessage::Infinite(infinite) => self.infinite = infinite,
|
NewDocumentDialogMessage::Infinite { infinite } => self.infinite = infinite,
|
||||||
NewDocumentDialogMessage::DimensionsX(x) => self.dimensions.x = x as u32,
|
NewDocumentDialogMessage::DimensionsX { width } => self.dimensions.x = width as u32,
|
||||||
NewDocumentDialogMessage::DimensionsY(y) => self.dimensions.y = y as u32,
|
NewDocumentDialogMessage::DimensionsY { height } => self.dimensions.y = height as u32,
|
||||||
NewDocumentDialogMessage::Submit => {
|
NewDocumentDialogMessage::Submit => {
|
||||||
responses.add(PortfolioMessage::NewDocumentWithName { name: self.name.clone() });
|
responses.add(PortfolioMessage::NewDocumentWithName { name: self.name.clone() });
|
||||||
|
|
||||||
@@ -79,26 +79,26 @@ impl DialogLayoutHolder for NewDocumentDialogMessageHandler {
|
|||||||
impl LayoutHolder for NewDocumentDialogMessageHandler {
|
impl LayoutHolder for NewDocumentDialogMessageHandler {
|
||||||
fn layout(&self) -> Layout {
|
fn layout(&self) -> Layout {
|
||||||
let name = vec![
|
let name = vec![
|
||||||
TextLabel::new("Name").table_align(true).min_width(90).widget_holder(),
|
TextLabel::new("Name").table_align(true).min_width("90px").widget_holder(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
TextInput::new(&self.name)
|
TextInput::new(&self.name)
|
||||||
.on_update(|text_input: &TextInput| NewDocumentDialogMessage::Name(text_input.value.clone()).into())
|
.on_update(|text_input: &TextInput| NewDocumentDialogMessage::Name { name: text_input.value.clone() }.into())
|
||||||
.min_width(204) // Matches the 100px of both NumberInputs below + the 4px of the Unrelated-type separator
|
.min_width(204) // Matches the 100px of both NumberInputs below + the 4px of the Unrelated-type separator
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let checkbox_id = CheckboxId::new();
|
let checkbox_id = CheckboxId::new();
|
||||||
let infinite = vec![
|
let infinite = vec![
|
||||||
TextLabel::new("Infinite Canvas").table_align(true).min_width(90).for_checkbox(checkbox_id).widget_holder(),
|
TextLabel::new("Infinite Canvas").table_align(true).min_width("90px").for_checkbox(checkbox_id).widget_holder(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
CheckboxInput::new(self.infinite)
|
CheckboxInput::new(self.infinite)
|
||||||
.on_update(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite(checkbox_input.checked).into())
|
.on_update(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite { infinite: checkbox_input.checked }.into())
|
||||||
.for_label(checkbox_id)
|
.for_label(checkbox_id)
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
];
|
];
|
||||||
|
|
||||||
let scale = vec![
|
let scale = vec![
|
||||||
TextLabel::new("Dimensions").table_align(true).min_width(90).widget_holder(),
|
TextLabel::new("Dimensions").table_align(true).min_width("90px").widget_holder(),
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
NumberInput::new(Some(self.dimensions.x as f64))
|
NumberInput::new(Some(self.dimensions.x as f64))
|
||||||
.label("W")
|
.label("W")
|
||||||
@@ -108,7 +108,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
|
|||||||
.is_integer(true)
|
.is_integer(true)
|
||||||
.disabled(self.infinite)
|
.disabled(self.infinite)
|
||||||
.min_width(100)
|
.min_width(100)
|
||||||
.on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsX(number_input.value.unwrap()).into())
|
.on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsX { width: number_input.value.unwrap() }.into())
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
Separator::new(SeparatorType::Related).widget_holder(),
|
Separator::new(SeparatorType::Related).widget_holder(),
|
||||||
NumberInput::new(Some(self.dimensions.y as f64))
|
NumberInput::new(Some(self.dimensions.y as f64))
|
||||||
@@ -119,7 +119,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
|
|||||||
.is_integer(true)
|
.is_integer(true)
|
||||||
.disabled(self.infinite)
|
.disabled(self.infinite)
|
||||||
.min_width(100)
|
.min_width(100)
|
||||||
.on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsY(number_input.value.unwrap()).into())
|
.on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsY { height: number_input.value.unwrap() }.into())
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::messages::broadcast::broadcast_event::BroadcastEvent;
|
use crate::messages::broadcast::event::EventMessage;
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ impl DialogLayoutHolder for CloseDocumentDialog {
|
|||||||
TextButton::new("Discard")
|
TextButton::new("Discard")
|
||||||
.on_update(move |_| {
|
.on_update(move |_| {
|
||||||
DialogMessage::CloseDialogAndThen {
|
DialogMessage::CloseDialogAndThen {
|
||||||
followups: vec![BroadcastEvent::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()],
|
followups: vec![EventMessage::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()],
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,22 +16,31 @@ impl DialogLayoutHolder for LicensesDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn layout_column_2(&self) -> Layout {
|
fn layout_column_2(&self) -> Layout {
|
||||||
let icons_license_link = "https://raw.githubusercontent.com/GraphiteEditor/Graphite/master/frontend/assets/LICENSE.md";
|
#[allow(clippy::type_complexity)]
|
||||||
let links = [
|
let button_definitions: &[(&str, &str, fn() -> Message)] = &[
|
||||||
("GraphiteLogo", "Graphite Logo", "https://graphite.rs/logo/"),
|
("GraphiteLogo", "Graphite Logo", || {
|
||||||
("IconsGrid", "Graphite Icons", icons_license_link),
|
FrontendMessage::TriggerVisitLink {
|
||||||
("License", "Graphite License", "https://graphite.rs/license/"),
|
url: "https://graphite.rs/logo/".into(),
|
||||||
("License", "Other Licenses", "/third-party-licenses.txt"),
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
("IconsGrid", "Graphite Icons", || {
|
||||||
|
FrontendMessage::TriggerVisitLink {
|
||||||
|
url: "https://raw.githubusercontent.com/GraphiteEditor/Graphite/master/frontend/assets/LICENSE.md".into(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
("License", "Graphite License", || {
|
||||||
|
FrontendMessage::TriggerVisitLink {
|
||||||
|
url: "https://graphite.rs/license/".into(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
("License", "Other Licenses", || FrontendMessage::TriggerDisplayThirdPartyLicensesDialog.into()),
|
||||||
];
|
];
|
||||||
let widgets = links
|
let widgets = button_definitions
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|(icon, label, url)| {
|
.map(|&(icon, label, message_factory)| TextButton::new(label).icon(Some((icon).into())).flush(true).on_update(move |_| message_factory()).widget_holder())
|
||||||
TextButton::new(label)
|
|
||||||
.icon(Some(icon.into()))
|
|
||||||
.flush(true)
|
|
||||||
.on_update(|_| FrontendMessage::TriggerVisitLink { url: url.into() }.into())
|
|
||||||
.widget_holder()
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Column { widgets }]))
|
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Column { widgets }]))
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
|
pub struct LicensesThirdPartyDialog {
|
||||||
|
pub license_text: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DialogLayoutHolder for LicensesThirdPartyDialog {
|
||||||
|
const ICON: &'static str = "License12px";
|
||||||
|
const TITLE: &'static str = "Third-Party Software License Notices";
|
||||||
|
|
||||||
|
fn layout_buttons(&self) -> Layout {
|
||||||
|
let widgets = vec![TextButton::new("OK").emphasized(true).on_update(|_| FrontendMessage::DisplayDialogDismiss.into()).widget_holder()];
|
||||||
|
|
||||||
|
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { widgets }]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutHolder for LicensesThirdPartyDialog {
|
||||||
|
fn layout(&self) -> Layout {
|
||||||
|
// Remove the header and begin with the line containing the first license section (we otherwise keep the title for standalone viewing of the licenses text file)
|
||||||
|
let license_text = if let Some(first_underscore_line) = self.license_text.lines().position(|line| line.contains('_')) {
|
||||||
|
// Find the byte position where the line with underscore starts
|
||||||
|
let char_position = self.license_text.split('\n').take(first_underscore_line).map(|line| line.len() + '\n'.len_utf8()).sum();
|
||||||
|
self.license_text[char_position..].to_string()
|
||||||
|
} else {
|
||||||
|
// This shouldn't be encountered, but if no underscore line is found, we use the full text as a safety fallback
|
||||||
|
self.license_text.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Two characters (one before, one after) the sequence of underscore characters, plus one additional column to provide a space between the text and the scrollbar
|
||||||
|
let non_wrapping_column_width = license_text.split('\n').map(|line| line.chars().filter(|&c| c == '_').count()).max().unwrap_or(0) + 2 + 1;
|
||||||
|
|
||||||
|
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
|
||||||
|
widgets: vec![
|
||||||
|
TextLabel::new(license_text)
|
||||||
|
.monospace(true)
|
||||||
|
.multiline(true)
|
||||||
|
.min_width(format!("{non_wrapping_column_width}ch"))
|
||||||
|
.widget_holder(),
|
||||||
|
],
|
||||||
|
}]))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ mod coming_soon_dialog;
|
|||||||
mod demo_artwork_dialog;
|
mod demo_artwork_dialog;
|
||||||
mod error_dialog;
|
mod error_dialog;
|
||||||
mod licenses_dialog;
|
mod licenses_dialog;
|
||||||
|
mod licenses_third_party_dialog;
|
||||||
|
|
||||||
pub use about_graphite_dialog::AboutGraphiteDialog;
|
pub use about_graphite_dialog::AboutGraphiteDialog;
|
||||||
pub use close_all_documents_dialog::CloseAllDocumentsDialog;
|
pub use close_all_documents_dialog::CloseAllDocumentsDialog;
|
||||||
@@ -14,3 +15,4 @@ pub use demo_artwork_dialog::ARTWORK;
|
|||||||
pub use demo_artwork_dialog::DemoArtworkDialog;
|
pub use demo_artwork_dialog::DemoArtworkDialog;
|
||||||
pub use error_dialog::ErrorDialog;
|
pub use error_dialog::ErrorDialog;
|
||||||
pub use licenses_dialog::LicensesDialog;
|
pub use licenses_dialog::LicensesDialog;
|
||||||
|
pub use licenses_third_party_dialog::LicensesThirdPartyDialog;
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ pub enum FrontendMessage {
|
|||||||
#[serde(rename = "commitDate")]
|
#[serde(rename = "commitDate")]
|
||||||
commit_date: String,
|
commit_date: String,
|
||||||
},
|
},
|
||||||
|
TriggerDisplayThirdPartyLicensesDialog,
|
||||||
TriggerSaveDocument {
|
TriggerSaveDocument {
|
||||||
document_id: DocumentId,
|
document_id: DocumentId,
|
||||||
name: String,
|
name: String,
|
||||||
@@ -328,16 +329,18 @@ pub enum FrontendMessage {
|
|||||||
UpdatePlatform {
|
UpdatePlatform {
|
||||||
platform: AppWindowPlatform,
|
platform: AppWindowPlatform,
|
||||||
},
|
},
|
||||||
UpdateMaximized {
|
UpdateWindowState {
|
||||||
maximized: bool,
|
maximized: bool,
|
||||||
|
minimized: bool,
|
||||||
},
|
},
|
||||||
|
CloseWindow,
|
||||||
UpdateViewportHolePunch {
|
UpdateViewportHolePunch {
|
||||||
active: bool,
|
active: bool,
|
||||||
},
|
},
|
||||||
#[cfg(not(target_family = "wasm"))]
|
#[cfg(not(target_family = "wasm"))]
|
||||||
RenderOverlays(
|
RenderOverlays {
|
||||||
#[serde(skip, default = "OverlayContext::default")]
|
#[serde(skip, default = "OverlayContext::default")]
|
||||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||||
OverlayContext,
|
context: OverlayContext,
|
||||||
),
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ pub fn input_mappings() -> Mapping {
|
|||||||
entry!(PointerMove; refresh_keys=[Control, Shift], action_dispatch=TransformLayerMessage::PointerMove { slow_key: Shift, increments_key: Control }),
|
entry!(PointerMove; refresh_keys=[Control, Shift], action_dispatch=TransformLayerMessage::PointerMove { slow_key: Shift, increments_key: Control }),
|
||||||
//
|
//
|
||||||
// SelectToolMessage
|
// SelectToolMessage
|
||||||
entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=SelectToolMessage::PointerMove(SelectToolPointerKeys { axis_align: Shift, snap_angle: Shift, center: Alt, duplicate: Alt })),
|
entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=SelectToolMessage::PointerMove { modifier_keys: SelectToolPointerKeys { axis_align: Shift, snap_angle: Shift, center: Alt, duplicate: Alt } }),
|
||||||
entry!(KeyDown(MouseLeft); action_dispatch=SelectToolMessage::DragStart { extend_selection: Shift, remove_from_selection: Alt, select_deepest: Accel, lasso_select: Control, skew: Control }),
|
entry!(KeyDown(MouseLeft); action_dispatch=SelectToolMessage::DragStart { extend_selection: Shift, remove_from_selection: Alt, select_deepest: Accel, lasso_select: Control, skew: Control }),
|
||||||
entry!(KeyUp(MouseLeft); action_dispatch=SelectToolMessage::DragStop { remove_from_selection: Alt }),
|
entry!(KeyUp(MouseLeft); action_dispatch=SelectToolMessage::DragStop { remove_from_selection: Alt }),
|
||||||
entry!(KeyDown(Enter); action_dispatch=SelectToolMessage::Enter),
|
entry!(KeyDown(Enter); action_dispatch=SelectToolMessage::Enter),
|
||||||
@@ -178,7 +178,7 @@ pub fn input_mappings() -> Mapping {
|
|||||||
entry!(KeyDown(Escape); action_dispatch=ShapeToolMessage::Abort),
|
entry!(KeyDown(Escape); action_dispatch=ShapeToolMessage::Abort),
|
||||||
entry!(KeyDown(BracketLeft); action_dispatch=ShapeToolMessage::DecreaseSides),
|
entry!(KeyDown(BracketLeft); action_dispatch=ShapeToolMessage::DecreaseSides),
|
||||||
entry!(KeyDown(BracketRight); action_dispatch=ShapeToolMessage::IncreaseSides),
|
entry!(KeyDown(BracketRight); action_dispatch=ShapeToolMessage::IncreaseSides),
|
||||||
entry!(PointerMove; refresh_keys=[Alt, Shift, Control], action_dispatch=ShapeToolMessage::PointerMove([Alt, Shift, Control])),
|
entry!(PointerMove; refresh_keys=[Alt, Shift, Control], action_dispatch=ShapeToolMessage::PointerMove { modifier: [Alt, Shift, Control] }),
|
||||||
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
|
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
|
||||||
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
|
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
|
||||||
entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
|
entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
|
||||||
@@ -223,9 +223,9 @@ pub fn input_mappings() -> Mapping {
|
|||||||
entry!(KeyDown(KeyS); action_dispatch=PathToolMessage::GRS { key: KeyS }),
|
entry!(KeyDown(KeyS); action_dispatch=PathToolMessage::GRS { key: KeyS }),
|
||||||
entry!(PointerMove; refresh_keys=[KeyC, Space, Control, Shift, Alt], action_dispatch=PathToolMessage::PointerMove { toggle_colinear: KeyC, equidistant: Alt, move_anchor_with_handles: Space, snap_angle: Shift, lock_angle: Control, delete_segment: Alt, break_colinear_molding: Alt, segment_editing_modifier: Control }),
|
entry!(PointerMove; refresh_keys=[KeyC, Space, Control, Shift, Alt], action_dispatch=PathToolMessage::PointerMove { toggle_colinear: KeyC, equidistant: Alt, move_anchor_with_handles: Space, snap_angle: Shift, lock_angle: Control, delete_segment: Alt, break_colinear_molding: Alt, segment_editing_modifier: Control }),
|
||||||
entry!(KeyDown(Delete); action_dispatch=PathToolMessage::Delete),
|
entry!(KeyDown(Delete); action_dispatch=PathToolMessage::Delete),
|
||||||
entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=PathToolMessage::SelectAllAnchors),
|
entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=PathToolMessage::SelectAll),
|
||||||
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=PathToolMessage::DeselectAllPoints),
|
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=PathToolMessage::DeselectAllSelected),
|
||||||
entry!(KeyDown(KeyA); modifiers=[Alt], action_dispatch=PathToolMessage::DeselectAllPoints),
|
entry!(KeyDown(KeyA); modifiers=[Alt], action_dispatch=PathToolMessage::DeselectAllSelected),
|
||||||
entry!(KeyDown(Backspace); action_dispatch=PathToolMessage::Delete),
|
entry!(KeyDown(Backspace); action_dispatch=PathToolMessage::Delete),
|
||||||
entry!(KeyUp(MouseLeft); action_dispatch=PathToolMessage::DragStop { extend_selection: Shift, shrink_selection: Alt }),
|
entry!(KeyUp(MouseLeft); action_dispatch=PathToolMessage::DragStop { extend_selection: Shift, shrink_selection: Alt }),
|
||||||
entry!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter { extend_selection: Shift, shrink_selection: Alt }),
|
entry!(KeyDown(Enter); action_dispatch=PathToolMessage::Enter { extend_selection: Shift, shrink_selection: Alt }),
|
||||||
@@ -297,8 +297,8 @@ pub fn input_mappings() -> Mapping {
|
|||||||
entry!(PointerMove; action_dispatch=BrushToolMessage::PointerMove),
|
entry!(PointerMove; action_dispatch=BrushToolMessage::PointerMove),
|
||||||
entry!(KeyDown(MouseLeft); action_dispatch=BrushToolMessage::DragStart),
|
entry!(KeyDown(MouseLeft); action_dispatch=BrushToolMessage::DragStart),
|
||||||
entry!(KeyUp(MouseLeft); action_dispatch=BrushToolMessage::DragStop),
|
entry!(KeyUp(MouseLeft); action_dispatch=BrushToolMessage::DragStop),
|
||||||
entry!(KeyDown(BracketLeft); action_dispatch=BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ChangeDiameter(-BRUSH_SIZE_CHANGE_KEYBOARD))),
|
entry!(KeyDown(BracketLeft); action_dispatch=BrushToolMessage::UpdateOptions { options: BrushToolMessageOptionsUpdate::ChangeDiameter(-BRUSH_SIZE_CHANGE_KEYBOARD) }),
|
||||||
entry!(KeyDown(BracketRight); action_dispatch=BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ChangeDiameter(BRUSH_SIZE_CHANGE_KEYBOARD))),
|
entry!(KeyDown(BracketRight); action_dispatch=BrushToolMessage::UpdateOptions { options: BrushToolMessageOptionsUpdate::ChangeDiameter(BRUSH_SIZE_CHANGE_KEYBOARD) }),
|
||||||
entry!(KeyDown(MouseRight); action_dispatch=BrushToolMessage::Abort),
|
entry!(KeyDown(MouseRight); action_dispatch=BrushToolMessage::Abort),
|
||||||
entry!(KeyDown(Escape); action_dispatch=BrushToolMessage::Abort),
|
entry!(KeyDown(Escape); action_dispatch=BrushToolMessage::Abort),
|
||||||
//
|
//
|
||||||
@@ -340,6 +340,7 @@ pub fn input_mappings() -> Mapping {
|
|||||||
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=DocumentMessage::DeselectAllLayers),
|
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=DocumentMessage::DeselectAllLayers),
|
||||||
entry!(KeyDown(KeyA); modifiers=[Alt], action_dispatch=DocumentMessage::DeselectAllLayers),
|
entry!(KeyDown(KeyA); modifiers=[Alt], action_dispatch=DocumentMessage::DeselectAllLayers),
|
||||||
entry!(KeyDown(KeyS); modifiers=[Accel], action_dispatch=DocumentMessage::SaveDocument),
|
entry!(KeyDown(KeyS); modifiers=[Accel], action_dispatch=DocumentMessage::SaveDocument),
|
||||||
|
entry!(KeyDown(KeyS); modifiers=[Accel, Shift], action_dispatch=DocumentMessage::SaveDocumentAs),
|
||||||
entry!(KeyDown(KeyD); modifiers=[Accel], canonical, action_dispatch=DocumentMessage::DuplicateSelectedLayers),
|
entry!(KeyDown(KeyD); modifiers=[Accel], canonical, action_dispatch=DocumentMessage::DuplicateSelectedLayers),
|
||||||
entry!(KeyDown(KeyJ); modifiers=[Accel], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
|
entry!(KeyDown(KeyJ); modifiers=[Accel], action_dispatch=DocumentMessage::DuplicateSelectedLayers),
|
||||||
entry!(KeyDown(KeyG); modifiers=[Accel], action_dispatch=DocumentMessage::GroupSelectedLayers { group_folder_type: GroupFolderType::Layer }),
|
entry!(KeyDown(KeyG); modifiers=[Accel], action_dispatch=DocumentMessage::GroupSelectedLayers { group_folder_type: GroupFolderType::Layer }),
|
||||||
|
|||||||
@@ -3,13 +3,16 @@ use crate::messages::prelude::*;
|
|||||||
#[impl_message(Message, KeyMapping)]
|
#[impl_message(Message, KeyMapping)]
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
|
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum KeyMappingMessage {
|
pub enum KeyMappingMessage {
|
||||||
|
// Sub-messages
|
||||||
#[child]
|
#[child]
|
||||||
Lookup(InputMapperMessage),
|
Lookup(InputMapperMessage),
|
||||||
#[child]
|
|
||||||
ModifyMapping(MappingVariant),
|
// Messages
|
||||||
|
ModifyMapping {
|
||||||
|
mapping: MappingVariant,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, KeyMappingMessage, ModifyMapping)]
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Default, Hash, serde::Serialize, serde::Deserialize)]
|
#[derive(PartialEq, Eq, Clone, Debug, Default, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum MappingVariant {
|
pub enum MappingVariant {
|
||||||
#[default]
|
#[default]
|
||||||
|
|||||||
@@ -19,8 +19,11 @@ impl MessageHandler<KeyMappingMessage, KeyMappingMessageContext<'_>> for KeyMapp
|
|||||||
let KeyMappingMessageContext { input, actions } = context;
|
let KeyMappingMessageContext { input, actions } = context;
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
|
// Sub-messages
|
||||||
KeyMappingMessage::Lookup(input_message) => self.mapping_handler.process_message(input_message, responses, InputMapperMessageContext { input, actions }),
|
KeyMappingMessage::Lookup(input_message) => self.mapping_handler.process_message(input_message, responses, InputMapperMessageContext { input, actions }),
|
||||||
KeyMappingMessage::ModifyMapping(new_layout) => self.mapping_handler.set_mapping(new_layout.into()),
|
|
||||||
|
// Messages
|
||||||
|
KeyMappingMessage::ModifyMapping { mapping } => self.mapping_handler.set_mapping(mapping.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
advertise_actions!();
|
advertise_actions!();
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ mod key_mapping_message;
|
|||||||
mod key_mapping_message_handler;
|
mod key_mapping_message_handler;
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use key_mapping_message::{KeyMappingMessage, KeyMappingMessageDiscriminant, MappingVariant, MappingVariantDiscriminant};
|
pub use key_mapping_message::{KeyMappingMessage, KeyMappingMessageDiscriminant, MappingVariant};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use key_mapping_message_handler::{KeyMappingMessageContext, KeyMappingMessageHandler};
|
pub use key_mapping_message_handler::{KeyMappingMessageContext, KeyMappingMessageHandler};
|
||||||
|
|||||||
@@ -479,7 +479,7 @@ impl<const LENGTH: usize> Iterator for BitVectorIter<'_, LENGTH> {
|
|||||||
impl<const LENGTH: usize> Display for BitVector<LENGTH> {
|
impl<const LENGTH: usize> Display for BitVector<LENGTH> {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
for storage in self.0.iter().rev() {
|
for storage in self.0.iter().rev() {
|
||||||
write!(f, "{:0width$b}", storage, width = STORAGE_SIZE_BITS)?;
|
write!(f, "{storage:0STORAGE_SIZE_BITS$b}")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -44,16 +44,18 @@ pub struct TextLabel {
|
|||||||
|
|
||||||
pub italic: bool,
|
pub italic: bool,
|
||||||
|
|
||||||
|
pub monospace: bool,
|
||||||
|
|
||||||
|
pub multiline: bool,
|
||||||
|
|
||||||
#[serde(rename = "centerAlign")]
|
#[serde(rename = "centerAlign")]
|
||||||
pub center_align: bool,
|
pub center_align: bool,
|
||||||
|
|
||||||
#[serde(rename = "tableAlign")]
|
#[serde(rename = "tableAlign")]
|
||||||
pub table_align: bool,
|
pub table_align: bool,
|
||||||
|
|
||||||
pub multiline: bool,
|
|
||||||
|
|
||||||
#[serde(rename = "minWidth")]
|
#[serde(rename = "minWidth")]
|
||||||
pub min_width: u32,
|
pub min_width: String,
|
||||||
|
|
||||||
pub tooltip: String,
|
pub tooltip: String,
|
||||||
|
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ pub enum Message {
|
|||||||
Tool(ToolMessage),
|
Tool(ToolMessage),
|
||||||
|
|
||||||
// Messages
|
// Messages
|
||||||
NoOp,
|
|
||||||
Batched {
|
Batched {
|
||||||
messages: Box<[Message]>,
|
messages: Box<[Message]>,
|
||||||
},
|
},
|
||||||
|
NoOp,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides an impl of `specta::Type` for `MessageDiscriminant`, the struct created by `impl_message`.
|
/// Provides an impl of `specta::Type` for `MessageDiscriminant`, the struct created by `impl_message`.
|
||||||
@@ -70,13 +70,11 @@ 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.message_handler_data_fields().is_some() || tree.message_handler_fields().is_some() {
|
let (branch, child_prefix) = if tree.message_handler_data_fields().is_some() || tree.message_handler_fields().is_some() {
|
||||||
("├── ", format!("{}│ ", prefix))
|
("├── ", format!("{prefix}│ "))
|
||||||
|
} else if is_last {
|
||||||
|
("└── ", format!("{prefix} "))
|
||||||
} else {
|
} else {
|
||||||
if is_last {
|
("├── ", format!("{prefix}│ "))
|
||||||
("└── ", format!("{} ", prefix))
|
|
||||||
} else {
|
|
||||||
("├── ", format!("{}│ ", prefix))
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if tree.path().is_empty() {
|
if tree.path().is_empty() {
|
||||||
@@ -94,24 +92,38 @@ mod test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Print message field if any
|
||||||
|
if let Some(fields) = tree.fields() {
|
||||||
|
let len = fields.len();
|
||||||
|
for (i, field) in fields.iter().enumerate() {
|
||||||
|
let is_last_field = i == len - 1;
|
||||||
|
let branch = if is_last_field { "└── " } else { "├── " };
|
||||||
|
|
||||||
|
file.write_all(format!("{child_prefix}{branch}{field}\n").as_bytes()).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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.message_handler_data_fields().is_some() {
|
let (branch, child_prefix) = if tree.message_handler_data_fields().is_some() {
|
||||||
("├── ", format!("{}│ ", prefix))
|
("├── ", format!("{prefix}│ "))
|
||||||
} else {
|
} else {
|
||||||
("└── ", format!("{} ", prefix))
|
("└── ", format!("{prefix} "))
|
||||||
};
|
};
|
||||||
if data.path().is_empty() {
|
|
||||||
file.write_all(format!("{}{}{}\n", prefix, branch, data.name()).as_bytes()).unwrap();
|
|
||||||
} else {
|
|
||||||
file.write_all(format!("{}{}{} `{}`\n", prefix, branch, data.name(), data.path()).as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
for (i, field) in data.fields().iter().enumerate() {
|
|
||||||
let is_last_field = i == len - 1;
|
|
||||||
let branch = if is_last_field { "└── " } else { "├── " };
|
|
||||||
|
|
||||||
file.write_all(format!("{}{}{}\n", child_prefix, branch, field.0).as_bytes()).unwrap();
|
const FRONTEND_MESSAGE_STR: &str = "FrontendMessage";
|
||||||
|
if data.name().is_empty() && tree.name() != FRONTEND_MESSAGE_STR {
|
||||||
|
panic!("{}'s MessageHandler is missing #[message_handler_data]", tree.name());
|
||||||
|
} else if tree.name() != FRONTEND_MESSAGE_STR {
|
||||||
|
file.write_all(format!("{}{}{} `{}`\n", prefix, branch, data.name(), data.path()).as_bytes()).unwrap();
|
||||||
|
|
||||||
|
for (i, field) in data.fields().iter().enumerate() {
|
||||||
|
let is_last_field = i == len - 1;
|
||||||
|
let branch = if is_last_field { "└── " } else { "├── " };
|
||||||
|
|
||||||
|
file.write_all(format!("{}{}{}\n", child_prefix, branch, field.0).as_bytes()).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use crate::messages::portfolio::document::data_panel::DataPanelMessage;
|
|||||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||||
|
use glam::{Affine2, Vec2};
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_std::Color;
|
use graphene_std::Color;
|
||||||
use graphene_std::Context;
|
use graphene_std::Context;
|
||||||
@@ -594,6 +595,19 @@ impl TableRowLayout for DVec2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TableRowLayout for Vec2 {
|
||||||
|
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 {
|
impl TableRowLayout for DAffine2 {
|
||||||
fn type_name() -> &'static str {
|
fn type_name() -> &'static str {
|
||||||
"Transform"
|
"Transform"
|
||||||
@@ -607,6 +621,20 @@ impl TableRowLayout for DAffine2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TableRowLayout for Affine2 {
|
||||||
|
fn type_name() -> &'static str {
|
||||||
|
"Transform"
|
||||||
|
}
|
||||||
|
fn identifier(&self) -> String {
|
||||||
|
"Transform".to_string()
|
||||||
|
}
|
||||||
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
|
let matrix = DAffine2::from_cols_array(&self.to_cols_array().map(|x| x as f64));
|
||||||
|
let widgets = vec![TextLabel::new(format_transform_matrix(&matrix)).widget_holder()];
|
||||||
|
vec![LayoutGroup::Row { widgets }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn format_transform_matrix(transform: &DAffine2) -> String {
|
fn format_transform_matrix(transform: &DAffine2) -> String {
|
||||||
let (scale, angle, translation) = transform.to_scale_angle_translation();
|
let (scale, angle, translation) = transform.to_scale_angle_translation();
|
||||||
let rotation = if angle == -0. { 0. } else { angle.to_degrees() };
|
let rotation = if angle == -0. { 0. } else { angle.to_degrees() };
|
||||||
|
|||||||
@@ -53,7 +53,9 @@ pub enum DocumentMessage {
|
|||||||
DocumentHistoryBackward,
|
DocumentHistoryBackward,
|
||||||
DocumentHistoryForward,
|
DocumentHistoryForward,
|
||||||
DocumentStructureChanged,
|
DocumentStructureChanged,
|
||||||
DrawArtboardOverlays(OverlayContext),
|
DrawArtboardOverlays {
|
||||||
|
context: OverlayContext,
|
||||||
|
},
|
||||||
DuplicateSelectedLayers,
|
DuplicateSelectedLayers,
|
||||||
EnterNestedNetwork {
|
EnterNestedNetwork {
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
@@ -72,9 +74,15 @@ pub enum DocumentMessage {
|
|||||||
open: bool,
|
open: bool,
|
||||||
},
|
},
|
||||||
GraphViewOverlayToggle,
|
GraphViewOverlayToggle,
|
||||||
GridOptions(GridSnapping),
|
GridOptions {
|
||||||
GridOverlays(OverlayContext),
|
options: GridSnapping,
|
||||||
GridVisibility(bool),
|
},
|
||||||
|
GridOverlays {
|
||||||
|
context: OverlayContext,
|
||||||
|
},
|
||||||
|
GridVisibility {
|
||||||
|
visible: bool,
|
||||||
|
},
|
||||||
GroupSelectedLayers {
|
GroupSelectedLayers {
|
||||||
group_folder_type: GroupFolderType,
|
group_folder_type: GroupFolderType,
|
||||||
},
|
},
|
||||||
@@ -110,6 +118,7 @@ pub enum DocumentMessage {
|
|||||||
RenderRulers,
|
RenderRulers,
|
||||||
RenderScrollbars,
|
RenderScrollbars,
|
||||||
SaveDocument,
|
SaveDocument,
|
||||||
|
SaveDocumentAs,
|
||||||
SavedDocument {
|
SavedDocument {
|
||||||
path: Option<PathBuf>,
|
path: Option<PathBuf>,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use super::utility_types::misc::{GroupFolderType, SNAP_FUNCTIONS_FOR_BOUNDING_BO
|
|||||||
use super::utility_types::network_interface::{self, NodeNetworkInterface, TransactionStatus};
|
use super::utility_types::network_interface::{self, NodeNetworkInterface, TransactionStatus};
|
||||||
use super::utility_types::nodes::{CollapsedLayers, SelectedNodes};
|
use super::utility_types::nodes::{CollapsedLayers, SelectedNodes};
|
||||||
use crate::application::{GRAPHITE_GIT_COMMIT_HASH, generate_uuid};
|
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_EXTENSION, 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::data_panel::{DataPanelMessageContext, DataPanelMessageHandler};
|
||||||
@@ -27,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};
|
||||||
@@ -35,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;
|
||||||
|
|
||||||
@@ -83,8 +85,6 @@ pub struct DocumentMessageHandler {
|
|||||||
/// List of the [`LayerNodeIdentifier`]s that are currently collapsed by the user in the Layers panel.
|
/// List of the [`LayerNodeIdentifier`]s that are currently collapsed by the user in the Layers panel.
|
||||||
/// Collapsed means that the expansion arrow isn't set to show the children of these layers.
|
/// Collapsed means that the expansion arrow isn't set to show the children of these layers.
|
||||||
pub collapsed: CollapsedLayers,
|
pub collapsed: CollapsedLayers,
|
||||||
/// The name of the document, which is displayed in the tab and title bar of the editor.
|
|
||||||
pub name: String,
|
|
||||||
/// The full Git commit hash of the Graphite repository that was used to build the editor.
|
/// The full Git commit hash of the Graphite repository that was used to build the editor.
|
||||||
/// We save this to provide a hint about which version of the editor was used to create the document.
|
/// We save this to provide a hint about which version of the editor was used to create the document.
|
||||||
pub commit_hash: String,
|
pub commit_hash: String,
|
||||||
@@ -111,6 +111,12 @@ pub struct DocumentMessageHandler {
|
|||||||
// Fields omitted from the saved document format
|
// Fields omitted from the saved document format
|
||||||
// =============================================
|
// =============================================
|
||||||
//
|
//
|
||||||
|
/// The name of the document, which is displayed in the tab and title bar of the editor.
|
||||||
|
#[serde(skip)]
|
||||||
|
pub name: String,
|
||||||
|
/// The path of the to the document file.
|
||||||
|
#[serde(skip)]
|
||||||
|
pub(crate) path: Option<PathBuf>,
|
||||||
/// Path to network currently viewed in the node graph overlay. This will eventually be stored in each panel, so that multiple panels can refer to different networks
|
/// Path to network currently viewed in the node graph overlay. This will eventually be stored in each panel, so that multiple panels can refer to different networks
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
breadcrumb_network_path: Vec<NodeId>,
|
breadcrumb_network_path: Vec<NodeId>,
|
||||||
@@ -123,9 +129,6 @@ pub struct DocumentMessageHandler {
|
|||||||
/// Stack of document network snapshots for future history states.
|
/// Stack of document network snapshots for future history states.
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
document_redo_history: VecDeque<NodeNetworkInterface>,
|
document_redo_history: VecDeque<NodeNetworkInterface>,
|
||||||
/// The path of the to the document file.
|
|
||||||
#[serde(skip)]
|
|
||||||
path: Option<PathBuf>,
|
|
||||||
/// Hash of the document snapshot that was most recently saved to disk by the user.
|
/// Hash of the document snapshot that was most recently saved to disk by the user.
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
saved_hash: Option<u64>,
|
saved_hash: Option<u64>,
|
||||||
@@ -157,7 +160,6 @@ impl Default for DocumentMessageHandler {
|
|||||||
// ============================================
|
// ============================================
|
||||||
network_interface: default_document_network_interface(),
|
network_interface: default_document_network_interface(),
|
||||||
collapsed: CollapsedLayers::default(),
|
collapsed: CollapsedLayers::default(),
|
||||||
name: DEFAULT_DOCUMENT_NAME.to_string(),
|
|
||||||
commit_hash: GRAPHITE_GIT_COMMIT_HASH.to_string(),
|
commit_hash: GRAPHITE_GIT_COMMIT_HASH.to_string(),
|
||||||
document_ptz: PTZ::default(),
|
document_ptz: PTZ::default(),
|
||||||
document_mode: DocumentMode::DesignMode,
|
document_mode: DocumentMode::DesignMode,
|
||||||
@@ -170,11 +172,12 @@ impl Default for DocumentMessageHandler {
|
|||||||
// =============================================
|
// =============================================
|
||||||
// Fields omitted from the saved document format
|
// Fields omitted from the saved document format
|
||||||
// =============================================
|
// =============================================
|
||||||
|
name: DEFAULT_DOCUMENT_NAME.to_string(),
|
||||||
|
path: None,
|
||||||
breadcrumb_network_path: Vec::new(),
|
breadcrumb_network_path: Vec::new(),
|
||||||
selection_network_path: Vec::new(),
|
selection_network_path: Vec::new(),
|
||||||
document_undo_history: VecDeque::new(),
|
document_undo_history: VecDeque::new(),
|
||||||
document_redo_history: VecDeque::new(),
|
document_redo_history: VecDeque::new(),
|
||||||
path: None,
|
|
||||||
saved_hash: None,
|
saved_hash: None,
|
||||||
auto_saved_hash: None,
|
auto_saved_hash: None,
|
||||||
layer_range_selection_reference: None,
|
layer_range_selection_reference: None,
|
||||||
@@ -389,7 +392,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
|
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DocumentMessage::DrawArtboardOverlays(overlay_context) => {
|
DocumentMessage::DrawArtboardOverlays { context: overlay_context } => {
|
||||||
if !overlay_context.visibility_settings.artboard_name() {
|
if !overlay_context.visibility_settings.artboard_name() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -580,24 +583,25 @@ 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 => {
|
||||||
responses.add(DocumentMessage::GraphViewOverlay { open: !self.graph_view_overlay_open });
|
responses.add(DocumentMessage::GraphViewOverlay { open: !self.graph_view_overlay_open });
|
||||||
}
|
}
|
||||||
DocumentMessage::GridOptions(grid) => {
|
DocumentMessage::GridOptions { options } => {
|
||||||
self.snapping_state.grid = grid;
|
self.snapping_state.grid = options;
|
||||||
self.snapping_state.grid_snapping = true;
|
self.snapping_state.grid_snapping = true;
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||||
}
|
}
|
||||||
DocumentMessage::GridOverlays(mut overlay_context) => {
|
DocumentMessage::GridOverlays { context: mut overlay_context } => {
|
||||||
if self.snapping_state.grid_snapping {
|
if self.snapping_state.grid_snapping {
|
||||||
grid_overlay(self, &mut overlay_context)
|
grid_overlay(self, &mut overlay_context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DocumentMessage::GridVisibility(enabled) => {
|
DocumentMessage::GridVisibility { visible } => {
|
||||||
self.snapping_state.grid_snapping = enabled;
|
self.snapping_state.grid_snapping = visible;
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
}
|
}
|
||||||
DocumentMessage::GroupSelectedLayers { group_folder_type } => {
|
DocumentMessage::GroupSelectedLayers { group_folder_type } => {
|
||||||
@@ -944,7 +948,11 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
}
|
}
|
||||||
DocumentMessage::RenameDocument { new_name } => {
|
DocumentMessage::RenameDocument { new_name } => {
|
||||||
self.name = new_name;
|
self.name = new_name.clone();
|
||||||
|
|
||||||
|
self.path = None;
|
||||||
|
self.set_save_state(false);
|
||||||
|
|
||||||
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||||
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
|
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
|
||||||
}
|
}
|
||||||
@@ -1017,25 +1025,40 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
multiplier: scrollbar_multiplier.into(),
|
multiplier: scrollbar_multiplier.into(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
DocumentMessage::SaveDocument => {
|
DocumentMessage::SaveDocument | DocumentMessage::SaveDocumentAs => {
|
||||||
|
if let DocumentMessage::SaveDocumentAs = message {
|
||||||
|
self.path = None;
|
||||||
|
}
|
||||||
|
|
||||||
self.set_save_state(true);
|
self.set_save_state(true);
|
||||||
responses.add(PortfolioMessage::AutoSaveActiveDocument);
|
responses.add(PortfolioMessage::AutoSaveActiveDocument);
|
||||||
// Update the save status of the just saved document
|
// Update the save status of the just saved document
|
||||||
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||||
|
|
||||||
let name = match self.name.ends_with(FILE_SAVE_SUFFIX) {
|
|
||||||
true => self.name.clone(),
|
|
||||||
false => self.name.clone() + FILE_SAVE_SUFFIX,
|
|
||||||
};
|
|
||||||
responses.add(FrontendMessage::TriggerSaveDocument {
|
responses.add(FrontendMessage::TriggerSaveDocument {
|
||||||
document_id,
|
document_id,
|
||||||
name,
|
name: format!("{}.{}", self.name.clone(), FILE_EXTENSION),
|
||||||
path: self.path.clone(),
|
path: self.path.clone(),
|
||||||
content: self.serialize_document().into_bytes(),
|
content: self.serialize_document().into_bytes(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
DocumentMessage::SavedDocument { path } => {
|
DocumentMessage::SavedDocument { path } => {
|
||||||
self.path = path;
|
self.path = path;
|
||||||
|
|
||||||
|
// Update the name to match the file stem
|
||||||
|
let document_name_from_path = self.path.as_ref().and_then(|path| {
|
||||||
|
if path.extension().is_some_and(|e| e == FILE_EXTENSION) {
|
||||||
|
path.file_stem().map(|n| n.to_string_lossy().to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let Some(name) = document_name_from_path {
|
||||||
|
self.name = name;
|
||||||
|
|
||||||
|
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||||
|
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DocumentMessage::SelectParentLayer => {
|
DocumentMessage::SelectParentLayer => {
|
||||||
let selected_nodes = self.network_interface.selected_nodes();
|
let selected_nodes = self.network_interface.selected_nodes();
|
||||||
@@ -1059,7 +1082,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
if !parent_layers.is_empty() {
|
if !parent_layers.is_empty() {
|
||||||
let nodes = parent_layers.into_iter().collect();
|
let nodes = parent_layers.into_iter().collect();
|
||||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
|
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
|
||||||
responses.add(BroadcastEvent::SelectionChanged);
|
responses.add(EventMessage::SelectionChanged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DocumentMessage::SelectAllLayers => {
|
DocumentMessage::SelectAllLayers => {
|
||||||
@@ -1134,7 +1157,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
} else {
|
} else {
|
||||||
responses.add_front(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] });
|
responses.add_front(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] });
|
||||||
}
|
}
|
||||||
responses.add(BroadcastEvent::SelectionChanged);
|
responses.add(EventMessage::SelectionChanged);
|
||||||
} else {
|
} else {
|
||||||
nodes.push(id);
|
nodes.push(id);
|
||||||
}
|
}
|
||||||
@@ -1203,7 +1226,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
Some(overlays_type) => overlays_type,
|
Some(overlays_type) => overlays_type,
|
||||||
None => {
|
None => {
|
||||||
visibility_settings.all = visible;
|
visibility_settings.all = visible;
|
||||||
responses.add(BroadcastEvent::ToolAbort);
|
responses.add(EventMessage::ToolAbort);
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1226,7 +1249,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
OverlaysType::Handles => visibility_settings.handles = visible,
|
OverlaysType::Handles => visibility_settings.handles = visible,
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.add(BroadcastEvent::ToolAbort);
|
responses.add(EventMessage::ToolAbort);
|
||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
}
|
}
|
||||||
DocumentMessage::SetRangeSelectionLayer { new_layer } => {
|
DocumentMessage::SetRangeSelectionLayer { new_layer } => {
|
||||||
@@ -1440,12 +1463,14 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
let transform = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
let transform = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
||||||
self.network_interface.set_document_to_viewport_transform(transform);
|
self.network_interface.set_document_to_viewport_transform(transform);
|
||||||
// Ensure selection box is kept in sync with the pointer when the PTZ changes
|
// Ensure selection box is kept in sync with the pointer when the PTZ changes
|
||||||
responses.add(SelectToolMessage::PointerMove(SelectToolPointerKeys {
|
responses.add(SelectToolMessage::PointerMove {
|
||||||
axis_align: Key::Shift,
|
modifier_keys: SelectToolPointerKeys {
|
||||||
snap_angle: Key::Shift,
|
axis_align: Key::Shift,
|
||||||
center: Key::Alt,
|
snap_angle: Key::Shift,
|
||||||
duplicate: Key::Alt,
|
center: Key::Alt,
|
||||||
}));
|
duplicate: Key::Alt,
|
||||||
|
},
|
||||||
|
});
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
} else {
|
} else {
|
||||||
let Some(network_metadata) = self.network_interface.network_metadata(&self.breadcrumb_network_path) else {
|
let Some(network_metadata) = self.network_interface.network_metadata(&self.breadcrumb_network_path) else {
|
||||||
@@ -1474,11 +1499,11 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
}
|
}
|
||||||
DocumentMessage::SelectionStepBack => {
|
DocumentMessage::SelectionStepBack => {
|
||||||
self.network_interface.selection_step_back(&self.selection_network_path);
|
self.network_interface.selection_step_back(&self.selection_network_path);
|
||||||
responses.add(BroadcastEvent::SelectionChanged);
|
responses.add(EventMessage::SelectionChanged);
|
||||||
}
|
}
|
||||||
DocumentMessage::SelectionStepForward => {
|
DocumentMessage::SelectionStepForward => {
|
||||||
self.network_interface.selection_step_forward(&self.selection_network_path);
|
self.network_interface.selection_step_forward(&self.selection_network_path);
|
||||||
responses.add(BroadcastEvent::SelectionChanged);
|
responses.add(EventMessage::SelectionChanged);
|
||||||
}
|
}
|
||||||
DocumentMessage::WrapContentInArtboard { place_artboard_at_origin } => {
|
DocumentMessage::WrapContentInArtboard { place_artboard_at_origin } => {
|
||||||
// Get bounding box of all layers
|
// Get bounding box of all layers
|
||||||
@@ -1566,6 +1591,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
ZoomCanvasToFitAll,
|
ZoomCanvasToFitAll,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Additional actions available on desktop
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
|
common.extend(actions!(DocumentMessageDiscriminant::SaveDocumentAs));
|
||||||
|
|
||||||
// Additional actions if there are any selected layers
|
// Additional actions if there are any selected layers
|
||||||
if self.network_interface.selected_nodes().selected_layers(self.metadata()).next().is_some() {
|
if self.network_interface.selected_nodes().selected_layers(self.metadata()).next().is_some() {
|
||||||
let mut select = actions!(DocumentMessageDiscriminant;
|
let mut select = actions!(DocumentMessageDiscriminant;
|
||||||
@@ -1753,7 +1782,8 @@ impl DocumentMessageHandler {
|
|||||||
|
|
||||||
pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {
|
pub fn deserialize_document(serialized_content: &str) -> Result<Self, EditorError> {
|
||||||
let document_message_handler = serde_json::from_str::<DocumentMessageHandler>(serialized_content)
|
let document_message_handler = serde_json::from_str::<DocumentMessageHandler>(serialized_content)
|
||||||
.or_else(|_| {
|
.or_else(|e| {
|
||||||
|
log::warn!("failed to directly load document with the following error: {e}. Trying old DocumentMessageHandler");
|
||||||
// TODO: Eventually remove this document upgrade code
|
// TODO: Eventually remove this document upgrade code
|
||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct OldDocumentMessageHandler {
|
pub struct OldDocumentMessageHandler {
|
||||||
@@ -2481,7 +2511,7 @@ impl DocumentMessageHandler {
|
|||||||
.icon("Grid")
|
.icon("Grid")
|
||||||
.tooltip("Grid")
|
.tooltip("Grid")
|
||||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleGridVisibility))
|
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleGridVisibility))
|
||||||
.on_update(|optional_input: &CheckboxInput| DocumentMessage::GridVisibility(optional_input.checked).into())
|
.on_update(|optional_input: &CheckboxInput| DocumentMessage::GridVisibility { visible: optional_input.checked }.into())
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
PopoverButton::new()
|
PopoverButton::new()
|
||||||
.popover_layout(overlay_options(&self.snapping_state.grid))
|
.popover_layout(overlay_options(&self.snapping_state.grid))
|
||||||
@@ -2982,10 +3012,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| {
|
||||||
@@ -2996,7 +3026,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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3019,11 +3049,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);
|
||||||
@@ -3072,7 +3102,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;
|
||||||
|
|||||||
@@ -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`.
|
||||||
@@ -91,6 +91,32 @@ pub fn get_current_normalized_pivot(inputs: &[NodeInput]) -> DVec2 {
|
|||||||
if let Some(&TaggedValue::DVec2(pivot)) = inputs[5].as_value() { pivot } else { DVec2::splat(0.5) }
|
if let Some(&TaggedValue::DVec2(pivot)) = inputs[5].as_value() { pivot } else { DVec2::splat(0.5) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Expand a bounds to avoid div zero errors
|
||||||
|
fn clamp_bounds(bounds_min: DVec2, mut bounds_max: DVec2) -> [DVec2; 2] {
|
||||||
|
let bounds_size = bounds_max - bounds_min;
|
||||||
|
if bounds_size.x < 1e-10 {
|
||||||
|
bounds_max.x = bounds_min.x + 1.;
|
||||||
|
}
|
||||||
|
if bounds_size.y < 1e-10 {
|
||||||
|
bounds_max.y = bounds_min.y + 1.;
|
||||||
|
}
|
||||||
|
[bounds_min, bounds_max]
|
||||||
|
}
|
||||||
|
/// Returns corners of all subpaths
|
||||||
|
fn subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {
|
||||||
|
subpaths
|
||||||
|
.iter()
|
||||||
|
.filter_map(|subpath| subpath.bounding_box())
|
||||||
|
.reduce(|b1, b2| [b1[0].min(b2[0]), b1[1].max(b2[1])])
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns corners of all subpaths (but expanded to avoid division-by-zero errors)
|
||||||
|
pub fn nonzero_subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {
|
||||||
|
let [bounds_min, bounds_max] = subpath_bounds(subpaths);
|
||||||
|
clamp_bounds(bounds_min, bounds_max)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -138,29 +164,3 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expand a bounds to avoid div zero errors
|
|
||||||
fn clamp_bounds(bounds_min: DVec2, mut bounds_max: DVec2) -> [DVec2; 2] {
|
|
||||||
let bounds_size = bounds_max - bounds_min;
|
|
||||||
if bounds_size.x < 1e-10 {
|
|
||||||
bounds_max.x = bounds_min.x + 1.;
|
|
||||||
}
|
|
||||||
if bounds_size.y < 1e-10 {
|
|
||||||
bounds_max.y = bounds_min.y + 1.;
|
|
||||||
}
|
|
||||||
[bounds_min, bounds_max]
|
|
||||||
}
|
|
||||||
/// Returns corners of all subpaths
|
|
||||||
fn subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {
|
|
||||||
subpaths
|
|
||||||
.iter()
|
|
||||||
.filter_map(|subpath| subpath.bounding_box())
|
|
||||||
.reduce(|b1, b2| [b1[0].min(b2[0]), b1[1].max(b2[1])])
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns corners of all subpaths (but expanded to avoid division-by-zero errors)
|
|
||||||
pub fn nonzero_subpath_bounds(subpaths: &[Subpath<PointId>]) -> [DVec2; 2] {
|
|
||||||
let [bounds_min, bounds_max] = subpath_bounds(subpaths);
|
|
||||||
clamp_bounds(bounds_min, bounds_max)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
|||||||
let transformed_delta = document_to_viewport.inverse().transform_vector2(delta);
|
let transformed_delta = document_to_viewport.inverse().transform_vector2(delta);
|
||||||
|
|
||||||
ptz.pan += transformed_delta;
|
ptz.pan += transformed_delta;
|
||||||
responses.add(BroadcastEvent::CanvasTransformed);
|
responses.add(EventMessage::CanvasTransformed);
|
||||||
responses.add(DocumentMessage::PTZUpdate);
|
responses.add(DocumentMessage::PTZUpdate);
|
||||||
}
|
}
|
||||||
NavigationMessage::CanvasPanAbortPrepare { x_not_y_axis } => {
|
NavigationMessage::CanvasPanAbortPrepare { x_not_y_axis } => {
|
||||||
@@ -286,7 +286,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
|||||||
ptz.flip = !ptz.flip;
|
ptz.flip = !ptz.flip;
|
||||||
|
|
||||||
responses.add(DocumentMessage::PTZUpdate);
|
responses.add(DocumentMessage::PTZUpdate);
|
||||||
responses.add(BroadcastEvent::CanvasTransformed);
|
responses.add(EventMessage::CanvasTransformed);
|
||||||
responses.add(MenuBarMessage::SendLayout);
|
responses.add(MenuBarMessage::SendLayout);
|
||||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||||
}
|
}
|
||||||
@@ -325,7 +325,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
|||||||
self.navigation_operation = NavigationOperation::None;
|
self.navigation_operation = NavigationOperation::None;
|
||||||
|
|
||||||
// Send the final messages to close out the operation
|
// Send the final messages to close out the operation
|
||||||
responses.add(BroadcastEvent::CanvasTransformed);
|
responses.add(EventMessage::CanvasTransformed);
|
||||||
responses.add(ToolMessage::UpdateCursor);
|
responses.add(ToolMessage::UpdateCursor);
|
||||||
responses.add(ToolMessage::UpdateHints);
|
responses.add(ToolMessage::UpdateHints);
|
||||||
responses.add(NavigateToolMessage::End);
|
responses.add(NavigateToolMessage::End);
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ impl NodePropertiesContext<'_> {
|
|||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
widget_override_lambda(*node_id, index, self)
|
widget_override_lambda(*node_id, index, self)
|
||||||
.map_err(|error| log::error!("Error in widget override lambda: {}", error))
|
.map_err(|error| log::error!("Error in widget override lambda: {error}"))
|
||||||
.ok()
|
.ok()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
@@ -111,7 +111,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||||
inputs: vec![NodeInput::value(TaggedValue::None, true)],
|
inputs: vec![NodeInput::value(TaggedValue::None, true)],
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
skip_deduplication: true,
|
skip_deduplication: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -151,19 +151,19 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::network(generic!(T), 0)],
|
inputs: vec![NodeInput::network(generic!(T), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -233,33 +233,33 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::network(generic!(T), 0)],
|
inputs: vec![NodeInput::network(generic!(T), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::to_graphic::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(graphic::to_graphic::IDENTIFIER),
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// Secondary (left) input type coercion
|
// Secondary (left) input type coercion
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::network(generic!(T), 1)],
|
inputs: vec![NodeInput::network(generic!(T), 1)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::wrap_graphic::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(graphic::wrap_graphic::IDENTIFIER),
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// Store the ID of the parent node (which encapsulates this sub-network) in each row we are extending the table with.
|
// Store the ID of the parent node (which encapsulates this sub-network) in each row we are extending the table with.
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
|
inputs: vec![NodeInput::node(NodeId(1), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER),
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// The monitor node is used to display a thumbnail in the UI
|
// The monitor node is used to display a thumbnail in the UI
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
skip_deduplication: true,
|
skip_deduplication: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(3), 0)],
|
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(3), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(graphic::extend::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -349,7 +349,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
nodes: [
|
nodes: [
|
||||||
// Ensure this ID is kept in sync with the ID in set_alias so that the name input is kept in sync with the alias
|
// Ensure this ID is kept in sync with the ID in set_alias so that the name input is kept in sync with the alias
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(artboard::create_artboard::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(artboard::create_artboard::IDENTIFIER),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::network(concrete!(TaggedValue), 1),
|
NodeInput::network(concrete!(TaggedValue), 1),
|
||||||
@@ -365,7 +365,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
|
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(graphic::source_node_id::IDENTIFIER),
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
// The monitor node is used to display a thumbnail in the UI.
|
// The monitor node is used to display a thumbnail in the UI.
|
||||||
@@ -373,12 +373,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
skip_deduplication: true,
|
skip_deduplication: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::network(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(Table<Artboard>))), 0),
|
NodeInput::network(graphene_std::Type::Fn(Box::new(concrete!(Context)), Box::new(concrete!(Table<Artboard>))), 0),
|
||||||
NodeInput::node(NodeId(2), 0),
|
NodeInput::node(NodeId(2), 0),
|
||||||
@@ -495,13 +495,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
nodes: [
|
nodes: [
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)],
|
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)],
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::load_resource::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::load_resource::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::decode_image::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::decode_image::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -568,7 +568,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -630,20 +630,20 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::scope("editor-api")],
|
inputs: vec![NodeInput::scope("editor-api")],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER),
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
skip_deduplication: true,
|
skip_deduplication: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::network(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)],
|
inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::network(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::rasterize::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::rasterize::IDENTIFIER),
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -716,7 +716,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
category: "Raster: Pattern",
|
category: "Raster: Pattern",
|
||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::std_nodes::noise_pattern::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::std_nodes::noise_pattern::IDENTIFIER),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::value(TaggedValue::None, false),
|
NodeInput::value(TaggedValue::None, false),
|
||||||
@@ -783,7 +783,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false),
|
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false),
|
||||||
],
|
],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
@@ -792,7 +792,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false),
|
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false),
|
||||||
],
|
],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
@@ -801,7 +801,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false),
|
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false),
|
||||||
],
|
],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
@@ -810,7 +810,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false),
|
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false),
|
||||||
],
|
],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -888,13 +888,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
|
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
|
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -962,7 +962,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
NodeInput::network(concrete!(Vec<brush::brush_stroke::BrushStroke>), 1),
|
NodeInput::network(concrete!(Vec<brush::brush_stroke::BrushStroke>), 1),
|
||||||
NodeInput::network(concrete!(BrushCache), 2),
|
NodeInput::network(concrete!(BrushCache), 2),
|
||||||
],
|
],
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(brush::brush::brush::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(brush::brush::brush::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}]
|
}]
|
||||||
@@ -1013,7 +1013,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||||
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
|
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
@@ -1032,7 +1032,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
|
||||||
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
|
inputs: vec![NodeInput::value(TaggedValue::Raster(Default::default()), true)],
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
@@ -1054,13 +1054,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
exports: vec![NodeInput::node(NodeId(1), 0)],
|
exports: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
nodes: [
|
nodes: [
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
inputs: vec![NodeInput::scope("editor-api")],
|
inputs: vec![NodeInput::scope("editor-api")],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::create_gpu_surface::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::create_gpu_surface::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -1126,12 +1126,12 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::node(NodeId(0), 0)],
|
inputs: vec![NodeInput::network(concrete!(Table<Raster<CPU>>), 0), NodeInput::node(NodeId(0), 0)],
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::texture_upload::upload_texture::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::texture_upload::upload_texture::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -1247,7 +1247,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0)],
|
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
skip_deduplication: true,
|
skip_deduplication: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -1257,7 +1257,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
NodeInput::network(concrete!(graphene_std::vector::VectorModification), 1),
|
NodeInput::network(concrete!(graphene_std::vector::VectorModification), 1),
|
||||||
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
|
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
|
||||||
],
|
],
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(vector::path_modify::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(vector::path_modify::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -1317,7 +1317,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(text::text::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(text::text::IDENTIFIER),
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::scope("editor-api"),
|
NodeInput::scope("editor-api"),
|
||||||
NodeInput::value(TaggedValue::String("Lorem ipsum".to_string()), false),
|
NodeInput::value(TaggedValue::String("Lorem ipsum".to_string()), false),
|
||||||
@@ -1427,7 +1427,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::network(generic!(T), 0)],
|
inputs: vec![NodeInput::network(generic!(T), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
skip_deduplication: true,
|
skip_deduplication: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -1439,7 +1439,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
NodeInput::network(concrete!(DVec2), 3),
|
NodeInput::network(concrete!(DVec2), 3),
|
||||||
NodeInput::network(concrete!(DVec2), 4),
|
NodeInput::network(concrete!(DVec2), 4),
|
||||||
],
|
],
|
||||||
manual_composition: Some(concrete!(Context)),
|
call_argument: concrete!(Context),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -1524,25 +1524,25 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
|
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -1622,7 +1622,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0)],
|
inputs: vec![NodeInput::network(concrete!(Table<Vector>), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(vector::subpath_segment_lengths::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(vector::subpath_segment_lengths::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
@@ -1637,25 +1637,25 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
NodeInput::node(NodeId(0), 0),
|
NodeInput::node(NodeId(0), 0),
|
||||||
],
|
],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(vector::sample_polyline::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(vector::sample_polyline::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(3), 0)],
|
inputs: vec![NodeInput::node(NodeId(3), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -1791,26 +1791,26 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
NodeInput::network(concrete!(f64), 1),
|
NodeInput::network(concrete!(f64), 1),
|
||||||
NodeInput::network(concrete!(u32), 2),
|
NodeInput::network(concrete!(u32), 2),
|
||||||
],
|
],
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(vector::poisson_disk_points::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(vector::poisson_disk_points::IDENTIFIER),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
inputs: vec![NodeInput::node(NodeId(0), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
inputs: vec![NodeInput::node(NodeId(2), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
call_argument: generic!(T),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -1944,10 +1944,10 @@ fn static_input_properties() -> InputProperties {
|
|||||||
"string".to_string(),
|
"string".to_string(),
|
||||||
Box::new(|node_id, index, context| {
|
Box::new(|node_id, index, context| {
|
||||||
let Some(value) = context.network_interface.input_data(&node_id, index, "string_properties", context.selection_network_path) else {
|
let Some(value) = context.network_interface.input_data(&node_id, index, "string_properties", context.selection_network_path) else {
|
||||||
return Err(format!("Could not get string properties for node {}", node_id));
|
return Err(format!("Could not get string properties for node {node_id}"));
|
||||||
};
|
};
|
||||||
let Some(string) = value.as_str() else {
|
let Some(string) = value.as_str() else {
|
||||||
return Err(format!("Could not downcast string properties for node {}", node_id));
|
return Err(format!("Could not downcast string properties for node {node_id}"));
|
||||||
};
|
};
|
||||||
Ok(node_properties::string_properties(string))
|
Ok(node_properties::string_properties(string))
|
||||||
}),
|
}),
|
||||||
|
|||||||
+2
-2
@@ -59,8 +59,8 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
|
|||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
inputs,
|
inputs,
|
||||||
manual_composition: Some(input_type.clone()),
|
call_argument: (input_type.clone()),
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(id.clone().into()),
|
implementation: DocumentNodeImplementation::ProtoNode(id.clone()),
|
||||||
visible: true,
|
visible: true,
|
||||||
skip_deduplication: false,
|
skip_deduplication: false,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
NodeGraphMessage::AddPathNode => {
|
NodeGraphMessage::AddPathNode => {
|
||||||
if let Some(layer) = make_path_editable_is_allowed(network_interface, network_interface.document_metadata()) {
|
if let Some(layer) = make_path_editable_is_allowed(network_interface, network_interface.document_metadata()) {
|
||||||
responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction { node_type: "Path".to_string(), layer });
|
responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction { node_type: "Path".to_string(), layer });
|
||||||
responses.add(BroadcastEvent::SelectionChanged);
|
responses.add(EventMessage::SelectionChanged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NodeGraphMessage::AddImport => {
|
NodeGraphMessage::AddImport => {
|
||||||
@@ -142,7 +142,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
}
|
}
|
||||||
NodeGraphMessage::Init => {
|
NodeGraphMessage::Init => {
|
||||||
responses.add(BroadcastMessage::SubscribeEvent {
|
responses.add(BroadcastMessage::SubscribeEvent {
|
||||||
on: BroadcastEvent::SelectionChanged,
|
on: EventMessage::SelectionChanged,
|
||||||
send: Box::new(NodeGraphMessage::SelectedNodesUpdated.into()),
|
send: Box::new(NodeGraphMessage::SelectedNodesUpdated.into()),
|
||||||
});
|
});
|
||||||
network_interface.load_structure();
|
network_interface.load_structure();
|
||||||
@@ -1472,7 +1472,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
selected_nodes.add_selected_nodes(nodes);
|
selected_nodes.add_selected_nodes(nodes);
|
||||||
responses.add(BroadcastEvent::SelectionChanged);
|
responses.add(EventMessage::SelectionChanged);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::SelectedNodesRemove { nodes } => {
|
NodeGraphMessage::SelectedNodesRemove { nodes } => {
|
||||||
let Some(selected_nodes) = network_interface.selected_nodes_mut(selection_network_path) else {
|
let Some(selected_nodes) = network_interface.selected_nodes_mut(selection_network_path) else {
|
||||||
@@ -1480,7 +1480,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
selected_nodes.retain_selected_nodes(|node| !nodes.contains(node));
|
selected_nodes.retain_selected_nodes(|node| !nodes.contains(node));
|
||||||
responses.add(BroadcastEvent::SelectionChanged);
|
responses.add(EventMessage::SelectionChanged);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::SelectedNodesSet { nodes } => {
|
NodeGraphMessage::SelectedNodesSet { nodes } => {
|
||||||
let Some(selected_nodes) = network_interface.selected_nodes_mut(selection_network_path) else {
|
let Some(selected_nodes) = network_interface.selected_nodes_mut(selection_network_path) else {
|
||||||
@@ -1488,7 +1488,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
selected_nodes.set_selected_nodes(nodes);
|
selected_nodes.set_selected_nodes(nodes);
|
||||||
responses.add(BroadcastEvent::SelectionChanged);
|
responses.add(EventMessage::SelectionChanged);
|
||||||
}
|
}
|
||||||
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)),
|
||||||
@@ -1516,7 +1516,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
let mut nodes = Vec::new();
|
let mut nodes = Vec::new();
|
||||||
for node_id in &self.frontend_nodes {
|
for node_id in &self.frontend_nodes {
|
||||||
let Some(node_bbox) = network_interface.node_bounding_box(node_id, breadcrumb_network_path) else {
|
let Some(node_bbox) = network_interface.node_bounding_box(node_id, breadcrumb_network_path) else {
|
||||||
log::error!("Could not get bbox for node: {:?}", node_id);
|
log::error!("Could not get bbox for node: {node_id:?}");
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1680,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);
|
||||||
@@ -1720,7 +1721,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
}
|
}
|
||||||
NodeGraphMessage::ToggleLocked { node_id } => {
|
NodeGraphMessage::ToggleLocked { node_id } => {
|
||||||
let Some(node_metadata) = network_interface.document_network_metadata().persistent_metadata.node_metadata.get(&node_id) else {
|
let Some(node_metadata) = network_interface.document_network_metadata().persistent_metadata.node_metadata.get(&node_id) else {
|
||||||
log::error!("Cannot get node {:?} in NodeGraphMessage::ToggleLocked", node_id);
|
log::error!("Cannot get node {node_id:?} in NodeGraphMessage::ToggleLocked");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1836,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1887,7 +1888,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
selected_nodes.clear_selected_nodes();
|
selected_nodes.clear_selected_nodes();
|
||||||
responses.add(BroadcastEvent::SelectionChanged);
|
responses.add(EventMessage::SelectionChanged);
|
||||||
|
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
}
|
}
|
||||||
@@ -2485,7 +2486,7 @@ impl NodeGraphMessageHandler {
|
|||||||
data_type: frontend_data_type,
|
data_type: frontend_data_type,
|
||||||
name: "Output 1".to_string(),
|
name: "Output 1".to_string(),
|
||||||
description: String::new(),
|
description: String::new(),
|
||||||
resolved_type: format!("{:?}", output_type),
|
resolved_type: format!("{output_type:?}"),
|
||||||
connected_to,
|
connected_to,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -2517,7 +2518,7 @@ impl NodeGraphMessageHandler {
|
|||||||
data_type,
|
data_type,
|
||||||
name: output_name,
|
name: output_name,
|
||||||
description: String::new(),
|
description: String::new(),
|
||||||
resolved_type: format!("{:?}", output_type),
|
resolved_type: format!("{output_type:?}"),
|
||||||
connected_to,
|
connected_to,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,9 +147,9 @@ pub(crate) fn property_from_type(
|
|||||||
Type::Concrete(concrete_type) => {
|
Type::Concrete(concrete_type) => {
|
||||||
match concrete_type.alias.as_ref().map(|x| x.as_ref()) {
|
match concrete_type.alias.as_ref().map(|x| x.as_ref()) {
|
||||||
// Aliased types (ambiguous values)
|
// Aliased types (ambiguous values)
|
||||||
Some("Percentage") => number_widget(default_info, number_input.percentage().min(min(0.)).max(max(100.))).into(),
|
Some("Percentage") | Some("PercentageF32") => number_widget(default_info, number_input.percentage().min(min(0.)).max(max(100.))).into(),
|
||||||
Some("SignedPercentage") => number_widget(default_info, number_input.percentage().min(min(-100.)).max(max(100.))).into(),
|
Some("SignedPercentage") | Some("SignedPercentageF32") => number_widget(default_info, number_input.percentage().min(min(-100.)).max(max(100.))).into(),
|
||||||
Some("Angle") => number_widget(default_info, number_input.mode_range().min(min(-180.)).max(max(180.)).unit(unit.unwrap_or("°"))).into(),
|
Some("Angle") | Some("AngleF32") => number_widget(default_info, number_input.mode_range().min(min(-180.)).max(max(180.)).unit(unit.unwrap_or("°"))).into(),
|
||||||
Some("Multiplier") => number_widget(default_info, number_input.unit(unit.unwrap_or("x"))).into(),
|
Some("Multiplier") => number_widget(default_info, number_input.unit(unit.unwrap_or("x"))).into(),
|
||||||
Some("PixelLength") => number_widget(default_info, number_input.min(min(0.)).unit(unit.unwrap_or(" px"))).into(),
|
Some("PixelLength") => number_widget(default_info, number_input.min(min(0.)).unit(unit.unwrap_or(" px"))).into(),
|
||||||
Some("Length") => number_widget(default_info, number_input.min(min(0.))).into(),
|
Some("Length") => number_widget(default_info, number_input.min(min(0.))).into(),
|
||||||
@@ -166,7 +166,7 @@ pub(crate) fn property_from_type(
|
|||||||
// ===============
|
// ===============
|
||||||
// PRIMITIVE TYPES
|
// PRIMITIVE TYPES
|
||||||
// ===============
|
// ===============
|
||||||
Some(x) if x == TypeId::of::<f64>() => number_widget(default_info, number_input.min(min(f64::NEG_INFINITY)).max(max(f64::INFINITY))).into(),
|
Some(x) if x == TypeId::of::<f64>() || x == TypeId::of::<f32>() => number_widget(default_info, number_input.min(min(f64::NEG_INFINITY)).max(max(f64::INFINITY))).into(),
|
||||||
Some(x) if x == TypeId::of::<u32>() => number_widget(default_info, number_input.int().min(min(0.)).max(max(f64::from(u32::MAX)))).into(),
|
Some(x) if x == TypeId::of::<u32>() => number_widget(default_info, number_input.int().min(min(0.)).max(max(f64::from(u32::MAX)))).into(),
|
||||||
Some(x) if x == TypeId::of::<u64>() => number_widget(default_info, number_input.int().min(min(0.))).into(),
|
Some(x) if x == TypeId::of::<u64>() => number_widget(default_info, number_input.int().min(min(0.))).into(),
|
||||||
Some(x) if x == TypeId::of::<bool>() => bool_widget(default_info, CheckboxInput::default()).into(),
|
Some(x) if x == TypeId::of::<bool>() => bool_widget(default_info, CheckboxInput::default()).into(),
|
||||||
@@ -805,6 +805,14 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props:
|
|||||||
.on_commit(commit_value)
|
.on_commit(commit_value)
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
]),
|
]),
|
||||||
|
Some(&TaggedValue::F32(x)) => widgets.extend_from_slice(&[
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
|
number_props
|
||||||
|
.value(Some(x as f64))
|
||||||
|
.on_update(update_value(move |x: &NumberInput| TaggedValue::F32(x.value.unwrap() as f32), node_id, index))
|
||||||
|
.on_commit(commit_value)
|
||||||
|
.widget_holder(),
|
||||||
|
]),
|
||||||
Some(&TaggedValue::U32(x)) => widgets.extend_from_slice(&[
|
Some(&TaggedValue::U32(x)) => widgets.extend_from_slice(&[
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
number_props
|
number_props
|
||||||
@@ -851,6 +859,15 @@ pub fn number_widget(parameter_widgets_info: ParameterWidgetsInfo, number_props:
|
|||||||
.on_commit(commit_value)
|
.on_commit(commit_value)
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
]),
|
]),
|
||||||
|
Some(&TaggedValue::FVec2(vec2)) => widgets.extend_from_slice(&[
|
||||||
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
|
number_props
|
||||||
|
// We use an arbitrary `y` instead of an arbitrary `x` here because the "Grid" node's "Spacing" value's height should be used from rectangular mode when transferred to "Y Spacing" in isometric mode
|
||||||
|
.value(Some(vec2.y as f64))
|
||||||
|
.on_update(update_value(move |x: &NumberInput| TaggedValue::F32(x.value.unwrap() as f32), node_id, index))
|
||||||
|
.on_commit(commit_value)
|
||||||
|
.widget_holder(),
|
||||||
|
]),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ impl FrontendGraphDataType {
|
|||||||
match TaggedValue::from_type_or_none(input) {
|
match TaggedValue::from_type_or_none(input) {
|
||||||
TaggedValue::U32(_)
|
TaggedValue::U32(_)
|
||||||
| TaggedValue::U64(_)
|
| TaggedValue::U64(_)
|
||||||
|
| TaggedValue::F32(_)
|
||||||
| TaggedValue::F64(_)
|
| TaggedValue::F64(_)
|
||||||
| TaggedValue::DVec2(_)
|
| TaggedValue::DVec2(_)
|
||||||
| TaggedValue::F64Array4(_)
|
| TaggedValue::F64Array4(_)
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
|
|||||||
move |input: &I| {
|
move |input: &I| {
|
||||||
let mut grid = grid.clone();
|
let mut grid = grid.clone();
|
||||||
update(&mut grid, input);
|
update(&mut grid, input);
|
||||||
DocumentMessage::GridOptions(grid).into()
|
DocumentMessage::GridOptions { options: grid }.into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let update_origin = |grid, update: fn(&mut GridSnapping) -> Option<&mut f64>| {
|
let update_origin = |grid, update: fn(&mut GridSnapping) -> Option<&mut f64>| {
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ use crate::messages::prelude::*;
|
|||||||
pub enum OverlaysMessage {
|
pub enum OverlaysMessage {
|
||||||
Draw,
|
Draw,
|
||||||
// Serde functionality isn't used but is required by the message system macros
|
// Serde functionality isn't used but is required by the message system macros
|
||||||
AddProvider(
|
AddProvider {
|
||||||
#[serde(skip, default = "empty_provider")]
|
#[serde(skip, default = "empty_provider")]
|
||||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||||
OverlayProvider,
|
provider: OverlayProvider,
|
||||||
),
|
},
|
||||||
RemoveProvider(
|
RemoveProvider {
|
||||||
#[serde(skip, default = "empty_provider")]
|
#[serde(skip, default = "empty_provider")]
|
||||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||||
OverlayProvider,
|
provider: OverlayProvider,
|
||||||
),
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,12 +56,14 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
|
|||||||
let _ = canvas_context.reset_transform();
|
let _ = canvas_context.reset_transform();
|
||||||
|
|
||||||
if visibility_settings.all() {
|
if visibility_settings.all() {
|
||||||
responses.add(DocumentMessage::GridOverlays(OverlayContext {
|
responses.add(DocumentMessage::GridOverlays {
|
||||||
render_context: canvas_context.clone(),
|
context: OverlayContext {
|
||||||
size: size.as_dvec2(),
|
render_context: canvas_context.clone(),
|
||||||
device_pixel_ratio,
|
size: size.as_dvec2(),
|
||||||
visibility_settings: visibility_settings.clone(),
|
device_pixel_ratio,
|
||||||
}));
|
visibility_settings: visibility_settings.clone(),
|
||||||
|
},
|
||||||
|
});
|
||||||
for provider in &self.overlay_providers {
|
for provider in &self.overlay_providers {
|
||||||
responses.add(provider(OverlayContext {
|
responses.add(provider(OverlayContext {
|
||||||
render_context: canvas_context.clone(),
|
render_context: canvas_context.clone(),
|
||||||
@@ -81,22 +83,22 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
|
|||||||
let overlay_context = OverlayContext::new(size, device_pixel_ratio, visibility_settings);
|
let overlay_context = OverlayContext::new(size, device_pixel_ratio, visibility_settings);
|
||||||
|
|
||||||
if visibility_settings.all() {
|
if visibility_settings.all() {
|
||||||
responses.add(DocumentMessage::GridOverlays(overlay_context.clone()));
|
responses.add(DocumentMessage::GridOverlays { context: overlay_context.clone() });
|
||||||
|
|
||||||
for provider in &self.overlay_providers {
|
for provider in &self.overlay_providers {
|
||||||
responses.add(provider(overlay_context.clone()));
|
responses.add(provider(overlay_context.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
responses.add(FrontendMessage::RenderOverlays(overlay_context));
|
responses.add(FrontendMessage::RenderOverlays { context: overlay_context });
|
||||||
}
|
}
|
||||||
#[cfg(all(not(target_family = "wasm"), test))]
|
#[cfg(all(not(target_family = "wasm"), test))]
|
||||||
OverlaysMessage::Draw => {
|
OverlaysMessage::Draw => {
|
||||||
let _ = (responses, visibility_settings, ipp, device_pixel_ratio);
|
let _ = (responses, visibility_settings, ipp, device_pixel_ratio);
|
||||||
}
|
}
|
||||||
OverlaysMessage::AddProvider(message) => {
|
OverlaysMessage::AddProvider { provider: message } => {
|
||||||
self.overlay_providers.insert(message);
|
self.overlay_providers.insert(message);
|
||||||
}
|
}
|
||||||
OverlaysMessage::RemoveProvider(message) => {
|
OverlaysMessage::RemoveProvider { provider: message } => {
|
||||||
self.overlay_providers.remove(&message);
|
self.overlay_providers.remove(&message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user