mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-25 04:58:12 +08:00
Merge branch 'master' into fix-range
This commit is contained in:
@@ -48,11 +48,9 @@ jobs:
|
|||||||
rustc --version
|
rustc --version
|
||||||
|
|
||||||
- name: ✂ Replace template in <head> of index.html
|
- name: ✂ Replace template in <head> of index.html
|
||||||
if: github.ref != 'refs/heads/master'
|
|
||||||
env:
|
|
||||||
INDEX_HTML_HEAD_REPLACEMENT: ""
|
|
||||||
run: |
|
run: |
|
||||||
# Remove the INDEX_HTML_HEAD_REPLACEMENT environment variable for build links (not master deploys)
|
# Remove the INDEX_HTML_HEAD_REPLACEMENT environment variable for build links (not master deploys)
|
||||||
|
git rev-parse --abbrev-ref HEAD | grep master > /dev/null || export INDEX_HTML_HEAD_REPLACEMENT=""
|
||||||
sed -i "s|<!-- INDEX_HTML_HEAD_REPLACEMENT -->|$INDEX_HTML_HEAD_REPLACEMENT|" frontend/index.html
|
sed -i "s|<!-- INDEX_HTML_HEAD_REPLACEMENT -->|$INDEX_HTML_HEAD_REPLACEMENT|" frontend/index.html
|
||||||
|
|
||||||
- name: 🌐 Build Graphite web code
|
- name: 🌐 Build Graphite web code
|
||||||
|
|||||||
@@ -44,6 +44,34 @@ jobs:
|
|||||||
# Remove the INDEX_HTML_HEAD_INCLUSION environment variable for build links (not master deploys)
|
# Remove the INDEX_HTML_HEAD_INCLUSION environment variable for build links (not master deploys)
|
||||||
git rev-parse --abbrev-ref HEAD | grep master > /dev/null || export INDEX_HTML_HEAD_INCLUSION=""
|
git rev-parse --abbrev-ref HEAD | grep master > /dev/null || export INDEX_HTML_HEAD_INCLUSION=""
|
||||||
|
|
||||||
|
- name: 💿 Obtain cache of auto-generated code docs artifacts
|
||||||
|
id: cache-website-code-docs
|
||||||
|
uses: actions/cache/restore@v3
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
key: website-code-docs
|
||||||
|
|
||||||
|
- name: 📁 Fallback in case auto-generated code docs artifacts weren't cached
|
||||||
|
if: steps.cache-website-code-docs.outputs.cache-hit != 'true'
|
||||||
|
run: |
|
||||||
|
echo "🦀 Initial system version of Rust:"
|
||||||
|
rustc --version
|
||||||
|
rustup update stable
|
||||||
|
echo "🦀 Latest updated version of Rust:"
|
||||||
|
rustc --version
|
||||||
|
cargo test --package graphite-editor --lib -- messages::message::test::generate_message_tree
|
||||||
|
mkdir artifacts
|
||||||
|
mv hierarchical_message_system_tree.txt artifacts/hierarchical_message_system_tree.txt
|
||||||
|
|
||||||
|
- name: 🚚 Move `artifacts` contents to `website/other/editor-structure`
|
||||||
|
run: |
|
||||||
|
mv artifacts/* website/other/editor-structure
|
||||||
|
|
||||||
|
- name: 🔧 Build auto-generated code docs artifacts into HTML
|
||||||
|
run: |
|
||||||
|
cd website/other/editor-structure
|
||||||
|
node generate.js hierarchical_message_system_tree.txt replacement.html
|
||||||
|
|
||||||
- name: 🌐 Build Graphite website with Zola
|
- name: 🌐 Build Graphite website with Zola
|
||||||
env:
|
env:
|
||||||
MODE: prod
|
MODE: prod
|
||||||
@@ -84,29 +112,6 @@ jobs:
|
|||||||
mkdir -p website/public
|
mkdir -p website/public
|
||||||
mv website/other/dist/* website/public
|
mv website/other/dist/* website/public
|
||||||
|
|
||||||
- name: 💿 Obtain cache of auto-generated code docs artifacts
|
|
||||||
id: cache-website-code-docs
|
|
||||||
uses: actions/cache/restore@v3
|
|
||||||
with:
|
|
||||||
path: artifacts
|
|
||||||
key: website-code-docs
|
|
||||||
|
|
||||||
- name: 📁 Fallback in case auto-generated code docs artifacts weren't cached
|
|
||||||
if: steps.cache-website-code-docs.outputs.cache-hit != 'true'
|
|
||||||
run: |
|
|
||||||
echo "🦀 Initial system version of Rust:"
|
|
||||||
rustc --version
|
|
||||||
rustup update stable
|
|
||||||
echo "🦀 Latest updated version of Rust:"
|
|
||||||
rustc --version
|
|
||||||
cargo test --package graphite-editor --lib -- messages::message::test::generate_message_tree
|
|
||||||
mkdir artifacts
|
|
||||||
mv hierarchical_message_system_tree.txt artifacts/hierarchical_message_system_tree.txt
|
|
||||||
|
|
||||||
- name: 🚚 Move `artifacts` contents to `website/public`
|
|
||||||
run: |
|
|
||||||
mv artifacts/* website/public
|
|
||||||
|
|
||||||
- name: 📤 Publish to Cloudflare Pages
|
- name: 📤 Publish to Cloudflare Pages
|
||||||
id: cloudflare
|
id: cloudflare
|
||||||
uses: cloudflare/pages-action@1
|
uses: cloudflare/pages-action@1
|
||||||
|
|||||||
Generated
+18
-3
@@ -1,5 +1,19 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
|
"flake-compat": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1733328505,
|
||||||
|
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
|
||||||
|
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
|
||||||
|
"revCount": 69,
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.1.0/01948eb7-9cba-704f-bbf3-3fa956735b52/source.tar.gz"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "tarball",
|
||||||
|
"url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
|
||||||
|
}
|
||||||
|
},
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
@@ -52,6 +66,7 @@
|
|||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
|
"flake-compat": "flake-compat",
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"nixpkgs-unstable": "nixpkgs-unstable",
|
"nixpkgs-unstable": "nixpkgs-unstable",
|
||||||
@@ -65,11 +80,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1748399823,
|
"lastModified": 1753238793,
|
||||||
"narHash": "sha256-kahD8D5hOXOsGbNdoLLnqCL887cjHkx98Izc37nDjlA=",
|
"narHash": "sha256-jmQeEpgX+++MEgrcikcwoSiI7vDZWLP0gci7XiWb9uQ=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "d68a69dc71bc19beb3479800392112c2f6218159",
|
"rev": "0ad7ab4ca8e83febf147197e65c006dff60623ab",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
+40
-27
@@ -1,14 +1,12 @@
|
|||||||
# This is a helper file for people using NixOS as their operating system.
|
# This is a helper file for people using NixOS as their operating system.
|
||||||
# If you don't know what this file does, you can safely ignore it.
|
# If you don't know what this file does, you can safely ignore it.
|
||||||
# This file defines both the development environment for the project.
|
# This file defines the reproducible development environment for the project.
|
||||||
#
|
#
|
||||||
# Development Environment:
|
# Development Environment:
|
||||||
# - Provides all necessary tools for Rust/WASM development
|
# - Provides all necessary tools for Rust/Wasm development
|
||||||
# - Includes Tauri dependencies for desktop app development
|
|
||||||
# - Sets up profiling and debugging tools
|
# - Sets up profiling and debugging tools
|
||||||
# - Configures mold as the default linker for faster builds
|
# - Configures mold as the default linker for faster builds
|
||||||
#
|
#
|
||||||
#
|
|
||||||
# Usage:
|
# Usage:
|
||||||
# - Development shell: `nix develop`
|
# - Development shell: `nix develop`
|
||||||
# - Run in dev shell with direnv: add `use flake` to .envrc
|
# - Run in dev shell with direnv: add `use flake` to .envrc
|
||||||
@@ -16,7 +14,6 @@
|
|||||||
description = "Development environment and build configuration";
|
description = "Development environment and build configuration";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
# 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
|
|
||||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
|
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||||
rust-overlay = {
|
rust-overlay = {
|
||||||
@@ -24,6 +21,9 @@
|
|||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
|
||||||
|
# This is used to provide a identical development shell at `shell.nix` for users that do not use flakes
|
||||||
|
flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = { nixpkgs, nixpkgs-unstable, rust-overlay, flake-utils, ... }:
|
outputs = { nixpkgs, nixpkgs-unstable, rust-overlay, flake-utils, ... }:
|
||||||
@@ -36,34 +36,50 @@
|
|||||||
pkgs-unstable = import nixpkgs-unstable {
|
pkgs-unstable = import nixpkgs-unstable {
|
||||||
inherit system overlays;
|
inherit system overlays;
|
||||||
};
|
};
|
||||||
|
|
||||||
rustc-wasm = pkgs.rust-bin.stable.latest.default.override {
|
rustc-wasm = pkgs.rust-bin.stable.latest.default.override {
|
||||||
targets = [ "wasm32-unknown-unknown" ];
|
targets = [ "wasm32-unknown-unknown" ];
|
||||||
extensions = [ "rust-src" "rust-analyzer" "clippy" "cargo" ];
|
extensions = [ "rust-src" "rust-analyzer" "clippy" "cargo" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
libcef = pkgs.libcef.overrideAttrs (finalAttrs: previousAttrs: {
|
||||||
|
version = "138.0.26";
|
||||||
|
gitRevision = "84f2d27";
|
||||||
|
chromiumVersion = "138.0.7204.158";
|
||||||
|
srcHash = "sha256-d9jQJX7rgdoHfROD3zmOdMSesRdKE3slB5ZV+U2wlbQ=";
|
||||||
|
|
||||||
|
__intentionallyOverridingVersion = true;
|
||||||
|
|
||||||
|
postInstall = ''
|
||||||
|
strip $out/lib/*
|
||||||
|
'';
|
||||||
|
});
|
||||||
|
|
||||||
|
libcefPath = pkgs.runCommand "libcef-path" {} ''
|
||||||
|
mkdir -p $out
|
||||||
|
|
||||||
|
ln -s ${libcef}/include $out/include
|
||||||
|
find ${libcef}/lib -type f -name "*" -exec ln -s {} $out/ \;
|
||||||
|
find ${libcef}/libexec -type f -name "*" -exec ln -s {} $out/ \;
|
||||||
|
cp -r ${libcef}/share/cef/* $out/
|
||||||
|
|
||||||
|
echo '${builtins.toJSON {
|
||||||
|
type = "minimal";
|
||||||
|
name = builtins.baseNameOf libcef.src.url;
|
||||||
|
sha1 = "";
|
||||||
|
}}' > $out/archive.json
|
||||||
|
'';
|
||||||
|
|
||||||
# Shared build inputs - system libraries that need to be in LD_LIBRARY_PATH
|
# Shared build inputs - system libraries that need to be in LD_LIBRARY_PATH
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
# System libraries
|
# System libraries
|
||||||
|
wayland
|
||||||
|
wayland.dev
|
||||||
openssl
|
openssl
|
||||||
vulkan-loader
|
vulkan-loader
|
||||||
mesa
|
mesa
|
||||||
libraw
|
libraw
|
||||||
|
libGL
|
||||||
|
|
||||||
# Tauri dependencies: keep in sync with https://v2.tauri.app/start/prerequisites/#system-dependencies (under the NixOS tab)
|
|
||||||
at-spi2-atk
|
|
||||||
atkmm
|
|
||||||
cairo
|
|
||||||
gdk-pixbuf
|
|
||||||
glib
|
|
||||||
gtk3
|
|
||||||
harfbuzz
|
|
||||||
librsvg
|
|
||||||
libsoup_3
|
|
||||||
pango
|
|
||||||
webkitgtk_4_1
|
|
||||||
openssl
|
|
||||||
];
|
];
|
||||||
|
|
||||||
# Development tools that don't need to be in LD_LIBRARY_PATH
|
# Development tools that don't need to be in LD_LIBRARY_PATH
|
||||||
@@ -77,7 +93,6 @@
|
|||||||
pkgs.pkg-config
|
pkgs.pkg-config
|
||||||
pkgs.git
|
pkgs.git
|
||||||
pkgs.gobject-introspection
|
pkgs.gobject-introspection
|
||||||
pkgs-unstable.cargo-tauri
|
|
||||||
pkgs-unstable.cargo-about
|
pkgs-unstable.cargo-about
|
||||||
|
|
||||||
# Linker
|
# Linker
|
||||||
@@ -88,12 +103,11 @@
|
|||||||
cargo-watch
|
cargo-watch
|
||||||
cargo-nextest
|
cargo-nextest
|
||||||
cargo-expand
|
cargo-expand
|
||||||
|
|
||||||
# Profiling tools
|
# Profiling tools
|
||||||
gnuplot
|
gnuplot
|
||||||
samply
|
samply
|
||||||
cargo-flamegraph
|
cargo-flamegraph
|
||||||
|
|
||||||
];
|
];
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
@@ -101,10 +115,9 @@
|
|||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
packages = buildInputs ++ buildTools ++ devTools;
|
packages = buildInputs ++ buildTools ++ devTools;
|
||||||
|
|
||||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath buildInputs;
|
LD_LIBRARY_PATH = "${pkgs.lib.makeLibraryPath buildInputs}:${libcefPath}";
|
||||||
GIO_MODULE_DIR="${pkgs.glib-networking}/lib/gio/modules/";
|
CEF_PATH = libcefPath;
|
||||||
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";
|
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";
|
||||||
|
|
||||||
|
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
alias cargo='mold --run cargo'
|
alias cargo='mold --run cargo'
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# This is a helper file for people using NixOS as their operating system.
|
||||||
|
# If you don't know what this file does, you can safely ignore it.
|
||||||
|
|
||||||
|
# If you are using Nix as your package manager, you can run 'nix-shell .nix'
|
||||||
|
# in the root directory of the project and Nix will open a bash shell
|
||||||
|
# with all the packages needed to build and run Graphite installed.
|
||||||
|
# A shell.nix file is used in the Nix ecosystem to define a development
|
||||||
|
# environment with specific dependencies. When you enter a Nix shell using
|
||||||
|
# this file, it ensures that all the specified tools and libraries are
|
||||||
|
# available regardless of the host system's configuration. This provides
|
||||||
|
# a reproducible development environment across different machines and developers.
|
||||||
|
|
||||||
|
# You can enter the Nix shell and run Graphite like normal with:
|
||||||
|
# > npm start
|
||||||
|
# Or you can run it like this without needing to first enter the Nix shell:
|
||||||
|
# > nix-shell .nix --command "npm start"
|
||||||
|
|
||||||
|
# Uses flake compat to provide a development shell that is identical to the one defined in the flake
|
||||||
|
(import
|
||||||
|
(
|
||||||
|
let
|
||||||
|
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
|
||||||
|
nodeName = lock.nodes.root.inputs.flake-compat;
|
||||||
|
in
|
||||||
|
fetchTarball {
|
||||||
|
url = lock.nodes.${nodeName}.locked.url;
|
||||||
|
sha256 = lock.nodes.${nodeName}.locked.narHash;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
{ src = ./.; }
|
||||||
|
).shellNix
|
||||||
Generated
+676
-2689
File diff suppressed because it is too large
Load Diff
+14
-4
@@ -1,12 +1,13 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"editor",
|
"editor",
|
||||||
|
"desktop",
|
||||||
"proc-macros",
|
"proc-macros",
|
||||||
"frontend/wasm",
|
"frontend/wasm",
|
||||||
"frontend/src-tauri",
|
|
||||||
"node-graph/gapplication-io",
|
"node-graph/gapplication-io",
|
||||||
"node-graph/gbrush",
|
"node-graph/gbrush",
|
||||||
"node-graph/gcore",
|
"node-graph/gcore",
|
||||||
|
"node-graph/gcore-shaders",
|
||||||
"node-graph/gstd",
|
"node-graph/gstd",
|
||||||
"node-graph/gmath-nodes",
|
"node-graph/gmath-nodes",
|
||||||
"node-graph/gpath-bool",
|
"node-graph/gpath-bool",
|
||||||
@@ -28,6 +29,7 @@ default-members = [
|
|||||||
"frontend/wasm",
|
"frontend/wasm",
|
||||||
"node-graph/gbrush",
|
"node-graph/gbrush",
|
||||||
"node-graph/gcore",
|
"node-graph/gcore",
|
||||||
|
"node-graph/gcore-shaders",
|
||||||
"node-graph/gstd",
|
"node-graph/gstd",
|
||||||
"node-graph/gmath-nodes",
|
"node-graph/gmath-nodes",
|
||||||
"node-graph/gpath-bool",
|
"node-graph/gpath-bool",
|
||||||
@@ -50,6 +52,7 @@ path-bool = { path = "libraries/path-bool" }
|
|||||||
graphene-application-io = { path = "node-graph/gapplication-io" }
|
graphene-application-io = { path = "node-graph/gapplication-io" }
|
||||||
graphene-brush = { path = "node-graph/gbrush" }
|
graphene-brush = { path = "node-graph/gbrush" }
|
||||||
graphene-core = { path = "node-graph/gcore" }
|
graphene-core = { path = "node-graph/gcore" }
|
||||||
|
graphene-core-shaders = { path = "node-graph/gcore-shaders" }
|
||||||
graphene-math-nodes = { path = "node-graph/gmath-nodes" }
|
graphene-math-nodes = { path = "node-graph/gmath-nodes" }
|
||||||
graphene-path-bool = { path = "node-graph/gpath-bool" }
|
graphene-path-bool = { path = "node-graph/gpath-bool" }
|
||||||
graph-craft = { path = "node-graph/graph-craft" }
|
graph-craft = { path = "node-graph/graph-craft" }
|
||||||
@@ -83,7 +86,7 @@ axum = "0.8"
|
|||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
ron = "0.8"
|
ron = "0.8"
|
||||||
fastnoise-lite = "1.1"
|
fastnoise-lite = "1.1"
|
||||||
wgpu = { version = "23", features = [
|
wgpu = { version = "25.0.2", features = [
|
||||||
# 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
|
# 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
|
||||||
"fragile-send-sync-non-atomic-wasm",
|
"fragile-send-sync-non-atomic-wasm",
|
||||||
"spirv",
|
"spirv",
|
||||||
@@ -111,10 +114,10 @@ web-sys = { version = "=0.3.77", features = [
|
|||||||
"HtmlImageElement",
|
"HtmlImageElement",
|
||||||
"ImageBitmapRenderingContext",
|
"ImageBitmapRenderingContext",
|
||||||
] }
|
] }
|
||||||
winit = "0.29"
|
winit = { version = "0.30", features = ["wayland", "rwh_06"] }
|
||||||
url = "2.5"
|
url = "2.5"
|
||||||
tokio = { version = "1.29", features = ["fs", "macros", "io-std", "rt"] }
|
tokio = { version = "1.29", features = ["fs", "macros", "io-std", "rt"] }
|
||||||
vello = { git = "https://github.com/linebender/vello.git", rev = "3275ec8" } # TODO switch back to stable when a release is made
|
vello = { git = "https://github.com/linebender/vello.git" } # TODO switch back to stable when a release is made
|
||||||
resvg = "0.44"
|
resvg = "0.44"
|
||||||
usvg = "0.44"
|
usvg = "0.44"
|
||||||
rand = { version = "0.9", default-features = false, features = ["std_rng"] }
|
rand = { version = "0.9", default-features = false, features = ["std_rng"] }
|
||||||
@@ -154,12 +157,19 @@ tinyvec = { version = "1", features = ["std"] }
|
|||||||
criterion = { version = "0.5", features = ["html_reports"] }
|
criterion = { version = "0.5", features = ["html_reports"] }
|
||||||
iai-callgrind = { version = "0.12.3" }
|
iai-callgrind = { version = "0.12.3" }
|
||||||
ndarray = "0.16.1"
|
ndarray = "0.16.1"
|
||||||
|
strum = { version = "0.26.3", features = ["derive"] }
|
||||||
|
dirs = "6.0"
|
||||||
|
cef = "138.5.0"
|
||||||
|
include_dir = "0.7.4"
|
||||||
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
|
tracing = "0.1.41"
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
opt-level = 1
|
opt-level = 1
|
||||||
|
|
||||||
[profile.dev.package]
|
[profile.dev.package]
|
||||||
graphite-editor = { opt-level = 1 }
|
graphite-editor = { opt-level = 1 }
|
||||||
|
graphene-core-shaders = { opt-level = 1 }
|
||||||
graphene-core = { opt-level = 1 }
|
graphene-core = { opt-level = 1 }
|
||||||
graphene-std = { opt-level = 1 }
|
graphene-std = { opt-level = 1 }
|
||||||
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
|
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
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ accepted = [
|
|||||||
"Unicode-DFS-2016",
|
"Unicode-DFS-2016",
|
||||||
"Zlib",
|
"Zlib",
|
||||||
"NCSA",
|
"NCSA",
|
||||||
|
"bzip2-1.0.6",
|
||||||
]
|
]
|
||||||
workarounds = ["ring"]
|
workarounds = ["ring"]
|
||||||
ignore-build-dependencies = true
|
ignore-build-dependencies = true
|
||||||
|
|||||||
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
Generated
+1
-1
File diff suppressed because one or more lines are too long
@@ -28,10 +28,6 @@ targets = [
|
|||||||
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
|
#{ triple = "wasm32-unknown-unknown", features = ["atomics"] },
|
||||||
]
|
]
|
||||||
|
|
||||||
# Tauri produces too many nonsense warnings.
|
|
||||||
exclude = ["tauri", "tauri-build"]
|
|
||||||
|
|
||||||
|
|
||||||
# This section is considered when running `cargo deny check advisories`
|
# This section is considered when running `cargo deny check advisories`
|
||||||
# More documentation for the advisories section can be found here:
|
# More documentation for the advisories section can be found here:
|
||||||
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
|
# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html
|
||||||
@@ -85,6 +81,7 @@ allow = [
|
|||||||
"Unicode-DFS-2016",
|
"Unicode-DFS-2016",
|
||||||
"Zlib",
|
"Zlib",
|
||||||
"NCSA",
|
"NCSA",
|
||||||
|
"bzip2-1.0.6",
|
||||||
]
|
]
|
||||||
# The confidence threshold for detecting a license from license text.
|
# The confidence threshold for detecting a license from license text.
|
||||||
# The higher the value, the more closely the license text must be to the
|
# The higher the value, the more closely the license text must be to the
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
# Generated by Cargo
|
# Generated by Cargo
|
||||||
# will have compiled files and executables
|
# will have compiled files and executables
|
||||||
/target/
|
/target/
|
||||||
# Generated by tauri
|
|
||||||
gen/
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
[package]
|
||||||
|
name = "graphite-desktop"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Graphite Desktop"
|
||||||
|
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||||
|
license = "Apache-2.0"
|
||||||
|
repository = ""
|
||||||
|
edition = "2024"
|
||||||
|
rust-version = "1.87"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["gpu"]
|
||||||
|
gpu = ["graphite-editor/gpu"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# # Local dependencies
|
||||||
|
graphite-editor = { path = "../editor", features = [
|
||||||
|
"gpu",
|
||||||
|
"ron",
|
||||||
|
"vello",
|
||||||
|
] }
|
||||||
|
graph-craft = { workspace = true }
|
||||||
|
wgpu-executor = { workspace = true }
|
||||||
|
|
||||||
|
wgpu = { workspace = true }
|
||||||
|
winit = { workspace = true, features = ["serde"] }
|
||||||
|
thiserror = { workspace = true }
|
||||||
|
futures = { workspace = true }
|
||||||
|
cef = { workspace = true }
|
||||||
|
include_dir = { workspace = true }
|
||||||
|
tracing-subscriber = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
dirs = { workspace = true }
|
||||||
|
ron = { workspace = true}
|
||||||
|
bytemuck = { workspace = true }
|
||||||
|
glam = { workspace = true }
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
use std::fs::metadata;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let frontend_dir = format!("{}/../frontend/dist", env!("CARGO_MANIFEST_DIR"));
|
||||||
|
metadata(&frontend_dir).expect("Failed to find frontend directory. Please build the frontend first.");
|
||||||
|
metadata(format!("{}/index.html", &frontend_dir)).expect("Failed to find index.html in frontend directory.");
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed=.");
|
||||||
|
println!("cargo:rerun-if-changed=../frontend/dist");
|
||||||
|
}
|
||||||
@@ -0,0 +1,204 @@
|
|||||||
|
use crate::CustomEvent;
|
||||||
|
use crate::WindowSize;
|
||||||
|
use crate::render::GraphicsState;
|
||||||
|
use crate::render::WgpuContext;
|
||||||
|
use graph_craft::wasm_application_io::WasmApplicationIo;
|
||||||
|
use graphite_editor::application::Editor;
|
||||||
|
use graphite_editor::messages::prelude::*;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::mpsc::Sender;
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::time::Instant;
|
||||||
|
use winit::application::ApplicationHandler;
|
||||||
|
use winit::dpi::PhysicalSize;
|
||||||
|
use winit::event::StartCause;
|
||||||
|
use winit::event::WindowEvent;
|
||||||
|
use winit::event_loop::ActiveEventLoop;
|
||||||
|
use winit::event_loop::ControlFlow;
|
||||||
|
use winit::window::Window;
|
||||||
|
use winit::window::WindowId;
|
||||||
|
|
||||||
|
use crate::cef;
|
||||||
|
|
||||||
|
pub(crate) struct WinitApp {
|
||||||
|
pub(crate) cef_context: cef::Context<cef::Initialized>,
|
||||||
|
pub(crate) window: Option<Arc<Window>>,
|
||||||
|
cef_schedule: Option<Instant>,
|
||||||
|
window_size_sender: Sender<WindowSize>,
|
||||||
|
graphics_state: Option<GraphicsState>,
|
||||||
|
wgpu_context: WgpuContext,
|
||||||
|
pub(crate) editor: Editor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WinitApp {
|
||||||
|
pub(crate) fn new(cef_context: cef::Context<cef::Initialized>, window_size_sender: Sender<WindowSize>, wgpu_context: WgpuContext) -> Self {
|
||||||
|
Self {
|
||||||
|
cef_context,
|
||||||
|
window: None,
|
||||||
|
cef_schedule: Some(Instant::now()),
|
||||||
|
graphics_state: None,
|
||||||
|
window_size_sender,
|
||||||
|
wgpu_context,
|
||||||
|
editor: Editor::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatch_message(&mut self, message: Message) {
|
||||||
|
let responses = self.editor.handle_message(message);
|
||||||
|
self.send_messages_to_editor(responses);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_messages_to_editor(&mut self, responses: Vec<FrontendMessage>) {
|
||||||
|
if responses.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Ok(message) = ron::to_string(&responses) else {
|
||||||
|
tracing::error!("Failed to serialize Messages");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.cef_context.send_web_message(message.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApplicationHandler<CustomEvent> for WinitApp {
|
||||||
|
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
|
// Set a timeout in case we miss any cef schedule requests
|
||||||
|
let timeout = Instant::now() + Duration::from_millis(10);
|
||||||
|
let wait_until = timeout.min(self.cef_schedule.unwrap_or(timeout));
|
||||||
|
self.cef_context.work();
|
||||||
|
|
||||||
|
event_loop.set_control_flow(ControlFlow::WaitUntil(wait_until));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_events(&mut self, _event_loop: &ActiveEventLoop, cause: StartCause) {
|
||||||
|
if let Some(schedule) = self.cef_schedule
|
||||||
|
&& schedule < Instant::now()
|
||||||
|
{
|
||||||
|
self.cef_schedule = None;
|
||||||
|
self.cef_context.work();
|
||||||
|
}
|
||||||
|
if let StartCause::ResumeTimeReached { .. } = cause {
|
||||||
|
if let Some(window) = &self.window {
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
|
||||||
|
let window = Arc::new(
|
||||||
|
event_loop
|
||||||
|
.create_window(
|
||||||
|
Window::default_attributes()
|
||||||
|
.with_title("CEF Offscreen Rendering")
|
||||||
|
.with_inner_size(winit::dpi::LogicalSize::new(1200, 800)),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
let graphics_state = GraphicsState::new(window.clone(), self.wgpu_context.clone());
|
||||||
|
|
||||||
|
self.window = Some(window);
|
||||||
|
self.graphics_state = Some(graphics_state);
|
||||||
|
|
||||||
|
tracing::info!("Winit window created and ready");
|
||||||
|
|
||||||
|
let application_io = WasmApplicationIo::new_with_context(self.wgpu_context.clone());
|
||||||
|
|
||||||
|
futures::executor::block_on(graphite_editor::node_graph_executor::replace_application_io(application_io));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn user_event(&mut self, _: &ActiveEventLoop, event: CustomEvent) {
|
||||||
|
match event {
|
||||||
|
CustomEvent::UiUpdate(texture) => {
|
||||||
|
if let Some(graphics_state) = self.graphics_state.as_mut() {
|
||||||
|
graphics_state.bind_ui_texture(&texture);
|
||||||
|
graphics_state.resize(texture.width(), texture.height());
|
||||||
|
}
|
||||||
|
if let Some(window) = &self.window {
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CustomEvent::ScheduleBrowserWork(instant) => {
|
||||||
|
if instant <= Instant::now() {
|
||||||
|
self.cef_context.work();
|
||||||
|
} else {
|
||||||
|
self.cef_schedule = Some(instant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CustomEvent::MessageReceived { message } => {
|
||||||
|
if let Message::InputPreprocessor(ipp_message) = &message {
|
||||||
|
if let Some(window) = &self.window {
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Message::InputPreprocessor(InputPreprocessorMessage::BoundsOfViewports { bounds_of_viewports }) = &message {
|
||||||
|
if let Some(graphic_state) = &mut self.graphics_state {
|
||||||
|
let window_size = self.window.as_ref().unwrap().inner_size();
|
||||||
|
let window_size = glam::Vec2::new(window_size.width as f32, window_size.height as f32);
|
||||||
|
let top_left = bounds_of_viewports[0].top_left.as_vec2() / window_size;
|
||||||
|
let bottom_right = bounds_of_viewports[0].bottom_right.as_vec2() / window_size;
|
||||||
|
let offset = top_left.to_array();
|
||||||
|
let scale = (bottom_right - top_left).recip();
|
||||||
|
graphic_state.set_viewport_offset(offset);
|
||||||
|
graphic_state.set_viewport_scale(scale.to_array());
|
||||||
|
} else {
|
||||||
|
panic!("graphics state not intialized, viewport offset might be lost");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.dispatch_message(message);
|
||||||
|
}
|
||||||
|
CustomEvent::NodeGraphRan { texture } => {
|
||||||
|
if let Some(texture) = texture
|
||||||
|
&& let Some(graphics_state) = &mut self.graphics_state
|
||||||
|
{
|
||||||
|
graphics_state.bind_viewport_texture(&texture);
|
||||||
|
}
|
||||||
|
let mut responses = VecDeque::new();
|
||||||
|
let err = self.editor.poll_node_graph_evaluation(&mut responses);
|
||||||
|
if let Err(e) = err {
|
||||||
|
if e != "No active document" {
|
||||||
|
tracing::error!("Error poling node graph: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for message in responses {
|
||||||
|
self.dispatch_message(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn window_event(&mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {
|
||||||
|
let Some(event) = self.cef_context.handle_window_event(event) else { return };
|
||||||
|
|
||||||
|
match event {
|
||||||
|
WindowEvent::CloseRequested => {
|
||||||
|
tracing::info!("The close button was pressed; stopping");
|
||||||
|
event_loop.exit();
|
||||||
|
}
|
||||||
|
WindowEvent::Resized(PhysicalSize { width, height }) => {
|
||||||
|
let _ = self.window_size_sender.send(WindowSize::new(width as usize, height as usize));
|
||||||
|
self.cef_context.notify_of_resize();
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowEvent::RedrawRequested => {
|
||||||
|
let Some(ref mut graphics_state) = self.graphics_state else { return };
|
||||||
|
// Only rerender once we have a new ui texture to display
|
||||||
|
|
||||||
|
match graphics_state.render() {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(wgpu::SurfaceError::Lost) => {
|
||||||
|
tracing::warn!("lost surface");
|
||||||
|
}
|
||||||
|
Err(wgpu::SurfaceError::OutOfMemory) => {
|
||||||
|
event_loop.exit();
|
||||||
|
}
|
||||||
|
Err(e) => tracing::error!("{:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify cef of possible input events
|
||||||
|
self.cef_context.work();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
use crate::{CustomEvent, WgpuContext, render::FrameBufferRef};
|
||||||
|
use std::{
|
||||||
|
sync::{Arc, Mutex, mpsc::Receiver},
|
||||||
|
time::Instant,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod context;
|
||||||
|
mod dirs;
|
||||||
|
mod input;
|
||||||
|
mod internal;
|
||||||
|
mod ipc;
|
||||||
|
mod scheme_handler;
|
||||||
|
mod utility;
|
||||||
|
|
||||||
|
pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError};
|
||||||
|
use winit::event_loop::EventLoopProxy;
|
||||||
|
|
||||||
|
pub(crate) trait CefEventHandler: Clone {
|
||||||
|
fn window_size(&self) -> WindowSize;
|
||||||
|
fn draw<'a>(&self, frame_buffer: FrameBufferRef<'a>);
|
||||||
|
/// Scheudule the main event loop to run the cef event loop after the timeout
|
||||||
|
/// [`_cef_browser_process_handler_t::on_schedule_message_pump_work`] for more documentation.
|
||||||
|
fn schedule_cef_message_loop_work(&self, scheduled_time: Instant);
|
||||||
|
fn receive_web_message(&self, message: &[u8]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub(crate) struct WindowSize {
|
||||||
|
pub(crate) width: usize,
|
||||||
|
pub(crate) height: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowSize {
|
||||||
|
pub(crate) fn new(width: usize, height: usize) -> Self {
|
||||||
|
Self { width, height }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct CefHandler {
|
||||||
|
window_size_receiver: Arc<Mutex<WindowSizeReceiver>>,
|
||||||
|
event_loop_proxy: EventLoopProxy<CustomEvent>,
|
||||||
|
wgpu_context: WgpuContext,
|
||||||
|
}
|
||||||
|
struct WindowSizeReceiver {
|
||||||
|
receiver: Receiver<WindowSize>,
|
||||||
|
window_size: WindowSize,
|
||||||
|
}
|
||||||
|
impl WindowSizeReceiver {
|
||||||
|
fn new(window_size_receiver: Receiver<WindowSize>) -> Self {
|
||||||
|
Self {
|
||||||
|
window_size: WindowSize { width: 1, height: 1 },
|
||||||
|
receiver: window_size_receiver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl CefHandler {
|
||||||
|
pub(crate) fn new(window_size_receiver: Receiver<WindowSize>, event_loop_proxy: EventLoopProxy<CustomEvent>, wgpu_context: WgpuContext) -> Self {
|
||||||
|
Self {
|
||||||
|
window_size_receiver: Arc::new(Mutex::new(WindowSizeReceiver::new(window_size_receiver))),
|
||||||
|
event_loop_proxy,
|
||||||
|
wgpu_context,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CefEventHandler for CefHandler {
|
||||||
|
fn window_size(&self) -> WindowSize {
|
||||||
|
let Ok(mut guard) = self.window_size_receiver.lock() else {
|
||||||
|
tracing::error!("Failed to lock window_size_receiver");
|
||||||
|
return WindowSize::new(1, 1);
|
||||||
|
};
|
||||||
|
let WindowSizeReceiver { receiver, window_size } = &mut *guard;
|
||||||
|
for new_window_size in receiver.try_iter() {
|
||||||
|
*window_size = new_window_size;
|
||||||
|
}
|
||||||
|
*window_size
|
||||||
|
}
|
||||||
|
fn draw<'a>(&self, frame_buffer: FrameBufferRef<'a>) {
|
||||||
|
let width = frame_buffer.width() as u32;
|
||||||
|
let height = frame_buffer.height() as u32;
|
||||||
|
let texture = self.wgpu_context.device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label: Some("CEF Texture"),
|
||||||
|
size: wgpu::Extent3d {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::Bgra8UnormSrgb,
|
||||||
|
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||||
|
view_formats: &[],
|
||||||
|
});
|
||||||
|
self.wgpu_context.queue.write_texture(
|
||||||
|
wgpu::TexelCopyTextureInfo {
|
||||||
|
texture: &texture,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: wgpu::Origin3d::ZERO,
|
||||||
|
aspect: wgpu::TextureAspect::All,
|
||||||
|
},
|
||||||
|
frame_buffer.buffer(),
|
||||||
|
wgpu::TexelCopyBufferLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: Some(4 * width),
|
||||||
|
rows_per_image: Some(height),
|
||||||
|
},
|
||||||
|
wgpu::Extent3d {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let _ = self.event_loop_proxy.send_event(CustomEvent::UiUpdate(texture));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn schedule_cef_message_loop_work(&self, scheduled_time: std::time::Instant) {
|
||||||
|
let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn receive_web_message(&self, message: &[u8]) {
|
||||||
|
let str = std::str::from_utf8(message).unwrap();
|
||||||
|
match ron::from_str(str) {
|
||||||
|
Ok(message) => {
|
||||||
|
let _ = self.event_loop_proxy.send_event(CustomEvent::MessageReceived { message });
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to deserialize message {:?}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
use cef::sys::{CEF_API_VERSION_LAST, cef_resultcode_t};
|
||||||
|
use cef::{App, BrowserSettings, Client, DictionaryValue, ImplBrowser, ImplBrowserHost, ImplCommandLine, RenderHandler, RequestContext, WindowInfo, browser_host_create_browser_sync, initialize};
|
||||||
|
use cef::{Browser, CefString, Settings, api_hash, args::Args, execute_process};
|
||||||
|
use thiserror::Error;
|
||||||
|
use winit::event::WindowEvent;
|
||||||
|
|
||||||
|
use crate::cef::dirs::{cef_cache_dir, cef_data_dir};
|
||||||
|
|
||||||
|
use super::input::InputState;
|
||||||
|
use super::ipc::{MessageType, SendMessage};
|
||||||
|
use super::scheme_handler::{FRONTEND_DOMAIN, GRAPHITE_SCHEME};
|
||||||
|
use super::{CefEventHandler, input};
|
||||||
|
|
||||||
|
use super::internal::{BrowserProcessAppImpl, BrowserProcessClientImpl, RenderHandlerImpl, RenderProcessAppImpl};
|
||||||
|
|
||||||
|
pub(crate) struct Setup {}
|
||||||
|
pub(crate) struct Initialized {}
|
||||||
|
pub(crate) trait ContextState {}
|
||||||
|
impl ContextState for Setup {}
|
||||||
|
impl ContextState for Initialized {}
|
||||||
|
|
||||||
|
pub(crate) struct Context<S: ContextState> {
|
||||||
|
args: Args,
|
||||||
|
pub(crate) browser: Option<Browser>,
|
||||||
|
pub(crate) input_state: InputState,
|
||||||
|
marker: std::marker::PhantomData<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context<Setup> {
|
||||||
|
pub(crate) fn new() -> Result<Context<Setup>, SetupError> {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
let _loader = {
|
||||||
|
let loader = library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), false);
|
||||||
|
assert!(loader.load());
|
||||||
|
loader
|
||||||
|
};
|
||||||
|
let _ = api_hash(CEF_API_VERSION_LAST, 0);
|
||||||
|
|
||||||
|
let args = Args::new();
|
||||||
|
let cmd = args.as_cmd_line().unwrap();
|
||||||
|
let switch = CefString::from("type");
|
||||||
|
let is_browser_process = cmd.has_switch(Some(&switch)) != 1;
|
||||||
|
|
||||||
|
if !is_browser_process {
|
||||||
|
let process_type = CefString::from(&cmd.switch_value(Some(&switch)));
|
||||||
|
let mut app = RenderProcessAppImpl::app();
|
||||||
|
let ret = execute_process(Some(args.as_main_args()), Some(&mut app), std::ptr::null_mut());
|
||||||
|
if ret >= 0 {
|
||||||
|
return Err(SetupError::SubprocessFailed(process_type.to_string()));
|
||||||
|
} else {
|
||||||
|
return Err(SetupError::Subprocess);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Context {
|
||||||
|
args,
|
||||||
|
browser: None,
|
||||||
|
input_state: InputState::default(),
|
||||||
|
marker: std::marker::PhantomData::<Setup>,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn init(self, event_handler: impl CefEventHandler) -> Result<Context<Initialized>, InitError> {
|
||||||
|
let settings = Settings {
|
||||||
|
windowless_rendering_enabled: 1,
|
||||||
|
multi_threaded_message_loop: 0,
|
||||||
|
external_message_pump: 1,
|
||||||
|
root_cache_path: cef_data_dir().to_str().map(CefString::from).unwrap(),
|
||||||
|
cache_path: cef_cache_dir().to_str().map(CefString::from).unwrap(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attention! Wrapping this in an extra App is necessary, otherwise the program still compiles but segfaults
|
||||||
|
let mut cef_app = App::new(BrowserProcessAppImpl::new(event_handler.clone()));
|
||||||
|
|
||||||
|
let result = initialize(Some(self.args.as_main_args()), Some(&settings), Some(&mut cef_app), std::ptr::null_mut());
|
||||||
|
if result != 1 {
|
||||||
|
let cef_exit_code = cef::get_exit_code() as u32;
|
||||||
|
if cef_exit_code == cef_resultcode_t::CEF_RESULT_CODE_NORMAL_EXIT_PROCESS_NOTIFIED as u32 {
|
||||||
|
return Err(InitError::AlreadyRunning);
|
||||||
|
}
|
||||||
|
return Err(InitError::InitializationFailed(cef_exit_code));
|
||||||
|
}
|
||||||
|
|
||||||
|
let render_handler = RenderHandler::new(RenderHandlerImpl::new(event_handler.clone()));
|
||||||
|
let mut client = Client::new(BrowserProcessClientImpl::new(render_handler, event_handler.clone()));
|
||||||
|
|
||||||
|
let url = CefString::from(format!("{GRAPHITE_SCHEME}://{FRONTEND_DOMAIN}/").as_str());
|
||||||
|
|
||||||
|
let window_info = WindowInfo {
|
||||||
|
windowless_rendering_enabled: 1,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let settings = BrowserSettings {
|
||||||
|
windowless_frame_rate: 60,
|
||||||
|
background_color: 0x0,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let browser = browser_host_create_browser_sync(
|
||||||
|
Some(&window_info),
|
||||||
|
Some(&mut client),
|
||||||
|
Some(&url),
|
||||||
|
Some(&settings),
|
||||||
|
Option::<&mut DictionaryValue>::None,
|
||||||
|
Option::<&mut RequestContext>::None,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Context {
|
||||||
|
args: self.args.clone(),
|
||||||
|
browser,
|
||||||
|
input_state: self.input_state.clone(),
|
||||||
|
marker: std::marker::PhantomData::<Initialized>,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context<Initialized> {
|
||||||
|
pub(crate) fn work(&mut self) {
|
||||||
|
cef::do_message_loop_work();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn handle_window_event(&mut self, event: WindowEvent) -> Option<WindowEvent> {
|
||||||
|
input::handle_window_event(self, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn notify_of_resize(&self) {
|
||||||
|
if let Some(browser) = &self.browser {
|
||||||
|
browser.host().unwrap().was_resized();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn send_web_message(&self, message: &[u8]) {
|
||||||
|
self.send_message(MessageType::SendToJS, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S: ContextState> Drop for Context<S> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.browser.is_some() {
|
||||||
|
cef::shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub(crate) enum SetupError {
|
||||||
|
#[error("this is the sub process should exit immediately")]
|
||||||
|
Subprocess,
|
||||||
|
#[error("subprocess returned non zero exit code")]
|
||||||
|
SubprocessFailed(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub(crate) enum InitError {
|
||||||
|
#[error("initialization failed")]
|
||||||
|
InitializationFailed(u32),
|
||||||
|
#[error("Another instance is already running")]
|
||||||
|
AlreadyRunning,
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::dirs::{ensure_dir_exists, graphite_data_dir};
|
||||||
|
|
||||||
|
static CEF_DIR_NAME: &str = "browser";
|
||||||
|
|
||||||
|
pub(crate) fn cef_data_dir() -> PathBuf {
|
||||||
|
let path = graphite_data_dir().join(CEF_DIR_NAME);
|
||||||
|
ensure_dir_exists(&path);
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cef_cache_dir() -> PathBuf {
|
||||||
|
let path = cef_data_dir().join("cache");
|
||||||
|
ensure_dir_exists(&path);
|
||||||
|
path
|
||||||
|
}
|
||||||
@@ -0,0 +1,277 @@
|
|||||||
|
use cef::sys::{cef_event_flags_t, cef_key_event_type_t, cef_mouse_button_type_t};
|
||||||
|
use cef::{ImplBrowser, ImplBrowserHost, KeyEvent, KeyEventType, MouseEvent};
|
||||||
|
use winit::dpi::PhysicalPosition;
|
||||||
|
use winit::event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent};
|
||||||
|
|
||||||
|
use super::context::{Context, Initialized};
|
||||||
|
|
||||||
|
mod keymap;
|
||||||
|
use keymap::{ToDomBits, ToVKBits};
|
||||||
|
|
||||||
|
pub(crate) fn handle_window_event(context: &mut Context<Initialized>, event: WindowEvent) -> Option<WindowEvent> {
|
||||||
|
match event {
|
||||||
|
WindowEvent::CursorMoved { position, .. } => {
|
||||||
|
if let Some(browser) = &context.browser {
|
||||||
|
if let Some(host) = browser.host() {
|
||||||
|
host.set_focus(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.input_state.update_mouse_position(&position);
|
||||||
|
let mouse_event: MouseEvent = (&context.input_state).into();
|
||||||
|
browser.host().unwrap().send_mouse_move_event(Some(&mouse_event), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WindowEvent::MouseInput { state, button, .. } => {
|
||||||
|
if let Some(browser) = &context.browser {
|
||||||
|
if let Some(host) = browser.host() {
|
||||||
|
host.set_focus(1);
|
||||||
|
|
||||||
|
let mouse_up = match state {
|
||||||
|
ElementState::Pressed => 0,
|
||||||
|
ElementState::Released => 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let cef_button = match button {
|
||||||
|
MouseButton::Left => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_LEFT)),
|
||||||
|
MouseButton::Right => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_RIGHT)),
|
||||||
|
MouseButton::Middle => Some(cef::MouseButtonType::from(cef_mouse_button_type_t::MBT_MIDDLE)),
|
||||||
|
MouseButton::Forward => None, //TODO: Handle Forward button
|
||||||
|
MouseButton::Back => None, //TODO: Handle Back button
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut mouse_state = context.input_state.mouse_state.clone();
|
||||||
|
match button {
|
||||||
|
MouseButton::Left => {
|
||||||
|
mouse_state.left = match state {
|
||||||
|
ElementState::Pressed => true,
|
||||||
|
ElementState::Released => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseButton::Right => {
|
||||||
|
mouse_state.right = match state {
|
||||||
|
ElementState::Pressed => true,
|
||||||
|
ElementState::Released => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseButton::Middle => {
|
||||||
|
mouse_state.middle = match state {
|
||||||
|
ElementState::Pressed => true,
|
||||||
|
ElementState::Released => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
context.input_state.update_mouse_state(mouse_state);
|
||||||
|
|
||||||
|
let mouse_event: MouseEvent = (&context.input_state).into();
|
||||||
|
|
||||||
|
if let Some(button) = cef_button {
|
||||||
|
host.send_mouse_click_event(
|
||||||
|
Some(&mouse_event),
|
||||||
|
button,
|
||||||
|
mouse_up,
|
||||||
|
1, // click count
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WindowEvent::MouseWheel { delta, phase: _, device_id: _, .. } => {
|
||||||
|
if let Some(browser) = &context.browser {
|
||||||
|
if let Some(host) = browser.host() {
|
||||||
|
let mouse_event = (&context.input_state).into();
|
||||||
|
let line_width = 40; //feels about right, TODO: replace with correct value
|
||||||
|
let line_height = 30; //feels about right, TODO: replace with correct value
|
||||||
|
let (delta_x, delta_y) = match delta {
|
||||||
|
MouseScrollDelta::LineDelta(x, y) => (x * line_width as f32, y * line_height as f32),
|
||||||
|
MouseScrollDelta::PixelDelta(physical_position) => (physical_position.x as f32, physical_position.y as f32),
|
||||||
|
};
|
||||||
|
host.send_mouse_wheel_event(Some(&mouse_event), delta_x as i32, delta_y as i32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WindowEvent::ModifiersChanged(modifiers) => {
|
||||||
|
context.input_state.update_modifiers(&modifiers.state());
|
||||||
|
}
|
||||||
|
WindowEvent::KeyboardInput { device_id: _, event, is_synthetic: _ } => {
|
||||||
|
if let Some(browser) = &context.browser {
|
||||||
|
if let Some(host) = browser.host() {
|
||||||
|
host.set_focus(1);
|
||||||
|
|
||||||
|
let (named_key, character) = match &event.logical_key {
|
||||||
|
winit::keyboard::Key::Named(named_key) => (
|
||||||
|
Some(named_key),
|
||||||
|
match named_key {
|
||||||
|
winit::keyboard::NamedKey::Space => Some(' '),
|
||||||
|
winit::keyboard::NamedKey::Enter => Some('\u{000d}'),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
winit::keyboard::Key::Character(str) => {
|
||||||
|
let char = str.chars().next().unwrap_or('\0');
|
||||||
|
(None, Some(char))
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut key_event = KeyEvent {
|
||||||
|
size: size_of::<KeyEvent>(),
|
||||||
|
focus_on_editable_field: 1,
|
||||||
|
modifiers: context.input_state.cef_modifiers(&event.location, event.repeat).raw(),
|
||||||
|
is_system_key: 0,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(named_key) = named_key {
|
||||||
|
key_event.native_key_code = named_key.to_dom_bits();
|
||||||
|
key_event.windows_key_code = named_key.to_vk_bits();
|
||||||
|
} else if let Some(char) = character {
|
||||||
|
key_event.native_key_code = char.to_dom_bits();
|
||||||
|
key_event.windows_key_code = char.to_vk_bits();
|
||||||
|
}
|
||||||
|
|
||||||
|
match event.state {
|
||||||
|
ElementState::Pressed => {
|
||||||
|
key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_RAWKEYDOWN);
|
||||||
|
host.send_key_event(Some(&key_event));
|
||||||
|
|
||||||
|
if let Some(char) = character {
|
||||||
|
let mut buf = [0; 2];
|
||||||
|
char.encode_utf16(&mut buf);
|
||||||
|
key_event.character = buf[0];
|
||||||
|
let mut buf = [0; 2];
|
||||||
|
char.to_lowercase().next().unwrap().encode_utf16(&mut buf);
|
||||||
|
key_event.unmodified_character = buf[0];
|
||||||
|
|
||||||
|
key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_CHAR);
|
||||||
|
host.send_key_event(Some(&key_event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ElementState::Released => {
|
||||||
|
key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_KEYUP);
|
||||||
|
host.send_key_event(Some(&key_event));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e => return Some(e),
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub(crate) struct MouseState {
|
||||||
|
left: bool,
|
||||||
|
right: bool,
|
||||||
|
middle: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, Debug)]
|
||||||
|
pub(crate) struct MousePosition {
|
||||||
|
x: usize,
|
||||||
|
y: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&PhysicalPosition<f64>> for MousePosition {
|
||||||
|
fn from(position: &PhysicalPosition<f64>) -> Self {
|
||||||
|
Self {
|
||||||
|
x: position.x as usize,
|
||||||
|
y: position.y as usize,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub(crate) struct InputState {
|
||||||
|
modifiers: winit::keyboard::ModifiersState,
|
||||||
|
mouse_position: MousePosition,
|
||||||
|
mouse_state: MouseState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputState {
|
||||||
|
fn update_modifiers(&mut self, modifiers: &winit::keyboard::ModifiersState) {
|
||||||
|
self.modifiers = *modifiers;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_mouse_position(&mut self, position: &PhysicalPosition<f64>) {
|
||||||
|
self.mouse_position = position.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_mouse_state(&mut self, state: MouseState) {
|
||||||
|
self.mouse_state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cef_modifiers(&self, location: &winit::keyboard::KeyLocation, is_repeat: bool) -> CefModifiers {
|
||||||
|
CefModifiers::new(self, location, is_repeat)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cef_modifiers_mouse_event(&self) -> CefModifiers {
|
||||||
|
self.cef_modifiers(&winit::keyboard::KeyLocation::Standard, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<InputState> for CefModifiers {
|
||||||
|
fn from(val: InputState) -> Self {
|
||||||
|
CefModifiers::new(&val, &winit::keyboard::KeyLocation::Standard, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&InputState> for MouseEvent {
|
||||||
|
fn from(val: &InputState) -> Self {
|
||||||
|
MouseEvent {
|
||||||
|
x: val.mouse_position.x as i32,
|
||||||
|
y: val.mouse_position.y as i32,
|
||||||
|
modifiers: val.cef_modifiers_mouse_event().raw(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CefModifiers(u32);
|
||||||
|
|
||||||
|
impl CefModifiers {
|
||||||
|
fn new(input_state: &InputState, location: &winit::keyboard::KeyLocation, is_repeat: bool) -> Self {
|
||||||
|
let mut inner = 0;
|
||||||
|
|
||||||
|
if input_state.modifiers.shift_key() {
|
||||||
|
inner |= cef_event_flags_t::EVENTFLAG_SHIFT_DOWN as u32;
|
||||||
|
}
|
||||||
|
if input_state.modifiers.control_key() {
|
||||||
|
inner |= cef_event_flags_t::EVENTFLAG_CONTROL_DOWN as u32;
|
||||||
|
}
|
||||||
|
if input_state.modifiers.alt_key() {
|
||||||
|
inner |= cef_event_flags_t::EVENTFLAG_ALT_DOWN as u32;
|
||||||
|
}
|
||||||
|
if input_state.modifiers.super_key() {
|
||||||
|
inner |= cef_event_flags_t::EVENTFLAG_COMMAND_DOWN as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
if input_state.mouse_state.left {
|
||||||
|
inner |= cef_event_flags_t::EVENTFLAG_LEFT_MOUSE_BUTTON as u32;
|
||||||
|
}
|
||||||
|
if input_state.mouse_state.right {
|
||||||
|
inner |= cef_event_flags_t::EVENTFLAG_RIGHT_MOUSE_BUTTON as u32;
|
||||||
|
}
|
||||||
|
if input_state.mouse_state.middle {
|
||||||
|
inner |= cef_event_flags_t::EVENTFLAG_MIDDLE_MOUSE_BUTTON as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_repeat {
|
||||||
|
inner |= cef_event_flags_t::EVENTFLAG_IS_REPEAT as u32;
|
||||||
|
}
|
||||||
|
|
||||||
|
inner |= match location {
|
||||||
|
winit::keyboard::KeyLocation::Left => cef_event_flags_t::EVENTFLAG_IS_LEFT as u32,
|
||||||
|
winit::keyboard::KeyLocation::Right => cef_event_flags_t::EVENTFLAG_IS_RIGHT as u32,
|
||||||
|
winit::keyboard::KeyLocation::Numpad => cef_event_flags_t::EVENTFLAG_IS_KEY_PAD as u32,
|
||||||
|
winit::keyboard::KeyLocation::Standard => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
Self(inner)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn raw(&self) -> u32 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,386 @@
|
|||||||
|
macro_rules! map_enum {
|
||||||
|
($target:expr, $enum:ident, $( ($code:expr, $variant:ident), )+ ) => {
|
||||||
|
match $target {
|
||||||
|
$(
|
||||||
|
$enum::$variant => $code,
|
||||||
|
)+
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! map {
|
||||||
|
($target:expr, $( ($code:expr, $variant:literal), )+ ) => {
|
||||||
|
match $target {
|
||||||
|
$(
|
||||||
|
$variant => $code,
|
||||||
|
)+
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows Virtual keyboard binary representation
|
||||||
|
pub(crate) trait ToVKBits {
|
||||||
|
fn to_vk_bits(&self) -> i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToVKBits for winit::keyboard::NamedKey {
|
||||||
|
fn to_vk_bits(&self) -> i32 {
|
||||||
|
use winit::keyboard::NamedKey;
|
||||||
|
map_enum!(
|
||||||
|
self,
|
||||||
|
NamedKey,
|
||||||
|
(0x12, Alt),
|
||||||
|
(0xA5, AltGraph),
|
||||||
|
(0x14, CapsLock),
|
||||||
|
(0x11, Control),
|
||||||
|
(0x90, NumLock),
|
||||||
|
(0x91, ScrollLock),
|
||||||
|
(0x10, Shift),
|
||||||
|
(0x5B, Meta),
|
||||||
|
(0x5C, Super),
|
||||||
|
(0x0D, Enter),
|
||||||
|
(0x09, Tab),
|
||||||
|
(0x20, Space),
|
||||||
|
(0x28, ArrowDown),
|
||||||
|
(0x25, ArrowLeft),
|
||||||
|
(0x27, ArrowRight),
|
||||||
|
(0x26, ArrowUp),
|
||||||
|
(0x23, End),
|
||||||
|
(0x24, Home),
|
||||||
|
(0x22, PageDown),
|
||||||
|
(0x21, PageUp),
|
||||||
|
(0x08, Backspace),
|
||||||
|
(0x0C, Clear),
|
||||||
|
(0xF7, CrSel),
|
||||||
|
(0x2E, Delete),
|
||||||
|
(0xF9, EraseEof),
|
||||||
|
(0xF8, ExSel),
|
||||||
|
(0x2D, Insert),
|
||||||
|
(0x1E, Accept),
|
||||||
|
(0xF6, Attn),
|
||||||
|
(0x03, Cancel),
|
||||||
|
(0x5D, ContextMenu),
|
||||||
|
(0x1B, Escape),
|
||||||
|
(0x2B, Execute),
|
||||||
|
(0x2F, Help),
|
||||||
|
(0x13, Pause),
|
||||||
|
(0xFA, Play),
|
||||||
|
(0x5D, Props),
|
||||||
|
(0x29, Select),
|
||||||
|
(0xFB, ZoomIn),
|
||||||
|
(0xFB, ZoomOut),
|
||||||
|
(0x2C, PrintScreen),
|
||||||
|
(0x5F, Standby),
|
||||||
|
(0x1C, Convert),
|
||||||
|
(0x18, FinalMode),
|
||||||
|
(0x1F, ModeChange),
|
||||||
|
(0x1D, NonConvert),
|
||||||
|
(0xE5, Process),
|
||||||
|
(0x15, HangulMode),
|
||||||
|
(0x19, HanjaMode),
|
||||||
|
(0x17, JunjaMode),
|
||||||
|
(0x15, KanaMode),
|
||||||
|
(0x19, KanjiMode),
|
||||||
|
(0xB0, MediaFastForward),
|
||||||
|
(0xB3, MediaPause),
|
||||||
|
(0xB3, MediaPlay),
|
||||||
|
(0xB3, MediaPlayPause),
|
||||||
|
(0xB1, MediaRewind),
|
||||||
|
(0xB2, MediaStop),
|
||||||
|
(0xB0, MediaTrackNext),
|
||||||
|
(0xB1, MediaTrackPrevious),
|
||||||
|
(0x2A, Print),
|
||||||
|
(0xAE, AudioVolumeDown),
|
||||||
|
(0xAF, AudioVolumeUp),
|
||||||
|
(0xAD, AudioVolumeMute),
|
||||||
|
(0xB6, LaunchApplication1),
|
||||||
|
(0xB7, LaunchApplication2),
|
||||||
|
(0xB4, LaunchMail),
|
||||||
|
(0xB5, LaunchMediaPlayer),
|
||||||
|
(0xB5, LaunchMusicPlayer),
|
||||||
|
(0xA6, BrowserBack),
|
||||||
|
(0xAB, BrowserFavorites),
|
||||||
|
(0xA7, BrowserForward),
|
||||||
|
(0xAC, BrowserHome),
|
||||||
|
(0xA8, BrowserRefresh),
|
||||||
|
(0xAA, BrowserSearch),
|
||||||
|
(0xA9, BrowserStop),
|
||||||
|
(0xFB, ZoomToggle),
|
||||||
|
(0x70, F1),
|
||||||
|
(0x71, F2),
|
||||||
|
(0x72, F3),
|
||||||
|
(0x73, F4),
|
||||||
|
(0x74, F5),
|
||||||
|
(0x75, F6),
|
||||||
|
(0x76, F7),
|
||||||
|
(0x77, F8),
|
||||||
|
(0x78, F9),
|
||||||
|
(0x79, F10),
|
||||||
|
(0x7A, F11),
|
||||||
|
(0x7B, F12),
|
||||||
|
(0x7C, F13),
|
||||||
|
(0x7D, F14),
|
||||||
|
(0x7E, F15),
|
||||||
|
(0x7F, F16),
|
||||||
|
(0x80, F17),
|
||||||
|
(0x81, F18),
|
||||||
|
(0x82, F19),
|
||||||
|
(0x83, F20),
|
||||||
|
(0x84, F21),
|
||||||
|
(0x85, F22),
|
||||||
|
(0x86, F23),
|
||||||
|
(0x87, F24),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToVKBits for char {
|
||||||
|
fn to_vk_bits(&self) -> i32 {
|
||||||
|
map!(
|
||||||
|
self,
|
||||||
|
(0x0041, 'a'),
|
||||||
|
(0x0042, 'b'),
|
||||||
|
(0x0043, 'c'),
|
||||||
|
(0x0044, 'd'),
|
||||||
|
(0x0045, 'e'),
|
||||||
|
(0x0046, 'f'),
|
||||||
|
(0x0047, 'g'),
|
||||||
|
(0x0048, 'h'),
|
||||||
|
(0x0049, 'i'),
|
||||||
|
(0x004a, 'j'),
|
||||||
|
(0x004b, 'k'),
|
||||||
|
(0x004c, 'l'),
|
||||||
|
(0x004d, 'm'),
|
||||||
|
(0x004e, 'n'),
|
||||||
|
(0x004f, 'o'),
|
||||||
|
(0x0050, 'p'),
|
||||||
|
(0x0051, 'q'),
|
||||||
|
(0x0052, 'r'),
|
||||||
|
(0x0053, 's'),
|
||||||
|
(0x0054, 't'),
|
||||||
|
(0x0055, 'u'),
|
||||||
|
(0x0056, 'v'),
|
||||||
|
(0x0057, 'w'),
|
||||||
|
(0x0058, 'x'),
|
||||||
|
(0x0059, 'y'),
|
||||||
|
(0x005a, 'z'),
|
||||||
|
(0x0041, 'A'),
|
||||||
|
(0x0042, 'B'),
|
||||||
|
(0x0043, 'C'),
|
||||||
|
(0x0044, 'D'),
|
||||||
|
(0x0045, 'E'),
|
||||||
|
(0x0046, 'F'),
|
||||||
|
(0x0047, 'G'),
|
||||||
|
(0x0048, 'H'),
|
||||||
|
(0x0049, 'I'),
|
||||||
|
(0x004a, 'J'),
|
||||||
|
(0x004b, 'K'),
|
||||||
|
(0x004c, 'L'),
|
||||||
|
(0x004d, 'M'),
|
||||||
|
(0x004e, 'N'),
|
||||||
|
(0x004f, 'O'),
|
||||||
|
(0x0050, 'P'),
|
||||||
|
(0x0051, 'Q'),
|
||||||
|
(0x0052, 'R'),
|
||||||
|
(0x0053, 'S'),
|
||||||
|
(0x0054, 'T'),
|
||||||
|
(0x0055, 'U'),
|
||||||
|
(0x0056, 'V'),
|
||||||
|
(0x0057, 'W'),
|
||||||
|
(0x0058, 'X'),
|
||||||
|
(0x0059, 'Y'),
|
||||||
|
(0x005a, 'Z'),
|
||||||
|
(0x0031, '1'),
|
||||||
|
(0x0032, '2'),
|
||||||
|
(0x0032, '3'),
|
||||||
|
(0x0033, '4'),
|
||||||
|
(0x0034, '5'),
|
||||||
|
(0x0035, '6'),
|
||||||
|
(0x0036, '7'),
|
||||||
|
(0x0037, '8'),
|
||||||
|
(0x0039, '9'),
|
||||||
|
(0x0030, '0'),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chromium dom key binary representation
|
||||||
|
pub(crate) trait ToDomBits {
|
||||||
|
fn to_dom_bits(&self) -> i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToDomBits for winit::keyboard::NamedKey {
|
||||||
|
fn to_dom_bits(&self) -> i32 {
|
||||||
|
use winit::keyboard::NamedKey;
|
||||||
|
map_enum!(
|
||||||
|
self,
|
||||||
|
NamedKey,
|
||||||
|
(0x0000, Hyper),
|
||||||
|
(0x0085, Super),
|
||||||
|
(0x0025, Control),
|
||||||
|
(0x0032, Shift),
|
||||||
|
(0x0040, Alt),
|
||||||
|
(0x0000, Fn),
|
||||||
|
(0x0000, FnLock),
|
||||||
|
(0x0024, Enter),
|
||||||
|
(0x0009, Escape),
|
||||||
|
(0x0016, Backspace),
|
||||||
|
(0x0017, Tab),
|
||||||
|
(0x0041, Space),
|
||||||
|
(0x0042, CapsLock),
|
||||||
|
(0x0043, F1),
|
||||||
|
(0x0044, F2),
|
||||||
|
(0x0045, F3),
|
||||||
|
(0x0046, F4),
|
||||||
|
(0x0047, F5),
|
||||||
|
(0x0048, F6),
|
||||||
|
(0x0049, F7),
|
||||||
|
(0x004a, F8),
|
||||||
|
(0x004b, F9),
|
||||||
|
(0x004c, F10),
|
||||||
|
(0x005f, F11),
|
||||||
|
(0x0060, F12),
|
||||||
|
(0x006b, PrintScreen),
|
||||||
|
(0x004e, ScrollLock),
|
||||||
|
(0x007f, Pause),
|
||||||
|
(0x0076, Insert),
|
||||||
|
(0x006e, Home),
|
||||||
|
(0x0070, PageUp),
|
||||||
|
(0x0077, Delete),
|
||||||
|
(0x0073, End),
|
||||||
|
(0x0075, PageDown),
|
||||||
|
(0x0072, ArrowRight),
|
||||||
|
(0x0071, ArrowLeft),
|
||||||
|
(0x0074, ArrowDown),
|
||||||
|
(0x006f, ArrowUp),
|
||||||
|
(0x004d, NumLock),
|
||||||
|
(0x0087, ContextMenu),
|
||||||
|
(0x007c, Power),
|
||||||
|
(0x00bf, F13),
|
||||||
|
(0x00c0, F14),
|
||||||
|
(0x00c1, F15),
|
||||||
|
(0x00c2, F16),
|
||||||
|
(0x00c3, F17),
|
||||||
|
(0x00c4, F18),
|
||||||
|
(0x00c5, F19),
|
||||||
|
(0x00c6, F20),
|
||||||
|
(0x00c7, F21),
|
||||||
|
(0x00c8, F22),
|
||||||
|
(0x00c9, F23),
|
||||||
|
(0x00ca, F24),
|
||||||
|
(0x008e, Open),
|
||||||
|
(0x0092, Help),
|
||||||
|
(0x008c, Select),
|
||||||
|
(0x0089, Again),
|
||||||
|
(0x008b, Undo),
|
||||||
|
(0x0091, Cut),
|
||||||
|
(0x008d, Copy),
|
||||||
|
(0x008f, Paste),
|
||||||
|
(0x0090, Find),
|
||||||
|
(0x0079, AudioVolumeMute),
|
||||||
|
(0x007b, AudioVolumeUp),
|
||||||
|
(0x007a, AudioVolumeDown),
|
||||||
|
(0x0065, KanaMode),
|
||||||
|
(0x0064, Convert),
|
||||||
|
(0x0066, NonConvert),
|
||||||
|
(0x0000, Props),
|
||||||
|
(0x00e9, BrightnessUp),
|
||||||
|
(0x00e8, BrightnessDown),
|
||||||
|
(0x00d7, MediaPlay),
|
||||||
|
(0x00d1, MediaPause),
|
||||||
|
(0x00af, MediaRecord),
|
||||||
|
(0x00d8, MediaFastForward),
|
||||||
|
(0x00b0, MediaRewind),
|
||||||
|
(0x00ab, MediaTrackNext),
|
||||||
|
(0x00ad, MediaTrackPrevious),
|
||||||
|
(0x00ae, MediaStop),
|
||||||
|
(0x00a9, Eject),
|
||||||
|
(0x00ac, MediaPlayPause),
|
||||||
|
(0x00a3, LaunchMail),
|
||||||
|
(0x024d, LaunchScreenSaver),
|
||||||
|
(0x00e1, BrowserSearch),
|
||||||
|
(0x00b4, BrowserHome),
|
||||||
|
(0x00a6, BrowserBack),
|
||||||
|
(0x00a7, BrowserForward),
|
||||||
|
(0x0088, BrowserStop),
|
||||||
|
(0x00b5, BrowserRefresh),
|
||||||
|
(0x00a4, BrowserFavorites),
|
||||||
|
(0x017c, ZoomToggle),
|
||||||
|
(0x00f0, MailReply),
|
||||||
|
(0x00f1, MailForward),
|
||||||
|
(0x00ef, MailSend),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToDomBits for char {
|
||||||
|
fn to_dom_bits(&self) -> i32 {
|
||||||
|
map!(
|
||||||
|
self,
|
||||||
|
(0x0026, 'a'),
|
||||||
|
(0x0038, 'b'),
|
||||||
|
(0x0036, 'c'),
|
||||||
|
(0x0028, 'd'),
|
||||||
|
(0x001a, 'e'),
|
||||||
|
(0x0029, 'f'),
|
||||||
|
(0x002a, 'g'),
|
||||||
|
(0x002b, 'h'),
|
||||||
|
(0x001f, 'i'),
|
||||||
|
(0x002c, 'j'),
|
||||||
|
(0x002d, 'k'),
|
||||||
|
(0x002e, 'l'),
|
||||||
|
(0x003a, 'm'),
|
||||||
|
(0x0039, 'n'),
|
||||||
|
(0x0020, 'o'),
|
||||||
|
(0x0021, 'p'),
|
||||||
|
(0x0018, 'q'),
|
||||||
|
(0x001b, 'r'),
|
||||||
|
(0x0027, 's'),
|
||||||
|
(0x001c, 't'),
|
||||||
|
(0x001e, 'u'),
|
||||||
|
(0x0037, 'v'),
|
||||||
|
(0x0019, 'w'),
|
||||||
|
(0x0035, 'x'),
|
||||||
|
(0x001d, 'y'),
|
||||||
|
(0x0034, 'z'),
|
||||||
|
(0x0026, 'A'),
|
||||||
|
(0x0038, 'B'),
|
||||||
|
(0x0036, 'C'),
|
||||||
|
(0x0028, 'D'),
|
||||||
|
(0x001a, 'E'),
|
||||||
|
(0x0029, 'F'),
|
||||||
|
(0x002a, 'G'),
|
||||||
|
(0x002b, 'H'),
|
||||||
|
(0x001f, 'I'),
|
||||||
|
(0x002c, 'J'),
|
||||||
|
(0x002d, 'K'),
|
||||||
|
(0x002e, 'L'),
|
||||||
|
(0x003a, 'M'),
|
||||||
|
(0x0039, 'N'),
|
||||||
|
(0x0020, 'O'),
|
||||||
|
(0x0021, 'P'),
|
||||||
|
(0x0018, 'Q'),
|
||||||
|
(0x001b, 'R'),
|
||||||
|
(0x0027, 'S'),
|
||||||
|
(0x001c, 'T'),
|
||||||
|
(0x001e, 'U'),
|
||||||
|
(0x0037, 'V'),
|
||||||
|
(0x0019, 'W'),
|
||||||
|
(0x0035, 'X'),
|
||||||
|
(0x001d, 'Y'),
|
||||||
|
(0x0034, 'Z'),
|
||||||
|
(0x000a, '1'),
|
||||||
|
(0x000b, '2'),
|
||||||
|
(0x000c, '3'),
|
||||||
|
(0x000d, '4'),
|
||||||
|
(0x000e, '5'),
|
||||||
|
(0x000f, '6'),
|
||||||
|
(0x0010, '7'),
|
||||||
|
(0x0011, '8'),
|
||||||
|
(0x0012, '9'),
|
||||||
|
(0x0013, '0'),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
mod browser_process_app;
|
||||||
|
mod browser_process_client;
|
||||||
|
mod browser_process_handler;
|
||||||
|
mod render_handler;
|
||||||
|
mod render_process_app;
|
||||||
|
mod render_process_handler;
|
||||||
|
mod render_process_v8_handler;
|
||||||
|
|
||||||
|
pub(crate) use browser_process_app::BrowserProcessAppImpl;
|
||||||
|
pub(crate) use browser_process_client::BrowserProcessClientImpl;
|
||||||
|
pub(crate) use render_handler::RenderHandlerImpl;
|
||||||
|
pub(crate) use render_process_app::RenderProcessAppImpl;
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
use std::env;
|
||||||
|
|
||||||
|
use cef::rc::{Rc, RcImpl};
|
||||||
|
use cef::sys::{_cef_app_t, cef_base_ref_counted_t};
|
||||||
|
use cef::{BrowserProcessHandler, CefString, ImplApp, ImplCommandLine, SchemeRegistrar, WrapApp};
|
||||||
|
|
||||||
|
use crate::cef::CefEventHandler;
|
||||||
|
|
||||||
|
use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory;
|
||||||
|
|
||||||
|
use super::browser_process_handler::BrowserProcessHandlerImpl;
|
||||||
|
|
||||||
|
pub(crate) struct BrowserProcessAppImpl<H: CefEventHandler> {
|
||||||
|
object: *mut RcImpl<_cef_app_t, Self>,
|
||||||
|
event_handler: H,
|
||||||
|
}
|
||||||
|
impl<H: CefEventHandler + Clone> BrowserProcessAppImpl<H> {
|
||||||
|
pub(crate) fn new(event_handler: H) -> Self {
|
||||||
|
Self {
|
||||||
|
object: std::ptr::null_mut(),
|
||||||
|
event_handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H: CefEventHandler + Clone> ImplApp for BrowserProcessAppImpl<H> {
|
||||||
|
fn browser_process_handler(&self) -> Option<BrowserProcessHandler> {
|
||||||
|
Some(BrowserProcessHandler::new(BrowserProcessHandlerImpl::new(self.event_handler.clone())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
|
||||||
|
GraphiteSchemeHandlerFactory::register_schemes(registrar);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_before_command_line_processing(&self, _process_type: Option<&cef::CefString>, command_line: Option<&mut cef::CommandLine>) {
|
||||||
|
if let Some(cmd) = command_line {
|
||||||
|
// Disable GPU acceleration, because it is not supported for Offscreen Rendering and can cause crashes.
|
||||||
|
cmd.append_switch(Some(&CefString::from("disable-gpu")));
|
||||||
|
cmd.append_switch(Some(&CefString::from("disable-gpu-compositing")));
|
||||||
|
|
||||||
|
// Tell CEF to use Wayland if available
|
||||||
|
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
|
||||||
|
{
|
||||||
|
let use_wayland = env::var("WAYLAND_DISPLAY")
|
||||||
|
.ok()
|
||||||
|
.filter(|var| !var.is_empty())
|
||||||
|
.or_else(|| env::var("WAYLAND_SOCKET").ok())
|
||||||
|
.filter(|var| !var.is_empty())
|
||||||
|
.is_some();
|
||||||
|
if use_wayland {
|
||||||
|
cmd.append_switch_with_value(Some(&CefString::from("ozone-platform")), Some(&CefString::from("wayland")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_raw(&self) -> *mut _cef_app_t {
|
||||||
|
self.object.cast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H: CefEventHandler + Clone> Clone for BrowserProcessAppImpl<H> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
unsafe {
|
||||||
|
let rc_impl = &mut *self.object;
|
||||||
|
rc_impl.interface.add_ref();
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
object: self.object,
|
||||||
|
event_handler: self.event_handler.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<H: CefEventHandler> Rc for BrowserProcessAppImpl<H> {
|
||||||
|
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||||
|
unsafe {
|
||||||
|
let base = &*self.object;
|
||||||
|
std::mem::transmute(&base.cef_object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<H: CefEventHandler + Clone> WrapApp for BrowserProcessAppImpl<H> {
|
||||||
|
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) {
|
||||||
|
self.object = object;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
use cef::rc::{Rc, RcImpl};
|
||||||
|
use cef::sys::{_cef_client_t, cef_base_ref_counted_t};
|
||||||
|
use cef::{ImplClient, RenderHandler, WrapClient};
|
||||||
|
|
||||||
|
use crate::cef::CefEventHandler;
|
||||||
|
use crate::cef::ipc::{MessageType, UnpackMessage, UnpackedMessage};
|
||||||
|
|
||||||
|
pub(crate) struct BrowserProcessClientImpl<H: CefEventHandler> {
|
||||||
|
object: *mut RcImpl<_cef_client_t, Self>,
|
||||||
|
render_handler: RenderHandler,
|
||||||
|
event_handler: H,
|
||||||
|
}
|
||||||
|
impl<H: CefEventHandler> BrowserProcessClientImpl<H> {
|
||||||
|
pub(crate) fn new(render_handler: RenderHandler, event_handler: H) -> Self {
|
||||||
|
Self {
|
||||||
|
object: std::ptr::null_mut(),
|
||||||
|
render_handler,
|
||||||
|
event_handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H: CefEventHandler> ImplClient for BrowserProcessClientImpl<H> {
|
||||||
|
fn on_process_message_received(
|
||||||
|
&self,
|
||||||
|
_browser: Option<&mut cef::Browser>,
|
||||||
|
_frame: Option<&mut cef::Frame>,
|
||||||
|
_source_process: cef::ProcessId,
|
||||||
|
message: Option<&mut cef::ProcessMessage>,
|
||||||
|
) -> ::std::os::raw::c_int {
|
||||||
|
let unpacked_message = unsafe { message.and_then(|m| m.unpack()) };
|
||||||
|
match unpacked_message {
|
||||||
|
Some(UnpackedMessage {
|
||||||
|
message_type: MessageType::SendToNative,
|
||||||
|
data,
|
||||||
|
}) => self.event_handler.receive_web_message(data),
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
tracing::error!("Unexpected message type received in browser process");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_handler(&self) -> Option<RenderHandler> {
|
||||||
|
Some(self.render_handler.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_raw(&self) -> *mut _cef_client_t {
|
||||||
|
self.object.cast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H: CefEventHandler> Clone for BrowserProcessClientImpl<H> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
unsafe {
|
||||||
|
let rc_impl = &mut *self.object;
|
||||||
|
rc_impl.interface.add_ref();
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
object: self.object,
|
||||||
|
render_handler: self.render_handler.clone(),
|
||||||
|
event_handler: self.event_handler.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<H: CefEventHandler> Rc for BrowserProcessClientImpl<H> {
|
||||||
|
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||||
|
unsafe {
|
||||||
|
let base = &*self.object;
|
||||||
|
std::mem::transmute(&base.cef_object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<H: CefEventHandler> WrapClient for BrowserProcessClientImpl<H> {
|
||||||
|
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_client_t, Self>) {
|
||||||
|
self.object = object;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use cef::rc::{Rc, RcImpl};
|
||||||
|
use cef::sys::{_cef_browser_process_handler_t, cef_base_ref_counted_t, cef_browser_process_handler_t};
|
||||||
|
use cef::{CefString, ImplBrowserProcessHandler, SchemeHandlerFactory, WrapBrowserProcessHandler};
|
||||||
|
|
||||||
|
use crate::cef::CefEventHandler;
|
||||||
|
use crate::cef::scheme_handler::{GRAPHITE_SCHEME, GraphiteSchemeHandlerFactory};
|
||||||
|
|
||||||
|
pub(crate) struct BrowserProcessHandlerImpl<H: CefEventHandler> {
|
||||||
|
object: *mut RcImpl<cef_browser_process_handler_t, Self>,
|
||||||
|
event_handler: H,
|
||||||
|
}
|
||||||
|
impl<H: CefEventHandler> BrowserProcessHandlerImpl<H> {
|
||||||
|
pub(crate) fn new(event_handler: H) -> Self {
|
||||||
|
Self {
|
||||||
|
object: std::ptr::null_mut(),
|
||||||
|
event_handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H: CefEventHandler + Clone> ImplBrowserProcessHandler for BrowserProcessHandlerImpl<H> {
|
||||||
|
fn on_context_initialized(&self) {
|
||||||
|
cef::register_scheme_handler_factory(Some(&CefString::from(GRAPHITE_SCHEME)), None, Some(&mut SchemeHandlerFactory::new(GraphiteSchemeHandlerFactory::new())));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_schedule_message_pump_work(&self, delay_ms: i64) {
|
||||||
|
self.event_handler.schedule_cef_message_loop_work(Instant::now() + Duration::from_millis(delay_ms as u64));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_already_running_app_relaunch(&self, _command_line: Option<&mut cef::CommandLine>, _current_directory: Option<&CefString>) -> ::std::os::raw::c_int {
|
||||||
|
1 // Return 1 to prevent default behavior of opening a empty browser window
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_raw(&self) -> *mut _cef_browser_process_handler_t {
|
||||||
|
self.object.cast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H: CefEventHandler + Clone> Clone for BrowserProcessHandlerImpl<H> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
unsafe {
|
||||||
|
let rc_impl = &mut *self.object;
|
||||||
|
rc_impl.interface.add_ref();
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
object: self.object,
|
||||||
|
event_handler: self.event_handler.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<H: CefEventHandler> Rc for BrowserProcessHandlerImpl<H> {
|
||||||
|
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||||
|
unsafe {
|
||||||
|
let base = &*self.object;
|
||||||
|
std::mem::transmute(&base.cef_object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<H: CefEventHandler + Clone> WrapBrowserProcessHandler for BrowserProcessHandlerImpl<H> {
|
||||||
|
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_browser_process_handler_t, Self>) {
|
||||||
|
self.object = object;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
use cef::rc::{Rc, RcImpl};
|
||||||
|
use cef::sys::{_cef_render_handler_t, cef_base_ref_counted_t};
|
||||||
|
use cef::{Browser, ImplRenderHandler, PaintElementType, Rect, WrapRenderHandler};
|
||||||
|
|
||||||
|
use crate::cef::CefEventHandler;
|
||||||
|
use crate::render::FrameBufferRef;
|
||||||
|
|
||||||
|
pub(crate) struct RenderHandlerImpl<H: CefEventHandler> {
|
||||||
|
object: *mut RcImpl<_cef_render_handler_t, Self>,
|
||||||
|
event_handler: H,
|
||||||
|
}
|
||||||
|
impl<H: CefEventHandler> RenderHandlerImpl<H> {
|
||||||
|
pub(crate) fn new(event_handler: H) -> Self {
|
||||||
|
Self {
|
||||||
|
object: std::ptr::null_mut(),
|
||||||
|
event_handler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<H: CefEventHandler> ImplRenderHandler for RenderHandlerImpl<H> {
|
||||||
|
fn view_rect(&self, _browser: Option<&mut Browser>, rect: Option<&mut Rect>) {
|
||||||
|
if let Some(rect) = rect {
|
||||||
|
let view = self.event_handler.window_size();
|
||||||
|
*rect = Rect {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: view.width as i32,
|
||||||
|
height: view.height as i32,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_paint(
|
||||||
|
&self,
|
||||||
|
_browser: Option<&mut Browser>,
|
||||||
|
_type_: PaintElementType,
|
||||||
|
_dirty_rect_count: usize,
|
||||||
|
_dirty_rects: Option<&Rect>,
|
||||||
|
buffer: *const u8,
|
||||||
|
width: ::std::os::raw::c_int,
|
||||||
|
height: ::std::os::raw::c_int,
|
||||||
|
) {
|
||||||
|
let buffer_size = (width * height * 4) as usize;
|
||||||
|
let buffer_slice = unsafe { std::slice::from_raw_parts(buffer, buffer_size) };
|
||||||
|
let frame_buffer = FrameBufferRef::new(buffer_slice, width as usize, height as usize).expect("Failed to create frame buffer");
|
||||||
|
|
||||||
|
self.event_handler.draw(frame_buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_raw(&self) -> *mut _cef_render_handler_t {
|
||||||
|
self.object.cast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<H: CefEventHandler> Clone for RenderHandlerImpl<H> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
unsafe {
|
||||||
|
let rc_impl = &mut *self.object;
|
||||||
|
rc_impl.interface.add_ref();
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
object: self.object,
|
||||||
|
event_handler: self.event_handler.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<H: CefEventHandler> Rc for RenderHandlerImpl<H> {
|
||||||
|
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||||
|
unsafe {
|
||||||
|
let base = &*self.object;
|
||||||
|
std::mem::transmute(&base.cef_object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<H: CefEventHandler> WrapRenderHandler for RenderHandlerImpl<H> {
|
||||||
|
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_render_handler_t, Self>) {
|
||||||
|
self.object = object;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
use cef::rc::{Rc, RcImpl};
|
||||||
|
use cef::sys::{_cef_app_t, cef_base_ref_counted_t};
|
||||||
|
use cef::{App, ImplApp, RenderProcessHandler, SchemeRegistrar, WrapApp};
|
||||||
|
|
||||||
|
use super::render_process_handler::RenderProcessHandlerImpl;
|
||||||
|
use crate::cef::scheme_handler::GraphiteSchemeHandlerFactory;
|
||||||
|
|
||||||
|
pub(crate) struct RenderProcessAppImpl {
|
||||||
|
object: *mut RcImpl<_cef_app_t, Self>,
|
||||||
|
render_process_handler: RenderProcessHandler,
|
||||||
|
}
|
||||||
|
impl RenderProcessAppImpl {
|
||||||
|
pub(crate) fn app() -> App {
|
||||||
|
App::new(Self {
|
||||||
|
object: std::ptr::null_mut(),
|
||||||
|
render_process_handler: RenderProcessHandler::new(RenderProcessHandlerImpl::new()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImplApp for RenderProcessAppImpl {
|
||||||
|
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
|
||||||
|
GraphiteSchemeHandlerFactory::register_schemes(registrar);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_process_handler(&self) -> Option<RenderProcessHandler> {
|
||||||
|
Some(self.render_process_handler.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_raw(&self) -> *mut _cef_app_t {
|
||||||
|
self.object.cast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for RenderProcessAppImpl {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
unsafe {
|
||||||
|
let rc_impl = &mut *self.object;
|
||||||
|
rc_impl.interface.add_ref();
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
object: self.object,
|
||||||
|
render_process_handler: self.render_process_handler.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Rc for RenderProcessAppImpl {
|
||||||
|
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||||
|
unsafe {
|
||||||
|
let base = &*self.object;
|
||||||
|
std::mem::transmute(&base.cef_object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl WrapApp for RenderProcessAppImpl {
|
||||||
|
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) {
|
||||||
|
self.object = object;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
use cef::rc::{ConvertReturnValue, Rc, RcImpl};
|
||||||
|
use cef::sys::{_cef_render_process_handler_t, cef_base_ref_counted_t, cef_render_process_handler_t, cef_v8_propertyattribute_t, cef_v8_value_create_array_buffer_with_copy};
|
||||||
|
use cef::{CefString, ImplFrame, ImplRenderProcessHandler, ImplV8Context, ImplV8Value, V8Handler, V8Propertyattribute, V8Value, WrapRenderProcessHandler, v8_value_create_function};
|
||||||
|
|
||||||
|
use crate::cef::ipc::{MessageType, UnpackMessage, UnpackedMessage};
|
||||||
|
|
||||||
|
use super::render_process_v8_handler::BrowserProcessV8HandlerImpl;
|
||||||
|
|
||||||
|
pub(crate) struct RenderProcessHandlerImpl {
|
||||||
|
object: *mut RcImpl<cef_render_process_handler_t, Self>,
|
||||||
|
}
|
||||||
|
impl RenderProcessHandlerImpl {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self { object: std::ptr::null_mut() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImplRenderProcessHandler for RenderProcessHandlerImpl {
|
||||||
|
fn on_process_message_received(
|
||||||
|
&self,
|
||||||
|
_browser: Option<&mut cef::Browser>,
|
||||||
|
frame: Option<&mut cef::Frame>,
|
||||||
|
_source_process: cef::ProcessId,
|
||||||
|
message: Option<&mut cef::ProcessMessage>,
|
||||||
|
) -> ::std::os::raw::c_int {
|
||||||
|
let unpacked_message = unsafe { message.and_then(|m| m.unpack()) };
|
||||||
|
match unpacked_message {
|
||||||
|
Some(UnpackedMessage {
|
||||||
|
message_type: MessageType::SendToJS,
|
||||||
|
data,
|
||||||
|
}) => {
|
||||||
|
let Some(frame) = frame else {
|
||||||
|
tracing::error!("Frame is not available");
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
let Some(context) = frame.v8_context() else {
|
||||||
|
tracing::error!("V8 context is not available");
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
if context.enter() == 0 {
|
||||||
|
tracing::error!("Failed to enter V8 context");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let mut value: V8Value = unsafe { cef_v8_value_create_array_buffer_with_copy(data.as_ptr() as *mut std::ffi::c_void, data.len()) }.wrap_result();
|
||||||
|
let Some(global) = context.global() else {
|
||||||
|
tracing::error!("Global object is not available in V8 context");
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
let function_name = "receiveNativeMessage";
|
||||||
|
let property_name = "receiveNativeMessageData";
|
||||||
|
|
||||||
|
let function_call = format!("window.{function_name}(window.{property_name})");
|
||||||
|
|
||||||
|
global.set_value_bykey(
|
||||||
|
Some(&CefString::from(property_name)),
|
||||||
|
Some(&mut value),
|
||||||
|
cef_v8_propertyattribute_t::V8_PROPERTY_ATTRIBUTE_READONLY.wrap_result(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if global.value_bykey(Some(&CefString::from(function_name))).is_some() {
|
||||||
|
frame.execute_java_script(Some(&CefString::from(function_call.as_str())), None, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if context.exit() == 0 {
|
||||||
|
tracing::error!("Failed to exit V8 context");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
tracing::error!("Unexpected message type received in render process");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_context_created(&self, _browser: Option<&mut cef::Browser>, _frame: Option<&mut cef::Frame>, context: Option<&mut cef::V8Context>) {
|
||||||
|
let function_name = "sendNativeMessage";
|
||||||
|
|
||||||
|
let Some(context) = context else {
|
||||||
|
tracing::error!("V8 context is not available");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut v8_handler = V8Handler::new(BrowserProcessV8HandlerImpl::new());
|
||||||
|
let Some(mut function) = v8_value_create_function(Some(&CefString::from(function_name)), Some(&mut v8_handler)) else {
|
||||||
|
tracing::error!("Failed to create V8 function {function_name}");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(global) = context.global() else {
|
||||||
|
tracing::error!("Global object is not available in V8 context");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
global.set_value_bykey(Some(&CefString::from(function_name)), Some(&mut function), V8Propertyattribute::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_raw(&self) -> *mut _cef_render_process_handler_t {
|
||||||
|
self.object.cast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for RenderProcessHandlerImpl {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
unsafe {
|
||||||
|
let rc_impl = &mut *self.object;
|
||||||
|
rc_impl.interface.add_ref();
|
||||||
|
}
|
||||||
|
Self { object: self.object }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Rc for RenderProcessHandlerImpl {
|
||||||
|
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||||
|
unsafe {
|
||||||
|
let base = &*self.object;
|
||||||
|
std::mem::transmute(&base.cef_object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl WrapRenderProcessHandler for RenderProcessHandlerImpl {
|
||||||
|
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_render_process_handler_t, Self>) {
|
||||||
|
self.object = object;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
use cef::{ImplV8Handler, ImplV8Value, V8Value, WrapV8Handler, rc::Rc, v8_context_get_current_context};
|
||||||
|
|
||||||
|
use crate::cef::ipc::{MessageType, SendMessage};
|
||||||
|
|
||||||
|
pub struct BrowserProcessV8HandlerImpl {
|
||||||
|
object: *mut cef::rc::RcImpl<cef::sys::_cef_v8_handler_t, Self>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BrowserProcessV8HandlerImpl {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self { object: std::ptr::null_mut() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ImplV8Handler for BrowserProcessV8HandlerImpl {
|
||||||
|
fn execute(
|
||||||
|
&self,
|
||||||
|
name: Option<&cef::CefString>,
|
||||||
|
_object: Option<&mut V8Value>,
|
||||||
|
arguments: Option<&[Option<V8Value>]>,
|
||||||
|
_retval: Option<&mut Option<V8Value>>,
|
||||||
|
_exception: Option<&mut cef::CefString>,
|
||||||
|
) -> ::std::os::raw::c_int {
|
||||||
|
if let Some(name) = name {
|
||||||
|
if name.to_string() == "sendNativeMessage" {
|
||||||
|
let Some(args) = arguments else {
|
||||||
|
tracing::error!("No arguments provided to sendNativeMessage");
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
let Some(arg1) = args.first() else {
|
||||||
|
tracing::error!("No arguments provided to sendNativeMessage");
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
let Some(arg1) = arg1.as_ref() else {
|
||||||
|
tracing::error!("First argument to sendNativeMessage is not an ArrayBuffer");
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
if arg1.is_array_buffer() == 0 {
|
||||||
|
tracing::error!("First argument to sendNativeMessage is not an ArrayBuffer");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = arg1.array_buffer_byte_length();
|
||||||
|
let ptr = arg1.array_buffer_data();
|
||||||
|
let data = unsafe { std::slice::from_raw_parts_mut(ptr as *mut u8, size) };
|
||||||
|
|
||||||
|
v8_context_get_current_context().send_message(MessageType::SendToNative, data);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_raw(&self) -> *mut cef::sys::_cef_v8_handler_t {
|
||||||
|
self.object.cast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for BrowserProcessV8HandlerImpl {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
unsafe {
|
||||||
|
let rc_impl = &mut *self.object;
|
||||||
|
rc_impl.interface.add_ref();
|
||||||
|
}
|
||||||
|
Self { object: self.object }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rc for BrowserProcessV8HandlerImpl {
|
||||||
|
fn as_base(&self) -> &cef::sys::cef_base_ref_counted_t {
|
||||||
|
unsafe {
|
||||||
|
let base = &*self.object;
|
||||||
|
std::mem::transmute(&base.cef_object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WrapV8Handler for BrowserProcessV8HandlerImpl {
|
||||||
|
fn wrap_rc(&mut self, object: *mut cef::rc::RcImpl<cef::sys::_cef_v8_handler_t, Self>) {
|
||||||
|
self.object = object;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
use cef::{CefString, Frame, ImplBinaryValue, ImplBrowser, ImplFrame, ImplListValue, ImplProcessMessage, ImplV8Context, ProcessId, V8Context, sys::cef_process_id_t};
|
||||||
|
|
||||||
|
use super::{Context, Initialized};
|
||||||
|
|
||||||
|
pub(crate) enum MessageType {
|
||||||
|
SendToJS,
|
||||||
|
SendToNative,
|
||||||
|
}
|
||||||
|
impl From<MessageType> for MessageInfo {
|
||||||
|
fn from(val: MessageType) -> Self {
|
||||||
|
match val {
|
||||||
|
MessageType::SendToJS => MessageInfo {
|
||||||
|
name: "send_to_js".to_string(),
|
||||||
|
target: cef_process_id_t::PID_RENDERER.into(),
|
||||||
|
},
|
||||||
|
MessageType::SendToNative => MessageInfo {
|
||||||
|
name: "send_to_native".to_string(),
|
||||||
|
target: cef_process_id_t::PID_BROWSER.into(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TryFrom<String> for MessageType {
|
||||||
|
type Error = ();
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
match value.as_str() {
|
||||||
|
"send_to_js" => Ok(MessageType::SendToJS),
|
||||||
|
"send_to_native" => Ok(MessageType::SendToNative),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct MessageInfo {
|
||||||
|
name: String,
|
||||||
|
target: ProcessId,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait SendMessage {
|
||||||
|
fn send_message(&self, message_type: MessageType, message: &[u8]);
|
||||||
|
}
|
||||||
|
impl SendMessage for Context<Initialized> {
|
||||||
|
fn send_message(&self, message_type: MessageType, message: &[u8]) {
|
||||||
|
let Some(browser) = &self.browser else {
|
||||||
|
tracing::error!("Browser is not initialized, cannot send message");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(frame) = browser.main_frame() else {
|
||||||
|
tracing::error!("Main frame is not available, cannot send message");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
frame.send_message(message_type, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl SendMessage for Option<V8Context> {
|
||||||
|
fn send_message(&self, message_type: MessageType, message: &[u8]) {
|
||||||
|
let Some(context) = self else {
|
||||||
|
tracing::error!("Current V8 context is not available, cannot send message");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
context.send_message(message_type, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl SendMessage for V8Context {
|
||||||
|
fn send_message(&self, message_type: MessageType, message: &[u8]) {
|
||||||
|
let Some(frame) = self.frame() else {
|
||||||
|
tracing::error!("Current V8 context does not have a frame, cannot send message");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
frame.send_message(message_type, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl SendMessage for Frame {
|
||||||
|
fn send_message(&self, message_type: MessageType, message: &[u8]) {
|
||||||
|
let MessageInfo { name, target } = message_type.into();
|
||||||
|
|
||||||
|
let Some(mut process_message) = cef::process_message_create(Some(&CefString::from(name.as_str()))) else {
|
||||||
|
tracing::error!("Failed to create process message: {}", name);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(arg_list) = process_message.argument_list() else { return };
|
||||||
|
let mut value = ::cef::binary_value_create(Some(message));
|
||||||
|
arg_list.set_binary(0, value.as_mut());
|
||||||
|
|
||||||
|
self.send_process_message(target, Some(&mut process_message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct UnpackedMessage<'a> {
|
||||||
|
pub(crate) message_type: MessageType,
|
||||||
|
pub(crate) data: &'a [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Sealed {}
|
||||||
|
impl Sealed for cef::ProcessMessage {}
|
||||||
|
#[allow(private_bounds)]
|
||||||
|
pub(crate) trait UnpackMessage: Sealed {
|
||||||
|
/// # Safety
|
||||||
|
///
|
||||||
|
/// The caller must ensure that the message is valid.
|
||||||
|
/// Message should come from cef.
|
||||||
|
unsafe fn unpack(&self) -> Option<UnpackedMessage<'_>>;
|
||||||
|
}
|
||||||
|
impl UnpackMessage for cef::ProcessMessage {
|
||||||
|
unsafe fn unpack(&self) -> Option<UnpackedMessage<'_>> {
|
||||||
|
let pointer: *mut cef::sys::_cef_string_utf16_t = self.name().into();
|
||||||
|
let message = unsafe { super::utility::pointer_to_string(pointer) };
|
||||||
|
let Ok(message_type) = message.try_into() else {
|
||||||
|
tracing::error!("Failed to get message type from process message");
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let arglist = self.argument_list()?;
|
||||||
|
let binary = arglist.binary(0)?;
|
||||||
|
let size = binary.size();
|
||||||
|
let ptr = binary.raw_data();
|
||||||
|
let buffer = unsafe { std::slice::from_raw_parts(ptr as *const u8, size) };
|
||||||
|
Some(UnpackedMessage { message_type, data: buffer })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::ffi::c_int;
|
||||||
|
use std::ops::DerefMut;
|
||||||
|
use std::slice::Iter;
|
||||||
|
|
||||||
|
use cef::rc::{Rc, RcImpl};
|
||||||
|
use cef::sys::{_cef_resource_handler_t, _cef_scheme_handler_factory_t, cef_base_ref_counted_t, cef_scheme_options_t};
|
||||||
|
use cef::{
|
||||||
|
Browser, Callback, CefString, Frame, ImplRequest, ImplResourceHandler, ImplResponse, ImplSchemeHandlerFactory, ImplSchemeRegistrar, Request, ResourceHandler, ResourceReadCallback, Response,
|
||||||
|
SchemeRegistrar, WrapResourceHandler, WrapSchemeHandlerFactory,
|
||||||
|
};
|
||||||
|
use include_dir::{Dir, include_dir};
|
||||||
|
|
||||||
|
pub(crate) const GRAPHITE_SCHEME: &str = "graphite-static";
|
||||||
|
pub(crate) const FRONTEND_DOMAIN: &str = "frontend";
|
||||||
|
|
||||||
|
pub(crate) struct GraphiteSchemeHandlerFactory {
|
||||||
|
object: *mut RcImpl<_cef_scheme_handler_factory_t, Self>,
|
||||||
|
}
|
||||||
|
impl GraphiteSchemeHandlerFactory {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Self { object: std::ptr::null_mut() }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn register_schemes(registrar: Option<&mut SchemeRegistrar>) {
|
||||||
|
if let Some(registrar) = registrar {
|
||||||
|
let mut scheme_options = 0;
|
||||||
|
scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_STANDARD as i32;
|
||||||
|
scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_FETCH_ENABLED as i32;
|
||||||
|
scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_SECURE as i32;
|
||||||
|
scheme_options |= cef_scheme_options_t::CEF_SCHEME_OPTION_CORS_ENABLED as i32;
|
||||||
|
registrar.add_custom_scheme(Some(&CefString::from(GRAPHITE_SCHEME)), scheme_options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ImplSchemeHandlerFactory for GraphiteSchemeHandlerFactory {
|
||||||
|
fn create(&self, _browser: Option<&mut Browser>, _frame: Option<&mut Frame>, scheme_name: Option<&CefString>, request: Option<&mut Request>) -> Option<ResourceHandler> {
|
||||||
|
if let Some(scheme_name) = scheme_name {
|
||||||
|
if scheme_name.to_string() != GRAPHITE_SCHEME {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if let Some(request) = request {
|
||||||
|
let url = CefString::from(&request.url()).to_string();
|
||||||
|
let path = url.strip_prefix(&format!("{GRAPHITE_SCHEME}://")).unwrap();
|
||||||
|
let domain = path.split('/').next().unwrap_or("");
|
||||||
|
let path = path.strip_prefix(domain).unwrap_or("");
|
||||||
|
let path = path.trim_start_matches('/');
|
||||||
|
return match domain {
|
||||||
|
FRONTEND_DOMAIN => {
|
||||||
|
if path.is_empty() {
|
||||||
|
Some(ResourceHandler::new(GraphiteFrontendResourceHandler::new("index.html")))
|
||||||
|
} else {
|
||||||
|
Some(ResourceHandler::new(GraphiteFrontendResourceHandler::new(path)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
fn get_raw(&self) -> *mut _cef_scheme_handler_factory_t {
|
||||||
|
self.object.cast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static FRONTEND: Dir = include_dir!("$CARGO_MANIFEST_DIR/../frontend/dist");
|
||||||
|
|
||||||
|
struct GraphiteFrontendResourceHandler<'a> {
|
||||||
|
object: *mut RcImpl<_cef_resource_handler_t, Self>,
|
||||||
|
data: Option<RefCell<Iter<'a, u8>>>,
|
||||||
|
mimetype: Option<String>,
|
||||||
|
}
|
||||||
|
impl<'a> GraphiteFrontendResourceHandler<'a> {
|
||||||
|
pub fn new(path: &str) -> Self {
|
||||||
|
let file = FRONTEND.get_file(path);
|
||||||
|
let data = if let Some(file) = file {
|
||||||
|
Some(RefCell::new(file.contents().iter()))
|
||||||
|
} else {
|
||||||
|
tracing::error!("Failed to find asset at path: {}", path);
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let mimetype = if let Some(file) = file {
|
||||||
|
let ext = file.path().extension().and_then(|s| s.to_str()).unwrap_or("");
|
||||||
|
|
||||||
|
// We know what file types will be in the assets this should be fine
|
||||||
|
match ext {
|
||||||
|
"html" => Some("text/html".to_string()),
|
||||||
|
"css" => Some("text/css".to_string()),
|
||||||
|
"wasm" => Some("application/wasm".to_string()),
|
||||||
|
"js" => Some("application/javascript".to_string()),
|
||||||
|
"png" => Some("image/png".to_string()),
|
||||||
|
"jpg" | "jpeg" => Some("image/jpeg".to_string()),
|
||||||
|
"svg" => Some("image/svg+xml".to_string()),
|
||||||
|
"xml" => Some("application/xml".to_string()),
|
||||||
|
"json" => Some("application/json".to_string()),
|
||||||
|
"ico" => Some("image/x-icon".to_string()),
|
||||||
|
"woff" => Some("font/woff".to_string()),
|
||||||
|
"woff2" => Some("font/woff2".to_string()),
|
||||||
|
"ttf" => Some("font/ttf".to_string()),
|
||||||
|
"otf" => Some("font/otf".to_string()),
|
||||||
|
"webmanifest" => Some("application/manifest+json".to_string()),
|
||||||
|
"graphite" => Some("application/graphite+json".to_string()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
object: std::ptr::null_mut(),
|
||||||
|
data,
|
||||||
|
mimetype,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> ImplResourceHandler for GraphiteFrontendResourceHandler<'a> {
|
||||||
|
fn open(&self, _request: Option<&mut Request>, handle_request: Option<&mut c_int>, _callback: Option<&mut Callback>) -> c_int {
|
||||||
|
if let Some(handle_request) = handle_request {
|
||||||
|
*handle_request = 1;
|
||||||
|
}
|
||||||
|
1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response_headers(&self, response: Option<&mut Response>, response_length: Option<&mut i64>, _redirect_url: Option<&mut CefString>) {
|
||||||
|
if let Some(response_length) = response_length {
|
||||||
|
*response_length = -1; // Indicating that the length is unknown
|
||||||
|
}
|
||||||
|
if let Some(response) = response {
|
||||||
|
if self.data.is_some() {
|
||||||
|
if let Some(mimetype) = &self.mimetype {
|
||||||
|
let cef_mime = CefString::from(mimetype.as_str());
|
||||||
|
response.set_mime_type(Some(&cef_mime));
|
||||||
|
} else {
|
||||||
|
response.set_mime_type(None);
|
||||||
|
}
|
||||||
|
response.set_status(200);
|
||||||
|
} else {
|
||||||
|
response.set_status(404);
|
||||||
|
response.set_mime_type(Some(&CefString::from("text/plain")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(&self, data_out: *mut u8, bytes_to_read: c_int, bytes_read: Option<&mut c_int>, _callback: Option<&mut ResourceReadCallback>) -> c_int {
|
||||||
|
let mut read = 0;
|
||||||
|
|
||||||
|
let out = unsafe { std::slice::from_raw_parts_mut(data_out, bytes_to_read as usize) };
|
||||||
|
if let Some(data) = &self.data {
|
||||||
|
let mut data = data.borrow_mut();
|
||||||
|
|
||||||
|
for (out, &data) in out.iter_mut().zip(data.deref_mut()) {
|
||||||
|
*out = data;
|
||||||
|
read += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(bytes_read) = bytes_read {
|
||||||
|
*bytes_read = read;
|
||||||
|
}
|
||||||
|
|
||||||
|
if read > 0 {
|
||||||
|
1 // Indicating that data was read
|
||||||
|
} else {
|
||||||
|
0 // Indicating no data was read
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_raw(&self) -> *mut _cef_resource_handler_t {
|
||||||
|
self.object.cast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WrapSchemeHandlerFactory for GraphiteSchemeHandlerFactory {
|
||||||
|
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_scheme_handler_factory_t, Self>) {
|
||||||
|
self.object = object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> WrapResourceHandler for GraphiteFrontendResourceHandler<'a> {
|
||||||
|
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_resource_handler_t, Self>) {
|
||||||
|
self.object = object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for GraphiteSchemeHandlerFactory {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
unsafe {
|
||||||
|
let rc_impl = &mut *self.object;
|
||||||
|
rc_impl.interface.add_ref();
|
||||||
|
}
|
||||||
|
Self { object: self.object }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> Clone for GraphiteFrontendResourceHandler<'a> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
unsafe {
|
||||||
|
let rc_impl = &mut *self.object;
|
||||||
|
rc_impl.interface.add_ref();
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
object: self.object,
|
||||||
|
data: self.data.clone(),
|
||||||
|
mimetype: self.mimetype.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rc for GraphiteSchemeHandlerFactory {
|
||||||
|
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||||
|
unsafe {
|
||||||
|
let base = &*self.object;
|
||||||
|
std::mem::transmute(&base.cef_object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> Rc for GraphiteFrontendResourceHandler<'a> {
|
||||||
|
fn as_base(&self) -> &cef_base_ref_counted_t {
|
||||||
|
unsafe {
|
||||||
|
let base = &*self.object;
|
||||||
|
std::mem::transmute(&base.cef_object)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
pub unsafe fn pointer_to_string(pointer: *mut cef::sys::_cef_string_utf16_t) -> String {
|
||||||
|
let str = unsafe { (*pointer).str_ };
|
||||||
|
let len = unsafe { (*pointer).length };
|
||||||
|
let slice = unsafe { std::slice::from_raw_parts(str, len) };
|
||||||
|
String::from_utf16(slice).unwrap()
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
use std::fs::create_dir_all;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
static APP_NAME: &str = "graphite-desktop";
|
||||||
|
|
||||||
|
pub(crate) fn ensure_dir_exists(path: &PathBuf) {
|
||||||
|
if !path.exists() {
|
||||||
|
create_dir_all(path).unwrap_or_else(|_| panic!("Failed to create directory at {path:?}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn graphite_data_dir() -> PathBuf {
|
||||||
|
let path = dirs::data_dir().expect("Failed to get data directory").join(APP_NAME);
|
||||||
|
ensure_dir_exists(&path);
|
||||||
|
path
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
use std::process::exit;
|
||||||
|
use std::time::Instant;
|
||||||
|
use std::{fmt::Debug, time::Duration};
|
||||||
|
|
||||||
|
use graphite_editor::messages::prelude::Message;
|
||||||
|
use tracing_subscriber::EnvFilter;
|
||||||
|
use winit::event_loop::EventLoop;
|
||||||
|
|
||||||
|
mod cef;
|
||||||
|
use cef::{Setup, WindowSize};
|
||||||
|
|
||||||
|
mod render;
|
||||||
|
use render::WgpuContext;
|
||||||
|
|
||||||
|
mod app;
|
||||||
|
use app::WinitApp;
|
||||||
|
|
||||||
|
mod dirs;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum CustomEvent {
|
||||||
|
UiUpdate(wgpu::Texture),
|
||||||
|
ScheduleBrowserWork(Instant),
|
||||||
|
MessageReceived { message: Message },
|
||||||
|
NodeGraphRan { texture: Option<wgpu::Texture> },
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).init();
|
||||||
|
|
||||||
|
let cef_context = match cef::Context::<Setup>::new() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(cef::SetupError::Subprocess) => exit(0),
|
||||||
|
Err(cef::SetupError::SubprocessFailed(t)) => {
|
||||||
|
tracing::error!("Subprocess of type {t} failed");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let event_loop = EventLoop::<CustomEvent>::with_user_event().build().unwrap();
|
||||||
|
|
||||||
|
let (window_size_sender, window_size_receiver) = std::sync::mpsc::channel();
|
||||||
|
|
||||||
|
let wgpu_context = futures::executor::block_on(WgpuContext::new()).unwrap();
|
||||||
|
let cef_context = match cef_context.init(cef::CefHandler::new(window_size_receiver, event_loop.create_proxy(), wgpu_context.clone())) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(cef::InitError::AlreadyRunning) => {
|
||||||
|
tracing::error!("Another instance is already running, Exiting.");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
Err(cef::InitError::InitializationFailed(code)) => {
|
||||||
|
tracing::error!("Cef initialization failed with code: {code}");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tracing::info!("Cef initialized successfully");
|
||||||
|
|
||||||
|
let rendering_loop_proxy = event_loop.create_proxy();
|
||||||
|
let target_fps = 60;
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
loop {
|
||||||
|
let last_render = Instant::now();
|
||||||
|
let (has_run, texture) = futures::executor::block_on(graphite_editor::node_graph_executor::run_node_graph());
|
||||||
|
if has_run {
|
||||||
|
let _ = rendering_loop_proxy.send_event(CustomEvent::NodeGraphRan {
|
||||||
|
texture: texture.map(|t| (*t.texture).clone()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let frame_time = Duration::from_secs_f32((target_fps as f32).recip());
|
||||||
|
let sleep = last_render + frame_time - Instant::now();
|
||||||
|
std::thread::sleep(sleep);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut winit_app = WinitApp::new(cef_context, window_size_sender, wgpu_context);
|
||||||
|
|
||||||
|
event_loop.run_app(&mut winit_app).unwrap();
|
||||||
|
}
|
||||||
@@ -0,0 +1,310 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use bytemuck::{Pod, Zeroable};
|
||||||
|
use thiserror::Error;
|
||||||
|
use winit::window::Window;
|
||||||
|
|
||||||
|
pub(crate) struct FrameBufferRef<'a> {
|
||||||
|
buffer: &'a [u8],
|
||||||
|
width: usize,
|
||||||
|
height: usize,
|
||||||
|
}
|
||||||
|
impl<'a> FrameBufferRef<'a> {
|
||||||
|
pub(crate) fn new(buffer: &'a [u8], width: usize, height: usize) -> Result<Self, FrameBufferError> {
|
||||||
|
let fb = Self { buffer, width, height };
|
||||||
|
fb.validate_size()?;
|
||||||
|
Ok(fb)
|
||||||
|
}
|
||||||
|
pub(crate) fn buffer(&self) -> &[u8] {
|
||||||
|
self.buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn width(&self) -> usize {
|
||||||
|
self.width
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn height(&self) -> usize {
|
||||||
|
self.height
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validate_size(&self) -> Result<(), FrameBufferError> {
|
||||||
|
if self.buffer.len() != self.width * self.height * 4 {
|
||||||
|
Err(FrameBufferError::InvalidSize {
|
||||||
|
buffer_size: self.buffer.len(),
|
||||||
|
expected_size: self.width * self.height * 4,
|
||||||
|
width: self.width,
|
||||||
|
height: self.height,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> std::fmt::Debug for FrameBufferRef<'a> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("FrameBuffer")
|
||||||
|
.field("width", &self.width)
|
||||||
|
.field("height", &self.height)
|
||||||
|
.field("len", &self.buffer.len())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub(crate) enum FrameBufferError {
|
||||||
|
#[error("Invalid buffer size {buffer_size}, expected {expected_size} for width {width} multiplied with height {height} multiplied by 4 channels")]
|
||||||
|
InvalidSize { buffer_size: usize, expected_size: usize, width: usize, height: usize },
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use wgpu_executor::Context as WgpuContext;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct GraphicsState {
|
||||||
|
surface: wgpu::Surface<'static>,
|
||||||
|
context: WgpuContext,
|
||||||
|
config: wgpu::SurfaceConfiguration,
|
||||||
|
render_pipeline: wgpu::RenderPipeline,
|
||||||
|
sampler: wgpu::Sampler,
|
||||||
|
viewport_scale: [f32; 2],
|
||||||
|
viewport_offset: [f32; 2],
|
||||||
|
viewport_texture: Option<wgpu::Texture>,
|
||||||
|
ui_texture: Option<wgpu::Texture>,
|
||||||
|
bind_group: Option<wgpu::BindGroup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GraphicsState {
|
||||||
|
pub(crate) fn new(window: Arc<Window>, context: WgpuContext) -> Self {
|
||||||
|
let size = window.inner_size();
|
||||||
|
|
||||||
|
let surface = context.instance.create_surface(window).unwrap();
|
||||||
|
|
||||||
|
let surface_caps = surface.get_capabilities(&context.adapter);
|
||||||
|
let surface_format = surface_caps.formats.iter().find(|f| f.is_srgb()).copied().unwrap_or(surface_caps.formats[0]);
|
||||||
|
|
||||||
|
let config = wgpu::SurfaceConfiguration {
|
||||||
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||||
|
format: surface_format,
|
||||||
|
width: size.width,
|
||||||
|
height: size.height,
|
||||||
|
present_mode: surface_caps.present_modes[0],
|
||||||
|
alpha_mode: surface_caps.alpha_modes[0],
|
||||||
|
view_formats: vec![],
|
||||||
|
desired_maximum_frame_latency: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
surface.configure(&context.device, &config);
|
||||||
|
|
||||||
|
// Create shader module
|
||||||
|
let shader = context.device.create_shader_module(wgpu::include_wgsl!("render/fullscreen_texture.wgsl"));
|
||||||
|
|
||||||
|
// Create sampler
|
||||||
|
let sampler = context.device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
|
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||||
|
mag_filter: wgpu::FilterMode::Linear,
|
||||||
|
min_filter: wgpu::FilterMode::Nearest,
|
||||||
|
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let texture_bind_group_layout = context.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 0,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
multisampled: false,
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 1,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Texture {
|
||||||
|
multisampled: false,
|
||||||
|
view_dimension: wgpu::TextureViewDimension::D2,
|
||||||
|
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||||
|
},
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
wgpu::BindGroupLayoutEntry {
|
||||||
|
binding: 2,
|
||||||
|
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||||
|
count: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: Some("texture_bind_group_layout"),
|
||||||
|
});
|
||||||
|
|
||||||
|
let render_pipeline_layout = context.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
|
label: Some("Render Pipeline Layout"),
|
||||||
|
bind_group_layouts: &[&texture_bind_group_layout],
|
||||||
|
push_constant_ranges: &[wgpu::PushConstantRange {
|
||||||
|
stages: wgpu::ShaderStages::FRAGMENT,
|
||||||
|
range: 0..size_of::<Constants>() as u32,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
|
||||||
|
let render_pipeline = context.device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
|
label: Some("Render Pipeline"),
|
||||||
|
layout: Some(&render_pipeline_layout),
|
||||||
|
vertex: wgpu::VertexState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: Some("vs_main"),
|
||||||
|
buffers: &[],
|
||||||
|
compilation_options: Default::default(),
|
||||||
|
},
|
||||||
|
fragment: Some(wgpu::FragmentState {
|
||||||
|
module: &shader,
|
||||||
|
entry_point: Some("fs_main"),
|
||||||
|
targets: &[Some(wgpu::ColorTargetState {
|
||||||
|
format: config.format,
|
||||||
|
blend: Some(wgpu::BlendState::REPLACE),
|
||||||
|
write_mask: wgpu::ColorWrites::ALL,
|
||||||
|
})],
|
||||||
|
compilation_options: Default::default(),
|
||||||
|
}),
|
||||||
|
primitive: wgpu::PrimitiveState {
|
||||||
|
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||||
|
strip_index_format: None,
|
||||||
|
front_face: wgpu::FrontFace::Ccw,
|
||||||
|
cull_mode: Some(wgpu::Face::Back),
|
||||||
|
polygon_mode: wgpu::PolygonMode::Fill,
|
||||||
|
unclipped_depth: false,
|
||||||
|
conservative: false,
|
||||||
|
},
|
||||||
|
depth_stencil: None,
|
||||||
|
multisample: wgpu::MultisampleState {
|
||||||
|
count: 1,
|
||||||
|
mask: !0,
|
||||||
|
alpha_to_coverage_enabled: false,
|
||||||
|
},
|
||||||
|
multiview: None,
|
||||||
|
cache: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
surface,
|
||||||
|
context,
|
||||||
|
config,
|
||||||
|
render_pipeline,
|
||||||
|
sampler,
|
||||||
|
viewport_scale: [1.0, 1.0],
|
||||||
|
viewport_offset: [0.0, 0.0],
|
||||||
|
viewport_texture: None,
|
||||||
|
ui_texture: None,
|
||||||
|
bind_group: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn resize(&mut self, width: u32, height: u32) {
|
||||||
|
if width > 0 && height > 0 && (self.config.width != width || self.config.height != height) {
|
||||||
|
self.config.width = width;
|
||||||
|
self.config.height = height;
|
||||||
|
self.surface.configure(&self.context.device, &self.config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn bind_ui_texture(&mut self, texture: &wgpu::Texture) {
|
||||||
|
let bind_group = self.create_bindgroup(texture, &self.viewport_texture.clone().unwrap_or(texture.clone()));
|
||||||
|
|
||||||
|
self.ui_texture = Some(texture.clone());
|
||||||
|
|
||||||
|
self.bind_group = Some(bind_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn bind_viewport_texture(&mut self, texture: &wgpu::Texture) {
|
||||||
|
let bind_group = self.create_bindgroup(&self.ui_texture.clone().unwrap_or(texture.clone()), texture);
|
||||||
|
|
||||||
|
self.viewport_texture = Some(texture.clone());
|
||||||
|
|
||||||
|
self.bind_group = Some(bind_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_viewport_scale(&mut self, scale: [f32; 2]) {
|
||||||
|
self.viewport_scale = scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_viewport_offset(&mut self, offset: [f32; 2]) {
|
||||||
|
self.viewport_offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_bindgroup(&self, ui_texture: &wgpu::Texture, viewport_texture: &wgpu::Texture) -> wgpu::BindGroup {
|
||||||
|
let ui_texture_view = ui_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
let viewport_texture_view = viewport_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
|
self.context.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout: &self.render_pipeline.get_bind_group_layout(0),
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&ui_texture_view),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&viewport_texture_view),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 2,
|
||||||
|
resource: wgpu::BindingResource::Sampler(&self.sampler),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: Some("texture_bind_group"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
|
||||||
|
let output = self.surface.get_current_texture()?;
|
||||||
|
let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
|
let mut encoder = self.context.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some("Render Encoder") });
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: Some("Render Pass"),
|
||||||
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
|
view: &view,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.01, g: 0.01, b: 0.01, a: 1.0 }),
|
||||||
|
store: wgpu::StoreOp::Store,
|
||||||
|
},
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
|
timestamp_writes: None,
|
||||||
|
});
|
||||||
|
|
||||||
|
render_pass.set_pipeline(&self.render_pipeline);
|
||||||
|
render_pass.set_push_constants(
|
||||||
|
wgpu::ShaderStages::FRAGMENT,
|
||||||
|
0,
|
||||||
|
bytemuck::bytes_of(&Constants {
|
||||||
|
viewport_scale: self.viewport_scale,
|
||||||
|
viewport_offset: self.viewport_offset,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if let Some(bind_group) = &self.bind_group {
|
||||||
|
render_pass.set_bind_group(0, bind_group, &[]);
|
||||||
|
render_pass.draw(0..6, 0..1); // Draw 3 vertices for fullscreen triangle
|
||||||
|
} else {
|
||||||
|
tracing::warn!("No bind group available - showing clear color only");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.context.queue.submit(std::iter::once(encoder.finish()));
|
||||||
|
output.present();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone, Pod, Zeroable)]
|
||||||
|
struct Constants {
|
||||||
|
viewport_scale: [f32; 2],
|
||||||
|
viewport_offset: [f32; 2],
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) clip_position: vec4<f32>,
|
||||||
|
@location(0) tex_coords: vec2<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_main(@builtin(vertex_index) vertex_index: u32) -> VertexOutput {
|
||||||
|
var out: VertexOutput;
|
||||||
|
|
||||||
|
let pos = array(
|
||||||
|
// 1st triangle
|
||||||
|
vec2f( -1.0, -1.0), // center
|
||||||
|
vec2f( 1.0, -1.0), // right, center
|
||||||
|
vec2f( -1.0, 1.0), // center, top
|
||||||
|
|
||||||
|
// 2nd triangle
|
||||||
|
vec2f( -1.0, 1.0), // center, top
|
||||||
|
vec2f( 1.0, -1.0), // right, center
|
||||||
|
vec2f( 1.0, 1.0), // right, top
|
||||||
|
);
|
||||||
|
let xy = pos[vertex_index];
|
||||||
|
out.clip_position = vec4f(xy , 0.0, 1.0);
|
||||||
|
let coords = (xy / 2. + 0.5);
|
||||||
|
out.tex_coords = vec2f(coords.x, 1. - coords.y);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Constants {
|
||||||
|
viewport_scale: vec2<f32>,
|
||||||
|
viewport_offset: vec2<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
var<push_constant> constants: Constants;
|
||||||
|
|
||||||
|
@group(0) @binding(0)
|
||||||
|
var t_ui: texture_2d<f32>;
|
||||||
|
@group(0) @binding(1)
|
||||||
|
var t_viewport: texture_2d<f32>;
|
||||||
|
@group(0) @binding(2)
|
||||||
|
var s_diffuse: sampler;
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
let ui_color: vec4<f32> = textureSample(t_ui, s_diffuse, in.tex_coords);
|
||||||
|
if (ui_color.a == 1.0) {
|
||||||
|
return ui_color;
|
||||||
|
}
|
||||||
|
let viewport_tex_coords = (in.tex_coords - constants.viewport_offset) * constants.viewport_scale;
|
||||||
|
let viewport_color: vec4<f32> = textureSample(t_viewport, s_diffuse, viewport_tex_coords);
|
||||||
|
return ui_color * ui_color.a + viewport_color * (1.0 - ui_color.a);
|
||||||
|
}
|
||||||
+3
-3
@@ -2,7 +2,7 @@
|
|||||||
name = "graphite-editor"
|
name = "graphite-editor"
|
||||||
publish = false
|
publish = false
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
rust-version = "1.85"
|
rust-version = "1.88"
|
||||||
authors = ["Graphite Authors <contact@graphite.rs>"]
|
authors = ["Graphite Authors <contact@graphite.rs>"]
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
readme = "../README.md"
|
readme = "../README.md"
|
||||||
@@ -14,8 +14,6 @@ license = "Apache-2.0"
|
|||||||
default = ["wasm"]
|
default = ["wasm"]
|
||||||
wasm = ["wasm-bindgen", "graphene-std/wasm", "wasm-bindgen-futures"]
|
wasm = ["wasm-bindgen", "graphene-std/wasm", "wasm-bindgen-futures"]
|
||||||
gpu = ["interpreted-executor/gpu", "wgpu-executor"]
|
gpu = ["interpreted-executor/gpu", "wgpu-executor"]
|
||||||
tauri = ["ron", "decouple-execution"]
|
|
||||||
decouple-execution = []
|
|
||||||
resvg = ["graphene-std/resvg"]
|
resvg = ["graphene-std/resvg"]
|
||||||
vello = ["graphene-std/vello", "resvg"]
|
vello = ["graphene-std/vello", "resvg"]
|
||||||
ron = ["dep:ron"]
|
ron = ["dep:ron"]
|
||||||
@@ -46,6 +44,8 @@ num_enum = { workspace = true }
|
|||||||
usvg = { workspace = true }
|
usvg = { workspace = true }
|
||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
web-sys = { workspace = true }
|
web-sys = { workspace = true }
|
||||||
|
bytemuck = { workspace = true }
|
||||||
|
vello = { workspace = true }
|
||||||
|
|
||||||
# Required dependencies
|
# Required dependencies
|
||||||
spin = "0.9.8"
|
spin = "0.9.8"
|
||||||
|
|||||||
@@ -101,10 +101,12 @@ pub const MIN_LENGTH_FOR_SKEW_TRIANGLE_VISIBILITY: f64 = 48.;
|
|||||||
// PATH TOOL
|
// PATH TOOL
|
||||||
pub const MANIPULATOR_GROUP_MARKER_SIZE: f64 = 6.;
|
pub const MANIPULATOR_GROUP_MARKER_SIZE: f64 = 6.;
|
||||||
pub const SELECTION_THRESHOLD: f64 = 10.;
|
pub const SELECTION_THRESHOLD: f64 = 10.;
|
||||||
|
pub const DRILL_THROUGH_THRESHOLD: f64 = 10.;
|
||||||
pub const HIDE_HANDLE_DISTANCE: f64 = 3.;
|
pub const HIDE_HANDLE_DISTANCE: f64 = 3.;
|
||||||
pub const HANDLE_ROTATE_SNAP_ANGLE: f64 = 15.;
|
pub const HANDLE_ROTATE_SNAP_ANGLE: f64 = 15.;
|
||||||
pub const SEGMENT_INSERTION_DISTANCE: f64 = 5.;
|
pub const SEGMENT_INSERTION_DISTANCE: f64 = 5.;
|
||||||
pub const SEGMENT_OVERLAY_SIZE: f64 = 10.;
|
pub const SEGMENT_OVERLAY_SIZE: f64 = 10.;
|
||||||
|
pub const SEGMENT_SELECTED_THICKNESS: f64 = 3.;
|
||||||
pub const HANDLE_LENGTH_FACTOR: f64 = 0.5;
|
pub const HANDLE_LENGTH_FACTOR: f64 = 0.5;
|
||||||
|
|
||||||
// PEN TOOL
|
// PEN TOOL
|
||||||
@@ -125,6 +127,9 @@ pub const POINT_RADIUS_HANDLE_SNAP_THRESHOLD: f64 = 8.;
|
|||||||
pub const POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD: f64 = 7.9;
|
pub const POINT_RADIUS_HANDLE_SEGMENT_THRESHOLD: f64 = 7.9;
|
||||||
pub const NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION: f64 = 1.2;
|
pub const NUMBER_OF_POINTS_DIAL_SPOKE_EXTENSION: f64 = 1.2;
|
||||||
pub const NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH: f64 = 10.;
|
pub const NUMBER_OF_POINTS_DIAL_SPOKE_LENGTH: f64 = 10.;
|
||||||
|
pub const ARC_SNAP_THRESHOLD: f64 = 5.;
|
||||||
|
pub const ARC_SWEEP_GIZMO_RADIUS: f64 = 14.;
|
||||||
|
pub const ARC_SWEEP_GIZMO_TEXT_HEIGHT: f64 = 12.;
|
||||||
pub const GIZMO_HIDE_THRESHOLD: f64 = 20.;
|
pub const GIZMO_HIDE_THRESHOLD: f64 = 20.;
|
||||||
|
|
||||||
// SCROLLBARS
|
// SCROLLBARS
|
||||||
@@ -134,13 +139,15 @@ pub const SCALE_EFFECT: f64 = 0.5;
|
|||||||
|
|
||||||
// COLORS
|
// COLORS
|
||||||
pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff";
|
pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff";
|
||||||
|
pub const COLOR_OVERLAY_BLUE_50: &str = "#00a8ff80";
|
||||||
pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848";
|
pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848";
|
||||||
pub const COLOR_OVERLAY_YELLOW_DULL: &str = "#d7ba8b";
|
pub const COLOR_OVERLAY_YELLOW_DULL: &str = "#d7ba8b";
|
||||||
pub const COLOR_OVERLAY_GREEN: &str = "#63ce63";
|
pub const COLOR_OVERLAY_GREEN: &str = "#63ce63";
|
||||||
pub const COLOR_OVERLAY_RED: &str = "#ef5454";
|
pub const COLOR_OVERLAY_RED: &str = "#ef5454";
|
||||||
pub const COLOR_OVERLAY_GRAY: &str = "#cccccc";
|
pub const COLOR_OVERLAY_GRAY: &str = "#cccccc";
|
||||||
|
pub const COLOR_OVERLAY_GRAY_25: &str = "#cccccc40";
|
||||||
pub const COLOR_OVERLAY_WHITE: &str = "#ffffff";
|
pub const COLOR_OVERLAY_WHITE: &str = "#ffffff";
|
||||||
pub const COLOR_OVERLAY_LABEL_BACKGROUND: &str = "#000000cc";
|
pub const COLOR_OVERLAY_BLACK_75: &str = "#000000bf";
|
||||||
|
|
||||||
// DOCUMENT
|
// DOCUMENT
|
||||||
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
|
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
|
||||||
|
|||||||
+31
-73
@@ -1,11 +1,10 @@
|
|||||||
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
|
use crate::messages::debug::utility_types::MessageLoggingVerbosity;
|
||||||
use crate::messages::dialog::DialogMessageData;
|
use crate::messages::dialog::DialogMessageContext;
|
||||||
use crate::messages::portfolio::document::node_graph::document_node_definitions;
|
use crate::messages::layout::layout_message_handler::LayoutMessageContext;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Dispatcher {
|
pub struct Dispatcher {
|
||||||
buffered_queue: Option<Vec<VecDeque<Message>>>,
|
|
||||||
message_queues: Vec<VecDeque<Message>>,
|
message_queues: Vec<VecDeque<Message>>,
|
||||||
pub responses: Vec<FrontendMessage>,
|
pub responses: Vec<FrontendMessage>,
|
||||||
pub message_handlers: DispatcherMessageHandlers,
|
pub message_handlers: DispatcherMessageHandlers,
|
||||||
@@ -14,8 +13,10 @@ pub struct Dispatcher {
|
|||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct DispatcherMessageHandlers {
|
pub struct DispatcherMessageHandlers {
|
||||||
animation_message_handler: AnimationMessageHandler,
|
animation_message_handler: AnimationMessageHandler,
|
||||||
|
app_window_message_handler: AppWindowMessageHandler,
|
||||||
broadcast_message_handler: BroadcastMessageHandler,
|
broadcast_message_handler: BroadcastMessageHandler,
|
||||||
debug_message_handler: DebugMessageHandler,
|
debug_message_handler: DebugMessageHandler,
|
||||||
|
defer_message_handler: DeferMessageHandler,
|
||||||
dialog_message_handler: DialogMessageHandler,
|
dialog_message_handler: DialogMessageHandler,
|
||||||
globals_message_handler: GlobalsMessageHandler,
|
globals_message_handler: GlobalsMessageHandler,
|
||||||
input_preprocessor_message_handler: InputPreprocessorMessageHandler,
|
input_preprocessor_message_handler: InputPreprocessorMessageHandler,
|
||||||
@@ -50,7 +51,10 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
|
|||||||
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerStructure),
|
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerStructure),
|
||||||
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
|
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
|
||||||
];
|
];
|
||||||
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::AnimationFrame))];
|
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[
|
||||||
|
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::AnimationFrame)),
|
||||||
|
MessageDiscriminant::Animation(AnimationMessageDiscriminant::IncrementFrameCounter),
|
||||||
|
];
|
||||||
// TODO: Find a way to combine these with the list above. We use strings for now since these are the standard variant names used by multiple messages. But having these also type-checked would be best.
|
// TODO: Find a way to combine these with the list above. We use strings for now since these are the standard variant names used by multiple messages. But having these also type-checked would be best.
|
||||||
const DEBUG_MESSAGE_ENDING_BLOCK_LIST: &[&str] = &["PointerMove", "PointerOutsideViewport", "Overlays", "Draw", "CurrentTime", "Time"];
|
const DEBUG_MESSAGE_ENDING_BLOCK_LIST: &[&str] = &["PointerMove", "PointerOutsideViewport", "Overlays", "Draw", "CurrentTime", "Time"];
|
||||||
|
|
||||||
@@ -90,14 +94,6 @@ impl Dispatcher {
|
|||||||
|
|
||||||
pub fn handle_message<T: Into<Message>>(&mut self, message: T, process_after_all_current: bool) {
|
pub fn handle_message<T: Into<Message>>(&mut self, message: T, process_after_all_current: bool) {
|
||||||
let message = message.into();
|
let message = message.into();
|
||||||
// Add all additional messages to the buffer if it exists (except from the end buffer message)
|
|
||||||
if !matches!(message, Message::EndBuffer(_)) {
|
|
||||||
if let Some(buffered_queue) = &mut self.buffered_queue {
|
|
||||||
Self::schedule_execution(buffered_queue, true, [message]);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we are not maintaining the buffer, simply add to the current queue
|
// If we are not maintaining the buffer, simply add to the current queue
|
||||||
Self::schedule_execution(&mut self.message_queues, process_after_all_current, [message]);
|
Self::schedule_execution(&mut self.message_queues, process_after_all_current, [message]);
|
||||||
@@ -126,71 +122,25 @@ impl Dispatcher {
|
|||||||
|
|
||||||
// Process the action by forwarding it to the relevant message handler, or saving the FrontendMessage to be sent to the frontend
|
// Process the action by forwarding it to the relevant message handler, or saving the FrontendMessage to be sent to the frontend
|
||||||
match message {
|
match message {
|
||||||
Message::StartBuffer => {
|
|
||||||
self.buffered_queue = Some(std::mem::take(&mut self.message_queues));
|
|
||||||
}
|
|
||||||
Message::EndBuffer(render_metadata) => {
|
|
||||||
// Assign the message queue to the currently buffered queue
|
|
||||||
if let Some(buffered_queue) = self.buffered_queue.take() {
|
|
||||||
self.cleanup_queues(false);
|
|
||||||
assert!(self.message_queues.is_empty(), "message queues are always empty when ending a buffer");
|
|
||||||
self.message_queues = buffered_queue;
|
|
||||||
};
|
|
||||||
|
|
||||||
let graphene_std::renderer::RenderMetadata {
|
|
||||||
upstream_footprints: footprints,
|
|
||||||
local_transforms,
|
|
||||||
first_instance_source_id,
|
|
||||||
click_targets,
|
|
||||||
clip_targets,
|
|
||||||
} = render_metadata;
|
|
||||||
|
|
||||||
// Run these update state messages immediately
|
|
||||||
let messages = [
|
|
||||||
DocumentMessage::UpdateUpstreamTransforms {
|
|
||||||
upstream_footprints: footprints,
|
|
||||||
local_transforms,
|
|
||||||
first_instance_source_id,
|
|
||||||
},
|
|
||||||
DocumentMessage::UpdateClickTargets { click_targets },
|
|
||||||
DocumentMessage::UpdateClipTargets { clip_targets },
|
|
||||||
];
|
|
||||||
Self::schedule_execution(&mut self.message_queues, false, messages.map(Message::from));
|
|
||||||
}
|
|
||||||
Message::NoOp => {}
|
|
||||||
Message::Init => {
|
|
||||||
// Load persistent data from the browser database
|
|
||||||
queue.add(FrontendMessage::TriggerLoadFirstAutoSaveDocument);
|
|
||||||
queue.add(FrontendMessage::TriggerLoadPreferences);
|
|
||||||
|
|
||||||
// Display the menu bar at the top of the window
|
|
||||||
queue.add(MenuBarMessage::SendLayout);
|
|
||||||
|
|
||||||
// Send the information for tooltips and categories for each node/input.
|
|
||||||
queue.add(FrontendMessage::SendUIMetadata {
|
|
||||||
node_descriptions: document_node_definitions::collect_node_descriptions(),
|
|
||||||
node_types: document_node_definitions::collect_node_types(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Finish loading persistent data from the browser database
|
|
||||||
queue.add(FrontendMessage::TriggerLoadRestAutoSaveDocuments);
|
|
||||||
}
|
|
||||||
Message::Animation(message) => {
|
Message::Animation(message) => {
|
||||||
self.message_handlers.animation_message_handler.process_message(message, &mut queue, ());
|
self.message_handlers.animation_message_handler.process_message(message, &mut queue, ());
|
||||||
}
|
}
|
||||||
Message::Batched(messages) => {
|
Message::AppWindow(message) => {
|
||||||
messages.iter().for_each(|message| self.handle_message(message.to_owned(), false));
|
self.message_handlers.app_window_message_handler.process_message(message, &mut queue, ());
|
||||||
}
|
}
|
||||||
Message::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()),
|
Message::Broadcast(message) => self.message_handlers.broadcast_message_handler.process_message(message, &mut queue, ()),
|
||||||
Message::Debug(message) => {
|
Message::Debug(message) => {
|
||||||
self.message_handlers.debug_message_handler.process_message(message, &mut queue, ());
|
self.message_handlers.debug_message_handler.process_message(message, &mut queue, ());
|
||||||
}
|
}
|
||||||
|
Message::Defer(message) => {
|
||||||
|
self.message_handlers.defer_message_handler.process_message(message, &mut queue, ());
|
||||||
|
}
|
||||||
Message::Dialog(message) => {
|
Message::Dialog(message) => {
|
||||||
let data = DialogMessageData {
|
let context = DialogMessageContext {
|
||||||
portfolio: &self.message_handlers.portfolio_message_handler,
|
portfolio: &self.message_handlers.portfolio_message_handler,
|
||||||
preferences: &self.message_handlers.preferences_message_handler,
|
preferences: &self.message_handlers.preferences_message_handler,
|
||||||
};
|
};
|
||||||
self.message_handlers.dialog_message_handler.process_message(message, &mut queue, data);
|
self.message_handlers.dialog_message_handler.process_message(message, &mut queue, context);
|
||||||
}
|
}
|
||||||
Message::Frontend(message) => {
|
Message::Frontend(message) => {
|
||||||
// Handle these messages immediately by returning early
|
// Handle these messages immediately by returning early
|
||||||
@@ -213,7 +163,7 @@ impl Dispatcher {
|
|||||||
|
|
||||||
self.message_handlers
|
self.message_handlers
|
||||||
.input_preprocessor_message_handler
|
.input_preprocessor_message_handler
|
||||||
.process_message(message, &mut queue, InputPreprocessorMessageData { keyboard_platform });
|
.process_message(message, &mut queue, InputPreprocessorMessageContext { keyboard_platform });
|
||||||
}
|
}
|
||||||
Message::KeyMapping(message) => {
|
Message::KeyMapping(message) => {
|
||||||
let input = &self.message_handlers.input_preprocessor_message_handler;
|
let input = &self.message_handlers.input_preprocessor_message_handler;
|
||||||
@@ -221,12 +171,13 @@ impl Dispatcher {
|
|||||||
|
|
||||||
self.message_handlers
|
self.message_handlers
|
||||||
.key_mapping_message_handler
|
.key_mapping_message_handler
|
||||||
.process_message(message, &mut queue, KeyMappingMessageData { input, actions });
|
.process_message(message, &mut queue, KeyMappingMessageContext { input, actions });
|
||||||
}
|
}
|
||||||
Message::Layout(message) => {
|
Message::Layout(message) => {
|
||||||
let action_input_mapping = &|action_to_find: &MessageDiscriminant| self.message_handlers.key_mapping_message_handler.action_input_mapping(action_to_find);
|
let action_input_mapping = &|action_to_find: &MessageDiscriminant| self.message_handlers.key_mapping_message_handler.action_input_mapping(action_to_find);
|
||||||
|
let context = LayoutMessageContext { action_input_mapping };
|
||||||
|
|
||||||
self.message_handlers.layout_message_handler.process_message(message, &mut queue, action_input_mapping);
|
self.message_handlers.layout_message_handler.process_message(message, &mut queue, context);
|
||||||
}
|
}
|
||||||
Message::Portfolio(message) => {
|
Message::Portfolio(message) => {
|
||||||
let ipp = &self.message_handlers.input_preprocessor_message_handler;
|
let ipp = &self.message_handlers.input_preprocessor_message_handler;
|
||||||
@@ -240,7 +191,7 @@ impl Dispatcher {
|
|||||||
self.message_handlers.portfolio_message_handler.process_message(
|
self.message_handlers.portfolio_message_handler.process_message(
|
||||||
message,
|
message,
|
||||||
&mut queue,
|
&mut queue,
|
||||||
PortfolioMessageData {
|
PortfolioMessageContext {
|
||||||
ipp,
|
ipp,
|
||||||
preferences,
|
preferences,
|
||||||
current_tool,
|
current_tool,
|
||||||
@@ -255,13 +206,16 @@ impl Dispatcher {
|
|||||||
self.message_handlers.preferences_message_handler.process_message(message, &mut queue, ());
|
self.message_handlers.preferences_message_handler.process_message(message, &mut queue, ());
|
||||||
}
|
}
|
||||||
Message::Tool(message) => {
|
Message::Tool(message) => {
|
||||||
let document_id = self.message_handlers.portfolio_message_handler.active_document_id().unwrap();
|
let Some(document_id) = self.message_handlers.portfolio_message_handler.active_document_id() else {
|
||||||
let Some(document) = self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id) else {
|
|
||||||
warn!("Called ToolMessage without an active document.\nGot {message:?}");
|
warn!("Called ToolMessage without an active document.\nGot {message:?}");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
let Some(document) = self.message_handlers.portfolio_message_handler.documents.get_mut(&document_id) else {
|
||||||
|
warn!("Called ToolMessage with an invalid active document.\nGot {message:?}");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let data = ToolMessageData {
|
let context = ToolMessageContext {
|
||||||
document_id,
|
document_id,
|
||||||
document,
|
document,
|
||||||
input: &self.message_handlers.input_preprocessor_message_handler,
|
input: &self.message_handlers.input_preprocessor_message_handler,
|
||||||
@@ -270,11 +224,15 @@ impl Dispatcher {
|
|||||||
preferences: &self.message_handlers.preferences_message_handler,
|
preferences: &self.message_handlers.preferences_message_handler,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.message_handlers.tool_message_handler.process_message(message, &mut queue, data);
|
self.message_handlers.tool_message_handler.process_message(message, &mut queue, context);
|
||||||
}
|
}
|
||||||
Message::Workspace(message) => {
|
Message::Workspace(message) => {
|
||||||
self.message_handlers.workspace_message_handler.process_message(message, &mut queue, ());
|
self.message_handlers.workspace_message_handler.process_message(message, &mut queue, ());
|
||||||
}
|
}
|
||||||
|
Message::NoOp => {}
|
||||||
|
Message::Batched { messages } => {
|
||||||
|
messages.iter().for_each(|message| self.handle_message(message.to_owned(), false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are child messages, append the queue to the list of queues
|
// If there are child messages, append the queue to the list of queues
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ pub enum AnimationMessage {
|
|||||||
EnableLivePreview,
|
EnableLivePreview,
|
||||||
DisableLivePreview,
|
DisableLivePreview,
|
||||||
RestartAnimation,
|
RestartAnimation,
|
||||||
SetFrameIndex(f64),
|
SetFrameIndex { frame: f64 },
|
||||||
SetTime(f64),
|
SetTime { time: f64 },
|
||||||
UpdateTime,
|
UpdateTime,
|
||||||
IncrementFrameCounter,
|
IncrementFrameCounter,
|
||||||
SetAnimationTimeMode(AnimationTimeMode),
|
SetAnimationTimeMode { animation_time_mode: AnimationTimeMode },
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ impl AnimationMessageHandler {
|
|||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
|
impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
|
||||||
fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque<Message>, _: ()) {
|
||||||
match message {
|
match message {
|
||||||
AnimationMessage::ToggleLivePreview => match self.animation_state {
|
AnimationMessage::ToggleLivePreview => match self.animation_state {
|
||||||
AnimationState::Stopped => responses.add(AnimationMessage::EnableLivePreview),
|
AnimationState::Stopped => responses.add(AnimationMessage::EnableLivePreview),
|
||||||
@@ -82,13 +82,13 @@ impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
|
|||||||
// Update the restart and pause/play buttons
|
// Update the restart and pause/play buttons
|
||||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||||
}
|
}
|
||||||
AnimationMessage::SetFrameIndex(frame) => {
|
AnimationMessage::SetFrameIndex { frame } => {
|
||||||
self.frame_index = frame;
|
self.frame_index = frame;
|
||||||
responses.add(PortfolioMessage::SubmitActiveGraphRender);
|
responses.add(PortfolioMessage::SubmitActiveGraphRender);
|
||||||
// Update the restart and pause/play buttons
|
// Update the restart and pause/play buttons
|
||||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||||
}
|
}
|
||||||
AnimationMessage::SetTime(time) => {
|
AnimationMessage::SetTime { time } => {
|
||||||
self.timestamp = time;
|
self.timestamp = time;
|
||||||
responses.add(AnimationMessage::UpdateTime);
|
responses.add(AnimationMessage::UpdateTime);
|
||||||
}
|
}
|
||||||
@@ -120,7 +120,7 @@ impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
|
|||||||
// Update the restart and pause/play buttons
|
// Update the restart and pause/play buttons
|
||||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||||
}
|
}
|
||||||
AnimationMessage::SetAnimationTimeMode(animation_time_mode) => {
|
AnimationMessage::SetAnimationTimeMode { animation_time_mode } => {
|
||||||
self.animation_time_mode = animation_time_mode;
|
self.animation_time_mode = animation_time_mode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
|
#[impl_message(Message, AppWindow)]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum AppWindowMessage {
|
||||||
|
AppWindowMinimize,
|
||||||
|
AppWindowMaximize,
|
||||||
|
AppWindowClose,
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
use crate::messages::app_window::AppWindowMessage;
|
||||||
|
use crate::messages::prelude::*;
|
||||||
|
use graphite_proc_macros::{ExtractField, message_handler_data};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, ExtractField)]
|
||||||
|
pub struct AppWindowMessageHandler {
|
||||||
|
platform: AppWindowPlatform,
|
||||||
|
maximized: bool,
|
||||||
|
viewport_hole_punch_active: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
|
impl MessageHandler<AppWindowMessage, ()> for AppWindowMessageHandler {
|
||||||
|
fn process_message(&mut self, message: AppWindowMessage, responses: &mut std::collections::VecDeque<Message>, _: ()) {
|
||||||
|
match message {
|
||||||
|
AppWindowMessage::AppWindowMinimize => {
|
||||||
|
self.platform = if self.platform == AppWindowPlatform::Mac {
|
||||||
|
AppWindowPlatform::Windows
|
||||||
|
} else {
|
||||||
|
AppWindowPlatform::Mac
|
||||||
|
};
|
||||||
|
responses.add(FrontendMessage::UpdatePlatform { platform: self.platform });
|
||||||
|
}
|
||||||
|
AppWindowMessage::AppWindowMaximize => {
|
||||||
|
self.maximized = !self.maximized;
|
||||||
|
responses.add(FrontendMessage::UpdateMaximized { maximized: self.maximized });
|
||||||
|
|
||||||
|
self.viewport_hole_punch_active = !self.viewport_hole_punch_active;
|
||||||
|
responses.add(FrontendMessage::UpdateViewportHolePunch {
|
||||||
|
active: self.viewport_hole_punch_active,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
AppWindowMessage::AppWindowClose => {
|
||||||
|
self.platform = AppWindowPlatform::Web;
|
||||||
|
responses.add(FrontendMessage::UpdatePlatform { platform: self.platform });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn actions(&self) -> ActionList {
|
||||||
|
actions!(AppWindowMessageDiscriminant;)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
|
pub enum AppWindowPlatform {
|
||||||
|
#[default]
|
||||||
|
Web,
|
||||||
|
Windows,
|
||||||
|
Mac,
|
||||||
|
Linux,
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
mod app_window_message;
|
||||||
|
pub mod app_window_message_handler;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use app_window_message::{AppWindowMessage, AppWindowMessageDiscriminant};
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use app_window_message_handler::AppWindowMessageHandler;
|
||||||
@@ -7,7 +7,7 @@ pub struct BroadcastMessageHandler {
|
|||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl MessageHandler<BroadcastMessage, ()> for BroadcastMessageHandler {
|
impl MessageHandler<BroadcastMessage, ()> for BroadcastMessageHandler {
|
||||||
fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque<Message>, _: ()) {
|
||||||
match message {
|
match message {
|
||||||
// Sub-messages
|
// Sub-messages
|
||||||
BroadcastMessage::TriggerEvent(event) => {
|
BroadcastMessage::TriggerEvent(event) => {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ pub struct DebugMessageHandler {
|
|||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl MessageHandler<DebugMessage, ()> for DebugMessageHandler {
|
impl MessageHandler<DebugMessage, ()> for DebugMessageHandler {
|
||||||
fn process_message(&mut self, message: DebugMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
fn process_message(&mut self, message: DebugMessage, responses: &mut VecDeque<Message>, _: ()) {
|
||||||
match message {
|
match message {
|
||||||
DebugMessage::ToggleTraceLogs => {
|
DebugMessage::ToggleTraceLogs => {
|
||||||
if log::max_level() == log::LevelFilter::Debug {
|
if log::max_level() == log::LevelFilter::Debug {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
|
#[impl_message(Message, Defer)]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum DeferMessage {
|
||||||
|
SetGraphSubmissionIndex(u64),
|
||||||
|
TriggerGraphRun(u64),
|
||||||
|
AfterGraphRun { messages: Vec<Message> },
|
||||||
|
TriggerNavigationReady,
|
||||||
|
AfterNavigationReady { messages: Vec<Message> },
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, ExtractField)]
|
||||||
|
pub struct DeferMessageHandler {
|
||||||
|
after_graph_run: Vec<(u64, Message)>,
|
||||||
|
after_viewport_resize: Vec<Message>,
|
||||||
|
current_graph_submission_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[message_handler_data]
|
||||||
|
impl MessageHandler<DeferMessage, ()> for DeferMessageHandler {
|
||||||
|
fn process_message(&mut self, message: DeferMessage, responses: &mut VecDeque<Message>, _: ()) {
|
||||||
|
match message {
|
||||||
|
DeferMessage::AfterGraphRun { mut messages } => {
|
||||||
|
self.after_graph_run.extend(messages.drain(..).map(|m| (self.current_graph_submission_id, m)));
|
||||||
|
}
|
||||||
|
DeferMessage::AfterNavigationReady { messages } => {
|
||||||
|
self.after_viewport_resize.extend_from_slice(&messages);
|
||||||
|
}
|
||||||
|
DeferMessage::SetGraphSubmissionIndex(execution_id) => {
|
||||||
|
self.current_graph_submission_id = execution_id + 1;
|
||||||
|
}
|
||||||
|
DeferMessage::TriggerGraphRun(execution_id) => {
|
||||||
|
if self.after_graph_run.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Find the index of the last message we can process
|
||||||
|
let num_elements_to_remove = self.after_graph_run.binary_search_by_key(&(execution_id + 1), |x| x.0).unwrap_or_else(|pos| pos - 1);
|
||||||
|
let elements = self.after_graph_run.drain(0..=num_elements_to_remove);
|
||||||
|
for (_, message) in elements.rev() {
|
||||||
|
responses.add_front(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DeferMessage::TriggerNavigationReady => {
|
||||||
|
for message in self.after_viewport_resize.drain(..).rev() {
|
||||||
|
responses.add_front(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
advertise_actions!(DeferMessageDiscriminant;
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
mod defer_message;
|
||||||
|
mod defer_message_handler;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use defer_message::{DeferMessage, DeferMessageDiscriminant};
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use defer_message_handler::DeferMessageHandler;
|
||||||
@@ -3,7 +3,7 @@ use crate::messages::layout::utility_types::widget_prelude::*;
|
|||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
#[derive(ExtractField)]
|
#[derive(ExtractField)]
|
||||||
pub struct DialogMessageData<'a> {
|
pub struct DialogMessageContext<'a> {
|
||||||
pub portfolio: &'a PortfolioMessageHandler,
|
pub portfolio: &'a PortfolioMessageHandler,
|
||||||
pub preferences: &'a PreferencesMessageHandler,
|
pub preferences: &'a PreferencesMessageHandler,
|
||||||
}
|
}
|
||||||
@@ -17,14 +17,14 @@ pub struct DialogMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl MessageHandler<DialogMessage, DialogMessageData<'_>> for DialogMessageHandler {
|
impl MessageHandler<DialogMessage, DialogMessageContext<'_>> for DialogMessageHandler {
|
||||||
fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque<Message>, data: DialogMessageData) {
|
fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque<Message>, context: DialogMessageContext) {
|
||||||
let DialogMessageData { portfolio, preferences } = data;
|
let DialogMessageContext { portfolio, preferences } = context;
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
DialogMessage::ExportDialog(message) => self.export_dialog.process_message(message, responses, ExportDialogMessageData { portfolio }),
|
DialogMessage::ExportDialog(message) => self.export_dialog.process_message(message, responses, ExportDialogMessageContext { portfolio }),
|
||||||
DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_message(message, responses, ()),
|
DialogMessage::NewDocumentDialog(message) => self.new_document_dialog.process_message(message, responses, ()),
|
||||||
DialogMessage::PreferencesDialog(message) => self.preferences_dialog.process_message(message, responses, PreferencesDialogMessageData { preferences }),
|
DialogMessage::PreferencesDialog(message) => self.preferences_dialog.process_message(message, responses, PreferencesDialogMessageContext { preferences }),
|
||||||
|
|
||||||
DialogMessage::CloseAllDocumentsWithConfirmation => {
|
DialogMessage::CloseAllDocumentsWithConfirmation => {
|
||||||
let dialog = simple_dialogs::CloseAllDocumentsDialog {
|
let dialog = simple_dialogs::CloseAllDocumentsDialog {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
|||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
#[derive(ExtractField)]
|
#[derive(ExtractField)]
|
||||||
pub struct ExportDialogMessageData<'a> {
|
pub struct ExportDialogMessageContext<'a> {
|
||||||
pub portfolio: &'a PortfolioMessageHandler,
|
pub portfolio: &'a PortfolioMessageHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,9 +33,9 @@ impl Default for ExportDialogMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl MessageHandler<ExportDialogMessage, ExportDialogMessageData<'_>> for ExportDialogMessageHandler {
|
impl MessageHandler<ExportDialogMessage, ExportDialogMessageContext<'_>> for ExportDialogMessageHandler {
|
||||||
fn process_message(&mut self, message: ExportDialogMessage, responses: &mut VecDeque<Message>, data: ExportDialogMessageData) {
|
fn process_message(&mut self, message: ExportDialogMessage, responses: &mut VecDeque<Message>, context: ExportDialogMessageContext) {
|
||||||
let ExportDialogMessageData { portfolio } = data;
|
let ExportDialogMessageContext { portfolio } = context;
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
ExportDialogMessage::FileType(export_type) => self.file_type = export_type,
|
ExportDialogMessage::FileType(export_type) => self.file_type = export_type,
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ mod export_dialog_message_handler;
|
|||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use export_dialog_message::{ExportDialogMessage, ExportDialogMessageDiscriminant};
|
pub use export_dialog_message::{ExportDialogMessage, ExportDialogMessageDiscriminant};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use export_dialog_message_handler::{ExportDialogMessageData, ExportDialogMessageHandler};
|
pub use export_dialog_message_handler::{ExportDialogMessageContext, ExportDialogMessageHandler};
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ pub mod simple_dialogs;
|
|||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use dialog_message::{DialogMessage, DialogMessageDiscriminant};
|
pub use dialog_message::{DialogMessage, DialogMessageDiscriminant};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use dialog_message_handler::{DialogMessageData, DialogMessageHandler};
|
pub use dialog_message_handler::{DialogMessageContext, DialogMessageHandler};
|
||||||
|
|||||||
+13
-11
@@ -13,7 +13,7 @@ pub struct NewDocumentDialogMessageHandler {
|
|||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHandler {
|
impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHandler {
|
||||||
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, _data: ()) {
|
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, _: ()) {
|
||||||
match message {
|
match message {
|
||||||
NewDocumentDialogMessage::Name(name) => self.name = name,
|
NewDocumentDialogMessage::Name(name) => self.name = name,
|
||||||
NewDocumentDialogMessage::Infinite(infinite) => self.infinite = infinite,
|
NewDocumentDialogMessage::Infinite(infinite) => self.infinite = infinite,
|
||||||
@@ -24,18 +24,20 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
|
|||||||
|
|
||||||
let create_artboard = !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0;
|
let create_artboard = !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0;
|
||||||
if create_artboard {
|
if create_artboard {
|
||||||
responses.add(Message::StartBuffer);
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
responses.add(GraphOperationMessage::NewArtboard {
|
responses.add(DeferMessage::AfterGraphRun {
|
||||||
id: NodeId::new(),
|
messages: vec![
|
||||||
artboard: graphene_std::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()),
|
GraphOperationMessage::NewArtboard {
|
||||||
|
id: NodeId::new(),
|
||||||
|
artboard: graphene_std::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
responses.add(DeferMessage::AfterNavigationReady {
|
||||||
|
messages: vec![DocumentMessage::ZoomCanvasToFitAll.into(), DocumentMessage::DeselectAllLayers.into()],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Figure out how to get StartBuffer to work here so we can delete this and use `DocumentMessage::ZoomCanvasToFitAll` instead
|
|
||||||
// Currently, it is necessary to use `FrontendMessage::TriggerDelayedZoomCanvasToFitAll` rather than `DocumentMessage::ZoomCanvasToFitAll` because the size of the viewport is not yet populated
|
|
||||||
responses.add(Message::StartBuffer);
|
|
||||||
responses.add(FrontendMessage::TriggerDelayedZoomCanvasToFitAll);
|
|
||||||
responses.add(DocumentMessage::DeselectAllLayers);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ mod preferences_dialog_message_handler;
|
|||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use preferences_dialog_message::{PreferencesDialogMessage, PreferencesDialogMessageDiscriminant};
|
pub use preferences_dialog_message::{PreferencesDialogMessage, PreferencesDialogMessageDiscriminant};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use preferences_dialog_message_handler::{PreferencesDialogMessageData, PreferencesDialogMessageHandler};
|
pub use preferences_dialog_message_handler::{PreferencesDialogMessageContext, PreferencesDialogMessageHandler};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::messages::preferences::SelectionMode;
|
|||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
#[derive(ExtractField)]
|
#[derive(ExtractField)]
|
||||||
pub struct PreferencesDialogMessageData<'a> {
|
pub struct PreferencesDialogMessageContext<'a> {
|
||||||
pub preferences: &'a PreferencesMessageHandler,
|
pub preferences: &'a PreferencesMessageHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -14,9 +14,9 @@ pub struct PreferencesDialogMessageData<'a> {
|
|||||||
pub struct PreferencesDialogMessageHandler {}
|
pub struct PreferencesDialogMessageHandler {}
|
||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl MessageHandler<PreferencesDialogMessage, PreferencesDialogMessageData<'_>> for PreferencesDialogMessageHandler {
|
impl MessageHandler<PreferencesDialogMessage, PreferencesDialogMessageContext<'_>> for PreferencesDialogMessageHandler {
|
||||||
fn process_message(&mut self, message: PreferencesDialogMessage, responses: &mut VecDeque<Message>, data: PreferencesDialogMessageData) {
|
fn process_message(&mut self, message: PreferencesDialogMessage, responses: &mut VecDeque<Message>, context: PreferencesDialogMessageContext) {
|
||||||
let PreferencesDialogMessageData { preferences } = data;
|
let PreferencesDialogMessageContext { preferences } = context;
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
PreferencesDialogMessage::Confirm => {}
|
PreferencesDialogMessage::Confirm => {}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
|
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
|
||||||
|
use crate::messages::app_window::app_window_message_handler::AppWindowPlatform;
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::portfolio::document::node_graph::utility_types::{
|
use crate::messages::portfolio::document::node_graph::utility_types::{
|
||||||
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, Transform,
|
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, Transform,
|
||||||
@@ -8,8 +9,9 @@ use crate::messages::portfolio::document::utility_types::wires::{WirePath, WireP
|
|||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::utility_types::HintData;
|
use crate::messages::tool::utility_types::HintData;
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
|
use graphene_std::raster::Image;
|
||||||
use graphene_std::raster::color::Color;
|
use graphene_std::raster::color::Color;
|
||||||
use graphene_std::text::Font;
|
use graphene_std::text::{Font, TextAlign};
|
||||||
|
|
||||||
#[impl_message(Message, Frontend)]
|
#[impl_message(Message, Frontend)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
@@ -37,6 +39,7 @@ pub enum FrontendMessage {
|
|||||||
max_width: Option<f64>,
|
max_width: Option<f64>,
|
||||||
#[serde(rename = "maxHeight")]
|
#[serde(rename = "maxHeight")]
|
||||||
max_height: Option<f64>,
|
max_height: Option<f64>,
|
||||||
|
align: TextAlign,
|
||||||
},
|
},
|
||||||
DisplayEditableTextboxTransform {
|
DisplayEditableTextboxTransform {
|
||||||
transform: [f64; 6],
|
transform: [f64; 6],
|
||||||
@@ -56,7 +59,6 @@ pub enum FrontendMessage {
|
|||||||
#[serde(rename = "commitDate")]
|
#[serde(rename = "commitDate")]
|
||||||
commit_date: String,
|
commit_date: String,
|
||||||
},
|
},
|
||||||
TriggerDelayedZoomCanvasToFitAll,
|
|
||||||
TriggerDownloadImage {
|
TriggerDownloadImage {
|
||||||
svg: String,
|
svg: String,
|
||||||
name: String,
|
name: String,
|
||||||
@@ -179,6 +181,9 @@ pub enum FrontendMessage {
|
|||||||
UpdateDocumentArtwork {
|
UpdateDocumentArtwork {
|
||||||
svg: String,
|
svg: String,
|
||||||
},
|
},
|
||||||
|
UpdateImageData {
|
||||||
|
image_data: Vec<(u64, Image<Color>)>,
|
||||||
|
},
|
||||||
UpdateDocumentBarLayout {
|
UpdateDocumentBarLayout {
|
||||||
#[serde(rename = "layoutTarget")]
|
#[serde(rename = "layoutTarget")]
|
||||||
layout_target: LayoutTarget,
|
layout_target: LayoutTarget,
|
||||||
@@ -304,4 +309,13 @@ pub enum FrontendMessage {
|
|||||||
layout_target: LayoutTarget,
|
layout_target: LayoutTarget,
|
||||||
diff: Vec<WidgetDiff>,
|
diff: Vec<WidgetDiff>,
|
||||||
},
|
},
|
||||||
|
UpdatePlatform {
|
||||||
|
platform: AppWindowPlatform,
|
||||||
|
},
|
||||||
|
UpdateMaximized {
|
||||||
|
maximized: bool,
|
||||||
|
},
|
||||||
|
UpdateViewportHolePunch {
|
||||||
|
active: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ pub struct GlobalsMessageHandler {}
|
|||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl MessageHandler<GlobalsMessage, ()> for GlobalsMessageHandler {
|
impl MessageHandler<GlobalsMessage, ()> for GlobalsMessageHandler {
|
||||||
fn process_message(&mut self, message: GlobalsMessage, _responses: &mut VecDeque<Message>, _data: ()) {
|
fn process_message(&mut self, message: GlobalsMessage, _responses: &mut VecDeque<Message>, _: ()) {
|
||||||
match message {
|
match message {
|
||||||
GlobalsMessage::SetPlatform { platform } => {
|
GlobalsMessage::SetPlatform { platform } => {
|
||||||
if GLOBAL_PLATFORM.get() != Some(&platform) {
|
if GLOBAL_PLATFORM.get() != Some(&platform) {
|
||||||
|
|||||||
@@ -19,5 +19,6 @@ pub enum InputMapperMessage {
|
|||||||
|
|
||||||
// Messages
|
// Messages
|
||||||
PointerMove,
|
PointerMove,
|
||||||
|
PointerShake,
|
||||||
WheelScroll,
|
WheelScroll,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use crate::messages::prelude::*;
|
|||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
#[derive(ExtractField)]
|
#[derive(ExtractField)]
|
||||||
pub struct InputMapperMessageData<'a> {
|
pub struct InputMapperMessageContext<'a> {
|
||||||
pub input: &'a InputPreprocessorMessageHandler,
|
pub input: &'a InputPreprocessorMessageHandler,
|
||||||
pub actions: ActionList,
|
pub actions: ActionList,
|
||||||
}
|
}
|
||||||
@@ -18,9 +18,9 @@ pub struct InputMapperMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl MessageHandler<InputMapperMessage, InputMapperMessageData<'_>> for InputMapperMessageHandler {
|
impl MessageHandler<InputMapperMessage, InputMapperMessageContext<'_>> for InputMapperMessageHandler {
|
||||||
fn process_message(&mut self, message: InputMapperMessage, responses: &mut VecDeque<Message>, data: InputMapperMessageData) {
|
fn process_message(&mut self, message: InputMapperMessage, responses: &mut VecDeque<Message>, context: InputMapperMessageContext) {
|
||||||
let InputMapperMessageData { input, actions } = data;
|
let InputMapperMessageContext { input, actions } = context;
|
||||||
|
|
||||||
if let Some(message) = self.mapping.match_input_message(message, &input.keyboard, actions) {
|
if let Some(message) = self.mapping.match_input_message(message, &input.keyboard, actions) {
|
||||||
responses.add(message);
|
responses.add(message);
|
||||||
|
|||||||
@@ -54,14 +54,15 @@ pub fn input_mappings() -> Mapping {
|
|||||||
entry!(KeyDown(KeyZ); modifiers=[Accel, MouseLeft], action_dispatch=DocumentMessage::Noop),
|
entry!(KeyDown(KeyZ); modifiers=[Accel, MouseLeft], action_dispatch=DocumentMessage::Noop),
|
||||||
//
|
//
|
||||||
// NodeGraphMessage
|
// NodeGraphMessage
|
||||||
entry!(KeyDown(MouseLeft); action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: false, right_click: false}),
|
entry!(KeyDown(MouseLeft); action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: false, right_click: false }),
|
||||||
entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=NodeGraphMessage::PointerDown {shift_click: true, control_click: false, alt_click: false, right_click: false}),
|
entry!(KeyDown(MouseLeft); modifiers=[Shift], action_dispatch=NodeGraphMessage::PointerDown { shift_click: true, control_click: false, alt_click: false, right_click: false }),
|
||||||
entry!(KeyDown(MouseLeft); modifiers=[Accel], action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: true, alt_click: false, right_click: false}),
|
entry!(KeyDown(MouseLeft); modifiers=[Accel], action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: true, alt_click: false, right_click: false }),
|
||||||
entry!(KeyDown(MouseLeft); modifiers=[Shift, Accel], action_dispatch=NodeGraphMessage::PointerDown {shift_click: true, control_click: true, alt_click: false, right_click: false}),
|
entry!(KeyDown(MouseLeft); modifiers=[Shift, Accel], action_dispatch=NodeGraphMessage::PointerDown { shift_click: true, control_click: true, alt_click: false, right_click: false }),
|
||||||
entry!(KeyDown(MouseLeft); modifiers=[Alt], action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: true, right_click: false}),
|
entry!(KeyDown(MouseLeft); modifiers=[Alt], action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: true, right_click: false }),
|
||||||
entry!(KeyDown(MouseRight); action_dispatch=NodeGraphMessage::PointerDown {shift_click: false, control_click: false, alt_click: false, right_click: true}),
|
entry!(KeyDown(MouseRight); action_dispatch=NodeGraphMessage::PointerDown { shift_click: false, control_click: false, alt_click: false, right_click: true }),
|
||||||
entry!(DoubleClick(MouseButton::Left); action_dispatch=NodeGraphMessage::EnterNestedNetwork),
|
entry!(DoubleClick(MouseButton::Left); action_dispatch=NodeGraphMessage::EnterNestedNetwork),
|
||||||
entry!(PointerMove; refresh_keys=[Shift], action_dispatch=NodeGraphMessage::PointerMove {shift: Shift}),
|
entry!(PointerMove; refresh_keys=[Shift], action_dispatch=NodeGraphMessage::PointerMove { shift: Shift }),
|
||||||
|
entry!(PointerShake; action_dispatch=NodeGraphMessage::ShakeNode),
|
||||||
entry!(KeyUp(MouseLeft); action_dispatch=NodeGraphMessage::PointerUp),
|
entry!(KeyUp(MouseLeft); action_dispatch=NodeGraphMessage::PointerUp),
|
||||||
entry!(KeyDown(Delete); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: false }),
|
entry!(KeyDown(Delete); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: false }),
|
||||||
entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: false }),
|
entry!(KeyDown(Backspace); modifiers=[Accel], action_dispatch=NodeGraphMessage::DeleteSelectedNodes { delete_children: false }),
|
||||||
@@ -177,7 +178,7 @@ pub fn input_mappings() -> Mapping {
|
|||||||
entry!(KeyDown(Escape); action_dispatch=ShapeToolMessage::Abort),
|
entry!(KeyDown(Escape); action_dispatch=ShapeToolMessage::Abort),
|
||||||
entry!(KeyDown(BracketLeft); action_dispatch=ShapeToolMessage::DecreaseSides),
|
entry!(KeyDown(BracketLeft); action_dispatch=ShapeToolMessage::DecreaseSides),
|
||||||
entry!(KeyDown(BracketRight); action_dispatch=ShapeToolMessage::IncreaseSides),
|
entry!(KeyDown(BracketRight); action_dispatch=ShapeToolMessage::IncreaseSides),
|
||||||
entry!(PointerMove; refresh_keys=[Alt, Shift, Control], action_dispatch=ShapeToolMessage::PointerMove([Alt, Shift, Control, Shift])),
|
entry!(PointerMove; refresh_keys=[Alt, Shift, Control], action_dispatch=ShapeToolMessage::PointerMove([Alt, Shift, Control])),
|
||||||
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
|
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
|
||||||
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
|
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowRight], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
|
||||||
entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
|
entry!(KeyDown(ArrowUp); modifiers=[Shift], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: 0., delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
|
||||||
@@ -211,13 +212,13 @@ pub fn input_mappings() -> Mapping {
|
|||||||
entry!(KeyDown(Delete); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
|
entry!(KeyDown(Delete); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
|
||||||
entry!(KeyDown(Backspace); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
|
entry!(KeyDown(Backspace); modifiers=[Shift], action_dispatch=PathToolMessage::BreakPath),
|
||||||
entry!(KeyDownNoRepeat(Tab); action_dispatch=PathToolMessage::SwapSelectedHandles),
|
entry!(KeyDownNoRepeat(Tab); action_dispatch=PathToolMessage::SwapSelectedHandles),
|
||||||
entry!(KeyDown(MouseLeft); action_dispatch=PathToolMessage::MouseDown { extend_selection: Shift, lasso_select: Control, handle_drag_from_anchor: Alt, drag_restore_handle: Control, molding_in_segment_edit: KeyA }),
|
entry!(KeyDown(MouseLeft); action_dispatch=PathToolMessage::MouseDown { extend_selection: Shift, lasso_select: Control, handle_drag_from_anchor: Alt, drag_restore_handle: Control, segment_editing_modifier: Control }),
|
||||||
entry!(KeyDown(MouseRight); action_dispatch=PathToolMessage::RightClick),
|
entry!(KeyDown(MouseRight); action_dispatch=PathToolMessage::RightClick),
|
||||||
entry!(KeyDown(Escape); action_dispatch=PathToolMessage::Escape),
|
entry!(KeyDown(Escape); action_dispatch=PathToolMessage::Escape),
|
||||||
entry!(KeyDown(KeyG); action_dispatch=PathToolMessage::GRS { key: KeyG }),
|
entry!(KeyDown(KeyG); action_dispatch=PathToolMessage::GRS { key: KeyG }),
|
||||||
entry!(KeyDown(KeyR); action_dispatch=PathToolMessage::GRS { key: KeyR }),
|
entry!(KeyDown(KeyR); action_dispatch=PathToolMessage::GRS { key: KeyR }),
|
||||||
entry!(KeyDown(KeyS); action_dispatch=PathToolMessage::GRS { key: KeyS }),
|
entry!(KeyDown(KeyS); action_dispatch=PathToolMessage::GRS { key: KeyS }),
|
||||||
entry!(PointerMove; refresh_keys=[KeyC, Space, Control, Shift, Alt], action_dispatch=PathToolMessage::PointerMove { toggle_colinear: KeyC, equidistant: Alt, move_anchor_with_handles: Space, snap_angle: Shift, lock_angle: Control, delete_segment: Alt, break_colinear_molding: Alt }),
|
entry!(PointerMove; refresh_keys=[KeyC, Space, Control, Shift, Alt], action_dispatch=PathToolMessage::PointerMove { toggle_colinear: KeyC, equidistant: Alt, move_anchor_with_handles: Space, snap_angle: Shift, lock_angle: Control, delete_segment: Alt, break_colinear_molding: Alt, segment_editing_modifier: Control }),
|
||||||
entry!(KeyDown(Delete); action_dispatch=PathToolMessage::Delete),
|
entry!(KeyDown(Delete); action_dispatch=PathToolMessage::Delete),
|
||||||
entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=PathToolMessage::SelectAllAnchors),
|
entry!(KeyDown(KeyA); modifiers=[Accel], action_dispatch=PathToolMessage::SelectAllAnchors),
|
||||||
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=PathToolMessage::DeselectAllPoints),
|
entry!(KeyDown(KeyA); modifiers=[Accel, Shift], canonical, action_dispatch=PathToolMessage::DeselectAllPoints),
|
||||||
@@ -317,6 +318,7 @@ pub fn input_mappings() -> Mapping {
|
|||||||
entry!(KeyDown(KeyX); modifiers=[Shift], action_dispatch=ToolMessage::SwapColors),
|
entry!(KeyDown(KeyX); modifiers=[Shift], action_dispatch=ToolMessage::SwapColors),
|
||||||
entry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=ToolMessage::SelectRandomWorkingColor { primary: true }),
|
entry!(KeyDown(KeyC); modifiers=[Alt], action_dispatch=ToolMessage::SelectRandomWorkingColor { primary: true }),
|
||||||
entry!(KeyDown(KeyC); modifiers=[Alt, Shift], action_dispatch=ToolMessage::SelectRandomWorkingColor { primary: false }),
|
entry!(KeyDown(KeyC); modifiers=[Alt, Shift], action_dispatch=ToolMessage::SelectRandomWorkingColor { primary: false }),
|
||||||
|
entry!(KeyDownNoRepeat(Tab); action_dispatch=ToolMessage::ToggleSelectVsPath),
|
||||||
//
|
//
|
||||||
// DocumentMessage
|
// DocumentMessage
|
||||||
entry!(KeyDown(Space); modifiers=[Control], action_dispatch=DocumentMessage::GraphViewOverlayToggle),
|
entry!(KeyDown(Space); modifiers=[Control], action_dispatch=DocumentMessage::GraphViewOverlayToggle),
|
||||||
@@ -416,7 +418,7 @@ pub fn input_mappings() -> Mapping {
|
|||||||
entry!(KeyDown(Tab); modifiers=[Control], action_dispatch=PortfolioMessage::NextDocument),
|
entry!(KeyDown(Tab); modifiers=[Control], action_dispatch=PortfolioMessage::NextDocument),
|
||||||
entry!(KeyDown(Tab); modifiers=[Control, Shift], action_dispatch=PortfolioMessage::PrevDocument),
|
entry!(KeyDown(Tab); modifiers=[Control, Shift], action_dispatch=PortfolioMessage::PrevDocument),
|
||||||
entry!(KeyDown(KeyW); modifiers=[Accel], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation),
|
entry!(KeyDown(KeyW); modifiers=[Accel], action_dispatch=PortfolioMessage::CloseActiveDocumentWithConfirmation),
|
||||||
entry!(KeyDown(KeyW); modifiers=[Accel,Alt], action_dispatch=PortfolioMessage::CloseAllDocumentsWithConfirmation),
|
entry!(KeyDown(KeyW); modifiers=[Accel, Alt], action_dispatch=PortfolioMessage::CloseAllDocumentsWithConfirmation),
|
||||||
entry!(KeyDown(KeyO); modifiers=[Accel], action_dispatch=PortfolioMessage::OpenDocument),
|
entry!(KeyDown(KeyO); modifiers=[Accel], action_dispatch=PortfolioMessage::OpenDocument),
|
||||||
entry!(KeyDown(KeyI); modifiers=[Accel], action_dispatch=PortfolioMessage::Import),
|
entry!(KeyDown(KeyI); modifiers=[Accel], action_dispatch=PortfolioMessage::Import),
|
||||||
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }),
|
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }),
|
||||||
@@ -439,7 +441,7 @@ pub fn input_mappings() -> Mapping {
|
|||||||
entry!(KeyDown(Space); modifiers=[Shift], action_dispatch=AnimationMessage::ToggleLivePreview),
|
entry!(KeyDown(Space); modifiers=[Shift], action_dispatch=AnimationMessage::ToggleLivePreview),
|
||||||
entry!(KeyDown(Home); modifiers=[Shift], action_dispatch=AnimationMessage::RestartAnimation),
|
entry!(KeyDown(Home); modifiers=[Shift], action_dispatch=AnimationMessage::RestartAnimation),
|
||||||
];
|
];
|
||||||
let (mut key_up, mut key_down, mut key_up_no_repeat, mut key_down_no_repeat, mut double_click, mut wheel_scroll, mut pointer_move) = mappings;
|
let (mut key_up, mut key_down, mut key_up_no_repeat, mut key_down_no_repeat, mut double_click, mut wheel_scroll, mut pointer_move, mut pointer_shake) = mappings;
|
||||||
|
|
||||||
let sort = |list: &mut KeyMappingEntries| list.0.sort_by(|a, b| b.modifiers.count_ones().cmp(&a.modifiers.count_ones()));
|
let sort = |list: &mut KeyMappingEntries| list.0.sort_by(|a, b| b.modifiers.count_ones().cmp(&a.modifiers.count_ones()));
|
||||||
// Sort the sublists of `key_up`, `key_down`, `key_up_no_repeat`, and `key_down_no_repeat`
|
// Sort the sublists of `key_up`, `key_down`, `key_up_no_repeat`, and `key_down_no_repeat`
|
||||||
@@ -456,6 +458,8 @@ pub fn input_mappings() -> Mapping {
|
|||||||
sort(&mut wheel_scroll);
|
sort(&mut wheel_scroll);
|
||||||
// Sort `pointer_move`
|
// Sort `pointer_move`
|
||||||
sort(&mut pointer_move);
|
sort(&mut pointer_move);
|
||||||
|
// Sort `pointer_shake`
|
||||||
|
sort(&mut pointer_shake);
|
||||||
|
|
||||||
Mapping {
|
Mapping {
|
||||||
key_up,
|
key_up,
|
||||||
@@ -465,6 +469,7 @@ pub fn input_mappings() -> Mapping {
|
|||||||
double_click,
|
double_click,
|
||||||
wheel_scroll,
|
wheel_scroll,
|
||||||
pointer_move,
|
pointer_move,
|
||||||
|
pointer_shake,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use crate::messages::input_mapper::input_mapper_message_handler::InputMapperMessageData;
|
use crate::messages::input_mapper::input_mapper_message_handler::InputMapperMessageContext;
|
||||||
use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
|
use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
#[derive(ExtractField)]
|
#[derive(ExtractField)]
|
||||||
pub struct KeyMappingMessageData<'a> {
|
pub struct KeyMappingMessageContext<'a> {
|
||||||
pub input: &'a InputPreprocessorMessageHandler,
|
pub input: &'a InputPreprocessorMessageHandler,
|
||||||
pub actions: ActionList,
|
pub actions: ActionList,
|
||||||
}
|
}
|
||||||
@@ -14,12 +14,12 @@ pub struct KeyMappingMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl MessageHandler<KeyMappingMessage, KeyMappingMessageData<'_>> for KeyMappingMessageHandler {
|
impl MessageHandler<KeyMappingMessage, KeyMappingMessageContext<'_>> for KeyMappingMessageHandler {
|
||||||
fn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque<Message>, data: KeyMappingMessageData) {
|
fn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque<Message>, context: KeyMappingMessageContext) {
|
||||||
let KeyMappingMessageData { input, actions } = data;
|
let KeyMappingMessageContext { input, actions } = context;
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
KeyMappingMessage::Lookup(input_message) => self.mapping_handler.process_message(input_message, responses, InputMapperMessageData { input, actions }),
|
KeyMappingMessage::Lookup(input_message) => self.mapping_handler.process_message(input_message, responses, InputMapperMessageContext { input, actions }),
|
||||||
KeyMappingMessage::ModifyMapping(new_layout) => self.mapping_handler.set_mapping(new_layout.into()),
|
KeyMappingMessage::ModifyMapping(new_layout) => self.mapping_handler.set_mapping(new_layout.into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ mod key_mapping_message_handler;
|
|||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use key_mapping_message::{KeyMappingMessage, KeyMappingMessageDiscriminant, MappingVariant, MappingVariantDiscriminant};
|
pub use key_mapping_message::{KeyMappingMessage, KeyMappingMessageDiscriminant, MappingVariant, MappingVariantDiscriminant};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use key_mapping_message_handler::{KeyMappingMessageData, KeyMappingMessageHandler};
|
pub use key_mapping_message_handler::{KeyMappingMessageContext, KeyMappingMessageHandler};
|
||||||
|
|||||||
@@ -8,4 +8,4 @@ pub mod utility_types;
|
|||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use input_mapper_message::{InputMapperMessage, InputMapperMessageDiscriminant};
|
pub use input_mapper_message::{InputMapperMessage, InputMapperMessageDiscriminant};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use input_mapper_message_handler::{InputMapperMessageData, InputMapperMessageHandler};
|
pub use input_mapper_message_handler::{InputMapperMessageContext, InputMapperMessageHandler};
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ macro_rules! mapping {
|
|||||||
let mut double_click = KeyMappingEntries::mouse_buttons_arrays();
|
let mut double_click = KeyMappingEntries::mouse_buttons_arrays();
|
||||||
let mut wheel_scroll = KeyMappingEntries::new();
|
let mut wheel_scroll = KeyMappingEntries::new();
|
||||||
let mut pointer_move = KeyMappingEntries::new();
|
let mut pointer_move = KeyMappingEntries::new();
|
||||||
|
let mut pointer_shake = KeyMappingEntries::new();
|
||||||
|
|
||||||
$(
|
$(
|
||||||
// Each of the many entry slices, one specified per action
|
// Each of the many entry slices, one specified per action
|
||||||
@@ -104,6 +105,7 @@ macro_rules! mapping {
|
|||||||
InputMapperMessage::DoubleClick(key) => &mut double_click[key as usize],
|
InputMapperMessage::DoubleClick(key) => &mut double_click[key as usize],
|
||||||
InputMapperMessage::WheelScroll => &mut wheel_scroll,
|
InputMapperMessage::WheelScroll => &mut wheel_scroll,
|
||||||
InputMapperMessage::PointerMove => &mut pointer_move,
|
InputMapperMessage::PointerMove => &mut pointer_move,
|
||||||
|
InputMapperMessage::PointerShake => &mut pointer_shake,
|
||||||
};
|
};
|
||||||
// Push each entry to the corresponding `KeyMappingEntries` list for its input type
|
// Push each entry to the corresponding `KeyMappingEntries` list for its input type
|
||||||
corresponding_list.push(entry.clone());
|
corresponding_list.push(entry.clone());
|
||||||
@@ -111,7 +113,7 @@ macro_rules! mapping {
|
|||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
|
|
||||||
(key_up, key_down, key_up_no_repeat, key_down_no_repeat, double_click, wheel_scroll, pointer_move)
|
(key_up, key_down, key_up_no_repeat, key_down_no_repeat, double_click, wheel_scroll, pointer_move, pointer_shake)
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ pub struct Mapping {
|
|||||||
pub double_click: [KeyMappingEntries; NUMBER_OF_MOUSE_BUTTONS],
|
pub double_click: [KeyMappingEntries; NUMBER_OF_MOUSE_BUTTONS],
|
||||||
pub wheel_scroll: KeyMappingEntries,
|
pub wheel_scroll: KeyMappingEntries,
|
||||||
pub pointer_move: KeyMappingEntries,
|
pub pointer_move: KeyMappingEntries,
|
||||||
|
pub pointer_shake: KeyMappingEntries,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Mapping {
|
impl Default for Mapping {
|
||||||
@@ -47,6 +48,7 @@ impl Mapping {
|
|||||||
InputMapperMessage::DoubleClick(key) => &self.double_click[*key as usize],
|
InputMapperMessage::DoubleClick(key) => &self.double_click[*key as usize],
|
||||||
InputMapperMessage::WheelScroll => &self.wheel_scroll,
|
InputMapperMessage::WheelScroll => &self.wheel_scroll,
|
||||||
InputMapperMessage::PointerMove => &self.pointer_move,
|
InputMapperMessage::PointerMove => &self.pointer_move,
|
||||||
|
InputMapperMessage::PointerShake => &self.pointer_shake,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +61,7 @@ impl Mapping {
|
|||||||
InputMapperMessage::DoubleClick(key) => &mut self.double_click[*key as usize],
|
InputMapperMessage::DoubleClick(key) => &mut self.double_click[*key as usize],
|
||||||
InputMapperMessage::WheelScroll => &mut self.wheel_scroll,
|
InputMapperMessage::WheelScroll => &mut self.wheel_scroll,
|
||||||
InputMapperMessage::PointerMove => &mut self.pointer_move,
|
InputMapperMessage::PointerMove => &mut self.pointer_move,
|
||||||
|
InputMapperMessage::PointerShake => &mut self.pointer_shake,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ pub enum InputPreprocessorMessage {
|
|||||||
PointerDown { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
PointerDown { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
||||||
PointerMove { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
PointerMove { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
||||||
PointerUp { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
PointerUp { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
||||||
|
PointerShake { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
||||||
CurrentTime { timestamp: u64 },
|
CurrentTime { timestamp: u64 },
|
||||||
WheelScroll { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
WheelScroll { editor_mouse_state: EditorMouseState, modifier_keys: ModifierKeys },
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use glam::DVec2;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(ExtractField)]
|
#[derive(ExtractField)]
|
||||||
pub struct InputPreprocessorMessageData {
|
pub struct InputPreprocessorMessageContext {
|
||||||
pub keyboard_platform: KeyboardPlatformLayout,
|
pub keyboard_platform: KeyboardPlatformLayout,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,9 +21,9 @@ pub struct InputPreprocessorMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageData> for InputPreprocessorMessageHandler {
|
impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageContext> for InputPreprocessorMessageHandler {
|
||||||
fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque<Message>, data: InputPreprocessorMessageData) {
|
fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque<Message>, context: InputPreprocessorMessageContext) {
|
||||||
let InputPreprocessorMessageData { keyboard_platform } = data;
|
let InputPreprocessorMessageContext { keyboard_platform } = context;
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
InputPreprocessorMessage::BoundsOfViewports { bounds_of_viewports } => {
|
InputPreprocessorMessage::BoundsOfViewports { bounds_of_viewports } => {
|
||||||
@@ -97,8 +97,16 @@ impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageData> for
|
|||||||
|
|
||||||
self.translate_mouse_event(mouse_state, false, responses);
|
self.translate_mouse_event(mouse_state, false, responses);
|
||||||
}
|
}
|
||||||
|
InputPreprocessorMessage::PointerShake { editor_mouse_state, modifier_keys } => {
|
||||||
|
self.update_states_of_modifier_keys(modifier_keys, keyboard_platform, responses);
|
||||||
|
|
||||||
|
let mouse_state = editor_mouse_state.to_mouse_state(&self.viewport_bounds);
|
||||||
|
self.mouse.position = mouse_state.position;
|
||||||
|
|
||||||
|
responses.add(InputMapperMessage::PointerShake);
|
||||||
|
}
|
||||||
InputPreprocessorMessage::CurrentTime { timestamp } => {
|
InputPreprocessorMessage::CurrentTime { timestamp } => {
|
||||||
responses.add(AnimationMessage::SetTime(timestamp as f64));
|
responses.add(AnimationMessage::SetTime { time: timestamp as f64 });
|
||||||
self.time = timestamp;
|
self.time = timestamp;
|
||||||
self.frame_time.advance_timestamp(Duration::from_millis(timestamp));
|
self.frame_time.advance_timestamp(Duration::from_millis(timestamp));
|
||||||
}
|
}
|
||||||
@@ -214,10 +222,10 @@ mod test {
|
|||||||
|
|
||||||
let mut responses = VecDeque::new();
|
let mut responses = VecDeque::new();
|
||||||
|
|
||||||
let data = InputPreprocessorMessageData {
|
let context = InputPreprocessorMessageContext {
|
||||||
keyboard_platform: KeyboardPlatformLayout::Standard,
|
keyboard_platform: KeyboardPlatformLayout::Standard,
|
||||||
};
|
};
|
||||||
input_preprocessor.process_message(message, &mut responses, data);
|
input_preprocessor.process_message(message, &mut responses, context);
|
||||||
|
|
||||||
assert!(input_preprocessor.keyboard.get(Key::Alt as usize));
|
assert!(input_preprocessor.keyboard.get(Key::Alt as usize));
|
||||||
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Alt).into()));
|
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Alt).into()));
|
||||||
@@ -233,10 +241,10 @@ mod test {
|
|||||||
|
|
||||||
let mut responses = VecDeque::new();
|
let mut responses = VecDeque::new();
|
||||||
|
|
||||||
let data = InputPreprocessorMessageData {
|
let context = InputPreprocessorMessageContext {
|
||||||
keyboard_platform: KeyboardPlatformLayout::Standard,
|
keyboard_platform: KeyboardPlatformLayout::Standard,
|
||||||
};
|
};
|
||||||
input_preprocessor.process_message(message, &mut responses, data);
|
input_preprocessor.process_message(message, &mut responses, context);
|
||||||
|
|
||||||
assert!(input_preprocessor.keyboard.get(Key::Control as usize));
|
assert!(input_preprocessor.keyboard.get(Key::Control as usize));
|
||||||
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Control).into()));
|
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Control).into()));
|
||||||
@@ -252,10 +260,10 @@ mod test {
|
|||||||
|
|
||||||
let mut responses = VecDeque::new();
|
let mut responses = VecDeque::new();
|
||||||
|
|
||||||
let data = InputPreprocessorMessageData {
|
let context = InputPreprocessorMessageContext {
|
||||||
keyboard_platform: KeyboardPlatformLayout::Standard,
|
keyboard_platform: KeyboardPlatformLayout::Standard,
|
||||||
};
|
};
|
||||||
input_preprocessor.process_message(message, &mut responses, data);
|
input_preprocessor.process_message(message, &mut responses, context);
|
||||||
|
|
||||||
assert!(input_preprocessor.keyboard.get(Key::Shift as usize));
|
assert!(input_preprocessor.keyboard.get(Key::Shift as usize));
|
||||||
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Shift).into()));
|
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyDown(Key::Shift).into()));
|
||||||
@@ -273,10 +281,10 @@ mod test {
|
|||||||
|
|
||||||
let mut responses = VecDeque::new();
|
let mut responses = VecDeque::new();
|
||||||
|
|
||||||
let data = InputPreprocessorMessageData {
|
let context = InputPreprocessorMessageContext {
|
||||||
keyboard_platform: KeyboardPlatformLayout::Standard,
|
keyboard_platform: KeyboardPlatformLayout::Standard,
|
||||||
};
|
};
|
||||||
input_preprocessor.process_message(message, &mut responses, data);
|
input_preprocessor.process_message(message, &mut responses, context);
|
||||||
|
|
||||||
assert!(!input_preprocessor.keyboard.get(Key::Control as usize));
|
assert!(!input_preprocessor.keyboard.get(Key::Control as usize));
|
||||||
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyUp(Key::Control).into()));
|
assert_eq!(responses.pop_front(), Some(InputMapperMessage::KeyUp(Key::Control).into()));
|
||||||
@@ -293,10 +301,10 @@ mod test {
|
|||||||
|
|
||||||
let mut responses = VecDeque::new();
|
let mut responses = VecDeque::new();
|
||||||
|
|
||||||
let data = InputPreprocessorMessageData {
|
let context = InputPreprocessorMessageContext {
|
||||||
keyboard_platform: KeyboardPlatformLayout::Standard,
|
keyboard_platform: KeyboardPlatformLayout::Standard,
|
||||||
};
|
};
|
||||||
input_preprocessor.process_message(message, &mut responses, data);
|
input_preprocessor.process_message(message, &mut responses, context);
|
||||||
|
|
||||||
assert!(input_preprocessor.keyboard.get(Key::Control as usize));
|
assert!(input_preprocessor.keyboard.get(Key::Control as usize));
|
||||||
assert!(input_preprocessor.keyboard.get(Key::Shift as usize));
|
assert!(input_preprocessor.keyboard.get(Key::Shift as usize));
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ mod input_preprocessor_message_handler;
|
|||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use input_preprocessor_message::{InputPreprocessorMessage, InputPreprocessorMessageDiscriminant};
|
pub use input_preprocessor_message::{InputPreprocessorMessage, InputPreprocessorMessageDiscriminant};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use input_preprocessor_message_handler::{InputPreprocessorMessageData, InputPreprocessorMessageHandler};
|
pub use input_preprocessor_message_handler::{InputPreprocessorMessageContext, InputPreprocessorMessageHandler};
|
||||||
|
|||||||
@@ -6,14 +6,53 @@ use graphene_std::text::Font;
|
|||||||
use graphene_std::vector::style::{FillChoice, GradientStops};
|
use graphene_std::vector::style::{FillChoice, GradientStops};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
|
#[derive(ExtractField)]
|
||||||
|
pub struct LayoutMessageContext<'a> {
|
||||||
|
pub action_input_mapping: &'a dyn Fn(&MessageDiscriminant) -> Option<KeysGroup>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, ExtractField)]
|
#[derive(Debug, Clone, Default, ExtractField)]
|
||||||
pub struct LayoutMessageHandler {
|
pub struct LayoutMessageHandler {
|
||||||
layouts: [Layout; LayoutTarget::LayoutTargetLength as usize],
|
layouts: [Layout; LayoutTarget::LayoutTargetLength as usize],
|
||||||
}
|
}
|
||||||
|
|
||||||
enum WidgetValueAction {
|
#[message_handler_data]
|
||||||
Commit,
|
impl MessageHandler<LayoutMessage, LayoutMessageContext<'_>> for LayoutMessageHandler {
|
||||||
Update,
|
fn process_message(&mut self, message: LayoutMessage, responses: &mut std::collections::VecDeque<Message>, context: LayoutMessageContext) {
|
||||||
|
let action_input_mapping = &context.action_input_mapping;
|
||||||
|
|
||||||
|
match message {
|
||||||
|
LayoutMessage::ResendActiveWidget { layout_target, widget_id } => {
|
||||||
|
// Find the updated diff based on the specified layout target
|
||||||
|
let Some(diff) = (match &self.layouts[layout_target as usize] {
|
||||||
|
Layout::MenuLayout(_) => return,
|
||||||
|
Layout::WidgetLayout(layout) => Self::get_widget_path(layout, widget_id).map(|(widget, widget_path)| {
|
||||||
|
// Create a widget update diff for the relevant id
|
||||||
|
let new_value = DiffUpdate::Widget(widget.clone());
|
||||||
|
WidgetDiff { widget_path, new_value }
|
||||||
|
}),
|
||||||
|
}) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// Resend that diff
|
||||||
|
self.send_diff(vec![diff], layout_target, responses, action_input_mapping);
|
||||||
|
}
|
||||||
|
LayoutMessage::SendLayout { layout, layout_target } => {
|
||||||
|
self.diff_and_send_layout_to_frontend(layout_target, layout, responses, action_input_mapping);
|
||||||
|
}
|
||||||
|
LayoutMessage::WidgetValueCommit { layout_target, widget_id, value } => {
|
||||||
|
self.handle_widget_callback(layout_target, widget_id, value, WidgetValueAction::Commit, responses);
|
||||||
|
}
|
||||||
|
LayoutMessage::WidgetValueUpdate { layout_target, widget_id, value } => {
|
||||||
|
self.handle_widget_callback(layout_target, widget_id, value, WidgetValueAction::Update, responses);
|
||||||
|
responses.add(LayoutMessage::ResendActiveWidget { layout_target, widget_id });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn actions(&self) -> ActionList {
|
||||||
|
actions!(LayoutMessageDiscriminant;)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutMessageHandler {
|
impl LayoutMessageHandler {
|
||||||
@@ -84,7 +123,10 @@ impl LayoutMessageHandler {
|
|||||||
let callback_message = match action {
|
let callback_message = match action {
|
||||||
WidgetValueAction::Commit => (breadcrumb_trail_buttons.on_commit.callback)(&()),
|
WidgetValueAction::Commit => (breadcrumb_trail_buttons.on_commit.callback)(&()),
|
||||||
WidgetValueAction::Update => {
|
WidgetValueAction::Update => {
|
||||||
let update_value = value.as_u64().expect("BreadcrumbTrailButtons update was not of type: u64");
|
let Some(update_value) = value.as_u64() else {
|
||||||
|
error!("BreadcrumbTrailButtons update was not of type: u64");
|
||||||
|
return;
|
||||||
|
};
|
||||||
(breadcrumb_trail_buttons.on_update.callback)(&update_value)
|
(breadcrumb_trail_buttons.on_update.callback)(&update_value)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -94,7 +136,10 @@ impl LayoutMessageHandler {
|
|||||||
let callback_message = match action {
|
let callback_message = match action {
|
||||||
WidgetValueAction::Commit => (checkbox_input.on_commit.callback)(&()),
|
WidgetValueAction::Commit => (checkbox_input.on_commit.callback)(&()),
|
||||||
WidgetValueAction::Update => {
|
WidgetValueAction::Update => {
|
||||||
let update_value = value.as_bool().expect("CheckboxInput update was not of type: bool");
|
let Some(update_value) = value.as_bool() else {
|
||||||
|
error!("CheckboxInput update was not of type: bool");
|
||||||
|
return;
|
||||||
|
};
|
||||||
checkbox_input.checked = update_value;
|
checkbox_input.checked = update_value;
|
||||||
(checkbox_input.on_update.callback)(checkbox_input)
|
(checkbox_input.on_update.callback)(checkbox_input)
|
||||||
}
|
}
|
||||||
@@ -169,7 +214,10 @@ impl LayoutMessageHandler {
|
|||||||
let callback_message = match action {
|
let callback_message = match action {
|
||||||
WidgetValueAction::Commit => (curve_input.on_commit.callback)(&()),
|
WidgetValueAction::Commit => (curve_input.on_commit.callback)(&()),
|
||||||
WidgetValueAction::Update => {
|
WidgetValueAction::Update => {
|
||||||
let curve = serde_json::from_value(value).expect("CurveInput event data could not be deserialized");
|
let Some(curve) = serde_json::from_value(value).ok() else {
|
||||||
|
error!("CurveInput event data could not be deserialized");
|
||||||
|
return;
|
||||||
|
};
|
||||||
curve_input.value = curve;
|
curve_input.value = curve;
|
||||||
(curve_input.on_update.callback)(curve_input)
|
(curve_input.on_update.callback)(curve_input)
|
||||||
}
|
}
|
||||||
@@ -180,13 +228,27 @@ impl LayoutMessageHandler {
|
|||||||
Widget::DropdownInput(dropdown_input) => {
|
Widget::DropdownInput(dropdown_input) => {
|
||||||
let callback_message = match action {
|
let callback_message = match action {
|
||||||
WidgetValueAction::Commit => {
|
WidgetValueAction::Commit => {
|
||||||
let update_value = value.as_u64().unwrap_or_else(|| panic!("DropdownInput commit was not of type `u64`, found {value:?}"));
|
let Some(update_value) = value.as_u64() else {
|
||||||
(dropdown_input.entries.iter().flatten().nth(update_value as usize).unwrap().on_commit.callback)(&())
|
error!("DropdownInput commit was not of type `u64`, found {value:?}");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(entry) = dropdown_input.entries.iter().flatten().nth(update_value as usize) else {
|
||||||
|
error!("DropdownInput commit was not able to find entry for index {update_value}");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
(entry.on_commit.callback)(&())
|
||||||
}
|
}
|
||||||
WidgetValueAction::Update => {
|
WidgetValueAction::Update => {
|
||||||
let update_value = value.as_u64().unwrap_or_else(|| panic!("DropdownInput update was not of type `u64`, found {value:?}"));
|
let Some(update_value) = value.as_u64() else {
|
||||||
|
error!("DropdownInput update was not of type `u64`, found {value:?}");
|
||||||
|
return;
|
||||||
|
};
|
||||||
dropdown_input.selected_index = Some(update_value as u32);
|
dropdown_input.selected_index = Some(update_value as u32);
|
||||||
(dropdown_input.entries.iter().flatten().nth(update_value as usize).unwrap().on_update.callback)(&())
|
let Some(entry) = dropdown_input.entries.iter().flatten().nth(update_value as usize) else {
|
||||||
|
error!("DropdownInput update was not able to find entry for index {update_value}");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
(entry.on_update.callback)(&())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -196,12 +258,27 @@ impl LayoutMessageHandler {
|
|||||||
let callback_message = match action {
|
let callback_message = match action {
|
||||||
WidgetValueAction::Commit => (font_input.on_commit.callback)(&()),
|
WidgetValueAction::Commit => (font_input.on_commit.callback)(&()),
|
||||||
WidgetValueAction::Update => {
|
WidgetValueAction::Update => {
|
||||||
let update_value = value.as_object().expect("FontInput update was not of type: object");
|
let Some(update_value) = value.as_object() else {
|
||||||
let font_family_value = update_value.get("fontFamily").expect("FontInput update does not have a fontFamily");
|
error!("FontInput update was not of type: object");
|
||||||
let font_style_value = update_value.get("fontStyle").expect("FontInput update does not have a fontStyle");
|
return;
|
||||||
|
};
|
||||||
|
let Some(font_family_value) = update_value.get("fontFamily") else {
|
||||||
|
error!("FontInput update does not have a fontFamily");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(font_style_value) = update_value.get("fontStyle") else {
|
||||||
|
error!("FontInput update does not have a fontStyle");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let font_family = font_family_value.as_str().expect("FontInput update fontFamily was not of type: string");
|
let Some(font_family) = font_family_value.as_str() else {
|
||||||
let font_style = font_style_value.as_str().expect("FontInput update fontStyle was not of type: string");
|
error!("FontInput update fontFamily was not of type: string");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(font_style) = font_style_value.as_str() else {
|
||||||
|
error!("FontInput update fontStyle was not of type: string");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
font_input.font_family = font_family.into();
|
font_input.font_family = font_family.into();
|
||||||
font_input.font_style = font_style.into();
|
font_input.font_style = font_style.into();
|
||||||
@@ -246,7 +323,10 @@ impl LayoutMessageHandler {
|
|||||||
responses.add(callback_message);
|
responses.add(callback_message);
|
||||||
}
|
}
|
||||||
WidgetValueAction::Update => {
|
WidgetValueAction::Update => {
|
||||||
let value = value.as_str().expect("NodeCatalog update was not of type String").to_string();
|
let Some(value) = value.as_str().map(|s| s.to_string()) else {
|
||||||
|
error!("NodeCatalog update was not of type String");
|
||||||
|
return;
|
||||||
|
};
|
||||||
let callback_message = (node_type_input.on_update.callback)(&value);
|
let callback_message = (node_type_input.on_update.callback)(&value);
|
||||||
responses.add(callback_message);
|
responses.add(callback_message);
|
||||||
}
|
}
|
||||||
@@ -257,8 +337,11 @@ impl LayoutMessageHandler {
|
|||||||
responses.add(callback_message);
|
responses.add(callback_message);
|
||||||
}
|
}
|
||||||
WidgetValueAction::Update => match value {
|
WidgetValueAction::Update => match value {
|
||||||
Value::Number(num) => {
|
Value::Number(ref num) => {
|
||||||
let update_value = num.as_f64().unwrap();
|
let Some(update_value) = num.as_f64() else {
|
||||||
|
error!("NumberInput update was not of type: f64, found {value:?}");
|
||||||
|
return;
|
||||||
|
};
|
||||||
number_input.value = Some(update_value);
|
number_input.value = Some(update_value);
|
||||||
let callback_message = (number_input.on_update.callback)(number_input);
|
let callback_message = (number_input.on_update.callback)(number_input);
|
||||||
responses.add(callback_message);
|
responses.add(callback_message);
|
||||||
@@ -284,7 +367,10 @@ impl LayoutMessageHandler {
|
|||||||
let callback_message = match action {
|
let callback_message = match action {
|
||||||
WidgetValueAction::Commit => (reference_point_input.on_commit.callback)(&()),
|
WidgetValueAction::Commit => (reference_point_input.on_commit.callback)(&()),
|
||||||
WidgetValueAction::Update => {
|
WidgetValueAction::Update => {
|
||||||
let update_value = value.as_str().expect("ReferencePointInput update was not of type: u64");
|
let Some(update_value) = value.as_str() else {
|
||||||
|
error!("ReferencePointInput update was not of type: u64");
|
||||||
|
return;
|
||||||
|
};
|
||||||
reference_point_input.value = update_value.into();
|
reference_point_input.value = update_value.into();
|
||||||
(reference_point_input.on_update.callback)(reference_point_input)
|
(reference_point_input.on_update.callback)(reference_point_input)
|
||||||
}
|
}
|
||||||
@@ -294,7 +380,10 @@ impl LayoutMessageHandler {
|
|||||||
}
|
}
|
||||||
Widget::PopoverButton(_) => {}
|
Widget::PopoverButton(_) => {}
|
||||||
Widget::RadioInput(radio_input) => {
|
Widget::RadioInput(radio_input) => {
|
||||||
let update_value = value.as_u64().expect("RadioInput update was not of type: u64");
|
let Some(update_value) = value.as_u64() else {
|
||||||
|
error!("RadioInput update was not of type: u64");
|
||||||
|
return;
|
||||||
|
};
|
||||||
radio_input.selected_index = Some(update_value as u32);
|
radio_input.selected_index = Some(update_value as u32);
|
||||||
let callback_message = match action {
|
let callback_message = match action {
|
||||||
WidgetValueAction::Commit => (radio_input.entries[update_value as usize].on_commit.callback)(&()),
|
WidgetValueAction::Commit => (radio_input.entries[update_value as usize].on_commit.callback)(&()),
|
||||||
@@ -308,7 +397,10 @@ impl LayoutMessageHandler {
|
|||||||
let callback_message = match action {
|
let callback_message = match action {
|
||||||
WidgetValueAction::Commit => (text_area_input.on_commit.callback)(&()),
|
WidgetValueAction::Commit => (text_area_input.on_commit.callback)(&()),
|
||||||
WidgetValueAction::Update => {
|
WidgetValueAction::Update => {
|
||||||
let update_value = value.as_str().expect("TextAreaInput update was not of type: string");
|
let Some(update_value) = value.as_str() else {
|
||||||
|
error!("TextAreaInput update was not of type: string");
|
||||||
|
return;
|
||||||
|
};
|
||||||
text_area_input.value = update_value.into();
|
text_area_input.value = update_value.into();
|
||||||
(text_area_input.on_update.callback)(text_area_input)
|
(text_area_input.on_update.callback)(text_area_input)
|
||||||
}
|
}
|
||||||
@@ -328,7 +420,10 @@ impl LayoutMessageHandler {
|
|||||||
let callback_message = match action {
|
let callback_message = match action {
|
||||||
WidgetValueAction::Commit => (text_input.on_commit.callback)(&()),
|
WidgetValueAction::Commit => (text_input.on_commit.callback)(&()),
|
||||||
WidgetValueAction::Update => {
|
WidgetValueAction::Update => {
|
||||||
let update_value = value.as_str().expect("TextInput update was not of type: string");
|
let Some(update_value) = value.as_str() else {
|
||||||
|
error!("TextInput update was not of type: string");
|
||||||
|
return;
|
||||||
|
};
|
||||||
text_input.value = update_value.into();
|
text_input.value = update_value.into();
|
||||||
(text_input.on_update.callback)(text_input)
|
(text_input.on_update.callback)(text_input)
|
||||||
}
|
}
|
||||||
@@ -340,54 +435,7 @@ impl LayoutMessageHandler {
|
|||||||
Widget::WorkingColorsInput(_) => {}
|
Widget::WorkingColorsInput(_) => {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn custom_data() -> MessageData {
|
|
||||||
// TODO: When <https://github.com/dtolnay/proc-macro2/issues/503> is resolved and released,
|
|
||||||
// TODO: use <https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.line> to get
|
|
||||||
// TODO: the line number instead of hardcoding it to the magic number on the following line.
|
|
||||||
// TODO: Also, utilize the line number in the actual output, since it is currently unused.
|
|
||||||
MessageData::new(String::from("Function"), vec![(String::from("Fn(&MessageDiscriminant) -> Option<KeysGroup>"), 350)], file!())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[message_handler_data(CustomData)]
|
|
||||||
impl<F: Fn(&MessageDiscriminant) -> Option<KeysGroup>> MessageHandler<LayoutMessage, F> for LayoutMessageHandler {
|
|
||||||
fn process_message(&mut self, message: LayoutMessage, responses: &mut std::collections::VecDeque<Message>, action_input_mapping: F) {
|
|
||||||
match message {
|
|
||||||
LayoutMessage::ResendActiveWidget { layout_target, widget_id } => {
|
|
||||||
// Find the updated diff based on the specified layout target
|
|
||||||
let Some(diff) = (match &self.layouts[layout_target as usize] {
|
|
||||||
Layout::MenuLayout(_) => return,
|
|
||||||
Layout::WidgetLayout(layout) => Self::get_widget_path(layout, widget_id).map(|(widget, widget_path)| {
|
|
||||||
// Create a widget update diff for the relevant id
|
|
||||||
let new_value = DiffUpdate::Widget(widget.clone());
|
|
||||||
WidgetDiff { widget_path, new_value }
|
|
||||||
}),
|
|
||||||
}) else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
// Resend that diff
|
|
||||||
self.send_diff(vec![diff], layout_target, responses, &action_input_mapping);
|
|
||||||
}
|
|
||||||
LayoutMessage::SendLayout { layout, layout_target } => {
|
|
||||||
self.diff_and_send_layout_to_frontend(layout_target, layout, responses, &action_input_mapping);
|
|
||||||
}
|
|
||||||
LayoutMessage::WidgetValueCommit { layout_target, widget_id, value } => {
|
|
||||||
self.handle_widget_callback(layout_target, widget_id, value, WidgetValueAction::Commit, responses);
|
|
||||||
}
|
|
||||||
LayoutMessage::WidgetValueUpdate { layout_target, widget_id, value } => {
|
|
||||||
self.handle_widget_callback(layout_target, widget_id, value, WidgetValueAction::Update, responses);
|
|
||||||
responses.add(LayoutMessage::ResendActiveWidget { layout_target, widget_id });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn actions(&self) -> ActionList {
|
|
||||||
actions!(LayoutMessageDiscriminant;)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LayoutMessageHandler {
|
|
||||||
/// Diff the update and send to the frontend where necessary
|
/// Diff the update and send to the frontend where necessary
|
||||||
fn diff_and_send_layout_to_frontend(
|
fn diff_and_send_layout_to_frontend(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -419,10 +467,11 @@ impl LayoutMessageHandler {
|
|||||||
self.layouts[layout_target as usize] = new_layout;
|
self.layouts[layout_target as usize] = new_layout;
|
||||||
|
|
||||||
// Update the UI
|
// Update the UI
|
||||||
responses.add(FrontendMessage::UpdateMenuBarLayout {
|
let Some(layout) = self.layouts[layout_target as usize].clone().as_menu_layout(action_input_mapping).map(|x| x.layout) else {
|
||||||
layout_target,
|
error!("Called unwrap_menu_layout on a widget layout");
|
||||||
layout: self.layouts[layout_target as usize].clone().unwrap_menu_layout(action_input_mapping).layout,
|
return;
|
||||||
});
|
};
|
||||||
|
responses.add(FrontendMessage::UpdateMenuBarLayout { layout_target, layout });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -453,3 +502,8 @@ impl LayoutMessageHandler {
|
|||||||
responses.add(message);
|
responses.add(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum WidgetValueAction {
|
||||||
|
Commit,
|
||||||
|
Update,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
mod layout_message;
|
mod layout_message;
|
||||||
mod layout_message_handler;
|
pub mod layout_message_handler;
|
||||||
|
|
||||||
pub mod utility_types;
|
pub mod utility_types;
|
||||||
|
|
||||||
|
|||||||
@@ -109,14 +109,14 @@ pub enum Layout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Layout {
|
impl Layout {
|
||||||
pub fn unwrap_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) -> MenuLayout {
|
pub fn as_menu_layout(self, action_input_mapping: &impl Fn(&MessageDiscriminant) -> Option<KeysGroup>) -> Option<MenuLayout> {
|
||||||
if let Self::MenuLayout(mut menu) = self {
|
if let Self::MenuLayout(mut menu) = self {
|
||||||
menu.layout
|
menu.layout
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.for_each(|menu_column| menu_column.children.fill_in_shortcut_actions_with_keys(action_input_mapping));
|
.for_each(|menu_column| menu_column.children.fill_in_shortcut_actions_with_keys(action_input_mapping));
|
||||||
menu
|
Some(menu)
|
||||||
} else {
|
} else {
|
||||||
panic!("Called unwrap_menu_layout on a widget layout");
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,9 @@ impl<'a> serde::Deserialize<'a> for CheckboxId {
|
|||||||
where
|
where
|
||||||
D: serde::Deserializer<'a>,
|
D: serde::Deserializer<'a>,
|
||||||
{
|
{
|
||||||
let id = u64::deserialize(deserializer)?;
|
let optional_id: Option<u64> = Option::deserialize(deserializer)?;
|
||||||
|
// TODO: This is potentially weird because after deserialization the two labels will be decoupled if the value not existent
|
||||||
|
let id = optional_id.unwrap_or(0);
|
||||||
let checkbox_id = CheckboxId(OnceCell::new().into());
|
let checkbox_id = CheckboxId(OnceCell::new().into());
|
||||||
checkbox_id.0.set(id).map_err(serde::de::Error::custom)?;
|
checkbox_id.0.set(id).map_err(serde::de::Error::custom)?;
|
||||||
Ok(checkbox_id)
|
Ok(checkbox_id)
|
||||||
@@ -349,6 +351,9 @@ pub enum NumberInputMode {
|
|||||||
pub struct NodeCatalog {
|
pub struct NodeCatalog {
|
||||||
pub disabled: bool,
|
pub disabled: bool,
|
||||||
|
|
||||||
|
#[serde(rename = "initialSearchTerm")]
|
||||||
|
pub intial_search: String,
|
||||||
|
|
||||||
// Callbacks
|
// Callbacks
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||||
|
|||||||
@@ -4,19 +4,18 @@ use graphite_proc_macros::*;
|
|||||||
#[impl_message]
|
#[impl_message]
|
||||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum Message {
|
pub enum Message {
|
||||||
NoOp,
|
// Sub-messages
|
||||||
Init,
|
|
||||||
Batched(Box<[Message]>),
|
|
||||||
StartBuffer,
|
|
||||||
EndBuffer(graphene_std::renderer::RenderMetadata),
|
|
||||||
|
|
||||||
#[child]
|
#[child]
|
||||||
Animation(AnimationMessage),
|
Animation(AnimationMessage),
|
||||||
#[child]
|
#[child]
|
||||||
|
AppWindow(AppWindowMessage),
|
||||||
|
#[child]
|
||||||
Broadcast(BroadcastMessage),
|
Broadcast(BroadcastMessage),
|
||||||
#[child]
|
#[child]
|
||||||
Debug(DebugMessage),
|
Debug(DebugMessage),
|
||||||
#[child]
|
#[child]
|
||||||
|
Defer(DeferMessage),
|
||||||
|
#[child]
|
||||||
Dialog(DialogMessage),
|
Dialog(DialogMessage),
|
||||||
#[child]
|
#[child]
|
||||||
Frontend(FrontendMessage),
|
Frontend(FrontendMessage),
|
||||||
@@ -36,6 +35,12 @@ pub enum Message {
|
|||||||
Tool(ToolMessage),
|
Tool(ToolMessage),
|
||||||
#[child]
|
#[child]
|
||||||
Workspace(WorkspaceMessage),
|
Workspace(WorkspaceMessage),
|
||||||
|
|
||||||
|
// Messages
|
||||||
|
NoOp,
|
||||||
|
Batched {
|
||||||
|
messages: Box<[Message]>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides an impl of `specta::Type` for `MessageDiscriminant`, the struct created by `impl_message`.
|
/// Provides an impl of `specta::Type` for `MessageDiscriminant`, the struct created by `impl_message`.
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
//! The root-level messages forming the first layer of the message system architecture.
|
//! The root-level messages forming the first layer of the message system architecture.
|
||||||
|
|
||||||
pub mod animation;
|
pub mod animation;
|
||||||
|
pub mod app_window;
|
||||||
pub mod broadcast;
|
pub mod broadcast;
|
||||||
pub mod debug;
|
pub mod debug;
|
||||||
|
pub mod defer;
|
||||||
pub mod dialog;
|
pub mod dialog;
|
||||||
pub mod frontend;
|
pub mod frontend;
|
||||||
pub mod globals;
|
pub mod globals;
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ use crate::consts::{ASYMPTOTIC_EFFECT, COLOR_OVERLAY_GRAY, DEFAULT_DOCUMENT_NAME
|
|||||||
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||||
use crate::messages::portfolio::document::node_graph::NodeGraphHandlerData;
|
use crate::messages::portfolio::document::node_graph::NodeGraphMessageContext;
|
||||||
use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options};
|
use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options};
|
||||||
use crate::messages::portfolio::document::overlays::utility_types::{OverlaysType, OverlaysVisibilitySettings};
|
use crate::messages::portfolio::document::overlays::utility_types::{OverlaysType, OverlaysVisibilitySettings};
|
||||||
use crate::messages::portfolio::document::properties_panel::utility_types::PropertiesPanelMessageHandlerData;
|
use crate::messages::portfolio::document::properties_panel::properties_panel_message_handler::PropertiesPanelMessageContext;
|
||||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ};
|
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ};
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate};
|
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate};
|
||||||
@@ -39,7 +39,7 @@ use graphene_std::vector::style::ViewMode;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
#[derive(ExtractField)]
|
#[derive(ExtractField)]
|
||||||
pub struct DocumentMessageData<'a> {
|
pub struct DocumentMessageContext<'a> {
|
||||||
pub document_id: DocumentId,
|
pub document_id: DocumentId,
|
||||||
pub ipp: &'a InputPreprocessorMessageHandler,
|
pub ipp: &'a InputPreprocessorMessageHandler,
|
||||||
pub persistent_data: &'a PersistentData,
|
pub persistent_data: &'a PersistentData,
|
||||||
@@ -170,9 +170,9 @@ impl Default for DocumentMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessageHandler {
|
impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMessageHandler {
|
||||||
fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque<Message>, data: DocumentMessageData) {
|
fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque<Message>, context: DocumentMessageContext) {
|
||||||
let DocumentMessageData {
|
let DocumentMessageContext {
|
||||||
document_id,
|
document_id,
|
||||||
ipp,
|
ipp,
|
||||||
persistent_data,
|
persistent_data,
|
||||||
@@ -180,28 +180,21 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
current_tool,
|
current_tool,
|
||||||
preferences,
|
preferences,
|
||||||
device_pixel_ratio,
|
device_pixel_ratio,
|
||||||
} = data;
|
} = context;
|
||||||
|
|
||||||
let selected_nodes_bounding_box_viewport = self.network_interface.selected_nodes_bounding_box_viewport(&self.breadcrumb_network_path);
|
|
||||||
let selected_visible_layers_bounding_box_viewport = self.selected_visible_layers_bounding_box_viewport();
|
|
||||||
match message {
|
match message {
|
||||||
// Sub-messages
|
// Sub-messages
|
||||||
DocumentMessage::Navigation(message) => {
|
DocumentMessage::Navigation(message) => {
|
||||||
let data = NavigationMessageData {
|
let context = NavigationMessageContext {
|
||||||
network_interface: &mut self.network_interface,
|
network_interface: &mut self.network_interface,
|
||||||
breadcrumb_network_path: &self.breadcrumb_network_path,
|
breadcrumb_network_path: &self.breadcrumb_network_path,
|
||||||
ipp,
|
ipp,
|
||||||
selection_bounds: if self.graph_view_overlay_open {
|
|
||||||
selected_nodes_bounding_box_viewport
|
|
||||||
} else {
|
|
||||||
selected_visible_layers_bounding_box_viewport
|
|
||||||
},
|
|
||||||
document_ptz: &mut self.document_ptz,
|
document_ptz: &mut self.document_ptz,
|
||||||
graph_view_overlay_open: self.graph_view_overlay_open,
|
graph_view_overlay_open: self.graph_view_overlay_open,
|
||||||
preferences,
|
preferences,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.navigation_handler.process_message(message, responses, data);
|
self.navigation_handler.process_message(message, responses, context);
|
||||||
}
|
}
|
||||||
DocumentMessage::Overlays(message) => {
|
DocumentMessage::Overlays(message) => {
|
||||||
let visibility_settings = self.overlays_visibility_settings;
|
let visibility_settings = self.overlays_visibility_settings;
|
||||||
@@ -210,7 +203,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
self.overlays_message_handler.process_message(
|
self.overlays_message_handler.process_message(
|
||||||
message,
|
message,
|
||||||
responses,
|
responses,
|
||||||
OverlaysMessageData {
|
OverlaysMessageContext {
|
||||||
visibility_settings,
|
visibility_settings,
|
||||||
ipp,
|
ipp,
|
||||||
device_pixel_ratio,
|
device_pixel_ratio,
|
||||||
@@ -218,20 +211,20 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
DocumentMessage::PropertiesPanel(message) => {
|
DocumentMessage::PropertiesPanel(message) => {
|
||||||
let properties_panel_message_handler_data = PropertiesPanelMessageHandlerData {
|
let context = PropertiesPanelMessageContext {
|
||||||
network_interface: &mut self.network_interface,
|
network_interface: &mut self.network_interface,
|
||||||
selection_network_path: &self.selection_network_path,
|
selection_network_path: &self.selection_network_path,
|
||||||
document_name: self.name.as_str(),
|
document_name: self.name.as_str(),
|
||||||
executor,
|
executor,
|
||||||
|
persistent_data,
|
||||||
};
|
};
|
||||||
self.properties_panel_message_handler
|
self.properties_panel_message_handler.process_message(message, responses, context);
|
||||||
.process_message(message, responses, (persistent_data, properties_panel_message_handler_data));
|
|
||||||
}
|
}
|
||||||
DocumentMessage::NodeGraph(message) => {
|
DocumentMessage::NodeGraph(message) => {
|
||||||
self.node_graph_handler.process_message(
|
self.node_graph_handler.process_message(
|
||||||
message,
|
message,
|
||||||
responses,
|
responses,
|
||||||
NodeGraphHandlerData {
|
NodeGraphMessageContext {
|
||||||
network_interface: &mut self.network_interface,
|
network_interface: &mut self.network_interface,
|
||||||
selection_network_path: &self.selection_network_path,
|
selection_network_path: &self.selection_network_path,
|
||||||
breadcrumb_network_path: &self.breadcrumb_network_path,
|
breadcrumb_network_path: &self.breadcrumb_network_path,
|
||||||
@@ -246,20 +239,20 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
DocumentMessage::GraphOperation(message) => {
|
DocumentMessage::GraphOperation(message) => {
|
||||||
let data = GraphOperationMessageData {
|
let context = GraphOperationMessageContext {
|
||||||
network_interface: &mut self.network_interface,
|
network_interface: &mut self.network_interface,
|
||||||
collapsed: &mut self.collapsed,
|
collapsed: &mut self.collapsed,
|
||||||
node_graph: &mut self.node_graph_handler,
|
node_graph: &mut self.node_graph_handler,
|
||||||
};
|
};
|
||||||
let mut graph_operation_message_handler = GraphOperationMessageHandler {};
|
let mut graph_operation_message_handler = GraphOperationMessageHandler {};
|
||||||
graph_operation_message_handler.process_message(message, responses, data);
|
graph_operation_message_handler.process_message(message, responses, context);
|
||||||
}
|
}
|
||||||
DocumentMessage::AlignSelectedLayers { axis, aggregate } => {
|
DocumentMessage::AlignSelectedLayers { axis, aggregate } => {
|
||||||
let axis = match axis {
|
let axis = match axis {
|
||||||
AlignAxis::X => DVec2::X,
|
AlignAxis::X => DVec2::X,
|
||||||
AlignAxis::Y => DVec2::Y,
|
AlignAxis::Y => DVec2::Y,
|
||||||
};
|
};
|
||||||
let Some(combined_box) = self.selected_visible_layers_bounding_box_viewport() else {
|
let Some(combined_box) = self.network_interface.selected_layers_artwork_bounding_box_viewport() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -486,7 +479,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
FlipAxis::X => DVec2::new(-1., 1.),
|
FlipAxis::X => DVec2::new(-1., 1.),
|
||||||
FlipAxis::Y => DVec2::new(1., -1.),
|
FlipAxis::Y => DVec2::new(1., -1.),
|
||||||
};
|
};
|
||||||
if let Some([min, max]) = self.selected_visible_and_unlock_layers_bounding_box_viewport() {
|
if let Some([min, max]) = self.network_interface.selected_unlocked_layers_bounding_box_viewport() {
|
||||||
let center = (max + min) / 2.;
|
let center = (max + min) / 2.;
|
||||||
let bbox_trans = DAffine2::from_translation(-center);
|
let bbox_trans = DAffine2::from_translation(-center);
|
||||||
let mut added_transaction = false;
|
let mut added_transaction = false;
|
||||||
@@ -506,7 +499,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
}
|
}
|
||||||
DocumentMessage::RotateSelectedLayers { degrees } => {
|
DocumentMessage::RotateSelectedLayers { degrees } => {
|
||||||
// Get the bounding box of selected layers in viewport space
|
// Get the bounding box of selected layers in viewport space
|
||||||
if let Some([min, max]) = self.selected_visible_and_unlock_layers_bounding_box_viewport() {
|
if let Some([min, max]) = self.network_interface.selected_unlocked_layers_bounding_box_viewport() {
|
||||||
// Calculate the center of the bounding box to use as rotation pivot
|
// Calculate the center of the bounding box to use as rotation pivot
|
||||||
let center = (max + min) / 2.;
|
let center = (max + min) / 2.;
|
||||||
// Transform that moves pivot point to origin
|
// Transform that moves pivot point to origin
|
||||||
@@ -1063,13 +1056,13 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
self.selected_layers_reorder(relative_index_offset, responses);
|
self.selected_layers_reorder(relative_index_offset, responses);
|
||||||
}
|
}
|
||||||
DocumentMessage::ClipLayer { id } => {
|
DocumentMessage::ClipLayer { id } => {
|
||||||
let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]);
|
let layer = LayerNodeIdentifier::new(id, &self.network_interface);
|
||||||
|
|
||||||
responses.add(DocumentMessage::AddTransaction);
|
responses.add(DocumentMessage::AddTransaction);
|
||||||
responses.add(GraphOperationMessage::ClipModeToggle { layer });
|
responses.add(GraphOperationMessage::ClipModeToggle { layer });
|
||||||
}
|
}
|
||||||
DocumentMessage::SelectLayer { id, ctrl, shift } => {
|
DocumentMessage::SelectLayer { id, ctrl, shift } => {
|
||||||
let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]);
|
let layer = LayerNodeIdentifier::new(id, &self.network_interface);
|
||||||
|
|
||||||
let mut nodes = vec![];
|
let mut nodes = vec![];
|
||||||
|
|
||||||
@@ -1266,7 +1259,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
responses.add(OverlaysMessage::Draw);
|
responses.add(OverlaysMessage::Draw);
|
||||||
}
|
}
|
||||||
DocumentMessage::ToggleLayerExpansion { id, recursive } => {
|
DocumentMessage::ToggleLayerExpansion { id, recursive } => {
|
||||||
let layer = LayerNodeIdentifier::new(id, &self.network_interface, &[]);
|
let layer = LayerNodeIdentifier::new(id, &self.network_interface);
|
||||||
let metadata = self.metadata();
|
let metadata = self.metadata();
|
||||||
|
|
||||||
let is_collapsed = self.collapsed.0.contains(&layer);
|
let is_collapsed = self.collapsed.0.contains(&layer);
|
||||||
@@ -1323,7 +1316,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
self.network_interface.document_network().nodes.contains_key(node_id))
|
self.network_interface.document_network().nodes.contains_key(node_id))
|
||||||
.filter_map(|(node_id, click_targets)| {
|
.filter_map(|(node_id, click_targets)| {
|
||||||
self.network_interface.is_layer(&node_id, &[]).then(|| {
|
self.network_interface.is_layer(&node_id, &[]).then(|| {
|
||||||
let layer = LayerNodeIdentifier::new(node_id, &self.network_interface, &[]);
|
let layer = LayerNodeIdentifier::new(node_id, &self.network_interface);
|
||||||
(layer, click_targets)
|
(layer, click_targets)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -1442,6 +1435,20 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
// Some parts of the editior (e.g. navigation messages) depend on these bounds to be present
|
||||||
|
let bounds = if self.graph_view_overlay_open {
|
||||||
|
self.network_interface.all_nodes_bounding_box(&self.breadcrumb_network_path).cloned()
|
||||||
|
} else {
|
||||||
|
self.network_interface.document_bounds_document_space(true)
|
||||||
|
};
|
||||||
|
if bounds.is_some() {
|
||||||
|
responses.add(DeferMessage::TriggerNavigationReady);
|
||||||
|
} else {
|
||||||
|
// If we don't have bounds yet, we need wait until the node graph has run once more
|
||||||
|
responses.add(DeferMessage::AfterGraphRun {
|
||||||
|
messages: vec![DocumentMessage::PTZUpdate.into()],
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DocumentMessage::SelectionStepBack => {
|
DocumentMessage::SelectionStepBack => {
|
||||||
self.network_interface.selection_step_back(&self.selection_network_path);
|
self.network_interface.selection_step_back(&self.selection_network_path);
|
||||||
@@ -1683,6 +1690,11 @@ impl DocumentMessageHandler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn click_list_no_parents<'a>(&'a self, ipp: &InputPreprocessorMessageHandler) -> impl Iterator<Item = LayerNodeIdentifier> + use<'a> {
|
||||||
|
self.click_xray(ipp)
|
||||||
|
.filter(move |&layer| !self.network_interface.is_artboard(&layer.to_node(), &[]) && !layer.has_children(self.network_interface.document_metadata()))
|
||||||
|
}
|
||||||
|
|
||||||
/// Find the deepest layer that has been clicked on from a location in viewport space.
|
/// Find the deepest layer that has been clicked on from a location in viewport space.
|
||||||
pub fn click(&self, ipp: &InputPreprocessorMessageHandler) -> Option<LayerNodeIdentifier> {
|
pub fn click(&self, ipp: &InputPreprocessorMessageHandler) -> Option<LayerNodeIdentifier> {
|
||||||
self.click_list(ipp).last()
|
self.click_list(ipp).last()
|
||||||
@@ -1703,31 +1715,6 @@ impl DocumentMessageHandler {
|
|||||||
.last()
|
.last()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the combined bounding box of the click targets of the selected visible layers in viewport space
|
|
||||||
pub fn selected_visible_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
|
|
||||||
self.network_interface
|
|
||||||
.selected_nodes()
|
|
||||||
.selected_visible_layers(&self.network_interface)
|
|
||||||
.filter_map(|layer| self.metadata().bounding_box_viewport(layer))
|
|
||||||
.reduce(graphene_std::renderer::Quad::combine_bounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn selected_visible_and_unlock_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
|
|
||||||
self.network_interface
|
|
||||||
.selected_nodes()
|
|
||||||
.selected_visible_and_unlocked_layers(&self.network_interface)
|
|
||||||
.filter_map(|layer| self.metadata().bounding_box_viewport(layer))
|
|
||||||
.reduce(graphene_std::renderer::Quad::combine_bounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn selected_visible_and_unlock_layers_bounding_box_document(&self) -> Option<[DVec2; 2]> {
|
|
||||||
self.network_interface
|
|
||||||
.selected_nodes()
|
|
||||||
.selected_visible_and_unlocked_layers(&self.network_interface)
|
|
||||||
.map(|layer| self.metadata().nonzero_bounding_box(layer))
|
|
||||||
.reduce(graphene_std::renderer::Quad::combine_bounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn document_network(&self) -> &NodeNetwork {
|
pub fn document_network(&self) -> &NodeNetwork {
|
||||||
self.network_interface.document_network()
|
self.network_interface.document_network()
|
||||||
}
|
}
|
||||||
@@ -1897,10 +1884,11 @@ impl DocumentMessageHandler {
|
|||||||
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
|
||||||
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
responses.add(NodeGraphMessage::SelectedNodesUpdated);
|
||||||
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
|
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
|
||||||
|
|
||||||
// TODO: Remove once the footprint is used to load the imports/export distances from the edge
|
// TODO: Remove once the footprint is used to load the imports/export distances from the edge
|
||||||
responses.add(NodeGraphMessage::UnloadWires);
|
responses.add(NodeGraphMessage::UnloadWires);
|
||||||
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
||||||
responses.add(Message::StartBuffer);
|
|
||||||
Some(previous_network)
|
Some(previous_network)
|
||||||
}
|
}
|
||||||
pub fn redo_with_history(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
pub fn redo_with_history(&mut self, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) {
|
||||||
@@ -2740,7 +2728,22 @@ impl DocumentMessageHandler {
|
|||||||
.tooltip("Add an operation to the end of this layer's chain of nodes")
|
.tooltip("Add an operation to the end of this layer's chain of nodes")
|
||||||
.disabled(!has_selection || has_multiple_selection)
|
.disabled(!has_selection || has_multiple_selection)
|
||||||
.popover_layout({
|
.popover_layout({
|
||||||
let node_chooser = NodeCatalog::new()
|
// Showing only compatible types
|
||||||
|
let compatible_type = selected_layer.and_then(|layer| {
|
||||||
|
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &self.network_interface);
|
||||||
|
let node_type = graph_layer.horizontal_layer_flow().nth(1);
|
||||||
|
if let Some(node_id) = node_type {
|
||||||
|
let (output_type, _) = self.network_interface.output_type(&node_id, 0, &self.selection_network_path);
|
||||||
|
Some(format!("type:{}", output_type.nested_type()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut node_chooser = NodeCatalog::new();
|
||||||
|
node_chooser.intial_search = compatible_type.unwrap_or("".to_string());
|
||||||
|
|
||||||
|
let node_chooser = node_chooser
|
||||||
.on_update(move |node_type| {
|
.on_update(move |node_type| {
|
||||||
if let Some(layer) = selected_layer {
|
if let Some(layer) = selected_layer {
|
||||||
NodeGraphMessage::CreateNodeInLayerWithTransaction {
|
NodeGraphMessage::CreateNodeInLayerWithTransaction {
|
||||||
|
|||||||
+12
-12
@@ -14,15 +14,8 @@ use graphene_std::renderer::convert_usvg_path::convert_usvg_path;
|
|||||||
use graphene_std::text::{Font, TypesettingConfig};
|
use graphene_std::text::{Font, TypesettingConfig};
|
||||||
use graphene_std::vector::style::{Fill, Gradient, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
use graphene_std::vector::style::{Fill, Gradient, GradientStops, GradientType, PaintOrder, Stroke, StrokeAlign, StrokeCap, StrokeJoin};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct ArtboardInfo {
|
|
||||||
input_node: NodeInput,
|
|
||||||
output_nodes: Vec<InputConnector>,
|
|
||||||
merge_node: NodeId,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(ExtractField)]
|
#[derive(ExtractField)]
|
||||||
pub struct GraphOperationMessageData<'a> {
|
pub struct GraphOperationMessageContext<'a> {
|
||||||
pub network_interface: &'a mut NodeNetworkInterface,
|
pub network_interface: &'a mut NodeNetworkInterface,
|
||||||
pub collapsed: &'a mut CollapsedLayers,
|
pub collapsed: &'a mut CollapsedLayers,
|
||||||
pub node_graph: &'a mut NodeGraphMessageHandler,
|
pub node_graph: &'a mut NodeGraphMessageHandler,
|
||||||
@@ -34,9 +27,9 @@ pub struct GraphOperationMessageHandler {}
|
|||||||
// GraphOperationMessageHandler always modified the document network. This is so changes to the layers panel will only affect the document network.
|
// GraphOperationMessageHandler always modified the document network. This is so changes to the layers panel will only affect the document network.
|
||||||
// For changes to the selected network, use NodeGraphMessageHandler. No NodeGraphMessage's should be added here, since they will affect the selected nested network.
|
// For changes to the selected network, use NodeGraphMessageHandler. No NodeGraphMessage's should be added here, since they will affect the selected nested network.
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for GraphOperationMessageHandler {
|
impl MessageHandler<GraphOperationMessage, GraphOperationMessageContext<'_>> for GraphOperationMessageHandler {
|
||||||
fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, data: GraphOperationMessageData) {
|
fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, context: GraphOperationMessageContext) {
|
||||||
let network_interface = data.network_interface;
|
let network_interface = context.network_interface;
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
GraphOperationMessage::FillSet { layer, fill } => {
|
GraphOperationMessage::FillSet { layer, fill } => {
|
||||||
@@ -126,7 +119,7 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
|||||||
let primary_input = artboard.inputs.first().expect("Artboard should have a primary input").clone();
|
let primary_input = artboard.inputs.first().expect("Artboard should have a primary input").clone();
|
||||||
if let NodeInput::Node { node_id, .. } = &primary_input {
|
if let NodeInput::Node { node_id, .. } = &primary_input {
|
||||||
if network_interface.is_layer(node_id, &[]) && !network_interface.is_artboard(node_id, &[]) {
|
if network_interface.is_layer(node_id, &[]) && !network_interface.is_artboard(node_id, &[]) {
|
||||||
network_interface.move_layer_to_stack(LayerNodeIdentifier::new(*node_id, network_interface, &[]), artboard_layer, 0, &[]);
|
network_interface.move_layer_to_stack(LayerNodeIdentifier::new(*node_id, network_interface), artboard_layer, 0, &[]);
|
||||||
} else {
|
} else {
|
||||||
network_interface.disconnect_input(&InputConnector::node(artboard_layer.to_node(), 0), &[]);
|
network_interface.disconnect_input(&InputConnector::node(artboard_layer.to_node(), 0), &[]);
|
||||||
network_interface.set_input(&InputConnector::node(id, 0), primary_input, &[]);
|
network_interface.set_input(&InputConnector::node(id, 0), primary_input, &[]);
|
||||||
@@ -323,6 +316,13 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct ArtboardInfo {
|
||||||
|
input_node: NodeInput,
|
||||||
|
output_nodes: Vec<InputConnector>,
|
||||||
|
merge_node: NodeId,
|
||||||
|
}
|
||||||
|
|
||||||
fn usvg_color(c: usvg::Color, a: f32) -> Color {
|
fn usvg_color(c: usvg::Color, a: f32) -> Color {
|
||||||
Color::from_rgbaf32_unchecked(c.red as f32 / 255., c.green as f32 / 255., c.blue as f32 / 255., a)
|
Color::from_rgbaf32_unchecked(c.red as f32 / 255., c.green as f32 / 255., c.blue as f32 / 255., a)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let layer_input_connector = post_node_input_connector.clone();
|
let layer_input_connector = post_node_input_connector;
|
||||||
|
|
||||||
// Sink post_node down to the end of the non layer chain that feeds into post_node, such that pre_node is the layer node at insert_index + 1, or None if insert_index is the last layer
|
// Sink post_node down to the end of the non layer chain that feeds into post_node, such that pre_node is the layer node at insert_index + 1, or None if insert_index is the last layer
|
||||||
loop {
|
loop {
|
||||||
@@ -124,7 +124,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
pub fn create_layer(&mut self, new_id: NodeId) -> LayerNodeIdentifier {
|
pub fn create_layer(&mut self, new_id: NodeId) -> LayerNodeIdentifier {
|
||||||
let new_merge_node = resolve_document_node_type("Merge").expect("Merge node").default_node_template();
|
let new_merge_node = resolve_document_node_type("Merge").expect("Merge node").default_node_template();
|
||||||
self.network_interface.insert_node(new_id, new_merge_node, &[]);
|
self.network_interface.insert_node(new_id, new_merge_node, &[]);
|
||||||
LayerNodeIdentifier::new(new_id, self.network_interface, &[])
|
LayerNodeIdentifier::new(new_id, self.network_interface)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates an artboard as the primary export for the document network
|
/// Creates an artboard as the primary export for the document network
|
||||||
@@ -132,13 +132,13 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
let artboard_node_template = resolve_document_node_type("Artboard").expect("Node").node_template_input_override([
|
let artboard_node_template = resolve_document_node_type("Artboard").expect("Node").node_template_input_override([
|
||||||
Some(NodeInput::value(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroupTable::default()), true)),
|
Some(NodeInput::value(TaggedValue::ArtboardGroup(graphene_std::ArtboardGroupTable::default()), true)),
|
||||||
Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_std::GraphicGroupTable::default()), true)),
|
Some(NodeInput::value(TaggedValue::GraphicGroup(graphene_std::GraphicGroupTable::default()), true)),
|
||||||
Some(NodeInput::value(TaggedValue::IVec2(artboard.location), false)),
|
Some(NodeInput::value(TaggedValue::DVec2(artboard.location.into()), false)),
|
||||||
Some(NodeInput::value(TaggedValue::IVec2(artboard.dimensions), false)),
|
Some(NodeInput::value(TaggedValue::DVec2(artboard.dimensions.into()), false)),
|
||||||
Some(NodeInput::value(TaggedValue::Color(artboard.background), false)),
|
Some(NodeInput::value(TaggedValue::Color(artboard.background), false)),
|
||||||
Some(NodeInput::value(TaggedValue::Bool(artboard.clip), false)),
|
Some(NodeInput::value(TaggedValue::Bool(artboard.clip), false)),
|
||||||
]);
|
]);
|
||||||
self.network_interface.insert_node(new_id, artboard_node_template, &[]);
|
self.network_interface.insert_node(new_id, artboard_node_template, &[]);
|
||||||
LayerNodeIdentifier::new(new_id, self.network_interface, &[])
|
LayerNodeIdentifier::new(new_id, self.network_interface)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_boolean_data(&mut self, operation: graphene_std::path_bool::BooleanOperation, layer: LayerNodeIdentifier) {
|
pub fn insert_boolean_data(&mut self, operation: graphene_std::path_bool::BooleanOperation, layer: LayerNodeIdentifier) {
|
||||||
@@ -198,6 +198,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_width), false)),
|
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_width), false)),
|
||||||
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_height), false)),
|
Some(NodeInput::value(TaggedValue::OptionalF64(typesetting.max_height), false)),
|
||||||
Some(NodeInput::value(TaggedValue::F64(typesetting.tilt), false)),
|
Some(NodeInput::value(TaggedValue::F64(typesetting.tilt), false)),
|
||||||
|
Some(NodeInput::value(TaggedValue::TextAlign(typesetting.align), false)),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let text_id = NodeId::new();
|
let text_id = NodeId::new();
|
||||||
@@ -236,7 +237,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
self.layer_node.or_else(|| {
|
self.layer_node.or_else(|| {
|
||||||
let export_node = self.network_interface.document_network().exports.first().and_then(|export| export.as_node())?;
|
let export_node = self.network_interface.document_network().exports.first().and_then(|export| export.as_node())?;
|
||||||
if self.network_interface.is_layer(&export_node, &[]) {
|
if self.network_interface.is_layer(&export_node, &[]) {
|
||||||
Some(LayerNodeIdentifier::new(export_node, self.network_interface, &[]))
|
Some(LayerNodeIdentifier::new(export_node, self.network_interface))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -486,8 +487,8 @@ impl<'a> ModifyInputsContext<'a> {
|
|||||||
dimensions.y *= -1;
|
dimensions.y *= -1;
|
||||||
location.y -= dimensions.y;
|
location.y -= dimensions.y;
|
||||||
}
|
}
|
||||||
self.set_input_with_refresh(InputConnector::node(artboard_node_id, 2), NodeInput::value(TaggedValue::IVec2(location), false), false);
|
self.set_input_with_refresh(InputConnector::node(artboard_node_id, 2), NodeInput::value(TaggedValue::DVec2(location.into()), false), false);
|
||||||
self.set_input_with_refresh(InputConnector::node(artboard_node_id, 3), NodeInput::value(TaggedValue::IVec2(dimensions), false), false);
|
self.set_input_with_refresh(InputConnector::node(artboard_node_id, 3), NodeInput::value(TaggedValue::DVec2(dimensions.into()), false), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the input, refresh the properties panel, and run the document graph if skip_rerender is false
|
/// Set the input, refresh the properties panel, and run the document graph if skip_rerender is false
|
||||||
|
|||||||
@@ -11,4 +11,4 @@ pub mod utility_types;
|
|||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use document_message::{DocumentMessage, DocumentMessageDiscriminant};
|
pub use document_message::{DocumentMessage, DocumentMessageDiscriminant};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use document_message_handler::{DocumentMessageData, DocumentMessageHandler};
|
pub use document_message_handler::{DocumentMessageContext, DocumentMessageHandler};
|
||||||
|
|||||||
@@ -5,4 +5,4 @@ pub mod utility_types;
|
|||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use navigation_message::{NavigationMessage, NavigationMessageDiscriminant};
|
pub use navigation_message::{NavigationMessage, NavigationMessageDiscriminant};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use navigation_message_handler::{NavigationMessageData, NavigationMessageHandler};
|
pub use navigation_message_handler::{NavigationMessageContext, NavigationMessageHandler};
|
||||||
|
|||||||
@@ -14,11 +14,10 @@ use glam::{DAffine2, DVec2};
|
|||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
|
|
||||||
#[derive(ExtractField)]
|
#[derive(ExtractField)]
|
||||||
pub struct NavigationMessageData<'a> {
|
pub struct NavigationMessageContext<'a> {
|
||||||
pub network_interface: &'a mut NodeNetworkInterface,
|
pub network_interface: &'a mut NodeNetworkInterface,
|
||||||
pub breadcrumb_network_path: &'a [NodeId],
|
pub breadcrumb_network_path: &'a [NodeId],
|
||||||
pub ipp: &'a InputPreprocessorMessageHandler,
|
pub ipp: &'a InputPreprocessorMessageHandler,
|
||||||
pub selection_bounds: Option<[DVec2; 2]>,
|
|
||||||
pub document_ptz: &'a mut PTZ,
|
pub document_ptz: &'a mut PTZ,
|
||||||
pub graph_view_overlay_open: bool,
|
pub graph_view_overlay_open: bool,
|
||||||
pub preferences: &'a PreferencesMessageHandler,
|
pub preferences: &'a PreferencesMessageHandler,
|
||||||
@@ -33,17 +32,16 @@ pub struct NavigationMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for NavigationMessageHandler {
|
impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for NavigationMessageHandler {
|
||||||
fn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque<Message>, data: NavigationMessageData) {
|
fn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque<Message>, context: NavigationMessageContext) {
|
||||||
let NavigationMessageData {
|
let NavigationMessageContext {
|
||||||
network_interface,
|
network_interface,
|
||||||
breadcrumb_network_path,
|
breadcrumb_network_path,
|
||||||
ipp,
|
ipp,
|
||||||
selection_bounds,
|
|
||||||
document_ptz,
|
document_ptz,
|
||||||
graph_view_overlay_open,
|
graph_view_overlay_open,
|
||||||
preferences,
|
preferences,
|
||||||
} = data;
|
} = context;
|
||||||
|
|
||||||
fn get_ptz<'a>(document_ptz: &'a PTZ, network_interface: &'a NodeNetworkInterface, graph_view_overlay_open: bool, breadcrumb_network_path: &[NodeId]) -> Option<&'a PTZ> {
|
fn get_ptz<'a>(document_ptz: &'a PTZ, network_interface: &'a NodeNetworkInterface, graph_view_overlay_open: bool, breadcrumb_network_path: &[NodeId]) -> Option<&'a PTZ> {
|
||||||
if !graph_view_overlay_open {
|
if !graph_view_overlay_open {
|
||||||
@@ -386,9 +384,16 @@ impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for Navigation
|
|||||||
responses.add(DocumentMessage::PTZUpdate);
|
responses.add(DocumentMessage::PTZUpdate);
|
||||||
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
responses.add(NodeGraphMessage::SetGridAlignedEdges);
|
||||||
}
|
}
|
||||||
|
// Fully zooms in on the selected
|
||||||
NavigationMessage::FitViewportToSelection => {
|
NavigationMessage::FitViewportToSelection => {
|
||||||
|
let selection_bounds = if graph_view_overlay_open {
|
||||||
|
network_interface.selected_nodes_bounding_box_viewport(breadcrumb_network_path)
|
||||||
|
} else {
|
||||||
|
network_interface.selected_layers_artwork_bounding_box_viewport()
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(bounds) = selection_bounds {
|
if let Some(bounds) = selection_bounds {
|
||||||
let Some(ptz) = get_ptz_mut(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
let Some(ptz) = get_ptz(document_ptz, network_interface, graph_view_overlay_open, breadcrumb_network_path) else {
|
||||||
log::error!("Could not get node graph PTZ in FitViewportToSelection");
|
log::error!("Could not get node graph PTZ in FitViewportToSelection");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -378,8 +378,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::value(TaggedValue::ArtboardGroup(ArtboardGroupTable::default()), true),
|
NodeInput::value(TaggedValue::ArtboardGroup(ArtboardGroupTable::default()), true),
|
||||||
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
|
NodeInput::value(TaggedValue::GraphicGroup(GraphicGroupTable::default()), true),
|
||||||
NodeInput::value(TaggedValue::IVec2(glam::IVec2::ZERO), false),
|
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
||||||
NodeInput::value(TaggedValue::IVec2(glam::IVec2::new(1920, 1080)), false),
|
NodeInput::value(TaggedValue::DVec2(DVec2::new(1920., 1080.)), false),
|
||||||
NodeInput::value(TaggedValue::Color(Color::WHITE), false),
|
NodeInput::value(TaggedValue::Color(Color::WHITE), false),
|
||||||
NodeInput::value(TaggedValue::Bool(false), false),
|
NodeInput::value(TaggedValue::Bool(false), false),
|
||||||
],
|
],
|
||||||
@@ -396,6 +396,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
x: "X".to_string(),
|
x: "X".to_string(),
|
||||||
y: "Y".to_string(),
|
y: "Y".to_string(),
|
||||||
unit: " px".to_string(),
|
unit: " px".to_string(),
|
||||||
|
is_integer: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -406,6 +407,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
x: "W".to_string(),
|
x: "W".to_string(),
|
||||||
y: "H".to_string(),
|
y: "H".to_string(),
|
||||||
unit: " px".to_string(),
|
unit: " px".to_string(),
|
||||||
|
is_integer: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -628,8 +630,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
NodeInput::value(TaggedValue::VectorData(VectorDataTable::default()), true),
|
NodeInput::value(TaggedValue::VectorData(VectorDataTable::default()), true),
|
||||||
NodeInput::value(
|
NodeInput::value(
|
||||||
TaggedValue::Footprint(Footprint {
|
TaggedValue::Footprint(Footprint {
|
||||||
transform: DAffine2::from_scale_angle_translation(DVec2::new(100., 100.), 0., DVec2::new(0., 0.)),
|
transform: DAffine2::from_scale_angle_translation(DVec2::new(1000., 1000.), 0., DVec2::new(0., 0.)),
|
||||||
resolution: UVec2::new(100, 100),
|
resolution: UVec2::new(1000, 1000),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
false,
|
false,
|
||||||
@@ -849,7 +851,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
properties: None,
|
properties: None,
|
||||||
},
|
},
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: "Split Coordinate",
|
identifier: "Split Vec2",
|
||||||
category: "Math: Vector",
|
category: "Math: Vector",
|
||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
@@ -880,7 +882,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
persistent_node_metadata: DocumentNodePersistentMetadata {
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
input_metadata: vec![("Coordinate", "TODO").into()],
|
input_metadata: vec![("Vec2", "TODO").into()],
|
||||||
output_names: vec!["X".to_string(), "Y".to_string()],
|
output_names: vec!["X".to_string(), "Y".to_string()],
|
||||||
has_primary_output: false,
|
has_primary_output: false,
|
||||||
network_metadata: Some(NodeNetworkMetadata {
|
network_metadata: Some(NodeNetworkMetadata {
|
||||||
@@ -915,7 +917,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
description: Cow::Borrowed(
|
description: Cow::Borrowed(
|
||||||
"Decomposes the X and Y components of a 2D coordinate.\n\nThe inverse of this node is \"Coordinate Value\", which can have either or both its X and Y exposed as graph inputs.",
|
"Decomposes the X and Y components of a vec2.\n\nThe inverse of this node is \"Vec2 Value\", which can have either or both its X and Y parameters exposed as graph inputs.",
|
||||||
),
|
),
|
||||||
properties: None,
|
properties: None,
|
||||||
},
|
},
|
||||||
@@ -1081,6 +1083,86 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
description: Cow::Borrowed("TODO"),
|
description: Cow::Borrowed("TODO"),
|
||||||
properties: None,
|
properties: None,
|
||||||
},
|
},
|
||||||
|
#[cfg(feature = "gpu")]
|
||||||
|
DocumentNodeDefinition {
|
||||||
|
identifier: "Upload Texture",
|
||||||
|
category: "Debug: GPU",
|
||||||
|
node_template: NodeTemplate {
|
||||||
|
document_node: DocumentNode {
|
||||||
|
implementation: DocumentNodeImplementation::Network(NodeNetwork {
|
||||||
|
exports: vec![NodeInput::node(NodeId(2), 0)],
|
||||||
|
nodes: [
|
||||||
|
DocumentNode {
|
||||||
|
inputs: vec![NodeInput::scope("editor-api")],
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<&WgpuExecutor>")),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
DocumentNode {
|
||||||
|
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::node(NodeId(0), 0)],
|
||||||
|
manual_composition: Some(generic!(T)),
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::texture_upload::upload_texture::IDENTIFIER),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
DocumentNode {
|
||||||
|
manual_composition: Some(generic!(T)),
|
||||||
|
inputs: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
|
implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(id, node)| (NodeId(id as u64), node))
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
persistent_node_metadata: DocumentNodePersistentMetadata {
|
||||||
|
output_names: vec!["Texture".to_string()],
|
||||||
|
network_metadata: Some(NodeNetworkMetadata {
|
||||||
|
persistent_metadata: NodeNetworkPersistentMetadata {
|
||||||
|
node_metadata: [
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
display_name: "Extract Executor".to_string(),
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
display_name: "Upload Texture".to_string(),
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
DocumentNodeMetadata {
|
||||||
|
persistent_metadata: DocumentNodePersistentMetadata {
|
||||||
|
display_name: "Cache".to_string(),
|
||||||
|
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(id, node)| (NodeId(id as u64), node))
|
||||||
|
.collect(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: Cow::Borrowed("TODO"),
|
||||||
|
properties: None,
|
||||||
|
},
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: "Extract",
|
identifier: "Extract",
|
||||||
category: "Debug",
|
category: "Debug",
|
||||||
@@ -1222,6 +1304,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false),
|
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_width), false),
|
||||||
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false),
|
NodeInput::value(TaggedValue::OptionalF64(TypesettingConfig::default().max_height), false),
|
||||||
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false),
|
NodeInput::value(TaggedValue::F64(TypesettingConfig::default().tilt), false),
|
||||||
|
NodeInput::value(TaggedValue::TextAlign(text::TextAlign::default()), false),
|
||||||
|
NodeInput::value(TaggedValue::Bool(false), false),
|
||||||
],
|
],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
@@ -1254,7 +1338,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
"TODO",
|
"TODO",
|
||||||
WidgetOverride::Number(NumberInputSettings {
|
WidgetOverride::Number(NumberInputSettings {
|
||||||
unit: Some(" px".to_string()),
|
unit: Some(" px".to_string()),
|
||||||
min: Some(0.),
|
|
||||||
step: Some(0.1),
|
step: Some(0.1),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
@@ -1281,7 +1364,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
),
|
),
|
||||||
InputMetadata::with_name_description_override(
|
InputMetadata::with_name_description_override(
|
||||||
"Tilt",
|
"Tilt",
|
||||||
"Faux italic",
|
"Faux italic.",
|
||||||
WidgetOverride::Number(NumberInputSettings {
|
WidgetOverride::Number(NumberInputSettings {
|
||||||
min: Some(-85.),
|
min: Some(-85.),
|
||||||
max: Some(85.),
|
max: Some(85.),
|
||||||
@@ -1289,6 +1372,8 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
InputMetadata::with_name_description_override("Align", "TODO", WidgetOverride::Custom("text_align".to_string())),
|
||||||
|
("Per-Glyph Instances", "Splits each text glyph into its own instance, i.e. row in the table of vector data.").into(),
|
||||||
],
|
],
|
||||||
output_names: vec!["Vector".to_string()],
|
output_names: vec!["Vector".to_string()],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@@ -1299,11 +1384,11 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
},
|
},
|
||||||
DocumentNodeDefinition {
|
DocumentNodeDefinition {
|
||||||
identifier: "Transform",
|
identifier: "Transform",
|
||||||
category: "General",
|
category: "Math: Transform",
|
||||||
node_template: NodeTemplate {
|
node_template: NodeTemplate {
|
||||||
document_node: DocumentNode {
|
document_node: DocumentNode {
|
||||||
inputs: vec![
|
inputs: vec![
|
||||||
NodeInput::value(TaggedValue::VectorData(VectorDataTable::default()), true),
|
NodeInput::value(TaggedValue::DAffine2(DAffine2::default()), true),
|
||||||
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
||||||
NodeInput::value(TaggedValue::F64(0.), false),
|
NodeInput::value(TaggedValue::F64(0.), false),
|
||||||
NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),
|
NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),
|
||||||
@@ -1313,7 +1398,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
exports: vec![NodeInput::node(NodeId(1), 0)],
|
exports: vec![NodeInput::node(NodeId(1), 0)],
|
||||||
nodes: [
|
nodes: [
|
||||||
DocumentNode {
|
DocumentNode {
|
||||||
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)],
|
inputs: vec![NodeInput::network(generic!(T), 0)],
|
||||||
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
|
||||||
manual_composition: Some(generic!(T)),
|
manual_composition: Some(generic!(T)),
|
||||||
skip_deduplication: true,
|
skip_deduplication: true,
|
||||||
@@ -1370,7 +1455,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
input_metadata: vec![
|
input_metadata: vec![
|
||||||
("Vector Data", "TODO").into(),
|
("Value", "TODO").into(),
|
||||||
InputMetadata::with_name_description_override(
|
InputMetadata::with_name_description_override(
|
||||||
"Translation",
|
"Translation",
|
||||||
"TODO",
|
"TODO",
|
||||||
@@ -1951,8 +2036,20 @@ fn static_input_properties() -> InputProperties {
|
|||||||
.network_interface
|
.network_interface
|
||||||
.input_data(&node_id, index, "min", context.selection_network_path)
|
.input_data(&node_id, index, "min", context.selection_network_path)
|
||||||
.and_then(|value| value.as_f64());
|
.and_then(|value| value.as_f64());
|
||||||
|
let is_integer = context
|
||||||
|
.network_interface
|
||||||
|
.input_data(&node_id, index, "is_integer", context.selection_network_path)
|
||||||
|
.and_then(|value| value.as_bool())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
Ok(vec![node_properties::coordinate_widget(ParameterWidgetsInfo::new(node_id, index, true, context), &x, &y, &unit, min)])
|
Ok(vec![node_properties::vec2_widget(
|
||||||
|
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||||
|
&x,
|
||||||
|
&y,
|
||||||
|
&unit,
|
||||||
|
min,
|
||||||
|
is_integer,
|
||||||
|
)])
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
map.insert(
|
map.insert(
|
||||||
@@ -2201,7 +2298,7 @@ fn static_input_properties() -> InputProperties {
|
|||||||
"spline_input".to_string(),
|
"spline_input".to_string(),
|
||||||
Box::new(|node_id, index, context| {
|
Box::new(|node_id, index, context| {
|
||||||
Ok(vec![LayoutGroup::Row {
|
Ok(vec![LayoutGroup::Row {
|
||||||
widgets: node_properties::array_of_coordinates_widget(ParameterWidgetsInfo::new(node_id, index, true, context), TextInput::default().centered(true)),
|
widgets: node_properties::array_of_vec2_widget(ParameterWidgetsInfo::new(node_id, index, true, context), TextInput::default().centered(true)),
|
||||||
}])
|
}])
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -2308,6 +2405,13 @@ fn static_input_properties() -> InputProperties {
|
|||||||
)])
|
)])
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
map.insert(
|
||||||
|
"text_align".to_string(),
|
||||||
|
Box::new(|node_id, index, context| {
|
||||||
|
let choices = enum_choice::<text::TextAlign>().for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)).property_row();
|
||||||
|
Ok(vec![choices])
|
||||||
|
}),
|
||||||
|
);
|
||||||
map
|
map
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ pub enum NodeGraphMessage {
|
|||||||
nodes: Vec<(NodeId, NodeTemplate)>,
|
nodes: Vec<(NodeId, NodeTemplate)>,
|
||||||
new_ids: HashMap<NodeId, NodeId>,
|
new_ids: HashMap<NodeId, NodeId>,
|
||||||
},
|
},
|
||||||
|
AddPathNode,
|
||||||
AddImport,
|
AddImport,
|
||||||
AddExport,
|
AddExport,
|
||||||
Init,
|
Init,
|
||||||
@@ -81,6 +82,9 @@ pub enum NodeGraphMessage {
|
|||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
parent: LayerNodeIdentifier,
|
parent: LayerNodeIdentifier,
|
||||||
},
|
},
|
||||||
|
SetChainPosition {
|
||||||
|
node_id: NodeId,
|
||||||
|
},
|
||||||
PasteNodes {
|
PasteNodes {
|
||||||
serialized_nodes: String,
|
serialized_nodes: String,
|
||||||
},
|
},
|
||||||
@@ -97,6 +101,7 @@ pub enum NodeGraphMessage {
|
|||||||
PointerOutsideViewport {
|
PointerOutsideViewport {
|
||||||
shift: Key,
|
shift: Key,
|
||||||
},
|
},
|
||||||
|
ShakeNode,
|
||||||
RemoveImport {
|
RemoveImport {
|
||||||
import_index: usize,
|
import_index: usize,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,25 +10,29 @@ use crate::messages::portfolio::document::node_graph::utility_types::{ContextMen
|
|||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::portfolio::document::utility_types::misc::GroupFolderType;
|
use crate::messages::portfolio::document::utility_types::misc::GroupFolderType;
|
||||||
use crate::messages::portfolio::document::utility_types::network_interface::{
|
use crate::messages::portfolio::document::utility_types::network_interface::{
|
||||||
self, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource,
|
self, FlowType, InputConnector, NodeNetworkInterface, NodeTemplate, NodeTypePersistentMetadata, OutputConnector, Previewing, TypeSource,
|
||||||
};
|
};
|
||||||
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry};
|
use crate::messages::portfolio::document::utility_types::nodes::{CollapsedLayers, LayerPanelEntry};
|
||||||
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire};
|
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils::get_clip_mode;
|
use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_clip_mode};
|
||||||
use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion};
|
use crate::messages::tool::tool_messages::tool_prelude::{Key, MouseMotion};
|
||||||
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
|
||||||
|
use bezier_rs::Subpath;
|
||||||
use glam::{DAffine2, DVec2, IVec2};
|
use glam::{DAffine2, DVec2, IVec2};
|
||||||
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
|
use graph_craft::document::{DocumentNodeImplementation, NodeId, NodeInput};
|
||||||
use graph_craft::proto::GraphErrors;
|
use graph_craft::proto::GraphErrors;
|
||||||
use graphene_std::math::math_ext::QuadExt;
|
use graphene_std::math::math_ext::QuadExt;
|
||||||
|
use graphene_std::vector::misc::subpath_to_kurbo_bezpath;
|
||||||
use graphene_std::*;
|
use graphene_std::*;
|
||||||
|
use kurbo::{Line, Point};
|
||||||
use renderer::Quad;
|
use renderer::Quad;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
#[derive(Debug, ExtractField)]
|
#[derive(Debug, ExtractField)]
|
||||||
pub struct NodeGraphHandlerData<'a> {
|
pub struct NodeGraphMessageContext<'a> {
|
||||||
pub network_interface: &'a mut NodeNetworkInterface,
|
pub network_interface: &'a mut NodeNetworkInterface,
|
||||||
pub selection_network_path: &'a [NodeId],
|
pub selection_network_path: &'a [NodeId],
|
||||||
pub breadcrumb_network_path: &'a [NodeId],
|
pub breadcrumb_network_path: &'a [NodeId],
|
||||||
@@ -55,6 +59,8 @@ pub struct NodeGraphMessageHandler {
|
|||||||
/// If dragging the selected nodes, this stores the starting position both in viewport and node graph coordinates,
|
/// If dragging the selected nodes, this stores the starting position both in viewport and node graph coordinates,
|
||||||
/// plus a flag indicating if it has been dragged since the mousedown began.
|
/// plus a flag indicating if it has been dragged since the mousedown began.
|
||||||
pub drag_start: Option<(DragStart, bool)>,
|
pub drag_start: Option<(DragStart, bool)>,
|
||||||
|
// Store the selected chain nodes on drag start so they can be reconnected if shaken
|
||||||
|
pub drag_start_chain_nodes: Vec<NodeId>,
|
||||||
/// If dragging the background to create a box selection, this stores its starting point in node graph coordinates,
|
/// If dragging the background to create a box selection, this stores its starting point in node graph coordinates,
|
||||||
/// plus a flag indicating if it has been dragged since the mousedown began.
|
/// plus a flag indicating if it has been dragged since the mousedown began.
|
||||||
box_selection_start: Option<(DVec2, bool)>,
|
box_selection_start: Option<(DVec2, bool)>,
|
||||||
@@ -93,9 +99,9 @@ pub struct NodeGraphMessageHandler {
|
|||||||
|
|
||||||
/// NodeGraphMessageHandler always modifies the network which the selected nodes are in. No GraphOperationMessages should be added here, since those messages will always affect the document network.
|
/// NodeGraphMessageHandler always modifies the network which the selected nodes are in. No GraphOperationMessages should be added here, since those messages will always affect the document network.
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGraphMessageHandler {
|
impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeGraphMessageHandler {
|
||||||
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, data: NodeGraphHandlerData<'a>) {
|
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, context: NodeGraphMessageContext<'a>) {
|
||||||
let NodeGraphHandlerData {
|
let NodeGraphMessageContext {
|
||||||
network_interface,
|
network_interface,
|
||||||
selection_network_path,
|
selection_network_path,
|
||||||
breadcrumb_network_path,
|
breadcrumb_network_path,
|
||||||
@@ -106,7 +112,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
graph_fade_artwork_percentage,
|
graph_fade_artwork_percentage,
|
||||||
navigation_handler,
|
navigation_handler,
|
||||||
preferences,
|
preferences,
|
||||||
} = data;
|
} = context;
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
// TODO: automatically remove broadcast messages.
|
// TODO: automatically remove broadcast messages.
|
||||||
@@ -119,6 +125,38 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
|
|
||||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_layer_id] });
|
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![new_layer_id] });
|
||||||
}
|
}
|
||||||
|
NodeGraphMessage::AddPathNode => {
|
||||||
|
let selected_nodes = network_interface.selected_nodes();
|
||||||
|
let mut selected_layers = selected_nodes.selected_layers(network_interface.document_metadata());
|
||||||
|
let first_layer = selected_layers.next();
|
||||||
|
let second_layer = selected_layers.next();
|
||||||
|
let has_single_selection = first_layer.is_some() && second_layer.is_none();
|
||||||
|
|
||||||
|
let compatible_type = first_layer.and_then(|layer| {
|
||||||
|
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &network_interface);
|
||||||
|
graph_layer.horizontal_layer_flow().nth(1).and_then(|node_id| {
|
||||||
|
let (output_type, _) = network_interface.output_type(&node_id, 0, &[]);
|
||||||
|
Some(format!("type:{}", output_type.nested_type()))
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let is_compatible = compatible_type.as_deref() == Some("type:Instances<VectorData>");
|
||||||
|
|
||||||
|
if first_layer.is_some() && has_single_selection && is_compatible {
|
||||||
|
if let Some(layer) = first_layer {
|
||||||
|
let node_type = "Path".to_string();
|
||||||
|
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, &network_interface);
|
||||||
|
let is_modifiable = matches!(graph_layer.find_input("Path", 1), Some(TaggedValue::VectorModification(_)));
|
||||||
|
if !is_modifiable {
|
||||||
|
responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction {
|
||||||
|
node_type: node_type.clone(),
|
||||||
|
layer: LayerNodeIdentifier::new_unchecked(layer.to_node()),
|
||||||
|
});
|
||||||
|
responses.add(BroadcastEvent::SelectionChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
NodeGraphMessage::AddImport => {
|
NodeGraphMessage::AddImport => {
|
||||||
network_interface.add_import(graph_craft::document::value::TaggedValue::None, true, -1, "", "", breadcrumb_network_path);
|
network_interface.add_import(graph_craft::document::value::TaggedValue::None, true, -1, "", "", breadcrumb_network_path);
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
@@ -568,6 +606,9 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
NodeGraphMessage::MoveNodeToChainStart { node_id, parent } => {
|
NodeGraphMessage::MoveNodeToChainStart { node_id, parent } => {
|
||||||
network_interface.move_node_to_chain_start(&node_id, parent, selection_network_path);
|
network_interface.move_node_to_chain_start(&node_id, parent, selection_network_path);
|
||||||
}
|
}
|
||||||
|
NodeGraphMessage::SetChainPosition { node_id } => {
|
||||||
|
network_interface.set_chain_position(&node_id, selection_network_path);
|
||||||
|
}
|
||||||
NodeGraphMessage::PasteNodes { serialized_nodes } => {
|
NodeGraphMessage::PasteNodes { serialized_nodes } => {
|
||||||
let data = match serde_json::from_str::<Vec<(NodeId, NodeTemplate)>>(&serialized_nodes) {
|
let data = match serde_json::from_str::<Vec<(NodeId, NodeTemplate)>>(&serialized_nodes) {
|
||||||
Ok(d) => d,
|
Ok(d) => d,
|
||||||
@@ -821,6 +862,20 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.drag_start = Some((drag_start, false));
|
self.drag_start = Some((drag_start, false));
|
||||||
|
let selected_chain_nodes = updated_selected
|
||||||
|
.iter()
|
||||||
|
.filter(|node_id| network_interface.is_chain(node_id, selection_network_path))
|
||||||
|
.copied()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
self.drag_start_chain_nodes = selected_chain_nodes
|
||||||
|
.iter()
|
||||||
|
.flat_map(|selected| {
|
||||||
|
network_interface
|
||||||
|
.upstream_flow_back_from_nodes(vec![*selected], selection_network_path, FlowType::PrimaryFlow)
|
||||||
|
.skip(1)
|
||||||
|
.filter(|node_id| network_interface.is_chain(node_id, selection_network_path))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
self.begin_dragging = true;
|
self.begin_dragging = true;
|
||||||
self.node_has_moved_in_drag = false;
|
self.node_has_moved_in_drag = false;
|
||||||
self.update_node_graph_hints(responses);
|
self.update_node_graph_hints(responses);
|
||||||
@@ -1188,10 +1243,39 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
log::debug!("preferences.graph_wire_style: {:?}", preferences.graph_wire_style);
|
||||||
let (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?;
|
let (wire, is_stack) = network_interface.vector_wire_from_input(&input, preferences.graph_wire_style, selection_network_path)?;
|
||||||
wire.rectangle_intersections_exist(bounding_box[0], bounding_box[1]).then_some((input, is_stack))
|
|
||||||
|
let bbox_rect = kurbo::Rect::new(bounding_box[0].x, bounding_box[0].y, bounding_box[1].x, bounding_box[1].y);
|
||||||
|
|
||||||
|
let p1 = DVec2::new(bbox_rect.x0, bbox_rect.y0);
|
||||||
|
let p2 = DVec2::new(bbox_rect.x1, bbox_rect.y0);
|
||||||
|
let p3 = DVec2::new(bbox_rect.x1, bbox_rect.y1);
|
||||||
|
let p4 = DVec2::new(bbox_rect.x0, bbox_rect.y1);
|
||||||
|
let ps = [p1, p2, p3, p4];
|
||||||
|
|
||||||
|
let inside = wire.is_inside_subpath(&Subpath::from_anchors_linear(ps, true), None, None);
|
||||||
|
|
||||||
|
let wire = subpath_to_kurbo_bezpath(wire);
|
||||||
|
|
||||||
|
let intersect = wire.segments().any(|segment| {
|
||||||
|
let rect = kurbo::Rect::new(bounding_box[0].x, bounding_box[0].y, bounding_box[1].x, bounding_box[1].y);
|
||||||
|
|
||||||
|
let top_line = Line::new(Point::new(rect.x0, rect.y0), Point::new(rect.x1, rect.y0));
|
||||||
|
let bottom_line = Line::new(Point::new(rect.x0, rect.y1), Point::new(rect.x1, rect.y1));
|
||||||
|
let left_line = Line::new(Point::new(rect.x0, rect.y0), Point::new(rect.x0, rect.y1));
|
||||||
|
let right_line = Line::new(Point::new(rect.x1, rect.y0), Point::new(rect.x1, rect.y1));
|
||||||
|
|
||||||
|
!segment.intersect_line(top_line).is_empty()
|
||||||
|
|| !segment.intersect_line(bottom_line).is_empty()
|
||||||
|
|| !segment.intersect_line(left_line).is_empty()
|
||||||
|
|| !segment.intersect_line(right_line).is_empty()
|
||||||
|
});
|
||||||
|
|
||||||
|
(intersect || inside).then_some((input, is_stack))
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Prioritize vertical thick lines and cancel if there are multiple potential wires
|
// Prioritize vertical thick lines and cancel if there are multiple potential wires
|
||||||
let mut node_wires = Vec::new();
|
let mut node_wires = Vec::new();
|
||||||
let mut stack_wires = Vec::new();
|
let mut stack_wires = Vec::new();
|
||||||
@@ -1270,6 +1354,135 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
self.auto_panning.stop(&messages, responses);
|
self.auto_panning.stop(&messages, responses);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
NodeGraphMessage::ShakeNode => {
|
||||||
|
let Some(drag_start) = &self.drag_start else {
|
||||||
|
log::error!("Drag start should be initialized when shaking a node");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(network_metadata) = network_interface.network_metadata(selection_network_path) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let viewport_location = ipp.mouse.position;
|
||||||
|
let point = network_metadata
|
||||||
|
.persistent_metadata
|
||||||
|
.navigation_metadata
|
||||||
|
.node_graph_to_viewport
|
||||||
|
.inverse()
|
||||||
|
.transform_point2(viewport_location);
|
||||||
|
|
||||||
|
// Collect the distance to move the shaken nodes after the undo
|
||||||
|
let graph_delta = IVec2::new(((point.x - drag_start.0.start_x) / 24.).round() as i32, ((point.y - drag_start.0.start_y) / 24.).round() as i32);
|
||||||
|
|
||||||
|
// Undo to the state of the graph before shaking
|
||||||
|
responses.add(DocumentMessage::AbortTransaction);
|
||||||
|
|
||||||
|
// Add a history step to abort to the state before shaking if right clicked
|
||||||
|
responses.add(DocumentMessage::StartTransaction);
|
||||||
|
|
||||||
|
let Some(selected_nodes) = network_interface.selected_nodes_in_nested_network(selection_network_path) else {
|
||||||
|
log::error!("Could not get selected nodes in ShakeNode");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut all_selected_nodes = selected_nodes.0.iter().copied().collect::<HashSet<_>>();
|
||||||
|
for selected_layer in selected_nodes
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.filter(|selected_node| network_interface.is_layer(selected_node, selection_network_path))
|
||||||
|
.copied()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
{
|
||||||
|
for sole_dependent in network_interface.upstream_nodes_below_layer(&selected_layer, selection_network_path) {
|
||||||
|
all_selected_nodes.insert(sole_dependent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for selected_node in &all_selected_nodes {
|
||||||
|
// Handle inputs of selected node
|
||||||
|
for input_index in 0..network_interface.number_of_inputs(selected_node, selection_network_path) {
|
||||||
|
let input_connector = InputConnector::node(*selected_node, input_index);
|
||||||
|
// Only disconnect inputs to non selected nodes
|
||||||
|
if network_interface
|
||||||
|
.upstream_output_connector(&input_connector, selection_network_path)
|
||||||
|
.and_then(|connector| connector.node_id())
|
||||||
|
.is_some_and(|node_id| !all_selected_nodes.contains(&node_id))
|
||||||
|
{
|
||||||
|
responses.add(NodeGraphMessage::DisconnectInput { input_connector });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let number_of_outputs = network_interface.number_of_outputs(selected_node, selection_network_path);
|
||||||
|
let first_deselected_upstream_node = network_interface
|
||||||
|
.upstream_flow_back_from_nodes(vec![*selected_node], selection_network_path, FlowType::PrimaryFlow)
|
||||||
|
.find(|upstream_node| !all_selected_nodes.contains(upstream_node));
|
||||||
|
let Some(outward_wires) = network_interface.outward_wires(selection_network_path) else {
|
||||||
|
log::error!("Could not get output wires in shake input");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Disconnect output wires to non selected nodes
|
||||||
|
for output_index in 0..number_of_outputs {
|
||||||
|
let output_connector = OutputConnector::node(*selected_node, output_index);
|
||||||
|
if let Some(downstream_connections) = outward_wires.get(&output_connector) {
|
||||||
|
for &input_connector in downstream_connections {
|
||||||
|
if input_connector.node_id().is_some_and(|downstream_node| !all_selected_nodes.contains(&downstream_node)) {
|
||||||
|
responses.add(NodeGraphMessage::DisconnectInput { input_connector });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle reconnection
|
||||||
|
// Find first non selected upstream node by primary flow
|
||||||
|
if let Some(first_deselected_upstream_node) = first_deselected_upstream_node {
|
||||||
|
let Some(downstream_connections_to_first_output) = outward_wires.get(&OutputConnector::node(*selected_node, 0)).cloned() else {
|
||||||
|
log::error!("Could not get downstream_connections_to_first_output in shake node");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// Reconnect only if all downstream outputs are not selected
|
||||||
|
if !downstream_connections_to_first_output
|
||||||
|
.iter()
|
||||||
|
.any(|connector| connector.node_id().is_some_and(|node_id| all_selected_nodes.contains(&node_id)))
|
||||||
|
{
|
||||||
|
// Find what output on the deselected upstream node to reconnect to
|
||||||
|
for output_index in 0..network_interface.number_of_outputs(&first_deselected_upstream_node, selection_network_path) {
|
||||||
|
let output_connector = &OutputConnector::node(first_deselected_upstream_node, output_index);
|
||||||
|
let Some(outward_wires) = network_interface.outward_wires(selection_network_path) else {
|
||||||
|
log::error!("Could not get output wires in shake input");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if let Some(inputs) = outward_wires.get(output_connector) {
|
||||||
|
// This can only run once
|
||||||
|
if inputs.iter().any(|input_connector| {
|
||||||
|
input_connector
|
||||||
|
.node_id()
|
||||||
|
.is_some_and(|upstream_node| all_selected_nodes.contains(&upstream_node) && input_connector.input_index() == 0)
|
||||||
|
}) {
|
||||||
|
// Output index is the output of the deselected upstream node to reconnect to
|
||||||
|
for downstream_connections_to_first_output in &downstream_connections_to_first_output {
|
||||||
|
responses.add(NodeGraphMessage::CreateWire {
|
||||||
|
output_connector: OutputConnector::node(first_deselected_upstream_node, output_index),
|
||||||
|
input_connector: *downstream_connections_to_first_output,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set all chain nodes back to chain position
|
||||||
|
// TODO: Fix
|
||||||
|
// for chain_node_to_reset in std::mem::take(&mut self.drag_start_chain_nodes) {
|
||||||
|
// responses.add(NodeGraphMessage::SetChainPosition { node_id: chain_node_to_reset });
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responses.add(NodeGraphMessage::ShiftSelectedNodesByAmount { graph_delta, rubber_band: false });
|
||||||
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
|
}
|
||||||
NodeGraphMessage::RemoveImport { import_index: usize } => {
|
NodeGraphMessage::RemoveImport { import_index: usize } => {
|
||||||
network_interface.remove_import(usize, selection_network_path);
|
network_interface.remove_import(usize, selection_network_path);
|
||||||
responses.add(NodeGraphMessage::SendGraph);
|
responses.add(NodeGraphMessage::SendGraph);
|
||||||
@@ -1354,6 +1567,11 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
|||||||
if node_bbox[1].x >= document_bbox[0].x && node_bbox[0].x <= document_bbox[1].x && node_bbox[1].y >= document_bbox[0].y && node_bbox[0].y <= document_bbox[1].y {
|
if node_bbox[1].x >= document_bbox[0].x && node_bbox[0].x <= document_bbox[1].x && node_bbox[1].y >= document_bbox[0].y && node_bbox[0].y <= document_bbox[1].y {
|
||||||
nodes.push(*node_id);
|
nodes.push(*node_id);
|
||||||
}
|
}
|
||||||
|
for error in &self.node_graph_errors {
|
||||||
|
if error.node_path.contains(node_id) {
|
||||||
|
nodes.push(*node_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.add(FrontendMessage::UpdateVisibleNodes { nodes });
|
responses.add(FrontendMessage::UpdateVisibleNodes { nodes });
|
||||||
@@ -1785,6 +2003,12 @@ impl NodeGraphMessageHandler {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.drag_start.is_some() {
|
||||||
|
common.extend(actions!(NodeGraphMessageDiscriminant;
|
||||||
|
ShakeNode,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
common
|
common
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1824,25 +2048,58 @@ impl NodeGraphMessageHandler {
|
|||||||
let selection_all_locked = network_interface.selected_nodes().selected_unlocked_layers(network_interface).count() == 0;
|
let selection_all_locked = network_interface.selected_nodes().selected_unlocked_layers(network_interface).count() == 0;
|
||||||
let selection_all_visible = selected_nodes.selected_nodes().all(|node_id| network_interface.is_visible(node_id, breadcrumb_network_path));
|
let selection_all_visible = selected_nodes.selected_nodes().all(|node_id| network_interface.is_visible(node_id, breadcrumb_network_path));
|
||||||
|
|
||||||
|
let mut selected_layers = selected_nodes.selected_layers(network_interface.document_metadata());
|
||||||
|
let selected_layer = selected_layers.next();
|
||||||
|
let has_multiple_selection = selected_layers.next().is_some();
|
||||||
|
|
||||||
let mut widgets = vec![
|
let mut widgets = vec![
|
||||||
PopoverButton::new()
|
PopoverButton::new()
|
||||||
.icon(Some("Node".to_string()))
|
.icon(Some("Node".to_string()))
|
||||||
.tooltip("New Node (Right Click)")
|
.tooltip("New Node (Right Click)")
|
||||||
.popover_layout({
|
.popover_layout({
|
||||||
let node_chooser = NodeCatalog::new()
|
// Showing only compatible types
|
||||||
.on_update(move |node_type| {
|
let compatible_type = match (selection_includes_layers, has_multiple_selection, selected_layer) {
|
||||||
let node_id = NodeId::new();
|
(true, false, Some(layer)) => {
|
||||||
|
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface);
|
||||||
|
let node_type = graph_layer.horizontal_layer_flow().nth(1);
|
||||||
|
if let Some(node_id) = node_type {
|
||||||
|
let (output_type, _) = network_interface.output_type(&node_id, 0, &[]);
|
||||||
|
Some(format!("type:{}", output_type.nested_type()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
Message::Batched(Box::new([
|
let single_layer_selected = selection_includes_layers && !has_multiple_selection;
|
||||||
NodeGraphMessage::CreateNodeFromContextMenu {
|
|
||||||
node_id: Some(node_id),
|
let mut node_chooser = NodeCatalog::new();
|
||||||
|
node_chooser.intial_search = compatible_type.unwrap_or("".to_string());
|
||||||
|
|
||||||
|
let node_chooser = node_chooser
|
||||||
|
.on_update(move |node_type| {
|
||||||
|
if let (true, Some(layer)) = (single_layer_selected, selected_layer) {
|
||||||
|
NodeGraphMessage::CreateNodeInLayerWithTransaction {
|
||||||
node_type: node_type.clone(),
|
node_type: node_type.clone(),
|
||||||
xy: None,
|
layer: LayerNodeIdentifier::new_unchecked(layer.to_node()),
|
||||||
add_transaction: true,
|
|
||||||
}
|
}
|
||||||
.into(),
|
.into()
|
||||||
NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(),
|
} else {
|
||||||
]))
|
let node_id = NodeId::new();
|
||||||
|
Message::Batched {
|
||||||
|
messages: Box::new([
|
||||||
|
NodeGraphMessage::CreateNodeFromContextMenu {
|
||||||
|
node_id: Some(node_id),
|
||||||
|
node_type: node_type.clone(),
|
||||||
|
xy: None,
|
||||||
|
add_transaction: true,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
NodeGraphMessage::SelectedNodesSet { nodes: vec![node_id] }.into(),
|
||||||
|
]),
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.widget_holder();
|
.widget_holder();
|
||||||
vec![LayoutGroup::Row { widgets: vec![node_chooser] }]
|
vec![LayoutGroup::Row { widgets: vec![node_chooser] }]
|
||||||
@@ -2008,6 +2265,12 @@ impl NodeGraphMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The same layer/node may appear several times. Sort and dedup them for a stable ordering.
|
||||||
|
layers.sort();
|
||||||
|
layers.dedup();
|
||||||
|
nodes.sort();
|
||||||
|
nodes.dedup();
|
||||||
|
|
||||||
// Next, we decide what to display based on the number of layers and nodes selected
|
// Next, we decide what to display based on the number of layers and nodes selected
|
||||||
match *layers.as_slice() {
|
match *layers.as_slice() {
|
||||||
// If no layers are selected, show properties for all selected nodes
|
// If no layers are selected, show properties for all selected nodes
|
||||||
@@ -2113,7 +2376,22 @@ impl NodeGraphMessageHandler {
|
|||||||
.icon(Some("Node".to_string()))
|
.icon(Some("Node".to_string()))
|
||||||
.tooltip("Add an operation to the end of this layer's chain of nodes")
|
.tooltip("Add an operation to the end of this layer's chain of nodes")
|
||||||
.popover_layout({
|
.popover_layout({
|
||||||
let node_chooser = NodeCatalog::new()
|
let layer_identifier = LayerNodeIdentifier::new(layer, &context.network_interface);
|
||||||
|
let compatible_type = {
|
||||||
|
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer_identifier, &context.network_interface);
|
||||||
|
let node_type = graph_layer.horizontal_layer_flow().nth(1);
|
||||||
|
if let Some(node_id) = node_type {
|
||||||
|
let (output_type, _) = context.network_interface.output_type(&node_id, 0, &[]);
|
||||||
|
Some(format!("type:{}", output_type.nested_type()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut node_chooser = NodeCatalog::new();
|
||||||
|
node_chooser.intial_search = compatible_type.unwrap_or("".to_string());
|
||||||
|
|
||||||
|
let node_chooser = node_chooser
|
||||||
.on_update(move |node_type| {
|
.on_update(move |node_type| {
|
||||||
NodeGraphMessage::CreateNodeInLayerWithTransaction {
|
NodeGraphMessage::CreateNodeInLayerWithTransaction {
|
||||||
node_type: node_type.clone(),
|
node_type: node_type.clone(),
|
||||||
@@ -2364,19 +2642,19 @@ impl NodeGraphMessageHandler {
|
|||||||
let mut ancestors_of_selected = HashSet::new();
|
let mut ancestors_of_selected = HashSet::new();
|
||||||
let mut descendants_of_selected = HashSet::new();
|
let mut descendants_of_selected = HashSet::new();
|
||||||
for selected_layer in &selected_layers {
|
for selected_layer in &selected_layers {
|
||||||
for ancestor in LayerNodeIdentifier::new(*selected_layer, network_interface, &[]).ancestors(network_interface.document_metadata()) {
|
for ancestor in LayerNodeIdentifier::new(*selected_layer, network_interface).ancestors(network_interface.document_metadata()) {
|
||||||
if ancestor != LayerNodeIdentifier::ROOT_PARENT && ancestor.to_node() != *selected_layer {
|
if ancestor != LayerNodeIdentifier::ROOT_PARENT && ancestor.to_node() != *selected_layer {
|
||||||
ancestors_of_selected.insert(ancestor.to_node());
|
ancestors_of_selected.insert(ancestor.to_node());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for descendant in LayerNodeIdentifier::new(*selected_layer, network_interface, &[]).descendants(network_interface.document_metadata()) {
|
for descendant in LayerNodeIdentifier::new(*selected_layer, network_interface).descendants(network_interface.document_metadata()) {
|
||||||
descendants_of_selected.insert(descendant.to_node());
|
descendants_of_selected.insert(descendant.to_node());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (&node_id, node_metadata) in &network_interface.document_network_metadata().persistent_metadata.node_metadata {
|
for (&node_id, node_metadata) in &network_interface.document_network_metadata().persistent_metadata.node_metadata {
|
||||||
if node_metadata.persistent_metadata.is_layer() {
|
if node_metadata.persistent_metadata.is_layer() {
|
||||||
let layer = LayerNodeIdentifier::new(node_id, network_interface, &[]);
|
let layer = LayerNodeIdentifier::new(node_id, network_interface);
|
||||||
|
|
||||||
let children_allowed =
|
let children_allowed =
|
||||||
// The layer has other layers as children along the secondary input's horizontal flow
|
// The layer has other layers as children along the secondary input's horizontal flow
|
||||||
@@ -2557,6 +2835,7 @@ impl Default for NodeGraphMessageHandler {
|
|||||||
node_has_moved_in_drag: false,
|
node_has_moved_in_drag: false,
|
||||||
shift_without_push: false,
|
shift_without_push: false,
|
||||||
box_selection_start: None,
|
box_selection_start: None,
|
||||||
|
drag_start_chain_nodes: Vec::new(),
|
||||||
selection_before_pointer_down: Vec::new(),
|
selection_before_pointer_down: Vec::new(),
|
||||||
disconnecting: None,
|
disconnecting: None,
|
||||||
initial_disconnecting: false,
|
initial_disconnecting: false,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::Inpu
|
|||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use choice::enum_choice;
|
use choice::enum_choice;
|
||||||
use dyn_any::DynAny;
|
use dyn_any::DynAny;
|
||||||
use glam::{DAffine2, DVec2, IVec2, UVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use graph_craft::Type;
|
use graph_craft::Type;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
|
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput};
|
||||||
@@ -20,8 +20,8 @@ use graphene_std::raster::{
|
|||||||
SelectiveColorChoice,
|
SelectiveColorChoice,
|
||||||
};
|
};
|
||||||
use graphene_std::raster_types::{CPU, GPU, RasterDataTable};
|
use graphene_std::raster_types::{CPU, GPU, RasterDataTable};
|
||||||
use graphene_std::text::Font;
|
use graphene_std::text::{Font, TextAlign};
|
||||||
use graphene_std::transform::{Footprint, ReferencePoint};
|
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
|
||||||
use graphene_std::vector::VectorDataTable;
|
use graphene_std::vector::VectorDataTable;
|
||||||
use graphene_std::vector::misc::GridType;
|
use graphene_std::vector::misc::GridType;
|
||||||
use graphene_std::vector::misc::{ArcType, MergeByDistanceAlgorithm};
|
use graphene_std::vector::misc::{ArcType, MergeByDistanceAlgorithm};
|
||||||
@@ -59,13 +59,13 @@ pub fn expose_widget(node_id: NodeId, index: usize, data_type: FrontendGraphData
|
|||||||
} else {
|
} else {
|
||||||
"Expose this parameter as a node input in the graph"
|
"Expose this parameter as a node input in the graph"
|
||||||
})
|
})
|
||||||
.on_update(move |_parameter| {
|
.on_update(move |_parameter| Message::Batched {
|
||||||
Message::Batched(Box::new([NodeGraphMessage::ExposeInput {
|
messages: Box::new([NodeGraphMessage::ExposeInput {
|
||||||
input_connector: InputConnector::node(node_id, index),
|
input_connector: InputConnector::node(node_id, index),
|
||||||
set_to_exposed: !exposed,
|
set_to_exposed: !exposed,
|
||||||
start_transaction: true,
|
start_transaction: true,
|
||||||
}
|
}
|
||||||
.into()]))
|
.into()]),
|
||||||
})
|
})
|
||||||
.widget_holder()
|
.widget_holder()
|
||||||
}
|
}
|
||||||
@@ -166,8 +166,7 @@ pub(crate) fn property_from_type(
|
|||||||
Some("Fraction") => number_widget(default_info, number_input.mode_range().min(min(0.)).max(max(1.))).into(),
|
Some("Fraction") => number_widget(default_info, number_input.mode_range().min(min(0.)).max(max(1.))).into(),
|
||||||
Some("IntegerCount") => number_widget(default_info, number_input.int().min(min(1.))).into(),
|
Some("IntegerCount") => number_widget(default_info, number_input.int().min(min(1.))).into(),
|
||||||
Some("SeedValue") => number_widget(default_info, number_input.int().min(min(0.))).into(),
|
Some("SeedValue") => number_widget(default_info, number_input.int().min(min(0.))).into(),
|
||||||
Some("Resolution") => coordinate_widget(default_info, "W", "H", unit.unwrap_or(" px"), Some(64.)),
|
Some("PixelSize") => vec2_widget(default_info, "X", "Y", unit.unwrap_or(" px"), None, false),
|
||||||
Some("PixelSize") => coordinate_widget(default_info, "X", "Y", unit.unwrap_or(" px"), None),
|
|
||||||
Some("TextArea") => text_area_widget(default_info).into(),
|
Some("TextArea") => text_area_widget(default_info).into(),
|
||||||
|
|
||||||
// For all other types, use TypeId-based matching
|
// For all other types, use TypeId-based matching
|
||||||
@@ -182,14 +181,13 @@ pub(crate) fn property_from_type(
|
|||||||
Some(x) if x == TypeId::of::<u64>() => number_widget(default_info, number_input.int().min(min(0.))).into(),
|
Some(x) if x == TypeId::of::<u64>() => number_widget(default_info, number_input.int().min(min(0.))).into(),
|
||||||
Some(x) if x == TypeId::of::<bool>() => bool_widget(default_info, CheckboxInput::default()).into(),
|
Some(x) if x == TypeId::of::<bool>() => bool_widget(default_info, CheckboxInput::default()).into(),
|
||||||
Some(x) if x == TypeId::of::<String>() => text_widget(default_info).into(),
|
Some(x) if x == TypeId::of::<String>() => text_widget(default_info).into(),
|
||||||
Some(x) if x == TypeId::of::<DVec2>() => coordinate_widget(default_info, "X", "Y", "", None),
|
Some(x) if x == TypeId::of::<DVec2>() => vec2_widget(default_info, "X", "Y", "", None, false),
|
||||||
Some(x) if x == TypeId::of::<UVec2>() => coordinate_widget(default_info, "X", "Y", "", Some(0.)),
|
Some(x) if x == TypeId::of::<DAffine2>() => transform_widget(default_info, &mut extra_widgets),
|
||||||
Some(x) if x == TypeId::of::<IVec2>() => coordinate_widget(default_info, "X", "Y", "", None),
|
|
||||||
// ==========================
|
// ==========================
|
||||||
// PRIMITIVE COLLECTION TYPES
|
// PRIMITIVE COLLECTION TYPES
|
||||||
// ==========================
|
// ==========================
|
||||||
Some(x) if x == TypeId::of::<Vec<f64>>() => array_of_number_widget(default_info, TextInput::default()).into(),
|
Some(x) if x == TypeId::of::<Vec<f64>>() => array_of_number_widget(default_info, TextInput::default()).into(),
|
||||||
Some(x) if x == TypeId::of::<Vec<DVec2>>() => array_of_coordinates_widget(default_info, TextInput::default()).into(),
|
Some(x) if x == TypeId::of::<Vec<DVec2>>() => array_of_vec2_widget(default_info, TextInput::default()).into(),
|
||||||
// ====================
|
// ====================
|
||||||
// GRAPHICAL DATA TYPES
|
// GRAPHICAL DATA TYPES
|
||||||
// ====================
|
// ====================
|
||||||
@@ -231,6 +229,7 @@ pub(crate) fn property_from_type(
|
|||||||
Some(x) if x == TypeId::of::<StrokeAlign>() => enum_choice::<StrokeAlign>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<StrokeAlign>() => enum_choice::<StrokeAlign>().for_socket(default_info).property_row(),
|
||||||
Some(x) if x == TypeId::of::<PaintOrder>() => enum_choice::<PaintOrder>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<PaintOrder>() => enum_choice::<PaintOrder>().for_socket(default_info).property_row(),
|
||||||
Some(x) if x == TypeId::of::<ArcType>() => enum_choice::<ArcType>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<ArcType>() => enum_choice::<ArcType>().for_socket(default_info).property_row(),
|
||||||
|
Some(x) if x == TypeId::of::<TextAlign>() => enum_choice::<TextAlign>().for_socket(default_info).property_row(),
|
||||||
Some(x) if x == TypeId::of::<MergeByDistanceAlgorithm>() => enum_choice::<MergeByDistanceAlgorithm>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<MergeByDistanceAlgorithm>() => enum_choice::<MergeByDistanceAlgorithm>().for_socket(default_info).property_row(),
|
||||||
Some(x) if x == TypeId::of::<PointSpacingType>() => enum_choice::<PointSpacingType>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<PointSpacingType>() => enum_choice::<PointSpacingType>().for_socket(default_info).property_row(),
|
||||||
Some(x) if x == TypeId::of::<BooleanOperation>() => enum_choice::<BooleanOperation>().for_socket(default_info).property_row(),
|
Some(x) if x == TypeId::of::<BooleanOperation>() => enum_choice::<BooleanOperation>().for_socket(default_info).property_row(),
|
||||||
@@ -487,6 +486,10 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
|
|||||||
resolution_widgets.push(
|
resolution_widgets.push(
|
||||||
NumberInput::new(Some((footprint.resolution.as_dvec2() / bounds).x * 100.))
|
NumberInput::new(Some((footprint.resolution.as_dvec2() / bounds).x * 100.))
|
||||||
.label("Resolution")
|
.label("Resolution")
|
||||||
|
.mode_range()
|
||||||
|
.min(0.)
|
||||||
|
.range_min(Some(1.))
|
||||||
|
.range_max(Some(100.))
|
||||||
.unit("%")
|
.unit("%")
|
||||||
.on_update(update_value(
|
.on_update(update_value(
|
||||||
move |x: &NumberInput| {
|
move |x: &NumberInput| {
|
||||||
@@ -513,7 +516,127 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
|
|||||||
last.clone()
|
last.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>) -> LayoutGroup {
|
pub fn transform_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widgets: &mut Vec<LayoutGroup>) -> LayoutGroup {
|
||||||
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
|
let mut location_widgets = start_widgets(parameter_widgets_info);
|
||||||
|
location_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
|
|
||||||
|
let mut rotation_widgets = vec![TextLabel::new("").widget_holder()];
|
||||||
|
add_blank_assist(&mut rotation_widgets);
|
||||||
|
rotation_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
|
|
||||||
|
let mut scale_widgets = vec![TextLabel::new("").widget_holder()];
|
||||||
|
add_blank_assist(&mut scale_widgets);
|
||||||
|
scale_widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||||
|
|
||||||
|
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||||
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
|
return Vec::new().into();
|
||||||
|
};
|
||||||
|
|
||||||
|
let widgets = if let Some(&TaggedValue::DAffine2(transform)) = input.as_non_exposed_value() {
|
||||||
|
let translation = transform.translation;
|
||||||
|
let rotation = transform.decompose_rotation();
|
||||||
|
let scale = transform.decompose_scale();
|
||||||
|
|
||||||
|
location_widgets.extend_from_slice(&[
|
||||||
|
NumberInput::new(Some(translation.x))
|
||||||
|
.label("X")
|
||||||
|
.unit(" px")
|
||||||
|
.on_update(update_value(
|
||||||
|
move |x: &NumberInput| {
|
||||||
|
let mut transform = transform;
|
||||||
|
transform.translation.x = x.value.unwrap_or(transform.translation.x);
|
||||||
|
TaggedValue::DAffine2(transform)
|
||||||
|
},
|
||||||
|
node_id,
|
||||||
|
index,
|
||||||
|
))
|
||||||
|
.on_commit(commit_value)
|
||||||
|
.widget_holder(),
|
||||||
|
Separator::new(SeparatorType::Related).widget_holder(),
|
||||||
|
NumberInput::new(Some(translation.y))
|
||||||
|
.label("Y")
|
||||||
|
.unit(" px")
|
||||||
|
.on_update(update_value(
|
||||||
|
move |y: &NumberInput| {
|
||||||
|
let mut transform = transform;
|
||||||
|
transform.translation.y = y.value.unwrap_or(transform.translation.y);
|
||||||
|
TaggedValue::DAffine2(transform)
|
||||||
|
},
|
||||||
|
node_id,
|
||||||
|
index,
|
||||||
|
))
|
||||||
|
.on_commit(commit_value)
|
||||||
|
.widget_holder(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
rotation_widgets.extend_from_slice(&[NumberInput::new(Some(rotation.to_degrees()))
|
||||||
|
.unit("°")
|
||||||
|
.mode(NumberInputMode::Range)
|
||||||
|
.range_min(Some(-180.))
|
||||||
|
.range_max(Some(180.))
|
||||||
|
.on_update(update_value(
|
||||||
|
move |r: &NumberInput| {
|
||||||
|
let transform = DAffine2::from_scale_angle_translation(scale, r.value.map(|r| r.to_radians()).unwrap_or(rotation), translation);
|
||||||
|
TaggedValue::DAffine2(transform)
|
||||||
|
},
|
||||||
|
node_id,
|
||||||
|
index,
|
||||||
|
))
|
||||||
|
.on_commit(commit_value)
|
||||||
|
.widget_holder()]);
|
||||||
|
|
||||||
|
scale_widgets.extend_from_slice(&[
|
||||||
|
NumberInput::new(Some(scale.x))
|
||||||
|
.label("W")
|
||||||
|
.unit("x")
|
||||||
|
.on_update(update_value(
|
||||||
|
move |w: &NumberInput| {
|
||||||
|
let transform = DAffine2::from_scale_angle_translation(DVec2::new(w.value.unwrap_or(scale.x), scale.y), rotation, translation);
|
||||||
|
TaggedValue::DAffine2(transform)
|
||||||
|
},
|
||||||
|
node_id,
|
||||||
|
index,
|
||||||
|
))
|
||||||
|
.on_commit(commit_value)
|
||||||
|
.widget_holder(),
|
||||||
|
Separator::new(SeparatorType::Related).widget_holder(),
|
||||||
|
NumberInput::new(Some(scale.y))
|
||||||
|
.label("H")
|
||||||
|
.unit("x")
|
||||||
|
.on_update(update_value(
|
||||||
|
move |h: &NumberInput| {
|
||||||
|
let transform = DAffine2::from_scale_angle_translation(DVec2::new(scale.x, h.value.unwrap_or(scale.y)), rotation, translation);
|
||||||
|
TaggedValue::DAffine2(transform)
|
||||||
|
},
|
||||||
|
node_id,
|
||||||
|
index,
|
||||||
|
))
|
||||||
|
.on_commit(commit_value)
|
||||||
|
.widget_holder(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
vec![
|
||||||
|
LayoutGroup::Row { widgets: location_widgets },
|
||||||
|
LayoutGroup::Row { widgets: rotation_widgets },
|
||||||
|
LayoutGroup::Row { widgets: scale_widgets },
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
vec![LayoutGroup::Row { widgets: location_widgets }]
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some((last, rest)) = widgets.split_last() {
|
||||||
|
*extra_widgets = rest.to_vec();
|
||||||
|
last.clone()
|
||||||
|
} else {
|
||||||
|
LayoutGroup::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vec2_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &str, unit: &str, min: Option<f64>, is_integer: bool) -> LayoutGroup {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
let mut widgets = start_widgets(parameter_widgets_info);
|
let mut widgets = start_widgets(parameter_widgets_info);
|
||||||
@@ -532,6 +655,7 @@ pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str,
|
|||||||
.unit(unit)
|
.unit(unit)
|
||||||
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
|
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
|
||||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||||
|
.is_integer(is_integer)
|
||||||
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), dvec2.y)), node_id, index))
|
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), dvec2.y)), node_id, index))
|
||||||
.on_commit(commit_value)
|
.on_commit(commit_value)
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
@@ -541,63 +665,12 @@ pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str,
|
|||||||
.unit(unit)
|
.unit(unit)
|
||||||
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
|
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
|
||||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||||
|
.is_integer(is_integer)
|
||||||
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(dvec2.x, input.value.unwrap())), node_id, index))
|
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(dvec2.x, input.value.unwrap())), node_id, index))
|
||||||
.on_commit(commit_value)
|
.on_commit(commit_value)
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
Some(&TaggedValue::IVec2(ivec2)) => {
|
|
||||||
let update_x = move |input: &NumberInput| TaggedValue::IVec2(IVec2::new(input.value.unwrap() as i32, ivec2.y));
|
|
||||||
let update_y = move |input: &NumberInput| TaggedValue::IVec2(IVec2::new(ivec2.x, input.value.unwrap() as i32));
|
|
||||||
widgets.extend_from_slice(&[
|
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
|
||||||
NumberInput::new(Some(ivec2.x as f64))
|
|
||||||
.int()
|
|
||||||
.label(x)
|
|
||||||
.unit(unit)
|
|
||||||
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
|
|
||||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
|
||||||
.on_update(update_value(update_x, node_id, index))
|
|
||||||
.on_commit(commit_value)
|
|
||||||
.widget_holder(),
|
|
||||||
Separator::new(SeparatorType::Related).widget_holder(),
|
|
||||||
NumberInput::new(Some(ivec2.y as f64))
|
|
||||||
.int()
|
|
||||||
.label(y)
|
|
||||||
.unit(unit)
|
|
||||||
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
|
|
||||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
|
||||||
.on_update(update_value(update_y, node_id, index))
|
|
||||||
.on_commit(commit_value)
|
|
||||||
.widget_holder(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
Some(&TaggedValue::UVec2(uvec2)) => {
|
|
||||||
let update_x = move |input: &NumberInput| TaggedValue::UVec2(UVec2::new(input.value.unwrap() as u32, uvec2.y));
|
|
||||||
let update_y = move |input: &NumberInput| TaggedValue::UVec2(UVec2::new(uvec2.x, input.value.unwrap() as u32));
|
|
||||||
widgets.extend_from_slice(&[
|
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
|
||||||
NumberInput::new(Some(uvec2.x as f64))
|
|
||||||
.int()
|
|
||||||
.label(x)
|
|
||||||
.unit(unit)
|
|
||||||
.min(min.unwrap_or(0.))
|
|
||||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
|
||||||
.on_update(update_value(update_x, node_id, index))
|
|
||||||
.on_commit(commit_value)
|
|
||||||
.widget_holder(),
|
|
||||||
Separator::new(SeparatorType::Related).widget_holder(),
|
|
||||||
NumberInput::new(Some(uvec2.y as f64))
|
|
||||||
.int()
|
|
||||||
.label(y)
|
|
||||||
.unit(unit)
|
|
||||||
.min(min.unwrap_or(0.))
|
|
||||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
|
||||||
.on_update(update_value(update_y, node_id, index))
|
|
||||||
.on_commit(commit_value)
|
|
||||||
.widget_holder(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
Some(&TaggedValue::F64(value)) => {
|
Some(&TaggedValue::F64(value)) => {
|
||||||
widgets.extend_from_slice(&[
|
widgets.extend_from_slice(&[
|
||||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||||
@@ -606,6 +679,7 @@ pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str,
|
|||||||
.unit(unit)
|
.unit(unit)
|
||||||
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
|
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
|
||||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||||
|
.is_integer(is_integer)
|
||||||
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), value)), node_id, index))
|
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), value)), node_id, index))
|
||||||
.on_commit(commit_value)
|
.on_commit(commit_value)
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
@@ -615,6 +689,7 @@ pub fn coordinate_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str,
|
|||||||
.unit(unit)
|
.unit(unit)
|
||||||
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
|
.min(min.unwrap_or(-((1_u64 << f64::MANTISSA_DIGITS) as f64)))
|
||||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||||
|
.is_integer(is_integer)
|
||||||
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(value, input.value.unwrap())), node_id, index))
|
.on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(value, input.value.unwrap())), node_id, index))
|
||||||
.on_commit(commit_value)
|
.on_commit(commit_value)
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
@@ -658,7 +733,7 @@ pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text
|
|||||||
widgets
|
widgets
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn array_of_coordinates_widget(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec<WidgetHolder> {
|
pub fn array_of_vec2_widget(parameter_widgets_info: ParameterWidgetsInfo, text_props: TextInput) -> Vec<WidgetHolder> {
|
||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = parameter_widgets_info;
|
||||||
|
|
||||||
let mut widgets = start_widgets(parameter_widgets_info);
|
let mut widgets = start_widgets(parameter_widgets_info);
|
||||||
@@ -1184,7 +1259,7 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
if let Some(&TaggedValue::GridType(grid_type)) = grid_type_input.as_non_exposed_value() {
|
if let Some(&TaggedValue::GridType(grid_type)) = grid_type_input.as_non_exposed_value() {
|
||||||
match grid_type {
|
match grid_type {
|
||||||
GridType::Rectangular => {
|
GridType::Rectangular => {
|
||||||
let spacing = coordinate_widget(ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context), "W", "H", " px", Some(0.));
|
let spacing = vec2_widget(ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context), "W", "H", " px", Some(0.), false);
|
||||||
widgets.push(spacing);
|
widgets.push(spacing);
|
||||||
}
|
}
|
||||||
GridType::Isometric => {
|
GridType::Isometric => {
|
||||||
@@ -1194,7 +1269,7 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
NumberInput::default().label("H").min(0.).unit(" px"),
|
NumberInput::default().label("H").min(0.).unit(" px"),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
let angles = coordinate_widget(ParameterWidgetsInfo::new(node_id, AnglesInput::INDEX, true, context), "", "", "°", None);
|
let angles = vec2_widget(ParameterWidgetsInfo::new(node_id, AnglesInput::INDEX, true, context), "", "", "°", None, false);
|
||||||
widgets.extend([spacing, angles]);
|
widgets.extend([spacing, angles]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1313,8 +1388,8 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
|||||||
// Uniform/individual radio input widget
|
// Uniform/individual radio input widget
|
||||||
let uniform = RadioEntryData::new("Uniform")
|
let uniform = RadioEntryData::new("Uniform")
|
||||||
.label("Uniform")
|
.label("Uniform")
|
||||||
.on_update(move |_| {
|
.on_update(move |_| Message::Batched {
|
||||||
Message::Batched(Box::new([
|
messages: Box::new([
|
||||||
NodeGraphMessage::SetInputValue {
|
NodeGraphMessage::SetInputValue {
|
||||||
node_id,
|
node_id,
|
||||||
input_index: IndividualCornerRadiiInput::INDEX,
|
input_index: IndividualCornerRadiiInput::INDEX,
|
||||||
@@ -1327,13 +1402,13 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
|||||||
value: TaggedValue::F64(uniform_val),
|
value: TaggedValue::F64(uniform_val),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
]))
|
]),
|
||||||
})
|
})
|
||||||
.on_commit(commit_value);
|
.on_commit(commit_value);
|
||||||
let individual = RadioEntryData::new("Individual")
|
let individual = RadioEntryData::new("Individual")
|
||||||
.label("Individual")
|
.label("Individual")
|
||||||
.on_update(move |_| {
|
.on_update(move |_| Message::Batched {
|
||||||
Message::Batched(Box::new([
|
messages: Box::new([
|
||||||
NodeGraphMessage::SetInputValue {
|
NodeGraphMessage::SetInputValue {
|
||||||
node_id,
|
node_id,
|
||||||
input_index: IndividualCornerRadiiInput::INDEX,
|
input_index: IndividualCornerRadiiInput::INDEX,
|
||||||
@@ -1346,7 +1421,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
|||||||
value: TaggedValue::F64Array4(individual_val),
|
value: TaggedValue::F64Array4(individual_val),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
]))
|
]),
|
||||||
})
|
})
|
||||||
.on_commit(commit_value);
|
.on_commit(commit_value);
|
||||||
let radio_input = RadioInput::new(vec![uniform, individual]).selected_index(Some(is_individual as u32)).widget_holder();
|
let radio_input = RadioInput::new(vec![uniform, individual]).selected_index(Some(is_individual as u32)).widget_holder();
|
||||||
@@ -1402,9 +1477,9 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
|||||||
|
|
||||||
pub(crate) fn node_no_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub(crate) fn node_no_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
let text = if context.network_interface.is_layer(&node_id, context.selection_network_path) {
|
let text = if context.network_interface.is_layer(&node_id, context.selection_network_path) {
|
||||||
"Layer has no properties"
|
"Layer has no parameters"
|
||||||
} else {
|
} else {
|
||||||
"Node has no properties"
|
"Node has no parameters"
|
||||||
};
|
};
|
||||||
string_properties(text)
|
string_properties(text)
|
||||||
}
|
}
|
||||||
@@ -1545,8 +1620,8 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
widgets_first_row.push(
|
widgets_first_row.push(
|
||||||
ColorInput::default()
|
ColorInput::default()
|
||||||
.value(fill.clone().into())
|
.value(fill.clone().into())
|
||||||
.on_update(move |x: &ColorInput| {
|
.on_update(move |x: &ColorInput| Message::Batched {
|
||||||
Message::Batched(Box::new([
|
messages: Box::new([
|
||||||
match &fill2 {
|
match &fill2 {
|
||||||
Fill::None => NodeGraphMessage::SetInputValue {
|
Fill::None => NodeGraphMessage::SetInputValue {
|
||||||
node_id,
|
node_id,
|
||||||
@@ -1573,7 +1648,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
value: TaggedValue::Fill(x.value.to_fill(fill2.as_gradient())),
|
value: TaggedValue::Fill(x.value.to_fill(fill2.as_gradient())),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
]))
|
]),
|
||||||
})
|
})
|
||||||
.on_commit(commit_value)
|
.on_commit(commit_value)
|
||||||
.widget_holder(),
|
.widget_holder(),
|
||||||
@@ -1874,7 +1949,7 @@ pub mod choice {
|
|||||||
use super::ParameterWidgetsInfo;
|
use super::ParameterWidgetsInfo;
|
||||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graphene_std::registry::{ChoiceTypeStatic, ChoiceWidgetHint};
|
use graphene_std::choice_type::{ChoiceTypeStatic, ChoiceWidgetHint};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
pub trait WidgetFactory {
|
pub trait WidgetFactory {
|
||||||
@@ -1934,10 +2009,7 @@ pub mod choice {
|
|||||||
.map(|(item, metadata)| {
|
.map(|(item, metadata)| {
|
||||||
let updater = updater_factory();
|
let updater = updater_factory();
|
||||||
let committer = committer_factory();
|
let committer = committer_factory();
|
||||||
MenuListEntry::new(metadata.name.as_ref())
|
MenuListEntry::new(metadata.name).label(metadata.label).on_update(move |_| updater(item)).on_commit(committer)
|
||||||
.label(metadata.label.as_ref())
|
|
||||||
.on_update(move |_| updater(item))
|
|
||||||
.on_commit(committer)
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
@@ -1956,11 +2028,11 @@ pub mod choice {
|
|||||||
.map(|(item, var_meta)| {
|
.map(|(item, var_meta)| {
|
||||||
let updater = updater_factory();
|
let updater = updater_factory();
|
||||||
let committer = committer_factory();
|
let committer = committer_factory();
|
||||||
let entry = RadioEntryData::new(var_meta.name.as_ref()).on_update(move |_| updater(item)).on_commit(committer);
|
let entry = RadioEntryData::new(var_meta.name).on_update(move |_| updater(item)).on_commit(committer);
|
||||||
match (var_meta.icon.as_deref(), var_meta.docstring.as_deref()) {
|
match (var_meta.icon, var_meta.docstring) {
|
||||||
(None, None) => entry.label(var_meta.label.as_ref()),
|
(None, None) => entry.label(var_meta.label),
|
||||||
(None, Some(doc)) => entry.label(var_meta.label.as_ref()).tooltip(doc),
|
(None, Some(doc)) => entry.label(var_meta.label).tooltip(doc),
|
||||||
(Some(icon), None) => entry.icon(icon).tooltip(var_meta.label.as_ref()),
|
(Some(icon), None) => entry.icon(icon).tooltip(var_meta.label),
|
||||||
(Some(icon), Some(doc)) => entry.icon(icon).tooltip(format!("{}\n\n{}", var_meta.label, doc)),
|
(Some(icon), Some(doc)) => entry.icon(icon).tooltip(format!("{}\n\n{}", var_meta.label, doc)),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -23,13 +23,12 @@ impl FrontendGraphDataType {
|
|||||||
TaggedValue::U32(_)
|
TaggedValue::U32(_)
|
||||||
| TaggedValue::U64(_)
|
| TaggedValue::U64(_)
|
||||||
| TaggedValue::F64(_)
|
| TaggedValue::F64(_)
|
||||||
| TaggedValue::UVec2(_)
|
|
||||||
| TaggedValue::IVec2(_)
|
|
||||||
| TaggedValue::DVec2(_)
|
| TaggedValue::DVec2(_)
|
||||||
| TaggedValue::OptionalDVec2(_)
|
| TaggedValue::OptionalDVec2(_)
|
||||||
| TaggedValue::F64Array4(_)
|
| TaggedValue::F64Array4(_)
|
||||||
| TaggedValue::VecF64(_)
|
| TaggedValue::VecF64(_)
|
||||||
| TaggedValue::VecDVec2(_) => Self::Number,
|
| TaggedValue::VecDVec2(_)
|
||||||
|
| TaggedValue::DAffine2(_) => Self::Number,
|
||||||
TaggedValue::GraphicGroup(_) | TaggedValue::GraphicElement(_) => Self::Group, // TODO: Is GraphicElement supposed to be included here?
|
TaggedValue::GraphicGroup(_) | TaggedValue::GraphicElement(_) => Self::Group, // TODO: Is GraphicElement supposed to be included here?
|
||||||
TaggedValue::ArtboardGroup(_) => Self::Artboard,
|
TaggedValue::ArtboardGroup(_) => Self::Artboard,
|
||||||
_ => Self::General,
|
_ => Self::General,
|
||||||
|
|||||||
@@ -2,9 +2,14 @@ pub mod grid_overlays;
|
|||||||
mod overlays_message;
|
mod overlays_message;
|
||||||
mod overlays_message_handler;
|
mod overlays_message_handler;
|
||||||
pub mod utility_functions;
|
pub mod utility_functions;
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub mod utility_types;
|
pub mod utility_types;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub mod utility_types_vello;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub use utility_types_vello as utility_types;
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant};
|
pub use overlays_message::{OverlaysMessage, OverlaysMessageDiscriminant};
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use overlays_message_handler::{OverlaysMessageData, OverlaysMessageHandler};
|
pub use overlays_message_handler::{OverlaysMessageContext, OverlaysMessageHandler};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use super::utility_types::{OverlayProvider, OverlaysVisibilitySettings};
|
|||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
#[derive(ExtractField)]
|
#[derive(ExtractField)]
|
||||||
pub struct OverlaysMessageData<'a> {
|
pub struct OverlaysMessageContext<'a> {
|
||||||
pub visibility_settings: OverlaysVisibilitySettings,
|
pub visibility_settings: OverlaysVisibilitySettings,
|
||||||
pub ipp: &'a InputPreprocessorMessageHandler,
|
pub ipp: &'a InputPreprocessorMessageHandler,
|
||||||
pub device_pixel_ratio: f64,
|
pub device_pixel_ratio: f64,
|
||||||
@@ -18,9 +18,10 @@ pub struct OverlaysMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessageHandler {
|
impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMessageHandler {
|
||||||
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, data: OverlaysMessageData) {
|
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, context: OverlaysMessageContext) {
|
||||||
let OverlaysMessageData { visibility_settings, ipp, .. } = data;
|
let OverlaysMessageContext { visibility_settings, ipp, .. } = context;
|
||||||
|
let device_pixel_ratio = context.device_pixel_ratio;
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
@@ -30,8 +31,6 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessag
|
|||||||
use glam::{DAffine2, DVec2};
|
use glam::{DAffine2, DVec2};
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
|
|
||||||
let device_pixel_ratio = data.device_pixel_ratio;
|
|
||||||
|
|
||||||
let canvas = match &self.canvas {
|
let canvas = match &self.canvas {
|
||||||
Some(canvas) => canvas,
|
Some(canvas) => canvas,
|
||||||
None => {
|
None => {
|
||||||
@@ -40,28 +39,28 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessag
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let context = self.context.get_or_insert_with(|| {
|
let canvas_context = self.context.get_or_insert_with(|| {
|
||||||
let context = canvas.get_context("2d").ok().flatten().expect("Failed to get canvas context");
|
let canvas_context = canvas.get_context("2d").ok().flatten().expect("Failed to get canvas context");
|
||||||
context.dyn_into().expect("Context should be a canvas 2d context")
|
canvas_context.dyn_into().expect("Context should be a canvas 2d context")
|
||||||
});
|
});
|
||||||
|
|
||||||
let size = ipp.viewport_bounds.size().as_uvec2();
|
let size = ipp.viewport_bounds.size().as_uvec2();
|
||||||
|
|
||||||
let [a, b, c, d, e, f] = DAffine2::from_scale(DVec2::splat(device_pixel_ratio)).to_cols_array();
|
let [a, b, c, d, e, f] = DAffine2::from_scale(DVec2::splat(device_pixel_ratio)).to_cols_array();
|
||||||
let _ = context.set_transform(a, b, c, d, e, f);
|
let _ = canvas_context.set_transform(a, b, c, d, e, f);
|
||||||
context.clear_rect(0., 0., ipp.viewport_bounds.size().x, ipp.viewport_bounds.size().y);
|
canvas_context.clear_rect(0., 0., ipp.viewport_bounds.size().x, ipp.viewport_bounds.size().y);
|
||||||
let _ = context.reset_transform();
|
let _ = canvas_context.reset_transform();
|
||||||
|
|
||||||
if visibility_settings.all() {
|
if visibility_settings.all() {
|
||||||
responses.add(DocumentMessage::GridOverlays(OverlayContext {
|
responses.add(DocumentMessage::GridOverlays(OverlayContext {
|
||||||
render_context: context.clone(),
|
render_context: canvas_context.clone(),
|
||||||
size: size.as_dvec2(),
|
size: size.as_dvec2(),
|
||||||
device_pixel_ratio,
|
device_pixel_ratio,
|
||||||
visibility_settings: visibility_settings.clone(),
|
visibility_settings: visibility_settings.clone(),
|
||||||
}));
|
}));
|
||||||
for provider in &self.overlay_providers {
|
for provider in &self.overlay_providers {
|
||||||
responses.add(provider(OverlayContext {
|
responses.add(provider(OverlayContext {
|
||||||
render_context: context.clone(),
|
render_context: canvas_context.clone(),
|
||||||
size: size.as_dvec2(),
|
size: size.as_dvec2(),
|
||||||
device_pixel_ratio,
|
device_pixel_ratio,
|
||||||
visibility_settings: visibility_settings.clone(),
|
visibility_settings: visibility_settings.clone(),
|
||||||
@@ -69,9 +68,39 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(test)]
|
||||||
|
OverlaysMessage::Draw => {}
|
||||||
|
#[cfg(all(not(target_arch = "wasm32"), not(test)))]
|
||||||
OverlaysMessage::Draw => {
|
OverlaysMessage::Draw => {
|
||||||
warn!("Cannot render overlays on non-Wasm targets.\n{responses:?} {visibility_settings:?} {ipp:?}",);
|
use super::utility_types::OverlayContext;
|
||||||
|
use vello::Scene;
|
||||||
|
|
||||||
|
let size = ipp.viewport_bounds.size().as_uvec2();
|
||||||
|
|
||||||
|
let scene = Scene::new();
|
||||||
|
|
||||||
|
if visibility_settings.all() {
|
||||||
|
let overlay_context = OverlayContext {
|
||||||
|
scene,
|
||||||
|
size: size.as_dvec2(),
|
||||||
|
device_pixel_ratio,
|
||||||
|
visibility_settings,
|
||||||
|
};
|
||||||
|
|
||||||
|
responses.add(DocumentMessage::GridOverlays(overlay_context.clone()));
|
||||||
|
|
||||||
|
for provider in &self.overlay_providers {
|
||||||
|
let overlay_context = OverlayContext {
|
||||||
|
scene: Scene::new(),
|
||||||
|
size: size.as_dvec2(),
|
||||||
|
device_pixel_ratio,
|
||||||
|
visibility_settings,
|
||||||
|
};
|
||||||
|
responses.add(provider(overlay_context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Render the Vello scene to a texture and display it
|
||||||
}
|
}
|
||||||
OverlaysMessage::AddProvider(message) => {
|
OverlaysMessage::AddProvider(message) => {
|
||||||
self.overlay_providers.insert(message);
|
self.overlay_providers.insert(message);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use super::utility_functions::overlay_canvas_context;
|
use super::utility_functions::overlay_canvas_context;
|
||||||
use crate::consts::{
|
use crate::consts::{
|
||||||
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER,
|
ARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL,
|
||||||
COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
|
COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE,
|
||||||
|
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, SEGMENT_SELECTED_THICKNESS,
|
||||||
};
|
};
|
||||||
use crate::messages::prelude::Message;
|
use crate::messages::prelude::Message;
|
||||||
use bezier_rs::{Bezier, Subpath};
|
use bezier_rs::{Bezier, Subpath};
|
||||||
@@ -293,6 +294,147 @@ impl OverlayContext {
|
|||||||
self.end_dpi_aware_transform();
|
self.end_dpi_aware_transform();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn dashed_ellipse(
|
||||||
|
&mut self,
|
||||||
|
center: DVec2,
|
||||||
|
radius_x: f64,
|
||||||
|
radius_y: f64,
|
||||||
|
rotation: Option<f64>,
|
||||||
|
start_angle: Option<f64>,
|
||||||
|
end_angle: Option<f64>,
|
||||||
|
counterclockwise: Option<bool>,
|
||||||
|
color_fill: Option<&str>,
|
||||||
|
color_stroke: Option<&str>,
|
||||||
|
dash_width: Option<f64>,
|
||||||
|
dash_gap_width: Option<f64>,
|
||||||
|
dash_offset: Option<f64>,
|
||||||
|
) {
|
||||||
|
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||||
|
let center = center.round();
|
||||||
|
|
||||||
|
self.start_dpi_aware_transform();
|
||||||
|
|
||||||
|
if let Some(dash_width) = dash_width {
|
||||||
|
let dash_gap_width = dash_gap_width.unwrap_or(1.);
|
||||||
|
let array = js_sys::Array::new();
|
||||||
|
array.push(&JsValue::from(dash_width));
|
||||||
|
array.push(&JsValue::from(dash_gap_width));
|
||||||
|
|
||||||
|
if let Some(dash_offset) = dash_offset {
|
||||||
|
if dash_offset != 0. {
|
||||||
|
self.render_context.set_line_dash_offset(dash_offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.render_context
|
||||||
|
.set_line_dash(&JsValue::from(array))
|
||||||
|
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.render_context.begin_path();
|
||||||
|
self.render_context
|
||||||
|
.ellipse_with_anticlockwise(
|
||||||
|
center.x,
|
||||||
|
center.y,
|
||||||
|
radius_x,
|
||||||
|
radius_y,
|
||||||
|
rotation.unwrap_or_default(),
|
||||||
|
start_angle.unwrap_or_default(),
|
||||||
|
end_angle.unwrap_or(TAU),
|
||||||
|
counterclockwise.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
.expect("Failed to draw ellipse");
|
||||||
|
self.render_context.set_stroke_style_str(color_stroke);
|
||||||
|
|
||||||
|
if let Some(fill_color) = color_fill {
|
||||||
|
self.render_context.set_fill_style_str(fill_color);
|
||||||
|
self.render_context.fill();
|
||||||
|
}
|
||||||
|
self.render_context.stroke();
|
||||||
|
|
||||||
|
// Reset the dash pattern back to solid
|
||||||
|
if dash_width.is_some() {
|
||||||
|
self.render_context
|
||||||
|
.set_line_dash(&JsValue::from(js_sys::Array::new()))
|
||||||
|
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
if dash_offset.is_some() && dash_offset != Some(0.) {
|
||||||
|
self.render_context.set_line_dash_offset(0.);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.end_dpi_aware_transform();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dashed_circle(
|
||||||
|
&mut self,
|
||||||
|
position: DVec2,
|
||||||
|
radius: f64,
|
||||||
|
color_fill: Option<&str>,
|
||||||
|
color_stroke: Option<&str>,
|
||||||
|
dash_width: Option<f64>,
|
||||||
|
dash_gap_width: Option<f64>,
|
||||||
|
dash_offset: Option<f64>,
|
||||||
|
transform: Option<DAffine2>,
|
||||||
|
) {
|
||||||
|
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||||
|
let position = position.round();
|
||||||
|
|
||||||
|
self.start_dpi_aware_transform();
|
||||||
|
|
||||||
|
if let Some(transform) = transform {
|
||||||
|
let [a, b, c, d, e, f] = transform.to_cols_array();
|
||||||
|
self.render_context.transform(a, b, c, d, e, f).expect("Failed to transform circle");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(dash_width) = dash_width {
|
||||||
|
let dash_gap_width = dash_gap_width.unwrap_or(1.);
|
||||||
|
let array = js_sys::Array::new();
|
||||||
|
array.push(&JsValue::from(dash_width));
|
||||||
|
array.push(&JsValue::from(dash_gap_width));
|
||||||
|
|
||||||
|
if let Some(dash_offset) = dash_offset {
|
||||||
|
if dash_offset != 0. {
|
||||||
|
self.render_context.set_line_dash_offset(dash_offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.render_context
|
||||||
|
.set_line_dash(&JsValue::from(array))
|
||||||
|
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.render_context.begin_path();
|
||||||
|
self.render_context.arc(position.x, position.y, radius, 0., TAU).expect("Failed to draw the circle");
|
||||||
|
self.render_context.set_stroke_style_str(color_stroke);
|
||||||
|
|
||||||
|
if let Some(fill_color) = color_fill {
|
||||||
|
self.render_context.set_fill_style_str(fill_color);
|
||||||
|
self.render_context.fill();
|
||||||
|
}
|
||||||
|
self.render_context.stroke();
|
||||||
|
|
||||||
|
// Reset the dash pattern back to solid
|
||||||
|
if dash_width.is_some() {
|
||||||
|
self.render_context
|
||||||
|
.set_line_dash(&JsValue::from(js_sys::Array::new()))
|
||||||
|
.map_err(|error| log::warn!("Error drawing dashed line: {:?}", error))
|
||||||
|
.ok();
|
||||||
|
}
|
||||||
|
if dash_offset.is_some() && dash_offset != Some(0.) {
|
||||||
|
self.render_context.set_line_dash_offset(0.);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.end_dpi_aware_transform();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||||
|
self.dashed_circle(position, radius, color_fill, color_stroke, None, None, None, None);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn manipulator_handle(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
pub fn manipulator_handle(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
||||||
self.start_dpi_aware_transform();
|
self.start_dpi_aware_transform();
|
||||||
|
|
||||||
@@ -318,6 +460,42 @@ impl OverlayContext {
|
|||||||
self.square(position, None, Some(color_fill), Some(color_stroke));
|
self.square(position, None, Some(color_fill), Some(color_stroke));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) {
|
||||||
|
self.start_dpi_aware_transform();
|
||||||
|
|
||||||
|
let position = position.round() - DVec2::splat(0.5);
|
||||||
|
|
||||||
|
self.render_context.begin_path();
|
||||||
|
self.render_context
|
||||||
|
.arc(position.x, position.y, (MANIPULATOR_GROUP_MARKER_SIZE + 2.) / 2., 0., TAU)
|
||||||
|
.expect("Failed to draw the circle");
|
||||||
|
|
||||||
|
self.render_context.set_fill_style_str(COLOR_OVERLAY_BLUE_50);
|
||||||
|
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50);
|
||||||
|
self.render_context.fill();
|
||||||
|
self.render_context.stroke();
|
||||||
|
|
||||||
|
self.render_context.begin_path();
|
||||||
|
self.render_context
|
||||||
|
.arc(position.x, position.y, MANIPULATOR_GROUP_MARKER_SIZE / 2., 0., TAU)
|
||||||
|
.expect("Failed to draw the circle");
|
||||||
|
|
||||||
|
let color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
|
||||||
|
|
||||||
|
self.render_context.set_fill_style_str(color_fill);
|
||||||
|
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE);
|
||||||
|
self.render_context.fill();
|
||||||
|
self.render_context.stroke();
|
||||||
|
|
||||||
|
self.end_dpi_aware_transform();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hover_manipulator_anchor(&mut self, position: DVec2, selected: bool) {
|
||||||
|
self.square(position, Some(MANIPULATOR_GROUP_MARKER_SIZE + 2.), Some(COLOR_OVERLAY_BLUE_50), Some(COLOR_OVERLAY_BLUE_50));
|
||||||
|
let color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
|
||||||
|
self.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE));
|
||||||
|
}
|
||||||
|
|
||||||
/// Transforms the canvas context to adjust for DPI scaling
|
/// Transforms the canvas context to adjust for DPI scaling
|
||||||
///
|
///
|
||||||
/// Overwrites all existing tranforms. This operation can be reversed with [`Self::reset_transform`].
|
/// Overwrites all existing tranforms. This operation can be reversed with [`Self::reset_transform`].
|
||||||
@@ -349,6 +527,7 @@ impl OverlayContext {
|
|||||||
self.render_context.rect(corner.x, corner.y, size, size);
|
self.render_context.rect(corner.x, corner.y, size, size);
|
||||||
self.render_context.set_fill_style_str(color_fill);
|
self.render_context.set_fill_style_str(color_fill);
|
||||||
self.render_context.set_stroke_style_str(color_stroke);
|
self.render_context.set_stroke_style_str(color_stroke);
|
||||||
|
self.render_context.set_line_width(1.);
|
||||||
self.render_context.fill();
|
self.render_context.fill();
|
||||||
self.render_context.stroke();
|
self.render_context.stroke();
|
||||||
|
|
||||||
@@ -372,23 +551,6 @@ impl OverlayContext {
|
|||||||
self.end_dpi_aware_transform();
|
self.end_dpi_aware_transform();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
|
||||||
let color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);
|
|
||||||
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
|
||||||
let position = position.round();
|
|
||||||
|
|
||||||
self.start_dpi_aware_transform();
|
|
||||||
|
|
||||||
self.render_context.begin_path();
|
|
||||||
self.render_context.arc(position.x, position.y, radius, 0., TAU).expect("Failed to draw the circle");
|
|
||||||
self.render_context.set_fill_style_str(color_fill);
|
|
||||||
self.render_context.set_stroke_style_str(color_stroke);
|
|
||||||
self.render_context.fill();
|
|
||||||
self.render_context.stroke();
|
|
||||||
|
|
||||||
self.end_dpi_aware_transform();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_arc(&mut self, center: DVec2, radius: f64, start_from: f64, end_at: f64) {
|
pub fn draw_arc(&mut self, center: DVec2, radius: f64, start_from: f64, end_at: f64) {
|
||||||
let segments = ((end_at - start_from).abs() / (std::f64::consts::PI / 4.)).ceil() as usize;
|
let segments = ((end_at - start_from).abs() / (std::f64::consts::PI / 4.)).ceil() as usize;
|
||||||
let step = (end_at - start_from) / segments as f64;
|
let step = (end_at - start_from) / segments as f64;
|
||||||
@@ -421,6 +583,12 @@ impl OverlayContext {
|
|||||||
self.render_context.stroke();
|
self.render_context.stroke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
||||||
|
let end_point1 = pivot + bold_radius * DVec2::from_angle(angle + offset_angle);
|
||||||
|
self.line(pivot, end_point1, None, None);
|
||||||
|
self.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn draw_angle(&mut self, pivot: DVec2, radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
pub fn draw_angle(&mut self, pivot: DVec2, radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
||||||
let end_point1 = pivot + radius * DVec2::from_angle(angle + offset_angle);
|
let end_point1 = pivot + radius * DVec2::from_angle(angle + offset_angle);
|
||||||
let end_point2 = pivot + radius * DVec2::from_angle(offset_angle);
|
let end_point2 = pivot + radius * DVec2::from_angle(offset_angle);
|
||||||
@@ -582,6 +750,12 @@ impl OverlayContext {
|
|||||||
self.end_dpi_aware_transform();
|
self.end_dpi_aware_transform();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {
|
||||||
|
self.manipulator_handle(end_point_position, true, None);
|
||||||
|
self.draw_arc_gizmo_angle(pivot, bold_radius, ARC_SWEEP_GIZMO_RADIUS, offset_angle, angle.to_radians());
|
||||||
|
self.text(&text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||||
|
}
|
||||||
|
|
||||||
/// Used by the Pen and Path tools to outline the path of the shape.
|
/// Used by the Pen and Path tools to outline the path of the shape.
|
||||||
pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
|
pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
|
||||||
self.start_dpi_aware_transform();
|
self.start_dpi_aware_transform();
|
||||||
@@ -620,7 +794,7 @@ impl OverlayContext {
|
|||||||
self.render_context.begin_path();
|
self.render_context.begin_path();
|
||||||
self.bezier_command(bezier, transform, true);
|
self.bezier_command(bezier, transform, true);
|
||||||
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE);
|
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE);
|
||||||
self.render_context.set_line_width(4.);
|
self.render_context.set_line_width(SEGMENT_SELECTED_THICKNESS);
|
||||||
self.render_context.stroke();
|
self.render_context.stroke();
|
||||||
|
|
||||||
self.render_context.set_line_width(1.);
|
self.render_context.set_line_width(1.);
|
||||||
@@ -631,12 +805,10 @@ impl OverlayContext {
|
|||||||
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||||
self.start_dpi_aware_transform();
|
self.start_dpi_aware_transform();
|
||||||
|
|
||||||
let color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
|
|
||||||
|
|
||||||
self.render_context.begin_path();
|
self.render_context.begin_path();
|
||||||
self.bezier_command(bezier, transform, true);
|
self.bezier_command(bezier, transform, true);
|
||||||
self.render_context.set_stroke_style_str(&color);
|
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50);
|
||||||
self.render_context.set_line_width(4.);
|
self.render_context.set_line_width(SEGMENT_SELECTED_THICKNESS);
|
||||||
self.render_context.stroke();
|
self.render_context.stroke();
|
||||||
|
|
||||||
self.render_context.set_line_width(1.);
|
self.render_context.set_line_width(1.);
|
||||||
@@ -727,6 +899,7 @@ impl OverlayContext {
|
|||||||
|
|
||||||
let color = color.unwrap_or(COLOR_OVERLAY_BLUE);
|
let color = color.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||||
self.render_context.set_stroke_style_str(color);
|
self.render_context.set_stroke_style_str(color);
|
||||||
|
self.render_context.set_line_width(1.);
|
||||||
self.render_context.stroke();
|
self.render_context.stroke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,799 @@
|
|||||||
|
use crate::consts::{
|
||||||
|
ARC_SWEEP_GIZMO_RADIUS, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL,
|
||||||
|
COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE,
|
||||||
|
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
|
||||||
|
};
|
||||||
|
use crate::messages::prelude::Message;
|
||||||
|
use bezier_rs::{Bezier, Subpath};
|
||||||
|
use core::borrow::Borrow;
|
||||||
|
use core::f64::consts::{FRAC_PI_2, PI, TAU};
|
||||||
|
use glam::{DAffine2, DVec2};
|
||||||
|
use graphene_std::Color;
|
||||||
|
use graphene_std::math::quad::Quad;
|
||||||
|
use graphene_std::vector::click_target::ClickTargetType;
|
||||||
|
use graphene_std::vector::{PointId, SegmentId, VectorData};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use vello::Scene;
|
||||||
|
use vello::kurbo::{self, BezPath};
|
||||||
|
use vello::peniko;
|
||||||
|
|
||||||
|
pub type OverlayProvider = fn(OverlayContext) -> Message;
|
||||||
|
|
||||||
|
pub fn empty_provider() -> OverlayProvider {
|
||||||
|
|_| Message::NoOp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Types of overlays used by DocumentMessage to enable/disable select group of overlays in the frontend
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
|
pub enum OverlaysType {
|
||||||
|
ArtboardName,
|
||||||
|
CompassRose,
|
||||||
|
QuickMeasurement,
|
||||||
|
TransformMeasurement,
|
||||||
|
TransformCage,
|
||||||
|
HoverOutline,
|
||||||
|
SelectionOutline,
|
||||||
|
Pivot,
|
||||||
|
Origin,
|
||||||
|
Path,
|
||||||
|
Anchors,
|
||||||
|
Handles,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct OverlaysVisibilitySettings {
|
||||||
|
pub all: bool,
|
||||||
|
pub artboard_name: bool,
|
||||||
|
pub compass_rose: bool,
|
||||||
|
pub quick_measurement: bool,
|
||||||
|
pub transform_measurement: bool,
|
||||||
|
pub transform_cage: bool,
|
||||||
|
pub hover_outline: bool,
|
||||||
|
pub selection_outline: bool,
|
||||||
|
pub pivot: bool,
|
||||||
|
pub origin: bool,
|
||||||
|
pub path: bool,
|
||||||
|
pub anchors: bool,
|
||||||
|
pub handles: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for OverlaysVisibilitySettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
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,
|
||||||
|
origin: true,
|
||||||
|
path: true,
|
||||||
|
anchors: true,
|
||||||
|
handles: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OverlaysVisibilitySettings {
|
||||||
|
pub fn all(&self) -> bool {
|
||||||
|
self.all
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn artboard_name(&self) -> bool {
|
||||||
|
self.all && self.artboard_name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compass_rose(&self) -> bool {
|
||||||
|
self.all && self.compass_rose
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn quick_measurement(&self) -> bool {
|
||||||
|
self.all && self.quick_measurement
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_measurement(&self) -> bool {
|
||||||
|
self.all && self.transform_measurement
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_cage(&self) -> bool {
|
||||||
|
self.all && self.transform_cage
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hover_outline(&self) -> bool {
|
||||||
|
self.all && self.hover_outline
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selection_outline(&self) -> bool {
|
||||||
|
self.all && self.selection_outline
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pivot(&self) -> bool {
|
||||||
|
self.all && self.pivot
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn origin(&self) -> bool {
|
||||||
|
self.all && self.origin
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn path(&self) -> bool {
|
||||||
|
self.all && self.path
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn anchors(&self) -> bool {
|
||||||
|
self.all && self.anchors
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handles(&self) -> bool {
|
||||||
|
self.all && self.anchors && self.handles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||||
|
pub struct OverlayContext {
|
||||||
|
// Serde functionality isn't used but is required by the message system macros
|
||||||
|
#[serde(skip)]
|
||||||
|
#[specta(skip)]
|
||||||
|
pub scene: Scene,
|
||||||
|
pub size: DVec2,
|
||||||
|
// The device pixel ratio is a property provided by the browser window and is the CSS pixel size divided by the physical monitor's pixel size.
|
||||||
|
// It allows better pixel density of visualizations on high-DPI displays where the OS display scaling is not 100%, or where the browser is zoomed.
|
||||||
|
pub device_pixel_ratio: f64,
|
||||||
|
pub visibility_settings: OverlaysVisibilitySettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manual implementations since Scene doesn't implement PartialEq or Debug
|
||||||
|
impl PartialEq for OverlayContext {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.size == other.size && self.device_pixel_ratio == other.device_pixel_ratio && self.visibility_settings == other.visibility_settings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for OverlayContext {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("OverlayContext")
|
||||||
|
.field("scene", &"Scene { ... }")
|
||||||
|
.field("size", &self.size)
|
||||||
|
.field("device_pixel_ratio", &self.device_pixel_ratio)
|
||||||
|
.field("visibility_settings", &self.visibility_settings)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default implementation for Scene
|
||||||
|
impl Default for OverlayContext {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
scene: Scene::new(),
|
||||||
|
size: DVec2::ZERO,
|
||||||
|
device_pixel_ratio: 1.0,
|
||||||
|
visibility_settings: OverlaysVisibilitySettings::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message hashing isn't used but is required by the message system macros
|
||||||
|
impl core::hash::Hash for OverlayContext {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, _state: &mut H) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OverlayContext {
|
||||||
|
fn parse_color(color: &str) -> peniko::Color {
|
||||||
|
let hex = color.trim_start_matches('#');
|
||||||
|
let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0);
|
||||||
|
let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0);
|
||||||
|
let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0);
|
||||||
|
let a = if hex.len() >= 8 { u8::from_str_radix(&hex[6..8], 16).unwrap_or(255) } else { 255 };
|
||||||
|
peniko::Color::from_rgba8(r, g, b, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>) {
|
||||||
|
self.dashed_polygon(&quad.0, stroke_color, color_fill, None, None, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_triangle(&mut self, base: DVec2, direction: DVec2, size: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||||
|
let color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);
|
||||||
|
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||||
|
let normal = direction.perp();
|
||||||
|
let top = base + direction * size;
|
||||||
|
let edge1 = base + normal * size / 2.;
|
||||||
|
let edge2 = base - normal * size / 2.;
|
||||||
|
|
||||||
|
let transform = self.get_transform();
|
||||||
|
|
||||||
|
let mut path = BezPath::new();
|
||||||
|
path.move_to(kurbo::Point::new(top.x, top.y));
|
||||||
|
path.line_to(kurbo::Point::new(edge1.x, edge1.y));
|
||||||
|
path.line_to(kurbo::Point::new(edge2.x, edge2.y));
|
||||||
|
path.close_path();
|
||||||
|
|
||||||
|
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &path);
|
||||||
|
|
||||||
|
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &path);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dashed_quad(&mut self, quad: Quad, stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||||
|
self.dashed_polygon(&quad.0, stroke_color, color_fill, dash_width, dash_gap_width, dash_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>) {
|
||||||
|
self.dashed_polygon(polygon, stroke_color, color_fill, None, None, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dashed_polygon(&mut self, polygon: &[DVec2], stroke_color: Option<&str>, color_fill: Option<&str>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||||
|
if polygon.len() < 2 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let transform = self.get_transform();
|
||||||
|
|
||||||
|
let mut path = BezPath::new();
|
||||||
|
if let Some(first) = polygon.last() {
|
||||||
|
path.move_to(kurbo::Point::new(first.x.round() - 0.5, first.y.round() - 0.5));
|
||||||
|
}
|
||||||
|
|
||||||
|
for point in polygon {
|
||||||
|
path.line_to(kurbo::Point::new(point.x.round() - 0.5, point.y.round() - 0.5));
|
||||||
|
}
|
||||||
|
path.close_path();
|
||||||
|
|
||||||
|
if let Some(color_fill) = color_fill {
|
||||||
|
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &path);
|
||||||
|
}
|
||||||
|
|
||||||
|
let stroke_color = stroke_color.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||||
|
let mut stroke = kurbo::Stroke::new(1.0);
|
||||||
|
|
||||||
|
if let Some(dash_width) = dash_width {
|
||||||
|
let dash_gap = dash_gap_width.unwrap_or(1.);
|
||||||
|
stroke = stroke.with_dashes(dash_offset.unwrap_or(0.), [dash_width, dash_gap]);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scene.stroke(&stroke, transform, Self::parse_color(stroke_color), None, &path);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>) {
|
||||||
|
self.dashed_line(start, end, color, thickness, None, None, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn dashed_line(&mut self, start: DVec2, end: DVec2, color: Option<&str>, thickness: Option<f64>, dash_width: Option<f64>, dash_gap_width: Option<f64>, dash_offset: Option<f64>) {
|
||||||
|
let transform = self.get_transform();
|
||||||
|
|
||||||
|
let start = start.round() - DVec2::splat(0.5);
|
||||||
|
let end = end.round() - DVec2::splat(0.5);
|
||||||
|
|
||||||
|
let mut path = BezPath::new();
|
||||||
|
path.move_to(kurbo::Point::new(start.x, start.y));
|
||||||
|
path.line_to(kurbo::Point::new(end.x, end.y));
|
||||||
|
|
||||||
|
let mut stroke = kurbo::Stroke::new(thickness.unwrap_or(1.));
|
||||||
|
|
||||||
|
if let Some(dash_width) = dash_width {
|
||||||
|
let dash_gap = dash_gap_width.unwrap_or(1.);
|
||||||
|
stroke = stroke.with_dashes(dash_offset.unwrap_or(0.), [dash_width, dash_gap]);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scene.stroke(&stroke, transform, Self::parse_color(color.unwrap_or(COLOR_OVERLAY_BLUE)), None, &path);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn manipulator_handle(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
||||||
|
let transform = self.get_transform();
|
||||||
|
let position = position.round() - DVec2::splat(0.5);
|
||||||
|
|
||||||
|
let circle = kurbo::Circle::new((position.x, position.y), MANIPULATOR_GROUP_MARKER_SIZE / 2.);
|
||||||
|
|
||||||
|
let fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
|
||||||
|
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(fill), None, &circle);
|
||||||
|
|
||||||
|
self.scene
|
||||||
|
.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color.unwrap_or(COLOR_OVERLAY_BLUE)), None, &circle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hover_manipulator_handle(&mut self, position: DVec2, selected: bool) {
|
||||||
|
let transform = self.get_transform();
|
||||||
|
|
||||||
|
let position = position.round() - DVec2::splat(0.5);
|
||||||
|
|
||||||
|
let circle = kurbo::Circle::new((position.x, position.y), (MANIPULATOR_GROUP_MARKER_SIZE + 2.) / 2.);
|
||||||
|
|
||||||
|
let fill = COLOR_OVERLAY_BLUE_50;
|
||||||
|
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(fill), None, &circle);
|
||||||
|
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &circle);
|
||||||
|
|
||||||
|
let inner_circle = kurbo::Circle::new((position.x, position.y), MANIPULATOR_GROUP_MARKER_SIZE / 2.);
|
||||||
|
|
||||||
|
let color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
|
||||||
|
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &circle);
|
||||||
|
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &inner_circle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn manipulator_anchor(&mut self, position: DVec2, selected: bool, color: Option<&str>) {
|
||||||
|
let color_stroke = color.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||||
|
let color_fill = if selected { color_stroke } else { COLOR_OVERLAY_WHITE };
|
||||||
|
self.square(position, None, Some(color_fill), Some(color_stroke));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hover_manipulator_anchor(&mut self, position: DVec2, selected: bool) {
|
||||||
|
self.square(position, Some(MANIPULATOR_GROUP_MARKER_SIZE + 2.), Some(COLOR_OVERLAY_BLUE_50), Some(COLOR_OVERLAY_BLUE_50));
|
||||||
|
let color_fill = if selected { COLOR_OVERLAY_BLUE } else { COLOR_OVERLAY_WHITE };
|
||||||
|
self.square(position, None, Some(color_fill), Some(COLOR_OVERLAY_BLUE));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_transform(&self) -> kurbo::Affine {
|
||||||
|
kurbo::Affine::scale(self.device_pixel_ratio)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn square(&mut self, position: DVec2, size: Option<f64>, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||||
|
let size = size.unwrap_or(MANIPULATOR_GROUP_MARKER_SIZE);
|
||||||
|
let color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);
|
||||||
|
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||||
|
|
||||||
|
let position = position.round() - DVec2::splat(0.5);
|
||||||
|
let corner = position - DVec2::splat(size) / 2.;
|
||||||
|
|
||||||
|
let transform = self.get_transform();
|
||||||
|
let rect = kurbo::Rect::new(corner.x, corner.y, corner.x + size, corner.y + size);
|
||||||
|
|
||||||
|
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &rect);
|
||||||
|
|
||||||
|
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pixel(&mut self, position: DVec2, color: Option<&str>) {
|
||||||
|
let size = 1.;
|
||||||
|
let color_fill = color.unwrap_or(COLOR_OVERLAY_WHITE);
|
||||||
|
|
||||||
|
let position = position.round() - DVec2::splat(0.5);
|
||||||
|
let corner = position - DVec2::splat(size) / 2.;
|
||||||
|
|
||||||
|
let transform = self.get_transform();
|
||||||
|
let rect = kurbo::Rect::new(corner.x, corner.y, corner.x + size, corner.y + size);
|
||||||
|
|
||||||
|
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn circle(&mut self, position: DVec2, radius: f64, color_fill: Option<&str>, color_stroke: Option<&str>) {
|
||||||
|
let color_fill = color_fill.unwrap_or(COLOR_OVERLAY_WHITE);
|
||||||
|
let color_stroke = color_stroke.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||||
|
let position = position.round();
|
||||||
|
|
||||||
|
let transform = self.get_transform();
|
||||||
|
let circle = kurbo::Circle::new((position.x, position.y), radius);
|
||||||
|
|
||||||
|
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color_fill), None, &circle);
|
||||||
|
|
||||||
|
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color_stroke), None, &circle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dashed_ellipse(
|
||||||
|
&mut self,
|
||||||
|
_center: DVec2,
|
||||||
|
_radius_x: f64,
|
||||||
|
_radius_y: f64,
|
||||||
|
_rotation: Option<f64>,
|
||||||
|
_start_angle: Option<f64>,
|
||||||
|
_end_angle: Option<f64>,
|
||||||
|
_counterclockwise: Option<bool>,
|
||||||
|
_color_fill: Option<&str>,
|
||||||
|
_color_stroke: Option<&str>,
|
||||||
|
_dash_width: Option<f64>,
|
||||||
|
_dash_gap_width: Option<f64>,
|
||||||
|
_dash_offset: Option<f64>,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_arc(&mut self, center: DVec2, radius: f64, start_from: f64, end_at: f64) {
|
||||||
|
let segments = ((end_at - start_from).abs() / (std::f64::consts::PI / 4.)).ceil() as usize;
|
||||||
|
let step = (end_at - start_from) / segments as f64;
|
||||||
|
let half_step = step / 2.;
|
||||||
|
let factor = 4. / 3. * half_step.sin() / (1. + half_step.cos());
|
||||||
|
|
||||||
|
let mut path = BezPath::new();
|
||||||
|
|
||||||
|
for i in 0..segments {
|
||||||
|
let start_angle = start_from + step * i as f64;
|
||||||
|
let end_angle = start_angle + step;
|
||||||
|
let start_vec = DVec2::from_angle(start_angle);
|
||||||
|
let end_vec = DVec2::from_angle(end_angle);
|
||||||
|
|
||||||
|
let start = center + radius * start_vec;
|
||||||
|
let end = center + radius * end_vec;
|
||||||
|
|
||||||
|
let handle_start = start + start_vec.perp() * radius * factor;
|
||||||
|
let handle_end = end - end_vec.perp() * radius * factor;
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
path.move_to(kurbo::Point::new(start.x, start.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
path.curve_to(
|
||||||
|
kurbo::Point::new(handle_start.x, handle_start.y),
|
||||||
|
kurbo::Point::new(handle_end.x, handle_end.y),
|
||||||
|
kurbo::Point::new(end.x, end.y),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scene.stroke(&kurbo::Stroke::new(1.0), self.get_transform(), Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_arc_gizmo_angle(&mut self, pivot: DVec2, bold_radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
||||||
|
let end_point1 = pivot + bold_radius * DVec2::from_angle(angle + offset_angle);
|
||||||
|
self.line(pivot, end_point1, None, None);
|
||||||
|
self.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_angle(&mut self, pivot: DVec2, radius: f64, arc_radius: f64, offset_angle: f64, angle: f64) {
|
||||||
|
let end_point1 = pivot + radius * DVec2::from_angle(angle + offset_angle);
|
||||||
|
let end_point2 = pivot + radius * DVec2::from_angle(offset_angle);
|
||||||
|
self.line(pivot, end_point1, None, None);
|
||||||
|
self.dashed_line(pivot, end_point2, None, None, Some(2.), Some(2.), Some(0.5));
|
||||||
|
self.draw_arc(pivot, arc_radius, offset_angle, (angle) % TAU + offset_angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {
|
||||||
|
let sign = scale.signum();
|
||||||
|
let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
|
||||||
|
fill_color.insert(0, '#');
|
||||||
|
let fill_color = Some(fill_color.as_str());
|
||||||
|
self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None, None);
|
||||||
|
self.circle(start, radius, fill_color, None);
|
||||||
|
self.circle(start, radius * scale.abs(), fill_color, None);
|
||||||
|
self.text(
|
||||||
|
text,
|
||||||
|
COLOR_OVERLAY_BLUE,
|
||||||
|
None,
|
||||||
|
DAffine2::from_translation(start + sign * DVec2::X * radius * (1. + scale.abs()) / 2.),
|
||||||
|
2.,
|
||||||
|
[Pivot::Middle, Pivot::End],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compass_rose(&mut self, compass_center: DVec2, angle: f64, show_compass_with_hover_ring: Option<bool>) {
|
||||||
|
const HOVER_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_HOVER_RING_DIAMETER / 2.;
|
||||||
|
const MAIN_RING_OUTER_RADIUS: f64 = COMPASS_ROSE_MAIN_RING_DIAMETER / 2.;
|
||||||
|
const MAIN_RING_INNER_RADIUS: f64 = COMPASS_ROSE_RING_INNER_DIAMETER / 2.;
|
||||||
|
const ARROW_RADIUS: f64 = COMPASS_ROSE_ARROW_SIZE / 2.;
|
||||||
|
const HOVER_RING_STROKE_WIDTH: f64 = HOVER_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS;
|
||||||
|
const HOVER_RING_CENTERLINE_RADIUS: f64 = (HOVER_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.;
|
||||||
|
const MAIN_RING_STROKE_WIDTH: f64 = MAIN_RING_OUTER_RADIUS - MAIN_RING_INNER_RADIUS;
|
||||||
|
const MAIN_RING_CENTERLINE_RADIUS: f64 = (MAIN_RING_OUTER_RADIUS + MAIN_RING_INNER_RADIUS) / 2.;
|
||||||
|
|
||||||
|
let Some(show_hover_ring) = show_compass_with_hover_ring else { return };
|
||||||
|
|
||||||
|
let transform = self.get_transform();
|
||||||
|
let center = compass_center.round() - DVec2::splat(0.5);
|
||||||
|
|
||||||
|
// Hover ring
|
||||||
|
if show_hover_ring {
|
||||||
|
let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.5).to_rgba_hex_srgb();
|
||||||
|
fill_color.insert(0, '#');
|
||||||
|
|
||||||
|
let circle = kurbo::Circle::new((center.x, center.y), HOVER_RING_CENTERLINE_RADIUS);
|
||||||
|
self.scene
|
||||||
|
.stroke(&kurbo::Stroke::new(HOVER_RING_STROKE_WIDTH), transform, Self::parse_color(&fill_color), None, &circle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arrows
|
||||||
|
for i in 0..4 {
|
||||||
|
let direction = DVec2::from_angle(i as f64 * FRAC_PI_2 + angle);
|
||||||
|
let color = if i % 2 == 0 { COLOR_OVERLAY_RED } else { COLOR_OVERLAY_GREEN };
|
||||||
|
|
||||||
|
let tip = center + direction * HOVER_RING_OUTER_RADIUS;
|
||||||
|
let base = center + direction * (MAIN_RING_INNER_RADIUS + MAIN_RING_OUTER_RADIUS) / 2.;
|
||||||
|
|
||||||
|
let r = (ARROW_RADIUS.powi(2) + MAIN_RING_INNER_RADIUS.powi(2)).sqrt();
|
||||||
|
let (cos, sin) = (MAIN_RING_INNER_RADIUS / r, ARROW_RADIUS / r);
|
||||||
|
let side1 = center + r * DVec2::new(cos * direction.x - sin * direction.y, sin * direction.x + direction.y * cos);
|
||||||
|
let side2 = center + r * DVec2::new(cos * direction.x + sin * direction.y, -sin * direction.x + direction.y * cos);
|
||||||
|
|
||||||
|
let mut path = BezPath::new();
|
||||||
|
path.move_to(kurbo::Point::new(tip.x, tip.y));
|
||||||
|
path.line_to(kurbo::Point::new(side1.x, side1.y));
|
||||||
|
path.line_to(kurbo::Point::new(base.x, base.y));
|
||||||
|
path.line_to(kurbo::Point::new(side2.x, side2.y));
|
||||||
|
path.close_path();
|
||||||
|
|
||||||
|
let color_parsed = Self::parse_color(color);
|
||||||
|
self.scene.fill(peniko::Fill::NonZero, transform, color_parsed, None, &path);
|
||||||
|
self.scene.stroke(&kurbo::Stroke::new(0.01), transform, color_parsed, None, &path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main ring
|
||||||
|
let circle = kurbo::Circle::new((center.x, center.y), MAIN_RING_CENTERLINE_RADIUS);
|
||||||
|
self.scene
|
||||||
|
.stroke(&kurbo::Stroke::new(MAIN_RING_STROKE_WIDTH), transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &circle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pivot(&mut self, position: DVec2, angle: f64) {
|
||||||
|
let uv = DVec2::from_angle(angle);
|
||||||
|
let (x, y) = (position.round() - DVec2::splat(0.5)).into();
|
||||||
|
|
||||||
|
let transform = self.get_transform();
|
||||||
|
|
||||||
|
// Circle
|
||||||
|
let circle = kurbo::Circle::new((x, y), PIVOT_DIAMETER / 2.);
|
||||||
|
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(COLOR_OVERLAY_YELLOW), None, &circle);
|
||||||
|
|
||||||
|
// Crosshair
|
||||||
|
const CROSSHAIR_RADIUS: f64 = (PIVOT_CROSSHAIR_LENGTH - PIVOT_CROSSHAIR_THICKNESS) / 2.;
|
||||||
|
|
||||||
|
let mut stroke = kurbo::Stroke::new(PIVOT_CROSSHAIR_THICKNESS);
|
||||||
|
stroke = stroke.with_caps(kurbo::Cap::Round);
|
||||||
|
|
||||||
|
// Horizontal line
|
||||||
|
let mut path = BezPath::new();
|
||||||
|
path.move_to(kurbo::Point::new(x + CROSSHAIR_RADIUS * uv.x, y + CROSSHAIR_RADIUS * uv.y));
|
||||||
|
path.line_to(kurbo::Point::new(x - CROSSHAIR_RADIUS * uv.x, y - CROSSHAIR_RADIUS * uv.y));
|
||||||
|
|
||||||
|
self.scene.stroke(&stroke, transform, Self::parse_color(COLOR_OVERLAY_YELLOW), None, &path);
|
||||||
|
|
||||||
|
// Vertical line
|
||||||
|
let mut path = BezPath::new();
|
||||||
|
path.move_to(kurbo::Point::new(x - CROSSHAIR_RADIUS * uv.y, y + CROSSHAIR_RADIUS * uv.x));
|
||||||
|
path.line_to(kurbo::Point::new(x + CROSSHAIR_RADIUS * uv.y, y - CROSSHAIR_RADIUS * uv.x));
|
||||||
|
|
||||||
|
self.scene.stroke(&stroke, transform, Self::parse_color(COLOR_OVERLAY_YELLOW), None, &path);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dowel_pin(&mut self, position: DVec2, angle: f64, color: Option<&str>) {
|
||||||
|
let (x, y) = (position.round() - DVec2::splat(0.5)).into();
|
||||||
|
let color = color.unwrap_or(COLOR_OVERLAY_YELLOW_DULL);
|
||||||
|
|
||||||
|
let transform = self.get_transform();
|
||||||
|
|
||||||
|
// Draw the background circle with a white fill and colored outline
|
||||||
|
let circle = kurbo::Circle::new((x, y), DOWEL_PIN_RADIUS);
|
||||||
|
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(COLOR_OVERLAY_WHITE), None, &circle);
|
||||||
|
self.scene.stroke(&kurbo::Stroke::new(1.0), transform, Self::parse_color(color), None, &circle);
|
||||||
|
|
||||||
|
// Draw the two filled sectors using paths
|
||||||
|
let mut path = BezPath::new();
|
||||||
|
|
||||||
|
// Top-left sector
|
||||||
|
path.move_to(kurbo::Point::new(x, y));
|
||||||
|
let end_x = x + DOWEL_PIN_RADIUS * (FRAC_PI_2 + angle).cos();
|
||||||
|
let end_y = y + DOWEL_PIN_RADIUS * (FRAC_PI_2 + angle).sin();
|
||||||
|
path.line_to(kurbo::Point::new(end_x, end_y));
|
||||||
|
// Draw arc manually
|
||||||
|
let arc = kurbo::Arc::new((x, y), (DOWEL_PIN_RADIUS, DOWEL_PIN_RADIUS), FRAC_PI_2 + angle, FRAC_PI_2, 0.0);
|
||||||
|
arc.to_cubic_beziers(0.1, |p1, p2, p| {
|
||||||
|
path.curve_to(p1, p2, p);
|
||||||
|
});
|
||||||
|
path.close_path();
|
||||||
|
|
||||||
|
// Bottom-right sector
|
||||||
|
path.move_to(kurbo::Point::new(x, y));
|
||||||
|
let end_x = x + DOWEL_PIN_RADIUS * (PI + FRAC_PI_2 + angle).cos();
|
||||||
|
let end_y = y + DOWEL_PIN_RADIUS * (PI + FRAC_PI_2 + angle).sin();
|
||||||
|
path.line_to(kurbo::Point::new(end_x, end_y));
|
||||||
|
// Draw arc manually
|
||||||
|
let arc = kurbo::Arc::new((x, y), (DOWEL_PIN_RADIUS, DOWEL_PIN_RADIUS), PI + FRAC_PI_2 + angle, FRAC_PI_2, 0.0);
|
||||||
|
arc.to_cubic_beziers(0.1, |p1, p2, p| {
|
||||||
|
path.curve_to(p1, p2, p);
|
||||||
|
});
|
||||||
|
path.close_path();
|
||||||
|
|
||||||
|
self.scene.fill(peniko::Fill::NonZero, transform, Self::parse_color(color), None, &path);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn arc_sweep_angle(&mut self, offset_angle: f64, angle: f64, end_point_position: DVec2, bold_radius: f64, pivot: DVec2, text: &str, transform: DAffine2) {
|
||||||
|
self.manipulator_handle(end_point_position, true, None);
|
||||||
|
self.draw_arc_gizmo_angle(pivot, bold_radius, ARC_SWEEP_GIZMO_RADIUS, offset_angle, angle.to_radians());
|
||||||
|
self.text(text, COLOR_OVERLAY_BLUE, None, transform, 16., [Pivot::Middle, Pivot::Middle]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used by the Pen and Path tools to outline the path of the shape.
|
||||||
|
pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
|
||||||
|
let vello_transform = self.get_transform();
|
||||||
|
let mut path = BezPath::new();
|
||||||
|
|
||||||
|
let mut last_point = None;
|
||||||
|
for (_, bezier, start_id, end_id) in vector_data.segment_bezier_iter() {
|
||||||
|
let move_to = last_point != Some(start_id);
|
||||||
|
last_point = Some(end_id);
|
||||||
|
|
||||||
|
self.bezier_to_path(bezier, transform, move_to, &mut path);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.scene.stroke(&kurbo::Stroke::new(1.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
||||||
|
pub fn outline_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||||
|
let vello_transform = self.get_transform();
|
||||||
|
let mut path = BezPath::new();
|
||||||
|
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||||
|
|
||||||
|
self.scene.stroke(&kurbo::Stroke::new(1.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used by the path tool segment mode in order to show the selected segments.
|
||||||
|
pub fn outline_select_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||||
|
let vello_transform = self.get_transform();
|
||||||
|
let mut path = BezPath::new();
|
||||||
|
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||||
|
|
||||||
|
self.scene.stroke(&kurbo::Stroke::new(4.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||||
|
let vello_transform = self.get_transform();
|
||||||
|
let mut path = BezPath::new();
|
||||||
|
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||||
|
|
||||||
|
self.scene.stroke(&kurbo::Stroke::new(4.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &path);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bezier_to_path(&self, bezier: Bezier, transform: DAffine2, move_to: bool, path: &mut BezPath) {
|
||||||
|
let Bezier { start, end, handles } = bezier.apply_transformation(|point| transform.transform_point2(point));
|
||||||
|
if move_to {
|
||||||
|
path.move_to(kurbo::Point::new(start.x, start.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
match handles {
|
||||||
|
bezier_rs::BezierHandles::Linear => path.line_to(kurbo::Point::new(end.x, end.y)),
|
||||||
|
bezier_rs::BezierHandles::Quadratic { handle } => path.quad_to(kurbo::Point::new(handle.x, handle.y), kurbo::Point::new(end.x, end.y)),
|
||||||
|
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => path.curve_to(
|
||||||
|
kurbo::Point::new(handle_start.x, handle_start.y),
|
||||||
|
kurbo::Point::new(handle_end.x, handle_end.y),
|
||||||
|
kurbo::Point::new(end.x, end.y),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2) -> BezPath {
|
||||||
|
let mut path = BezPath::new();
|
||||||
|
|
||||||
|
for subpath in subpaths {
|
||||||
|
let subpath = subpath.borrow();
|
||||||
|
let mut curves = subpath.iter().peekable();
|
||||||
|
|
||||||
|
let Some(first) = curves.peek() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let start_point = transform.transform_point2(first.start());
|
||||||
|
path.move_to(kurbo::Point::new(start_point.x, start_point.y));
|
||||||
|
|
||||||
|
for curve in curves {
|
||||||
|
match curve.handles {
|
||||||
|
bezier_rs::BezierHandles::Linear => {
|
||||||
|
let a = transform.transform_point2(curve.end());
|
||||||
|
let a = a.round() - DVec2::splat(0.5);
|
||||||
|
path.line_to(kurbo::Point::new(a.x, a.y));
|
||||||
|
}
|
||||||
|
bezier_rs::BezierHandles::Quadratic { handle } => {
|
||||||
|
let a = transform.transform_point2(handle);
|
||||||
|
let b = transform.transform_point2(curve.end());
|
||||||
|
let a = a.round() - DVec2::splat(0.5);
|
||||||
|
let b = b.round() - DVec2::splat(0.5);
|
||||||
|
path.quad_to(kurbo::Point::new(a.x, a.y), kurbo::Point::new(b.x, b.y));
|
||||||
|
}
|
||||||
|
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||||
|
let a = transform.transform_point2(handle_start);
|
||||||
|
let b = transform.transform_point2(handle_end);
|
||||||
|
let c = transform.transform_point2(curve.end());
|
||||||
|
let a = a.round() - DVec2::splat(0.5);
|
||||||
|
let b = b.round() - DVec2::splat(0.5);
|
||||||
|
let c = c.round() - DVec2::splat(0.5);
|
||||||
|
path.curve_to(kurbo::Point::new(a.x, a.y), kurbo::Point::new(b.x, b.y), kurbo::Point::new(c.x, c.y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if subpath.closed() {
|
||||||
|
path.close_path();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used by the Select tool to outline a path or a free point when selected or hovered.
|
||||||
|
pub fn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {
|
||||||
|
let mut subpaths: Vec<bezier_rs::Subpath<PointId>> = vec![];
|
||||||
|
|
||||||
|
for target_type in target_types {
|
||||||
|
match target_type.borrow() {
|
||||||
|
ClickTargetType::FreePoint(point) => {
|
||||||
|
self.manipulator_anchor(transform.transform_point2(point.position), false, None);
|
||||||
|
}
|
||||||
|
ClickTargetType::Subpath(subpath) => subpaths.push(subpath.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !subpaths.is_empty() {
|
||||||
|
let path = self.push_path(subpaths.iter(), transform);
|
||||||
|
let color = color.unwrap_or(COLOR_OVERLAY_BLUE);
|
||||||
|
|
||||||
|
self.scene.stroke(&kurbo::Stroke::new(1.0), self.get_transform(), Self::parse_color(color), None, &path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fills the area inside the path. Assumes `color` is in gamma space.
|
||||||
|
/// Used by the Pen tool to show the path being closed.
|
||||||
|
pub fn fill_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &str) {
|
||||||
|
let path = self.push_path(subpaths, transform);
|
||||||
|
|
||||||
|
self.scene.fill(peniko::Fill::NonZero, self.get_transform(), Self::parse_color(color), None, &path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fills the area inside the path with a pattern. Assumes `color` is in gamma space.
|
||||||
|
/// Used by the fill tool to show the area to be filled.
|
||||||
|
pub fn fill_path_pattern(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2, color: &Color) {
|
||||||
|
// TODO: Implement pattern fill in Vello
|
||||||
|
// For now, just fill with a semi-transparent version of the color
|
||||||
|
let path = self.push_path(subpaths, transform);
|
||||||
|
let semi_transparent_color = color.with_alpha(0.5);
|
||||||
|
|
||||||
|
self.scene.fill(
|
||||||
|
peniko::Fill::NonZero,
|
||||||
|
self.get_transform(),
|
||||||
|
peniko::Color::from_rgba8(
|
||||||
|
(semi_transparent_color.r() * 255.) as u8,
|
||||||
|
(semi_transparent_color.g() * 255.) as u8,
|
||||||
|
(semi_transparent_color.b() * 255.) as u8,
|
||||||
|
(semi_transparent_color.a() * 255.) as u8,
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
&path,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_width(&self, _text: &str) -> f64 {
|
||||||
|
// TODO: Implement proper text measurement in Vello
|
||||||
|
0.
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn text(&self, _text: &str, _font_color: &str, _background_color: Option<&str>, _transform: DAffine2, _padding: f64, _pivot: [Pivot; 2]) {
|
||||||
|
// TODO: Implement text rendering in Vello
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn translation_box(&mut self, translation: DVec2, quad: Quad, typed_string: Option<String>) {
|
||||||
|
if translation.x.abs() > 1e-3 {
|
||||||
|
self.dashed_line(quad.top_left(), quad.top_right(), None, None, Some(2.), Some(2.), Some(0.5));
|
||||||
|
|
||||||
|
let width = match typed_string {
|
||||||
|
Some(ref typed_string) => typed_string,
|
||||||
|
None => &format!("{:.2}", translation.x).trim_end_matches('0').trim_end_matches('.').to_string(),
|
||||||
|
};
|
||||||
|
let x_transform = DAffine2::from_translation((quad.top_left() + quad.top_right()) / 2.);
|
||||||
|
self.text(width, COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if translation.y.abs() > 1e-3 {
|
||||||
|
self.dashed_line(quad.top_left(), quad.bottom_left(), None, None, Some(2.), Some(2.), Some(0.5));
|
||||||
|
|
||||||
|
let height = match typed_string {
|
||||||
|
Some(ref typed_string) => typed_string,
|
||||||
|
None => &format!("{:.2}", translation.y).trim_end_matches('0').trim_end_matches('.').to_string(),
|
||||||
|
};
|
||||||
|
let y_transform = DAffine2::from_translation((quad.top_left() + quad.bottom_left()) / 2.);
|
||||||
|
let height_pivot = if translation.x > -1e-3 { Pivot::Start } else { Pivot::End };
|
||||||
|
self.text(height, COLOR_OVERLAY_BLUE, None, y_transform, 3., [height_pivot, Pivot::Middle]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if translation.x.abs() > 1e-3 && translation.y.abs() > 1e-3 {
|
||||||
|
self.line(quad.top_right(), quad.bottom_right(), None, None);
|
||||||
|
self.line(quad.bottom_left(), quad.bottom_right(), None, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Pivot {
|
||||||
|
Start,
|
||||||
|
Middle,
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum DrawHandles {
|
||||||
|
All,
|
||||||
|
SelectedAnchors(Vec<SegmentId>),
|
||||||
|
FrontierHandles(HashMap<SegmentId, Vec<PointId>>),
|
||||||
|
None,
|
||||||
|
}
|
||||||
@@ -1,7 +1,5 @@
|
|||||||
mod properties_panel_message;
|
mod properties_panel_message;
|
||||||
mod properties_panel_message_handler;
|
pub mod properties_panel_message_handler;
|
||||||
|
|
||||||
pub mod utility_types;
|
|
||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use properties_panel_message::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant};
|
pub use properties_panel_message::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant};
|
||||||
|
|||||||
+21
-8
@@ -1,21 +1,34 @@
|
|||||||
use super::utility_types::PropertiesPanelMessageHandlerData;
|
use graphene_std::uuid::NodeId;
|
||||||
|
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::NodePropertiesContext;
|
use crate::messages::portfolio::document::node_graph::document_node_definitions::NodePropertiesContext;
|
||||||
|
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||||
use crate::messages::portfolio::utility_types::PersistentData;
|
use crate::messages::portfolio::utility_types::PersistentData;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
use crate::node_graph_executor::NodeGraphExecutor;
|
||||||
|
|
||||||
|
#[derive(ExtractField)]
|
||||||
|
pub struct PropertiesPanelMessageContext<'a> {
|
||||||
|
pub network_interface: &'a mut NodeNetworkInterface,
|
||||||
|
pub selection_network_path: &'a [NodeId],
|
||||||
|
pub document_name: &'a str,
|
||||||
|
pub executor: &'a mut NodeGraphExecutor,
|
||||||
|
pub persistent_data: &'a PersistentData,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, ExtractField)]
|
#[derive(Debug, Clone, Default, ExtractField)]
|
||||||
pub struct PropertiesPanelMessageHandler {}
|
pub struct PropertiesPanelMessageHandler {}
|
||||||
|
|
||||||
#[message_handler_data]
|
#[message_handler_data]
|
||||||
impl MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPanelMessageHandlerData<'_>)> for PropertiesPanelMessageHandler {
|
impl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> for PropertiesPanelMessageHandler {
|
||||||
fn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque<Message>, (persistent_data, data): (&PersistentData, PropertiesPanelMessageHandlerData)) {
|
fn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque<Message>, context: PropertiesPanelMessageContext) {
|
||||||
let PropertiesPanelMessageHandlerData {
|
let PropertiesPanelMessageContext {
|
||||||
network_interface,
|
network_interface,
|
||||||
selection_network_path,
|
selection_network_path,
|
||||||
document_name,
|
document_name,
|
||||||
executor,
|
executor,
|
||||||
} = data;
|
persistent_data,
|
||||||
|
} = context;
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
PropertiesPanelMessage::Clear => {
|
PropertiesPanelMessage::Clear => {
|
||||||
@@ -25,7 +38,7 @@ impl MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPanelMes
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
PropertiesPanelMessage::Refresh => {
|
PropertiesPanelMessage::Refresh => {
|
||||||
let mut context = NodePropertiesContext {
|
let mut node_properties_context = NodePropertiesContext {
|
||||||
persistent_data,
|
persistent_data,
|
||||||
responses,
|
responses,
|
||||||
network_interface,
|
network_interface,
|
||||||
@@ -33,9 +46,9 @@ impl MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPanelMes
|
|||||||
document_name,
|
document_name,
|
||||||
executor,
|
executor,
|
||||||
};
|
};
|
||||||
let properties_sections = NodeGraphMessageHandler::collate_properties(&mut context);
|
let properties_sections = NodeGraphMessageHandler::collate_properties(&mut node_properties_context);
|
||||||
|
|
||||||
context.responses.add(LayoutMessage::SendLayout {
|
node_properties_context.responses.add(LayoutMessage::SendLayout {
|
||||||
layout: Layout::WidgetLayout(WidgetLayout::new(properties_sections)),
|
layout: Layout::WidgetLayout(WidgetLayout::new(properties_sections)),
|
||||||
layout_target: LayoutTarget::PropertiesSections,
|
layout_target: LayoutTarget::PropertiesSections,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
|
||||||
use crate::node_graph_executor::NodeGraphExecutor;
|
|
||||||
use graph_craft::document::NodeId;
|
|
||||||
|
|
||||||
pub struct PropertiesPanelMessageHandlerData<'a> {
|
|
||||||
pub network_interface: &'a mut NodeNetworkInterface,
|
|
||||||
pub selection_network_path: &'a [NodeId],
|
|
||||||
pub document_name: &'a str,
|
|
||||||
pub executor: &'a mut NodeGraphExecutor,
|
|
||||||
}
|
|
||||||
@@ -250,12 +250,8 @@ impl LayerNodeIdentifier {
|
|||||||
|
|
||||||
/// Construct a [`LayerNodeIdentifier`], debug asserting that it is a layer node. This should only be used in the document network since the structure is not loaded in nested networks.
|
/// Construct a [`LayerNodeIdentifier`], debug asserting that it is a layer node. This should only be used in the document network since the structure is not loaded in nested networks.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn new(node_id: NodeId, network_interface: &NodeNetworkInterface, network_path: &[NodeId]) -> Self {
|
pub fn new(node_id: NodeId, network_interface: &NodeNetworkInterface) -> Self {
|
||||||
debug_assert!(
|
debug_assert!(network_interface.is_layer(&node_id, &[]), "Layer identifier constructed from non-layer node {node_id}",);
|
||||||
network_interface.is_layer(&node_id, network_path),
|
|
||||||
"Layer identifier constructed from non-layer node {node_id}: {:#?}",
|
|
||||||
network_interface.nested_network(network_path).unwrap().nodes.get(&node_id)
|
|
||||||
);
|
|
||||||
Self::new_unchecked(node_id)
|
Self::new_unchecked(node_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -203,12 +203,12 @@ impl NodeNetworkInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the first downstream layer(inclusive) from a node. If the node is a layer, it will return itself.
|
/// Returns the first downstream layer(inclusive) from a node. If the node is a layer, it will return itself.
|
||||||
pub fn downstream_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<LayerNodeIdentifier> {
|
pub fn downstream_layer_for_chain_node(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> Option<NodeId> {
|
||||||
let mut id = *node_id;
|
let mut id = *node_id;
|
||||||
while !self.is_layer(&id, network_path) {
|
while !self.is_layer(&id, network_path) {
|
||||||
id = self.outward_wires(network_path)?.get(&OutputConnector::node(id, 0))?.first()?.node_id()?;
|
id = self.outward_wires(network_path)?.get(&OutputConnector::node(id, 0))?.first()?.node_id()?;
|
||||||
}
|
}
|
||||||
Some(LayerNodeIdentifier::new(id, self, network_path))
|
Some(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns all downstream layers (inclusive) from a node. If the node is a layer, it will return itself.
|
/// Returns all downstream layers (inclusive) from a node. If the node is a layer, it will return itself.
|
||||||
@@ -388,8 +388,8 @@ impl NodeNetworkInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If a chain node does not have a selected downstream layer, then set the position to absolute
|
// If a chain node does not have a selected downstream layer, then set the position to absolute
|
||||||
let downstream_layer = self.downstream_layer(node_id, network_path);
|
let downstream_layer = self.downstream_layer_for_chain_node(node_id, network_path);
|
||||||
if downstream_layer.is_none_or(|downstream_layer| new_ids.keys().all(|key| *key != downstream_layer.to_node())) {
|
if downstream_layer.is_none_or(|downstream_layer| new_ids.keys().all(|key| *key != downstream_layer)) {
|
||||||
let Some(position) = self.position(node_id, network_path) else {
|
let Some(position) = self.position(node_id, network_path) else {
|
||||||
log::error!("Could not get position in create_node_template");
|
log::error!("Could not get position in create_node_template");
|
||||||
return None;
|
return None;
|
||||||
@@ -1244,7 +1244,7 @@ impl NodeNetworkInterface {
|
|||||||
.as_ref()
|
.as_ref()
|
||||||
.is_some_and(|reference| reference == "Artboard" && self.connected_to_output(node_id, &[]) && self.is_layer(node_id, &[]))
|
.is_some_and(|reference| reference == "Artboard" && self.connected_to_output(node_id, &[]) && self.is_layer(node_id, &[]))
|
||||||
{
|
{
|
||||||
Some(LayerNodeIdentifier::new(*node_id, self, &[]))
|
Some(LayerNodeIdentifier::new(*node_id, self))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -3025,7 +3025,7 @@ impl NodeNetworkInterface {
|
|||||||
|
|
||||||
// Helper functions for mutable getters
|
// Helper functions for mutable getters
|
||||||
impl NodeNetworkInterface {
|
impl NodeNetworkInterface {
|
||||||
pub fn upstream_chain_nodes(&mut self, network_path: &[NodeId]) -> Vec<NodeId> {
|
pub fn upstream_chain_nodes(&self, network_path: &[NodeId]) -> Vec<NodeId> {
|
||||||
let Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else {
|
let Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else {
|
||||||
log::error!("Could not get selected nodes in upstream_chain_nodes");
|
log::error!("Could not get selected nodes in upstream_chain_nodes");
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
@@ -3156,7 +3156,7 @@ impl NodeNetworkInterface {
|
|||||||
self.document_metadata.document_to_viewport = transform;
|
self.document_metadata.document_to_viewport = transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_eligible_to_be_layer(&mut self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
pub fn is_eligible_to_be_layer(&self, node_id: &NodeId, network_path: &[NodeId]) -> bool {
|
||||||
let Some(node) = self.document_node(node_id, network_path) else {
|
let Some(node) = self.document_node(node_id, network_path) else {
|
||||||
log::error!("Could not get node {node_id} in is_eligible_to_be_layer");
|
log::error!("Could not get node {node_id} in is_eligible_to_be_layer");
|
||||||
return false;
|
return false;
|
||||||
@@ -3362,6 +3362,24 @@ impl NodeNetworkInterface {
|
|||||||
.map(|[a, b]| [node_graph_to_viewport.transform_point2(a), node_graph_to_viewport.transform_point2(b)])
|
.map(|[a, b]| [node_graph_to_viewport.transform_point2(a), node_graph_to_viewport.transform_point2(b)])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn selected_layers_artwork_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
|
||||||
|
self.selected_nodes()
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.filter(|node| self.is_layer(&node, &[]))
|
||||||
|
.filter_map(|layer| self.document_metadata.bounding_box_viewport(LayerNodeIdentifier::new(*layer, self)))
|
||||||
|
.reduce(Quad::combine_bounds)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn selected_unlocked_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
|
||||||
|
self.selected_nodes()
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.filter(|node| self.is_layer(&node, &[]) && !self.is_locked(&node, &[]))
|
||||||
|
.filter_map(|layer| self.document_metadata.bounding_box_viewport(LayerNodeIdentifier::new(*layer, self)))
|
||||||
|
.reduce(Quad::combine_bounds)
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the combined bounding box of the click targets of the selected nodes in the node graph in layer space
|
/// Get the combined bounding box of the click targets of the selected nodes in the node graph in layer space
|
||||||
pub fn selected_nodes_bounding_box(&mut self, network_path: &[NodeId]) -> Option<[DVec2; 2]> {
|
pub fn selected_nodes_bounding_box(&mut self, network_path: &[NodeId]) -> Option<[DVec2; 2]> {
|
||||||
let Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else {
|
let Some(selected_nodes) = self.selected_nodes_in_nested_network(network_path) else {
|
||||||
@@ -3451,7 +3469,7 @@ impl NodeNetworkInterface {
|
|||||||
|
|
||||||
let Some(first_root_layer) = self
|
let Some(first_root_layer) = self
|
||||||
.upstream_flow_back_from_nodes(vec![root_node.node_id], &[], FlowType::PrimaryFlow)
|
.upstream_flow_back_from_nodes(vec![root_node.node_id], &[], FlowType::PrimaryFlow)
|
||||||
.find_map(|node_id| if self.is_layer(&node_id, &[]) { Some(LayerNodeIdentifier::new(node_id, self, &[])) } else { None })
|
.find_map(|node_id| if self.is_layer(&node_id, &[]) { Some(LayerNodeIdentifier::new(node_id, self)) } else { None })
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -3467,7 +3485,7 @@ impl NodeNetworkInterface {
|
|||||||
if horizontal_root_node_id == first_root_layer.to_node() {
|
if horizontal_root_node_id == first_root_layer.to_node() {
|
||||||
for current_node_id in horizontal_flow_iter {
|
for current_node_id in horizontal_flow_iter {
|
||||||
if self.is_layer(¤t_node_id, &[]) {
|
if self.is_layer(¤t_node_id, &[]) {
|
||||||
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self, &[]);
|
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self);
|
||||||
if !self.document_metadata.structure.contains_key(¤t_layer_node) {
|
if !self.document_metadata.structure.contains_key(¤t_layer_node) {
|
||||||
if current_node_id == first_root_layer.to_node() {
|
if current_node_id == first_root_layer.to_node() {
|
||||||
awaiting_primary_flow.push((current_node_id, LayerNodeIdentifier::ROOT_PARENT));
|
awaiting_primary_flow.push((current_node_id, LayerNodeIdentifier::ROOT_PARENT));
|
||||||
@@ -3484,7 +3502,7 @@ impl NodeNetworkInterface {
|
|||||||
// Skip the horizontal_root_node_id node
|
// Skip the horizontal_root_node_id node
|
||||||
for current_node_id in horizontal_flow_iter.skip(1) {
|
for current_node_id in horizontal_flow_iter.skip(1) {
|
||||||
if self.is_layer(¤t_node_id, &[]) {
|
if self.is_layer(¤t_node_id, &[]) {
|
||||||
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self, &[]);
|
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self);
|
||||||
if !self.document_metadata.structure.contains_key(¤t_layer_node) {
|
if !self.document_metadata.structure.contains_key(¤t_layer_node) {
|
||||||
awaiting_primary_flow.push((current_node_id, parent_layer_node));
|
awaiting_primary_flow.push((current_node_id, parent_layer_node));
|
||||||
children.push((parent_layer_node, current_layer_node));
|
children.push((parent_layer_node, current_layer_node));
|
||||||
@@ -3505,7 +3523,7 @@ impl NodeNetworkInterface {
|
|||||||
for current_node_id in primary_flow_iter.skip(1) {
|
for current_node_id in primary_flow_iter.skip(1) {
|
||||||
if self.is_layer(¤t_node_id, &[]) {
|
if self.is_layer(¤t_node_id, &[]) {
|
||||||
// Create a new layer for the top of each stack, and add it as a child to the previous parent
|
// Create a new layer for the top of each stack, and add it as a child to the previous parent
|
||||||
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self, &[]);
|
let current_layer_node = LayerNodeIdentifier::new(current_node_id, self);
|
||||||
if !self.document_metadata.structure.contains_key(¤t_layer_node) {
|
if !self.document_metadata.structure.contains_key(¤t_layer_node) {
|
||||||
children.push(current_layer_node);
|
children.push(current_layer_node);
|
||||||
|
|
||||||
@@ -3568,7 +3586,7 @@ impl NodeNetworkInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stack.extend(self_network_metadata.persistent_metadata.node_metadata.keys().map(|node_id| {
|
stack.extend(self_network_metadata.persistent_metadata.node_metadata.keys().map(|node_id| {
|
||||||
let mut current_path = path.clone();
|
let mut current_path: Vec<NodeId> = path.clone();
|
||||||
current_path.push(*node_id);
|
current_path.push(*node_id);
|
||||||
current_path
|
current_path
|
||||||
}));
|
}));
|
||||||
@@ -5085,12 +5103,45 @@ impl NodeNetworkInterface {
|
|||||||
else {
|
else {
|
||||||
log::error!("Could not set chain position for layer node {node_id}");
|
log::error!("Could not set chain position for layer node {node_id}");
|
||||||
}
|
}
|
||||||
|
// let previous_upstream_node = self.upstream_output_connector(&InputConnector::node(*node_id, 0), network_path).and_then(|output| output.node_id());
|
||||||
|
// let Some(previous_upstream_node_position) = previous_upstream_node.and_then(|upstream| self.position_from_downstream_node(&upstream, network_path)) else {
|
||||||
|
// log::error!("Could not get previous_upstream_node_position");
|
||||||
|
// return;
|
||||||
|
// };
|
||||||
self.unload_upstream_node_click_targets(vec![*node_id], network_path);
|
self.unload_upstream_node_click_targets(vec![*node_id], network_path);
|
||||||
// Reload click target of the layer which encapsulate the chain
|
// Reload click target of the layer which encapsulate the chain
|
||||||
if let Some(downstream_layer) = self.downstream_layer(node_id, network_path) {
|
if let Some(downstream_layer) = self.downstream_layer_for_chain_node(node_id, network_path) {
|
||||||
self.unload_node_click_targets(&downstream_layer.to_node(), network_path);
|
self.unload_node_click_targets(&downstream_layer, network_path);
|
||||||
}
|
}
|
||||||
self.unload_all_nodes_bounding_box(network_path);
|
self.unload_all_nodes_bounding_box(network_path);
|
||||||
|
|
||||||
|
// let Some(new_upstream_node_position) = previous_upstream_node.and_then(|upstream| self.position_from_downstream_node(&upstream, network_path)) else {
|
||||||
|
// log::error!("Could not get new_upstream_node_position");
|
||||||
|
// return;
|
||||||
|
// };
|
||||||
|
// if let Some(previous_upstream_node) = {
|
||||||
|
// let x_delta = new_upstream_node_position.x - previous_upstream_node_position.x;
|
||||||
|
// // Upstream node got shifted to left, so shift all upstream absolute sole dependents
|
||||||
|
// if x_delta != 0 {
|
||||||
|
// let upstream_absolute_nodes = SelectedNodes(
|
||||||
|
// self.upstream_flow_back_from_nodes(vec![previous_upstream_node], network_path, FlowType::UpstreamFlow)
|
||||||
|
// .into_iter()
|
||||||
|
// .filter(|node_id| self.is_absolute(node_id, network_path))
|
||||||
|
// .collect::<Vec<_>>(),
|
||||||
|
// );
|
||||||
|
// let old_selected_nodes = std::mem::replace(self.selected_nodes_mut(network_path).unwrap(), upstream_absolute_nodes);
|
||||||
|
// if x_delta < 0 {
|
||||||
|
// for _ in 0..x_delta.abs() {
|
||||||
|
// self.shift_selected_nodes(Direction::Left, false, network_path);
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// for _ in 0..x_delta.abs() {
|
||||||
|
// self.shift_selected_nodes(Direction::Right, false, network_path);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// let _ = std::mem::replace(self.selected_nodes_mut(network_path).unwrap(), old_selected_nodes);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn valid_upstream_chain_nodes(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec<NodeId> {
|
fn valid_upstream_chain_nodes(&mut self, input_connector: &InputConnector, network_path: &[NodeId]) -> Vec<NodeId> {
|
||||||
@@ -5205,7 +5256,7 @@ impl NodeNetworkInterface {
|
|||||||
|
|
||||||
/// node_id is the first chain node, not the layer
|
/// node_id is the first chain node, not the layer
|
||||||
fn set_upstream_chain_to_absolute(&mut self, node_id: &NodeId, network_path: &[NodeId]) {
|
fn set_upstream_chain_to_absolute(&mut self, node_id: &NodeId, network_path: &[NodeId]) {
|
||||||
let Some(downstream_layer) = self.downstream_layer(node_id, network_path) else {
|
let Some(downstream_layer) = self.downstream_layer_for_chain_node(node_id, network_path) else {
|
||||||
log::error!("Could not get downstream layer in set_upstream_chain_to_absolute");
|
log::error!("Could not get downstream layer in set_upstream_chain_to_absolute");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -5218,7 +5269,7 @@ impl NodeNetworkInterface {
|
|||||||
if self.is_chain(upstream_id, network_path) {
|
if self.is_chain(upstream_id, network_path) {
|
||||||
self.set_absolute_position(upstream_id, previous_position, network_path);
|
self.set_absolute_position(upstream_id, previous_position, network_path);
|
||||||
// Reload click target of the layer which used to encapsulate the chain
|
// Reload click target of the layer which used to encapsulate the chain
|
||||||
self.unload_node_click_targets(&downstream_layer.to_node(), network_path);
|
self.unload_node_click_targets(&downstream_layer, network_path);
|
||||||
}
|
}
|
||||||
// If there is an upstream layer then stop breaking the chain
|
// If there is an upstream layer then stop breaking the chain
|
||||||
else {
|
else {
|
||||||
@@ -5297,8 +5348,8 @@ impl NodeNetworkInterface {
|
|||||||
// Deselect chain nodes upstream from a selected layer
|
// Deselect chain nodes upstream from a selected layer
|
||||||
if self.is_chain(selected_node, network_path)
|
if self.is_chain(selected_node, network_path)
|
||||||
&& self
|
&& self
|
||||||
.downstream_layer(selected_node, network_path)
|
.downstream_layer_for_chain_node(selected_node, network_path)
|
||||||
.is_some_and(|downstream_layer| node_ids.contains(&downstream_layer.to_node()))
|
.is_some_and(|downstream_layer| node_ids.contains(&downstream_layer))
|
||||||
{
|
{
|
||||||
node_ids.remove(selected_node);
|
node_ids.remove(selected_node);
|
||||||
}
|
}
|
||||||
@@ -5947,31 +5998,6 @@ impl NodeNetworkInterface {
|
|||||||
self.create_wire(&OutputConnector::node(*node_id, 0), &InputConnector::node(parent.to_node(), 1), network_path);
|
self.create_wire(&OutputConnector::node(*node_id, 0), &InputConnector::node(parent.to_node(), 1), network_path);
|
||||||
self.set_chain_position(node_id, network_path);
|
self.set_chain_position(node_id, network_path);
|
||||||
} else {
|
} else {
|
||||||
// TODO: Implement a more robust horizontal shift system when inserting a node into a chain.
|
|
||||||
// This should be done by breaking the chain and shifting the sole dependents for each node upstream of the insertion.
|
|
||||||
// Before inserting the node, shift the layer right 7 units so that all sole dependents are also shifted
|
|
||||||
// let input_connector = InputConnector::node(parent.to_node(), 0);
|
|
||||||
// let old_upstream = self.upstream_output_connector(&input_connector, network_path);
|
|
||||||
// This also needs to disconnect from the downstream layer
|
|
||||||
// self.disconnect_input(&input_connector, network_path);
|
|
||||||
// let Some(selected_nodes) = self.selected_nodes_mut(network_path) else {
|
|
||||||
// log::error!("Could not get selected nodes in move_layer_to_stack");
|
|
||||||
// return;
|
|
||||||
// };
|
|
||||||
// let old_selected_nodes = selected_nodes.replace_with(vec![parent.to_node()]);
|
|
||||||
|
|
||||||
// for _ in 0..7 {
|
|
||||||
// self.shift_selected_nodes(Direction::Left, false, network_path);
|
|
||||||
// }
|
|
||||||
// // Grip drag it back to the right
|
|
||||||
// for _ in 0..7 {
|
|
||||||
// self.shift_selected_nodes(Direction::Right, true, network_path);
|
|
||||||
// }
|
|
||||||
// let _ = self.selected_nodes_mut(network_path).unwrap().replace_with(old_selected_nodes);
|
|
||||||
// if let Some(old_upstream) = old_upstream {
|
|
||||||
// self.create_wire(&old_upstream, &input_connector, network_path);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Insert the node in the gap and set the upstream to a chain
|
// Insert the node in the gap and set the upstream to a chain
|
||||||
self.insert_node_between(node_id, &InputConnector::node(parent.to_node(), 1), 0, network_path);
|
self.insert_node_between(node_id, &InputConnector::node(parent.to_node(), 1), 0, network_path);
|
||||||
self.force_set_upstream_to_chain(node_id, network_path);
|
self.force_set_upstream_to_chain(node_id, network_path);
|
||||||
@@ -6473,6 +6499,7 @@ pub struct Vec2InputSettings {
|
|||||||
pub y: String,
|
pub y: String,
|
||||||
pub unit: String,
|
pub unit: String,
|
||||||
pub min: Option<f64>,
|
pub min: Option<f64>,
|
||||||
|
pub is_integer: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
@@ -6547,6 +6574,7 @@ impl InputPersistentMetadata {
|
|||||||
self.input_data.insert("x".to_string(), json!(vec2_properties.x));
|
self.input_data.insert("x".to_string(), json!(vec2_properties.x));
|
||||||
self.input_data.insert("y".to_string(), json!(vec2_properties.y));
|
self.input_data.insert("y".to_string(), json!(vec2_properties.y));
|
||||||
self.input_data.insert("unit".to_string(), json!(vec2_properties.unit));
|
self.input_data.insert("unit".to_string(), json!(vec2_properties.unit));
|
||||||
|
self.input_data.insert("is_integer".to_string(), Value::Bool(vec2_properties.is_integer));
|
||||||
if let Some(min) = vec2_properties.min {
|
if let Some(min) = vec2_properties.min {
|
||||||
self.input_data.insert("min".to_string(), json!(min));
|
self.input_data.insert("min".to_string(), json!(min));
|
||||||
}
|
}
|
||||||
@@ -6776,13 +6804,6 @@ impl From<DocumentNodePersistentMetadataPropertiesRow> for DocumentNodePersisten
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize)]
|
|
||||||
enum NodePersistentMetadataVersions {
|
|
||||||
DocumentNodePersistentMetadataPropertiesRow(DocumentNodePersistentMetadataPropertiesRow),
|
|
||||||
NodePersistentMetadataInputNames(DocumentNodePersistentMetadataInputNames),
|
|
||||||
NodePersistentMetadata(DocumentNodePersistentMetadata),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_node_persistent_metadata<'de, D>(deserializer: D) -> Result<DocumentNodePersistentMetadata, D::Error>
|
fn deserialize_node_persistent_metadata<'de, D>(deserializer: D) -> Result<DocumentNodePersistentMetadata, D::Error>
|
||||||
where
|
where
|
||||||
D: serde::Deserializer<'de>,
|
D: serde::Deserializer<'de>,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user