openfree commited on
Commit
2409829
·
verified ·
1 Parent(s): 4199003

Deploy from GitHub repository

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .cargo/config.toml +12 -0
  2. .devcontainer/devcontainer.json +32 -0
  3. .editorconfig +7 -0
  4. .envrc +1 -0
  5. .gitattributes +9 -0
  6. .nix/flake.lock +99 -0
  7. .nix/flake.nix +115 -0
  8. .nvmrc +1 -0
  9. .prettierrc +18 -0
  10. .vscode/extensions.json +20 -0
  11. .vscode/launch.json +48 -0
  12. .vscode/settings.json +56 -0
  13. Cargo.lock +0 -0
  14. Cargo.toml +179 -0
  15. LICENSE.txt +201 -0
  16. README.md +14 -4
  17. about.hbs +27 -0
  18. about.toml +36 -0
  19. app.py +29 -0
  20. demo-artwork/changing-seasons.graphite +0 -0
  21. demo-artwork/isometric-fountain.graphite +0 -0
  22. demo-artwork/marbled-mandelbrot.graphite +1 -0
  23. demo-artwork/painted-dreams.graphite +0 -0
  24. demo-artwork/parametric-dunescape.graphite +0 -0
  25. demo-artwork/procedural-string-lights.graphite +0 -0
  26. demo-artwork/red-dress.graphite +0 -0
  27. demo-artwork/valley-of-spires.graphite +0 -0
  28. deny.toml +194 -0
  29. editor/Cargo.toml +71 -0
  30. editor/build.rs +24 -0
  31. editor/src/application.rs +57 -0
  32. editor/src/consts.rs +151 -0
  33. editor/src/dispatcher.rs +562 -0
  34. editor/src/generate_ts_types.rs +34 -0
  35. editor/src/lib.rs +17 -0
  36. editor/src/macros.rs +56 -0
  37. editor/src/messages/animation/animation_message.rs +17 -0
  38. editor/src/messages/animation/animation_message_handler.rs +133 -0
  39. editor/src/messages/animation/mod.rs +9 -0
  40. editor/src/messages/broadcast/broadcast_event.rs +12 -0
  41. editor/src/messages/broadcast/broadcast_message.rs +19 -0
  42. editor/src/messages/broadcast/broadcast_message_handler.rs +27 -0
  43. editor/src/messages/broadcast/mod.rs +9 -0
  44. editor/src/messages/debug/debug_message.rs +10 -0
  45. editor/src/messages/debug/debug_message_handler.rs +49 -0
  46. editor/src/messages/debug/mod.rs +9 -0
  47. editor/src/messages/debug/utility_types.rs +7 -0
  48. editor/src/messages/dialog/dialog_message.rs +38 -0
  49. editor/src/messages/dialog/dialog_message_handler.rs +118 -0
  50. editor/src/messages/dialog/export_dialog/export_dialog_message.rs +13 -0
.cargo/config.toml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [target.wasm32-unknown-unknown]
2
+ rustflags = [
3
+ # Currently disabled because of https://github.com/GraphiteEditor/Graphite/issues/1262
4
+ # The current simd implementation leads to undefined behavior
5
+ #"-C",
6
+ #"target-feature=+simd128",
7
+ "-C",
8
+ "target-feature=+bulk-memory",
9
+ "-C",
10
+ "link-arg=--max-memory=4294967296",
11
+ "--cfg=web_sys_unstable_apis",
12
+ ]
.devcontainer/devcontainer.json ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "image": "mcr.microsoft.com/devcontainers/base:debian",
3
+ "features": {
4
+ "ghcr.io/devcontainers/features/rust:1": {
5
+ "profile": "default"
6
+ },
7
+ "ghcr.io/devcontainers/features/node:1": {}
8
+ },
9
+ "onCreateCommand": "cargo install cargo-watch wasm-pack cargo-about && cargo install -f [email protected]",
10
+ "customizations": {
11
+ "vscode": {
12
+ // NOTE: Keep this in sync with `.vscode/extensions.json`
13
+ "extensions": [
14
+ // Rust
15
+ "rust-lang.rust-analyzer",
16
+ "tamasfe.even-better-toml",
17
+ // Web
18
+ "dbaeumer.vscode-eslint",
19
+ "svelte.svelte-vscode",
20
+ "vitaliymaz.vscode-svg-previewer",
21
+ // Code quality
22
+ "wayou.vscode-todo-highlight",
23
+ "streetsidesoftware.code-spell-checker",
24
+ // Helpful
25
+ "mhutchie.git-graph",
26
+ "waderyan.gitblame",
27
+ "qezhu.gitlink",
28
+ "wmaurer.change-case"
29
+ ]
30
+ }
31
+ }
32
+ }
.editorconfig ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ [*.{rs,js,ts,svelte,json,toml,svg,html,css,scss}]
2
+ indent_style = tab
3
+ indent_size = 4
4
+ end_of_line = lf
5
+ trim_trailing_whitespace = true
6
+ insert_final_newline = true
7
+ max_line_length = 200
.envrc ADDED
@@ -0,0 +1 @@
 
 
1
+ use flake .nix
.gitattributes CHANGED
@@ -33,3 +33,12 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ frontend/src-tauri/icons/icon.icns filter=lfs diff=lfs merge=lfs -text
37
+ node-graph/graphene-cli/test_files/cat.jpg filter=lfs diff=lfs merge=lfs -text
38
+ node-graph/graphene-cli/test_files/cow_transparent.png filter=lfs diff=lfs merge=lfs -text
39
+ node-graph/graphene-cli/test_files/duck.jpg filter=lfs diff=lfs merge=lfs -text
40
+ node-graph/graphene-cli/test_files/football.jpg filter=lfs diff=lfs merge=lfs -text
41
+ node-graph/graphene-cli/test_files/mansion.jpg filter=lfs diff=lfs merge=lfs -text
42
+ node-graph/graphene-cli/test_files/paper.jpg filter=lfs diff=lfs merge=lfs -text
43
+ node-graph/graphene-cli/test_files/pizza.jpg filter=lfs diff=lfs merge=lfs -text
44
+ node-graph/graphene-cli/test_files/pizza_transparent.png filter=lfs diff=lfs merge=lfs -text
.nix/flake.lock ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "nodes": {
3
+ "flake-utils": {
4
+ "inputs": {
5
+ "systems": "systems"
6
+ },
7
+ "locked": {
8
+ "lastModified": 1731533236,
9
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
10
+ "owner": "numtide",
11
+ "repo": "flake-utils",
12
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
13
+ "type": "github"
14
+ },
15
+ "original": {
16
+ "owner": "numtide",
17
+ "repo": "flake-utils",
18
+ "type": "github"
19
+ }
20
+ },
21
+ "nixpkgs": {
22
+ "locked": {
23
+ "lastModified": 1748190013,
24
+ "narHash": "sha256-R5HJFflOfsP5FBtk+zE8FpL8uqE7n62jqOsADvVshhE=",
25
+ "owner": "nixos",
26
+ "repo": "nixpkgs",
27
+ "rev": "62b852f6c6742134ade1abdd2a21685fd617a291",
28
+ "type": "github"
29
+ },
30
+ "original": {
31
+ "owner": "nixos",
32
+ "ref": "nixos-unstable",
33
+ "repo": "nixpkgs",
34
+ "type": "github"
35
+ }
36
+ },
37
+ "nixpkgs-unstable": {
38
+ "locked": {
39
+ "lastModified": 1748190013,
40
+ "narHash": "sha256-R5HJFflOfsP5FBtk+zE8FpL8uqE7n62jqOsADvVshhE=",
41
+ "owner": "nixos",
42
+ "repo": "nixpkgs",
43
+ "rev": "62b852f6c6742134ade1abdd2a21685fd617a291",
44
+ "type": "github"
45
+ },
46
+ "original": {
47
+ "owner": "nixos",
48
+ "ref": "nixos-unstable",
49
+ "repo": "nixpkgs",
50
+ "type": "github"
51
+ }
52
+ },
53
+ "root": {
54
+ "inputs": {
55
+ "flake-utils": "flake-utils",
56
+ "nixpkgs": "nixpkgs",
57
+ "nixpkgs-unstable": "nixpkgs-unstable",
58
+ "rust-overlay": "rust-overlay"
59
+ }
60
+ },
61
+ "rust-overlay": {
62
+ "inputs": {
63
+ "nixpkgs": [
64
+ "nixpkgs"
65
+ ]
66
+ },
67
+ "locked": {
68
+ "lastModified": 1748399823,
69
+ "narHash": "sha256-kahD8D5hOXOsGbNdoLLnqCL887cjHkx98Izc37nDjlA=",
70
+ "owner": "oxalica",
71
+ "repo": "rust-overlay",
72
+ "rev": "d68a69dc71bc19beb3479800392112c2f6218159",
73
+ "type": "github"
74
+ },
75
+ "original": {
76
+ "owner": "oxalica",
77
+ "repo": "rust-overlay",
78
+ "type": "github"
79
+ }
80
+ },
81
+ "systems": {
82
+ "locked": {
83
+ "lastModified": 1681028828,
84
+ "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
85
+ "owner": "nix-systems",
86
+ "repo": "default",
87
+ "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
88
+ "type": "github"
89
+ },
90
+ "original": {
91
+ "owner": "nix-systems",
92
+ "repo": "default",
93
+ "type": "github"
94
+ }
95
+ }
96
+ },
97
+ "root": "root",
98
+ "version": 7
99
+ }
.nix/flake.nix ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This is a helper file for people using NixOS as their operating system.
2
+ # If you don't know what this file does, you can safely ignore it.
3
+ # This file defines both the development environment for the project.
4
+ #
5
+ # Development Environment:
6
+ # - Provides all necessary tools for Rust/WASM development
7
+ # - Includes Tauri dependencies for desktop app development
8
+ # - Sets up profiling and debugging tools
9
+ # - Configures mold as the default linker for faster builds
10
+ #
11
+ #
12
+ # Usage:
13
+ # - Development shell: `nix develop`
14
+ # - Run in dev shell with direnv: add `use flake` to .envrc
15
+ {
16
+ description = "Development environment and build configuration";
17
+
18
+ inputs = {
19
+ # This url should be changed to match your system packages if you work on tauri because you need to use the same graphics library versions as the ones used by your system
20
+ nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
21
+ nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
22
+ rust-overlay = {
23
+ url = "github:oxalica/rust-overlay";
24
+ inputs.nixpkgs.follows = "nixpkgs";
25
+ };
26
+ flake-utils.url = "github:numtide/flake-utils";
27
+ };
28
+
29
+ outputs = { nixpkgs, nixpkgs-unstable, rust-overlay, flake-utils, ... }:
30
+ flake-utils.lib.eachDefaultSystem (system:
31
+ let
32
+ overlays = [ (import rust-overlay) ];
33
+ pkgs = import nixpkgs {
34
+ inherit system overlays;
35
+ };
36
+ pkgs-unstable = import nixpkgs-unstable {
37
+ inherit system overlays;
38
+ };
39
+
40
+ rustc-wasm = pkgs.rust-bin.stable.latest.default.override {
41
+ targets = [ "wasm32-unknown-unknown" ];
42
+ extensions = [ "rust-src" "rust-analyzer" "clippy" "cargo" ];
43
+ };
44
+
45
+ # Shared build inputs - system libraries that need to be in LD_LIBRARY_PATH
46
+ buildInputs = with pkgs; [
47
+ # System libraries
48
+ openssl
49
+ vulkan-loader
50
+ mesa
51
+ libraw
52
+
53
+
54
+ # Tauri dependencies: keep in sync with https://v2.tauri.app/start/prerequisites/#system-dependencies (under the NixOS tab)
55
+ at-spi2-atk
56
+ atkmm
57
+ cairo
58
+ gdk-pixbuf
59
+ glib
60
+ gtk3
61
+ harfbuzz
62
+ librsvg
63
+ libsoup_3
64
+ pango
65
+ webkitgtk_4_1
66
+ openssl
67
+ ];
68
+
69
+ # Development tools that don't need to be in LD_LIBRARY_PATH
70
+ buildTools = [
71
+ rustc-wasm
72
+ pkgs.nodejs
73
+ pkgs.nodePackages.npm
74
+ pkgs.binaryen
75
+ pkgs.wasm-bindgen-cli
76
+ pkgs-unstable.wasm-pack
77
+ pkgs.pkg-config
78
+ pkgs.git
79
+ pkgs.gobject-introspection
80
+ pkgs-unstable.cargo-tauri
81
+ pkgs-unstable.cargo-about
82
+
83
+ # Linker
84
+ pkgs.mold
85
+ ];
86
+ # Development tools that don't need to be in LD_LIBRARY_PATH
87
+ devTools = with pkgs; [
88
+ cargo-watch
89
+ cargo-nextest
90
+ cargo-expand
91
+
92
+ # Profiling tools
93
+ gnuplot
94
+ samply
95
+ cargo-flamegraph
96
+
97
+ ];
98
+ in
99
+ {
100
+ # Development shell configuration
101
+ devShells.default = pkgs.mkShell {
102
+ packages = buildInputs ++ buildTools ++ devTools;
103
+
104
+ LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
105
+ GIO_MODULE_DIR="${pkgs.glib-networking}/lib/gio/modules/";
106
+ XDG_DATA_DIRS="${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}:${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}:$XDG_DATA_DIRS";
107
+
108
+
109
+ shellHook = ''
110
+ alias cargo='mold --run cargo'
111
+ '';
112
+ };
113
+ }
114
+ );
115
+ }
.nvmrc ADDED
@@ -0,0 +1 @@
 
 
1
+ 20
.prettierrc ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "singleQuote": false,
3
+ "useTabs": true,
4
+ "tabWidth": 4,
5
+ "printWidth": 200,
6
+ "overrides": [
7
+ {
8
+ "files": [
9
+ "*.yml",
10
+ "*.yaml"
11
+ ],
12
+ "options": {
13
+ "useTabs": false,
14
+ "tabWidth": 2
15
+ }
16
+ }
17
+ ]
18
+ }
.vscode/extensions.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ // NOTE: Keep this in sync with `.devcontainer/devcontainer.json`
3
+ "recommendations": [
4
+ // Rust
5
+ "rust-lang.rust-analyzer",
6
+ "tamasfe.even-better-toml",
7
+ // Web
8
+ "dbaeumer.vscode-eslint",
9
+ "svelte.svelte-vscode",
10
+ "vitaliymaz.vscode-svg-previewer",
11
+ // Code quality
12
+ "wayou.vscode-todo-highlight",
13
+ "streetsidesoftware.code-spell-checker",
14
+ // Helpful
15
+ "mhutchie.git-graph",
16
+ "waderyan.gitblame",
17
+ "qezhu.gitlink",
18
+ "wmaurer.change-case"
19
+ ]
20
+ }
.vscode/launch.json ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ // Use IntelliSense to learn about possible attributes.
3
+ // Hover to view descriptions of existing attributes.
4
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5
+ "version": "0.2.0",
6
+ "configurations": [
7
+ {
8
+ "type": "lldb",
9
+ "request": "launch",
10
+ "name": "Graphite debug executable",
11
+ "cargo": {
12
+ "args": [
13
+ "build",
14
+ "--bin=graphite",
15
+ "--package=graphite",
16
+ ],
17
+ "filter": {
18
+ "name": "graphite",
19
+ "kind": "bin",
20
+ },
21
+ },
22
+ "args": [],
23
+ "cwd": "${workspaceFolder}",
24
+ "env": {
25
+ "RUST_LOG": "error",
26
+ },
27
+ },
28
+ {
29
+ "type": "lldb",
30
+ "request": "launch",
31
+ "name": "Debug unit tests in executable 'graphite'",
32
+ "cargo": {
33
+ "args": [
34
+ "test",
35
+ "--no-run",
36
+ "--bin=graphite",
37
+ "--package=graphite",
38
+ ],
39
+ "filter": {
40
+ "name": "graphite",
41
+ "kind": "bin",
42
+ },
43
+ },
44
+ "args": [],
45
+ "cwd": "${workspaceFolder}",
46
+ },
47
+ ],
48
+ }
.vscode/settings.json ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ // Rust: save on format
3
+ "[rust]": {
4
+ "editor.formatOnSave": true,
5
+ "editor.formatOnPaste": true,
6
+ "editor.defaultFormatter": "rust-lang.rust-analyzer"
7
+ },
8
+ // Web: save on format
9
+ "[javascript][typescript][svelte]": {
10
+ "editor.codeActionsOnSave": {
11
+ "source.fixAll.eslint": "explicit"
12
+ },
13
+ "editor.formatOnSave": true,
14
+ "editor.defaultFormatter": "dbaeumer.vscode-eslint"
15
+ },
16
+ "[scss]": {
17
+ "editor.codeActionsOnSave": {
18
+ "source.fixAll.eslint": "explicit"
19
+ },
20
+ "editor.formatOnSave": true,
21
+ // Configured in `.prettierrc`
22
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
23
+ },
24
+ "[json][jsonc][yaml][github-actions-workflow]": {
25
+ "editor.formatOnSave": true,
26
+ // Configured in `.prettierrc`
27
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
28
+ },
29
+ // Handlebars: don't save on format
30
+ // (`about.hbs` is used by Cargo About to encode license information)
31
+ "[handlebars]": {
32
+ "editor.formatOnSave": false
33
+ },
34
+ // Rust Analyzer config
35
+ "rust-analyzer.cargo.allTargets": false,
36
+ // ESLint config
37
+ "eslint.format.enable": true,
38
+ "eslint.workingDirectories": ["./frontend", "./website/other/bezier-rs-demos", "./website"],
39
+ "eslint.validate": ["javascript", "typescript", "svelte"],
40
+ // Svelte config
41
+ "svelte.plugin.svelte.compilerWarnings": {
42
+ // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
43
+ "css-unused-selector": "ignore",
44
+ "vite-plugin-svelte-css-no-scopable-elements": "ignore",
45
+ "a11y-no-static-element-interactions": "ignore",
46
+ "a11y-no-noninteractive-element-interactions": "ignore",
47
+ "a11y-click-events-have-key-events": "ignore"
48
+ },
49
+ // VS Code config
50
+ "html.format.wrapLineLength": 200,
51
+ "files.eol": "\n",
52
+ "files.insertFinalNewline": true,
53
+ "files.associations": {
54
+ "*.graphite": "json"
55
+ }
56
+ }
Cargo.lock ADDED
The diff for this file is too large to render. See raw diff
 
Cargo.toml ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [workspace]
2
+ members = [
3
+ "editor",
4
+ "proc-macros",
5
+ "frontend/wasm",
6
+ "frontend/src-tauri",
7
+ "node-graph/gapplication-io",
8
+ "node-graph/gbrush",
9
+ "node-graph/gcore",
10
+ "node-graph/gstd",
11
+ "node-graph/gmath-nodes",
12
+ "node-graph/gpath-bool",
13
+ "node-graph/graph-craft",
14
+ "node-graph/graphene-cli",
15
+ "node-graph/graster-nodes",
16
+ "node-graph/gsvg-renderer",
17
+ "node-graph/interpreted-executor",
18
+ "node-graph/node-macro",
19
+ "node-graph/preprocessor",
20
+ "libraries/dyn-any",
21
+ "libraries/path-bool",
22
+ "libraries/bezier-rs",
23
+ "libraries/math-parser",
24
+ "website/other/bezier-rs-demos/wasm",
25
+ ]
26
+ default-members = [
27
+ "editor",
28
+ "frontend/wasm",
29
+ "node-graph/gbrush",
30
+ "node-graph/gcore",
31
+ "node-graph/gstd",
32
+ "node-graph/gmath-nodes",
33
+ "node-graph/gpath-bool",
34
+ "node-graph/graph-craft",
35
+ "node-graph/graphene-cli",
36
+ "node-graph/graster-nodes",
37
+ "node-graph/gsvg-renderer",
38
+ "node-graph/interpreted-executor",
39
+ "node-graph/node-macro",
40
+ ]
41
+ resolver = "2"
42
+
43
+ [workspace.dependencies]
44
+ # Local dependencies
45
+ bezier-rs = { path = "libraries/bezier-rs", features = ["dyn-any", "serde"] }
46
+ dyn-any = { path = "libraries/dyn-any", features = ["derive", "glam", "reqwest", "log-bad-types", "rc"] }
47
+ preprocessor = { path = "node-graph/preprocessor"}
48
+ math-parser = { path = "libraries/math-parser" }
49
+ path-bool = { path = "libraries/path-bool" }
50
+ graphene-application-io = { path = "node-graph/gapplication-io" }
51
+ graphene-brush = { path = "node-graph/gbrush" }
52
+ graphene-core = { path = "node-graph/gcore" }
53
+ graphene-math-nodes = { path = "node-graph/gmath-nodes" }
54
+ graphene-path-bool = { path = "node-graph/gpath-bool" }
55
+ graph-craft = { path = "node-graph/graph-craft" }
56
+ graphene-raster-nodes = { path = "node-graph/graster-nodes" }
57
+ graphene-std = { path = "node-graph/gstd" }
58
+ graphene-svg-renderer = { path = "node-graph/gsvg-renderer" }
59
+ interpreted-executor = { path = "node-graph/interpreted-executor" }
60
+ node-macro = { path = "node-graph/node-macro" }
61
+ wgpu-executor = { path = "node-graph/wgpu-executor" }
62
+ graphite-proc-macros = { path = "proc-macros" }
63
+
64
+ # Workspace dependencies
65
+ rustc-hash = "2.0"
66
+ bytemuck = { version = "1.13", features = ["derive"] }
67
+ serde = { version = "1.0", features = ["derive", "rc"] }
68
+ serde_json = "1.0"
69
+ serde-wasm-bindgen = "0.6"
70
+ reqwest = { version = "0.12", features = ["blocking", "rustls-tls", "json"] }
71
+ futures = "0.3"
72
+ env_logger = "0.11"
73
+ log = "0.4"
74
+ bitflags = { version = "2.4", features = ["serde"] }
75
+ ctor = "0.2"
76
+ convert_case = "0.7"
77
+ derivative = "2.2"
78
+ thiserror = "2"
79
+ anyhow = "1.0"
80
+ proc-macro2 = { version = "1", features = [ "span-locations" ] }
81
+ quote = "1.0"
82
+ axum = "0.8"
83
+ chrono = "0.4"
84
+ ron = "0.8"
85
+ fastnoise-lite = "1.1"
86
+ wgpu = { version = "23", features = [
87
+ # We don't have wgpu on multiple threads (yet) https://github.com/gfx-rs/wgpu/blob/trunk/CHANGELOG.md#wgpu-types-now-send-sync-on-wasm
88
+ "fragile-send-sync-non-atomic-wasm",
89
+ "spirv",
90
+ "strict_asserts",
91
+ ] }
92
+ once_cell = "1.13" # Remove when `core::cell::LazyCell` (<https://doc.rust-lang.org/core/cell/struct.LazyCell.html>) is stabilized in Rust 1.80 and we bump our MSRV
93
+ wasm-bindgen = "=0.2.100" # NOTICE: ensure this stays in sync with the `wasm-bindgen-cli` version in `website/content/volunteer/guide/project-setup/_index.md`. We pin this version because wasm-bindgen upgrades may break various things.
94
+ wasm-bindgen-futures = "0.4"
95
+ js-sys = "=0.3.77"
96
+ web-sys = { version = "=0.3.77", features = [
97
+ "Document",
98
+ "DomRect",
99
+ "Element",
100
+ "HtmlCanvasElement",
101
+ "CanvasRenderingContext2d",
102
+ "CanvasPattern",
103
+ "OffscreenCanvas",
104
+ "OffscreenCanvasRenderingContext2d",
105
+ "TextMetrics",
106
+ "Window",
107
+ "IdleRequestOptions",
108
+ "ImageData",
109
+ "Navigator",
110
+ "Gpu",
111
+ "HtmlImageElement",
112
+ "ImageBitmapRenderingContext",
113
+ ] }
114
+ winit = "0.29"
115
+ url = "2.5"
116
+ tokio = { version = "1.29", features = ["fs", "macros", "io-std", "rt"] }
117
+ vello = { git = "https://github.com/linebender/vello.git", rev = "3275ec8" } # TODO switch back to stable when a release is made
118
+ resvg = "0.44"
119
+ usvg = "0.44"
120
+ rand = { version = "0.9", default-features = false, features = ["std_rng"] }
121
+ rand_chacha = "0.9"
122
+ glam = { version = "0.29", default-features = false, features = ["serde", "scalar-math", "debug-glam-assert"] }
123
+ base64 = "0.22"
124
+ image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp"] }
125
+ parley = "0.5.0"
126
+ skrifa = "0.32.0"
127
+ pretty_assertions = "1.4.1"
128
+ fern = { version = "0.7", features = ["colored"] }
129
+ num_enum = "0.7"
130
+ num-derive = "0.4"
131
+ num-traits = { version = "0.2", default-features = false, features = ["i128"] }
132
+ specta = { version = "2.0.0-rc.22", features = [
133
+ "glam",
134
+ "derive",
135
+ # "typescript",
136
+ ] }
137
+ syn = { version = "2.0", default-features = false, features = [
138
+ "full",
139
+ "derive",
140
+ "parsing",
141
+ "printing",
142
+ "visit-mut",
143
+ "visit",
144
+ "clone-impls",
145
+ "extra-traits",
146
+ "proc-macro",
147
+ ] }
148
+ kurbo = { version = "0.11.0", features = ["serde"] }
149
+ petgraph = { version = "0.7.1", default-features = false, features = [
150
+ "graphmap",
151
+ ] }
152
+ half = { version = "2.4.1", default-features = false, features = ["bytemuck", "serde"] }
153
+ tinyvec = { version = "1", features = ["std"] }
154
+ criterion = { version = "0.5", features = ["html_reports"] }
155
+ iai-callgrind = { version = "0.12.3" }
156
+ ndarray = "0.16.1"
157
+
158
+ [profile.dev]
159
+ opt-level = 1
160
+
161
+ [profile.dev.package]
162
+ graphite-editor = { opt-level = 1 }
163
+ graphene-core = { opt-level = 1 }
164
+ graphene-std = { opt-level = 1 }
165
+ interpreted-executor = { opt-level = 1 } # This is a mitigation for https://github.com/rustwasm/wasm-pack/issues/981 which is needed because the node_registry function is too large
166
+ graphite-proc-macros = { opt-level = 1 }
167
+ image = { opt-level = 2 }
168
+ rustc-hash = { opt-level = 3 }
169
+ serde_derive = { opt-level = 1 }
170
+ specta-macros = { opt-level = 1 }
171
+ syn = { opt-level = 1 }
172
+
173
+ [profile.release]
174
+ lto = "thin"
175
+ debug = true
176
+
177
+ [profile.profiling]
178
+ inherits = "release"
179
+ debug = true
LICENSE.txt ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
README.md CHANGED
@@ -1,12 +1,22 @@
1
  ---
2
  title: Graphite2
3
- emoji: 📉
4
- colorFrom: pink
5
- colorTo: yellow
6
  sdk: gradio
7
- sdk_version: 5.35.0
8
  app_file: app.py
9
  pinned: false
10
  ---
11
 
 
 
 
 
 
 
 
 
 
 
12
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
  title: Graphite2
3
+ emoji: 🚀
4
+ colorFrom: blue
5
+ colorTo: green
6
  sdk: gradio
7
+ sdk_version: "5.35.0"
8
  app_file: app.py
9
  pinned: false
10
  ---
11
 
12
+ # Graphite2
13
+
14
+ <a href="https://graphite.rs/">
15
+
16
+ Deployed from: https://github.com/seawolf2357/Graphite
17
+
18
+ ## Features
19
+ This Space provides a Gradio interface for the repository's main functionality.
20
+ The app.py was automatically generated based on repository analysis.
21
+
22
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
about.hbs ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {{!
2
+ Be careful to prevent auto-formatting from breaking this file's indentation.
3
+ Replace this file with JSON output once this is resolved: https://github.com/EmbarkStudios/cargo-about/issues/73
4
+
5
+ The `GENERATED_BY_CARGO_ABOUT` prefix is a JS labeled statement
6
+ (<https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/label>)
7
+ used so the reader of the generated file can verify the file does indeed start with that string,
8
+ while remaining valid JS for subsequent parsing.
9
+ }}
10
+ GENERATED_BY_CARGO_ABOUT: [
11
+ {{#each licenses}}
12
+ {
13
+ licenseName: `{{name}}`,
14
+ licenseText: `{{text}}`,
15
+ packages: [
16
+ {{#each used_by}}
17
+ {
18
+ name: `{{crate.name}}`,
19
+ version: `{{crate.version}}`,
20
+ author: `{{crate.authors}}`,
21
+ repository: `{{crate.repository}}`,
22
+ },
23
+ {{/each}}
24
+ ],
25
+ },
26
+ {{/each}}
27
+ ]
about.toml ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Keep this list in sync with those in `/deny.toml` and `/frontend/vite.config.ts`.
2
+ accepted = [
3
+ "Apache-2.0 WITH LLVM-exception",
4
+ "Apache-2.0",
5
+ "BSD-2-Clause",
6
+ "BSD-3-Clause",
7
+ "BSL-1.0",
8
+ "CC0-1.0",
9
+ "ISC",
10
+ "MIT-0",
11
+ "MIT",
12
+ "MPL-2.0",
13
+ "OpenSSL",
14
+ "Unicode-3.0",
15
+ "Unicode-DFS-2016",
16
+ "Zlib",
17
+ ]
18
+ workarounds = ["ring"]
19
+ ignore-build-dependencies = true
20
+ ignore-dev-dependencies = true
21
+ # Clearly Defined's API would occasionally (every few months) return errors for at least a full day (maybe some weird rate limiting?), but we can just disable to perform local checking (see #1653)
22
+ no-clearly-defined = true
23
+
24
+ # https://raw.githubusercontent.com/briansmith/webpki/main/LICENSE
25
+ # is the ISC license but test code within the repo is BSD-3-Clause, but is not compiled into the crate when we use it
26
+ [webpki.clarify]
27
+ license = "ISC"
28
+ [[webpki.clarify.files]]
29
+ path = "LICENSE"
30
+ checksum = "5b698ca13897be3afdb7174256fa1574f8c6892b8bea1a66dd6469d3fe27885a"
31
+
32
+ [rustls-webpki.clarify]
33
+ license = "ISC"
34
+ [[rustls-webpki.clarify.files]]
35
+ path = "LICENSE"
36
+ checksum = "5b698ca13897be3afdb7174256fa1574f8c6892b8bea1a66dd6469d3fe27885a"
app.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+
3
+ # Function to simulate the Graphite tool functionality
4
+
5
+ def create_graphics_tool(api_key, description):
6
+ if not api_key:
7
+ return "Error: API key is required. Please enter a valid API key."
8
+ if not description:
9
+ return "Error: Description is required. Please provide a description for the graphics tool."
10
+ # Simulate some processing (this would be replaced with actual functionality)
11
+ return f"Graphics tool created with description: '{description}'!"
12
+
13
+ # Gradio interface setup
14
+
15
+ def main():
16
+ with gr.Blocks() as demo:
17
+ gr.Markdown("# Graphite Graphics Tool\n\nCreate your own graphics tool using Graphite!\n\n## Instructions:\n1. Enter your API key (required).\n2. Provide a description for your graphics tool (required).\n3. Click 'Create Tool' to simulate the creation of a graphics tool.")
18
+
19
+ api_key = gr.Textbox(label="API Key", placeholder="Enter your API key here...")
20
+ description = gr.Textbox(label="Tool Description", placeholder="Describe your graphics tool...")
21
+ create_button = gr.Button("Create Tool")
22
+ output = gr.Textbox(label="Output")
23
+
24
+ create_button.click(create_graphics_tool, inputs=[api_key, description], outputs=output)
25
+
26
+ demo.launch()
27
+
28
+ if __name__ == '__main__':
29
+ main()
demo-artwork/changing-seasons.graphite ADDED
The diff for this file is too large to render. See raw diff
 
demo-artwork/isometric-fountain.graphite ADDED
The diff for this file is too large to render. See raw diff
 
demo-artwork/marbled-mandelbrot.graphite ADDED
@@ -0,0 +1 @@
 
 
1
+ {"network_interface":{"network":{"exports":[{"Node":{"node_id":12241147352993594415,"output_index":0,"lambda":false}}],"nodes":[[4388711862172196665,{"inputs":[],"manual_composition":{"Concrete":{"name":"core::option::Option<alloc::sync::Arc<graphene_core::context::OwnedContextImpl>>","alias":null}},"implementation":{"ProtoNode":{"name":"graphene_std::raster::MandelbrotNode"}},"visible":true,"skip_deduplication":false}],[3606681156406984991,{"inputs":[{"Node":{"node_id":6323350524796370485,"output_index":0,"lambda":false}},{"Value":{"tagged_value":{"GradientStops":[[0.3237704918032787,{"red":1.0,"green":0.0,"blue":1.0,"alpha":0.1}],[0.5655737704918032,{"red":0.8901961,"green":0.8117647,"blue":0.39215687,"alpha":1.0}],[0.8155737704918032,{"red":0.0,"green":1.0,"blue":1.0,"alpha":0.5}]]},"exposed":false}},{"Value":{"tagged_value":{"Bool":true},"exposed":false}}],"manual_composition":{"Concrete":{"name":"core::option::Option<alloc::sync::Arc<graphene_core::context::OwnedContextImpl>>","alias":null}},"implementation":{"ProtoNode":{"name":"graphene_core::raster::adjustments::GradientMapNode"}},"visible":true,"skip_deduplication":false}],[6029481207635803402,{"inputs":[{"Node":{"node_id":4388711862172196665,"output_index":0,"lambda":false}},{"Value":{"tagged_value":{"DVec2":[0.0,0.0]},"exposed":false}},{"Value":{"tagged_value":{"F64":0.0},"exposed":false}},{"Value":{"tagged_value":{"DVec2":[1000.0,1000.0]},"exposed":false}},{"Value":{"tagged_value":{"DVec2":[0.0,0.0]},"exposed":false}},{"Value":{"tagged_value":{"DVec2":[0.5,0.5]},"exposed":false}}],"manual_composition":null,"implementation":{"Network":{"exports":[{"Node":{"node_id":1,"output_index":0,"lambda":false}}],"nodes":[[0,{"inputs":[{"Network":{"import_type":{"Concrete":{"name":"graphene_core::instances::Instances<graphene_core::vector::vector_data::VectorData>","alias":null}},"import_index":0}}],"manual_composition":{"Generic":"T"},"implementation":{"ProtoNode":{"name":"graphene_core::memo::MonitorNode"}},"visible":true,"skip_deduplication":true}],[1,{"inputs":[{"Node":{"node_id":0,"output_index":0,"lambda":false}},{"Network":{"import_type":{"Concrete":{"name":"glam::f64::dvec2::DVec2","alias":null}},"import_index":1}},{"Network":{"import_type":{"Concrete":{"name":"f64","alias":null}},"import_index":2}},{"Network":{"import_type":{"Concrete":{"name":"glam::f64::dvec2::DVec2","alias":null}},"import_index":3}},{"Network":{"import_type":{"Concrete":{"name":"glam::f64::dvec2::DVec2","alias":null}},"import_index":4}},{"Network":{"import_type":{"Concrete":{"name":"glam::f64::dvec2::DVec2","alias":null}},"import_index":5}}],"manual_composition":{"Generic":"T"},"implementation":{"ProtoNode":{"name":"graphene_core::transform_nodes::TransformNode"}},"visible":true,"skip_deduplication":false}]],"scope_injections":[]}},"visible":true,"skip_deduplication":false}],[7624113397561636853,{"inputs":[{"Value":{"tagged_value":{"GraphicGroup":{"instance":[],"transform":[],"alpha_blending":[],"source_node_id":[]}},"exposed":true}},{"Node":{"node_id":3606681156406984991,"output_index":0,"lambda":false}}],"manual_composition":null,"implementation":{"Network":{"exports":[{"Node":{"node_id":3,"output_index":0,"lambda":false}}],"nodes":[[0,{"inputs":[{"Network":{"import_type":{"Generic":"T"},"import_index":1}}],"manual_composition":{"Generic":"T"},"implementation":{"ProtoNode":{"name":"graphene_core::graphic_element::ToElementNode"}},"visible":true,"skip_deduplication":false}],[3,{"inputs":[{"Node":{"node_id":1,"output_index":0,"lambda":false}},{"Node":{"node_id":2,"output_index":0,"lambda":false}},{"Reflection":"DocumentNodePath"}],"manual_composition":{"Generic":"T"},"implementation":{"ProtoNode":{"name":"graphene_core::graphic_element::LayerNode"}},"visible":true,"skip_deduplication":false}],[2,{"inputs":[{"Node":{"node_id":0,"output_index":0,"lambda":false}}],"manual_composition":{"Generic":"T"},"implementation":{"ProtoNode":{"name":"graphene_core::memo::MonitorNode"}},"visible":true,"skip_deduplication":true}],[1,{"inputs":[{"Network":{"import_type":{"Generic":"T"},"import_index":0}}],"manual_composition":{"Generic":"T"},"implementation":{"ProtoNode":{"name":"graphene_core::graphic_element::ToGroupNode"}},"visible":true,"skip_deduplication":false}]],"scope_injections":[]}},"visible":true,"skip_deduplication":false}],[80924370013313595,{"inputs":[{"Node":{"node_id":6029481207635803402,"output_index":0,"lambda":false}},{"Value":{"tagged_value":{"GradientStops":[[0.0,{"red":0.19607843,"green":0.0,"blue":0.21176471,"alpha":1.0}],[0.12704918032786883,{"red":0.0627451,"green":0.19215687,"blue":0.39607844,"alpha":1.0}],[0.5,{"red":0.8901961,"green":0.8117647,"blue":0.39215687,"alpha":1.0}],[1.0,{"red":0.58431375,"green":0.92941177,"blue":0.92941177,"alpha":0.01}]]},"exposed":false}},{"Value":{"tagged_value":{"Bool":false},"exposed":false}}],"manual_composition":{"Concrete":{"name":"core::option::Option<alloc::sync::Arc<graphene_core::context::OwnedContextImpl>>","alias":null}},"implementation":{"ProtoNode":{"name":"graphene_core::raster::adjustments::GradientMapNode"}},"visible":true,"skip_deduplication":false}],[6323350524796370485,{"inputs":[{"Value":{"tagged_value":"None","exposed":false}},{"Value":{"tagged_value":{"Bool":false},"exposed":false}},{"Value":{"tagged_value":{"U32":0},"exposed":false}},{"Value":{"tagged_value":{"F64":35.0},"exposed":false}},{"Value":{"tagged_value":{"NoiseType":"OpenSimplex2"},"exposed":false}},{"Value":{"tagged_value":{"DomainWarpType":"OpenSimplex2"},"exposed":false}},{"Value":{"tagged_value":{"F64":100.0},"exposed":false}},{"Value":{"tagged_value":{"FractalType":"PingPong"},"exposed":false}},{"Value":{"tagged_value":{"U32":3},"exposed":false}},{"Value":{"tagged_value":{"F64":2.0},"exposed":false}},{"Value":{"tagged_value":{"F64":0.5},"exposed":false}},{"Value":{"tagged_value":{"F64":0.0},"exposed":false}},{"Value":{"tagged_value":{"F64":2.0},"exposed":false}},{"Value":{"tagged_value":{"CellularDistanceFunction":"Hybrid"},"exposed":false}},{"Value":{"tagged_value":{"CellularReturnType":"CellValue"},"exposed":false}},{"Value":{"tagged_value":{"F64":1.0},"exposed":false}}],"manual_composition":{"Concrete":{"name":"core::option::Option<alloc::sync::Arc<graphene_core::context::OwnedContextImpl>>","alias":null}},"implementation":{"ProtoNode":{"name":"graphene_std::raster::NoisePatternNode"}},"visible":true,"skip_deduplication":false}],[6565638614909771142,{"inputs":[{"Node":{"node_id":7624113397561636853,"output_index":0,"lambda":false}},{"Node":{"node_id":80924370013313595,"output_index":0,"lambda":false}}],"manual_composition":null,"implementation":{"Network":{"exports":[{"Node":{"node_id":3,"output_index":0,"lambda":false}}],"nodes":[[0,{"inputs":[{"Network":{"import_type":{"Generic":"T"},"import_index":1}}],"manual_composition":{"Generic":"T"},"implementation":{"ProtoNode":{"name":"graphene_core::graphic_element::ToElementNode"}},"visible":true,"skip_deduplication":false}],[3,{"inputs":[{"Node":{"node_id":1,"output_index":0,"lambda":false}},{"Node":{"node_id":2,"output_index":0,"lambda":false}},{"Reflection":"DocumentNodePath"}],"manual_composition":{"Generic":"T"},"implementation":{"ProtoNode":{"name":"graphene_core::graphic_element::LayerNode"}},"visible":true,"skip_deduplication":false}],[2,{"inputs":[{"Node":{"node_id":0,"output_index":0,"lambda":false}}],"manual_composition":{"Generic":"T"},"implementation":{"ProtoNode":{"name":"graphene_core::memo::MonitorNode"}},"visible":true,"skip_deduplication":true}],[1,{"inputs":[{"Network":{"import_type":{"Generic":"T"},"import_index":0}}],"manual_composition":{"Generic":"T"},"implementation":{"ProtoNode":{"name":"graphene_core::graphic_element::ToGroupNode"}},"visible":true,"skip_deduplication":false}]],"scope_injections":[]}},"visible":true,"skip_deduplication":false}],[12241147352993594415,{"inputs":[{"Value":{"tagged_value":{"ArtboardGroup":{"instance":[],"transform":[],"alpha_blending":[],"source_node_id":[]}},"exposed":true}},{"Node":{"node_id":6565638614909771142,"output_index":0,"lambda":false}},{"Value":{"tagged_value":{"IVec2":[0,0]},"exposed":false}},{"Value":{"tagged_value":{"IVec2":[1000,1000]},"exposed":false}},{"Value":{"tagged_value":{"Color":{"red":0.0,"green":0.0,"blue":0.0,"alpha":1.0}},"exposed":false}},{"Value":{"tagged_value":{"Bool":true},"exposed":false}}],"manual_composition":null,"implementation":{"Network":{"exports":[{"Node":{"node_id":2,"output_index":0,"lambda":false}}],"nodes":[[0,{"inputs":[{"Network":{"import_type":{"Concrete":{"name":"graph_craft::document::value::TaggedValue","alias":null}},"import_index":1}},{"Value":{"tagged_value":{"String":"Artboard"},"exposed":false}},{"Network":{"import_type":{"Concrete":{"name":"graph_craft::document::value::TaggedValue","alias":null}},"import_index":2}},{"Network":{"import_type":{"Concrete":{"name":"graph_craft::document::value::TaggedValue","alias":null}},"import_index":3}},{"Network":{"import_type":{"Concrete":{"name":"graph_craft::document::value::TaggedValue","alias":null}},"import_index":4}},{"Network":{"import_type":{"Concrete":{"name":"graph_craft::document::value::TaggedValue","alias":null}},"import_index":5}}],"manual_composition":{"Generic":"T"},"implementation":{"ProtoNode":{"name":"graphene_core::graphic_element::ToArtboardNode"}},"visible":true,"skip_deduplication":false}],[2,{"inputs":[{"Network":{"import_type":{"Fn":[{"Concrete":{"name":"core::option::Option<alloc::sync::Arc<graphene_core::context::OwnedContextImpl>>","alias":null}},{"Concrete":{"name":"graphene_core::instances::Instances<graphene_core::graphic_element::Artboard>","alias":null}}]},"import_index":0}},{"Node":{"node_id":1,"output_index":0,"lambda":false}},{"Reflection":"DocumentNodePath"}],"manual_composition":{"Concrete":{"name":"core::option::Option<alloc::sync::Arc<graphene_core::context::OwnedContextImpl>>","alias":null}},"implementation":{"ProtoNode":{"name":"graphene_core::graphic_element::AppendArtboardNode"}},"visible":true,"skip_deduplication":false}],[1,{"inputs":[{"Node":{"node_id":0,"output_index":0,"lambda":false}}],"manual_composition":{"Generic":"T"},"implementation":{"ProtoNode":{"name":"graphene_core::memo::MonitorNode"}},"visible":true,"skip_deduplication":true}]],"scope_injections":[]}},"visible":true,"skip_deduplication":false}]],"scope_injections":[]},"network_metadata":{"persistent_metadata":{"node_metadata":[[6029481207635803402,{"persistent_metadata":{"reference":"Transform","display_name":"","input_properties":[{"input_data":{"input_name":"Vector Data"},"widget_override":null},{"input_data":{"x":"X","input_name":"Translation","unit":" px","y":"Y"},"widget_override":"vec2"},{"input_data":{"input_name":"Rotation"},"widget_override":"transform_rotation"},{"input_data":{"x":"W","unit":"x","y":"H","input_name":"Scale"},"widget_override":"vec2"},{"input_data":{"input_name":"Skew"},"widget_override":"hidden"},{"input_data":{"input_name":"Pivot"},"widget_override":"hidden"}],"output_names":["Data"],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":"Chain"}},"network_metadata":{"persistent_metadata":{"node_metadata":[[0,{"persistent_metadata":{"reference":null,"display_name":"Monitor","input_properties":[{"input_data":{"input_name":""},"widget_override":null}],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[0,0]}}},"network_metadata":null}}],[1,{"persistent_metadata":{"reference":null,"display_name":"Transform","input_properties":[{"input_data":{"input_name":""},"widget_override":null},{"input_data":{"input_name":""},"widget_override":null},{"input_data":{"input_name":""},"widget_override":null},{"input_data":{"input_name":""},"widget_override":null},{"input_data":{"input_name":""},"widget_override":null},{"input_data":{"input_name":""},"widget_override":null}],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[0,0]}}},"network_metadata":null}}]],"previewing":"No","navigation_metadata":{"node_graph_ptz":{"pan":[0.0,0.0],"tilt":0.0,"zoom":1.0,"flip":false},"node_graph_to_viewport":[1.0,0.0,0.0,1.0,0.0,0.0],"node_graph_top_right":[0.0,0.0]},"selection_undo_history":[],"selection_redo_history":[]}}}}],[7624113397561636853,{"persistent_metadata":{"reference":"Merge","display_name":"Swirly Noise","input_properties":[{"input_data":{"input_name":"Graphical Data"},"widget_override":null},{"input_data":{"input_name":"Over"},"widget_override":null}],"output_names":["Out"],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Layer":{"position":{"Stack":0}}},"network_metadata":{"persistent_metadata":{"node_metadata":[[2,{"persistent_metadata":{"reference":null,"display_name":"Monitor","input_properties":[{"input_data":{"input_name":""},"widget_override":null}],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[-7,-1]}}},"network_metadata":null}}],[0,{"persistent_metadata":{"reference":null,"display_name":"To Element","input_properties":[{"input_data":{"input_name":""},"widget_override":null}],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[-14,-1]}}},"network_metadata":null}}],[3,{"persistent_metadata":{"reference":null,"display_name":"Layer","input_properties":[{"input_data":{"input_name":""},"widget_override":null},{"input_data":{"input_name":""},"widget_override":null},{"input_data":{"input_name":""},"widget_override":null}],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[1,-3]}}},"network_metadata":null}}],[1,{"persistent_metadata":{"reference":null,"display_name":"To Group","input_properties":[{"input_data":{"input_name":""},"widget_override":null}],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[-14,-3]}}},"network_metadata":null}}]],"previewing":"No","navigation_metadata":{"node_graph_ptz":{"pan":[0.0,0.0],"tilt":0.0,"zoom":1.0,"flip":false},"node_graph_to_viewport":[1.0,0.0,0.0,1.0,0.0,0.0],"node_graph_top_right":[0.0,0.0]},"selection_undo_history":[],"selection_redo_history":[]}}}}],[4388711862172196665,{"persistent_metadata":{"reference":"Mandelbrot","display_name":"","input_properties":[],"output_names":["Raster"],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":"Chain"}},"network_metadata":null}}],[6323350524796370485,{"persistent_metadata":{"reference":"Noise Pattern","display_name":"","input_properties":[{"input_data":{"input_name":"Spacer"},"widget_override":null},{"input_data":{"input_name":"Clip"},"widget_override":null},{"input_data":{"input_name":"Seed"},"widget_override":null},{"input_data":{"input_name":"Scale"},"widget_override":"noise_properties_scale"},{"input_data":{"input_name":"Noise Type"},"widget_override":"noise_properties_noise_type"},{"input_data":{"input_name":"Domain Warp Type"},"widget_override":"noise_properties_domain_warp_type"},{"input_data":{"input_name":"Domain Warp Amplitude"},"widget_override":"noise_properties_domain_warp_amplitude"},{"input_data":{"input_name":"Fractal Type"},"widget_override":"noise_properties_fractal_type"},{"input_data":{"input_name":"Fractal Octaves"},"widget_override":"noise_properties_fractal_octaves"},{"input_data":{"input_name":"Fractal Lacunarity"},"widget_override":"noise_properties_fractal_lacunarity"},{"input_data":{"input_name":"Fractal Gain"},"widget_override":"noise_properties_fractal_gain"},{"input_data":{"input_name":"Fractal Weighted Strength"},"widget_override":"noise_properties_fractal_weighted_strength"},{"input_data":{"input_name":"Fractal Ping Pong Strength"},"widget_override":"noise_properties_ping_pong_strength"},{"input_data":{"input_name":"Cellular Distance Function"},"widget_override":"noise_properties_cellular_distance_function"},{"input_data":{"input_name":"Cellular Return Type"},"widget_override":"noise_properties_cellular_return_type"},{"input_data":{"input_name":"Cellular Jitter"},"widget_override":"noise_properties_cellular_jitter"}],"output_names":["Image"],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":"Chain"}},"network_metadata":{"persistent_metadata":{"node_metadata":[[0,{"persistent_metadata":{"reference":null,"display_name":"Noise Pattern","input_properties":[],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[0,0]}}},"network_metadata":null}}],[1,{"persistent_metadata":{"reference":null,"display_name":"Cull","input_properties":[],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[0,0]}}},"network_metadata":null}}]],"previewing":"No","navigation_metadata":{"node_graph_ptz":{"pan":[0.0,0.0],"tilt":0.0,"zoom":1.0,"flip":false},"node_graph_to_viewport":[1.0,0.0,0.0,1.0,0.0,0.0],"node_graph_top_right":[0.0,0.0]},"selection_undo_history":[],"selection_redo_history":[]}}}}],[12241147352993594415,{"persistent_metadata":{"reference":"Artboard","display_name":"","input_properties":[{"input_data":{"input_name":"Artboards"},"widget_override":null},{"input_data":{"input_name":"Contents"},"widget_override":"hidden"},{"input_data":{"input_name":"Location","x":"X","y":"Y","unit":" px"},"widget_override":"vec2"},{"input_data":{"input_name":"Dimensions","unit":" px","x":"W","y":"H"},"widget_override":"vec2"},{"input_data":{"input_name":"Background"},"widget_override":"artboard_background"},{"input_data":{"input_name":"Clip"},"widget_override":null}],"output_names":["Out"],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Layer":{"position":{"Absolute":[-8,3]}}},"network_metadata":{"persistent_metadata":{"node_metadata":[[1,{"persistent_metadata":{"reference":null,"display_name":"Monitor","input_properties":[{"input_data":{"input_name":""},"widget_override":null}],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[-2,-3]}}},"network_metadata":null}}],[0,{"persistent_metadata":{"reference":null,"display_name":"To Artboard","input_properties":[{"input_data":{"input_name":""},"widget_override":null},{"input_data":{"input_name":""},"widget_override":null},{"input_data":{"input_name":""},"widget_override":null},{"input_data":{"input_name":""},"widget_override":null},{"input_data":{"input_name":""},"widget_override":null},{"input_data":{"input_name":""},"widget_override":null}],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[-10,-3]}}},"network_metadata":null}}],[2,{"persistent_metadata":{"reference":null,"display_name":"Append Artboards","input_properties":[{"input_data":{"input_name":""},"widget_override":null},{"input_data":{"input_name":""},"widget_override":null},{"input_data":{"input_name":""},"widget_override":null}],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[6,-4]}}},"network_metadata":null}}]],"previewing":"No","navigation_metadata":{"node_graph_ptz":{"pan":[0.0,0.0],"tilt":0.0,"zoom":1.0,"flip":false},"node_graph_to_viewport":[1.0,0.0,0.0,1.0,0.0,0.0],"node_graph_top_right":[0.0,0.0]},"selection_undo_history":[],"selection_redo_history":[]}}}}],[6565638614909771142,{"persistent_metadata":{"reference":"Merge","display_name":"Mandelbrot Set","input_properties":[{"input_data":{"input_name":"Graphical Data"},"widget_override":null},{"input_data":{"input_name":"Over"},"widget_override":null}],"output_names":["Out"],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Layer":{"position":{"Absolute":[-16,6]}}},"network_metadata":{"persistent_metadata":{"node_metadata":[[0,{"persistent_metadata":{"reference":null,"display_name":"To Element","input_properties":[{"input_data":{"input_name":""},"widget_override":null}],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[-14,-1]}}},"network_metadata":null}}],[2,{"persistent_metadata":{"reference":null,"display_name":"Monitor","input_properties":[{"input_data":{"input_name":""},"widget_override":null}],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[-7,-1]}}},"network_metadata":null}}],[1,{"persistent_metadata":{"reference":null,"display_name":"To Group","input_properties":[{"input_data":{"input_name":""},"widget_override":null}],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[-14,-3]}}},"network_metadata":null}}],[3,{"persistent_metadata":{"reference":null,"display_name":"Layer","input_properties":[{"input_data":{"input_name":""},"widget_override":null},{"input_data":{"input_name":""},"widget_override":null},{"input_data":{"input_name":""},"widget_override":null}],"output_names":[],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":{"Absolute":[1,-3]}}},"network_metadata":null}}]],"previewing":"No","navigation_metadata":{"node_graph_ptz":{"pan":[0.0,0.0],"tilt":0.0,"zoom":1.0,"flip":false},"node_graph_to_viewport":[1.0,0.0,0.0,1.0,0.0,0.0],"node_graph_top_right":[0.0,0.0]},"selection_undo_history":[],"selection_redo_history":[]}}}}],[3606681156406984991,{"persistent_metadata":{"reference":"Gradient Map","display_name":"","input_properties":[{"input_data":{"input_name":"Image"},"widget_override":null},{"input_data":{"input_name":"Gradient"},"widget_override":null},{"input_data":{"input_name":"Reverse"},"widget_override":null}],"output_names":["Image"],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":"Chain"}},"network_metadata":null}}],[80924370013313595,{"persistent_metadata":{"reference":"Gradient Map","display_name":"","input_properties":[{"input_data":{"input_name":"Image"},"widget_override":null},{"input_data":{"input_name":"Gradient"},"widget_override":null},{"input_data":{"input_name":"Reverse"},"widget_override":null}],"output_names":["Image"],"has_primary_output":true,"locked":false,"pinned":false,"node_type_metadata":{"Node":{"position":"Chain"}},"network_metadata":null}}]],"previewing":"No","navigation_metadata":{"node_graph_ptz":{"pan":[345.0,-187.0],"tilt":0.0,"zoom":1.0,"flip":false},"node_graph_to_viewport":[1.0,0.0,0.0,1.0,1336.0,394.0],"node_graph_top_right":[1468.796875,0.0]},"selection_undo_history":[[12241147352993594415]],"selection_redo_history":[]}}},"collapsed":[],"name":"marbled-mandelbrot.graphite","commit_hash":"f6ffa45a8180183d70a67d3e41249934a8fcacc9","document_ptz":{"pan":[-339.3903349049215,-502.62390663267854],"tilt":0.0,"zoom":4.0,"flip":false},"document_mode":"DesignMode","view_mode":"Normal","overlays_visibility_settings":{"all":true,"artboard_name":true,"compass_rose":true,"quick_measurement":true,"transform_measurement":true,"transform_cage":true,"hover_outline":true,"selection_outline":true,"pivot":true,"path":true,"anchors":true,"handles":true},"rulers_visible":true,"snapping_state":{"snapping_enabled":true,"grid_snapping":false,"artboards":true,"tolerance":8.0,"bounding_box":{"center_point":true,"corner_point":true,"edge_midpoint":true,"align_with_edges":true,"distribute_evenly":true},"path":{"anchor_point":true,"line_midpoint":true,"along_path":true,"normal_to_path":true,"tangent_to_path":true,"path_intersection_point":true,"align_with_anchor_point":true,"perpendicular_from_endpoint":true},"grid":{"origin":[0.0,0.0],"grid_type":{"Rectangular":{"spacing":[1.0,1.0]}},"rectangular_spacing":[1.0,1.0],"isometric_y_spacing":1.0,"isometric_angle_a":30.0,"isometric_angle_b":30.0,"grid_color":{"red":0.6038274,"green":0.6038274,"blue":0.6038274,"alpha":1.0},"dot_display":false}},"graph_view_overlay_open":false,"graph_fade_artwork_percentage":80.0}
demo-artwork/painted-dreams.graphite ADDED
The diff for this file is too large to render. See raw diff
 
demo-artwork/parametric-dunescape.graphite ADDED
The diff for this file is too large to render. See raw diff
 
demo-artwork/procedural-string-lights.graphite ADDED
The diff for this file is too large to render. See raw diff
 
demo-artwork/red-dress.graphite ADDED
The diff for this file is too large to render. See raw diff
 
demo-artwork/valley-of-spires.graphite ADDED
The diff for this file is too large to render. See raw diff
 
deny.toml ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This template contains all of the possible sections and their default values
2
+
3
+ # Note that all fields that take a lint level have these possible values:
4
+ # * deny - An error will be produced and the check will fail
5
+ # * warn - A warning will be produced, but the check will not fail
6
+ # * allow - No warning or error will be produced, though in some cases a note
7
+ # will be
8
+
9
+ # The values provided in this template are the default values that will be used
10
+ # when any section or field is not specified in your own configuration
11
+
12
+ [graph]
13
+ # If 1 or more target triples (and optionally, target_features) are specified,
14
+ # only the specified targets will be checked when running `cargo deny check`.
15
+ # This means, if a particular package is only ever used as a target specific
16
+ # dependency, such as, for example, the `nix` crate only being used via the
17
+ # `target_family = "unix"` configuration, that only having windows targets in
18
+ # this list would mean the nix crate, as well as any of its exclusive
19
+ # dependencies not shared by any other crates, would be ignored, as the target
20
+ # list here is effectively saying which targets you are building for.
21
+ targets = [
22
+ # The triple can be any string, but only the target triples built in to
23
+ # rustc (as of 1.40) can be checked against actual config expressions
24
+ #{ triple = "x86_64-unknown-linux-musl" },
25
+ # You can also specify which target_features you promise are enabled for a
26
+ # particular target. target_features are currently not validated against
27
+ # the actual valid features supported by the target architecture.
28
+ #{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
29
+ ]
30
+
31
+ # Tauri produces too many nonsense warnings.
32
+ exclude = ["tauri", "tauri-build"]
33
+
34
+
35
+ # This section is considered when running `cargo deny check advisories`
36
+ # More documentation for the advisories section can be found here:
37
+ # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
38
+ [advisories]
39
+ # The path where the advisory database is cloned/fetched into
40
+ db-path = "~/.cargo/advisory-db"
41
+ # The url(s) of the advisory databases to use
42
+ db-urls = ["https://github.com/rustsec/advisory-db"]
43
+ # A list of advisory IDs to ignore. Note that ignored advisories will still
44
+ # output a note when they are encountered.
45
+ ignore = [
46
+ "RUSTSEC-2024-0370", # Unmaintained but still fully functional crate `proc-macro-error`
47
+ "RUSTSEC-2024-0388", # Unmaintained but still fully functional crate `derivative`
48
+ "RUSTSEC-2025-0007", # Unmaintained but still fully functional crate `ring`
49
+ "RUSTSEC-2024-0436", # Unmaintained but still fully functional crate `paste`
50
+ "RUSTSEC-2025-0014", # Unmaintained but still fully functional crate `humantime`
51
+ ]
52
+ # Threshold for security vulnerabilities, any vulnerability with a CVSS score
53
+ # lower than the range specified will be ignored. Note that ignored advisories
54
+ # will still output a note when they are encountered.
55
+ # * None - CVSS Score 0.0
56
+ # * Low - CVSS Score 0.1 - 3.9
57
+ # * Medium - CVSS Score 4.0 - 6.9
58
+ # * High - CVSS Score 7.0 - 8.9
59
+ # * Critical - CVSS Score 9.0 - 10.0
60
+ #severity-threshold =
61
+
62
+ # This section is considered when running `cargo deny check licenses`
63
+ # More documentation for the licenses section can be found here:
64
+ # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html
65
+ [licenses]
66
+ # List of explicitly allowed licenses
67
+ # See https://spdx.org/licenses/ for list of possible licenses
68
+ # [possible values: any SPDX 3.11 short identifier (+ optional exception)].
69
+ #
70
+ # Keep this list in sync with those in `/about.toml` and `/frontend/vite.config.ts`.
71
+ allow = [
72
+ "Apache-2.0 WITH LLVM-exception",
73
+ "Apache-2.0",
74
+ "BSD-2-Clause",
75
+ "BSD-3-Clause",
76
+ "BSL-1.0",
77
+ "CC0-1.0",
78
+ "ISC",
79
+ "MIT",
80
+ "MPL-2.0",
81
+ "OpenSSL",
82
+ "Unicode-3.0",
83
+ "Zlib",
84
+ "NCSA",
85
+ ]
86
+ # The confidence threshold for detecting a license from license text.
87
+ # The higher the value, the more closely the license text must be to the
88
+ # canonical license text of a valid SPDX license file.
89
+ # [possible values: any between 0.0 and 1.0].
90
+ confidence-threshold = 0.8
91
+ # Allow 1 or more licenses on a per-crate basis, so that particular licenses
92
+ # aren't accepted for every possible crate as with the normal allow list
93
+ exceptions = [
94
+ # Each entry is the crate and version constraint, and its specific allow
95
+ # list
96
+ #{ allow = ["Zlib"], name = "adler32", version = "*" },
97
+ ]
98
+
99
+ # Some crates don't have (easily) machine readable licensing information,
100
+ # adding a clarification entry for it allows you to manually specify the
101
+ # licensing information
102
+ [[licenses.clarify]]
103
+ # The name of the crate the clarification applies to
104
+ name = "ring"
105
+ # The optional version constraint for the crate
106
+ #version = "*"
107
+ # The SPDX expression for the license requirements of the crate
108
+ expression = "MIT AND ISC AND OpenSSL"
109
+ # One or more files in the crate's source used as the "source of truth" for
110
+ # the license expression. If the contents match, the clarification will be used
111
+ # when running the license check, otherwise the clarification will be ignored
112
+ # and the crate will be checked normally, which may produce warnings or errors
113
+ # depending on the rest of your configuration
114
+ license-files = [
115
+ # Each entry is a crate relative path, and the (opaque) hash of its contents
116
+ { path = "LICENSE", hash = 0xbd0eed23 },
117
+ ]
118
+
119
+ [licenses.private]
120
+ # If true, ignores workspace crates that aren't published, or are only
121
+ # published to private registries
122
+ ignore = false
123
+ # One or more private registries that you might publish crates to, if a crate
124
+ # is only published to private registries, and ignore is true, the crate will
125
+ # not have its license(s) checked
126
+ registries = [
127
+ #"https://sekretz.com/registry
128
+ ]
129
+
130
+ # This section is considered when running `cargo deny check bans`.
131
+ # More documentation about the 'bans' section can be found here:
132
+ # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html
133
+ [bans]
134
+ # Lint level for when multiple versions of the same crate are detected
135
+ multiple-versions = "allow"
136
+
137
+ # Lint level for when a crate version requirement is `*`
138
+ wildcards = "allow"
139
+ # The graph highlighting used when creating dotgraphs for crates
140
+ # with multiple versions
141
+ # * lowest-version - The path to the lowest versioned duplicate is highlighted
142
+ # * simplest-path - The path to the version with the fewest edges is highlighted
143
+ # * all - Both lowest-version and simplest-path are used
144
+ highlight = "all"
145
+ # List of crates that are allowed. Use with care!
146
+ allow = [
147
+ #{ name = "ansi_term", version = "=0.11.0" },
148
+ ]
149
+ # List of crates to deny
150
+ deny = [
151
+ # Each entry the name of a crate and a version range. If version is
152
+ # not specified, all versions will be matched.
153
+ #{ name = "ansi_term", version = "=0.11.0" },
154
+ #
155
+ # Wrapper crates can optionally be specified to allow the crate when it
156
+ # is a direct dependency of the otherwise banned crate
157
+ #{ name = "ansi_term", version = "=0.11.0", wrappers = [] },
158
+ ]
159
+ # Certain crates/versions that will be skipped when doing duplicate detection.
160
+ skip = [
161
+ #{ name = "ansi_term", version = "=0.11.0" },
162
+ #{ name = "cfg-if", version = "=0.1.10" },
163
+ ]
164
+ # Similarly to `skip` allows you to skip certain crates during duplicate
165
+ # detection. Unlike skip, it also includes the entire tree of transitive
166
+ # dependencies starting at the specified crate, up to a certain depth, which is
167
+ # by default infinite
168
+ skip-tree = [
169
+ #{ name = "ansi_term", version = "=0.11.0", depth = 20 },
170
+ ]
171
+
172
+ # This section is considered when running `cargo deny check sources`.
173
+ # More documentation about the 'sources' section can be found here:
174
+ # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html
175
+ [sources]
176
+ # Lint level for what to happen when a crate from a crate registry that is not
177
+ # in the allow list is encountered
178
+ unknown-registry = "warn"
179
+ # Lint level for what to happen when a crate from a git repository that is not
180
+ # in the allow list is encountered
181
+ unknown-git = "warn"
182
+ # List of URLs for allowed crate registries. Defaults to the crates.io index
183
+ # if not specified. If it is specified but empty, no registries are allowed.
184
+ allow-registry = ["https://github.com/rust-lang/crates.io-index"]
185
+ # List of URLs for allowed Git repositories
186
+ allow-git = []
187
+
188
+ [sources.allow-org]
189
+ # 1 or more github.com organizations to allow git sources for
190
+ github = ["linebender", "Rust-GPU", "specta-rs"]
191
+ # 1 or more gitlab.com organizations to allow git sources for
192
+ #gitlab = [""]
193
+ # 1 or more bitbucket.org organizations to allow git sources for
194
+ #bitbucket = [""]
editor/Cargo.toml ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [package]
2
+ name = "graphite-editor"
3
+ publish = false
4
+ version = "0.0.0"
5
+ rust-version = "1.85"
6
+ authors = ["Graphite Authors <[email protected]>"]
7
+ edition = "2024"
8
+ readme = "../README.md"
9
+ homepage = "https://graphite.rs"
10
+ repository = "https://github.com/GraphiteEditor/Graphite"
11
+ license = "Apache-2.0"
12
+
13
+ [features]
14
+ default = ["wasm"]
15
+ wasm = ["wasm-bindgen", "graphene-std/wasm", "wasm-bindgen-futures"]
16
+ gpu = ["interpreted-executor/gpu", "wgpu-executor"]
17
+ tauri = ["ron", "decouple-execution"]
18
+ decouple-execution = []
19
+ resvg = ["graphene-std/resvg"]
20
+ vello = ["graphene-std/vello", "resvg"]
21
+ ron = ["dep:ron"]
22
+
23
+ [dependencies]
24
+ # Local dependencies
25
+ graphite-proc-macros = { workspace = true }
26
+ graph-craft = { workspace = true }
27
+ interpreted-executor = { workspace = true }
28
+ graphene-std = { workspace = true }
29
+ preprocessor = { workspace = true }
30
+
31
+ # Workspace dependencies
32
+ js-sys = { workspace = true }
33
+ log = { workspace = true }
34
+ bitflags = { workspace = true }
35
+ thiserror = { workspace = true }
36
+ serde = { workspace = true }
37
+ serde_json = { workspace = true }
38
+ bezier-rs = { workspace = true }
39
+ kurbo = { workspace = true }
40
+ futures = { workspace = true }
41
+ glam = { workspace = true }
42
+ derivative = { workspace = true }
43
+ specta = { workspace = true }
44
+ dyn-any = { workspace = true }
45
+ num_enum = { workspace = true }
46
+ usvg = { workspace = true }
47
+ once_cell = { workspace = true }
48
+ web-sys = { workspace = true }
49
+
50
+ # Required dependencies
51
+ spin = "0.9.8"
52
+
53
+ # Optional local dependencies
54
+ wgpu-executor = { workspace = true, optional = true }
55
+
56
+ # Optional workspace dependencies
57
+ wasm-bindgen = { workspace = true, optional = true }
58
+ wasm-bindgen-futures = { workspace = true, optional = true }
59
+ ron = { workspace = true, optional = true }
60
+
61
+ [dev-dependencies]
62
+ # Workspace dependencies
63
+ env_logger = { workspace = true }
64
+ futures = { workspace = true }
65
+ tokio = { workspace = true }
66
+
67
+ [lints.rust]
68
+ # TODO: figure out why we check these features when they do not exist
69
+ unexpected_cfgs = { level = "warn", check-cfg = [
70
+ 'cfg(feature, values("resvg", "vello"))',
71
+ ] }
editor/build.rs ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::process::Command;
2
+
3
+ const GRAPHITE_RELEASE_SERIES: &str = "Alpha 4";
4
+
5
+ fn main() {
6
+ // Execute a Git command for its stdout. Early exit if it fails for any of the possible reasons.
7
+ let try_git_command = |args: &[&str]| -> Option<String> {
8
+ let git_output = Command::new("git").args(args).output().ok()?;
9
+ let maybe_empty = String::from_utf8(git_output.stdout).ok()?;
10
+ let command_result = (!maybe_empty.is_empty()).then_some(maybe_empty)?;
11
+ Some(command_result)
12
+ };
13
+ // Execute a Git command for its output. Return "unknown" if it fails for any of the possible reasons.
14
+ let git_command = |args| -> String { try_git_command(args).unwrap_or_else(|| String::from("unknown")) };
15
+
16
+ // Rather than printing to any terminal, these commands set environment variables in the Cargo toolchain.
17
+ // They are accessed with the `env!("...")` macro in the codebase.
18
+ println!("cargo:rustc-env=GRAPHITE_GIT_COMMIT_DATE={}", git_command(&["log", "-1", "--format=%cd"]));
19
+ println!("cargo:rustc-env=GRAPHITE_GIT_COMMIT_HASH={}", git_command(&["rev-parse", "HEAD"]));
20
+ let branch = std::env::var("GITHUB_HEAD_REF").unwrap_or_default();
21
+ let branch = if branch.is_empty() { git_command(&["name-rev", "--name-only", "HEAD"]) } else { branch };
22
+ println!("cargo:rustc-env=GRAPHITE_GIT_COMMIT_BRANCH={branch}");
23
+ println!("cargo:rustc-env=GRAPHITE_RELEASE_SERIES={GRAPHITE_RELEASE_SERIES}");
24
+ }
editor/src/application.rs ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::dispatcher::Dispatcher;
2
+ use crate::messages::prelude::*;
3
+ pub use graphene_std::uuid::*;
4
+
5
+ // TODO: serialize with serde to save the current editor state
6
+ pub struct Editor {
7
+ pub dispatcher: Dispatcher,
8
+ }
9
+
10
+ impl Editor {
11
+ /// Construct the editor.
12
+ /// Remember to provide a random seed with `editor::set_uuid_seed(seed)` before any editors can be used.
13
+ pub fn new() -> Self {
14
+ Self { dispatcher: Dispatcher::new() }
15
+ }
16
+
17
+ #[cfg(test)]
18
+ pub(crate) fn new_local_executor() -> (Self, crate::node_graph_executor::NodeRuntime) {
19
+ let (runtime, executor) = crate::node_graph_executor::NodeGraphExecutor::new_with_local_runtime();
20
+ let dispatcher = Dispatcher::with_executor(executor);
21
+ (Self { dispatcher }, runtime)
22
+ }
23
+
24
+ pub fn handle_message<T: Into<Message>>(&mut self, message: T) -> Vec<FrontendMessage> {
25
+ self.dispatcher.handle_message(message, true);
26
+
27
+ std::mem::take(&mut self.dispatcher.responses)
28
+ }
29
+
30
+ pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) -> Result<(), String> {
31
+ self.dispatcher.poll_node_graph_evaluation(responses)
32
+ }
33
+ }
34
+
35
+ impl Default for Editor {
36
+ fn default() -> Self {
37
+ Self::new()
38
+ }
39
+ }
40
+
41
+ pub const GRAPHITE_RELEASE_SERIES: &str = env!("GRAPHITE_RELEASE_SERIES");
42
+ pub const GRAPHITE_GIT_COMMIT_DATE: &str = env!("GRAPHITE_GIT_COMMIT_DATE");
43
+ pub const GRAPHITE_GIT_COMMIT_HASH: &str = env!("GRAPHITE_GIT_COMMIT_HASH");
44
+ pub const GRAPHITE_GIT_COMMIT_BRANCH: &str = env!("GRAPHITE_GIT_COMMIT_BRANCH");
45
+
46
+ pub fn commit_info_localized(localized_commit_date: &str) -> String {
47
+ format!(
48
+ "Release Series: {}\n\
49
+ Branch: {}\n\
50
+ Commit: {}\n\
51
+ {}",
52
+ GRAPHITE_RELEASE_SERIES,
53
+ GRAPHITE_GIT_COMMIT_BRANCH,
54
+ &GRAPHITE_GIT_COMMIT_HASH[..8],
55
+ localized_commit_date
56
+ )
57
+ }
editor/src/consts.rs ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // GRAPH
2
+ pub const GRID_SIZE: u32 = 24;
3
+ pub const EXPORTS_TO_TOP_EDGE_PIXEL_GAP: u32 = 72;
4
+ pub const EXPORTS_TO_RIGHT_EDGE_PIXEL_GAP: u32 = 120;
5
+ pub const IMPORTS_TO_TOP_EDGE_PIXEL_GAP: u32 = 72;
6
+ pub const IMPORTS_TO_LEFT_EDGE_PIXEL_GAP: u32 = 120;
7
+
8
+ // VIEWPORT
9
+ pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = (1. / 600.) * 3.;
10
+ pub const VIEWPORT_ZOOM_MOUSE_RATE: f64 = 1. / 400.;
11
+ pub const VIEWPORT_ZOOM_SCALE_MIN: f64 = 0.000_000_1;
12
+ pub const VIEWPORT_ZOOM_SCALE_MAX: f64 = 10_000.;
13
+ pub const VIEWPORT_ZOOM_MIN_FRACTION_COVER: f64 = 0.01;
14
+ pub const VIEWPORT_ZOOM_LEVELS: [f64; 74] = [
15
+ 0.0001, 0.000125, 0.00016, 0.0002, 0.00025, 0.00032, 0.0004, 0.0005, 0.00064, 0.0008, 0.001, 0.0016, 0.002, 0.0025, 0.0032, 0.004, 0.005, 0.0064, 0.008, 0.01, 0.01125, 0.015, 0.02, 0.025, 0.03,
16
+ 0.04, 0.05, 0.06, 0.08, 0.1, 0.125, 0.15, 0.2, 0.25, 0.33333333, 0.4, 0.5, 0.66666666, 0.8, 1., 1.25, 1.6, 2., 2.5, 3.2, 4., 5., 6.4, 8., 10., 12.5, 16., 20., 25., 32., 40., 50., 64., 80., 100.,
17
+ 128., 160., 200., 256., 320., 400., 512., 640., 800., 1024., 1280., 1600., 2048., 2560.,
18
+ ];
19
+ /// Higher values create a steeper curve (a faster zoom rate change)
20
+ pub const VIEWPORT_ZOOM_WHEEL_RATE_CHANGE: f64 = 3.;
21
+
22
+ /// Helps push values that end in approximately half, plus or minus some floating point imprecision, towards the same side of the round() function.
23
+ pub const VIEWPORT_GRID_ROUNDING_BIAS: f64 = 0.002;
24
+
25
+ pub const VIEWPORT_SCROLL_RATE: f64 = 0.6;
26
+
27
+ pub const VIEWPORT_ROTATE_SNAP_INTERVAL: f64 = 15.;
28
+
29
+ pub const VIEWPORT_ZOOM_TO_FIT_PADDING_SCALE_FACTOR: f64 = 0.95;
30
+
31
+ pub const DRAG_BEYOND_VIEWPORT_MAX_OVEREXTENSION_PIXELS: f64 = 50.;
32
+ pub const DRAG_BEYOND_VIEWPORT_SPEED_FACTOR: f64 = 20.;
33
+
34
+ // SNAPPING POINT
35
+ pub const SNAP_POINT_TOLERANCE: f64 = 5.;
36
+ /// These are layers whose bounding boxes are used for alignment.
37
+ pub const MAX_ALIGNMENT_CANDIDATES: usize = 100;
38
+ /// These are layers that are used for the layer snapper.
39
+ pub const MAX_SNAP_CANDIDATES: usize = 10;
40
+ /// These are points (anchors and bounding box corners etc.) in the layer snapper.
41
+ pub const MAX_LAYER_SNAP_POINTS: usize = 100;
42
+
43
+ pub const DRAG_THRESHOLD: f64 = 1.;
44
+
45
+ // TRANSFORMING LAYER
46
+ pub const ROTATE_INCREMENT: f64 = 15.;
47
+ pub const SCALE_INCREMENT: f64 = 0.1;
48
+ pub const SLOWING_DIVISOR: f64 = 10.;
49
+ pub const NUDGE_AMOUNT: f64 = 1.;
50
+ pub const BIG_NUDGE_AMOUNT: f64 = 10.;
51
+
52
+ // TOOLS
53
+ pub const DEFAULT_STROKE_WIDTH: f64 = 2.;
54
+
55
+ // SELECT TOOL
56
+ pub const SELECTION_TOLERANCE: f64 = 5.;
57
+ pub const DRAG_DIRECTION_MODE_DETERMINATION_THRESHOLD: f64 = 15.;
58
+ pub const SELECTION_DRAG_ANGLE: f64 = 90.;
59
+
60
+ // PIVOT
61
+ pub const PIVOT_CROSSHAIR_THICKNESS: f64 = 1.;
62
+ pub const PIVOT_CROSSHAIR_LENGTH: f64 = 9.;
63
+ pub const PIVOT_DIAMETER: f64 = 5.;
64
+
65
+ // COMPASS ROSE
66
+ pub const COMPASS_ROSE_RING_INNER_DIAMETER: f64 = 13.;
67
+ pub const COMPASS_ROSE_MAIN_RING_DIAMETER: f64 = 15.;
68
+ pub const COMPASS_ROSE_HOVER_RING_DIAMETER: f64 = 23.;
69
+ pub const COMPASS_ROSE_ARROW_SIZE: f64 = 5.;
70
+ // Angle to either side of the compass arrows where they are targetted by the cursor (in degrees, must be less than 45°)
71
+ pub const COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE: f64 = 20.;
72
+
73
+ // TRANSFORM OVERLAY
74
+ pub const ANGLE_MEASURE_RADIUS_FACTOR: f64 = 0.04;
75
+ pub const ARC_MEASURE_RADIUS_FACTOR_RANGE: (f64, f64) = (0.05, 0.15);
76
+
77
+ // TRANSFORM CAGE
78
+ pub const RESIZE_HANDLE_SIZE: f64 = 6.;
79
+ pub const BOUNDS_SELECT_THRESHOLD: f64 = 10.;
80
+ pub const BOUNDS_ROTATE_THRESHOLD: f64 = 20.;
81
+ pub const MIN_LENGTH_FOR_MIDPOINT_VISIBILITY: f64 = 20.;
82
+ pub const MIN_LENGTH_FOR_CORNERS_VISIBILITY: f64 = 12.;
83
+ /// The width or height that the transform cage needs to be (at least) before the corner resize handle click targets take up their full surroundings. Otherwise, when less than this value, the interior edge resize handle takes precedence so the corner handles don't eat into the edge area, making it harder to resize the cage from its edges.
84
+ pub const MIN_LENGTH_FOR_EDGE_RESIZE_PRIORITY_OVER_CORNERS: f64 = 10.;
85
+ /// When the width or height of the transform cage is less than this value, only the exterior of the bounding box will act as a click target for resizing.
86
+ pub const MIN_LENGTH_FOR_RESIZE_TO_INCLUDE_INTERIOR: f64 = 40.;
87
+ /// When dragging the edge of a cage with Alt, it centers around the pivot.
88
+ /// However if the pivot is on or near the same edge you are dragging, we should avoid scaling by a massive factor caused by the small denominator.
89
+ ///
90
+ /// The motion of the user's cursor by an `x` pixel offset results in `x * scale_factor` pixels of offset on the other side.
91
+ pub const MAXIMUM_ALT_SCALE_FACTOR: f64 = 25.;
92
+ /// The width or height that the transform cage needs before it is considered to have no width or height.
93
+ pub const MAX_LENGTH_FOR_NO_WIDTH_OR_HEIGHT: f64 = 1e-4;
94
+
95
+ // SKEW TRIANGLES
96
+ pub const SKEW_TRIANGLE_SIZE: f64 = 7.;
97
+ pub const SKEW_TRIANGLE_OFFSET: f64 = 4.;
98
+ pub const MIN_LENGTH_FOR_SKEW_TRIANGLE_VISIBILITY: f64 = 48.;
99
+
100
+ // PATH TOOL
101
+ pub const MANIPULATOR_GROUP_MARKER_SIZE: f64 = 6.;
102
+ pub const SELECTION_THRESHOLD: f64 = 10.;
103
+ pub const HIDE_HANDLE_DISTANCE: f64 = 3.;
104
+ pub const HANDLE_ROTATE_SNAP_ANGLE: f64 = 15.;
105
+ pub const SEGMENT_INSERTION_DISTANCE: f64 = 5.;
106
+ pub const SEGMENT_OVERLAY_SIZE: f64 = 10.;
107
+ pub const HANDLE_LENGTH_FACTOR: f64 = 0.5;
108
+
109
+ // PEN TOOL
110
+ pub const CREATE_CURVE_THRESHOLD: f64 = 5.;
111
+
112
+ // SPLINE TOOL
113
+ pub const PATH_JOIN_THRESHOLD: f64 = 5.;
114
+
115
+ // LINE TOOL
116
+ pub const LINE_ROTATE_SNAP_ANGLE: f64 = 15.;
117
+
118
+ // BRUSH TOOL
119
+ pub const BRUSH_SIZE_CHANGE_KEYBOARD: f64 = 5.;
120
+ pub const DEFAULT_BRUSH_SIZE: f64 = 20.;
121
+
122
+ // GIZMOS
123
+ pub const POINT_RADIUS_HANDLE_SNAP_THRESHOLD: f64 = 8.;
124
+ pub const POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD: f64 = 7.9;
125
+ pub const NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION: f64 = 1.2;
126
+ pub const NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH: f64 = 10.;
127
+ pub const GIZMO_HIDE_THRESHOLD: f64 = 20.;
128
+
129
+ // SCROLLBARS
130
+ pub const SCROLLBAR_SPACING: f64 = 0.1;
131
+ pub const ASYMPTOTIC_EFFECT: f64 = 0.5;
132
+ pub const SCALE_EFFECT: f64 = 0.5;
133
+
134
+ // COLORS
135
+ pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff";
136
+ pub const COLOR_OVERLAY_BLUE_50: &str = "rgba(0, 168, 255, 0.5)";
137
+ pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848";
138
+ pub const COLOR_OVERLAY_GREEN: &str = "#63ce63";
139
+ pub const COLOR_OVERLAY_RED: &str = "#ef5454";
140
+ pub const COLOR_OVERLAY_GRAY: &str = "#cccccc";
141
+ pub const COLOR_OVERLAY_WHITE: &str = "#ffffff";
142
+ pub const COLOR_OVERLAY_LABEL_BACKGROUND: &str = "#000000cc";
143
+
144
+ // DOCUMENT
145
+ pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
146
+ pub const FILE_SAVE_SUFFIX: &str = ".graphite";
147
+ pub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences
148
+ pub const AUTO_SAVE_TIMEOUT_SECONDS: u64 = 15;
149
+
150
+ // INPUT
151
+ pub const DOUBLE_CLICK_MILLISECONDS: u64 = 500;
editor/src/dispatcher.rs ADDED
@@ -0,0 +1,562 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::messages::debug::utility_types::MessageLoggingVerbosity;
2
+ use crate::messages::dialog::DialogMessageData;
3
+ use crate::messages::portfolio::document::node_graph::document_node_definitions;
4
+ use crate::messages::prelude::*;
5
+
6
+ #[derive(Debug, Default)]
7
+ pub struct Dispatcher {
8
+ buffered_queue: Option<Vec<VecDeque<Message>>>,
9
+ message_queues: Vec<VecDeque<Message>>,
10
+ pub responses: Vec<FrontendMessage>,
11
+ pub message_handlers: DispatcherMessageHandlers,
12
+ }
13
+
14
+ #[derive(Debug, Default)]
15
+ pub struct DispatcherMessageHandlers {
16
+ animation_message_handler: AnimationMessageHandler,
17
+ broadcast_message_handler: BroadcastMessageHandler,
18
+ debug_message_handler: DebugMessageHandler,
19
+ dialog_message_handler: DialogMessageHandler,
20
+ globals_message_handler: GlobalsMessageHandler,
21
+ input_preprocessor_message_handler: InputPreprocessorMessageHandler,
22
+ key_mapping_message_handler: KeyMappingMessageHandler,
23
+ layout_message_handler: LayoutMessageHandler,
24
+ pub portfolio_message_handler: PortfolioMessageHandler,
25
+ preferences_message_handler: PreferencesMessageHandler,
26
+ tool_message_handler: ToolMessageHandler,
27
+ workspace_message_handler: WorkspaceMessageHandler,
28
+ }
29
+
30
+ impl DispatcherMessageHandlers {
31
+ pub fn with_executor(executor: crate::node_graph_executor::NodeGraphExecutor) -> Self {
32
+ Self {
33
+ portfolio_message_handler: PortfolioMessageHandler::with_executor(executor),
34
+ ..Default::default()
35
+ }
36
+ }
37
+ }
38
+
39
+ /// For optimization, these are messages guaranteed to be redundant when repeated.
40
+ /// The last occurrence of the message in the message queue is sufficient to ensure correct behavior.
41
+ /// In addition, these messages do not change any state in the backend (aside from caches).
42
+ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
43
+ MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
44
+ PropertiesPanelMessageDiscriminant::Refresh,
45
+ ))),
46
+ MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::DocumentStructureChanged)),
47
+ MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::Overlays(OverlaysMessageDiscriminant::Draw))),
48
+ MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderRulers)),
49
+ MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderScrollbars)),
50
+ MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerStructure),
51
+ MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
52
+ ];
53
+ const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::AnimationFrame))];
54
+ // 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.
55
+ const DEBUG_MESSAGE_ENDING_BLOCK_LIST: &[&str] = &["PointerMove", "PointerOutsideViewport", "Overlays", "Draw", "CurrentTime", "Time"];
56
+
57
+ impl Dispatcher {
58
+ pub fn new() -> Self {
59
+ Self::default()
60
+ }
61
+
62
+ pub fn with_executor(executor: crate::node_graph_executor::NodeGraphExecutor) -> Self {
63
+ Self {
64
+ message_handlers: DispatcherMessageHandlers::with_executor(executor),
65
+ ..Default::default()
66
+ }
67
+ }
68
+
69
+ // If the deepest queues (higher index in queues list) are now empty (after being popped from) then remove them
70
+ fn cleanup_queues(&mut self, leave_last: bool) {
71
+ while self.message_queues.last().filter(|queue| queue.is_empty()).is_some() {
72
+ if leave_last && self.message_queues.len() == 1 {
73
+ break;
74
+ }
75
+ self.message_queues.pop();
76
+ }
77
+ }
78
+
79
+ /// Add a message to a queue so that it can be executed.
80
+ /// If `process_after_all_current` is set, all currently queued messages (including children) will be processed first.
81
+ /// If not set, it (and its children) will be processed as soon as possible.
82
+ pub fn schedule_execution(message_queues: &mut Vec<VecDeque<Message>>, process_after_all_current: bool, messages: impl IntoIterator<Item = Message>) {
83
+ match message_queues.first_mut() {
84
+ // If there are currently messages being processed and we are processing after them, add to the end of the first queue
85
+ Some(queue) if process_after_all_current => queue.extend(messages),
86
+ // In all other cases, make a new inner queue and add our message there
87
+ _ => message_queues.push(VecDeque::from_iter(messages)),
88
+ }
89
+ }
90
+
91
+ pub fn handle_message<T: Into<Message>>(&mut self, message: T, process_after_all_current: bool) {
92
+ let message = message.into();
93
+ // Add all additional messages to the buffer if it exists (except from the end buffer message)
94
+ if !matches!(message, Message::EndBuffer(_)) {
95
+ if let Some(buffered_queue) = &mut self.buffered_queue {
96
+ Self::schedule_execution(buffered_queue, true, [message]);
97
+
98
+ return;
99
+ }
100
+ }
101
+
102
+ // If we are not maintaining the buffer, simply add to the current queue
103
+ Self::schedule_execution(&mut self.message_queues, process_after_all_current, [message]);
104
+
105
+ while let Some(message) = self.message_queues.last_mut().and_then(VecDeque::pop_front) {
106
+ // Skip processing of this message if it will be processed later (at the end of the shallowest level queue)
107
+ if SIDE_EFFECT_FREE_MESSAGES.contains(&message.to_discriminant()) {
108
+ let already_in_queue = self.message_queues.first().filter(|queue| queue.contains(&message)).is_some();
109
+ if already_in_queue {
110
+ self.log_deferred_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.message_logging_verbosity);
111
+ self.cleanup_queues(false);
112
+ continue;
113
+ } else if self.message_queues.len() > 1 {
114
+ self.log_deferred_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.message_logging_verbosity);
115
+ self.cleanup_queues(true);
116
+ self.message_queues[0].add(message);
117
+ continue;
118
+ }
119
+ }
120
+
121
+ // Print the message at a verbosity level of `info`
122
+ self.log_message(&message, &self.message_queues, self.message_handlers.debug_message_handler.message_logging_verbosity);
123
+
124
+ // Create a new queue for the child messages
125
+ let mut queue = VecDeque::new();
126
+
127
+ // Process the action by forwarding it to the relevant message handler, or saving the FrontendMessage to be sent to the frontend
128
+ match message {
129
+ Message::StartBuffer => {
130
+ self.buffered_queue = Some(std::mem::take(&mut self.message_queues));
131
+ }
132
+ Message::EndBuffer(render_metadata) => {
133
+ // Assign the message queue to the currently buffered queue
134
+ if let Some(buffered_queue) = self.buffered_queue.take() {
135
+ self.cleanup_queues(false);
136
+ assert!(self.message_queues.is_empty(), "message queues are always empty when ending a buffer");
137
+ self.message_queues = buffered_queue;
138
+ };
139
+
140
+ let graphene_std::renderer::RenderMetadata {
141
+ upstream_footprints: footprints,
142
+ local_transforms,
143
+ click_targets,
144
+ clip_targets,
145
+ } = render_metadata;
146
+
147
+ // Run these update state messages immediately
148
+ let messages = [
149
+ DocumentMessage::UpdateUpstreamTransforms {
150
+ upstream_footprints: footprints,
151
+ local_transforms,
152
+ },
153
+ DocumentMessage::UpdateClickTargets { click_targets },
154
+ DocumentMessage::UpdateClipTargets { clip_targets },
155
+ ];
156
+ Self::schedule_execution(&mut self.message_queues, false, messages.map(Message::from));
157
+ }
158
+ Message::NoOp => {}
159
+ Message::Init => {
160
+ // Load persistent data from the browser database
161
+ queue.add(FrontendMessage::TriggerLoadFirstAutoSaveDocument);
162
+ queue.add(FrontendMessage::TriggerLoadPreferences);
163
+
164
+ // Display the menu bar at the top of the window
165
+ queue.add(MenuBarMessage::SendLayout);
166
+
167
+ // Send the information for tooltips and categories for each node/input.
168
+ queue.add(FrontendMessage::SendUIMetadata {
169
+ node_descriptions: document_node_definitions::collect_node_descriptions(),
170
+ node_types: document_node_definitions::collect_node_types(),
171
+ });
172
+
173
+ // Finish loading persistent data from the browser database
174
+ queue.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments);
175
+ }
176
+ Message::Animation(message) => {
177
+ self.message_handlers.animation_message_handler.process_message(message, &mut queue, ());
178
+ }
179
+ Message::Batched(messages) => {
180
+ messages.iter().for_each(|message| self.handle_message(message.to_owned(), false));
181
+ }
182
+ Message::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()),
183
+ Message::Debug(message) => {
184
+ self.message_handlers.debug_message_handler.process_message(message, &mut queue, ());
185
+ }
186
+ Message::Dialog(message) => {
187
+ let data = DialogMessageData {
188
+ portfolio: &self.message_handlers.portfolio_message_handler,
189
+ preferences: &self.message_handlers.preferences_message_handler,
190
+ };
191
+ self.message_handlers.dialog_message_handler.process_message(message, &mut queue, data);
192
+ }
193
+ Message::Frontend(message) => {
194
+ // Handle these messages immediately by returning early
195
+ if let FrontendMessage::TriggerFontLoad { .. } = message {
196
+ self.responses.push(message);
197
+ self.cleanup_queues(false);
198
+
199
+ // Return early to avoid running the code after the match block
200
+ return;
201
+ } else {
202
+ // `FrontendMessage`s are saved and will be sent to the frontend after the message queue is done being processed
203
+ self.responses.push(message);
204
+ }
205
+ }
206
+ Message::Globals(message) => {
207
+ self.message_handlers.globals_message_handler.process_message(message, &mut queue, ());
208
+ }
209
+ Message::InputPreprocessor(message) => {
210
+ let keyboard_platform = GLOBAL_PLATFORM.get().copied().unwrap_or_default().as_keyboard_platform_layout();
211
+
212
+ self.message_handlers
213
+ .input_preprocessor_message_handler
214
+ .process_message(message, &mut queue, InputPreprocessorMessageData { keyboard_platform });
215
+ }
216
+ Message::KeyMapping(message) => {
217
+ let input = &self.message_handlers.input_preprocessor_message_handler;
218
+ let actions = self.collect_actions();
219
+
220
+ self.message_handlers
221
+ .key_mapping_message_handler
222
+ .process_message(message, &mut queue, KeyMappingMessageData { input, actions });
223
+ }
224
+ Message::Layout(message) => {
225
+ let action_input_mapping = &|action_to_find: &MessageDiscriminant| self.message_handlers.key_mapping_message_handler.action_input_mapping(action_to_find);
226
+
227
+ self.message_handlers.layout_message_handler.process_message(message, &mut queue, action_input_mapping);
228
+ }
229
+ Message::Portfolio(message) => {
230
+ let ipp = &self.message_handlers.input_preprocessor_message_handler;
231
+ let preferences = &self.message_handlers.preferences_message_handler;
232
+ let current_tool = &self.message_handlers.tool_message_handler.tool_state.tool_data.active_tool_type;
233
+ let message_logging_verbosity = self.message_handlers.debug_message_handler.message_logging_verbosity;
234
+ let reset_node_definitions_on_open = self.message_handlers.portfolio_message_handler.reset_node_definitions_on_open;
235
+ let timing_information = self.message_handlers.animation_message_handler.timing_information();
236
+ let animation = &self.message_handlers.animation_message_handler;
237
+
238
+ self.message_handlers.portfolio_message_handler.process_message(
239
+ message,
240
+ &mut queue,
241
+ PortfolioMessageData {
242
+ ipp,
243
+ preferences,
244
+ current_tool,
245
+ message_logging_verbosity,
246
+ reset_node_definitions_on_open,
247
+ timing_information,
248
+ animation,
249
+ },
250
+ );
251
+ }
252
+ Message::Preferences(message) => {
253
+ self.message_handlers.preferences_message_handler.process_message(message, &mut queue, ());
254
+ }
255
+ Message::Tool(message) => {
256
+ let document_id = self.message_handlers.portfolio_message_handler.active_document_id().unwrap();
257
+ let Some(document) = self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id) else {
258
+ warn!("Called ToolMessage without an active document.\nGot {message:?}");
259
+ return;
260
+ };
261
+
262
+ let data = ToolMessageData {
263
+ document_id,
264
+ document,
265
+ input: &self.message_handlers.input_preprocessor_message_handler,
266
+ persistent_data: &self.message_handlers.portfolio_message_handler.persistent_data,
267
+ node_graph: &self.message_handlers.portfolio_message_handler.executor,
268
+ preferences: &self.message_handlers.preferences_message_handler,
269
+ };
270
+
271
+ self.message_handlers.tool_message_handler.process_message(message, &mut queue, data);
272
+ }
273
+ Message::Workspace(message) => {
274
+ self.message_handlers.workspace_message_handler.process_message(message, &mut queue, ());
275
+ }
276
+ }
277
+
278
+ // If there are child messages, append the queue to the list of queues
279
+ if !queue.is_empty() {
280
+ self.message_queues.push(queue);
281
+ }
282
+
283
+ self.cleanup_queues(false);
284
+ }
285
+ }
286
+
287
+ pub fn collect_actions(&self) -> ActionList {
288
+ // TODO: Reduce the number of heap allocations
289
+ let mut list = Vec::new();
290
+ list.extend(self.message_handlers.dialog_message_handler.actions());
291
+ list.extend(self.message_handlers.animation_message_handler.actions());
292
+ list.extend(self.message_handlers.input_preprocessor_message_handler.actions());
293
+ list.extend(self.message_handlers.key_mapping_message_handler.actions());
294
+ list.extend(self.message_handlers.debug_message_handler.actions());
295
+ if let Some(document) = self.message_handlers.portfolio_message_handler.active_document() {
296
+ if !document.graph_view_overlay_open {
297
+ list.extend(self.message_handlers.tool_message_handler.actions());
298
+ }
299
+ }
300
+ list.extend(self.message_handlers.portfolio_message_handler.actions());
301
+ list
302
+ }
303
+
304
+ pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) -> Result<(), String> {
305
+ self.message_handlers.portfolio_message_handler.poll_node_graph_evaluation(responses)
306
+ }
307
+
308
+ /// Create the tree structure for logging the messages as a tree
309
+ fn create_indents(queues: &[VecDeque<Message>]) -> String {
310
+ String::from_iter(queues.iter().enumerate().skip(1).map(|(index, queue)| {
311
+ if index == queues.len() - 1 {
312
+ if queue.is_empty() { "└── " } else { "├── " }
313
+ } else if queue.is_empty() {
314
+ " "
315
+ } else {
316
+ "│ "
317
+ }
318
+ }))
319
+ }
320
+
321
+ /// Logs a message that is about to be executed, either as a tree
322
+ /// with a discriminant or the entire payload (depending on settings)
323
+ fn log_message(&self, message: &Message, queues: &[VecDeque<Message>], message_logging_verbosity: MessageLoggingVerbosity) {
324
+ let discriminant = MessageDiscriminant::from(message);
325
+ let is_blocked = DEBUG_MESSAGE_BLOCK_LIST.contains(&discriminant) || DEBUG_MESSAGE_ENDING_BLOCK_LIST.iter().any(|blocked_name| discriminant.local_name().ends_with(blocked_name));
326
+
327
+ if !is_blocked {
328
+ match message_logging_verbosity {
329
+ MessageLoggingVerbosity::Off => {}
330
+ MessageLoggingVerbosity::Names => {
331
+ info!("{}{:?}", Self::create_indents(queues), message.to_discriminant());
332
+ }
333
+ MessageLoggingVerbosity::Contents => {
334
+ if !(matches!(message, Message::InputPreprocessor(_))) {
335
+ info!("Message: {}{:?}", Self::create_indents(queues), message);
336
+ }
337
+ }
338
+ }
339
+ }
340
+ }
341
+
342
+ /// Logs into the tree that the message is in the side effect free messages and its execution will be deferred
343
+ fn log_deferred_message(&self, message: &Message, queues: &[VecDeque<Message>], message_logging_verbosity: MessageLoggingVerbosity) {
344
+ if let MessageLoggingVerbosity::Names = message_logging_verbosity {
345
+ info!("{}Deferred \"{:?}\" because it's a SIDE_EFFECT_FREE_MESSAGE", Self::create_indents(queues), message.to_discriminant());
346
+ }
347
+ }
348
+ }
349
+
350
+ #[cfg(test)]
351
+ mod test {
352
+ pub use crate::test_utils::test_prelude::*;
353
+
354
+ /// Create an editor with three layers
355
+ /// 1. A red rectangle
356
+ /// 2. A blue shape
357
+ /// 3. A green ellipse
358
+ async fn create_editor_with_three_layers() -> EditorTestUtils {
359
+ let mut editor = EditorTestUtils::create();
360
+
361
+ editor.new_document().await;
362
+
363
+ editor.select_primary_color(Color::RED).await;
364
+ editor.draw_rect(100., 200., 300., 400.).await;
365
+
366
+ editor.select_primary_color(Color::BLUE).await;
367
+ editor.draw_polygon(10., 1200., 1300., 400.).await;
368
+
369
+ editor.select_primary_color(Color::GREEN).await;
370
+ editor.draw_ellipse(104., 1200., 1300., 400.).await;
371
+
372
+ editor
373
+ }
374
+
375
+ /// - create rect, shape and ellipse
376
+ /// - copy
377
+ /// - paste
378
+ /// - assert that ellipse was copied
379
+ #[tokio::test]
380
+ async fn copy_paste_single_layer() {
381
+ let mut editor = create_editor_with_three_layers().await;
382
+
383
+ let layers_before_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();
384
+ editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal }).await;
385
+ editor
386
+ .handle_message(PortfolioMessage::PasteIntoFolder {
387
+ clipboard: Clipboard::Internal,
388
+ parent: LayerNodeIdentifier::ROOT_PARENT,
389
+ insert_index: 0,
390
+ })
391
+ .await;
392
+
393
+ let layers_after_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();
394
+
395
+ assert_eq!(layers_before_copy.len(), 3);
396
+ assert_eq!(layers_after_copy.len(), 4);
397
+
398
+ // Existing layers are unaffected
399
+ for i in 0..=2 {
400
+ assert_eq!(layers_before_copy[i], layers_after_copy[i + 1]);
401
+ }
402
+ }
403
+
404
+ #[cfg_attr(miri, ignore)]
405
+ /// - create rect, shape and ellipse
406
+ /// - select shape
407
+ /// - copy
408
+ /// - paste
409
+ /// - assert that shape was copied
410
+ #[tokio::test]
411
+ async fn copy_paste_single_layer_from_middle() {
412
+ let mut editor = create_editor_with_three_layers().await;
413
+
414
+ let layers_before_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();
415
+ let shape_id = editor.active_document().metadata().all_layers().nth(1).unwrap();
416
+
417
+ editor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![shape_id.to_node()] }).await;
418
+ editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal }).await;
419
+ editor
420
+ .handle_message(PortfolioMessage::PasteIntoFolder {
421
+ clipboard: Clipboard::Internal,
422
+ parent: LayerNodeIdentifier::ROOT_PARENT,
423
+ insert_index: 0,
424
+ })
425
+ .await;
426
+
427
+ let layers_after_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();
428
+
429
+ assert_eq!(layers_before_copy.len(), 3);
430
+ assert_eq!(layers_after_copy.len(), 4);
431
+
432
+ // Existing layers are unaffected
433
+ for i in 0..=2 {
434
+ assert_eq!(layers_before_copy[i], layers_after_copy[i + 1]);
435
+ }
436
+ }
437
+
438
+ #[cfg_attr(miri, ignore)]
439
+ /// - create rect, shape and ellipse
440
+ /// - select ellipse and rect
441
+ /// - copy
442
+ /// - delete
443
+ /// - create another rect
444
+ /// - paste
445
+ /// - paste
446
+ #[tokio::test]
447
+ async fn copy_paste_deleted_layers() {
448
+ let mut editor = create_editor_with_three_layers().await;
449
+ assert_eq!(editor.active_document().metadata().all_layers().count(), 3);
450
+
451
+ let layers_before_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();
452
+ let rect_id = layers_before_copy[0];
453
+ let shape_id = layers_before_copy[1];
454
+ let ellipse_id = layers_before_copy[2];
455
+
456
+ editor
457
+ .handle_message(NodeGraphMessage::SelectedNodesSet {
458
+ nodes: vec![rect_id.to_node(), ellipse_id.to_node()],
459
+ })
460
+ .await;
461
+ editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal }).await;
462
+ editor.handle_message(NodeGraphMessage::DeleteSelectedNodes { delete_children: true }).await;
463
+ editor.draw_rect(0., 800., 12., 200.).await;
464
+ editor
465
+ .handle_message(PortfolioMessage::PasteIntoFolder {
466
+ clipboard: Clipboard::Internal,
467
+ parent: LayerNodeIdentifier::ROOT_PARENT,
468
+ insert_index: 0,
469
+ })
470
+ .await;
471
+ editor
472
+ .handle_message(PortfolioMessage::PasteIntoFolder {
473
+ clipboard: Clipboard::Internal,
474
+ parent: LayerNodeIdentifier::ROOT_PARENT,
475
+ insert_index: 0,
476
+ })
477
+ .await;
478
+
479
+ let layers_after_copy = editor.active_document().metadata().all_layers().collect::<Vec<_>>();
480
+
481
+ assert_eq!(layers_before_copy.len(), 3);
482
+ assert_eq!(layers_after_copy.len(), 6);
483
+
484
+ println!("{:?} {:?}", layers_after_copy, layers_before_copy);
485
+
486
+ assert_eq!(layers_after_copy[5], shape_id);
487
+ }
488
+
489
+ #[tokio::test]
490
+ /// This test will fail when you make changes to the underlying serialization format for a document.
491
+ async fn check_if_demo_art_opens() {
492
+ use crate::messages::layout::utility_types::widget_prelude::*;
493
+
494
+ let print_problem_to_terminal_on_failure = |value: &String| {
495
+ println!();
496
+ println!("-------------------------------------------------");
497
+ println!("Failed test due to receiving a DisplayDialogError while loading a Graphite demo file.");
498
+ println!();
499
+ println!("NOTE:");
500
+ println!("Document upgrading isn't performed in tests like when opening in the actual editor.");
501
+ println!("You may need to open and re-save a document in the editor to apply its migrations.");
502
+ println!();
503
+ println!("DisplayDialogError details:");
504
+ println!();
505
+ println!("Description:");
506
+ println!("{value}");
507
+ println!("-------------------------------------------------");
508
+ println!();
509
+
510
+ panic!()
511
+ };
512
+
513
+ let mut editor = EditorTestUtils::create();
514
+
515
+ // UNCOMMENT THIS FOR RUNNING UNDER MIRI
516
+ //
517
+ // let files = [
518
+ // include_str!("../../demo-artwork/changing-seasons.graphite"),
519
+ // include_str!("../../demo-artwork/isometric-fountain.graphite"),
520
+ // include_str!("../../demo-artwork/painted-dreams.graphite"),
521
+ // include_str!("../../demo-artwork/procedural-string-lights.graphite"),
522
+ // include_str!("../../demo-artwork/parametric-dunescape.graphite"),
523
+ // include_str!("../../demo-artwork/red-dress.graphite"),
524
+ // include_str!("../../demo-artwork/valley-of-spires.graphite"),
525
+ // ];
526
+ // for (id, document_serialized_content) in files.iter().enumerate() {
527
+ // let document_name = format!("document {id}");
528
+
529
+ for (document_name, _, file_name) in crate::messages::dialog::simple_dialogs::ARTWORK {
530
+ let document_serialized_content = std::fs::read_to_string(format!("../demo-artwork/{file_name}")).unwrap();
531
+
532
+ assert_eq!(
533
+ document_serialized_content.lines().count(),
534
+ 1,
535
+ "Demo artwork '{document_name}' has more than 1 line (remember to open and re-save it in Graphite)",
536
+ );
537
+
538
+ let responses = editor.editor.handle_message(PortfolioMessage::OpenDocumentFile {
539
+ document_name: document_name.into(),
540
+ document_serialized_content,
541
+ });
542
+
543
+ // Check if the graph renders
544
+ if let Err(e) = editor.eval_graph().await {
545
+ print_problem_to_terminal_on_failure(&format!("Failed to evaluate the graph for document '{document_name}':\n{e}"));
546
+ }
547
+
548
+ for response in responses {
549
+ // Check for the existence of the file format incompatibility warning dialog after opening the test file
550
+ if let FrontendMessage::UpdateDialogColumn1 { layout_target: _, diff } = response {
551
+ if let DiffUpdate::SubLayout(sub_layout) = &diff[0].new_value {
552
+ if let LayoutGroup::Row { widgets } = &sub_layout[0] {
553
+ if let Widget::TextLabel(TextLabel { value, .. }) = &widgets[0].widget {
554
+ print_problem_to_terminal_on_failure(value);
555
+ }
556
+ }
557
+ }
558
+ }
559
+ }
560
+ }
561
+ }
562
+ }
editor/src/generate_ts_types.rs ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /// Running this test will generate a `types.ts` file at the root of the repo,
2
+ /// containing every type annotated with `specta::Type`
3
+ // #[cfg(all(test, feature = "specta-export"))]
4
+ #[ignore]
5
+ #[test]
6
+ fn generate_ts_types() {
7
+ // TODO: Un-comment this out when we figure out how to reenable the "typescript` Specta feature flag
8
+
9
+ // use crate::messages::prelude::FrontendMessage;
10
+ // use specta::ts::{export_named_datatype, BigIntExportBehavior, ExportConfig};
11
+ // use specta::{NamedType, TypeMap};
12
+ // use std::fs::File;
13
+ // use std::io::Write;
14
+
15
+ // let config = ExportConfig::new().bigint(BigIntExportBehavior::Number);
16
+
17
+ // let mut type_map = TypeMap::default();
18
+
19
+ // let datatype = FrontendMessage::definition_named_data_type(&mut type_map);
20
+
21
+ // let mut export = String::new();
22
+
23
+ // export += &export_named_datatype(&config, &datatype, &type_map).unwrap();
24
+
25
+ // type_map
26
+ // .iter()
27
+ // .map(|(_, v)| v)
28
+ // .flat_map(|v| export_named_datatype(&config, v, &type_map))
29
+ // .for_each(|e| export += &format!("\n\n{e}"));
30
+
31
+ // let mut file = File::create("../types.ts").unwrap();
32
+
33
+ // write!(file, "{export}").ok();
34
+ }
editor/src/lib.rs ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ extern crate graphite_proc_macros;
2
+
3
+ // `macro_use` puts these macros into scope for all descendant code files
4
+ #[macro_use]
5
+ mod macros;
6
+ mod generate_ts_types;
7
+ #[macro_use]
8
+ extern crate log;
9
+
10
+ pub mod application;
11
+ pub mod consts;
12
+ pub mod dispatcher;
13
+ pub mod messages;
14
+ pub mod node_graph_executor;
15
+ #[cfg(test)]
16
+ pub mod test_utils;
17
+ pub mod utility_traits;
editor/src/macros.rs ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /// Syntax sugar for initializing an `ActionList`
2
+ ///
3
+ /// # Example
4
+ ///
5
+ /// ```ignore
6
+ /// actions!(DocumentMessage::Undo, DocumentMessage::Redo);
7
+ /// ```
8
+ ///
9
+ /// expands to:
10
+ /// ```ignore
11
+ /// vec![vec![DocumentMessage::Undo, DocumentMessage::Redo]];
12
+ /// ```
13
+ ///
14
+ /// and
15
+ /// ```ignore
16
+ /// actions!(DocumentMessage;
17
+ /// Undo,
18
+ /// Redo,
19
+ /// );
20
+ /// ```
21
+ ///
22
+ /// expands to:
23
+ /// ```ignore
24
+ /// vec![vec![DocumentMessage::Undo, DocumentMessage::Redo]];
25
+ /// ```
26
+ ///
27
+ macro_rules! actions {
28
+ ($($v:expr_2021),* $(,)?) => {{
29
+ vec![$(vec![$v.into()]),*]
30
+ }};
31
+
32
+ ($name:ident; $($v:ident),* $(,)?) => {{
33
+ vec![vec![$(($name::$v).into()),*]]
34
+ }};
35
+ }
36
+
37
+ /// Does the same thing as the `actions!` macro but wraps everything in:
38
+ ///
39
+ /// ```ignore
40
+ /// fn actions(&self) -> ActionList {
41
+ /// actions!(…)
42
+ /// }
43
+ /// ```
44
+ macro_rules! advertise_actions {
45
+ ($($v:expr_2021),* $(,)?) => {
46
+ fn actions(&self) -> $crate::utility_traits::ActionList {
47
+ actions!($($v),*)
48
+ }
49
+ };
50
+
51
+ ($name:ident; $($v:ident),* $(,)?) => {
52
+ fn actions(&self) -> $crate::utility_traits::ActionList {
53
+ actions!($name; $($v),*)
54
+ }
55
+ }
56
+ }
editor/src/messages/animation/animation_message.rs ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::messages::prelude::*;
2
+
3
+ use super::animation_message_handler::AnimationTimeMode;
4
+
5
+ #[impl_message(Message, Animation)]
6
+ #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
7
+ pub enum AnimationMessage {
8
+ ToggleLivePreview,
9
+ EnableLivePreview,
10
+ DisableLivePreview,
11
+ RestartAnimation,
12
+ SetFrameIndex(f64),
13
+ SetTime(f64),
14
+ UpdateTime,
15
+ IncrementFrameCounter,
16
+ SetAnimationTimeMode(AnimationTimeMode),
17
+ }
editor/src/messages/animation/animation_message_handler.rs ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use std::time::Duration;
2
+
3
+ use crate::messages::prelude::*;
4
+
5
+ use super::TimingInformation;
6
+
7
+ #[derive(PartialEq, Clone, Default, Debug, serde::Serialize, serde::Deserialize)]
8
+ pub enum AnimationTimeMode {
9
+ #[default]
10
+ TimeBased,
11
+ FrameBased,
12
+ }
13
+
14
+ #[derive(Default, Debug, Clone, PartialEq)]
15
+ enum AnimationState {
16
+ #[default]
17
+ Stopped,
18
+ Playing {
19
+ start: f64,
20
+ },
21
+ Paused {
22
+ start: f64,
23
+ pause_time: f64,
24
+ },
25
+ }
26
+
27
+ #[derive(Default, Debug, Clone, PartialEq)]
28
+ pub struct AnimationMessageHandler {
29
+ /// Used to re-send the UI on the next frame after playback starts
30
+ live_preview_recently_zero: bool,
31
+ timestamp: f64,
32
+ frame_index: f64,
33
+ animation_state: AnimationState,
34
+ fps: f64,
35
+ animation_time_mode: AnimationTimeMode,
36
+ }
37
+ impl AnimationMessageHandler {
38
+ pub(crate) fn timing_information(&self) -> TimingInformation {
39
+ let animation_time = self.timestamp - self.animation_start();
40
+ let animation_time = match self.animation_time_mode {
41
+ AnimationTimeMode::TimeBased => Duration::from_millis(animation_time as u64),
42
+ AnimationTimeMode::FrameBased => Duration::from_secs((self.frame_index / self.fps) as u64),
43
+ };
44
+ TimingInformation { time: self.timestamp, animation_time }
45
+ }
46
+
47
+ pub(crate) fn animation_start(&self) -> f64 {
48
+ match self.animation_state {
49
+ AnimationState::Stopped => self.timestamp,
50
+ AnimationState::Playing { start } => start,
51
+ AnimationState::Paused { start, pause_time } => start + self.timestamp - pause_time,
52
+ }
53
+ }
54
+
55
+ pub fn is_playing(&self) -> bool {
56
+ matches!(self.animation_state, AnimationState::Playing { .. })
57
+ }
58
+ }
59
+
60
+ impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
61
+ fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque<Message>, _data: ()) {
62
+ match message {
63
+ AnimationMessage::ToggleLivePreview => match self.animation_state {
64
+ AnimationState::Stopped => responses.add(AnimationMessage::EnableLivePreview),
65
+ AnimationState::Playing { .. } => responses.add(AnimationMessage::DisableLivePreview),
66
+ AnimationState::Paused { .. } => responses.add(AnimationMessage::EnableLivePreview),
67
+ },
68
+ AnimationMessage::EnableLivePreview => {
69
+ self.animation_state = AnimationState::Playing { start: self.animation_start() };
70
+
71
+ // Update the restart and pause/play buttons
72
+ responses.add(PortfolioMessage::UpdateDocumentWidgets);
73
+ }
74
+ AnimationMessage::DisableLivePreview => {
75
+ match self.animation_state {
76
+ AnimationState::Stopped => (),
77
+ AnimationState::Playing { start } => self.animation_state = AnimationState::Paused { start, pause_time: self.timestamp },
78
+ AnimationState::Paused { .. } => (),
79
+ }
80
+
81
+ // Update the restart and pause/play buttons
82
+ responses.add(PortfolioMessage::UpdateDocumentWidgets);
83
+ }
84
+ AnimationMessage::SetFrameIndex(frame) => {
85
+ self.frame_index = frame;
86
+ responses.add(PortfolioMessage::SubmitActiveGraphRender);
87
+ // Update the restart and pause/play buttons
88
+ responses.add(PortfolioMessage::UpdateDocumentWidgets);
89
+ }
90
+ AnimationMessage::SetTime(time) => {
91
+ self.timestamp = time;
92
+ responses.add(AnimationMessage::UpdateTime);
93
+ }
94
+ AnimationMessage::IncrementFrameCounter => {
95
+ if self.is_playing() {
96
+ self.frame_index += 1.;
97
+ responses.add(AnimationMessage::UpdateTime);
98
+ }
99
+ }
100
+ AnimationMessage::UpdateTime => {
101
+ if self.is_playing() {
102
+ responses.add(PortfolioMessage::SubmitActiveGraphRender);
103
+
104
+ if self.live_preview_recently_zero {
105
+ // Update the restart and pause/play buttons
106
+ responses.add(PortfolioMessage::UpdateDocumentWidgets);
107
+ self.live_preview_recently_zero = false;
108
+ }
109
+ }
110
+ }
111
+ AnimationMessage::RestartAnimation => {
112
+ self.frame_index = 0.;
113
+ self.animation_state = match self.animation_state {
114
+ AnimationState::Playing { .. } => AnimationState::Playing { start: self.timestamp },
115
+ _ => AnimationState::Stopped,
116
+ };
117
+ self.live_preview_recently_zero = true;
118
+ responses.add(PortfolioMessage::SubmitActiveGraphRender);
119
+ // Update the restart and pause/play buttons
120
+ responses.add(PortfolioMessage::UpdateDocumentWidgets);
121
+ }
122
+ AnimationMessage::SetAnimationTimeMode(animation_time_mode) => {
123
+ self.animation_time_mode = animation_time_mode;
124
+ }
125
+ }
126
+ }
127
+
128
+ advertise_actions!(AnimationMessageDiscriminant;
129
+ ToggleLivePreview,
130
+ SetFrameIndex,
131
+ RestartAnimation,
132
+ );
133
+ }
editor/src/messages/animation/mod.rs ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ mod animation_message;
2
+ mod animation_message_handler;
3
+
4
+ #[doc(inline)]
5
+ pub use animation_message::{AnimationMessage, AnimationMessageDiscriminant};
6
+ #[doc(inline)]
7
+ pub use animation_message_handler::AnimationMessageHandler;
8
+
9
+ pub use graphene_std::application_io::TimingInformation;
editor/src/messages/broadcast/broadcast_event.rs ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::messages::prelude::*;
2
+
3
+ #[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, Hash)]
4
+ #[impl_message(Message, BroadcastMessage, TriggerEvent)]
5
+ pub enum BroadcastEvent {
6
+ /// Triggered by requestAnimationFrame in JS
7
+ AnimationFrame,
8
+ CanvasTransformed,
9
+ ToolAbort,
10
+ SelectionChanged,
11
+ WorkingColorChanged,
12
+ }
editor/src/messages/broadcast/broadcast_message.rs ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::messages::prelude::*;
2
+
3
+ #[impl_message(Message, Broadcast)]
4
+ #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
5
+ pub enum BroadcastMessage {
6
+ // Sub-messages
7
+ #[child]
8
+ TriggerEvent(BroadcastEvent),
9
+
10
+ // Messages
11
+ SubscribeEvent {
12
+ on: BroadcastEvent,
13
+ send: Box<Message>,
14
+ },
15
+ UnsubscribeEvent {
16
+ on: BroadcastEvent,
17
+ message: Box<Message>,
18
+ },
19
+ }
editor/src/messages/broadcast/broadcast_message_handler.rs ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::messages::prelude::*;
2
+
3
+ #[derive(Debug, Clone, Default)]
4
+ pub struct BroadcastMessageHandler {
5
+ listeners: HashMap<BroadcastEvent, Vec<Message>>,
6
+ }
7
+
8
+ impl MessageHandler<BroadcastMessage, ()> for BroadcastMessageHandler {
9
+ fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque<Message>, _data: ()) {
10
+ match message {
11
+ // Sub-messages
12
+ BroadcastMessage::TriggerEvent(event) => {
13
+ for message in self.listeners.entry(event).or_default() {
14
+ responses.add_front(message.clone())
15
+ }
16
+ }
17
+
18
+ // Messages
19
+ BroadcastMessage::SubscribeEvent { on, send } => self.listeners.entry(on).or_default().push(*send),
20
+ BroadcastMessage::UnsubscribeEvent { on, message } => self.listeners.entry(on).or_default().retain(|msg| *msg != *message),
21
+ }
22
+ }
23
+
24
+ fn actions(&self) -> ActionList {
25
+ actions!(BroadcastEventDiscriminant;)
26
+ }
27
+ }
editor/src/messages/broadcast/mod.rs ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ mod broadcast_message;
2
+ mod broadcast_message_handler;
3
+
4
+ pub mod broadcast_event;
5
+
6
+ #[doc(inline)]
7
+ pub use broadcast_message::{BroadcastMessage, BroadcastMessageDiscriminant};
8
+ #[doc(inline)]
9
+ pub use broadcast_message_handler::BroadcastMessageHandler;
editor/src/messages/debug/debug_message.rs ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::messages::prelude::*;
2
+
3
+ #[impl_message(Message, Debug)]
4
+ #[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
5
+ pub enum DebugMessage {
6
+ ToggleTraceLogs,
7
+ MessageOff,
8
+ MessageNames,
9
+ MessageContents,
10
+ }
editor/src/messages/debug/debug_message_handler.rs ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::utility_types::MessageLoggingVerbosity;
2
+ use crate::messages::prelude::*;
3
+
4
+ #[derive(Debug, Default)]
5
+ pub struct DebugMessageHandler {
6
+ pub message_logging_verbosity: MessageLoggingVerbosity,
7
+ }
8
+
9
+ impl MessageHandler<DebugMessage, ()> for DebugMessageHandler {
10
+ fn process_message(&mut self, message: DebugMessage, responses: &mut VecDeque<Message>, _data: ()) {
11
+ match message {
12
+ DebugMessage::ToggleTraceLogs => {
13
+ if log::max_level() == log::LevelFilter::Debug {
14
+ log::set_max_level(log::LevelFilter::Trace);
15
+ } else {
16
+ log::set_max_level(log::LevelFilter::Debug);
17
+ }
18
+
19
+ // Refresh the checkmark beside the menu entry for this
20
+ responses.add(MenuBarMessage::SendLayout);
21
+ }
22
+ DebugMessage::MessageOff => {
23
+ self.message_logging_verbosity = MessageLoggingVerbosity::Off;
24
+
25
+ // Refresh the checkmark beside the menu entry for this
26
+ responses.add(MenuBarMessage::SendLayout);
27
+ }
28
+ DebugMessage::MessageNames => {
29
+ self.message_logging_verbosity = MessageLoggingVerbosity::Names;
30
+
31
+ // Refresh the checkmark beside the menu entry for this
32
+ responses.add(MenuBarMessage::SendLayout);
33
+ }
34
+ DebugMessage::MessageContents => {
35
+ self.message_logging_verbosity = MessageLoggingVerbosity::Contents;
36
+
37
+ // Refresh the checkmark beside the menu entry for this
38
+ responses.add(MenuBarMessage::SendLayout);
39
+ }
40
+ }
41
+ }
42
+
43
+ advertise_actions!(DebugMessageDiscriminant;
44
+ ToggleTraceLogs,
45
+ MessageOff,
46
+ MessageNames,
47
+ MessageContents,
48
+ );
49
+ }
editor/src/messages/debug/mod.rs ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ mod debug_message;
2
+ mod debug_message_handler;
3
+
4
+ pub mod utility_types;
5
+
6
+ #[doc(inline)]
7
+ pub use debug_message::{DebugMessage, DebugMessageDiscriminant};
8
+ #[doc(inline)]
9
+ pub use debug_message_handler::DebugMessageHandler;
editor/src/messages/debug/utility_types.rs ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ #[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
2
+ pub enum MessageLoggingVerbosity {
3
+ #[default]
4
+ Off,
5
+ Names,
6
+ Contents,
7
+ }
editor/src/messages/dialog/dialog_message.rs ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::messages::prelude::*;
2
+
3
+ #[impl_message(Message, Dialog)]
4
+ #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
5
+ pub enum DialogMessage {
6
+ // Sub-messages
7
+ #[child]
8
+ ExportDialog(ExportDialogMessage),
9
+ #[child]
10
+ NewDocumentDialog(NewDocumentDialogMessage),
11
+ #[child]
12
+ PreferencesDialog(PreferencesDialogMessage),
13
+
14
+ // Messages
15
+ CloseAllDocumentsWithConfirmation,
16
+ CloseDialogAndThen {
17
+ followups: Vec<Message>,
18
+ },
19
+ DisplayDialogError {
20
+ title: String,
21
+ description: String,
22
+ },
23
+ RequestAboutGraphiteDialog,
24
+ RequestAboutGraphiteDialogWithLocalizedCommitDate {
25
+ localized_commit_date: String,
26
+ localized_commit_year: String,
27
+ },
28
+ RequestComingSoonDialog {
29
+ issue: Option<u32>,
30
+ },
31
+ RequestDemoArtworkDialog,
32
+ RequestExportDialog,
33
+ RequestLicensesDialogWithLocalizedCommitDate {
34
+ localized_commit_year: String,
35
+ },
36
+ RequestNewDocumentDialog,
37
+ RequestPreferencesDialog,
38
+ }
editor/src/messages/dialog/dialog_message_handler.rs ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArtworkDialog, LicensesDialog};
2
+ use crate::messages::layout::utility_types::widget_prelude::*;
3
+ use crate::messages::prelude::*;
4
+
5
+ pub struct DialogMessageData<'a> {
6
+ pub portfolio: &'a PortfolioMessageHandler,
7
+ pub preferences: &'a PreferencesMessageHandler,
8
+ }
9
+
10
+ /// Stores the dialogs which require state. These are the ones that have their own message handlers, and are not the ones defined in `simple_dialogs`.
11
+ #[derive(Debug, Default, Clone)]
12
+ pub struct DialogMessageHandler {
13
+ export_dialog: ExportDialogMessageHandler,
14
+ new_document_dialog: NewDocumentDialogMessageHandler,
15
+ preferences_dialog: PreferencesDialogMessageHandler,
16
+ }
17
+
18
+ impl MessageHandler<DialogMessage, DialogMessageData<'_>> for DialogMessageHandler {
19
+ fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque<Message>, data: DialogMessageData) {
20
+ let DialogMessageData { portfolio, preferences } = data;
21
+
22
+ match message {
23
+ DialogMessage::ExportDialog(message) => self.export_dialog.process_message(message, responses, ExportDialogMessageData { portfolio }),
24
+ DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_message(message, responses, ()),
25
+ DialogMessage::PreferencesDialog(message) => self.preferences_dialog.process_message(message, responses, PreferencesDialogMessageData { preferences }),
26
+
27
+ DialogMessage::CloseAllDocumentsWithConfirmation => {
28
+ let dialog = simple_dialogs::CloseAllDocumentsDialog {
29
+ unsaved_document_names: portfolio.unsaved_document_names(),
30
+ };
31
+ dialog.send_dialog_to_frontend(responses);
32
+ }
33
+ DialogMessage::CloseDialogAndThen { followups } => {
34
+ for message in followups.into_iter() {
35
+ responses.add(message);
36
+ }
37
+
38
+ // This come after followups, so that the followups (which can cause the dialog to open) happen first, then we close it afterwards.
39
+ // If it comes before, the dialog reopens (and appears to not close at all).
40
+ responses.add(FrontendMessage::DisplayDialogDismiss);
41
+ }
42
+ DialogMessage::DisplayDialogError { title, description } => {
43
+ let dialog = simple_dialogs::ErrorDialog { title, description };
44
+ dialog.send_dialog_to_frontend(responses);
45
+ }
46
+ DialogMessage::RequestAboutGraphiteDialog => {
47
+ responses.add(FrontendMessage::TriggerAboutGraphiteLocalizedCommitDate {
48
+ commit_date: env!("GRAPHITE_GIT_COMMIT_DATE").into(),
49
+ });
50
+ }
51
+ DialogMessage::RequestAboutGraphiteDialogWithLocalizedCommitDate {
52
+ localized_commit_date,
53
+ localized_commit_year,
54
+ } => {
55
+ let dialog = AboutGraphiteDialog {
56
+ localized_commit_date,
57
+ localized_commit_year,
58
+ };
59
+
60
+ dialog.send_dialog_to_frontend(responses);
61
+ }
62
+ DialogMessage::RequestComingSoonDialog { issue } => {
63
+ let dialog = ComingSoonDialog { issue };
64
+ dialog.send_dialog_to_frontend(responses);
65
+ }
66
+ DialogMessage::RequestDemoArtworkDialog => {
67
+ let dialog = DemoArtworkDialog;
68
+ dialog.send_dialog_to_frontend(responses);
69
+ }
70
+ DialogMessage::RequestExportDialog => {
71
+ if let Some(document) = portfolio.active_document() {
72
+ let artboards = document
73
+ .metadata()
74
+ .all_layers()
75
+ .filter(|&layer| document.network_interface.is_artboard(&layer.to_node(), &[]))
76
+ .map(|layer| {
77
+ let name = document
78
+ .network_interface
79
+ .node_metadata(&layer.to_node(), &[])
80
+ .map(|node| node.persistent_metadata.display_name.clone())
81
+ .and_then(|name| if name.is_empty() { None } else { Some(name) })
82
+ .unwrap_or_else(|| "Artboard".to_string());
83
+ (layer, name)
84
+ })
85
+ .collect();
86
+
87
+ self.export_dialog.artboards = artboards;
88
+ self.export_dialog.has_selection = document.network_interface.selected_nodes().selected_layers(document.metadata()).next().is_some();
89
+ self.export_dialog.send_dialog_to_frontend(responses);
90
+ }
91
+ }
92
+ DialogMessage::RequestLicensesDialogWithLocalizedCommitDate { localized_commit_year } => {
93
+ let dialog = LicensesDialog { localized_commit_year };
94
+
95
+ dialog.send_dialog_to_frontend(responses);
96
+ }
97
+ DialogMessage::RequestNewDocumentDialog => {
98
+ self.new_document_dialog = NewDocumentDialogMessageHandler {
99
+ name: portfolio.generate_new_document_name(),
100
+ infinite: false,
101
+ dimensions: glam::UVec2::new(1920, 1080),
102
+ };
103
+ self.new_document_dialog.send_dialog_to_frontend(responses);
104
+ }
105
+ DialogMessage::RequestPreferencesDialog => {
106
+ self.preferences_dialog = PreferencesDialogMessageHandler {};
107
+ self.preferences_dialog.send_dialog_to_frontend(responses, preferences);
108
+ }
109
+ }
110
+ }
111
+
112
+ advertise_actions!(DialogMessageDiscriminant;
113
+ CloseAllDocumentsWithConfirmation,
114
+ RequestExportDialog,
115
+ RequestNewDocumentDialog,
116
+ RequestPreferencesDialog,
117
+ );
118
+ }
editor/src/messages/dialog/export_dialog/export_dialog_message.rs ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ use crate::messages::frontend::utility_types::{ExportBounds, FileType};
2
+ use crate::messages::prelude::*;
3
+
4
+ #[impl_message(Message, DialogMessage, ExportDialog)]
5
+ #[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
6
+ pub enum ExportDialogMessage {
7
+ FileType(FileType),
8
+ ScaleFactor(f64),
9
+ TransparentBackground(bool),
10
+ ExportBounds(ExportBounds),
11
+
12
+ Submit,
13
+ }