mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-09-02 11:17:20 +00:00
resolved conflicts
This commit is contained in:
parent
3603426aee
commit
1338310d9f
256 changed files with 5951 additions and 20321 deletions
40
.github/workflows/website.yml
vendored
40
.github/workflows/website.yml
vendored
|
@ -63,14 +63,14 @@ jobs:
|
|||
mkdir artifacts
|
||||
mv hierarchical_message_system_tree.txt artifacts/hierarchical_message_system_tree.txt
|
||||
|
||||
- name: 🚚 Move `artifacts` contents to `website/other/editor-structure`
|
||||
- name: 🚚 Move `artifacts` contents to the project root
|
||||
run: |
|
||||
mv artifacts/* website/other/editor-structure
|
||||
mv artifacts/* .
|
||||
|
||||
- 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
|
||||
cd website
|
||||
npm run generate-editor-structure
|
||||
|
||||
- name: 🌐 Build Graphite website with Zola
|
||||
env:
|
||||
|
@ -80,38 +80,6 @@ jobs:
|
|||
npm run install-fonts
|
||||
zola --config config.toml build --minify
|
||||
|
||||
- name: 💿 Restore cache of `website/other/dist` directory, if available and `website/other` didn't change
|
||||
if: steps.changes.outputs.website-other != 'true'
|
||||
id: cache-website-other-dist
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: website/other/dist
|
||||
key: website-other-dist-${{ runner.os }}
|
||||
|
||||
- name: 🟢 Set up Node only if we are going to build in the next step
|
||||
if: steps.cache-website-other-dist.outputs.cache-hit != 'true'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "latest"
|
||||
|
||||
- name: 📁 Build `website/other` directory only if changed or not cached
|
||||
if: steps.cache-website-other-dist.outputs.cache-hit != 'true'
|
||||
id: build-website-other
|
||||
run: |
|
||||
sh website/other/build.sh
|
||||
|
||||
- name: 💾 Save cache of `website/other/dist` directory if it was built above
|
||||
if: steps.cache-website-other-dist.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
path: website/other/dist
|
||||
key: ${{ steps.cache-website-other-dist.outputs.cache-primary-key }}
|
||||
|
||||
- name: 🚚 Move `website/other/dist` contents to `website/public`
|
||||
run: |
|
||||
mkdir -p website/public
|
||||
mv website/other/dist/* website/public
|
||||
|
||||
- name: 📤 Publish to Cloudflare Pages
|
||||
id: cloudflare
|
||||
uses: cloudflare/pages-action@1
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -8,3 +8,4 @@ flamegraph.svg
|
|||
.idea/
|
||||
.direnv
|
||||
hierarchical_message_system_tree.txt
|
||||
hierarchical_message_system_tree.html
|
||||
|
|
|
@ -40,10 +40,10 @@
|
|||
};
|
||||
|
||||
libcef = pkgs.libcef.overrideAttrs (finalAttrs: previousAttrs: {
|
||||
version = "138.0.26";
|
||||
gitRevision = "84f2d27";
|
||||
chromiumVersion = "138.0.7204.158";
|
||||
srcHash = "sha256-d9jQJX7rgdoHfROD3zmOdMSesRdKE3slB5ZV+U2wlbQ=";
|
||||
version = "139.0.17";
|
||||
gitRevision = "6c347eb";
|
||||
chromiumVersion = "139.0.7258.31";
|
||||
srcHash = "sha256-kRMO8DP4El1qytDsAZBdHvR9AAHXce90nPdyfJailBg=";
|
||||
|
||||
__intentionallyOverridingVersion = true;
|
||||
|
||||
|
@ -75,6 +75,12 @@
|
|||
vulkan-loader
|
||||
libraw
|
||||
libGL
|
||||
|
||||
# X11 libraries, not needed on wayland! Remove when x11 is finally dead
|
||||
libxkbcommon
|
||||
xorg.libXcursor
|
||||
xorg.libxcb
|
||||
xorg.libX11
|
||||
];
|
||||
|
||||
# Development tools that don't need to be in LD_LIBRARY_PATH
|
||||
|
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -35,7 +35,7 @@
|
|||
"rust-analyzer.cargo.allTargets": false,
|
||||
// ESLint config
|
||||
"eslint.format.enable": true,
|
||||
"eslint.workingDirectories": ["./frontend", "./website/other/bezier-rs-demos", "./website"],
|
||||
"eslint.workingDirectories": ["./frontend", "./website"],
|
||||
"eslint.validate": ["javascript", "typescript", "svelte"],
|
||||
// Svelte config
|
||||
"svelte.plugin.svelte.compilerWarnings": {
|
||||
|
|
60
Cargo.lock
generated
60
Cargo.lock
generated
|
@ -461,27 +461,6 @@ version = "0.22.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bezier-rs"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"dyn-any",
|
||||
"glam",
|
||||
"kurbo",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bezier-rs-wasm"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bezier-rs",
|
||||
"glam",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
|
@ -683,19 +662,20 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cef"
|
||||
version = "138.5.0+138.0.26"
|
||||
version = "139.0.1+139.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7bfa138b538b29584b02e990a631ef44261d9a70d35a77ce4ef624410046fe91"
|
||||
checksum = "39b749cfc4124f9505b3fbe32279c0e93f30831f1ecf3c2cf85863179319cd7b"
|
||||
dependencies = [
|
||||
"cef-dll-sys",
|
||||
"libloading",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cef-dll-sys"
|
||||
version = "138.5.0+138.0.26"
|
||||
version = "139.0.1+139.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "658450855d0ef25af50420b95fbdfacb5df17c7eb20a1615ec995470c26ed643"
|
||||
checksum = "fc42adb0adc477860b705e967d9f899be05ba3775fe4d6dc4d844a1391ffa3dd"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cmake",
|
||||
|
@ -1883,7 +1863,6 @@ dependencies = [
|
|||
name = "graph-craft"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bezier-rs",
|
||||
"criterion",
|
||||
"dyn-any",
|
||||
"glam",
|
||||
|
@ -1961,7 +1940,6 @@ name = "graphene-core"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bezier-rs",
|
||||
"bytemuck",
|
||||
"ctor",
|
||||
"dyn-any",
|
||||
|
@ -1974,6 +1952,7 @@ dependencies = [
|
|||
"num-traits",
|
||||
"parley",
|
||||
"petgraph 0.7.1",
|
||||
"poly-cool",
|
||||
"rand 0.9.1",
|
||||
"rand_chacha 0.9.0",
|
||||
"rustc-hash 2.1.1",
|
||||
|
@ -2018,7 +1997,6 @@ dependencies = [
|
|||
name = "graphene-path-bool"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bezier-rs",
|
||||
"dyn-any",
|
||||
"glam",
|
||||
"graphene-core",
|
||||
|
@ -2033,7 +2011,6 @@ dependencies = [
|
|||
name = "graphene-raster-nodes"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bezier-rs",
|
||||
"bytemuck",
|
||||
"dyn-any",
|
||||
"fastnoise-lite",
|
||||
|
@ -2042,6 +2019,7 @@ dependencies = [
|
|||
"graphene-core",
|
||||
"graphene-core-shaders",
|
||||
"image",
|
||||
"kurbo",
|
||||
"ndarray",
|
||||
"node-macro",
|
||||
"rand 0.9.1",
|
||||
|
@ -2056,10 +2034,7 @@ name = "graphene-std"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytemuck",
|
||||
"dyn-any",
|
||||
"fastnoise-lite",
|
||||
"futures",
|
||||
"glam",
|
||||
"graph-craft",
|
||||
"graphene-application-io",
|
||||
|
@ -2071,10 +2046,7 @@ dependencies = [
|
|||
"graphene-svg-renderer",
|
||||
"image",
|
||||
"log",
|
||||
"ndarray",
|
||||
"node-macro",
|
||||
"rand 0.9.1",
|
||||
"rand_chacha 0.9.0",
|
||||
"reqwest",
|
||||
"tokio",
|
||||
"vello",
|
||||
|
@ -2089,10 +2061,10 @@ name = "graphene-svg-renderer"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bezier-rs",
|
||||
"dyn-any",
|
||||
"glam",
|
||||
"graphene-core",
|
||||
"kurbo",
|
||||
"log",
|
||||
"num-traits",
|
||||
"serde",
|
||||
|
@ -2113,6 +2085,7 @@ dependencies = [
|
|||
"graph-craft",
|
||||
"graphene-std",
|
||||
"graphite-editor",
|
||||
"image",
|
||||
"include_dir",
|
||||
"open",
|
||||
"rfd",
|
||||
|
@ -2130,9 +2103,8 @@ dependencies = [
|
|||
name = "graphite-editor"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bezier-rs",
|
||||
"base64 0.22.1",
|
||||
"bitflags 2.9.1",
|
||||
"bytemuck",
|
||||
"derivative",
|
||||
"dyn-any",
|
||||
"env_logger",
|
||||
|
@ -2148,18 +2120,15 @@ dependencies = [
|
|||
"num_enum",
|
||||
"once_cell",
|
||||
"preprocessor",
|
||||
"ron",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"specta",
|
||||
"spin",
|
||||
"thiserror 2.0.12",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"usvg",
|
||||
"vello",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"wgpu-executor",
|
||||
]
|
||||
|
@ -4040,6 +4009,15 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
|
||||
|
||||
[[package]]
|
||||
name = "poly-cool"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6113ca52ade3ae52044cf0519e2c78ffa82120f6fa82f5099c8a4fd3ec8de43"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.11.1"
|
||||
|
|
27
Cargo.toml
27
Cargo.toml
|
@ -20,9 +20,7 @@ members = [
|
|||
"node-graph/preprocessor",
|
||||
"libraries/dyn-any",
|
||||
"libraries/path-bool",
|
||||
"libraries/bezier-rs",
|
||||
"libraries/math-parser",
|
||||
"website/other/bezier-rs-demos/wasm",
|
||||
]
|
||||
default-members = [
|
||||
"editor",
|
||||
|
@ -44,8 +42,13 @@ resolver = "2"
|
|||
|
||||
[workspace.dependencies]
|
||||
# Local dependencies
|
||||
bezier-rs = { path = "libraries/bezier-rs", features = ["dyn-any", "serde"] }
|
||||
dyn-any = { path = "libraries/dyn-any", features = ["derive", "glam", "reqwest", "log-bad-types", "rc"] }
|
||||
dyn-any = { path = "libraries/dyn-any", features = [
|
||||
"derive",
|
||||
"glam",
|
||||
"reqwest",
|
||||
"log-bad-types",
|
||||
"rc",
|
||||
] }
|
||||
preprocessor = { path = "node-graph/preprocessor" }
|
||||
math-parser = { path = "libraries/math-parser" }
|
||||
path-bool = { path = "libraries/path-bool" }
|
||||
|
@ -82,7 +85,6 @@ thiserror = "2"
|
|||
anyhow = "1.0"
|
||||
proc-macro2 = { version = "1", features = ["span-locations"] }
|
||||
quote = "1.0"
|
||||
axum = "0.8"
|
||||
chrono = "0.4"
|
||||
ron = "0.8"
|
||||
fastnoise-lite = "1.1"
|
||||
|
@ -122,9 +124,17 @@ resvg = "0.44"
|
|||
usvg = "0.44"
|
||||
rand = { version = "0.9", default-features = false, features = ["std_rng"] }
|
||||
rand_chacha = "0.9"
|
||||
glam = { version = "0.29", default-features = false, features = ["serde", "scalar-math", "debug-glam-assert"] }
|
||||
glam = { version = "0.29", default-features = false, features = [
|
||||
"serde",
|
||||
"scalar-math",
|
||||
"debug-glam-assert",
|
||||
] }
|
||||
base64 = "0.22"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp"] }
|
||||
image = { version = "0.25", default-features = false, features = [
|
||||
"png",
|
||||
"jpeg",
|
||||
"bmp",
|
||||
] }
|
||||
parley = "0.5.0"
|
||||
skrifa = "0.32.0"
|
||||
pretty_assertions = "1.4.1"
|
||||
|
@ -159,12 +169,13 @@ iai-callgrind = { version = "0.12.3" }
|
|||
ndarray = "0.16.1"
|
||||
strum = { version = "0.26.3", features = ["derive"] }
|
||||
dirs = "6.0"
|
||||
cef = "138.5.0"
|
||||
cef = "139.0.1"
|
||||
include_dir = "0.7.4"
|
||||
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||
tracing = "0.1.41"
|
||||
rfd = "0.15.4"
|
||||
open = "5.3.2"
|
||||
poly-cool = "0.2.0"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 1
|
||||
|
|
2
demo-artwork/changing-seasons.graphite
generated
2
demo-artwork/changing-seasons.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/isometric-fountain.graphite
generated
2
demo-artwork/isometric-fountain.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/marbled-mandelbrot.graphite
generated
2
demo-artwork/marbled-mandelbrot.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/painted-dreams.graphite
generated
2
demo-artwork/painted-dreams.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/parametric-dunescape.graphite
generated
2
demo-artwork/parametric-dunescape.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/procedural-string-lights.graphite
generated
2
demo-artwork/procedural-string-lights.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/red-dress.graphite
generated
2
demo-artwork/red-dress.graphite
generated
File diff suppressed because one or more lines are too long
2
demo-artwork/valley-of-spires.graphite
generated
2
demo-artwork/valley-of-spires.graphite
generated
File diff suppressed because one or more lines are too long
|
@ -39,3 +39,4 @@ vello = { workspace = true }
|
|||
derivative = { workspace = true }
|
||||
rfd = { workspace = true }
|
||||
open = { workspace = true }
|
||||
image = { workspace = true }
|
||||
|
|
|
@ -7,8 +7,12 @@ use crate::dialogs::dialog_save_graphite_file;
|
|||
use crate::render::GraphicsState;
|
||||
use crate::render::WgpuContext;
|
||||
use graph_craft::wasm_application_io::WasmApplicationIo;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::raster::Image;
|
||||
use graphite_editor::application::Editor;
|
||||
use graphite_editor::consts::DEFAULT_DOCUMENT_NAME;
|
||||
use graphite_editor::messages::prelude::*;
|
||||
use std::fs;
|
||||
use std::sync::Arc;
|
||||
use std::sync::mpsc::Sender;
|
||||
use std::thread;
|
||||
|
@ -57,8 +61,8 @@ impl WinitApp {
|
|||
}
|
||||
|
||||
fn send_messages_to_editor(&mut self, mut responses: Vec<FrontendMessage>) {
|
||||
for message in responses.extract_if(.., |m| matches!(m, FrontendMessage::RenderOverlays(_))) {
|
||||
let FrontendMessage::RenderOverlays(overlay_context) = message else { unreachable!() };
|
||||
for message in responses.extract_if(.., |m| matches!(m, FrontendMessage::RenderOverlays { .. })) {
|
||||
let FrontendMessage::RenderOverlays { context: overlay_context } = message else { unreachable!() };
|
||||
if let Some(graphics_state) = &mut self.graphics_state {
|
||||
let scene = overlay_context.take_scene();
|
||||
graphics_state.set_overlays_scene(scene);
|
||||
|
@ -75,7 +79,7 @@ impl WinitApp {
|
|||
String::new()
|
||||
});
|
||||
let message = PortfolioMessage::OpenDocumentFile {
|
||||
document_name: path.file_name().and_then(|s| s.to_str()).unwrap_or("unknown").to_string(),
|
||||
document_name: path.file_stem().and_then(|s| s.to_str()).unwrap_or("unknown").to_string(),
|
||||
document_serialized_content: content,
|
||||
};
|
||||
let _ = event_loop_proxy.send_event(CustomEvent::DispatchMessage(message.into()));
|
||||
|
@ -264,6 +268,75 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
|
|||
let Some(event) = self.cef_context.handle_window_event(event) else { return };
|
||||
|
||||
match event {
|
||||
// Currently not supported on wayland see https://github.com/rust-windowing/winit/issues/1881
|
||||
WindowEvent::DroppedFile(path) => {
|
||||
let name = path.file_stem().and_then(|s| s.to_str()).map(|s| s.to_string());
|
||||
let Some(extension) = path.extension().and_then(|s| s.to_str()) else {
|
||||
tracing::warn!("Unsupported file dropped: {}", path.display());
|
||||
// Fine to early return since we don't need to do cef work in this case
|
||||
return;
|
||||
};
|
||||
let load_string = |path: &std::path::PathBuf| {
|
||||
let Ok(content) = fs::read_to_string(path) else {
|
||||
tracing::error!("Failed to read file: {}", path.display());
|
||||
return None;
|
||||
};
|
||||
|
||||
if content.is_empty() {
|
||||
tracing::warn!("Dropped file is empty: {}", path.display());
|
||||
return None;
|
||||
}
|
||||
Some(content)
|
||||
};
|
||||
// TODO: Consider moving this logic to the editor so we have one message to load data which is then demultiplexed in the portfolio message handler
|
||||
match extension {
|
||||
"graphite" => {
|
||||
let Some(content) = load_string(&path) else { return };
|
||||
|
||||
let message = PortfolioMessage::OpenDocumentFile {
|
||||
document_name: name.unwrap_or(DEFAULT_DOCUMENT_NAME.to_string()),
|
||||
document_serialized_content: content,
|
||||
};
|
||||
self.dispatch_message(message.into());
|
||||
}
|
||||
"svg" => {
|
||||
let Some(content) = load_string(&path) else { return };
|
||||
|
||||
let message = PortfolioMessage::PasteSvg {
|
||||
name: path.file_stem().map(|s| s.to_string_lossy().to_string()),
|
||||
svg: content,
|
||||
mouse: None,
|
||||
parent_and_insert_index: None,
|
||||
};
|
||||
self.dispatch_message(message.into());
|
||||
}
|
||||
_ => match image::ImageReader::open(&path) {
|
||||
Ok(reader) => match reader.decode() {
|
||||
Ok(image) => {
|
||||
let width = image.width();
|
||||
let height = image.height();
|
||||
// TODO: support loading images with more than 8 bits per channel
|
||||
let image_data = image.to_rgba8();
|
||||
let image = Image::<Color>::from_image_data(image_data.as_raw(), width, height);
|
||||
|
||||
let message = PortfolioMessage::PasteImage {
|
||||
name,
|
||||
image,
|
||||
mouse: None,
|
||||
parent_and_insert_index: None,
|
||||
};
|
||||
self.dispatch_message(message.into());
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to decode image: {}: {}", path.display(), e);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to open image file: {}: {}", path.display(), e);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
WindowEvent::CloseRequested => {
|
||||
tracing::info!("The close button was pressed; stopping");
|
||||
event_loop.exit();
|
||||
|
|
|
@ -44,34 +44,34 @@ var s_diffuse: sampler;
|
|||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let ui = textureSample(t_ui, s_diffuse, in.tex_coords);
|
||||
if (ui.a >= 0.999) {
|
||||
return ui;
|
||||
let ui_linear = textureSample(t_ui, s_diffuse, in.tex_coords);
|
||||
if (ui_linear.a >= 0.999) {
|
||||
return ui_linear;
|
||||
}
|
||||
|
||||
let viewport_coordinate = (in.tex_coords - constants.viewport_offset) * constants.viewport_scale;
|
||||
|
||||
// Vello renders its values to an `RgbaUnorm` texture, but if we try to use this in the main rendering pipeline
|
||||
// which renders to an `Srgb` surface, gamma mapping is applied twice. This converts back to linear to compensate.
|
||||
let overlay_raw = textureSample(t_overlays, s_diffuse, viewport_coordinate);
|
||||
let overlay = vec4<f32>(srgb_to_linear(overlay_raw.rgb), overlay_raw.a);
|
||||
let viewport_raw = textureSample(t_viewport, s_diffuse, viewport_coordinate);
|
||||
let viewport = vec4<f32>(srgb_to_linear(viewport_raw.rgb), viewport_raw.a);
|
||||
let overlay_srgb = textureSample(t_overlays, s_diffuse, viewport_coordinate);
|
||||
let viewport_srgb = textureSample(t_viewport, s_diffuse, viewport_coordinate);
|
||||
|
||||
if (overlay.a < 0.001) {
|
||||
return blend(ui, viewport);
|
||||
// UI texture is premultiplied, we need to unpremultiply before blending
|
||||
let ui_srgb = linear_to_srgb(unpremultiply(ui_linear));
|
||||
|
||||
if (overlay_srgb.a < 0.001) {
|
||||
if (ui_srgb.a < 0.001) {
|
||||
return srgb_to_linear(viewport_srgb);
|
||||
} else {
|
||||
return srgb_to_linear(blend(ui_srgb, viewport_srgb));
|
||||
}
|
||||
}
|
||||
|
||||
let composite = blend(overlay, viewport);
|
||||
return blend(ui, composite);
|
||||
let composite_linear = blend(srgb_to_linear(overlay_srgb), srgb_to_linear(viewport_srgb));
|
||||
|
||||
if (ui_srgb.a < 0.001) {
|
||||
return composite_linear;
|
||||
}
|
||||
|
||||
fn srgb_to_linear(srgb: vec3<f32>) -> vec3<f32> {
|
||||
return select(
|
||||
pow((srgb + 0.055) / 1.055, vec3<f32>(2.4)),
|
||||
srgb / 12.92,
|
||||
srgb <= vec3<f32>(0.04045)
|
||||
);
|
||||
return srgb_to_linear(blend(ui_srgb, linear_to_srgb(composite_linear)));
|
||||
}
|
||||
|
||||
fn blend(fg: vec4<f32>, bg: vec4<f32>) -> vec4<f32> {
|
||||
|
@ -79,3 +79,25 @@ fn blend(fg: vec4<f32>, bg: vec4<f32>) -> vec4<f32> {
|
|||
let rgb = fg.rgb * fg.a + bg.rgb * bg.a * (1.0 - fg.a);
|
||||
return vec4<f32>(rgb, a);
|
||||
}
|
||||
|
||||
fn linear_to_srgb(in: vec4<f32>) -> vec4<f32> {
|
||||
let cutoff = vec3<f32>(0.0031308);
|
||||
let lo = in.rgb * 12.92;
|
||||
let hi = 1.055 * pow(max(in.rgb, vec3<f32>(0.0)), vec3<f32>(1.0/2.4)) - 0.055;
|
||||
return vec4<f32>(select(lo, hi, in.rgb > cutoff), in.a);
|
||||
}
|
||||
|
||||
fn srgb_to_linear(in: vec4<f32>) -> vec4<f32> {
|
||||
let cutoff = vec3<f32>(0.04045);
|
||||
let lo = in.rgb / 12.92;
|
||||
let hi = pow((in.rgb + 0.055) / 1.055, vec3<f32>(2.4));
|
||||
return vec4<f32>(select(lo, hi, in.rgb > cutoff), in.a);
|
||||
}
|
||||
|
||||
fn unpremultiply(in: vec4<f32>) -> vec4<f32> {
|
||||
if (in.a > 0.0) {
|
||||
return vec4<f32>((in.rgb / in.a), in.a);
|
||||
} else {
|
||||
return vec4<f32>(0.0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,18 +12,18 @@ license = "Apache-2.0"
|
|||
|
||||
[features]
|
||||
default = ["wasm"]
|
||||
wasm = ["wasm-bindgen", "graphene-std/wasm", "wasm-bindgen-futures"]
|
||||
wasm = ["wasm-bindgen", "graphene-std/wasm"]
|
||||
gpu = ["interpreted-executor/gpu", "wgpu-executor"]
|
||||
resvg = ["graphene-std/resvg"]
|
||||
vello = ["graphene-std/vello", "resvg"]
|
||||
ron = ["dep:ron"]
|
||||
ron = []
|
||||
|
||||
[dependencies]
|
||||
# Local dependencies
|
||||
graphite-proc-macros = { workspace = true }
|
||||
graph-craft = { workspace = true }
|
||||
interpreted-executor = { workspace = true }
|
||||
graphene-std = { workspace = true }
|
||||
graphene-std = { workspace = true } # NOTE: `graphene-core` should not be added here because `graphene-std` re-exports its contents
|
||||
preprocessor = { workspace = true }
|
||||
|
||||
# Workspace dependencies
|
||||
|
@ -33,7 +33,6 @@ bitflags = { workspace = true }
|
|||
thiserror = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
bezier-rs = { workspace = true }
|
||||
kurbo = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
glam = { workspace = true }
|
||||
|
@ -44,9 +43,8 @@ num_enum = { workspace = true }
|
|||
usvg = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
web-sys = { workspace = true }
|
||||
bytemuck = { workspace = true }
|
||||
vello = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
|
||||
# Required dependencies
|
||||
spin = "0.9.8"
|
||||
|
@ -56,8 +54,6 @@ wgpu-executor = { workspace = true, optional = true }
|
|||
|
||||
# Optional workspace dependencies
|
||||
wasm-bindgen = { workspace = true, optional = true }
|
||||
wasm-bindgen-futures = { workspace = true, optional = true }
|
||||
ron = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
# Workspace dependencies
|
||||
|
|
|
@ -26,7 +26,6 @@ pub struct DispatcherMessageHandlers {
|
|||
pub portfolio_message_handler: PortfolioMessageHandler,
|
||||
preferences_message_handler: PreferencesMessageHandler,
|
||||
tool_message_handler: ToolMessageHandler,
|
||||
workspace_message_handler: WorkspaceMessageHandler,
|
||||
}
|
||||
|
||||
impl DispatcherMessageHandlers {
|
||||
|
@ -53,7 +52,7 @@ const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
|
|||
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
|
||||
];
|
||||
const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[
|
||||
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::AnimationFrame)),
|
||||
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(EventMessageDiscriminant::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.
|
||||
|
@ -231,9 +230,6 @@ impl Dispatcher {
|
|||
|
||||
self.message_handlers.tool_message_handler.process_message(message, &mut queue, context);
|
||||
}
|
||||
Message::Workspace(message) => {
|
||||
self.message_handlers.workspace_message_handler.process_message(message, &mut queue, ());
|
||||
}
|
||||
Message::NoOp => {}
|
||||
Message::Batched { messages } => {
|
||||
messages.iter().for_each(|message| self.handle_message(message.to_owned(), false));
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::messages::prelude::*;
|
||||
|
||||
use super::animation_message_handler::AnimationTimeMode;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
#[impl_message(Message, Animation)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
|
|
|
@ -5,15 +5,15 @@ use crate::messages::prelude::*;
|
|||
pub enum BroadcastMessage {
|
||||
// Sub-messages
|
||||
#[child]
|
||||
TriggerEvent(BroadcastEvent),
|
||||
TriggerEvent(EventMessage),
|
||||
|
||||
// Messages
|
||||
SubscribeEvent {
|
||||
on: BroadcastEvent,
|
||||
on: EventMessage,
|
||||
send: Box<Message>,
|
||||
},
|
||||
UnsubscribeEvent {
|
||||
on: BroadcastEvent,
|
||||
message: Box<Message>,
|
||||
on: EventMessage,
|
||||
send: Box<Message>,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@ use crate::messages::prelude::*;
|
|||
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct BroadcastMessageHandler {
|
||||
listeners: HashMap<BroadcastEvent, Vec<Message>>,
|
||||
event: EventMessageHandler,
|
||||
listeners: HashMap<EventMessage, Vec<Message>>,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
|
@ -10,19 +11,15 @@ impl MessageHandler<BroadcastMessage, ()> for BroadcastMessageHandler {
|
|||
fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque<Message>, _: ()) {
|
||||
match message {
|
||||
// Sub-messages
|
||||
BroadcastMessage::TriggerEvent(event) => {
|
||||
for message in self.listeners.entry(event).or_default() {
|
||||
responses.add_front(message.clone())
|
||||
}
|
||||
}
|
||||
BroadcastMessage::TriggerEvent(message) => self.event.process_message(message, responses, EventMessageContext { listeners: &mut self.listeners }),
|
||||
|
||||
// Messages
|
||||
BroadcastMessage::SubscribeEvent { on, send } => self.listeners.entry(on).or_default().push(*send),
|
||||
BroadcastMessage::UnsubscribeEvent { on, message } => self.listeners.entry(on).or_default().retain(|msg| *msg != *message),
|
||||
BroadcastMessage::UnsubscribeEvent { on, send } => self.listeners.entry(on).or_default().retain(|msg| *msg != *send),
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
actions!(BroadcastEventDiscriminant;)
|
||||
actions!(EventMessageDiscriminant;)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, Hash)]
|
||||
#[impl_message(Message, BroadcastMessage, TriggerEvent)]
|
||||
pub enum BroadcastEvent {
|
||||
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, Hash)]
|
||||
pub enum EventMessage {
|
||||
/// Triggered by requestAnimationFrame in JS
|
||||
AnimationFrame,
|
||||
CanvasTransformed,
|
22
editor/src/messages/broadcast/event/event_message_handler.rs
Normal file
22
editor/src/messages/broadcast/event/event_message_handler.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
use crate::messages::prelude::*;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct EventMessageContext<'a> {
|
||||
pub listeners: &'a mut HashMap<EventMessage, Vec<Message>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
pub struct EventMessageHandler {}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<EventMessage, EventMessageContext<'_>> for EventMessageHandler {
|
||||
fn process_message(&mut self, message: EventMessage, responses: &mut VecDeque<Message>, context: EventMessageContext) {
|
||||
for message in context.listeners.entry(message).or_default() {
|
||||
responses.add_front(message.clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
actions!(EventMessageDiscriminant;)
|
||||
}
|
||||
}
|
7
editor/src/messages/broadcast/event/mod.rs
Normal file
7
editor/src/messages/broadcast/event/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
mod event_message;
|
||||
mod event_message_handler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use event_message::{EventMessage, EventMessageDiscriminant};
|
||||
#[doc(inline)]
|
||||
pub use event_message_handler::{EventMessageContext, EventMessageHandler};
|
|
@ -1,7 +1,7 @@
|
|||
mod broadcast_message;
|
||||
mod broadcast_message_handler;
|
||||
|
||||
pub mod broadcast_event;
|
||||
pub mod event;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use broadcast_message::{BroadcastMessage, BroadcastMessageDiscriminant};
|
||||
|
|
|
@ -3,8 +3,8 @@ use crate::messages::prelude::*;
|
|||
#[impl_message(Message, Defer)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum DeferMessage {
|
||||
SetGraphSubmissionIndex(u64),
|
||||
TriggerGraphRun(u64, DocumentId),
|
||||
SetGraphSubmissionIndex { execution_id: u64 },
|
||||
TriggerGraphRun { execution_id: u64, document_id: DocumentId },
|
||||
AfterGraphRun { messages: Vec<Message> },
|
||||
TriggerNavigationReady,
|
||||
AfterNavigationReady { messages: Vec<Message> },
|
||||
|
|
|
@ -24,10 +24,10 @@ impl MessageHandler<DeferMessage, DeferMessageContext<'_>> for DeferMessageHandl
|
|||
DeferMessage::AfterNavigationReady { messages } => {
|
||||
self.after_viewport_resize.extend_from_slice(&messages);
|
||||
}
|
||||
DeferMessage::SetGraphSubmissionIndex(execution_id) => {
|
||||
DeferMessage::SetGraphSubmissionIndex { execution_id } => {
|
||||
self.current_graph_submission_id = execution_id + 1;
|
||||
}
|
||||
DeferMessage::TriggerGraphRun(execution_id, document_id) => {
|
||||
DeferMessage::TriggerGraphRun { execution_id, document_id } => {
|
||||
let after_graph_run = self.after_graph_run.entry(document_id).or_default();
|
||||
if after_graph_run.is_empty() {
|
||||
return;
|
||||
|
@ -38,9 +38,9 @@ impl MessageHandler<DeferMessage, DeferMessageContext<'_>> for DeferMessageHandl
|
|||
for (_, message) in elements.rev() {
|
||||
responses.add_front(message);
|
||||
}
|
||||
for (id, messages) in self.after_graph_run.iter() {
|
||||
for (&document_id, messages) in self.after_graph_run.iter() {
|
||||
if !messages.is_empty() {
|
||||
responses.add(PortfolioMessage::SubmitGraphRender { document_id: *id, ignore_hash: false });
|
||||
responses.add(PortfolioMessage::SubmitGraphRender { document_id, ignore_hash: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@ use crate::messages::prelude::*;
|
|||
#[impl_message(Message, DialogMessage, ExportDialog)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum ExportDialogMessage {
|
||||
FileType(FileType),
|
||||
ScaleFactor(f64),
|
||||
TransparentBackground(bool),
|
||||
ExportBounds(ExportBounds),
|
||||
FileType { file_type: FileType },
|
||||
ScaleFactor { factor: f64 },
|
||||
TransparentBackground { transparent: bool },
|
||||
ExportBounds { bounds: ExportBounds },
|
||||
|
||||
Submit,
|
||||
}
|
||||
|
|
|
@ -38,10 +38,10 @@ impl MessageHandler<ExportDialogMessage, ExportDialogMessageContext<'_>> for Exp
|
|||
let ExportDialogMessageContext { portfolio } = context;
|
||||
|
||||
match message {
|
||||
ExportDialogMessage::FileType(export_type) => self.file_type = export_type,
|
||||
ExportDialogMessage::ScaleFactor(factor) => self.scale_factor = factor,
|
||||
ExportDialogMessage::TransparentBackground(transparent_background) => self.transparent_background = transparent_background,
|
||||
ExportDialogMessage::ExportBounds(export_area) => self.bounds = export_area,
|
||||
ExportDialogMessage::FileType { file_type } => self.file_type = file_type,
|
||||
ExportDialogMessage::ScaleFactor { factor } => self.scale_factor = factor,
|
||||
ExportDialogMessage::TransparentBackground { transparent } => self.transparent_background = transparent,
|
||||
ExportDialogMessage::ExportBounds { bounds } => self.bounds = bounds,
|
||||
|
||||
ExportDialogMessage::Submit => responses.add_front(PortfolioMessage::SubmitDocumentExport {
|
||||
file_name: portfolio.active_document().map(|document| document.name.clone()).unwrap_or_default(),
|
||||
|
@ -84,7 +84,11 @@ impl LayoutHolder for ExportDialogMessageHandler {
|
|||
fn layout(&self) -> Layout {
|
||||
let entries = [(FileType::Png, "PNG"), (FileType::Jpg, "JPG"), (FileType::Svg, "SVG")]
|
||||
.into_iter()
|
||||
.map(|(val, name)| RadioEntryData::new(format!("{val:?}")).label(name).on_update(move |_| ExportDialogMessage::FileType(val).into()))
|
||||
.map(|(file_type, name)| {
|
||||
RadioEntryData::new(format!("{file_type:?}"))
|
||||
.label(name)
|
||||
.on_update(move |_| ExportDialogMessage::FileType { file_type }.into())
|
||||
})
|
||||
.collect();
|
||||
|
||||
let export_type = vec![
|
||||
|
@ -101,7 +105,7 @@ impl LayoutHolder for ExportDialogMessageHandler {
|
|||
.min(0.)
|
||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
.disabled(self.file_type == FileType::Svg)
|
||||
.on_update(|number_input: &NumberInput| ExportDialogMessage::ScaleFactor(number_input.value.unwrap()).into())
|
||||
.on_update(|number_input: &NumberInput| ExportDialogMessage::ScaleFactor { factor: number_input.value.unwrap() }.into())
|
||||
.min_width(200)
|
||||
.widget_holder(),
|
||||
];
|
||||
|
@ -125,10 +129,10 @@ impl LayoutHolder for ExportDialogMessageHandler {
|
|||
.map(|choice| {
|
||||
choice
|
||||
.into_iter()
|
||||
.map(|(val, name, disabled)| {
|
||||
MenuListEntry::new(format!("{val:?}"))
|
||||
.map(|(bounds, name, disabled)| {
|
||||
MenuListEntry::new(format!("{bounds:?}"))
|
||||
.label(name)
|
||||
.on_commit(move |_| ExportDialogMessage::ExportBounds(val).into())
|
||||
.on_commit(move |_| ExportDialogMessage::ExportBounds { bounds }.into())
|
||||
.disabled(disabled)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
|
@ -151,7 +155,7 @@ impl LayoutHolder for ExportDialogMessageHandler {
|
|||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
CheckboxInput::new(self.transparent_background)
|
||||
.disabled(self.file_type == FileType::Jpg)
|
||||
.on_update(move |value: &CheckboxInput| ExportDialogMessage::TransparentBackground(value.checked).into())
|
||||
.on_update(move |value: &CheckboxInput| ExportDialogMessage::TransparentBackground { transparent: value.checked }.into())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
];
|
||||
|
|
|
@ -3,10 +3,10 @@ use crate::messages::prelude::*;
|
|||
#[impl_message(Message, DialogMessage, NewDocumentDialog)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum NewDocumentDialogMessage {
|
||||
Name(String),
|
||||
Infinite(bool),
|
||||
DimensionsX(f64),
|
||||
DimensionsY(f64),
|
||||
Name { name: String },
|
||||
Infinite { infinite: bool },
|
||||
DimensionsX { width: f64 },
|
||||
DimensionsY { height: f64 },
|
||||
|
||||
Submit,
|
||||
}
|
||||
|
|
|
@ -20,10 +20,10 @@ pub struct NewDocumentDialogMessageHandler {
|
|||
impl<'a> MessageHandler<NewDocumentDialogMessage, NewDocumentDialogMessageContext<'a>> for NewDocumentDialogMessageHandler {
|
||||
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, context: NewDocumentDialogMessageContext<'a>) {
|
||||
match message {
|
||||
NewDocumentDialogMessage::Name(name) => self.name = name,
|
||||
NewDocumentDialogMessage::Infinite(infinite) => self.infinite = infinite,
|
||||
NewDocumentDialogMessage::DimensionsX(x) => self.dimensions.x = x as u32,
|
||||
NewDocumentDialogMessage::DimensionsY(y) => self.dimensions.y = y as u32,
|
||||
NewDocumentDialogMessage::Name { name } => self.name = name,
|
||||
NewDocumentDialogMessage::Infinite { infinite } => self.infinite = infinite,
|
||||
NewDocumentDialogMessage::DimensionsX { width } => self.dimensions.x = width as u32,
|
||||
NewDocumentDialogMessage::DimensionsY { height } => self.dimensions.y = height as u32,
|
||||
NewDocumentDialogMessage::Submit => {
|
||||
responses.add(PortfolioMessage::NewDocumentWithName { name: self.name.clone() });
|
||||
|
||||
|
@ -82,7 +82,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
|
|||
TextLabel::new("Name").table_align(true).min_width(90).widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
TextInput::new(&self.name)
|
||||
.on_update(|text_input: &TextInput| NewDocumentDialogMessage::Name(text_input.value.clone()).into())
|
||||
.on_update(|text_input: &TextInput| NewDocumentDialogMessage::Name { name: text_input.value.clone() }.into())
|
||||
.min_width(204) // Matches the 100px of both NumberInputs below + the 4px of the Unrelated-type separator
|
||||
.widget_holder(),
|
||||
];
|
||||
|
@ -92,7 +92,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
|
|||
TextLabel::new("Infinite Canvas").table_align(true).min_width(90).for_checkbox(checkbox_id).widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
CheckboxInput::new(self.infinite)
|
||||
.on_update(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite(checkbox_input.checked).into())
|
||||
.on_update(|checkbox_input: &CheckboxInput| NewDocumentDialogMessage::Infinite { infinite: checkbox_input.checked }.into())
|
||||
.for_label(checkbox_id)
|
||||
.widget_holder(),
|
||||
];
|
||||
|
@ -108,7 +108,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
|
|||
.is_integer(true)
|
||||
.disabled(self.infinite)
|
||||
.min_width(100)
|
||||
.on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsX(number_input.value.unwrap()).into())
|
||||
.on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsX { width: number_input.value.unwrap() }.into())
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
NumberInput::new(Some(self.dimensions.y as f64))
|
||||
|
@ -119,7 +119,7 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
|
|||
.is_integer(true)
|
||||
.disabled(self.infinite)
|
||||
.min_width(100)
|
||||
.on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsY(number_input.value.unwrap()).into())
|
||||
.on_update(|number_input: &NumberInput| NewDocumentDialogMessage::DimensionsY { height: number_input.value.unwrap() }.into())
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::messages::broadcast::broadcast_event::BroadcastEvent;
|
||||
use crate::messages::broadcast::event::EventMessage;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
|
@ -27,7 +27,7 @@ impl DialogLayoutHolder for CloseDocumentDialog {
|
|||
TextButton::new("Discard")
|
||||
.on_update(move |_| {
|
||||
DialogMessage::CloseDialogAndThen {
|
||||
followups: vec![BroadcastEvent::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()],
|
||||
followups: vec![EventMessage::ToolAbort.into(), PortfolioMessage::CloseDocument { document_id }.into()],
|
||||
}
|
||||
.into()
|
||||
})
|
||||
|
|
|
@ -149,11 +149,16 @@ pub enum FrontendMessage {
|
|||
UpdateGraphViewOverlay {
|
||||
open: bool,
|
||||
},
|
||||
UpdateSpreadsheetState {
|
||||
UpdateDataPanelState {
|
||||
open: bool,
|
||||
node: Option<NodeId>,
|
||||
},
|
||||
UpdateSpreadsheetLayout {
|
||||
UpdatePropertiesPanelState {
|
||||
open: bool,
|
||||
},
|
||||
UpdateLayersPanelState {
|
||||
open: bool,
|
||||
},
|
||||
UpdateDataPanelLayout {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
layout_target: LayoutTarget,
|
||||
diff: Vec<WidgetDiff>,
|
||||
|
@ -296,7 +301,7 @@ pub enum FrontendMessage {
|
|||
#[serde(rename = "openDocuments")]
|
||||
open_documents: Vec<FrontendDocumentDetails>,
|
||||
},
|
||||
UpdatePropertyPanelSectionsLayout {
|
||||
UpdatePropertiesPanelLayout {
|
||||
#[serde(rename = "layoutTarget")]
|
||||
layout_target: LayoutTarget,
|
||||
diff: Vec<WidgetDiff>,
|
||||
|
@ -330,9 +335,9 @@ pub enum FrontendMessage {
|
|||
active: bool,
|
||||
},
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
RenderOverlays(
|
||||
RenderOverlays {
|
||||
#[serde(skip, default = "OverlayContext::default")]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
OverlayContext,
|
||||
),
|
||||
context: OverlayContext,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(PointerMove; refresh_keys=[Control, Shift], action_dispatch=TransformLayerMessage::PointerMove { slow_key: Shift, increments_key: Control }),
|
||||
//
|
||||
// SelectToolMessage
|
||||
entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=SelectToolMessage::PointerMove(SelectToolPointerKeys { axis_align: Shift, snap_angle: Shift, center: Alt, duplicate: Alt })),
|
||||
entry!(PointerMove; refresh_keys=[Control, Alt, Shift], action_dispatch=SelectToolMessage::PointerMove { modifier_keys: SelectToolPointerKeys { axis_align: Shift, snap_angle: Shift, center: Alt, duplicate: Alt } }),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=SelectToolMessage::DragStart { extend_selection: Shift, remove_from_selection: Alt, select_deepest: Accel, lasso_select: Control, skew: Control }),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=SelectToolMessage::DragStop { remove_from_selection: Alt }),
|
||||
entry!(KeyDown(Enter); action_dispatch=SelectToolMessage::Enter),
|
||||
|
@ -178,7 +178,7 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(KeyDown(Escape); action_dispatch=ShapeToolMessage::Abort),
|
||||
entry!(KeyDown(BracketLeft); action_dispatch=ShapeToolMessage::DecreaseSides),
|
||||
entry!(KeyDown(BracketRight); action_dispatch=ShapeToolMessage::IncreaseSides),
|
||||
entry!(PointerMove; refresh_keys=[Alt, Shift, Control], action_dispatch=ShapeToolMessage::PointerMove([Alt, Shift, Control])),
|
||||
entry!(PointerMove; refresh_keys=[Alt, Shift, Control], action_dispatch=ShapeToolMessage::PointerMove { modifier: [Alt, Shift, Control] }),
|
||||
entry!(KeyDown(ArrowUp); modifiers=[Shift, ArrowLeft], action_dispatch=ShapeToolMessage::NudgeSelectedLayers { delta_x: -BIG_NUDGE_AMOUNT, delta_y: -BIG_NUDGE_AMOUNT, resize: Alt, resize_opposite_corner: Control }),
|
||||
entry!(KeyDown(ArrowUp); modifiers=[Shift, 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 }),
|
||||
|
@ -297,8 +297,8 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(PointerMove; action_dispatch=BrushToolMessage::PointerMove),
|
||||
entry!(KeyDown(MouseLeft); action_dispatch=BrushToolMessage::DragStart),
|
||||
entry!(KeyUp(MouseLeft); action_dispatch=BrushToolMessage::DragStop),
|
||||
entry!(KeyDown(BracketLeft); action_dispatch=BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ChangeDiameter(-BRUSH_SIZE_CHANGE_KEYBOARD))),
|
||||
entry!(KeyDown(BracketRight); action_dispatch=BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ChangeDiameter(BRUSH_SIZE_CHANGE_KEYBOARD))),
|
||||
entry!(KeyDown(BracketLeft); action_dispatch=BrushToolMessage::UpdateOptions { options: BrushToolMessageOptionsUpdate::ChangeDiameter(-BRUSH_SIZE_CHANGE_KEYBOARD) }),
|
||||
entry!(KeyDown(BracketRight); action_dispatch=BrushToolMessage::UpdateOptions { options: BrushToolMessageOptionsUpdate::ChangeDiameter(BRUSH_SIZE_CHANGE_KEYBOARD) }),
|
||||
entry!(KeyDown(MouseRight); action_dispatch=BrushToolMessage::Abort),
|
||||
entry!(KeyDown(Escape); action_dispatch=BrushToolMessage::Abort),
|
||||
//
|
||||
|
@ -427,6 +427,7 @@ pub fn input_mappings() -> Mapping {
|
|||
entry!(KeyDown(KeyX); modifiers=[Accel], action_dispatch=PortfolioMessage::Cut { clipboard: Clipboard::Device }),
|
||||
entry!(KeyDown(KeyC); modifiers=[Accel], action_dispatch=PortfolioMessage::Copy { clipboard: Clipboard::Device }),
|
||||
entry!(KeyDown(KeyR); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleRulers),
|
||||
entry!(KeyDown(KeyD); modifiers=[Alt], action_dispatch=PortfolioMessage::ToggleDataPanelOpen),
|
||||
//
|
||||
// FrontendMessage
|
||||
entry!(KeyDown(KeyV); modifiers=[Accel], action_dispatch=FrontendMessage::TriggerPaste),
|
||||
|
|
|
@ -3,13 +3,16 @@ use crate::messages::prelude::*;
|
|||
#[impl_message(Message, KeyMapping)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
|
||||
pub enum KeyMappingMessage {
|
||||
// Sub-messages
|
||||
#[child]
|
||||
Lookup(InputMapperMessage),
|
||||
#[child]
|
||||
ModifyMapping(MappingVariant),
|
||||
|
||||
// Messages
|
||||
ModifyMapping {
|
||||
mapping: MappingVariant,
|
||||
},
|
||||
}
|
||||
|
||||
#[impl_message(Message, KeyMappingMessage, ModifyMapping)]
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Default, Hash, serde::Serialize, serde::Deserialize)]
|
||||
pub enum MappingVariant {
|
||||
#[default]
|
||||
|
|
|
@ -19,8 +19,11 @@ impl MessageHandler<KeyMappingMessage, KeyMappingMessageContext<'_>> for KeyMapp
|
|||
let KeyMappingMessageContext { input, actions } = context;
|
||||
|
||||
match message {
|
||||
// Sub-messages
|
||||
KeyMappingMessage::Lookup(input_message) => self.mapping_handler.process_message(input_message, responses, InputMapperMessageContext { input, actions }),
|
||||
KeyMappingMessage::ModifyMapping(new_layout) => self.mapping_handler.set_mapping(new_layout.into()),
|
||||
|
||||
// Messages
|
||||
KeyMappingMessage::ModifyMapping { mapping } => self.mapping_handler.set_mapping(mapping.into()),
|
||||
}
|
||||
}
|
||||
advertise_actions!();
|
||||
|
|
|
@ -2,6 +2,6 @@ mod key_mapping_message;
|
|||
mod key_mapping_message_handler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use key_mapping_message::{KeyMappingMessage, KeyMappingMessageDiscriminant, MappingVariant, MappingVariantDiscriminant};
|
||||
pub use key_mapping_message::{KeyMappingMessage, KeyMappingMessageDiscriminant, MappingVariant};
|
||||
#[doc(inline)]
|
||||
pub use key_mapping_message_handler::{KeyMappingMessageContext, KeyMappingMessageHandler};
|
||||
|
|
|
@ -308,6 +308,7 @@ impl LayoutMessageHandler {
|
|||
|
||||
responses.add(callback_message);
|
||||
}
|
||||
Widget::ImageLabel(_) => {}
|
||||
Widget::IconLabel(_) => {}
|
||||
Widget::InvisibleStandinInput(invisible) => {
|
||||
let callback_message = match action {
|
||||
|
@ -481,18 +482,18 @@ impl LayoutMessageHandler {
|
|||
diff.iter_mut().for_each(|diff| diff.new_value.apply_keyboard_shortcut(action_input_mapping));
|
||||
|
||||
let message = match layout_target {
|
||||
LayoutTarget::MenuBar => unreachable!("Menu bar is not diffed"),
|
||||
LayoutTarget::DialogButtons => FrontendMessage::UpdateDialogButtons { layout_target, diff },
|
||||
LayoutTarget::DialogColumn1 => FrontendMessage::UpdateDialogColumn1 { layout_target, diff },
|
||||
LayoutTarget::DialogColumn2 => FrontendMessage::UpdateDialogColumn2 { layout_target, diff },
|
||||
LayoutTarget::DocumentBar => FrontendMessage::UpdateDocumentBarLayout { layout_target, diff },
|
||||
LayoutTarget::DocumentMode => FrontendMessage::UpdateDocumentModeLayout { layout_target, diff },
|
||||
LayoutTarget::DataPanel => FrontendMessage::UpdateDataPanelLayout { layout_target, diff },
|
||||
LayoutTarget::LayersPanelControlLeftBar => FrontendMessage::UpdateLayersPanelControlBarLeftLayout { layout_target, diff },
|
||||
LayoutTarget::LayersPanelControlRightBar => FrontendMessage::UpdateLayersPanelControlBarRightLayout { layout_target, diff },
|
||||
LayoutTarget::LayersPanelBottomBar => FrontendMessage::UpdateLayersPanelBottomBarLayout { layout_target, diff },
|
||||
LayoutTarget::MenuBar => unreachable!("Menu bar is not diffed"),
|
||||
LayoutTarget::PropertiesPanel => FrontendMessage::UpdatePropertiesPanelLayout { layout_target, diff },
|
||||
LayoutTarget::NodeGraphControlBar => FrontendMessage::UpdateNodeGraphControlBarLayout { layout_target, diff },
|
||||
LayoutTarget::PropertiesSections => FrontendMessage::UpdatePropertyPanelSectionsLayout { layout_target, diff },
|
||||
LayoutTarget::Spreadsheet => FrontendMessage::UpdateSpreadsheetLayout { layout_target, diff },
|
||||
LayoutTarget::ToolOptions => FrontendMessage::UpdateToolOptionsLayout { layout_target, diff },
|
||||
LayoutTarget::ToolShelf => FrontendMessage::UpdateToolShelfLayout { layout_target, diff },
|
||||
LayoutTarget::WorkingColors => FrontendMessage::UpdateWorkingColorsLayout { layout_target, diff },
|
||||
|
|
|
@ -42,9 +42,9 @@ pub enum LayoutTarget {
|
|||
/// Bar at the top of the node graph containing the location and the "Preview" and "Hide" buttons.
|
||||
NodeGraphControlBar,
|
||||
/// The body of the Properties panel containing many collapsable sections.
|
||||
PropertiesSections,
|
||||
PropertiesPanel,
|
||||
/// The spredsheet panel allows for the visualisation of data in the graph.
|
||||
Spreadsheet,
|
||||
DataPanel,
|
||||
/// The bar directly above the canvas, left-aligned and to the right of the document mode dropdown.
|
||||
ToolOptions,
|
||||
/// The vertical buttons for all of the tools on the left of the canvas.
|
||||
|
@ -369,6 +369,7 @@ impl LayoutGroup {
|
|||
Widget::IconButton(x) => &mut x.tooltip,
|
||||
Widget::IconLabel(x) => &mut x.tooltip,
|
||||
Widget::ImageButton(x) => &mut x.tooltip,
|
||||
Widget::ImageLabel(x) => &mut x.tooltip,
|
||||
Widget::NumberInput(x) => &mut x.tooltip,
|
||||
Widget::ParameterExposeButton(x) => &mut x.tooltip,
|
||||
Widget::PopoverButton(x) => &mut x.tooltip,
|
||||
|
@ -546,6 +547,7 @@ pub enum Widget {
|
|||
IconButton(IconButton),
|
||||
IconLabel(IconLabel),
|
||||
ImageButton(ImageButton),
|
||||
ImageLabel(ImageLabel),
|
||||
InvisibleStandinInput(InvisibleStandinInput),
|
||||
NodeCatalog(NodeCatalog),
|
||||
NumberInput(NumberInput),
|
||||
|
@ -622,6 +624,7 @@ impl DiffUpdate {
|
|||
Widget::TextButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
|
||||
Widget::ImageButton(widget) => Some((&mut widget.tooltip, &mut widget.tooltip_shortcut)),
|
||||
Widget::IconLabel(_)
|
||||
| Widget::ImageLabel(_)
|
||||
| Widget::CurveInput(_)
|
||||
| Widget::InvisibleStandinInput(_)
|
||||
| Widget::NodeCatalog(_)
|
||||
|
|
|
@ -168,8 +168,6 @@ pub struct ColorInput {
|
|||
#[widget_builder(constructor)]
|
||||
pub value: FillChoice,
|
||||
|
||||
pub disabled: bool,
|
||||
|
||||
// TODO: Implement
|
||||
// #[serde(rename = "allowTransparency")]
|
||||
// #[derivative(Default(value = "false"))]
|
||||
|
@ -179,9 +177,11 @@ pub struct ColorInput {
|
|||
#[derivative(Default(value = "true"))]
|
||||
pub allow_none: bool,
|
||||
|
||||
// TODO: Implement
|
||||
// pub disabled: bool,
|
||||
//
|
||||
pub disabled: bool,
|
||||
|
||||
#[serde(rename = "menuDirection")]
|
||||
pub menu_direction: Option<MenuDirection>,
|
||||
|
||||
pub tooltip: String,
|
||||
|
||||
#[serde(skip)]
|
||||
|
|
|
@ -416,6 +416,9 @@ pub struct TextInput {
|
|||
#[serde(rename = "minWidth")]
|
||||
pub min_width: u32,
|
||||
|
||||
#[serde(rename = "maxWidth")]
|
||||
pub max_width: u32,
|
||||
|
||||
// Callbacks
|
||||
#[serde(skip)]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
|
|
|
@ -65,4 +65,17 @@ pub struct TextLabel {
|
|||
pub value: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
|
||||
#[derivative(Debug, PartialEq)]
|
||||
pub struct ImageLabel {
|
||||
#[widget_builder(constructor)]
|
||||
pub url: String,
|
||||
|
||||
pub width: Option<String>,
|
||||
|
||||
pub height: Option<String>,
|
||||
|
||||
pub tooltip: String,
|
||||
}
|
||||
|
||||
// TODO: Add UserInputLabel
|
||||
|
|
|
@ -33,14 +33,12 @@ pub enum Message {
|
|||
Preferences(PreferencesMessage),
|
||||
#[child]
|
||||
Tool(ToolMessage),
|
||||
#[child]
|
||||
Workspace(WorkspaceMessage),
|
||||
|
||||
// Messages
|
||||
NoOp,
|
||||
Batched {
|
||||
messages: Box<[Message]>,
|
||||
},
|
||||
NoOp,
|
||||
}
|
||||
|
||||
/// Provides an impl of `specta::Type` for `MessageDiscriminant`, the struct created by `impl_message`.
|
||||
|
@ -71,7 +69,7 @@ mod test {
|
|||
|
||||
fn print_tree_node(tree: &DebugMessageTree, prefix: &str, is_last: bool, file: &mut std::fs::File) {
|
||||
// Print the current node
|
||||
let (branch, child_prefix) = if tree.has_message_handler_data_fields() || tree.has_message_handler_fields() {
|
||||
let (branch, child_prefix) = if tree.message_handler_data_fields().is_some() || tree.message_handler_fields().is_some() {
|
||||
("├── ", format!("{}│ ", prefix))
|
||||
} else {
|
||||
if is_last {
|
||||
|
@ -96,19 +94,32 @@ mod test {
|
|||
}
|
||||
}
|
||||
|
||||
// Print message field if any
|
||||
if let Some(fields) = tree.fields() {
|
||||
let len = fields.len();
|
||||
for (i, field) in fields.iter().enumerate() {
|
||||
let is_last_field = i == len - 1;
|
||||
let branch = if is_last_field { "└── " } else { "├── " };
|
||||
|
||||
file.write_all(format!("{}{}{}\n", child_prefix, branch, field).as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// Print handler field if any
|
||||
if let Some(data) = tree.message_handler_fields() {
|
||||
let len = data.fields().len();
|
||||
let (branch, child_prefix) = if tree.has_message_handler_data_fields() {
|
||||
let (branch, child_prefix) = if tree.message_handler_data_fields().is_some() {
|
||||
("├── ", format!("{}│ ", prefix))
|
||||
} else {
|
||||
("└── ", format!("{} ", prefix))
|
||||
};
|
||||
if data.path().is_empty() {
|
||||
file.write_all(format!("{}{}{}\n", prefix, branch, data.name()).as_bytes()).unwrap();
|
||||
} else {
|
||||
|
||||
const FRONTEND_MESSAGE_STR: &str = "FrontendMessage";
|
||||
if data.name().is_empty() && tree.name() != FRONTEND_MESSAGE_STR {
|
||||
panic!("{}'s MessageHandler is missing #[message_handler_data]", tree.name());
|
||||
} else if tree.name() != FRONTEND_MESSAGE_STR {
|
||||
file.write_all(format!("{}{}{} `{}`\n", prefix, branch, data.name(), data.path()).as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
for (i, field) in data.fields().iter().enumerate() {
|
||||
let is_last_field = i == len - 1;
|
||||
let branch = if is_last_field { "└── " } else { "├── " };
|
||||
|
@ -116,6 +127,7 @@ mod test {
|
|||
file.write_all(format!("{}{}{}\n", child_prefix, branch, field.0).as_bytes()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Print data field if any
|
||||
if let Some(data) = tree.message_handler_data_fields() {
|
||||
|
|
|
@ -16,4 +16,3 @@ pub mod portfolio;
|
|||
pub mod preferences;
|
||||
pub mod prelude;
|
||||
pub mod tool;
|
||||
pub mod workspace;
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
use crate::messages::prelude::*;
|
||||
use crate::node_graph_executor::InspectResult;
|
||||
|
||||
/// The spreadsheet UI allows for graph data to be previewed.
|
||||
#[impl_message(Message, PortfolioMessage, Spreadsheet)]
|
||||
/// The Data panel UI allows the user to visualize the output data of the selected node.
|
||||
#[impl_message(Message, DocumentMessage, DataPanel)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum SpreadsheetMessage {
|
||||
ToggleOpen,
|
||||
|
||||
pub enum DataPanelMessage {
|
||||
UpdateLayout {
|
||||
#[serde(skip)]
|
||||
inspect_result: InspectResult,
|
||||
},
|
||||
ClearLayout,
|
||||
|
||||
PushToElementPath {
|
||||
index: usize,
|
||||
|
@ -19,14 +18,15 @@ pub enum SpreadsheetMessage {
|
|||
len: usize,
|
||||
},
|
||||
|
||||
ViewVectorDomain {
|
||||
domain: VectorDomain,
|
||||
ViewVectorTableTab {
|
||||
tab: VectorTableTab,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub enum VectorDomain {
|
||||
pub enum VectorTableTab {
|
||||
#[default]
|
||||
Properties,
|
||||
Points,
|
||||
Segments,
|
||||
Regions,
|
|
@ -0,0 +1,627 @@
|
|||
use super::VectorTableTab;
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, LayoutTarget, WidgetLayout};
|
||||
use crate::messages::portfolio::document::data_panel::DataPanelMessage;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::Context;
|
||||
use graphene_std::gradient::GradientStops;
|
||||
use graphene_std::memo::IORecord;
|
||||
use graphene_std::raster_types::{CPU, GPU, Raster};
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::vector::style::{Fill, FillChoice};
|
||||
use graphene_std::{Artboard, Graphic};
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct DataPanelMessageContext<'a> {
|
||||
pub network_interface: &'a mut NodeNetworkInterface,
|
||||
pub data_panel_open: bool,
|
||||
}
|
||||
|
||||
/// The data panel allows for graph data to be previewed.
|
||||
#[derive(Default, Debug, Clone, ExtractField)]
|
||||
pub struct DataPanelMessageHandler {
|
||||
introspected_node: Option<NodeId>,
|
||||
introspected_data: Option<Arc<dyn Any + Send + Sync>>,
|
||||
element_path: Vec<usize>,
|
||||
active_vector_table_tab: VectorTableTab,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<DataPanelMessage, DataPanelMessageContext<'_>> for DataPanelMessageHandler {
|
||||
fn process_message(&mut self, message: DataPanelMessage, responses: &mut VecDeque<Message>, context: DataPanelMessageContext) {
|
||||
match message {
|
||||
DataPanelMessage::UpdateLayout { mut inspect_result } => {
|
||||
self.introspected_node = Some(inspect_result.inspect_node);
|
||||
self.introspected_data = inspect_result.take_data();
|
||||
self.update_layout(responses, context);
|
||||
}
|
||||
DataPanelMessage::ClearLayout => {
|
||||
self.introspected_node = None;
|
||||
self.introspected_data = None;
|
||||
self.element_path.clear();
|
||||
self.active_vector_table_tab = VectorTableTab::default();
|
||||
self.update_layout(responses, context);
|
||||
}
|
||||
|
||||
DataPanelMessage::PushToElementPath { index } => {
|
||||
self.element_path.push(index);
|
||||
self.update_layout(responses, context);
|
||||
}
|
||||
DataPanelMessage::TruncateElementPath { len } => {
|
||||
self.element_path.truncate(len);
|
||||
self.update_layout(responses, context);
|
||||
}
|
||||
|
||||
DataPanelMessage::ViewVectorTableTab { tab } => {
|
||||
self.active_vector_table_tab = tab;
|
||||
self.update_layout(responses, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
actions!(DataPanelMessage;)
|
||||
}
|
||||
}
|
||||
|
||||
impl DataPanelMessageHandler {
|
||||
fn update_layout(&mut self, responses: &mut VecDeque<Message>, context: DataPanelMessageContext<'_>) {
|
||||
let DataPanelMessageContext { network_interface, .. } = context;
|
||||
|
||||
let mut layout_data = LayoutData {
|
||||
current_depth: 0,
|
||||
desired_path: &mut self.element_path,
|
||||
breadcrumbs: Vec::new(),
|
||||
vector_table_tab: self.active_vector_table_tab,
|
||||
};
|
||||
|
||||
// Main data visualization
|
||||
let mut layout = self
|
||||
.introspected_data
|
||||
.as_ref()
|
||||
.map(|instrospected_data| generate_layout(instrospected_data, &mut layout_data).unwrap_or_else(|| label("Visualization of this data type is not yet supported")))
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut widgets = Vec::new();
|
||||
|
||||
// Selected layer/node name
|
||||
if let Some(node_id) = self.introspected_node {
|
||||
let is_layer = network_interface.is_layer(&node_id, &[]);
|
||||
|
||||
widgets.extend([
|
||||
if is_layer {
|
||||
IconLabel::new("Layer").tooltip("Name of the selected layer").widget_holder()
|
||||
} else {
|
||||
IconLabel::new("Node").tooltip("Name of the selected node").widget_holder()
|
||||
},
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
TextInput::new(network_interface.display_name(&node_id, &[]))
|
||||
.tooltip(if is_layer { "Name of the selected layer" } else { "Name of the selected node" })
|
||||
.on_update(move |text_input| {
|
||||
NodeGraphMessage::SetDisplayName {
|
||||
node_id,
|
||||
alias: text_input.value.clone(),
|
||||
skip_adding_history_step: false,
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.max_width(200)
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Unrelated).widget_holder(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Element path breadcrumbs
|
||||
if !layout_data.breadcrumbs.is_empty() {
|
||||
let breadcrumb = BreadcrumbTrailButtons::new(layout_data.breadcrumbs)
|
||||
.on_update(|&len| DataPanelMessage::TruncateElementPath { len: len as usize }.into())
|
||||
.widget_holder();
|
||||
widgets.push(breadcrumb);
|
||||
}
|
||||
|
||||
if !widgets.is_empty() {
|
||||
layout.insert(0, LayoutGroup::Row { widgets });
|
||||
}
|
||||
|
||||
responses.add(LayoutMessage::SendLayout {
|
||||
layout: Layout::WidgetLayout(WidgetLayout { layout }),
|
||||
layout_target: LayoutTarget::DataPanel,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct LayoutData<'a> {
|
||||
current_depth: usize,
|
||||
desired_path: &'a mut Vec<usize>,
|
||||
breadcrumbs: Vec<String>,
|
||||
vector_table_tab: VectorTableTab,
|
||||
}
|
||||
|
||||
macro_rules! generate_layout_downcast {
|
||||
($introspected_data:expr, $data:expr, [ $($ty:ty),* $(,)? ]) => {
|
||||
if false { None }
|
||||
$(
|
||||
else if let Some(io) = $introspected_data.downcast_ref::<IORecord<Context, $ty>>() {
|
||||
Some(io.output.layout_with_breadcrumb($data))
|
||||
}
|
||||
)*
|
||||
else { None }
|
||||
}
|
||||
}
|
||||
// TODO: We simply try all these types sequentially. Find a better strategy.
|
||||
fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
|
||||
generate_layout_downcast!(introspected_data, data, [
|
||||
Table<Artboard>,
|
||||
Table<Graphic>,
|
||||
Table<Vector>,
|
||||
Table<Raster<CPU>>,
|
||||
Table<Raster<GPU>>,
|
||||
Table<Color>,
|
||||
Table<GradientStops>,
|
||||
f64,
|
||||
u32,
|
||||
u64,
|
||||
bool,
|
||||
String,
|
||||
Option<f64>,
|
||||
DVec2,
|
||||
DAffine2,
|
||||
])
|
||||
}
|
||||
|
||||
fn column_headings(value: &[&str]) -> Vec<WidgetHolder> {
|
||||
value.iter().map(|text| TextLabel::new(*text).widget_holder()).collect()
|
||||
}
|
||||
|
||||
fn label(x: impl Into<String>) -> Vec<LayoutGroup> {
|
||||
let error = vec![TextLabel::new(x).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets: error }]
|
||||
}
|
||||
|
||||
trait TableRowLayout {
|
||||
fn type_name() -> &'static str;
|
||||
fn identifier(&self) -> String;
|
||||
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
data.breadcrumbs.push(self.identifier());
|
||||
self.element_page(data)
|
||||
}
|
||||
fn element_widget(&self, index: usize) -> WidgetHolder {
|
||||
TextButton::new(self.identifier())
|
||||
.on_update(move |_| DataPanelMessage::PushToElementPath { index }.into())
|
||||
.widget_holder()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TableRowLayout> TableRowLayout for Table<T> {
|
||||
fn type_name() -> &'static str {
|
||||
"Table"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format!("Table<{}> ({} row{})", T::type_name(), self.len(), if self.len() == 1 { "" } else { "s" })
|
||||
}
|
||||
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
if let Some(index) = data.desired_path.get(data.current_depth).copied() {
|
||||
if let Some(row) = self.get(index) {
|
||||
data.current_depth += 1;
|
||||
let result = row.element.layout_with_breadcrumb(data);
|
||||
data.current_depth -= 1;
|
||||
return result;
|
||||
} else {
|
||||
warn!("Desired path truncated");
|
||||
data.desired_path.truncate(data.current_depth);
|
||||
}
|
||||
}
|
||||
|
||||
let mut rows = self
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, row)| {
|
||||
vec![
|
||||
TextLabel::new(format!("{index}")).widget_holder(),
|
||||
row.element.element_widget(index),
|
||||
TextLabel::new(format_transform_matrix(row.transform)).widget_holder(),
|
||||
TextLabel::new(format!("{}", row.alpha_blending)).widget_holder(),
|
||||
TextLabel::new(row.source_node_id.map_or_else(|| "-".to_string(), |id| format!("{}", id.0))).widget_holder(),
|
||||
]
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
rows.insert(0, column_headings(&["", "element", "transform", "alpha_blending", "source_node_id"]));
|
||||
|
||||
vec![LayoutGroup::Table { rows }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for Artboard {
|
||||
fn type_name() -> &'static str {
|
||||
"Artboard"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
self.label.clone()
|
||||
}
|
||||
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
self.content.element_page(data)
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for Graphic {
|
||||
fn type_name() -> &'static str {
|
||||
"Graphic"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
match self {
|
||||
Self::Graphic(table) => table.identifier(),
|
||||
Self::Vector(table) => table.identifier(),
|
||||
Self::RasterCPU(table) => table.identifier(),
|
||||
Self::RasterGPU(table) => table.identifier(),
|
||||
Self::Color(table) => table.identifier(),
|
||||
Self::Gradient(table) => table.identifier(),
|
||||
}
|
||||
}
|
||||
// Don't put a breadcrumb for Graphic
|
||||
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
self.element_page(data)
|
||||
}
|
||||
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
match self {
|
||||
Self::Graphic(table) => table.layout_with_breadcrumb(data),
|
||||
Self::Vector(table) => table.layout_with_breadcrumb(data),
|
||||
Self::RasterCPU(table) => table.layout_with_breadcrumb(data),
|
||||
Self::RasterGPU(table) => table.layout_with_breadcrumb(data),
|
||||
Self::Color(table) => table.layout_with_breadcrumb(data),
|
||||
Self::Gradient(table) => table.layout_with_breadcrumb(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for Vector {
|
||||
fn type_name() -> &'static str {
|
||||
"Vector"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format!(
|
||||
"Vector ({} point{}, {} segment{})",
|
||||
self.point_domain.ids().len(),
|
||||
if self.point_domain.ids().len() == 1 { "" } else { "s" },
|
||||
self.segment_domain.ids().len(),
|
||||
if self.segment_domain.ids().len() == 1 { "" } else { "s" }
|
||||
)
|
||||
}
|
||||
fn element_page(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let table_tab_entries = [VectorTableTab::Properties, VectorTableTab::Points, VectorTableTab::Segments, VectorTableTab::Regions]
|
||||
.into_iter()
|
||||
.map(|tab| {
|
||||
RadioEntryData::new(format!("{tab:?}"))
|
||||
.label(format!("{tab:?}"))
|
||||
.on_update(move |_| DataPanelMessage::ViewVectorTableTab { tab }.into())
|
||||
})
|
||||
.collect();
|
||||
let table_tabs = vec![RadioInput::new(table_tab_entries).selected_index(Some(data.vector_table_tab as u32)).widget_holder()];
|
||||
|
||||
let mut table_rows = Vec::new();
|
||||
match data.vector_table_tab {
|
||||
VectorTableTab::Properties => {
|
||||
table_rows.push(column_headings(&["property", "value"]));
|
||||
|
||||
match self.style.fill.clone() {
|
||||
Fill::None => table_rows.push(vec![
|
||||
TextLabel::new("Fill").widget_holder(),
|
||||
ColorInput::new(FillChoice::None).disabled(true).menu_direction(Some(MenuDirection::Top)).widget_holder(),
|
||||
]),
|
||||
Fill::Solid(color) => table_rows.push(vec![
|
||||
TextLabel::new("Fill").widget_holder(),
|
||||
ColorInput::new(FillChoice::Solid(color)).disabled(true).menu_direction(Some(MenuDirection::Top)).widget_holder(),
|
||||
]),
|
||||
Fill::Gradient(gradient) => {
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Fill").widget_holder(),
|
||||
ColorInput::new(FillChoice::Gradient(gradient.stops))
|
||||
.disabled(true)
|
||||
.menu_direction(Some(MenuDirection::Top))
|
||||
.widget_holder(),
|
||||
]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Fill Gradient Type").widget_holder(),
|
||||
TextLabel::new(gradient.gradient_type.to_string()).widget_holder(),
|
||||
]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Fill Gradient Start").widget_holder(),
|
||||
TextLabel::new(format_dvec2(gradient.start)).widget_holder(),
|
||||
]);
|
||||
table_rows.push(vec![TextLabel::new("Fill Gradient End").widget_holder(), TextLabel::new(format_dvec2(gradient.end)).widget_holder()]);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(stroke) = self.style.stroke.clone() {
|
||||
let color = if let Some(color) = stroke.color { FillChoice::Solid(color) } else { FillChoice::None };
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke").widget_holder(),
|
||||
ColorInput::new(color).disabled(true).menu_direction(Some(MenuDirection::Top)).widget_holder(),
|
||||
]);
|
||||
table_rows.push(vec![TextLabel::new("Stroke Weight").widget_holder(), TextLabel::new(format!("{} px", stroke.weight)).widget_holder()]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke Dash Lengths").widget_holder(),
|
||||
TextLabel::new(if stroke.dash_lengths.is_empty() {
|
||||
"-".to_string()
|
||||
} else {
|
||||
format!("[{}]", stroke.dash_lengths.iter().map(|x| format!("{x} px")).collect::<Vec<_>>().join(", "))
|
||||
})
|
||||
.widget_holder(),
|
||||
]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke Dash Offset").widget_holder(),
|
||||
TextLabel::new(format!("{}", stroke.dash_offset)).widget_holder(),
|
||||
]);
|
||||
table_rows.push(vec![TextLabel::new("Stroke Cap").widget_holder(), TextLabel::new(stroke.cap.to_string()).widget_holder()]);
|
||||
table_rows.push(vec![TextLabel::new("Stroke Join").widget_holder(), TextLabel::new(stroke.join.to_string()).widget_holder()]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke Join Miter Limit").widget_holder(),
|
||||
TextLabel::new(format!("{}", stroke.join_miter_limit)).widget_holder(),
|
||||
]);
|
||||
table_rows.push(vec![TextLabel::new("Stroke Align").widget_holder(), TextLabel::new(stroke.align.to_string()).widget_holder()]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke Transform").widget_holder(),
|
||||
TextLabel::new(format_transform_matrix(&stroke.transform)).widget_holder(),
|
||||
]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke Non-Scaling").widget_holder(),
|
||||
TextLabel::new((if stroke.non_scaling { "Yes" } else { "No" }).to_string()).widget_holder(),
|
||||
]);
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Stroke Paint Order").widget_holder(),
|
||||
TextLabel::new(stroke.paint_order.to_string()).widget_holder(),
|
||||
]);
|
||||
}
|
||||
|
||||
let colinear = self.colinear_manipulators.iter().map(|[a, b]| format!("[{a} / {b}]")).collect::<Vec<_>>().join(", ");
|
||||
let colinear = if colinear.is_empty() { "-".to_string() } else { colinear };
|
||||
table_rows.push(vec![TextLabel::new("Colinear Handle IDs").widget_holder(), TextLabel::new(colinear).widget_holder()]);
|
||||
|
||||
table_rows.push(vec![
|
||||
TextLabel::new("Upstream Nested Layers").widget_holder(),
|
||||
TextLabel::new(if self.upstream_nested_layers.is_some() {
|
||||
"Yes (this preserves references to its upstream nested layers for editing by tools)"
|
||||
} else {
|
||||
"No (this doesn't preserve references to its upstream nested layers for editing by tools)"
|
||||
})
|
||||
.widget_holder(),
|
||||
]);
|
||||
}
|
||||
VectorTableTab::Points => {
|
||||
table_rows.push(column_headings(&["", "position"]));
|
||||
table_rows.extend(
|
||||
self.point_domain
|
||||
.iter()
|
||||
.map(|(id, position)| vec![TextLabel::new(format!("{}", id.inner())).widget_holder(), TextLabel::new(format!("{position}")).widget_holder()]),
|
||||
);
|
||||
}
|
||||
VectorTableTab::Segments => {
|
||||
table_rows.push(column_headings(&["", "start_index", "end_index", "handles"]));
|
||||
table_rows.extend(self.segment_domain.iter().map(|(id, start, end, handles)| {
|
||||
vec![
|
||||
TextLabel::new(format!("{}", id.inner())).widget_holder(),
|
||||
TextLabel::new(format!("{start}")).widget_holder(),
|
||||
TextLabel::new(format!("{end}")).widget_holder(),
|
||||
TextLabel::new(format!("{handles:?}")).widget_holder(),
|
||||
]
|
||||
}));
|
||||
}
|
||||
VectorTableTab::Regions => {
|
||||
table_rows.push(column_headings(&["", "segment_range", "fill"]));
|
||||
table_rows.extend(self.region_domain.iter().map(|(id, segment_range, fill)| {
|
||||
vec![
|
||||
TextLabel::new(format!("{}", id.inner())).widget_holder(),
|
||||
TextLabel::new(format!("{segment_range:?}")).widget_holder(),
|
||||
TextLabel::new(format!("{}", fill.inner())).widget_holder(),
|
||||
]
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
vec![LayoutGroup::Row { widgets: table_tabs }, LayoutGroup::Table { rows: table_rows }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for Raster<CPU> {
|
||||
fn type_name() -> &'static str {
|
||||
"Raster"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format!("Raster ({}x{})", self.width, self.height)
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let base64_string = self.data().base64_string.clone().unwrap_or_else(|| {
|
||||
use base64::Engine;
|
||||
|
||||
let output = self.data().to_png();
|
||||
let preamble = "data:image/png;base64,";
|
||||
let mut base64_string = String::with_capacity(preamble.len() + output.len() * 4);
|
||||
base64_string.push_str(preamble);
|
||||
base64::engine::general_purpose::STANDARD.encode_string(output, &mut base64_string);
|
||||
base64_string
|
||||
});
|
||||
|
||||
let widgets = vec![ImageLabel::new(base64_string).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for Raster<GPU> {
|
||||
fn type_name() -> &'static str {
|
||||
"Raster"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format!("Raster ({}x{})", self.data().width(), self.data().height())
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextLabel::new("Raster is a texture on the GPU and cannot currently be displayed here").widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for Color {
|
||||
fn type_name() -> &'static str {
|
||||
"Color"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format!("Color (#{})", self.to_gamma_srgb().to_rgba_hex_srgb())
|
||||
}
|
||||
fn element_widget(&self, _index: usize) -> WidgetHolder {
|
||||
ColorInput::new(FillChoice::Solid(*self)).disabled(true).menu_direction(Some(MenuDirection::Top)).widget_holder()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![self.element_widget(0)];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for GradientStops {
|
||||
fn type_name() -> &'static str {
|
||||
"Gradient"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format!("Gradient ({} stops)", self.0.len())
|
||||
}
|
||||
fn element_widget(&self, _index: usize) -> WidgetHolder {
|
||||
ColorInput::new(FillChoice::Gradient(self.clone()))
|
||||
.disabled(true)
|
||||
.menu_direction(Some(MenuDirection::Top))
|
||||
.widget_holder()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![self.element_widget(0)];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for f64 {
|
||||
fn type_name() -> &'static str {
|
||||
"Number (f64)"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
"Number (f64)".to_string()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextLabel::new(self.to_string()).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for u32 {
|
||||
fn type_name() -> &'static str {
|
||||
"Number (u32)"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
"Number (u32)".to_string()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextLabel::new(self.to_string()).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for u64 {
|
||||
fn type_name() -> &'static str {
|
||||
"Number (u64)"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
"Number (u64)".to_string()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextLabel::new(self.to_string()).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for bool {
|
||||
fn type_name() -> &'static str {
|
||||
"Bool"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
"Bool".to_string()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextLabel::new(self.to_string()).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for String {
|
||||
fn type_name() -> &'static str {
|
||||
"String"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
"String".to_string()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextAreaInput::new(self.to_string()).disabled(true).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for Option<f64> {
|
||||
fn type_name() -> &'static str {
|
||||
"Option<f64>"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
"Option<f64>".to_string()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextLabel::new(format!("{self:?}")).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for DVec2 {
|
||||
fn type_name() -> &'static str {
|
||||
"Vec2"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
"Vec2".to_string()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextLabel::new(format!("({}, {})", self.x, self.y)).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for DAffine2 {
|
||||
fn type_name() -> &'static str {
|
||||
"Transform"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
"Transform".to_string()
|
||||
}
|
||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let widgets = vec![TextLabel::new(format_transform_matrix(self)).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets }]
|
||||
}
|
||||
}
|
||||
|
||||
fn format_transform_matrix(transform: &DAffine2) -> String {
|
||||
let (scale, angle, translation) = transform.to_scale_angle_translation();
|
||||
let rotation = if angle == -0. { 0. } else { angle.to_degrees() };
|
||||
let round = |x: f64| (x * 1e3).round() / 1e3;
|
||||
|
||||
format!(
|
||||
"Location: ({} px, {} px) — Rotation: {rotation:2}° — Scale: ({}x, {}x)",
|
||||
round(translation.x),
|
||||
round(translation.y),
|
||||
round(scale.x),
|
||||
round(scale.y)
|
||||
)
|
||||
}
|
||||
|
||||
fn format_dvec2(value: DVec2) -> String {
|
||||
let round = |x: f64| (x * 1e3).round() / 1e3;
|
||||
format!("({} px, {} px)", round(value.x), round(value.y))
|
||||
}
|
7
editor/src/messages/portfolio/document/data_panel/mod.rs
Normal file
7
editor/src/messages/portfolio/document/data_panel/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
mod data_panel_message;
|
||||
mod data_panel_message_handler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use data_panel_message::*;
|
||||
#[doc(inline)]
|
||||
pub use data_panel_message_handler::*;
|
|
@ -2,6 +2,7 @@ use std::path::PathBuf;
|
|||
|
||||
use super::utility_types::misc::{GroupFolderType, SnappingState};
|
||||
use crate::messages::input_mapper::utility_types::input_keyboard::Key;
|
||||
use crate::messages::portfolio::document::data_panel::DataPanelMessage;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||
use crate::messages::portfolio::document::overlays::utility_types::OverlaysType;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
|
@ -33,6 +34,8 @@ pub enum DocumentMessage {
|
|||
Overlays(OverlaysMessage),
|
||||
#[child]
|
||||
PropertiesPanel(PropertiesPanelMessage),
|
||||
#[child]
|
||||
DataPanel(DataPanelMessage),
|
||||
|
||||
// Messages
|
||||
AlignSelectedLayers {
|
||||
|
@ -50,7 +53,9 @@ pub enum DocumentMessage {
|
|||
DocumentHistoryBackward,
|
||||
DocumentHistoryForward,
|
||||
DocumentStructureChanged,
|
||||
DrawArtboardOverlays(OverlayContext),
|
||||
DrawArtboardOverlays {
|
||||
context: OverlayContext,
|
||||
},
|
||||
DuplicateSelectedLayers,
|
||||
EnterNestedNetwork {
|
||||
node_id: NodeId,
|
||||
|
@ -69,9 +74,15 @@ pub enum DocumentMessage {
|
|||
open: bool,
|
||||
},
|
||||
GraphViewOverlayToggle,
|
||||
GridOptions(GridSnapping),
|
||||
GridOverlays(OverlayContext),
|
||||
GridVisibility(bool),
|
||||
GridOptions {
|
||||
options: GridSnapping,
|
||||
},
|
||||
GridOverlays {
|
||||
context: OverlayContext,
|
||||
},
|
||||
GridVisibility {
|
||||
visible: bool,
|
||||
},
|
||||
GroupSelectedLayers {
|
||||
group_folder_type: GroupFolderType,
|
||||
},
|
||||
|
|
|
@ -9,6 +9,7 @@ use crate::application::{GRAPHITE_GIT_COMMIT_HASH, generate_uuid};
|
|||
use crate::consts::{ASYMPTOTIC_EFFECT, COLOR_OVERLAY_GRAY, DEFAULT_DOCUMENT_NAME, FILE_SAVE_SUFFIX, SCALE_EFFECT, SCROLLBAR_SPACING, VIEWPORT_ROTATE_SNAP_INTERVAL};
|
||||
use crate::messages::input_mapper::utility_types::macros::action_keys;
|
||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||
use crate::messages::portfolio::document::data_panel::{DataPanelMessageContext, DataPanelMessageHandler};
|
||||
use crate::messages::portfolio::document::graph_operation::utility_types::TransformIn;
|
||||
use crate::messages::portfolio::document::node_graph::NodeGraphMessageContext;
|
||||
use crate::messages::portfolio::document::overlays::grid_overlays::{grid_overlay, overlay_options};
|
||||
|
@ -18,6 +19,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::{Doc
|
|||
use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate, AlignAxis, DocumentMode, FlipAxis, PTZ};
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeTemplate};
|
||||
use crate::messages::portfolio::document::utility_types::nodes::RawBuffer;
|
||||
use crate::messages::portfolio::utility_types::PanelType;
|
||||
use crate::messages::portfolio::utility_types::PersistentData;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils::{self, get_blend_mode, get_fill, get_opacity};
|
||||
|
@ -25,7 +27,6 @@ use crate::messages::tool::tool_messages::select_tool::SelectToolPointerKeys;
|
|||
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
||||
use crate::messages::tool::utility_types::ToolType;
|
||||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput, NodeNetwork, OldNodeNetwork};
|
||||
|
@ -33,10 +34,13 @@ use graphene_std::math::quad::Quad;
|
|||
use graphene_std::path_bool::{boolean_intersect, path_bool_lib};
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::Raster;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||
use graphene_std::vector::misc::{dvec2_to_point, point_to_dvec2};
|
||||
use graphene_std::vector::style::ViewMode;
|
||||
use kurbo::{Affine, CubicBez, Line, ParamCurve, PathSeg, QuadBez};
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
|
@ -49,6 +53,9 @@ pub struct DocumentMessageContext<'a> {
|
|||
pub current_tool: &'a ToolType,
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
pub device_pixel_ratio: f64,
|
||||
pub data_panel_open: bool,
|
||||
pub layers_panel_open: bool,
|
||||
pub properties_panel_open: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)]
|
||||
|
@ -63,9 +70,11 @@ pub struct DocumentMessageHandler {
|
|||
#[serde(skip)]
|
||||
pub node_graph_handler: NodeGraphMessageHandler,
|
||||
#[serde(skip)]
|
||||
overlays_message_handler: OverlaysMessageHandler,
|
||||
pub overlays_message_handler: OverlaysMessageHandler,
|
||||
#[serde(skip)]
|
||||
properties_panel_message_handler: PropertiesPanelMessageHandler,
|
||||
pub properties_panel_message_handler: PropertiesPanelMessageHandler,
|
||||
#[serde(skip)]
|
||||
pub data_panel_message_handler: DataPanelMessageHandler,
|
||||
|
||||
// ============================================
|
||||
// Fields that are saved in the document format
|
||||
|
@ -144,6 +153,7 @@ impl Default for DocumentMessageHandler {
|
|||
node_graph_handler: NodeGraphMessageHandler::default(),
|
||||
overlays_message_handler: OverlaysMessageHandler::default(),
|
||||
properties_panel_message_handler: PropertiesPanelMessageHandler::default(),
|
||||
data_panel_message_handler: DataPanelMessageHandler::default(),
|
||||
// ============================================
|
||||
// Fields that are saved in the document format
|
||||
// ============================================
|
||||
|
@ -186,6 +196,9 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||
current_tool,
|
||||
preferences,
|
||||
device_pixel_ratio,
|
||||
data_panel_open,
|
||||
layers_panel_open,
|
||||
properties_panel_open,
|
||||
} = context;
|
||||
|
||||
match message {
|
||||
|
@ -223,9 +236,20 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||
document_name: self.name.as_str(),
|
||||
executor,
|
||||
persistent_data,
|
||||
properties_panel_open,
|
||||
};
|
||||
self.properties_panel_message_handler.process_message(message, responses, context);
|
||||
}
|
||||
DocumentMessage::DataPanel(message) => {
|
||||
self.data_panel_message_handler.process_message(
|
||||
message,
|
||||
responses,
|
||||
DataPanelMessageContext {
|
||||
network_interface: &mut self.network_interface,
|
||||
data_panel_open,
|
||||
},
|
||||
);
|
||||
}
|
||||
DocumentMessage::NodeGraph(message) => {
|
||||
self.node_graph_handler.process_message(
|
||||
message,
|
||||
|
@ -241,6 +265,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||
graph_fade_artwork_percentage: self.graph_fade_artwork_percentage,
|
||||
navigation_handler: &self.navigation_handler,
|
||||
preferences,
|
||||
layers_panel_open,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -356,14 +381,17 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||
DocumentMessage::DocumentHistoryBackward => self.undo_with_history(ipp, responses),
|
||||
DocumentMessage::DocumentHistoryForward => self.redo_with_history(ipp, responses),
|
||||
DocumentMessage::DocumentStructureChanged => {
|
||||
self.update_layers_panel_control_bar_widgets(responses);
|
||||
self.update_layers_panel_bottom_bar_widgets(responses);
|
||||
|
||||
if layers_panel_open {
|
||||
self.network_interface.load_structure();
|
||||
let data_buffer: RawBuffer = self.serialize_root();
|
||||
|
||||
self.update_layers_panel_control_bar_widgets(layers_panel_open, responses);
|
||||
self.update_layers_panel_bottom_bar_widgets(layers_panel_open, responses);
|
||||
|
||||
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
|
||||
}
|
||||
DocumentMessage::DrawArtboardOverlays(overlay_context) => {
|
||||
}
|
||||
DocumentMessage::DrawArtboardOverlays { context: overlay_context } => {
|
||||
if !overlay_context.visibility_settings.artboard_name() {
|
||||
return;
|
||||
}
|
||||
|
@ -554,24 +582,25 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||
responses.add(NodeGraphMessage::UpdateHints);
|
||||
} else {
|
||||
responses.add(ToolMessage::ActivateTool { tool_type: *current_tool });
|
||||
responses.add(OverlaysMessage::Draw); // Redraw overlays when graph is closed
|
||||
}
|
||||
}
|
||||
DocumentMessage::GraphViewOverlayToggle => {
|
||||
responses.add(DocumentMessage::GraphViewOverlay { open: !self.graph_view_overlay_open });
|
||||
}
|
||||
DocumentMessage::GridOptions(grid) => {
|
||||
self.snapping_state.grid = grid;
|
||||
DocumentMessage::GridOptions { options } => {
|
||||
self.snapping_state.grid = options;
|
||||
self.snapping_state.grid_snapping = true;
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
}
|
||||
DocumentMessage::GridOverlays(mut overlay_context) => {
|
||||
DocumentMessage::GridOverlays { context: mut overlay_context } => {
|
||||
if self.snapping_state.grid_snapping {
|
||||
grid_overlay(self, &mut overlay_context)
|
||||
}
|
||||
}
|
||||
DocumentMessage::GridVisibility(enabled) => {
|
||||
self.snapping_state.grid_snapping = enabled;
|
||||
DocumentMessage::GridVisibility { visible } => {
|
||||
self.snapping_state.grid_snapping = visible;
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
DocumentMessage::GroupSelectedLayers { group_folder_type } => {
|
||||
|
@ -1033,7 +1062,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||
if !parent_layers.is_empty() {
|
||||
let nodes = parent_layers.into_iter().collect();
|
||||
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
}
|
||||
}
|
||||
DocumentMessage::SelectAllLayers => {
|
||||
|
@ -1108,7 +1137,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||
} else {
|
||||
responses.add_front(NodeGraphMessage::SelectedNodesAdd { nodes: vec![id] });
|
||||
}
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
} else {
|
||||
nodes.push(id);
|
||||
}
|
||||
|
@ -1128,7 +1157,6 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||
}
|
||||
}
|
||||
DocumentMessage::SetActivePanel { active_panel: panel } => {
|
||||
use crate::messages::portfolio::utility_types::PanelType;
|
||||
match panel {
|
||||
PanelType::Document => {
|
||||
if self.graph_view_overlay_open {
|
||||
|
@ -1178,7 +1206,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||
Some(overlays_type) => overlays_type,
|
||||
None => {
|
||||
visibility_settings.all = visible;
|
||||
responses.add(BroadcastEvent::ToolAbort);
|
||||
responses.add(EventMessage::ToolAbort);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
return;
|
||||
}
|
||||
|
@ -1201,7 +1229,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||
OverlaysType::Handles => visibility_settings.handles = visible,
|
||||
}
|
||||
|
||||
responses.add(BroadcastEvent::ToolAbort);
|
||||
responses.add(EventMessage::ToolAbort);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
}
|
||||
DocumentMessage::SetRangeSelectionLayer { new_layer } => {
|
||||
|
@ -1415,12 +1443,14 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||
let transform = self.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.center(), &self.document_ptz);
|
||||
self.network_interface.set_document_to_viewport_transform(transform);
|
||||
// Ensure selection box is kept in sync with the pointer when the PTZ changes
|
||||
responses.add(SelectToolMessage::PointerMove(SelectToolPointerKeys {
|
||||
responses.add(SelectToolMessage::PointerMove {
|
||||
modifier_keys: SelectToolPointerKeys {
|
||||
axis_align: Key::Shift,
|
||||
snap_angle: Key::Shift,
|
||||
center: Key::Alt,
|
||||
duplicate: Key::Alt,
|
||||
}));
|
||||
},
|
||||
});
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
} else {
|
||||
let Some(network_metadata) = self.network_interface.network_metadata(&self.breadcrumb_network_path) else {
|
||||
|
@ -1449,11 +1479,11 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||
}
|
||||
DocumentMessage::SelectionStepBack => {
|
||||
self.network_interface.selection_step_back(&self.selection_network_path);
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
}
|
||||
DocumentMessage::SelectionStepForward => {
|
||||
self.network_interface.selection_step_forward(&self.selection_network_path);
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
}
|
||||
DocumentMessage::WrapContentInArtboard { place_artboard_at_origin } => {
|
||||
// Get bounding box of all layers
|
||||
|
@ -2456,7 +2486,7 @@ impl DocumentMessageHandler {
|
|||
.icon("Grid")
|
||||
.tooltip("Grid")
|
||||
.tooltip_shortcut(action_keys!(DocumentMessageDiscriminant::ToggleGridVisibility))
|
||||
.on_update(|optional_input: &CheckboxInput| DocumentMessage::GridVisibility(optional_input.checked).into())
|
||||
.on_update(|optional_input: &CheckboxInput| DocumentMessage::GridVisibility { visible: optional_input.checked }.into())
|
||||
.widget_holder(),
|
||||
PopoverButton::new()
|
||||
.popover_layout(overlay_options(&self.snapping_state.grid))
|
||||
|
@ -2549,7 +2579,11 @@ impl DocumentMessageHandler {
|
|||
responses.add(NodeGraphMessage::ForceRunDocumentGraph);
|
||||
}
|
||||
|
||||
pub fn update_layers_panel_control_bar_widgets(&self, responses: &mut VecDeque<Message>) {
|
||||
pub fn update_layers_panel_control_bar_widgets(&self, layers_panel_open: bool, responses: &mut VecDeque<Message>) {
|
||||
if !layers_panel_open {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get an iterator over the selected layers (excluding artboards which don't have an opacity or blend mode).
|
||||
let selected_nodes = self.network_interface.selected_nodes();
|
||||
let selected_layers_except_artboards = selected_nodes.selected_layers_except_artboards(&self.network_interface);
|
||||
|
@ -2707,7 +2741,11 @@ impl DocumentMessageHandler {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn update_layers_panel_bottom_bar_widgets(&self, responses: &mut VecDeque<Message>) {
|
||||
pub fn update_layers_panel_bottom_bar_widgets(&self, layers_panel_open: bool, responses: &mut VecDeque<Message>) {
|
||||
if !layers_panel_open {
|
||||
return;
|
||||
}
|
||||
|
||||
let selected_nodes = self.network_interface.selected_nodes();
|
||||
let mut selected_layers = selected_nodes.selected_layers(self.metadata());
|
||||
let selected_layer = selected_layers.next();
|
||||
|
@ -2949,10 +2987,10 @@ fn quad_to_path_lib_segments(quad: Quad) -> Vec<path_bool_lib::PathSegment> {
|
|||
}
|
||||
|
||||
fn click_targets_to_path_lib_segments<'a>(click_targets: impl Iterator<Item = &'a ClickTarget>, transform: DAffine2) -> Vec<path_bool_lib::PathSegment> {
|
||||
let segment = |bezier: bezier_rs::Bezier| match bezier.handles {
|
||||
bezier_rs::BezierHandles::Linear => path_bool_lib::PathSegment::Line(bezier.start, bezier.end),
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => path_bool_lib::PathSegment::Quadratic(bezier.start, handle, bezier.end),
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => path_bool_lib::PathSegment::Cubic(bezier.start, handle_start, handle_end, bezier.end),
|
||||
let segment = |bezier: PathSeg| match bezier {
|
||||
PathSeg::Line(line) => path_bool_lib::PathSegment::Line(point_to_dvec2(line.p0), point_to_dvec2(line.p1)),
|
||||
PathSeg::Quad(quad_bez) => path_bool_lib::PathSegment::Quadratic(point_to_dvec2(quad_bez.p0), point_to_dvec2(quad_bez.p1), point_to_dvec2(quad_bez.p2)),
|
||||
PathSeg::Cubic(cubic_bez) => path_bool_lib::PathSegment::Cubic(point_to_dvec2(cubic_bez.p0), point_to_dvec2(cubic_bez.p1), point_to_dvec2(cubic_bez.p2), point_to_dvec2(cubic_bez.p3)),
|
||||
};
|
||||
click_targets
|
||||
.filter_map(|target| {
|
||||
|
@ -2963,7 +3001,7 @@ fn click_targets_to_path_lib_segments<'a>(click_targets: impl Iterator<Item = &'
|
|||
}
|
||||
})
|
||||
.flatten()
|
||||
.map(|bezier| segment(bezier.apply_transformation(|x| transform.transform_point2(x))))
|
||||
.map(|bezier| segment(Affine::new(transform.to_cols_array()) * bezier))
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
@ -2986,11 +3024,11 @@ impl<'a> ClickXRayIter<'a> {
|
|||
|
||||
/// Handles the checking of the layer where the target is a rect or path
|
||||
fn check_layer_area_target(&mut self, click_targets: Option<&Vec<ClickTarget>>, clip: bool, layer: LayerNodeIdentifier, path: Vec<path_bool_lib::PathSegment>, transform: DAffine2) -> XRayResult {
|
||||
// Convert back to Bezier-rs types for intersections
|
||||
// Convert back to Kurbo types for intersections
|
||||
let segment = |bezier: &path_bool_lib::PathSegment| match *bezier {
|
||||
path_bool_lib::PathSegment::Line(start, end) => bezier_rs::Bezier::from_linear_dvec2(start, end),
|
||||
path_bool_lib::PathSegment::Cubic(start, h1, h2, end) => bezier_rs::Bezier::from_cubic_dvec2(start, h1, h2, end),
|
||||
path_bool_lib::PathSegment::Quadratic(start, h1, end) => bezier_rs::Bezier::from_quadratic_dvec2(start, h1, end),
|
||||
path_bool_lib::PathSegment::Line(start, end) => PathSeg::Line(Line::new(dvec2_to_point(start), dvec2_to_point(end))),
|
||||
path_bool_lib::PathSegment::Cubic(start, h1, h2, end) => PathSeg::Cubic(CubicBez::new(dvec2_to_point(start), dvec2_to_point(h1), dvec2_to_point(h2), dvec2_to_point(end))),
|
||||
path_bool_lib::PathSegment::Quadratic(start, h1, end) => PathSeg::Quad(QuadBez::new(dvec2_to_point(start), dvec2_to_point(h1), dvec2_to_point(end))),
|
||||
path_bool_lib::PathSegment::Arc(_, _, _, _, _, _, _) => unimplemented!(),
|
||||
};
|
||||
let get_clip = || path.iter().map(segment);
|
||||
|
@ -3039,7 +3077,10 @@ impl<'a> ClickXRayIter<'a> {
|
|||
XRayTarget::Quad(quad) => self.check_layer_area_target(click_targets, clip, layer, quad_to_path_lib_segments(*quad), transform),
|
||||
XRayTarget::Path(path) => self.check_layer_area_target(click_targets, clip, layer, path.clone(), transform),
|
||||
XRayTarget::Polygon(polygon) => {
|
||||
let polygon = polygon.iter_closed().map(|line| path_bool_lib::PathSegment::Line(line.start, line.end)).collect();
|
||||
let polygon = polygon
|
||||
.iter_closed()
|
||||
.map(|line| path_bool_lib::PathSegment::Line(point_to_dvec2(line.start()), point_to_dvec2(line.end())))
|
||||
.collect();
|
||||
self.check_layer_area_target(click_targets, clip, layer, polygon, transform)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,13 @@ use super::utility_types::TransformIn;
|
|||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
|
||||
use crate::messages::prelude::*;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, IVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Artboard;
|
||||
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{CPU, Raster};
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::PointId;
|
||||
|
|
|
@ -426,7 +426,6 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, t
|
|||
Fill::Gradient(Gradient {
|
||||
start,
|
||||
end,
|
||||
transform: DAffine2::IDENTITY,
|
||||
gradient_type: GradientType::Linear,
|
||||
stops,
|
||||
})
|
||||
|
@ -453,7 +452,6 @@ fn apply_usvg_fill(fill: &usvg::Fill, modify_inputs: &mut ModifyInputsContext, t
|
|||
Fill::Gradient(Gradient {
|
||||
start,
|
||||
end,
|
||||
transform: DAffine2::IDENTITY,
|
||||
gradient_type: GradientType::Radial,
|
||||
stops,
|
||||
})
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeNetworkInterface};
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::vector::PointId;
|
||||
|
||||
/// Convert an affine transform into the tuple `(scale, angle, translation, shear)` assuming `shear.y = 0`.
|
||||
|
|
|
@ -3,7 +3,6 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions:
|
|||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector};
|
||||
use crate::messages::prelude::*;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, IVec2};
|
||||
use graph_craft::concrete;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
|
@ -12,6 +11,7 @@ use graphene_std::Artboard;
|
|||
use graphene_std::brush::brush_stroke::BrushStroke;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{CPU, Raster};
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::Vector;
|
||||
|
@ -135,7 +135,7 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
Some(NodeInput::value(TaggedValue::Graphic(Default::default()), true)),
|
||||
Some(NodeInput::value(TaggedValue::DVec2(artboard.location.into()), false)),
|
||||
Some(NodeInput::value(TaggedValue::DVec2(artboard.dimensions.into()), false)),
|
||||
Some(NodeInput::value(TaggedValue::Color(artboard.background), false)),
|
||||
Some(NodeInput::value(TaggedValue::Color(Table::new_from_element(artboard.background)), false)),
|
||||
Some(NodeInput::value(TaggedValue::Bool(artboard.clip), false)),
|
||||
]);
|
||||
self.network_interface.insert_node(new_id, artboard_node_template, &[]);
|
||||
|
@ -329,11 +329,11 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
match &fill {
|
||||
Fill::None => {
|
||||
let input_connector = InputConnector::node(fill_node_id, backup_color_index);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::OptionalColor(None), false), true);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(Table::new()), false), true);
|
||||
}
|
||||
Fill::Solid(color) => {
|
||||
let input_connector = InputConnector::node(fill_node_id, backup_color_index);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::OptionalColor(Some(*color)), false), true);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(Table::new_from_element(*color)), false), true);
|
||||
}
|
||||
Fill::Gradient(gradient) => {
|
||||
let input_connector = InputConnector::node(fill_node_id, backup_gradient_index);
|
||||
|
@ -372,8 +372,10 @@ impl<'a> ModifyInputsContext<'a> {
|
|||
pub fn stroke_set(&mut self, stroke: Stroke) {
|
||||
let Some(stroke_node_id) = self.existing_node_id("Stroke", true) else { return };
|
||||
|
||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::ColorInput::<Option<graphene_std::Color>>::INDEX);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::OptionalColor(stroke.color), false), true);
|
||||
let stroke_color = if let Some(color) = stroke.color { Table::new_from_element(color) } else { Table::new() };
|
||||
|
||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::ColorInput::INDEX);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::Color(stroke_color), false), true);
|
||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::WeightInput::INDEX);
|
||||
self.set_input_with_refresh(input_connector, NodeInput::value(TaggedValue::F64(stroke.weight), false), true);
|
||||
let input_connector = InputConnector::node(stroke_node_id, graphene_std::vector::stroke::AlignInput::INDEX);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
mod document_message;
|
||||
mod document_message_handler;
|
||||
|
||||
pub mod data_panel;
|
||||
pub mod graph_operation;
|
||||
pub mod navigation;
|
||||
pub mod node_graph;
|
||||
|
|
|
@ -139,7 +139,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
|||
let transformed_delta = document_to_viewport.inverse().transform_vector2(delta);
|
||||
|
||||
ptz.pan += transformed_delta;
|
||||
responses.add(BroadcastEvent::CanvasTransformed);
|
||||
responses.add(EventMessage::CanvasTransformed);
|
||||
responses.add(DocumentMessage::PTZUpdate);
|
||||
}
|
||||
NavigationMessage::CanvasPanAbortPrepare { x_not_y_axis } => {
|
||||
|
@ -286,7 +286,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
|||
ptz.flip = !ptz.flip;
|
||||
|
||||
responses.add(DocumentMessage::PTZUpdate);
|
||||
responses.add(BroadcastEvent::CanvasTransformed);
|
||||
responses.add(EventMessage::CanvasTransformed);
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
responses.add(PortfolioMessage::UpdateDocumentWidgets);
|
||||
}
|
||||
|
@ -325,7 +325,7 @@ impl MessageHandler<NavigationMessage, NavigationMessageContext<'_>> for Navigat
|
|||
self.navigation_operation = NavigationOperation::None;
|
||||
|
||||
// Send the final messages to close out the operation
|
||||
responses.add(BroadcastEvent::CanvasTransformed);
|
||||
responses.add(EventMessage::CanvasTransformed);
|
||||
responses.add(ToolMessage::UpdateCursor);
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
responses.add(NavigateToolMessage::End);
|
||||
|
|
|
@ -399,7 +399,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
|
|||
NodeInput::value(TaggedValue::Graphic(Default::default()), true),
|
||||
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
|
||||
NodeInput::value(TaggedValue::DVec2(DVec2::new(1920., 1080.)), false),
|
||||
NodeInput::value(TaggedValue::Color(Color::WHITE), false),
|
||||
NodeInput::value(TaggedValue::Color(Table::new_from_element(Color::WHITE)), false),
|
||||
NodeInput::value(TaggedValue::Bool(false), false),
|
||||
],
|
||||
..Default::default()
|
||||
|
|
|
@ -42,6 +42,7 @@ pub struct NodeGraphMessageContext<'a> {
|
|||
pub graph_fade_artwork_percentage: f64,
|
||||
pub navigation_handler: &'a NavigationMessageHandler,
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
pub layers_panel_open: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, ExtractField)]
|
||||
|
@ -88,7 +89,7 @@ pub struct NodeGraphMessageHandler {
|
|||
reordering_import: Option<usize>,
|
||||
/// The index of the export that is being moved
|
||||
reordering_export: Option<usize>,
|
||||
/// The end index of the moved port
|
||||
/// The end index of the moved connector
|
||||
end_index: Option<usize>,
|
||||
/// Used to keep track of what nodes are sent to the front end so that only visible ones are sent to the frontend
|
||||
frontend_nodes: Vec<NodeId>,
|
||||
|
@ -111,6 +112,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||
graph_fade_artwork_percentage,
|
||||
navigation_handler,
|
||||
preferences,
|
||||
layers_panel_open,
|
||||
} = context;
|
||||
|
||||
match message {
|
||||
|
@ -127,7 +129,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||
NodeGraphMessage::AddPathNode => {
|
||||
if let Some(layer) = make_path_editable_is_allowed(network_interface, network_interface.document_metadata()) {
|
||||
responses.add(NodeGraphMessage::CreateNodeInLayerWithTransaction { node_type: "Path".to_string(), layer });
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
}
|
||||
}
|
||||
NodeGraphMessage::AddImport => {
|
||||
|
@ -140,7 +142,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||
}
|
||||
NodeGraphMessage::Init => {
|
||||
responses.add(BroadcastMessage::SubscribeEvent {
|
||||
on: BroadcastEvent::SelectionChanged,
|
||||
on: EventMessage::SelectionChanged,
|
||||
send: Box::new(NodeGraphMessage::SelectedNodesUpdated.into()),
|
||||
});
|
||||
network_interface.load_structure();
|
||||
|
@ -155,11 +157,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||
}
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
responses.add(NodeGraphMessage::UpdateLayerPanel);
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
responses.add(NodeGraphMessage::SendSelectedNodes);
|
||||
responses.add(ArtboardToolMessage::UpdateSelectedArtboard);
|
||||
responses.add(DocumentMessage::DocumentStructureChanged);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(PortfolioMessage::SubmitActiveGraphRender);
|
||||
}
|
||||
NodeGraphMessage::CreateWire { output_connector, input_connector } => {
|
||||
// TODO: Add support for flattening NodeInput::Network exports in flatten_with_fns https://github.com/GraphiteEditor/Graphite/issues/1762
|
||||
|
@ -1215,7 +1219,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||
{
|
||||
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 node_bbox = kurbo::Rect::new(node_bbox[0].x, node_bbox[0].y, node_bbox[1].x, node_bbox[1].y).to_path(DEFAULT_ACCURACY);
|
||||
|
@ -1468,7 +1472,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||
return;
|
||||
};
|
||||
selected_nodes.add_selected_nodes(nodes);
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
}
|
||||
NodeGraphMessage::SelectedNodesRemove { nodes } => {
|
||||
let Some(selected_nodes) = network_interface.selected_nodes_mut(selection_network_path) else {
|
||||
|
@ -1476,7 +1480,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||
return;
|
||||
};
|
||||
selected_nodes.retain_selected_nodes(|node| !nodes.contains(node));
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
}
|
||||
NodeGraphMessage::SelectedNodesSet { nodes } => {
|
||||
let Some(selected_nodes) = network_interface.selected_nodes_mut(selection_network_path) else {
|
||||
|
@ -1484,8 +1488,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||
return;
|
||||
};
|
||||
selected_nodes.set_selected_nodes(nodes);
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
}
|
||||
NodeGraphMessage::SendClickTargets => responses.add(FrontendMessage::UpdateClickTargets {
|
||||
click_targets: Some(network_interface.collect_frontend_click_targets(breadcrumb_network_path)),
|
||||
|
@ -1677,6 +1680,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||
responses.add(DocumentMessage::RenderRulers);
|
||||
responses.add(DocumentMessage::RenderScrollbars);
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
responses.add(OverlaysMessage::Draw); // Redraw overlays to update artboard names
|
||||
}
|
||||
NodeGraphMessage::SetDisplayNameImpl { node_id, alias } => {
|
||||
network_interface.set_display_name(&node_id, alias, selection_network_path);
|
||||
|
@ -1833,7 +1837,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||
continue;
|
||||
};
|
||||
let quad = Quad::from_box([box_selection_start, box_selection_end_graph]);
|
||||
if click_targets.node_click_target.intersect_path(|| quad.bezier_lines(), DAffine2::IDENTITY) {
|
||||
if click_targets.node_click_target.intersect_path(|| quad.to_lines(), DAffine2::IDENTITY) {
|
||||
nodes.insert(node_id);
|
||||
}
|
||||
}
|
||||
|
@ -1873,7 +1877,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||
}
|
||||
|
||||
NodeGraphMessage::UpdateLayerPanel => {
|
||||
Self::update_layer_panel(network_interface, selection_network_path, collapsed, responses);
|
||||
Self::update_layer_panel(network_interface, selection_network_path, collapsed, layers_panel_open, responses);
|
||||
}
|
||||
NodeGraphMessage::UpdateEdges => {
|
||||
// Update the import/export UI edges whenever the PTZ changes or the bounding box of all nodes changes
|
||||
|
@ -1884,7 +1888,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||
return;
|
||||
};
|
||||
selected_nodes.clear_selected_nodes();
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
|
||||
responses.add(NodeGraphMessage::SendGraph);
|
||||
}
|
||||
|
@ -2329,9 +2333,9 @@ impl NodeGraphMessageHandler {
|
|||
.icon(Some("Node".to_string()))
|
||||
.tooltip("Add an operation to the end of this layer's chain of nodes")
|
||||
.popover_layout({
|
||||
let layer_identifier = LayerNodeIdentifier::new(layer, &context.network_interface);
|
||||
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 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, &[]);
|
||||
|
@ -2413,7 +2417,7 @@ impl NodeGraphMessageHandler {
|
|||
} else {
|
||||
added_wires.push(WirePathUpdate {
|
||||
id: NodeId(u64::MAX),
|
||||
input_index: usize::MAX,
|
||||
input_index: u32::MAX as usize,
|
||||
wire_path_update: None,
|
||||
})
|
||||
}
|
||||
|
@ -2585,7 +2589,11 @@ impl NodeGraphMessageHandler {
|
|||
Some(subgraph_names)
|
||||
}
|
||||
|
||||
fn update_layer_panel(network_interface: &NodeNetworkInterface, selection_network_path: &[NodeId], collapsed: &CollapsedLayers, responses: &mut VecDeque<Message>) {
|
||||
fn update_layer_panel(network_interface: &NodeNetworkInterface, selection_network_path: &[NodeId], collapsed: &CollapsedLayers, layers_panel_open: bool, responses: &mut VecDeque<Message>) {
|
||||
if !layers_panel_open {
|
||||
return;
|
||||
}
|
||||
|
||||
let selected_layers = network_interface
|
||||
.selected_nodes()
|
||||
.selected_layers(network_interface.document_metadata())
|
||||
|
@ -2668,7 +2676,7 @@ impl NodeGraphMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn update_node_graph_hints(&self, responses: &mut VecDeque<Message>) {
|
||||
fn update_node_graph_hints(&self, responses: &mut VecDeque<Message>) {
|
||||
// A wire is in progress and its start and end connectors are set
|
||||
let wiring = self.wire_in_progress_from_connector.is_some();
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ use graphene_std::raster::{
|
|||
BlendMode, CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, LuminanceCalculation, NoiseType, RedGreenBlue, RedGreenBlueAlpha, RelativeAbsolute,
|
||||
SelectiveColorChoice,
|
||||
};
|
||||
use graphene_std::table::{Table, TableRow};
|
||||
use graphene_std::text::{Font, TextAlign};
|
||||
use graphene_std::transform::{Footprint, ReferencePoint, Transform};
|
||||
use graphene_std::vector::misc::{ArcType, CentroidType, GridType, MergeByDistanceAlgorithm, PointSpacingType};
|
||||
|
@ -180,8 +181,8 @@ pub(crate) fn property_from_type(
|
|||
// ============
|
||||
// STRUCT TYPES
|
||||
// ============
|
||||
Some(x) if x == TypeId::of::<Color>() => color_widget(default_info, ColorInput::default().allow_none(false)),
|
||||
Some(x) if x == TypeId::of::<Option<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)),
|
||||
Some(x) if x == TypeId::of::<Table<Color>>() => color_widget(default_info, ColorInput::default().allow_none(true)),
|
||||
Some(x) if x == TypeId::of::<Table<GradientStops>>() => color_widget(default_info, ColorInput::default().allow_none(false)),
|
||||
Some(x) if x == TypeId::of::<GradientStops>() => color_widget(default_info, ColorInput::default().allow_none(false)),
|
||||
Some(x) if x == TypeId::of::<Font>() => font_widget(default_info),
|
||||
Some(x) if x == TypeId::of::<Curve>() => curve_widget(default_info),
|
||||
|
@ -908,28 +909,39 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
|
|||
|
||||
// Add the color input
|
||||
match &**tagged_value {
|
||||
TaggedValue::Color(color) => widgets.push(
|
||||
TaggedValue::Color(color_table) => widgets.push(
|
||||
color_button
|
||||
.value(FillChoice::Solid(*color))
|
||||
.on_update(update_value(|x: &ColorInput| TaggedValue::Color(x.value.as_solid().unwrap_or_default()), node_id, index))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
),
|
||||
TaggedValue::OptionalColor(color) => widgets.push(
|
||||
color_button
|
||||
.value(match color {
|
||||
Some(color) => FillChoice::Solid(*color),
|
||||
.value(match color_table.iter().next() {
|
||||
Some(color) => FillChoice::Solid(*color.element),
|
||||
None => FillChoice::None,
|
||||
})
|
||||
.on_update(update_value(|x: &ColorInput| TaggedValue::OptionalColor(x.value.as_solid()), node_id, index))
|
||||
.on_update(update_value(
|
||||
|input: &ColorInput| TaggedValue::Color(input.value.as_solid().iter().map(|&color| TableRow::new_from_element(color)).collect()),
|
||||
node_id,
|
||||
index,
|
||||
))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
),
|
||||
TaggedValue::GradientStops(x) => widgets.push(
|
||||
TaggedValue::GradientTable(gradient_table) => widgets.push(
|
||||
color_button
|
||||
.value(FillChoice::Gradient(x.clone()))
|
||||
.value(match gradient_table.iter().next() {
|
||||
Some(row) => FillChoice::Gradient(row.element.clone()),
|
||||
None => FillChoice::None,
|
||||
})
|
||||
.on_update(update_value(
|
||||
|x: &ColorInput| TaggedValue::GradientStops(x.value.as_gradient().cloned().unwrap_or_default()),
|
||||
|input: &ColorInput| TaggedValue::GradientTable(input.value.as_gradient().iter().map(|&gradient| TableRow::new_from_element(gradient.clone())).collect()),
|
||||
node_id,
|
||||
index,
|
||||
))
|
||||
.on_commit(commit_value)
|
||||
.widget_holder(),
|
||||
),
|
||||
TaggedValue::GradientStops(gradient_stops) => widgets.push(
|
||||
color_button
|
||||
.value(FillChoice::Gradient(gradient_stops.clone()))
|
||||
.on_update(update_value(
|
||||
|input: &ColorInput| TaggedValue::GradientStops(input.value.as_gradient().cloned().unwrap_or_default()),
|
||||
node_id,
|
||||
index,
|
||||
))
|
||||
|
@ -1559,7 +1571,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||
}
|
||||
};
|
||||
|
||||
let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), &Some(&TaggedValue::OptionalColor(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = (
|
||||
let (fill, backup_color, backup_gradient) = if let (Some(TaggedValue::Fill(fill)), Some(TaggedValue::Color(backup_color)), Some(TaggedValue::Gradient(backup_gradient))) = (
|
||||
&document_node.inputs[FillInput::<Color>::INDEX].as_value(),
|
||||
&document_node.inputs[BackupColorInput::INDEX].as_value(),
|
||||
&document_node.inputs[BackupGradientInput::INDEX].as_value(),
|
||||
|
@ -1569,7 +1581,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||
return vec![LayoutGroup::Row { widgets: widgets_first_row }];
|
||||
};
|
||||
let fill2 = fill.clone();
|
||||
let backup_color_fill: Fill = backup_color.into();
|
||||
let backup_color_fill: Fill = backup_color.clone().into();
|
||||
let backup_gradient_fill: Fill = backup_gradient.clone().into();
|
||||
|
||||
widgets_first_row.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
|
@ -1582,13 +1594,13 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||
Fill::None => NodeGraphMessage::SetInputValue {
|
||||
node_id,
|
||||
input_index: BackupColorInput::INDEX,
|
||||
value: TaggedValue::OptionalColor(None),
|
||||
value: TaggedValue::Color(Table::new()),
|
||||
}
|
||||
.into(),
|
||||
Fill::Solid(color) => NodeGraphMessage::SetInputValue {
|
||||
node_id,
|
||||
input_index: BackupColorInput::INDEX,
|
||||
value: TaggedValue::OptionalColor(Some(*color)),
|
||||
value: TaggedValue::Color(Table::new_from_element(*color)),
|
||||
}
|
||||
.into(),
|
||||
Fill::Gradient(gradient) => NodeGraphMessage::SetInputValue {
|
||||
|
@ -1750,7 +1762,7 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
|
|||
let miter_limit_disabled = join_value != &StrokeJoin::Miter;
|
||||
|
||||
let color = color_widget(
|
||||
ParameterWidgetsInfo::new(node_id, ColorInput::<Option<Color>>::INDEX, true, context),
|
||||
ParameterWidgetsInfo::new(node_id, ColorInput::INDEX, true, context),
|
||||
crate::messages::layout::utility_types::widgets::button_widgets::ColorInput::default(),
|
||||
);
|
||||
let weight = number_widget(ParameterWidgetsInfo::new(node_id, WeightInput::INDEX, true, context), NumberInput::default().unit(" px").min(0.));
|
||||
|
|
|
@ -8,18 +8,19 @@ use std::borrow::Cow;
|
|||
pub enum FrontendGraphDataType {
|
||||
#[default]
|
||||
General,
|
||||
Number,
|
||||
Artboard,
|
||||
Graphic,
|
||||
Raster,
|
||||
Vector,
|
||||
Number,
|
||||
Graphic,
|
||||
Artboard,
|
||||
Color,
|
||||
Gradient,
|
||||
Typography,
|
||||
}
|
||||
|
||||
impl FrontendGraphDataType {
|
||||
pub fn from_type(input: &Type) -> Self {
|
||||
match TaggedValue::from_type_or_none(input) {
|
||||
TaggedValue::Raster(_) => Self::Raster,
|
||||
TaggedValue::Vector(_) => Self::Vector,
|
||||
TaggedValue::U32(_)
|
||||
| TaggedValue::U64(_)
|
||||
| TaggedValue::F64(_)
|
||||
|
@ -28,8 +29,13 @@ impl FrontendGraphDataType {
|
|||
| TaggedValue::VecF64(_)
|
||||
| TaggedValue::VecDVec2(_)
|
||||
| TaggedValue::DAffine2(_) => Self::Number,
|
||||
TaggedValue::Graphic(_) => Self::Graphic,
|
||||
TaggedValue::Artboard(_) => Self::Artboard,
|
||||
TaggedValue::Graphic(_) => Self::Graphic,
|
||||
TaggedValue::Raster(_) => Self::Raster,
|
||||
TaggedValue::Vector(_) => Self::Vector,
|
||||
TaggedValue::Color(_) => Self::Color,
|
||||
TaggedValue::Gradient(_) | TaggedValue::GradientStops(_) | TaggedValue::GradientTable(_) => Self::Gradient,
|
||||
TaggedValue::String(_) => Self::Typography,
|
||||
_ => Self::General,
|
||||
}
|
||||
}
|
||||
|
@ -177,8 +183,8 @@ pub struct FrontendClickTargets {
|
|||
pub node_click_targets: Vec<String>,
|
||||
#[serde(rename = "layerClickTargets")]
|
||||
pub layer_click_targets: Vec<String>,
|
||||
#[serde(rename = "portClickTargets")]
|
||||
pub port_click_targets: Vec<String>,
|
||||
#[serde(rename = "connectorClickTargets")]
|
||||
pub connector_click_targets: Vec<String>,
|
||||
#[serde(rename = "iconClickTargets")]
|
||||
pub icon_click_targets: Vec<String>,
|
||||
#[serde(rename = "allNodesBoundingBox")]
|
||||
|
|
|
@ -200,7 +200,7 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
|
|||
move |input: &I| {
|
||||
let mut grid = grid.clone();
|
||||
update(&mut grid, input);
|
||||
DocumentMessage::GridOptions(grid).into()
|
||||
DocumentMessage::GridOptions { options: grid }.into()
|
||||
}
|
||||
}
|
||||
let update_origin = |grid, update: fn(&mut GridSnapping) -> Option<&mut f64>| {
|
||||
|
|
|
@ -7,14 +7,14 @@ use crate::messages::prelude::*;
|
|||
pub enum OverlaysMessage {
|
||||
Draw,
|
||||
// Serde functionality isn't used but is required by the message system macros
|
||||
AddProvider(
|
||||
AddProvider {
|
||||
#[serde(skip, default = "empty_provider")]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
OverlayProvider,
|
||||
),
|
||||
RemoveProvider(
|
||||
provider: OverlayProvider,
|
||||
},
|
||||
RemoveProvider {
|
||||
#[serde(skip, default = "empty_provider")]
|
||||
#[derivative(Debug = "ignore", PartialEq = "ignore")]
|
||||
OverlayProvider,
|
||||
),
|
||||
provider: OverlayProvider,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -56,12 +56,14 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
|
|||
let _ = canvas_context.reset_transform();
|
||||
|
||||
if visibility_settings.all() {
|
||||
responses.add(DocumentMessage::GridOverlays(OverlayContext {
|
||||
responses.add(DocumentMessage::GridOverlays {
|
||||
context: OverlayContext {
|
||||
render_context: canvas_context.clone(),
|
||||
size: size.as_dvec2(),
|
||||
device_pixel_ratio,
|
||||
visibility_settings: visibility_settings.clone(),
|
||||
}));
|
||||
},
|
||||
});
|
||||
for provider in &self.overlay_providers {
|
||||
responses.add(provider(OverlayContext {
|
||||
render_context: canvas_context.clone(),
|
||||
|
@ -81,22 +83,22 @@ impl MessageHandler<OverlaysMessage, OverlaysMessageContext<'_>> for OverlaysMes
|
|||
let overlay_context = OverlayContext::new(size, device_pixel_ratio, visibility_settings);
|
||||
|
||||
if visibility_settings.all() {
|
||||
responses.add(DocumentMessage::GridOverlays(overlay_context.clone()));
|
||||
responses.add(DocumentMessage::GridOverlays { context: overlay_context.clone() });
|
||||
|
||||
for provider in &self.overlay_providers {
|
||||
responses.add(provider(overlay_context.clone()));
|
||||
}
|
||||
}
|
||||
responses.add(FrontendMessage::RenderOverlays(overlay_context));
|
||||
responses.add(FrontendMessage::RenderOverlays { context: overlay_context });
|
||||
}
|
||||
#[cfg(all(not(target_family = "wasm"), test))]
|
||||
OverlaysMessage::Draw => {
|
||||
let _ = (responses, visibility_settings, ipp, device_pixel_ratio);
|
||||
}
|
||||
OverlaysMessage::AddProvider(message) => {
|
||||
OverlaysMessage::AddProvider { provider: message } => {
|
||||
self.overlay_providers.insert(message);
|
||||
}
|
||||
OverlaysMessage::RemoveProvider(message) => {
|
||||
OverlaysMessage::RemoveProvider { provider: message } => {
|
||||
self.overlay_providers.remove(&message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ use crate::consts::HIDE_HANDLE_DISTANCE;
|
|||
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
|
||||
use crate::messages::tool::common_functionality::shape_editor::{SelectedLayerState, ShapeState};
|
||||
use crate::messages::tool::tool_messages::tool_prelude::{DocumentMessageHandler, PreferencesMessageHandler};
|
||||
use bezier_rs::{Bezier, BezierHandles};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::subpath::{Bezier, BezierHandles};
|
||||
use graphene_std::vector::misc::ManipulatorPointId;
|
||||
use graphene_std::vector::{PointId, SegmentId};
|
||||
use wasm_bindgen::JsCast;
|
||||
|
@ -125,7 +125,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
|
|||
}
|
||||
|
||||
// Get the selected segments and then add a bold line overlay on them
|
||||
for (segment_id, bezier, _, _) in vector.segment_bezier_iter() {
|
||||
for (segment_id, bezier, _, _) in vector.segment_iter() {
|
||||
let Some(selected_shape_state) = shape_editor.selected_shape_state.get_mut(&layer) else {
|
||||
continue;
|
||||
};
|
||||
|
|
|
@ -5,14 +5,16 @@ use crate::consts::{
|
|||
PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, SEGMENT_SELECTED_THICKNESS,
|
||||
};
|
||||
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::subpath::Subpath;
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::misc::{dvec2_to_point, point_to_dvec2};
|
||||
use graphene_std::vector::{PointId, SegmentId, Vector};
|
||||
use kurbo::{self, Affine, CubicBez, ParamCurve, PathSeg};
|
||||
use std::collections::HashMap;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
use web_sys::{OffscreenCanvas, OffscreenCanvasRenderingContext2d};
|
||||
|
@ -571,11 +573,7 @@ impl OverlayContext {
|
|||
let handle_start = start + start_vec.perp() * radius * factor;
|
||||
let handle_end = end - end_vec.perp() * radius * factor;
|
||||
|
||||
let bezier = Bezier {
|
||||
start,
|
||||
end,
|
||||
handles: bezier_rs::BezierHandles::Cubic { handle_start, handle_end },
|
||||
};
|
||||
let bezier = PathSeg::Cubic(CubicBez::new(dvec2_to_point(start), dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(end)));
|
||||
|
||||
self.bezier_command(bezier, DAffine2::IDENTITY, i == 0);
|
||||
}
|
||||
|
@ -762,7 +760,7 @@ impl OverlayContext {
|
|||
|
||||
self.render_context.begin_path();
|
||||
let mut last_point = None;
|
||||
for (_, bezier, start_id, end_id) in vector.segment_bezier_iter() {
|
||||
for (_, bezier, start_id, end_id) in vector.segment_iter() {
|
||||
let move_to = last_point != Some(start_id);
|
||||
last_point = Some(end_id);
|
||||
|
||||
|
@ -776,7 +774,7 @@ impl OverlayContext {
|
|||
}
|
||||
|
||||
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
||||
pub fn outline_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
pub fn outline_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
self.render_context.begin_path();
|
||||
|
@ -788,7 +786,7 @@ impl OverlayContext {
|
|||
}
|
||||
|
||||
/// Used by the path tool segment mode in order to show the selected segments.
|
||||
pub fn outline_select_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
pub fn outline_select_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
self.render_context.begin_path();
|
||||
|
@ -802,7 +800,7 @@ impl OverlayContext {
|
|||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
pub fn outline_overlay_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
self.render_context.begin_path();
|
||||
|
@ -816,18 +814,18 @@ impl OverlayContext {
|
|||
self.end_dpi_aware_transform();
|
||||
}
|
||||
|
||||
fn bezier_command(&self, bezier: Bezier, transform: DAffine2, move_to: bool) {
|
||||
fn bezier_command(&self, bezier: PathSeg, transform: DAffine2, move_to: bool) {
|
||||
self.start_dpi_aware_transform();
|
||||
|
||||
let Bezier { start, end, handles } = bezier.apply_transformation(|point| transform.transform_point2(point));
|
||||
let bezier = Affine::new(transform.to_cols_array()) * bezier;
|
||||
if move_to {
|
||||
self.render_context.move_to(start.x, start.y);
|
||||
self.render_context.move_to(bezier.start().x, bezier.start().y);
|
||||
}
|
||||
|
||||
match handles {
|
||||
bezier_rs::BezierHandles::Linear => self.render_context.line_to(end.x, end.y),
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => self.render_context.quadratic_curve_to(handle.x, handle.y, end.x, end.y),
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => self.render_context.bezier_curve_to(handle_start.x, handle_start.y, handle_end.x, handle_end.y, end.x, end.y),
|
||||
match bezier.as_path_el() {
|
||||
kurbo::PathEl::LineTo(point) => self.render_context.line_to(point.x, point.y),
|
||||
kurbo::PathEl::QuadTo(point, point1) => self.render_context.quadratic_curve_to(point.x, point.y, point1.x, point1.y),
|
||||
kurbo::PathEl::CurveTo(point, point1, point2) => self.render_context.bezier_curve_to(point.x, point.y, point1.x, point1.y, point2.x, point2.y),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
self.end_dpi_aware_transform();
|
||||
|
@ -841,36 +839,35 @@ impl OverlayContext {
|
|||
let subpath = subpath.borrow();
|
||||
let mut curves = subpath.iter().peekable();
|
||||
|
||||
let Some(first) = curves.peek() else {
|
||||
let Some(&first) = curves.peek() else {
|
||||
continue;
|
||||
};
|
||||
|
||||
self.render_context.move_to(transform.transform_point2(first.start()).x, transform.transform_point2(first.start()).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);
|
||||
let start_point = transform.transform_point2(point_to_dvec2(first.start()));
|
||||
self.render_context.move_to(start_point.x, start_point.y);
|
||||
|
||||
self.render_context.line_to(a.x, a.y)
|
||||
for curve in curves {
|
||||
match curve {
|
||||
PathSeg::Line(line) => {
|
||||
let a = transform.transform_point2(point_to_dvec2(line.p1));
|
||||
let a = a.round() - DVec2::splat(0.5);
|
||||
self.render_context.line_to(a.x, a.y);
|
||||
}
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => {
|
||||
let a = transform.transform_point2(handle);
|
||||
let b = transform.transform_point2(curve.end());
|
||||
PathSeg::Quad(quad_bez) => {
|
||||
let a = transform.transform_point2(point_to_dvec2(quad_bez.p1));
|
||||
let b = transform.transform_point2(point_to_dvec2(quad_bez.p2));
|
||||
let a = a.round() - DVec2::splat(0.5);
|
||||
let b = b.round() - DVec2::splat(0.5);
|
||||
|
||||
self.render_context.quadratic_curve_to(a.x, a.y, b.x, b.y)
|
||||
self.render_context.quadratic_curve_to(a.x, a.y, b.x, b.y);
|
||||
}
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
let a = transform.transform_point2(handle_start);
|
||||
let b = transform.transform_point2(handle_end);
|
||||
let c = transform.transform_point2(curve.end());
|
||||
PathSeg::Cubic(cubic_bez) => {
|
||||
let a = transform.transform_point2(point_to_dvec2(cubic_bez.p1));
|
||||
let b = transform.transform_point2(point_to_dvec2(cubic_bez.p2));
|
||||
let c = transform.transform_point2(point_to_dvec2(cubic_bez.p3));
|
||||
let a = a.round() - DVec2::splat(0.5);
|
||||
let b = b.round() - DVec2::splat(0.5);
|
||||
let c = c.round() - DVec2::splat(0.5);
|
||||
|
||||
self.render_context.bezier_curve_to(a.x, a.y, b.x, b.y, c.x, c.y)
|
||||
self.render_context.bezier_curve_to(a.x, a.y, b.x, b.y, c.x, c.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -885,7 +882,7 @@ impl OverlayContext {
|
|||
|
||||
/// Used by the Select tool to outline a path or a free point when selected or hovered.
|
||||
pub fn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {
|
||||
let mut subpaths: Vec<bezier_rs::Subpath<PointId>> = vec![];
|
||||
let mut subpaths: Vec<Subpath<PointId>> = vec![];
|
||||
|
||||
target_types.for_each(|target_type| match target_type.borrow() {
|
||||
ClickTargetType::FreePoint(point) => {
|
||||
|
|
|
@ -4,20 +4,22 @@ use crate::consts::{
|
|||
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::subpath::{self, Subpath};
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{TextAlign, TypesettingConfig, load_font, to_path};
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::misc::point_to_dvec2;
|
||||
use graphene_std::vector::{PointId, SegmentId, Vector};
|
||||
use kurbo::{self, BezPath, ParamCurve};
|
||||
use kurbo::{Affine, PathSeg};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex, MutexGuard};
|
||||
use vello::Scene;
|
||||
use vello::kurbo::{self, BezPath};
|
||||
use vello::peniko;
|
||||
|
||||
pub type OverlayProvider = fn(OverlayContext) -> Message;
|
||||
|
@ -200,6 +202,7 @@ impl core::hash::Hash for OverlayContext {
|
|||
}
|
||||
|
||||
impl OverlayContext {
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn new(size: DVec2, device_pixel_ratio: f64, visibility_settings: OverlaysVisibilitySettings) -> Self {
|
||||
Self {
|
||||
internal: Arc::new(Mutex::new(OverlayContextInternal::new(size, device_pixel_ratio, visibility_settings))),
|
||||
|
@ -345,16 +348,16 @@ impl OverlayContext {
|
|||
}
|
||||
|
||||
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
||||
pub fn outline_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
pub fn outline_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
self.internal().outline_bezier(bezier, transform);
|
||||
}
|
||||
|
||||
/// Used by the path tool segment mode in order to show the selected segments.
|
||||
pub fn outline_select_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
pub fn outline_select_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
self.internal().outline_select_bezier(bezier, transform);
|
||||
}
|
||||
|
||||
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
pub fn outline_overlay_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
self.internal().outline_overlay_bezier(bezier, transform);
|
||||
}
|
||||
|
||||
|
@ -842,7 +845,7 @@ impl OverlayContextInternal {
|
|||
let mut path = BezPath::new();
|
||||
|
||||
let mut last_point = None;
|
||||
for (_, bezier, start_id, end_id) in vector.segment_bezier_iter() {
|
||||
for (_, bezier, start_id, end_id) in vector.segment_iter() {
|
||||
let move_to = last_point != Some(start_id);
|
||||
last_point = Some(end_id);
|
||||
|
||||
|
@ -853,7 +856,7 @@ impl OverlayContextInternal {
|
|||
}
|
||||
|
||||
/// Used by the Pen tool in order to show how the bezier curve would look like.
|
||||
fn outline_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
fn outline_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
let vello_transform = self.get_transform();
|
||||
let mut path = BezPath::new();
|
||||
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||
|
@ -862,7 +865,7 @@ impl OverlayContextInternal {
|
|||
}
|
||||
|
||||
/// Used by the path tool segment mode in order to show the selected segments.
|
||||
fn outline_select_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
fn outline_select_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
let vello_transform = self.get_transform();
|
||||
let mut path = BezPath::new();
|
||||
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||
|
@ -870,7 +873,7 @@ impl OverlayContextInternal {
|
|||
self.scene.stroke(&kurbo::Stroke::new(4.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE), None, &path);
|
||||
}
|
||||
|
||||
fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
|
||||
fn outline_overlay_bezier(&mut self, bezier: PathSeg, transform: DAffine2) {
|
||||
let vello_transform = self.get_transform();
|
||||
let mut path = BezPath::new();
|
||||
self.bezier_to_path(bezier, transform, true, &mut path);
|
||||
|
@ -878,21 +881,12 @@ impl OverlayContextInternal {
|
|||
self.scene.stroke(&kurbo::Stroke::new(4.0), vello_transform, Self::parse_color(COLOR_OVERLAY_BLUE_50), None, &path);
|
||||
}
|
||||
|
||||
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));
|
||||
fn bezier_to_path(&self, bezier: PathSeg, transform: DAffine2, move_to: bool, path: &mut BezPath) {
|
||||
let bezier = Affine::new(transform.to_cols_array()) * bezier;
|
||||
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),
|
||||
),
|
||||
path.move_to(bezier.start());
|
||||
}
|
||||
path.push(bezier.as_path_el());
|
||||
}
|
||||
|
||||
fn push_path(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, transform: DAffine2) -> BezPath {
|
||||
|
@ -906,27 +900,27 @@ impl OverlayContextInternal {
|
|||
continue;
|
||||
};
|
||||
|
||||
let start_point = transform.transform_point2(first.start());
|
||||
let start_point = transform.transform_point2(point_to_dvec2(first.start()));
|
||||
path.move_to(kurbo::Point::new(start_point.x, start_point.y));
|
||||
|
||||
for curve in curves {
|
||||
match curve.handles {
|
||||
bezier_rs::BezierHandles::Linear => {
|
||||
let a = transform.transform_point2(curve.end());
|
||||
match curve {
|
||||
PathSeg::Line(line) => {
|
||||
let a = transform.transform_point2(point_to_dvec2(line.p1));
|
||||
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());
|
||||
PathSeg::Quad(quad_bez) => {
|
||||
let a = transform.transform_point2(point_to_dvec2(quad_bez.p1));
|
||||
let b = transform.transform_point2(point_to_dvec2(quad_bez.p2));
|
||||
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());
|
||||
PathSeg::Cubic(cubic_bez) => {
|
||||
let a = transform.transform_point2(point_to_dvec2(cubic_bez.p1));
|
||||
let b = transform.transform_point2(point_to_dvec2(cubic_bez.p2));
|
||||
let c = transform.transform_point2(point_to_dvec2(cubic_bez.p3));
|
||||
let a = a.round() - DVec2::splat(0.5);
|
||||
let b = b.round() - DVec2::splat(0.5);
|
||||
let c = c.round() - DVec2::splat(0.5);
|
||||
|
@ -945,7 +939,7 @@ impl OverlayContextInternal {
|
|||
|
||||
/// Used by the Select tool to outline a path or a free point when selected or hovered.
|
||||
fn outline(&mut self, target_types: impl Iterator<Item = impl Borrow<ClickTargetType>>, transform: DAffine2, color: Option<&str>) {
|
||||
let mut subpaths: Vec<bezier_rs::Subpath<PointId>> = vec![];
|
||||
let mut subpaths: Vec<subpath::Subpath<PointId>> = vec![];
|
||||
|
||||
for target_type in target_types {
|
||||
match target_type.borrow() {
|
||||
|
@ -1118,13 +1112,13 @@ impl OverlayContextInternal {
|
|||
|
||||
// Add handle points if they exist
|
||||
match transformed_bezier.handles {
|
||||
bezier_rs::BezierHandles::Quadratic { handle } => {
|
||||
subpath::BezierHandles::Quadratic { handle } => {
|
||||
min_x = min_x.min(handle.x);
|
||||
min_y = min_y.min(handle.y);
|
||||
max_x = max_x.max(handle.x);
|
||||
max_y = max_y.max(handle.y);
|
||||
}
|
||||
bezier_rs::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
subpath::BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
for handle in [handle_start, handle_end] {
|
||||
min_x = min_x.min(handle.x);
|
||||
min_y = min_y.min(handle.y);
|
||||
|
@ -1154,7 +1148,7 @@ impl OverlayContextInternal {
|
|||
let mut path = BezPath::new();
|
||||
let mut last_point = None;
|
||||
|
||||
for (_, bezier, start_id, end_id) in row.element.segment_bezier_iter() {
|
||||
for (_, bezier, start_id, end_id) in row.element.segment_iter() {
|
||||
let move_to = last_point != Some(start_id);
|
||||
last_point = Some(end_id);
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ pub struct PropertiesPanelMessageContext<'a> {
|
|||
pub document_name: &'a str,
|
||||
pub executor: &'a mut NodeGraphExecutor,
|
||||
pub persistent_data: &'a PersistentData,
|
||||
pub properties_panel_open: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, ExtractField)]
|
||||
|
@ -28,16 +29,22 @@ impl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> f
|
|||
document_name,
|
||||
executor,
|
||||
persistent_data,
|
||||
properties_panel_open,
|
||||
} = context;
|
||||
|
||||
match message {
|
||||
PropertiesPanelMessage::Clear => {
|
||||
responses.add(LayoutMessage::SendLayout {
|
||||
layout: Layout::WidgetLayout(WidgetLayout::new(vec![])),
|
||||
layout_target: LayoutTarget::PropertiesSections,
|
||||
layout_target: LayoutTarget::PropertiesPanel,
|
||||
});
|
||||
}
|
||||
PropertiesPanelMessage::Refresh => {
|
||||
if !properties_panel_open {
|
||||
responses.add(PropertiesPanelMessage::Clear);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut node_properties_context = NodePropertiesContext {
|
||||
persistent_data,
|
||||
responses,
|
||||
|
@ -50,7 +57,7 @@ impl MessageHandler<PropertiesPanelMessage, PropertiesPanelMessageContext<'_>> f
|
|||
|
||||
node_properties_context.responses.add(LayoutMessage::SendLayout {
|
||||
layout: Layout::WidgetLayout(WidgetLayout::new(properties_sections)),
|
||||
layout_target: LayoutTarget::PropertiesSections,
|
||||
layout_target: LayoutTarget::PropertiesPanel,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::messages::tool::common_functionality::graph_modification_utils;
|
|||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::subpath;
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||
use graphene_std::vector::{PointId, Vector};
|
||||
|
@ -196,7 +197,7 @@ impl DocumentMetadata {
|
|||
self.all_layers().filter_map(|layer| self.bounding_box_viewport(layer)).reduce(Quad::combine_bounds)
|
||||
}
|
||||
|
||||
pub fn layer_outline(&self, layer: LayerNodeIdentifier) -> impl Iterator<Item = &bezier_rs::Subpath<PointId>> {
|
||||
pub fn layer_outline(&self, layer: LayerNodeIdentifier) -> impl Iterator<Item = &subpath::Subpath<PointId>> {
|
||||
static EMPTY: Vec<ClickTarget> = Vec::new();
|
||||
let click_targets = self.click_targets.get(&layer).unwrap_or(&EMPTY);
|
||||
click_targets.iter().filter_map(|target| match target.target_type() {
|
||||
|
|
|
@ -8,13 +8,13 @@ use crate::messages::portfolio::document::node_graph::utility_types::{Direction,
|
|||
use crate::messages::portfolio::document::utility_types::wires::{GraphWireStyle, WirePath, WirePathUpdate, build_vector_wire};
|
||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::NumberInputMode;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DVec2, IVec2};
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, OldDocumentNodeImplementation, OldNodeNetwork};
|
||||
use graph_craft::{Type, concrete};
|
||||
use graphene_std::Artboard;
|
||||
use graphene_std::math::quad::Quad;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::transform::Footprint;
|
||||
use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
|
||||
|
@ -1287,7 +1287,7 @@ impl NodeNetworkInterface {
|
|||
let artboard = self.document_node(&artboard_node_identifier.to_node(), &[]);
|
||||
let clip_input = artboard.unwrap().inputs.get(5).unwrap();
|
||||
if let NodeInput::Value { tagged_value, .. } = clip_input {
|
||||
if tagged_value.to_primitive_string() == "true" {
|
||||
if tagged_value.clone().into_inner() == TaggedValue::Bool(true) {
|
||||
return Some(Quad::clip(
|
||||
self.document_metadata.bounding_box_document(layer).unwrap_or_default(),
|
||||
self.document_metadata.bounding_box_document(artboard_node_identifier).unwrap_or_default(),
|
||||
|
@ -2635,7 +2635,7 @@ impl NodeNetworkInterface {
|
|||
InputConnector::Node { node_id, input_index } => (*node_id, *input_index),
|
||||
InputConnector::Export(export_index) => (NodeId(u64::MAX), *export_index),
|
||||
})
|
||||
.chain(std::iter::once((NodeId(u64::MAX), usize::MAX)))
|
||||
.chain(std::iter::once((NodeId(u64::MAX), u32::MAX as usize)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
@ -2725,7 +2725,7 @@ impl NodeNetworkInterface {
|
|||
|
||||
Some(WirePathUpdate {
|
||||
id: NodeId(u64::MAX),
|
||||
input_index: usize::MAX,
|
||||
input_index: u32::MAX as usize,
|
||||
wire_path_update,
|
||||
})
|
||||
}
|
||||
|
@ -2832,7 +2832,7 @@ impl NodeNetworkInterface {
|
|||
let node_click_target_bottom_right = node_click_target_top_left + DVec2::new(width as f64, height as f64);
|
||||
|
||||
let radius = 3.;
|
||||
let subpath = bezier_rs::Subpath::new_rounded_rect(node_click_target_top_left, node_click_target_bottom_right, [radius; 4]);
|
||||
let subpath = Subpath::new_rounded_rect(node_click_target_top_left, node_click_target_bottom_right, [radius; 4]);
|
||||
let node_click_target = ClickTarget::new_with_subpath(subpath, 0.);
|
||||
|
||||
DocumentNodeClickTargets {
|
||||
|
@ -2871,7 +2871,7 @@ impl NodeNetworkInterface {
|
|||
let node_bottom_right = node_top_left + DVec2::new(width as f64, height as f64);
|
||||
let chain_top_left = node_top_left - DVec2::new((chain_width_grid_spaces * crate::consts::GRID_SIZE) as f64, 0.);
|
||||
let radius = 10.;
|
||||
let subpath = bezier_rs::Subpath::new_rounded_rect(chain_top_left, node_bottom_right, [radius; 4]);
|
||||
let subpath = Subpath::new_rounded_rect(chain_top_left, node_bottom_right, [radius; 4]);
|
||||
let node_click_target = ClickTarget::new_with_subpath(subpath, 0.);
|
||||
|
||||
DocumentNodeClickTargets {
|
||||
|
@ -3048,7 +3048,7 @@ impl NodeNetworkInterface {
|
|||
|
||||
pub fn collect_frontend_click_targets(&mut self, network_path: &[NodeId]) -> FrontendClickTargets {
|
||||
let mut all_node_click_targets = Vec::new();
|
||||
let mut port_click_targets = Vec::new();
|
||||
let mut connector_click_targets = Vec::new();
|
||||
let mut icon_click_targets = Vec::new();
|
||||
let Some(network_metadata) = self.network_metadata(network_path) else {
|
||||
log::error!("Could not get nested network_metadata in collect_frontend_click_targets");
|
||||
|
@ -3059,27 +3059,21 @@ impl NodeNetworkInterface {
|
|||
let mut node_path = String::new();
|
||||
|
||||
if let ClickTargetType::Subpath(subpath) = node_click_targets.node_click_target.target_type() {
|
||||
let _ = subpath.subpath_to_svg(&mut node_path, DAffine2::IDENTITY);
|
||||
node_path.push_str(subpath.to_bezpath().to_svg().as_str())
|
||||
}
|
||||
all_node_click_targets.push((node_id, node_path));
|
||||
for port in node_click_targets.port_click_targets.click_targets().chain(import_export_click_targets.click_targets()) {
|
||||
if let ClickTargetType::Subpath(subpath) = port.target_type() {
|
||||
let mut port_path = String::new();
|
||||
let _ = subpath.subpath_to_svg(&mut port_path, DAffine2::IDENTITY);
|
||||
port_click_targets.push(port_path);
|
||||
connector_click_targets.push(subpath.to_bezpath().to_svg());
|
||||
}
|
||||
}
|
||||
if let NodeTypeClickTargets::Layer(layer_metadata) = &node_click_targets.node_type_metadata {
|
||||
if let ClickTargetType::Subpath(subpath) = layer_metadata.visibility_click_target.target_type() {
|
||||
let mut port_path = String::new();
|
||||
let _ = subpath.subpath_to_svg(&mut port_path, DAffine2::IDENTITY);
|
||||
icon_click_targets.push(port_path);
|
||||
icon_click_targets.push(subpath.to_bezpath().to_svg());
|
||||
}
|
||||
|
||||
if let ClickTargetType::Subpath(subpath) = layer_metadata.grip_click_target.target_type() {
|
||||
let mut port_path = String::new();
|
||||
let _ = subpath.subpath_to_svg(&mut port_path, DAffine2::IDENTITY);
|
||||
icon_click_targets.push(port_path);
|
||||
icon_click_targets.push(subpath.to_bezpath().to_svg());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3095,9 +3089,8 @@ impl NodeNetworkInterface {
|
|||
});
|
||||
|
||||
let bounds = self.all_nodes_bounding_box(network_path).cloned().unwrap_or([DVec2::ZERO, DVec2::ZERO]);
|
||||
let rect = bezier_rs::Subpath::<PointId>::new_rect(bounds[0], bounds[1]);
|
||||
let mut all_nodes_bounding_box = String::new();
|
||||
let _ = rect.subpath_to_svg(&mut all_nodes_bounding_box, DAffine2::IDENTITY);
|
||||
let rect = Subpath::<PointId>::new_rect(bounds[0], bounds[1]);
|
||||
let all_nodes_bounding_box = rect.to_bezpath().to_svg();
|
||||
|
||||
let Some(rounded_network_edge_distance) = self.rounded_network_edge_distance(network_path).cloned() else {
|
||||
log::error!("Could not get rounded_network_edge_distance in collect_frontend_click_targets");
|
||||
|
@ -3123,9 +3116,8 @@ impl NodeNetworkInterface {
|
|||
.inverse()
|
||||
.transform_point2(import_exports_viewport_bottom_right);
|
||||
|
||||
let import_exports_target = bezier_rs::Subpath::<PointId>::new_rect(node_graph_top_left, node_graph_bottom_right);
|
||||
let mut import_exports_bounding_box = String::new();
|
||||
let _ = import_exports_target.subpath_to_svg(&mut import_exports_bounding_box, DAffine2::IDENTITY);
|
||||
let import_exports_target = Subpath::<PointId>::new_rect(node_graph_top_left, node_graph_bottom_right);
|
||||
let import_exports_bounding_box = import_exports_target.to_bezpath().to_svg();
|
||||
|
||||
let mut modify_import_export = Vec::new();
|
||||
if let Some(modify_import_export_click_targets) = self.modify_import_export(network_path) {
|
||||
|
@ -3136,16 +3128,14 @@ impl NodeNetworkInterface {
|
|||
.chain(modify_import_export_click_targets.reorder_imports_exports.click_targets())
|
||||
{
|
||||
if let ClickTargetType::Subpath(subpath) = click_target.target_type() {
|
||||
let mut remove_string = String::new();
|
||||
let _ = subpath.subpath_to_svg(&mut remove_string, DAffine2::IDENTITY);
|
||||
modify_import_export.push(remove_string);
|
||||
modify_import_export.push(subpath.to_bezpath().to_svg());
|
||||
}
|
||||
}
|
||||
}
|
||||
FrontendClickTargets {
|
||||
node_click_targets,
|
||||
layer_click_targets,
|
||||
port_click_targets,
|
||||
connector_click_targets,
|
||||
icon_click_targets,
|
||||
all_nodes_bounding_box,
|
||||
import_exports_bounding_box,
|
||||
|
@ -3407,7 +3397,7 @@ impl NodeNetworkInterface {
|
|||
return None;
|
||||
};
|
||||
|
||||
let bounding_box_subpath = bezier_rs::Subpath::<PointId>::new_rect(bounds[0], bounds[1]);
|
||||
let bounding_box_subpath = Subpath::<PointId>::new_rect(bounds[0], bounds[1]);
|
||||
bounding_box_subpath.bounding_box_with_transform(network_metadata.persistent_metadata.navigation_metadata.node_graph_to_viewport)
|
||||
}
|
||||
|
||||
|
@ -6047,7 +6037,7 @@ impl Iterator for FlowIter<'_> {
|
|||
} else {
|
||||
0
|
||||
};
|
||||
let take = if self.flow_type == FlowType::UpstreamFlow { usize::MAX } else { 1 };
|
||||
let take = if self.flow_type == FlowType::UpstreamFlow { u32::MAX as usize } else { 1 };
|
||||
let inputs = document_node.inputs.iter().skip(skip).take(take);
|
||||
|
||||
let node_ids = inputs.filter_map(|input| match input {
|
||||
|
|
|
@ -5,11 +5,11 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions:
|
|||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{InputConnector, NodeTemplate, OutputConnector};
|
||||
use crate::messages::prelude::DocumentMessageHandler;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::IVec2;
|
||||
use graph_craft::document::DocumentNode;
|
||||
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
|
||||
use graphene_std::ProtoNodeIdentifier;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{TextAlign, TypesettingConfig};
|
||||
use graphene_std::uuid::NodeId;
|
||||
|
@ -300,10 +300,6 @@ const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
|
|||
"graphene_core::raster::BlendNode",
|
||||
],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::raster_nodes::blending_nodes::blend_color_pair::IDENTIFIER,
|
||||
aliases: &["graphene_raster_nodes::adjustments::BlendColorPairNode", "graphene_core::raster::BlendColorPairNode"],
|
||||
},
|
||||
NodeReplacement {
|
||||
node: graphene_std::raster_nodes::blending_nodes::color_overlay::IDENTIFIER,
|
||||
aliases: &[
|
||||
|
@ -637,9 +633,9 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||
};
|
||||
let vector = Vector::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false));
|
||||
|
||||
// Retrieve the output connectors linked to the "Spline" node's output port
|
||||
// Retrieve the output connectors linked to the "Spline" node's output connector
|
||||
let Some(spline_outputs) = document.network_interface.outward_wires(network_path)?.get(&OutputConnector::node(*node_id, 0)).cloned() else {
|
||||
log::error!("Vec of InputConnector Spline node is connected to its output port 0.");
|
||||
log::error!("Vec of InputConnector Spline node is connected to its output connector 0.");
|
||||
return None;
|
||||
};
|
||||
|
||||
|
@ -684,7 +680,7 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
|
|||
// Reposition the new "Path" node with an offset relative to the original "Spline" node's position
|
||||
document.network_interface.shift_node(&new_path_id, node_position + IVec2::new(-7, 0), network_path);
|
||||
|
||||
// Redirect each output connection from the old node to the new "Spline" node's output port
|
||||
// Redirect each output connection from the old node to the new "Spline" node's output connector
|
||||
for input_connector in spline_outputs {
|
||||
document.network_interface.set_input(&input_connector, NodeInput::node(new_spline_id, 0), network_path);
|
||||
}
|
||||
|
|
|
@ -16,10 +16,12 @@ pub struct MenuBarMessageHandler {
|
|||
pub has_selected_nodes: bool,
|
||||
pub has_selected_layers: bool,
|
||||
pub has_selection_history: (bool, bool),
|
||||
pub spreadsheet_view_open: bool,
|
||||
pub message_logging_verbosity: MessageLoggingVerbosity,
|
||||
pub reset_node_definitions_on_open: bool,
|
||||
pub make_path_editable_is_allowed: bool,
|
||||
pub data_panel_open: bool,
|
||||
pub layers_panel_open: bool,
|
||||
pub properties_panel_open: bool,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
|
@ -585,18 +587,40 @@ impl LayoutHolder for MenuBarMessageHandler {
|
|||
disabled: no_active_document,
|
||||
..MenuBarEntry::default()
|
||||
}],
|
||||
]),
|
||||
),
|
||||
MenuBarEntry::new_root(
|
||||
"Window".into(),
|
||||
false,
|
||||
MenuBarEntryChildren(vec![
|
||||
vec![
|
||||
MenuBarEntry {
|
||||
label: "Properties".into(),
|
||||
icon: Some(if self.properties_panel_open { "CheckboxChecked" } else { "CheckboxUnchecked" }.into()),
|
||||
shortcut: action_keys!(PortfolioMessageDiscriminant::TogglePropertiesPanelOpen),
|
||||
action: MenuBarEntry::create_action(|_| PortfolioMessage::TogglePropertiesPanelOpen.into()),
|
||||
..MenuBarEntry::default()
|
||||
},
|
||||
MenuBarEntry {
|
||||
label: "Layers".into(),
|
||||
icon: Some(if self.layers_panel_open { "CheckboxChecked" } else { "CheckboxUnchecked" }.into()),
|
||||
shortcut: action_keys!(PortfolioMessageDiscriminant::ToggleLayersPanelOpen),
|
||||
action: MenuBarEntry::create_action(|_| PortfolioMessage::ToggleLayersPanelOpen.into()),
|
||||
..MenuBarEntry::default()
|
||||
},
|
||||
],
|
||||
vec![MenuBarEntry {
|
||||
label: "Window: Spreadsheet".into(),
|
||||
icon: Some(if self.spreadsheet_view_open { "CheckboxChecked" } else { "CheckboxUnchecked" }.into()),
|
||||
action: MenuBarEntry::create_action(|_| SpreadsheetMessage::ToggleOpen.into()),
|
||||
disabled: no_active_document,
|
||||
label: "Data".into(),
|
||||
icon: Some(if self.data_panel_open { "CheckboxChecked" } else { "CheckboxUnchecked" }.into()),
|
||||
shortcut: action_keys!(PortfolioMessageDiscriminant::ToggleDataPanelOpen),
|
||||
action: MenuBarEntry::create_action(|_| PortfolioMessage::ToggleDataPanelOpen.into()),
|
||||
..MenuBarEntry::default()
|
||||
}],
|
||||
]),
|
||||
),
|
||||
MenuBarEntry::new_root(
|
||||
"Help".into(),
|
||||
true,
|
||||
false,
|
||||
MenuBarEntryChildren(vec![
|
||||
vec![MenuBarEntry {
|
||||
label: "About Graphite…".into(),
|
||||
|
|
|
@ -4,7 +4,6 @@ mod portfolio_message_handler;
|
|||
pub mod document;
|
||||
pub mod document_migration;
|
||||
pub mod menu_bar;
|
||||
pub mod spreadsheet;
|
||||
pub mod utility_types;
|
||||
|
||||
#[doc(inline)]
|
||||
|
|
|
@ -15,8 +15,6 @@ pub enum PortfolioMessage {
|
|||
MenuBar(MenuBarMessage),
|
||||
#[child]
|
||||
Document(DocumentMessage),
|
||||
#[child]
|
||||
Spreadsheet(SpreadsheetMessage),
|
||||
|
||||
// Messages
|
||||
Init,
|
||||
|
@ -128,6 +126,9 @@ pub enum PortfolioMessage {
|
|||
document_id: DocumentId,
|
||||
ignore_hash: bool,
|
||||
},
|
||||
ToggleDataPanelOpen,
|
||||
TogglePropertiesPanelOpen,
|
||||
ToggleLayersPanelOpen,
|
||||
ToggleRulers,
|
||||
UpdateDocumentWidgets,
|
||||
UpdateOpenDocumentsList,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use super::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use super::document::utility_types::network_interface;
|
||||
use super::spreadsheet::SpreadsheetMessageHandler;
|
||||
use super::utility_types::{PanelType, PersistentData};
|
||||
use crate::application::generate_uuid;
|
||||
use crate::consts::{DEFAULT_DOCUMENT_NAME, DEFAULT_STROKE_WIDTH};
|
||||
|
@ -23,11 +22,12 @@ use crate::messages::tool::common_functionality::graph_modification_utils;
|
|||
use crate::messages::tool::common_functionality::utility_functions::make_path_editable_is_allowed;
|
||||
use crate::messages::tool::utility_types::{HintData, HintGroup, ToolType};
|
||||
use crate::node_graph_executor::{ExportConfig, NodeGraphExecutor};
|
||||
use bezier_rs::BezierHandles;
|
||||
use derivative::*;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::subpath::BezierHandles;
|
||||
use graphene_std::text::Font;
|
||||
use graphene_std::vector::misc::HandleId;
|
||||
use graphene_std::vector::{PointId, SegmentId, Vector, VectorModificationType};
|
||||
|
@ -37,14 +37,15 @@ use std::vec;
|
|||
pub struct PortfolioMessageContext<'a> {
|
||||
pub ipp: &'a InputPreprocessorMessageHandler,
|
||||
pub preferences: &'a PreferencesMessageHandler,
|
||||
pub animation: &'a AnimationMessageHandler,
|
||||
pub current_tool: &'a ToolType,
|
||||
pub message_logging_verbosity: MessageLoggingVerbosity,
|
||||
pub reset_node_definitions_on_open: bool,
|
||||
pub timing_information: TimingInformation,
|
||||
pub animation: &'a AnimationMessageHandler,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, ExtractField)]
|
||||
#[derive(Debug, Derivative, ExtractField)]
|
||||
#[derivative(Default)]
|
||||
pub struct PortfolioMessageHandler {
|
||||
menu_bar_message_handler: MenuBarMessageHandler,
|
||||
pub documents: HashMap<DocumentId, DocumentMessageHandler>,
|
||||
|
@ -55,10 +56,13 @@ pub struct PortfolioMessageHandler {
|
|||
pub persistent_data: PersistentData,
|
||||
pub executor: NodeGraphExecutor,
|
||||
pub selection_mode: SelectionMode,
|
||||
/// The spreadsheet UI allows for graph data to be previewed.
|
||||
pub spreadsheet: SpreadsheetMessageHandler,
|
||||
device_pixel_ratio: Option<f64>,
|
||||
pub reset_node_definitions_on_open: bool,
|
||||
pub data_panel_open: bool,
|
||||
#[derivative(Default(value = "true"))]
|
||||
pub layers_panel_open: bool,
|
||||
#[derivative(Default(value = "true"))]
|
||||
pub properties_panel_open: bool,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
|
@ -67,11 +71,11 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||
let PortfolioMessageContext {
|
||||
ipp,
|
||||
preferences,
|
||||
animation,
|
||||
current_tool,
|
||||
message_logging_verbosity,
|
||||
reset_node_definitions_on_open,
|
||||
timing_information,
|
||||
animation,
|
||||
} = context;
|
||||
|
||||
match message {
|
||||
|
@ -86,7 +90,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||
self.menu_bar_message_handler.has_selected_layers = false;
|
||||
self.menu_bar_message_handler.has_selection_history = (false, false);
|
||||
self.menu_bar_message_handler.make_path_editable_is_allowed = false;
|
||||
self.menu_bar_message_handler.spreadsheet_view_open = self.spreadsheet.spreadsheet_view_open;
|
||||
self.menu_bar_message_handler.data_panel_open = self.data_panel_open;
|
||||
self.menu_bar_message_handler.layers_panel_open = self.layers_panel_open;
|
||||
self.menu_bar_message_handler.properties_panel_open = self.properties_panel_open;
|
||||
self.menu_bar_message_handler.message_logging_verbosity = message_logging_verbosity;
|
||||
self.menu_bar_message_handler.reset_node_definitions_on_open = reset_node_definitions_on_open;
|
||||
|
||||
|
@ -108,9 +114,6 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||
|
||||
self.menu_bar_message_handler.process_message(message, responses, ());
|
||||
}
|
||||
PortfolioMessage::Spreadsheet(message) => {
|
||||
self.spreadsheet.process_message(message, responses, ());
|
||||
}
|
||||
PortfolioMessage::Document(message) => {
|
||||
if let Some(document_id) = self.active_document_id {
|
||||
if let Some(document) = self.documents.get_mut(&document_id) {
|
||||
|
@ -122,6 +125,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||
current_tool,
|
||||
preferences,
|
||||
device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.),
|
||||
data_panel_open: self.data_panel_open,
|
||||
layers_panel_open: self.layers_panel_open,
|
||||
properties_panel_open: self.properties_panel_open,
|
||||
};
|
||||
document.process_message(message, responses, document_inputs)
|
||||
}
|
||||
|
@ -156,6 +162,9 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||
current_tool,
|
||||
preferences,
|
||||
device_pixel_ratio: self.device_pixel_ratio.unwrap_or(1.),
|
||||
data_panel_open: self.data_panel_open,
|
||||
layers_panel_open: self.layers_panel_open,
|
||||
properties_panel_open: self.properties_panel_open,
|
||||
};
|
||||
document.process_message(message, responses, document_inputs)
|
||||
}
|
||||
|
@ -195,12 +204,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||
}
|
||||
PortfolioMessage::CloseAllDocuments => {
|
||||
if self.active_document_id.is_some() {
|
||||
responses.add(BroadcastEvent::ToolAbort);
|
||||
responses.add(EventMessage::ToolAbort);
|
||||
responses.add(ToolMessage::DeactivateTools);
|
||||
|
||||
// Clear relevant UI layouts if there are no documents
|
||||
responses.add(PropertiesPanelMessage::Clear);
|
||||
responses.add(DocumentMessage::ClearLayersPanel);
|
||||
responses.add(DataPanelMessage::ClearLayout);
|
||||
let hint_data = HintData(vec![HintGroup(vec![])]);
|
||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||
}
|
||||
|
@ -225,6 +235,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||
// Clear UI layouts that assume the existence of a document
|
||||
responses.add(PropertiesPanelMessage::Clear);
|
||||
responses.add(DocumentMessage::ClearLayersPanel);
|
||||
responses.add(DataPanelMessage::ClearLayout);
|
||||
let hint_data = HintData(vec![HintGroup(vec![])]);
|
||||
responses.add(FrontendMessage::UpdateInputHints { hint_data });
|
||||
}
|
||||
|
@ -239,7 +250,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||
PortfolioMessage::CloseDocumentWithConfirmation { document_id } => {
|
||||
let target_document = self.documents.get(&document_id).unwrap();
|
||||
if target_document.is_saved() {
|
||||
responses.add(BroadcastEvent::ToolAbort);
|
||||
responses.add(EventMessage::ToolAbort);
|
||||
responses.add(PortfolioMessage::CloseDocument { document_id });
|
||||
} else {
|
||||
let dialog = simple_dialogs::CloseDocumentDialog {
|
||||
|
@ -344,13 +355,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||
self.persistent_data.font_cache.insert(font, preview_url, data);
|
||||
self.executor.update_font_cache(self.persistent_data.font_cache.clone());
|
||||
for document_id in self.document_ids.iter() {
|
||||
let inspect_node = self.inspect_node_id();
|
||||
let node_to_inspect = self.node_to_inspect();
|
||||
if let Ok(message) = self.executor.submit_node_graph_evaluation(
|
||||
self.documents.get_mut(document_id).expect("Tried to render non-existent document"),
|
||||
*document_id,
|
||||
ipp.viewport_bounds.size().as_uvec2(),
|
||||
timing_information,
|
||||
inspect_node,
|
||||
node_to_inspect,
|
||||
true,
|
||||
) {
|
||||
responses.add_front(message);
|
||||
|
@ -384,11 +395,11 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||
|
||||
let document_id = DocumentId(generate_uuid());
|
||||
if self.active_document().is_some() {
|
||||
new_responses.add(BroadcastEvent::ToolAbort);
|
||||
new_responses.add(EventMessage::ToolAbort);
|
||||
new_responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });
|
||||
}
|
||||
|
||||
self.load_document(new_document, document_id, &mut new_responses, false);
|
||||
self.load_document(new_document, document_id, self.layers_panel_open, &mut new_responses, false);
|
||||
new_responses.add(PortfolioMessage::SelectDocument { document_id });
|
||||
new_responses.extend(responses.drain(..));
|
||||
*responses = new_responses;
|
||||
|
@ -504,7 +515,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||
document.set_save_state(document_is_saved);
|
||||
|
||||
// Load the document into the portfolio so it opens in the editor
|
||||
self.load_document(document, document_id, responses, to_front);
|
||||
self.load_document(document, document_id, self.layers_panel_open, responses, to_front);
|
||||
}
|
||||
PortfolioMessage::PasteIntoFolder { clipboard, parent, insert_index } => {
|
||||
let mut all_new_ids = Vec::new();
|
||||
|
@ -770,7 +781,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||
|
||||
if create_document {
|
||||
responses.add(PortfolioMessage::NewDocumentWithName {
|
||||
name: name.clone().unwrap_or("Untitled Document".into()),
|
||||
name: name.clone().unwrap_or(DEFAULT_DOCUMENT_NAME.into()),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -801,7 +812,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||
|
||||
if create_document {
|
||||
responses.add(PortfolioMessage::NewDocumentWithName {
|
||||
name: name.clone().unwrap_or("Untitled Document".into()),
|
||||
name: name.clone().unwrap_or(DEFAULT_DOCUMENT_NAME.into()),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -863,8 +874,8 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||
responses.add(ToolMessage::InitTools);
|
||||
responses.add(NodeGraphMessage::Init);
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
responses.add(BroadcastEvent::ToolAbort);
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(EventMessage::ToolAbort);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
responses.add(NavigationMessage::CanvasPan { delta: (0., 0.).into() });
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(DocumentMessage::GraphViewOverlay { open: node_graph_open });
|
||||
|
@ -918,13 +929,13 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||
}
|
||||
}
|
||||
PortfolioMessage::SubmitGraphRender { document_id, ignore_hash } => {
|
||||
let inspect_node = self.inspect_node_id();
|
||||
let node_to_inspect = self.node_to_inspect();
|
||||
let result = self.executor.submit_node_graph_evaluation(
|
||||
self.documents.get_mut(&document_id).expect("Tried to render non-existent document"),
|
||||
document_id,
|
||||
ipp.viewport_bounds.size().as_uvec2(),
|
||||
timing_information,
|
||||
inspect_node,
|
||||
node_to_inspect,
|
||||
ignore_hash,
|
||||
);
|
||||
|
||||
|
@ -938,6 +949,58 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||
Ok(message) => responses.add_front(message),
|
||||
}
|
||||
}
|
||||
PortfolioMessage::ToggleDataPanelOpen => {
|
||||
self.data_panel_open = !self.data_panel_open;
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
|
||||
// Run the graph to grab the data
|
||||
if self.data_panel_open {
|
||||
// When opening, we make the frontend show the panel first so it can start receiving its message subscriptions for the data it will display
|
||||
responses.add(FrontendMessage::UpdateDataPanelState { open: self.data_panel_open });
|
||||
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
} else {
|
||||
// If we don't clear the panel, the layout diffing system will assume widgets still exist when it attempts to update the data panel next time it is opened
|
||||
responses.add(DataPanelMessage::ClearLayout);
|
||||
|
||||
// When closing, we make the frontend hide the panel last so it can finish receiving its message subscriptions before it is destroyed
|
||||
responses.add(FrontendMessage::UpdateDataPanelState { open: self.data_panel_open });
|
||||
}
|
||||
}
|
||||
PortfolioMessage::TogglePropertiesPanelOpen => {
|
||||
self.properties_panel_open = !self.properties_panel_open;
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
|
||||
responses.add(FrontendMessage::UpdatePropertiesPanelState { open: self.properties_panel_open });
|
||||
|
||||
// Run the graph to grab the data
|
||||
if self.properties_panel_open {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
}
|
||||
PortfolioMessage::ToggleLayersPanelOpen => {
|
||||
self.layers_panel_open = !self.layers_panel_open;
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
|
||||
// Run the graph to grab the data
|
||||
if self.layers_panel_open {
|
||||
// When opening, we make the frontend show the panel first so it can start receiving its message subscriptions for the data it will display
|
||||
responses.add(FrontendMessage::UpdateLayersPanelState { open: self.layers_panel_open });
|
||||
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
responses.add(DeferMessage::AfterGraphRun {
|
||||
messages: vec![NodeGraphMessage::UpdateLayerPanel.into(), DocumentMessage::DocumentStructureChanged.into()],
|
||||
});
|
||||
} else {
|
||||
// If we don't clear the panel, the layout diffing system will assume widgets still exist when it attempts to update the layers panel next time it is opened
|
||||
responses.add(DocumentMessage::ClearLayersPanel);
|
||||
|
||||
// When closing, we make the frontend hide the panel last so it can finish receiving its message subscriptions before it is destroyed
|
||||
responses.add(FrontendMessage::UpdateLayersPanelState { open: self.layers_panel_open });
|
||||
}
|
||||
}
|
||||
PortfolioMessage::ToggleRulers => {
|
||||
if let Some(document) = self.active_document_mut() {
|
||||
document.rulers_visible = !document.rulers_visible;
|
||||
|
@ -987,6 +1050,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||
PasteIntoFolder,
|
||||
PrevDocument,
|
||||
ToggleRulers,
|
||||
ToggleDataPanelOpen,
|
||||
);
|
||||
|
||||
// Extend with actions that require an active document
|
||||
|
@ -1056,19 +1120,19 @@ impl PortfolioMessageHandler {
|
|||
}
|
||||
}
|
||||
|
||||
fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: DocumentId, responses: &mut VecDeque<Message>, to_front: bool) {
|
||||
fn load_document(&mut self, new_document: DocumentMessageHandler, document_id: DocumentId, layers_panel_open: bool, responses: &mut VecDeque<Message>, to_front: bool) {
|
||||
if to_front {
|
||||
self.document_ids.push_front(document_id);
|
||||
} else {
|
||||
self.document_ids.push_back(document_id);
|
||||
}
|
||||
new_document.update_layers_panel_control_bar_widgets(responses);
|
||||
new_document.update_layers_panel_bottom_bar_widgets(responses);
|
||||
new_document.update_layers_panel_control_bar_widgets(layers_panel_open, responses);
|
||||
new_document.update_layers_panel_bottom_bar_widgets(layers_panel_open, responses);
|
||||
|
||||
self.documents.insert(document_id, new_document);
|
||||
|
||||
if self.active_document().is_some() {
|
||||
responses.add(BroadcastEvent::ToolAbort);
|
||||
responses.add(EventMessage::ToolAbort);
|
||||
responses.add(ToolMessage::DeactivateTools);
|
||||
} else {
|
||||
// Load the default font upon creating the first document
|
||||
|
@ -1111,17 +1175,17 @@ impl PortfolioMessageHandler {
|
|||
result
|
||||
}
|
||||
|
||||
/// Get the id of the node that should be used as the target for the spreadsheet
|
||||
pub fn inspect_node_id(&self) -> Option<NodeId> {
|
||||
// Spreadsheet not open, skipping
|
||||
if !self.spreadsheet.spreadsheet_view_open {
|
||||
/// Get the ID of the selected node that should be used as the current source for the Data panel.
|
||||
pub fn node_to_inspect(&self) -> Option<NodeId> {
|
||||
// Skip if the Data panel is not open
|
||||
if !self.data_panel_open {
|
||||
return None;
|
||||
}
|
||||
|
||||
let document = self.documents.get(&self.active_document_id?)?;
|
||||
let selected_nodes = document.network_interface.selected_nodes().0;
|
||||
|
||||
// Selected nodes != 1, skipping
|
||||
// Skip if there is not exactly one selected node
|
||||
if selected_nodes.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
mod spreadsheet_message;
|
||||
mod spreadsheet_message_handler;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use spreadsheet_message::*;
|
||||
#[doc(inline)]
|
||||
pub use spreadsheet_message_handler::*;
|
|
@ -1,326 +0,0 @@
|
|||
use super::VectorDomain;
|
||||
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, LayoutTarget, WidgetLayout};
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::*;
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::Context;
|
||||
use graphene_std::memo::IORecord;
|
||||
use graphene_std::raster::Image;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::vector::Vector;
|
||||
use graphene_std::{Artboard, Graphic};
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// The spreadsheet UI allows for graph data to be previewed.
|
||||
#[derive(Default, Debug, Clone, ExtractField)]
|
||||
pub struct SpreadsheetMessageHandler {
|
||||
/// Sets whether or not the spreadsheet is drawn.
|
||||
pub spreadsheet_view_open: bool,
|
||||
inspect_node: Option<NodeId>,
|
||||
introspected_data: Option<Arc<dyn Any + Send + Sync>>,
|
||||
element_path: Vec<usize>,
|
||||
viewing_vector_domain: VectorDomain,
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl MessageHandler<SpreadsheetMessage, ()> for SpreadsheetMessageHandler {
|
||||
fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque<Message>, _: ()) {
|
||||
match message {
|
||||
SpreadsheetMessage::ToggleOpen => {
|
||||
self.spreadsheet_view_open = !self.spreadsheet_view_open;
|
||||
// Run the graph to grab the data
|
||||
if self.spreadsheet_view_open {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
// Update checked UI state for open
|
||||
responses.add(MenuBarMessage::SendLayout);
|
||||
self.update_layout(responses);
|
||||
}
|
||||
|
||||
SpreadsheetMessage::UpdateLayout { mut inspect_result } => {
|
||||
self.inspect_node = Some(inspect_result.inspect_node);
|
||||
self.introspected_data = inspect_result.take_data();
|
||||
self.update_layout(responses)
|
||||
}
|
||||
|
||||
SpreadsheetMessage::PushToElementPath { index } => {
|
||||
self.element_path.push(index);
|
||||
self.update_layout(responses);
|
||||
}
|
||||
SpreadsheetMessage::TruncateElementPath { len } => {
|
||||
self.element_path.truncate(len);
|
||||
self.update_layout(responses);
|
||||
}
|
||||
|
||||
SpreadsheetMessage::ViewVectorDomain { domain } => {
|
||||
self.viewing_vector_domain = domain;
|
||||
self.update_layout(responses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn actions(&self) -> ActionList {
|
||||
actions!(SpreadsheetMessage;)
|
||||
}
|
||||
}
|
||||
|
||||
impl SpreadsheetMessageHandler {
|
||||
fn update_layout(&mut self, responses: &mut VecDeque<Message>) {
|
||||
responses.add(FrontendMessage::UpdateSpreadsheetState {
|
||||
node: self.inspect_node,
|
||||
open: self.spreadsheet_view_open,
|
||||
});
|
||||
if !self.spreadsheet_view_open {
|
||||
return;
|
||||
}
|
||||
let mut layout_data = LayoutData {
|
||||
current_depth: 0,
|
||||
desired_path: &mut self.element_path,
|
||||
breadcrumbs: Vec::new(),
|
||||
vector_domain: self.viewing_vector_domain,
|
||||
};
|
||||
let mut layout = self
|
||||
.introspected_data
|
||||
.as_ref()
|
||||
.map(|instrospected_data| generate_layout(instrospected_data, &mut layout_data))
|
||||
.unwrap_or_else(|| Some(label("No data")))
|
||||
.unwrap_or_else(|| label("Failed to downcast data"));
|
||||
|
||||
if layout_data.breadcrumbs.len() > 1 {
|
||||
let breadcrumb = BreadcrumbTrailButtons::new(layout_data.breadcrumbs)
|
||||
.on_update(|&len| SpreadsheetMessage::TruncateElementPath { len: len as usize }.into())
|
||||
.widget_holder();
|
||||
layout.insert(0, LayoutGroup::Row { widgets: vec![breadcrumb] });
|
||||
}
|
||||
|
||||
responses.add(LayoutMessage::SendLayout {
|
||||
layout: Layout::WidgetLayout(WidgetLayout { layout }),
|
||||
layout_target: LayoutTarget::Spreadsheet,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct LayoutData<'a> {
|
||||
current_depth: usize,
|
||||
desired_path: &'a mut Vec<usize>,
|
||||
breadcrumbs: Vec<String>,
|
||||
vector_domain: VectorDomain,
|
||||
}
|
||||
|
||||
fn generate_layout(introspected_data: &Arc<dyn std::any::Any + Send + Sync + 'static>, data: &mut LayoutData) -> Option<Vec<LayoutGroup>> {
|
||||
// We simply try random types. TODO: better strategy.
|
||||
#[allow(clippy::manual_map)]
|
||||
if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Artboard>>>() {
|
||||
Some(io.output.layout_with_breadcrumb(data))
|
||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), Table<Artboard>>>() {
|
||||
Some(io.output.layout_with_breadcrumb(data))
|
||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Vector>>>() {
|
||||
Some(io.output.layout_with_breadcrumb(data))
|
||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), Table<Vector>>>() {
|
||||
Some(io.output.layout_with_breadcrumb(data))
|
||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<Context, Table<Graphic>>>() {
|
||||
Some(io.output.layout_with_breadcrumb(data))
|
||||
} else if let Some(io) = introspected_data.downcast_ref::<IORecord<(), Table<Graphic>>>() {
|
||||
Some(io.output.layout_with_breadcrumb(data))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn column_headings(value: &[&str]) -> Vec<WidgetHolder> {
|
||||
value.iter().map(|text| TextLabel::new(*text).widget_holder()).collect()
|
||||
}
|
||||
|
||||
fn label(x: impl Into<String>) -> Vec<LayoutGroup> {
|
||||
let error = vec![TextLabel::new(x).widget_holder()];
|
||||
vec![LayoutGroup::Row { widgets: error }]
|
||||
}
|
||||
|
||||
trait TableRowLayout {
|
||||
fn type_name() -> &'static str;
|
||||
fn identifier(&self) -> String;
|
||||
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
data.breadcrumbs.push(self.identifier());
|
||||
self.compute_layout(data)
|
||||
}
|
||||
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup>;
|
||||
}
|
||||
|
||||
impl TableRowLayout for Graphic {
|
||||
fn type_name() -> &'static str {
|
||||
"Graphic"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
match self {
|
||||
Self::Graphic(graphic) => graphic.identifier(),
|
||||
Self::Vector(vector) => vector.identifier(),
|
||||
Self::RasterCPU(_) => "Raster (on CPU)".to_string(),
|
||||
Self::RasterGPU(_) => "Raster (on GPU)".to_string(),
|
||||
}
|
||||
}
|
||||
// Don't put a breadcrumb for Graphic
|
||||
fn layout_with_breadcrumb(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
self.compute_layout(data)
|
||||
}
|
||||
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
match self {
|
||||
Self::Graphic(table) => table.layout_with_breadcrumb(data),
|
||||
Self::Vector(table) => table.layout_with_breadcrumb(data),
|
||||
Self::RasterCPU(_) => label("Raster is not supported"),
|
||||
Self::RasterGPU(_) => label("Raster is not supported"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for Vector {
|
||||
fn type_name() -> &'static str {
|
||||
"Vector"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format!(
|
||||
"Vector ({} point{}, {} segment{})",
|
||||
self.point_domain.ids().len(),
|
||||
if self.point_domain.ids().len() == 1 { "" } else { "s" },
|
||||
self.segment_domain.ids().len(),
|
||||
if self.segment_domain.ids().len() == 1 { "" } else { "s" }
|
||||
)
|
||||
}
|
||||
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let colinear = self.colinear_manipulators.iter().map(|[a, b]| format!("[{a} / {b}]")).collect::<Vec<_>>().join(", ");
|
||||
let colinear = if colinear.is_empty() { "None" } else { &colinear };
|
||||
let style = vec![
|
||||
TextLabel::new(format!(
|
||||
"{}\n\nColinear Handle IDs: {}\nPreserves Reference to Upstream Nested Layers for Editing by Tools: {}",
|
||||
self.style,
|
||||
colinear,
|
||||
if self.upstream_nested_layers.is_some() { "Yes" } else { "No" }
|
||||
))
|
||||
.multiline(true)
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
let domain_entries = [VectorDomain::Points, VectorDomain::Segments, VectorDomain::Regions]
|
||||
.into_iter()
|
||||
.map(|domain| {
|
||||
RadioEntryData::new(format!("{domain:?}"))
|
||||
.label(format!("{domain:?}"))
|
||||
.on_update(move |_| SpreadsheetMessage::ViewVectorDomain { domain }.into())
|
||||
})
|
||||
.collect();
|
||||
let domain = vec![RadioInput::new(domain_entries).selected_index(Some(data.vector_domain as u32)).widget_holder()];
|
||||
|
||||
let mut table_rows = Vec::new();
|
||||
match data.vector_domain {
|
||||
VectorDomain::Points => {
|
||||
table_rows.push(column_headings(&["", "position"]));
|
||||
table_rows.extend(
|
||||
self.point_domain
|
||||
.iter()
|
||||
.map(|(id, position)| vec![TextLabel::new(format!("{}", id.inner())).widget_holder(), TextLabel::new(format!("{}", position)).widget_holder()]),
|
||||
);
|
||||
}
|
||||
VectorDomain::Segments => {
|
||||
table_rows.push(column_headings(&["", "start_index", "end_index", "handles"]));
|
||||
table_rows.extend(self.segment_domain.iter().map(|(id, start, end, handles)| {
|
||||
vec![
|
||||
TextLabel::new(format!("{}", id.inner())).widget_holder(),
|
||||
TextLabel::new(format!("{}", start)).widget_holder(),
|
||||
TextLabel::new(format!("{}", end)).widget_holder(),
|
||||
TextLabel::new(format!("{:?}", handles)).widget_holder(),
|
||||
]
|
||||
}));
|
||||
}
|
||||
VectorDomain::Regions => {
|
||||
table_rows.push(column_headings(&["", "segment_range", "fill"]));
|
||||
table_rows.extend(self.region_domain.iter().map(|(id, segment_range, fill)| {
|
||||
vec![
|
||||
TextLabel::new(format!("{}", id.inner())).widget_holder(),
|
||||
TextLabel::new(format!("{:?}", segment_range)).widget_holder(),
|
||||
TextLabel::new(format!("{}", fill.inner())).widget_holder(),
|
||||
]
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
vec![LayoutGroup::Row { widgets: style }, LayoutGroup::Row { widgets: domain }, LayoutGroup::Table { rows: table_rows }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for Image<Color> {
|
||||
fn type_name() -> &'static str {
|
||||
"Image"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format!("Image ({}x{})", self.width, self.height)
|
||||
}
|
||||
fn compute_layout(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
let rows = vec![vec![TextLabel::new(format!("Image ({}x{})", self.width, self.height)).widget_holder()]];
|
||||
vec![LayoutGroup::Table { rows }]
|
||||
}
|
||||
}
|
||||
|
||||
impl TableRowLayout for Artboard {
|
||||
fn type_name() -> &'static str {
|
||||
"Artboard"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
self.label.clone()
|
||||
}
|
||||
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
self.content.compute_layout(data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TableRowLayout> TableRowLayout for Table<T> {
|
||||
fn type_name() -> &'static str {
|
||||
"Table"
|
||||
}
|
||||
fn identifier(&self) -> String {
|
||||
format!("Table<{}> ({} row{})", T::type_name(), self.len(), if self.len() == 1 { "" } else { "s" })
|
||||
}
|
||||
fn compute_layout(&self, data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||
if let Some(index) = data.desired_path.get(data.current_depth).copied() {
|
||||
if let Some(row) = self.get(index) {
|
||||
data.current_depth += 1;
|
||||
let result = row.element.layout_with_breadcrumb(data);
|
||||
data.current_depth -= 1;
|
||||
return result;
|
||||
} else {
|
||||
warn!("Desired path truncated");
|
||||
data.desired_path.truncate(data.current_depth);
|
||||
}
|
||||
}
|
||||
|
||||
let mut rows = self
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, row)| {
|
||||
let (scale, angle, translation) = row.transform.to_scale_angle_translation();
|
||||
let rotation = if angle == -0. { 0. } else { angle.to_degrees() };
|
||||
let round = |x: f64| (x * 1e3).round() / 1e3;
|
||||
vec![
|
||||
TextLabel::new(format!("{index}")).widget_holder(),
|
||||
TextButton::new(row.element.identifier())
|
||||
.on_update(move |_| SpreadsheetMessage::PushToElementPath { index }.into())
|
||||
.widget_holder(),
|
||||
TextLabel::new(format!(
|
||||
"Location: ({} px, {} px) — Rotation: {rotation:2}° — Scale: ({}x, {}x)",
|
||||
round(translation.x),
|
||||
round(translation.y),
|
||||
round(scale.x),
|
||||
round(scale.y)
|
||||
))
|
||||
.widget_holder(),
|
||||
TextLabel::new(format!("{}", row.alpha_blending)).widget_holder(),
|
||||
TextLabel::new(row.source_node_id.map_or_else(|| "-".to_string(), |id| format!("{}", id.0))).widget_holder(),
|
||||
]
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
rows.insert(0, column_headings(&["", "element", "transform", "alpha_blending", "source_node_id"]));
|
||||
|
||||
vec![LayoutGroup::Table { rows }]
|
||||
}
|
||||
}
|
|
@ -43,7 +43,7 @@ pub enum PanelType {
|
|||
Document,
|
||||
Layers,
|
||||
Properties,
|
||||
Spreadsheet,
|
||||
DataPanel,
|
||||
}
|
||||
|
||||
impl From<String> for PanelType {
|
||||
|
@ -52,7 +52,7 @@ impl From<String> for PanelType {
|
|||
"Document" => PanelType::Document,
|
||||
"Layers" => PanelType::Layers,
|
||||
"Properties" => PanelType::Properties,
|
||||
"Spreadsheet" => PanelType::Spreadsheet,
|
||||
"Data" => PanelType::DataPanel,
|
||||
_ => panic!("Unknown panel type: {value}"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
|||
}
|
||||
PreferencesMessage::ResetToDefaults => {
|
||||
refresh_dialog(responses);
|
||||
responses.add(KeyMappingMessage::ModifyMapping(MappingVariant::Default));
|
||||
responses.add(KeyMappingMessage::ModifyMapping { mapping: MappingVariant::Default });
|
||||
|
||||
*self = Self::default()
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
|
|||
self.zoom_with_scroll = zoom_with_scroll;
|
||||
|
||||
let variant = if zoom_with_scroll { MappingVariant::ZoomWithScroll } else { MappingVariant::Default };
|
||||
responses.add(KeyMappingMessage::ModifyMapping(variant));
|
||||
responses.add(KeyMappingMessage::ModifyMapping { mapping: variant });
|
||||
}
|
||||
PreferencesMessage::SelectionMode { selection_mode } => {
|
||||
self.selection_mode = selection_mode;
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
// Root
|
||||
pub use crate::utility_traits::{ActionList, AsMessage, HierarchicalTree, MessageHandler, ToDiscriminant, TransitiveChild};
|
||||
// Message-related
|
||||
pub use crate::utility_traits::{ActionList, AsMessage, ExtractField, HierarchicalTree, MessageHandler, ToDiscriminant, TransitiveChild};
|
||||
pub use crate::utility_types::{DebugMessageTree, MessageData};
|
||||
|
||||
// Message, MessageData, MessageDiscriminant, MessageHandler
|
||||
pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler};
|
||||
pub use crate::messages::app_window::{AppWindowMessage, AppWindowMessageDiscriminant, AppWindowMessageHandler};
|
||||
pub use crate::messages::broadcast::event::{EventMessage, EventMessageContext, EventMessageDiscriminant, EventMessageHandler};
|
||||
pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};
|
||||
pub use crate::messages::debug::{DebugMessage, DebugMessageDiscriminant, DebugMessageHandler};
|
||||
pub use crate::messages::defer::{DeferMessage, DeferMessageDiscriminant, DeferMessageHandler};
|
||||
|
@ -17,6 +19,7 @@ pub use crate::messages::input_mapper::key_mapping::{KeyMappingMessage, KeyMappi
|
|||
pub use crate::messages::input_mapper::{InputMapperMessage, InputMapperMessageContext, InputMapperMessageDiscriminant, InputMapperMessageHandler};
|
||||
pub use crate::messages::input_preprocessor::{InputPreprocessorMessage, InputPreprocessorMessageContext, InputPreprocessorMessageDiscriminant, InputPreprocessorMessageHandler};
|
||||
pub use crate::messages::layout::{LayoutMessage, LayoutMessageDiscriminant, LayoutMessageHandler};
|
||||
pub use crate::messages::portfolio::document::data_panel::{DataPanelMessage, DataPanelMessageDiscriminant};
|
||||
pub use crate::messages::portfolio::document::graph_operation::{GraphOperationMessage, GraphOperationMessageContext, GraphOperationMessageDiscriminant, GraphOperationMessageHandler};
|
||||
pub use crate::messages::portfolio::document::navigation::{NavigationMessage, NavigationMessageContext, NavigationMessageDiscriminant, NavigationMessageHandler};
|
||||
pub use crate::messages::portfolio::document::node_graph::{NodeGraphMessage, NodeGraphMessageDiscriminant, NodeGraphMessageHandler};
|
||||
|
@ -24,15 +27,12 @@ pub use crate::messages::portfolio::document::overlays::{OverlaysMessage, Overla
|
|||
pub use crate::messages::portfolio::document::properties_panel::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant, PropertiesPanelMessageHandler};
|
||||
pub use crate::messages::portfolio::document::{DocumentMessage, DocumentMessageContext, DocumentMessageDiscriminant, DocumentMessageHandler};
|
||||
pub use crate::messages::portfolio::menu_bar::{MenuBarMessage, MenuBarMessageDiscriminant, MenuBarMessageHandler};
|
||||
pub use crate::messages::portfolio::spreadsheet::{SpreadsheetMessage, SpreadsheetMessageDiscriminant};
|
||||
pub use crate::messages::portfolio::{PortfolioMessage, PortfolioMessageContext, PortfolioMessageDiscriminant, PortfolioMessageHandler};
|
||||
pub use crate::messages::preferences::{PreferencesMessage, PreferencesMessageDiscriminant, PreferencesMessageHandler};
|
||||
pub use crate::messages::tool::transform_layer::{TransformLayerMessage, TransformLayerMessageDiscriminant, TransformLayerMessageHandler};
|
||||
pub use crate::messages::tool::{ToolMessage, ToolMessageContext, ToolMessageDiscriminant, ToolMessageHandler};
|
||||
pub use crate::messages::workspace::{WorkspaceMessage, WorkspaceMessageDiscriminant, WorkspaceMessageHandler};
|
||||
|
||||
// Message, MessageDiscriminant
|
||||
pub use crate::messages::broadcast::broadcast_event::{BroadcastEvent, BroadcastEventDiscriminant};
|
||||
pub use crate::messages::message::{Message, MessageDiscriminant};
|
||||
pub use crate::messages::tool::tool_messages::artboard_tool::{ArtboardToolMessage, ArtboardToolMessageDiscriminant};
|
||||
pub use crate::messages::tool::tool_messages::brush_tool::{BrushToolMessage, BrushToolMessageDiscriminant};
|
||||
|
@ -48,7 +48,7 @@ pub use crate::messages::tool::tool_messages::shape_tool::{ShapeToolMessage, Sha
|
|||
pub use crate::messages::tool::tool_messages::spline_tool::{SplineToolMessage, SplineToolMessageDiscriminant};
|
||||
pub use crate::messages::tool::tool_messages::text_tool::{TextToolMessage, TextToolMessageDiscriminant};
|
||||
|
||||
// Helper
|
||||
// Helper/miscellaneous
|
||||
pub use crate::messages::globals::global_variables::*;
|
||||
pub use crate::messages::portfolio::document::utility_types::misc::DocumentId;
|
||||
pub use graphite_proc_macros::*;
|
||||
|
|
|
@ -14,7 +14,7 @@ impl AutoPanning {
|
|||
|
||||
for message in messages {
|
||||
responses.add(BroadcastMessage::SubscribeEvent {
|
||||
on: BroadcastEvent::AnimationFrame,
|
||||
on: EventMessage::AnimationFrame,
|
||||
send: Box::new(message.clone()),
|
||||
});
|
||||
}
|
||||
|
@ -27,8 +27,8 @@ impl AutoPanning {
|
|||
|
||||
for message in messages {
|
||||
responses.add(BroadcastMessage::UnsubscribeEvent {
|
||||
on: BroadcastEvent::AnimationFrame,
|
||||
message: Box::new(message.clone()),
|
||||
on: EventMessage::AnimationFrame,
|
||||
send: Box::new(message.clone()),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions;
|
|||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::network_interface::{FlowType, InputConnector, NodeNetworkInterface, NodeTemplate};
|
||||
use crate::messages::prelude::*;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::DVec2;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{NodeId, NodeInput};
|
||||
|
@ -12,10 +11,11 @@ use graphene_std::Color;
|
|||
use graphene_std::NodeInputDecleration;
|
||||
use graphene_std::raster::BlendMode;
|
||||
use graphene_std::raster_types::{CPU, GPU, Raster};
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{Font, TypesettingConfig};
|
||||
use graphene_std::vector::misc::ManipulatorPointId;
|
||||
use graphene_std::vector::style::Gradient;
|
||||
use graphene_std::vector::style::{Fill, Gradient};
|
||||
use graphene_std::vector::{PointId, SegmentId, VectorModificationType};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
|
@ -273,7 +273,7 @@ pub fn get_gradient(layer: LayerNodeIdentifier, network_interface: &NodeNetworkI
|
|||
let fill_index = 1;
|
||||
|
||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Fill")?;
|
||||
let TaggedValue::Fill(graphene_std::vector::style::Fill::Gradient(gradient)) = inputs.get(fill_index)?.as_value()? else {
|
||||
let TaggedValue::Fill(Fill::Gradient(gradient)) = inputs.get(fill_index)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
Some(gradient.clone())
|
||||
|
@ -284,7 +284,7 @@ pub fn get_fill_color(layer: LayerNodeIdentifier, network_interface: &NodeNetwor
|
|||
let fill_index = 1;
|
||||
|
||||
let inputs = NodeGraphLayer::new(layer, network_interface).find_node_inputs("Fill")?;
|
||||
let TaggedValue::Fill(graphene_std::vector::style::Fill::Solid(color)) = inputs.get(fill_index)?.as_value()? else {
|
||||
let TaggedValue::Fill(Fill::Solid(color)) = inputs.get(fill_index)?.as_value()? else {
|
||||
return None;
|
||||
};
|
||||
Some(color.to_linear_srgb())
|
||||
|
|
|
@ -43,18 +43,19 @@ fn draw_line_with_length(line_start: DVec2, line_end: DVec2, transform: DAffine2
|
|||
}
|
||||
}
|
||||
|
||||
/// Draws a dashed outline around a rectangle to visualize the AABB
|
||||
/// Draws a dashed outline around the given rectangle (assumed to be in document space).
|
||||
/// The provided transform is applied to convert coordinates (e.g., to viewport space) during rendering.
|
||||
fn draw_dashed_rect_outline(rect: Rect, transform: DAffine2, overlay_context: &mut OverlayContext) {
|
||||
let min = rect.min();
|
||||
let max = rect.max();
|
||||
|
||||
// Create the four corners of the rectangle
|
||||
let top_left = transform.transform_point2(DVec2::new(min.x, min.y));
|
||||
let top_right = transform.transform_point2(DVec2::new(max.x, min.y));
|
||||
let bottom_right = transform.transform_point2(DVec2::new(max.x, max.y));
|
||||
let bottom_left = transform.transform_point2(DVec2::new(min.x, max.y));
|
||||
// Define corners in document space
|
||||
let top_left = DVec2::new(min.x, min.y);
|
||||
let top_right = DVec2::new(max.x, min.y);
|
||||
let bottom_right = DVec2::new(max.x, max.y);
|
||||
let bottom_left = DVec2::new(min.x, max.y);
|
||||
|
||||
// Draw the four sides as dashed lines
|
||||
// Draw each edge using document-space coordinates; transform is applied inside draw_dashed_line
|
||||
draw_dashed_line(top_left, top_right, transform, overlay_context);
|
||||
draw_dashed_line(top_right, bottom_right, transform, overlay_context);
|
||||
draw_dashed_line(bottom_right, bottom_left, transform, overlay_context);
|
||||
|
|
|
@ -17,8 +17,14 @@ pub fn pin_pivot_widget(active: bool, enabled: bool, source: PivotToolSource) ->
|
|||
.tooltip(String::from(if active { "Unpin Custom Pivot" } else { "Pin Custom Pivot" }) + "\n\nUnless pinned, the pivot will return to its prior reference point when a new selection is made.")
|
||||
.disabled(!enabled)
|
||||
.on_update(move |_| match source {
|
||||
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotPinned).into(),
|
||||
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotPinned).into(),
|
||||
PivotToolSource::Select => SelectToolMessage::SelectOptions {
|
||||
options: SelectOptionsUpdate::TogglePivotPinned,
|
||||
}
|
||||
.into(),
|
||||
PivotToolSource::Path => PathToolMessage::UpdateOptions {
|
||||
options: PathOptionsUpdate::TogglePivotPinned,
|
||||
}
|
||||
.into(),
|
||||
})
|
||||
.widget_holder()
|
||||
}
|
||||
|
@ -41,8 +47,14 @@ pub fn pivot_gizmo_type_widget(state: PivotGizmoState, source: PivotToolSource)
|
|||
MenuListEntry::new(format!("{gizmo_type:?}")).label(gizmo_type.to_string()).on_commit({
|
||||
let value = source.clone();
|
||||
move |_| match value {
|
||||
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::PivotGizmoType(*gizmo_type)).into(),
|
||||
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::PivotGizmoType(*gizmo_type)).into(),
|
||||
PivotToolSource::Select => SelectToolMessage::SelectOptions {
|
||||
options: SelectOptionsUpdate::PivotGizmoType(*gizmo_type),
|
||||
}
|
||||
.into(),
|
||||
PivotToolSource::Path => PathToolMessage::UpdateOptions {
|
||||
options: PathOptionsUpdate::PivotGizmoType(*gizmo_type),
|
||||
}
|
||||
.into(),
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -57,8 +69,14 @@ pub fn pivot_gizmo_type_widget(state: PivotGizmoState, source: PivotToolSource)
|
|||
Disabled: rotation and scaling occurs about the center of the selection bounds.",
|
||||
)
|
||||
.on_update(move |optional_input: &CheckboxInput| match source {
|
||||
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(),
|
||||
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(),
|
||||
PivotToolSource::Select => SelectToolMessage::SelectOptions {
|
||||
options: SelectOptionsUpdate::TogglePivotGizmoType(optional_input.checked),
|
||||
}
|
||||
.into(),
|
||||
PivotToolSource::Path => PathToolMessage::UpdateOptions {
|
||||
options: PathOptionsUpdate::TogglePivotGizmoType(optional_input.checked),
|
||||
}
|
||||
.into(),
|
||||
})
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use super::graph_modification_utils::merge_layers;
|
||||
use super::snapping::{SnapCache, SnapCandidatePoint, SnapData, SnapManager, SnappedPoint};
|
||||
use super::utility_functions::{adjust_handle_colinearity, calculate_bezier_bbox, calculate_segment_angle, restore_g1_continuity, restore_previous_handle_position};
|
||||
use super::utility_functions::{adjust_handle_colinearity, calculate_segment_angle, restore_g1_continuity, restore_previous_handle_position};
|
||||
use crate::consts::HANDLE_LENGTH_FACTOR;
|
||||
use crate::messages::portfolio::document::overlays::utility_functions::selected_segments;
|
||||
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||
|
@ -9,12 +9,15 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node
|
|||
use crate::messages::preferences::SelectionMode;
|
||||
use crate::messages::prelude::*;
|
||||
use crate::messages::tool::common_functionality::snapping::SnapTypeConfiguration;
|
||||
use crate::messages::tool::common_functionality::utility_functions::{is_intersecting, is_visible_point};
|
||||
use crate::messages::tool::common_functionality::utility_functions::is_visible_point;
|
||||
use crate::messages::tool::tool_messages::path_tool::{PathOverlayMode, PointSelectState};
|
||||
use bezier_rs::{Bezier, BezierHandles, Subpath, TValue};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
|
||||
use graphene_std::subpath::{BezierHandles, Subpath};
|
||||
use graphene_std::subpath::{PathSegPoints, pathseg_points};
|
||||
use graphene_std::vector::algorithms::bezpath_algorithms::pathseg_compute_lookup_table;
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point, point_to_dvec2};
|
||||
use graphene_std::vector::{HandleExt, PointId, SegmentId, Vector, VectorModificationType};
|
||||
use kurbo::{Affine, DEFAULT_ACCURACY, Line, ParamCurve, ParamCurveNearest, PathSeg, Rect, Shape};
|
||||
use std::f64::consts::TAU;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
|
@ -26,7 +29,7 @@ pub enum SelectionChange {
|
|||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum SelectionShape<'a> {
|
||||
Box([DVec2; 2]),
|
||||
Box(Rect),
|
||||
Lasso(&'a Vec<DVec2>),
|
||||
}
|
||||
|
||||
|
@ -185,7 +188,7 @@ pub type OpposingHandleLengths = HashMap<LayerNodeIdentifier, HashMap<HandleId,
|
|||
pub struct ClosestSegment {
|
||||
layer: LayerNodeIdentifier,
|
||||
segment: SegmentId,
|
||||
bezier: Bezier,
|
||||
bezier: PathSeg,
|
||||
points: [PointId; 2],
|
||||
colinear: [Option<HandleId>; 2],
|
||||
t: f64,
|
||||
|
@ -205,12 +208,12 @@ impl ClosestSegment {
|
|||
self.points
|
||||
}
|
||||
|
||||
pub fn bezier(&self) -> Bezier {
|
||||
pub fn pathseg(&self) -> PathSeg {
|
||||
self.bezier
|
||||
}
|
||||
|
||||
pub fn closest_point_document(&self) -> DVec2 {
|
||||
self.bezier.evaluate(TValue::Parametric(self.t))
|
||||
point_to_dvec2(self.bezier.eval(self.t))
|
||||
}
|
||||
|
||||
pub fn closest_point_to_viewport(&self) -> DVec2 {
|
||||
|
@ -219,7 +222,7 @@ impl ClosestSegment {
|
|||
|
||||
pub fn closest_point(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> DVec2 {
|
||||
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
||||
let bezier_point = self.bezier.evaluate(TValue::Parametric(self.t));
|
||||
let bezier_point = point_to_dvec2(self.bezier.eval(self.t));
|
||||
transform.transform_point2(bezier_point)
|
||||
}
|
||||
|
||||
|
@ -228,10 +231,10 @@ impl ClosestSegment {
|
|||
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
||||
let layer_mouse_pos = transform.inverse().transform_point2(mouse_position);
|
||||
|
||||
let t = self.bezier.project(layer_mouse_pos).clamp(0., 1.);
|
||||
let t = self.bezier.nearest(dvec2_to_point(layer_mouse_pos), DEFAULT_ACCURACY).t.clamp(0., 1.);
|
||||
self.t = t;
|
||||
|
||||
let bezier_point = self.bezier.evaluate(TValue::Parametric(t));
|
||||
let bezier_point = point_to_dvec2(self.bezier.eval(t));
|
||||
let bezier_point = transform.transform_point2(bezier_point);
|
||||
self.bezier_point_to_viewport = bezier_point;
|
||||
}
|
||||
|
@ -249,22 +252,24 @@ impl ClosestSegment {
|
|||
let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
|
||||
|
||||
// Split the Bezier at the parameter `t`
|
||||
let [first, second] = self.bezier.split(TValue::Parametric(self.t));
|
||||
let first = self.bezier.subsegment(0_f64..self.t);
|
||||
let second = self.bezier.subsegment(self.t..1.);
|
||||
|
||||
// Transform the handle positions to viewport space
|
||||
let first_handle = first.handle_end().map(|handle| transform.transform_point2(handle));
|
||||
let second_handle = second.handle_start().map(|handle| transform.transform_point2(handle));
|
||||
let first_handle = pathseg_points(first).p2.map(|handle| transform.transform_point2(handle));
|
||||
let second_handle = pathseg_points(second).p1.map(|handle| transform.transform_point2(handle));
|
||||
|
||||
(first_handle, second_handle)
|
||||
}
|
||||
|
||||
pub fn adjusted_insert(&self, responses: &mut VecDeque<Message>) -> (PointId, [SegmentId; 2]) {
|
||||
let layer = self.layer;
|
||||
let [first, second] = self.bezier.split(TValue::Parametric(self.t));
|
||||
let first = pathseg_points(self.bezier.subsegment(0_f64..self.t));
|
||||
let second = pathseg_points(self.bezier.subsegment(self.t..1.));
|
||||
|
||||
// Point
|
||||
let midpoint = PointId::generate();
|
||||
let modification_type = VectorModificationType::InsertPoint { id: midpoint, position: first.end };
|
||||
let modification_type = VectorModificationType::InsertPoint { id: midpoint, position: first.p3 };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
// First segment
|
||||
|
@ -272,7 +277,7 @@ impl ClosestSegment {
|
|||
let modification_type = VectorModificationType::InsertSegment {
|
||||
id: segment_ids[0],
|
||||
points: [self.points[0], midpoint],
|
||||
handles: [first.handle_start().map(|handle| handle - first.start), first.handle_end().map(|handle| handle - first.end)],
|
||||
handles: [first.p1.map(|handle| handle - first.p0), first.p2.map(|handle| handle - first.p3)],
|
||||
};
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
|
@ -280,12 +285,12 @@ impl ClosestSegment {
|
|||
let modification_type = VectorModificationType::InsertSegment {
|
||||
id: segment_ids[1],
|
||||
points: [midpoint, self.points[1]],
|
||||
handles: [second.handle_start().map(|handle| handle - second.start), second.handle_end().map(|handle| handle - second.end)],
|
||||
handles: [second.p1.map(|handle| handle - second.p0), second.p2.map(|handle| handle - second.p3)],
|
||||
};
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
// G1 continuous on new handles
|
||||
if self.bezier.handle_end().is_some() {
|
||||
if pathseg_points(self.bezier).p2.is_some() {
|
||||
let handles = [HandleId::end(segment_ids[0]), HandleId::primary(segment_ids[1])];
|
||||
let modification_type = VectorModificationType::SetG1Continuous { handles, enabled: true };
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
@ -353,8 +358,8 @@ impl ClosestSegment {
|
|||
) -> Option<[Option<HandleId>; 2]> {
|
||||
let transform = document.metadata().transform_to_viewport_if_feeds(self.layer, &document.network_interface);
|
||||
|
||||
let start = self.bezier.start;
|
||||
let end = self.bezier.end;
|
||||
let start = point_to_dvec2(self.bezier.start());
|
||||
let end = point_to_dvec2(self.bezier.end());
|
||||
|
||||
// Apply the drag delta to the segment's handles
|
||||
let b = self.bezier_point_to_viewport;
|
||||
|
@ -1686,9 +1691,9 @@ impl ShapeState {
|
|||
|
||||
let vector = network_interface.compute_modified_vector(layer)?;
|
||||
|
||||
for (segment, mut bezier, start, end) in vector.segment_bezier_iter() {
|
||||
let t = bezier.project(layer_pos);
|
||||
let layerspace = bezier.evaluate(TValue::Parametric(t));
|
||||
for (segment_id, mut segment, start, end) in vector.segment_iter() {
|
||||
let t = segment.nearest(dvec2_to_point(layer_pos), DEFAULT_ACCURACY).t;
|
||||
let layerspace = point_to_dvec2(segment.eval(t));
|
||||
|
||||
let screenspace = transform.transform_point2(layerspace);
|
||||
let distance_squared = screenspace.distance_squared(position);
|
||||
|
@ -1697,20 +1702,22 @@ impl ShapeState {
|
|||
closest_distance_squared = distance_squared;
|
||||
|
||||
// Convert to linear if handes are on top of control points
|
||||
if let bezier_rs::BezierHandles::Cubic { handle_start, handle_end } = bezier.handles {
|
||||
if handle_start.abs_diff_eq(bezier.start(), f64::EPSILON * 100.) && handle_end.abs_diff_eq(bezier.end(), f64::EPSILON * 100.) {
|
||||
bezier = Bezier::from_linear_dvec2(bezier.start, bezier.end);
|
||||
let PathSegPoints { p0: _, p1, p2, p3: _ } = pathseg_points(segment);
|
||||
if let (Some(p1), Some(p2)) = (p1, p2) {
|
||||
let segment_points = pathseg_points(segment);
|
||||
if p1.abs_diff_eq(segment_points.p0, f64::EPSILON * 100.) && p2.abs_diff_eq(segment_points.p3, f64::EPSILON * 100.) {
|
||||
segment = PathSeg::Line(Line::new(segment.start(), segment.end()));
|
||||
}
|
||||
}
|
||||
|
||||
let primary_handle = vector.colinear_manipulators.iter().find(|handles| handles.contains(&HandleId::primary(segment)));
|
||||
let end_handle = vector.colinear_manipulators.iter().find(|handles| handles.contains(&HandleId::end(segment)));
|
||||
let primary_handle = primary_handle.and_then(|&handles| handles.into_iter().find(|handle| handle.segment != segment));
|
||||
let end_handle = end_handle.and_then(|&handles| handles.into_iter().find(|handle| handle.segment != segment));
|
||||
let primary_handle = vector.colinear_manipulators.iter().find(|handles| handles.contains(&HandleId::primary(segment_id)));
|
||||
let end_handle = vector.colinear_manipulators.iter().find(|handles| handles.contains(&HandleId::end(segment_id)));
|
||||
let primary_handle = primary_handle.and_then(|&handles| handles.into_iter().find(|handle| handle.segment != segment_id));
|
||||
let end_handle = end_handle.and_then(|&handles| handles.into_iter().find(|handle| handle.segment != segment_id));
|
||||
|
||||
closest = Some(ClosestSegment {
|
||||
segment,
|
||||
bezier,
|
||||
segment: segment_id,
|
||||
bezier: segment,
|
||||
points: [start, end],
|
||||
colinear: [primary_handle, end_handle],
|
||||
t,
|
||||
|
@ -2076,21 +2083,24 @@ impl ShapeState {
|
|||
};
|
||||
|
||||
// Selection segments
|
||||
for (id, bezier, _, _) in vector.segment_bezier_iter() {
|
||||
for (id, segment, _, _) in vector.segment_iter() {
|
||||
if select_segments {
|
||||
// Select segments if they lie inside the bounding box or lasso polygon
|
||||
let segment_bbox = calculate_bezier_bbox(bezier);
|
||||
let bottom_left = transform.transform_point2(segment_bbox[0]);
|
||||
let top_right = transform.transform_point2(segment_bbox[1]);
|
||||
let transformed_segment = Affine::new(transform.to_cols_array()) * segment;
|
||||
let segment_bbox = transformed_segment.bounding_box();
|
||||
|
||||
let select = match selection_shape {
|
||||
SelectionShape::Box(quad) => {
|
||||
let enclosed = quad[0].min(quad[1]).cmple(bottom_left).all() && quad[0].max(quad[1]).cmpge(top_right).all();
|
||||
SelectionShape::Box(rect) => {
|
||||
let enclosed = segment_bbox.contains_rect(rect);
|
||||
match selection_mode {
|
||||
SelectionMode::Enclosed => enclosed,
|
||||
_ => {
|
||||
// Check for intersection with the segment
|
||||
enclosed || is_intersecting(bezier, quad, transform)
|
||||
enclosed
|
||||
|| rect
|
||||
.path_segments(DEFAULT_ACCURACY)
|
||||
.map(|seg| seg.as_line().unwrap())
|
||||
.any(|line| !transformed_segment.intersect_line(line).is_empty())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2098,7 +2108,7 @@ impl ShapeState {
|
|||
let polygon = polygon_subpath.as_ref().expect("If `selection_shape` is a polygon then subpath is constructed beforehand.");
|
||||
|
||||
// Sample 10 points on the bezier and check if all or some lie inside the polygon
|
||||
let points = bezier.compute_lookup_table(Some(10), None);
|
||||
let points = pathseg_compute_lookup_table(segment, Some(10), false);
|
||||
match selection_mode {
|
||||
SelectionMode::Enclosed => points.map(|p| transform.transform_point2(p)).all(|p| polygon.contains_point(p)),
|
||||
_ => points.map(|p| transform.transform_point2(p)).any(|p| polygon.contains_point(p)),
|
||||
|
@ -2111,13 +2121,15 @@ impl ShapeState {
|
|||
}
|
||||
}
|
||||
|
||||
let segment_points = pathseg_points(segment);
|
||||
|
||||
// Selecting handles
|
||||
for (position, id) in [(bezier.handle_start(), ManipulatorPointId::PrimaryHandle(id)), (bezier.handle_end(), ManipulatorPointId::EndHandle(id))] {
|
||||
for (position, id) in [(segment_points.p1, ManipulatorPointId::PrimaryHandle(id)), (segment_points.p2, ManipulatorPointId::EndHandle(id))] {
|
||||
let Some(position) = position else { continue };
|
||||
let transformed_position = transform.transform_point2(position);
|
||||
|
||||
let select = match selection_shape {
|
||||
SelectionShape::Box(quad) => quad[0].min(quad[1]).cmple(transformed_position).all() && quad[0].max(quad[1]).cmpge(transformed_position).all(),
|
||||
SelectionShape::Box(rect) => rect.contains(dvec2_to_point(transformed_position)),
|
||||
SelectionShape::Lasso(_) => polygon_subpath
|
||||
.as_ref()
|
||||
.expect("If `selection_shape` is a polygon then subpath is constructed beforehand.")
|
||||
|
@ -2139,7 +2151,7 @@ impl ShapeState {
|
|||
let transformed_position = transform.transform_point2(position);
|
||||
|
||||
let select = match selection_shape {
|
||||
SelectionShape::Box(quad) => quad[0].min(quad[1]).cmple(transformed_position).all() && quad[0].max(quad[1]).cmpge(transformed_position).all(),
|
||||
SelectionShape::Box(rect) => rect.contains(dvec2_to_point(transformed_position)),
|
||||
SelectionShape::Lasso(_) => polygon_subpath
|
||||
.as_ref()
|
||||
.expect("If `selection_shape` is a polygon then subpath is constructed beforehand.")
|
||||
|
|
|
@ -182,7 +182,9 @@ impl Polygon {
|
|||
|
||||
let new_dimension = if increase { n + 1 } else { (n - 1).max(3) };
|
||||
|
||||
responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(new_dimension)));
|
||||
responses.add(ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::Vertices(new_dimension),
|
||||
});
|
||||
|
||||
responses.add(NodeGraphMessage::SetInput {
|
||||
input_connector: InputConnector::node(node_id, 1),
|
||||
|
|
|
@ -12,10 +12,10 @@ use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
|||
use crate::messages::tool::common_functionality::transformation_cage::BoundingBoxManager;
|
||||
use crate::messages::tool::tool_messages::tool_prelude::Key;
|
||||
use crate::messages::tool::utility_types::*;
|
||||
use bezier_rs::Subpath;
|
||||
use glam::{DAffine2, DMat2, DVec2};
|
||||
use graph_craft::document::NodeInput;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::subpath::{self, Subpath};
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::misc::{
|
||||
ArcType, {GridType, dvec2_to_point},
|
||||
|
@ -368,9 +368,9 @@ pub fn arc_outline(layer: Option<LayerNodeIdentifier>, document: &DocumentMessag
|
|||
start_angle / 360. * std::f64::consts::TAU,
|
||||
sweep_angle / 360. * std::f64::consts::TAU,
|
||||
match arc_type {
|
||||
ArcType::Open => bezier_rs::ArcType::Open,
|
||||
ArcType::Closed => bezier_rs::ArcType::Closed,
|
||||
ArcType::PieSlice => bezier_rs::ArcType::PieSlice,
|
||||
ArcType::Open => subpath::ArcType::Open,
|
||||
ArcType::Closed => subpath::ArcType::Closed,
|
||||
ArcType::PieSlice => subpath::ArcType::PieSlice,
|
||||
},
|
||||
))];
|
||||
let viewport = document.metadata().transform_to_viewport(layer);
|
||||
|
|
|
@ -10,14 +10,16 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
|
|||
use crate::messages::portfolio::document::utility_types::misc::{GridSnapTarget, PathSnapTarget, SnapTarget};
|
||||
use crate::messages::prelude::*;
|
||||
pub use alignment_snapper::*;
|
||||
use bezier_rs::TValue;
|
||||
pub use distribution_snapper::*;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::renderer::Rect;
|
||||
use graphene_std::vector::NoHashBuilder;
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::algorithms::intersection::filtered_segment_intersections;
|
||||
use graphene_std::vector::misc::point_to_dvec2;
|
||||
pub use grid_snapper::*;
|
||||
use kurbo::ParamCurve;
|
||||
pub use layer_snapper::*;
|
||||
pub use snap_results::*;
|
||||
use std::cmp::Ordering;
|
||||
|
@ -81,6 +83,7 @@ impl SnapConstraint {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn snap_tolerance(document: &DocumentMessageHandler) -> f64 {
|
||||
document.snapping_state.tolerance / document.document_ptz.zoom()
|
||||
}
|
||||
|
@ -127,13 +130,16 @@ fn get_closest_point(points: Vec<SnappedPoint>) -> Option<SnappedPoint> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_closest_curve(curves: &[SnappedCurve], exclude_paths: bool) -> Option<&SnappedPoint> {
|
||||
let keep_curve = |curve: &&SnappedCurve| !exclude_paths || curve.point.target != SnapTarget::Path(PathSnapTarget::AlongPath);
|
||||
curves.iter().filter(keep_curve).map(|curve| &curve.point).min_by(compare_points)
|
||||
}
|
||||
|
||||
fn get_closest_line(lines: &[SnappedLine]) -> Option<&SnappedPoint> {
|
||||
lines.iter().map(|curve| &curve.point).min_by(compare_points)
|
||||
}
|
||||
|
||||
fn get_closest_intersection(snap_to: DVec2, curves: &[SnappedCurve]) -> Option<SnappedPoint> {
|
||||
let mut best = None;
|
||||
for curve_i in curves {
|
||||
|
@ -141,8 +147,8 @@ fn get_closest_intersection(snap_to: DVec2, curves: &[SnappedCurve]) -> Option<S
|
|||
if curve_i.start == curve_j.start && curve_i.layer == curve_j.layer {
|
||||
continue;
|
||||
}
|
||||
for curve_i_t in curve_i.document_curve.intersections(&curve_j.document_curve, None, None) {
|
||||
let snapped_point_document = curve_i.document_curve.evaluate(TValue::Parametric(curve_i_t));
|
||||
for curve_i_t in filtered_segment_intersections(curve_i.document_curve, curve_j.document_curve, None, None) {
|
||||
let snapped_point_document = point_to_dvec2(curve_i.document_curve.eval(curve_i_t));
|
||||
let distance = snap_to.distance(snapped_point_document);
|
||||
let i_closer = curve_i.point.distance < curve_j.point.distance;
|
||||
let close = if i_closer { curve_i } else { curve_j };
|
||||
|
@ -165,6 +171,7 @@ fn get_closest_intersection(snap_to: DVec2, curves: &[SnappedCurve]) -> Option<S
|
|||
}
|
||||
best
|
||||
}
|
||||
|
||||
fn get_grid_intersection(snap_to: DVec2, lines: &[SnappedLine]) -> Option<SnappedPoint> {
|
||||
let mut best = None;
|
||||
for line_i in lines {
|
||||
|
@ -237,6 +244,7 @@ impl<'a> SnapData<'a> {
|
|||
self.node_snap_cache.is_some_and(|cache| !cache.manipulators.is_empty())
|
||||
}
|
||||
}
|
||||
|
||||
impl SnapManager {
|
||||
pub fn update_indicator(&mut self, snapped_point: SnappedPoint) {
|
||||
self.indicator = snapped_point.is_snapped().then_some(snapped_point);
|
||||
|
|
|
@ -3,11 +3,17 @@ use crate::consts::HIDE_HANDLE_DISTANCE;
|
|||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::misc::*;
|
||||
use crate::messages::prelude::*;
|
||||
use bezier_rs::{Bezier, Identifier, Subpath, TValue};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graphene_std::math::math_ext::QuadExt;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::subpath::pathseg_points;
|
||||
use graphene_std::subpath::{Identifier, ManipulatorGroup, Subpath};
|
||||
use graphene_std::vector::PointId;
|
||||
use graphene_std::vector::algorithms::bezpath_algorithms::{pathseg_normals_to_point, pathseg_tangents_to_point};
|
||||
use graphene_std::vector::algorithms::intersection::filtered_segment_intersections;
|
||||
use graphene_std::vector::misc::dvec2_to_point;
|
||||
use graphene_std::vector::misc::point_to_dvec2;
|
||||
use kurbo::{Affine, DEFAULT_ACCURACY, Nearest, ParamCurve, ParamCurveNearest, PathSeg};
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct LayerSnapper {
|
||||
|
@ -37,7 +43,7 @@ impl LayerSnapper {
|
|||
return;
|
||||
}
|
||||
|
||||
for document_curve in bounds.bezier_lines() {
|
||||
for document_curve in bounds.to_lines() {
|
||||
self.paths_to_snap.push(SnapCandidatePath {
|
||||
document_curve,
|
||||
layer,
|
||||
|
@ -70,7 +76,7 @@ impl LayerSnapper {
|
|||
if document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::IntersectionPoint)) || document.snapping_state.target_enabled(SnapTarget::Path(PathSnapTarget::AlongPath)) {
|
||||
for subpath in document.metadata().layer_outline(layer) {
|
||||
for (start_index, curve) in subpath.iter().enumerate() {
|
||||
let document_curve = curve.apply_transformation(|p| transform.transform_point2(p));
|
||||
let document_curve = Affine::new(transform.to_cols_array()) * curve;
|
||||
let start = subpath.manipulator_groups()[start_index].id;
|
||||
if snap_data.ignore_manipulator(layer, start) || snap_data.ignore_manipulator(layer, subpath.manipulator_groups()[(start_index + 1) % subpath.len()].id) {
|
||||
continue;
|
||||
|
@ -98,13 +104,12 @@ impl LayerSnapper {
|
|||
|
||||
for path in &self.paths_to_snap {
|
||||
// Skip very short paths
|
||||
if path.document_curve.start.distance_squared(path.document_curve.end) < tolerance * tolerance * 2. {
|
||||
if path.document_curve.start().distance_squared(path.document_curve.end()) < tolerance * tolerance * 2. {
|
||||
continue;
|
||||
}
|
||||
let time = path.document_curve.project(point.document_point);
|
||||
let snapped_point_document = path.document_curve.evaluate(bezier_rs::TValue::Parametric(time));
|
||||
|
||||
let distance = snapped_point_document.distance(point.document_point);
|
||||
let Nearest { distance_sq, t } = path.document_curve.nearest(dvec2_to_point(point.document_point), DEFAULT_ACCURACY);
|
||||
let snapped_point_document = point_to_dvec2(path.document_curve.eval(t));
|
||||
let distance = distance_sq.sqrt();
|
||||
|
||||
if distance < tolerance {
|
||||
snap_results.curves.push(SnappedCurve {
|
||||
|
@ -144,8 +149,8 @@ impl LayerSnapper {
|
|||
|
||||
for path in &self.paths_to_snap {
|
||||
for constraint_path in constraint_path.iter() {
|
||||
for time in path.document_curve.intersections(&constraint_path, None, None) {
|
||||
let snapped_point_document = path.document_curve.evaluate(bezier_rs::TValue::Parametric(time));
|
||||
for time in filtered_segment_intersections(path.document_curve, constraint_path, None, None) {
|
||||
let snapped_point_document = point_to_dvec2(path.document_curve.eval(time));
|
||||
|
||||
let distance = snapped_point_document.distance(point.document_point);
|
||||
|
||||
|
@ -266,8 +271,8 @@ impl LayerSnapper {
|
|||
fn normals_and_tangents(path: &SnapCandidatePath, normals: bool, tangents: bool, point: &SnapCandidatePoint, tolerance: f64, snap_results: &mut SnapResults) {
|
||||
if normals && path.bounds.is_none() {
|
||||
for &neighbor in &point.neighbors {
|
||||
for t in path.document_curve.normals_to_point(neighbor) {
|
||||
let normal_point = path.document_curve.evaluate(TValue::Parametric(t));
|
||||
for t in pathseg_normals_to_point(path.document_curve, dvec2_to_point(neighbor)) {
|
||||
let normal_point = point_to_dvec2(path.document_curve.eval(t));
|
||||
let distance = normal_point.distance(point.document_point);
|
||||
if distance > tolerance {
|
||||
continue;
|
||||
|
@ -287,8 +292,8 @@ fn normals_and_tangents(path: &SnapCandidatePath, normals: bool, tangents: bool,
|
|||
}
|
||||
if tangents && path.bounds.is_none() {
|
||||
for &neighbor in &point.neighbors {
|
||||
for t in path.document_curve.tangents_to_point(neighbor) {
|
||||
let tangent_point = path.document_curve.evaluate(TValue::Parametric(t));
|
||||
for t in pathseg_tangents_to_point(path.document_curve, dvec2_to_point(neighbor)) {
|
||||
let tangent_point = point_to_dvec2(path.document_curve.eval(t));
|
||||
let distance = tangent_point.distance(point.document_point);
|
||||
if distance > tolerance {
|
||||
continue;
|
||||
|
@ -310,7 +315,7 @@ fn normals_and_tangents(path: &SnapCandidatePath, normals: bool, tangents: bool,
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
struct SnapCandidatePath {
|
||||
document_curve: Bezier,
|
||||
document_curve: PathSeg,
|
||||
layer: LayerNodeIdentifier,
|
||||
start: PointId,
|
||||
target: SnapTarget,
|
||||
|
@ -440,12 +445,13 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<Poin
|
|||
if points.len() >= crate::consts::MAX_LAYER_SNAP_POINTS {
|
||||
return;
|
||||
}
|
||||
let curve = pathseg_points(curve);
|
||||
|
||||
let in_handle = curve.handle_start().map(|handle| handle - curve.start).filter(handle_not_under(to_document));
|
||||
let out_handle = curve.handle_end().map(|handle| handle - curve.end).filter(handle_not_under(to_document));
|
||||
let in_handle = curve.p1.map(|handle| handle - curve.p0).filter(handle_not_under(to_document));
|
||||
let out_handle = curve.p2.map(|handle| handle - curve.p3).filter(handle_not_under(to_document));
|
||||
if in_handle.is_none() && out_handle.is_none() {
|
||||
points.push(SnapCandidatePoint::new(
|
||||
to_document.transform_point2(curve.start() * 0.5 + curve.end * 0.5),
|
||||
to_document.transform_point2(curve.p0 * 0.5 + curve.p3 * 0.5),
|
||||
SnapSource::Path(PathSnapSource::LineMidpoint),
|
||||
SnapTarget::Path(PathSnapTarget::LineMidpoint),
|
||||
Some(layer),
|
||||
|
@ -487,7 +493,7 @@ fn subpath_anchor_snap_points(layer: LayerNodeIdentifier, subpath: &Subpath<Poin
|
|||
}
|
||||
}
|
||||
|
||||
pub fn are_manipulator_handles_colinear(manipulators: &bezier_rs::ManipulatorGroup<PointId>, to_document: DAffine2, subpath: &Subpath<PointId>, index: usize) -> bool {
|
||||
pub fn are_manipulator_handles_colinear(manipulators: &ManipulatorGroup<PointId>, to_document: DAffine2, subpath: &Subpath<PointId>, index: usize) -> bool {
|
||||
let anchor = manipulators.anchor;
|
||||
let handle_in = manipulators.in_handle.map(|handle| anchor - handle).filter(handle_not_under(to_document));
|
||||
let handle_out = manipulators.out_handle.map(|handle| handle - anchor).filter(handle_not_under(to_document));
|
||||
|
|
|
@ -2,11 +2,11 @@ use super::DistributionMatch;
|
|||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||
use crate::messages::portfolio::document::utility_types::misc::{DistributionSnapTarget, SnapSource, SnapTarget};
|
||||
use crate::messages::tool::common_functionality::snapping::SnapCandidatePoint;
|
||||
use bezier_rs::Bezier;
|
||||
use glam::DVec2;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::renderer::Rect;
|
||||
use graphene_std::vector::PointId;
|
||||
use kurbo::PathSeg;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
|
@ -120,5 +120,5 @@ pub struct SnappedCurve {
|
|||
pub layer: LayerNodeIdentifier,
|
||||
pub start: PointId,
|
||||
pub point: SnappedPoint,
|
||||
pub document_curve: Bezier,
|
||||
pub document_curve: PathSeg,
|
||||
}
|
||||
|
|
|
@ -9,16 +9,17 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{Node
|
|||
use crate::messages::tool::common_functionality::transformation_cage::SelectedEdges;
|
||||
use crate::messages::tool::tool_messages::path_tool::PathOverlayMode;
|
||||
use crate::messages::tool::utility_types::ToolType;
|
||||
use bezier_rs::{Bezier, BezierHandles};
|
||||
use glam::{DAffine2, DVec2};
|
||||
use graph_craft::concrete;
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::subpath::{Bezier, BezierHandles};
|
||||
use graphene_std::table::Table;
|
||||
use graphene_std::text::{FontCache, load_font};
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
|
||||
use graphene_std::vector::algorithms::bezpath_algorithms::pathseg_compute_lookup_table;
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point};
|
||||
use graphene_std::vector::{HandleExt, PointId, SegmentId, Vector, VectorModification, VectorModificationType};
|
||||
use kurbo::{CubicBez, Line, ParamCurveExtrema, PathSeg, Point, QuadBez};
|
||||
use kurbo::{CubicBez, DEFAULT_ACCURACY, Line, ParamCurve, PathSeg, Point, QuadBez, Shape};
|
||||
|
||||
/// Determines if a path should be extended. Goal in viewport space. Returns the path and if it is extending from the start, if applicable.
|
||||
pub fn should_extend(
|
||||
|
@ -208,25 +209,6 @@ pub fn is_visible_point(
|
|||
}
|
||||
}
|
||||
|
||||
/// Function to find the bounding box of bezier (uses method from kurbo)
|
||||
pub fn calculate_bezier_bbox(bezier: Bezier) -> [DVec2; 2] {
|
||||
let start = Point::new(bezier.start.x, bezier.start.y);
|
||||
let end = Point::new(bezier.end.x, bezier.end.y);
|
||||
let bbox = match bezier.handles {
|
||||
BezierHandles::Cubic { handle_start, handle_end } => {
|
||||
let p1 = Point::new(handle_start.x, handle_start.y);
|
||||
let p2 = Point::new(handle_end.x, handle_end.y);
|
||||
CubicBez::new(start, p1, p2, end).bounding_box()
|
||||
}
|
||||
BezierHandles::Quadratic { handle } => {
|
||||
let p1 = Point::new(handle.x, handle.y);
|
||||
QuadBez::new(start, p1, end).bounding_box()
|
||||
}
|
||||
BezierHandles::Linear => Line::new(start, end).bounding_box(),
|
||||
};
|
||||
[DVec2::new(bbox.x0, bbox.y0), DVec2::new(bbox.x1, bbox.y1)]
|
||||
}
|
||||
|
||||
pub fn is_intersecting(bezier: Bezier, quad: [DVec2; 2], transform: DAffine2) -> bool {
|
||||
let to_layerspace = transform.inverse();
|
||||
let quad = [to_layerspace.transform_point2(quad[0]), to_layerspace.transform_point2(quad[1])];
|
||||
|
@ -496,19 +478,19 @@ pub fn log_optimization(a: f64, b: f64, p1: DVec2, p3: DVec2, d1: DVec2, d2: DVe
|
|||
let c1 = p1 + d1 * start_handle_length;
|
||||
let c2 = p3 + d2 * end_handle_length;
|
||||
|
||||
let new_curve = Bezier::from_cubic_coordinates(p1.x, p1.y, c1.x, c1.y, c2.x, c2.y, p3.x, p3.y);
|
||||
let new_curve = PathSeg::Cubic(CubicBez::new(Point::new(p1.x, p1.y), Point::new(c1.x, c1.y), Point::new(c2.x, c2.y), Point::new(p3.x, p3.y)));
|
||||
|
||||
// Sample 2*n points from new curve and get the L2 metric between all of points
|
||||
let points = new_curve.compute_lookup_table(Some(2 * n), None).collect::<Vec<_>>();
|
||||
let points = pathseg_compute_lookup_table(new_curve, Some(2 * n), false);
|
||||
|
||||
let dist = points1.iter().zip(points.iter()).map(|(p1, p2)| (p1.x - p2.x).powi(2) + (p1.y - p2.y).powi(2)).sum::<f64>();
|
||||
let dist = points1.iter().zip(points).map(|(p1, p2)| (p1.x - p2.x).powi(2) + (p1.y - p2.y).powi(2)).sum::<f64>();
|
||||
|
||||
dist / (2 * n) as f64
|
||||
}
|
||||
|
||||
/// Calculates optimal handle lengths with adam optimization.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn find_two_param_best_approximate(p1: DVec2, p3: DVec2, d1: DVec2, d2: DVec2, min_len1: f64, min_len2: f64, farther_segment: Bezier, other_segment: Bezier) -> (DVec2, DVec2) {
|
||||
pub fn find_two_param_best_approximate(p1: DVec2, p3: DVec2, d1: DVec2, d2: DVec2, min_len1: f64, min_len2: f64, further_segment: PathSeg, other_segment: PathSeg) -> (DVec2, DVec2) {
|
||||
let h = 1e-6;
|
||||
let tol = 1e-6;
|
||||
let max_iter = 200;
|
||||
|
@ -530,21 +512,25 @@ pub fn find_two_param_best_approximate(p1: DVec2, p3: DVec2, d1: DVec2, d2: DVec
|
|||
|
||||
let n = 20;
|
||||
|
||||
let farther_segment = if farther_segment.start.distance(p1) >= f64::EPSILON {
|
||||
farther_segment.reverse()
|
||||
let further_segment = if further_segment.start().distance(dvec2_to_point(p1)) >= f64::EPSILON {
|
||||
further_segment.reverse()
|
||||
} else {
|
||||
farther_segment
|
||||
further_segment
|
||||
};
|
||||
|
||||
let other_segment = if other_segment.end.distance(p3) >= f64::EPSILON { other_segment.reverse() } else { other_segment };
|
||||
let other_segment = if other_segment.end().distance(dvec2_to_point(p3)) >= f64::EPSILON {
|
||||
other_segment.reverse()
|
||||
} else {
|
||||
other_segment
|
||||
};
|
||||
|
||||
// Now we sample points proportional to the lengths of the beziers
|
||||
let l1 = farther_segment.length(None);
|
||||
let l2 = other_segment.length(None);
|
||||
let l1 = further_segment.perimeter(DEFAULT_ACCURACY);
|
||||
let l2 = other_segment.perimeter(DEFAULT_ACCURACY);
|
||||
let ratio = l1 / (l1 + l2);
|
||||
let n_points1 = ((2 * n) as f64 * ratio).floor() as usize;
|
||||
let mut points1 = farther_segment.compute_lookup_table(Some(n_points1), None).collect::<Vec<_>>();
|
||||
let mut points2 = other_segment.compute_lookup_table(Some(n), None).collect::<Vec<_>>();
|
||||
let mut points1 = pathseg_compute_lookup_table(further_segment, Some(n_points1), false).collect::<Vec<_>>();
|
||||
let mut points2 = pathseg_compute_lookup_table(other_segment, Some(n), false).collect::<Vec<_>>();
|
||||
points1.append(&mut points2);
|
||||
|
||||
let f = |a: f64, b: f64| -> f64 { log_optimization(a, b, p1, p3, d1, d2, &points1, n) };
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::messages::tool::utility_types::ToolType;
|
|||
use crate::node_graph_executor::NodeGraphExecutor;
|
||||
use graphene_std::raster::color::Color;
|
||||
|
||||
const ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |overlay_context| DocumentMessage::DrawArtboardOverlays(overlay_context).into();
|
||||
const ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |context| DocumentMessage::DrawArtboardOverlays { context }.into();
|
||||
|
||||
#[derive(ExtractField)]
|
||||
pub struct ToolMessageContext<'a> {
|
||||
|
@ -75,8 +75,8 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
|
|||
self.tool_state.tool_data.active_tool_type = ToolType::Shape;
|
||||
}
|
||||
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape });
|
||||
responses.add(ShapeToolMessage::SetShape(ShapeType::Polygon));
|
||||
responses.add(ShapeToolMessage::HideShapeTypeWidget(false))
|
||||
responses.add(ShapeToolMessage::SetShape { shape: ShapeType::Polygon });
|
||||
responses.add(ShapeToolMessage::HideShapeTypeWidget { hide: false })
|
||||
}
|
||||
ToolMessage::ActivateToolBrush => responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Brush }),
|
||||
ToolMessage::ActivateToolShapeLine | ToolMessage::ActivateToolShapeRectangle | ToolMessage::ActivateToolShapeEllipse => {
|
||||
|
@ -89,8 +89,8 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
|
|||
|
||||
self.tool_state.tool_data.active_shape_type = Some(shape.tool_type());
|
||||
responses.add_front(ToolMessage::ActivateTool { tool_type: ToolType::Shape });
|
||||
responses.add(ShapeToolMessage::HideShapeTypeWidget(true));
|
||||
responses.add(ShapeToolMessage::SetShape(shape));
|
||||
responses.add(ShapeToolMessage::HideShapeTypeWidget { hide: true });
|
||||
responses.add(ShapeToolMessage::SetShape { shape });
|
||||
}
|
||||
ToolMessage::ActivateTool { tool_type } => {
|
||||
let tool_data = &mut self.tool_state.tool_data;
|
||||
|
@ -156,11 +156,14 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
|
|||
// Subscribe new tool
|
||||
tool_data.tools.get(&tool_type).unwrap().activate(responses);
|
||||
|
||||
// Re-add the artboard overlay provider when tools are reactivated
|
||||
responses.add(OverlaysMessage::AddProvider { provider: ARTBOARD_OVERLAY_PROVIDER });
|
||||
|
||||
// Send the SelectionChanged message to the active tool, this will ensure the selection is updated
|
||||
responses.add(BroadcastEvent::SelectionChanged);
|
||||
responses.add(EventMessage::SelectionChanged);
|
||||
|
||||
// Update the working colors for the active tool
|
||||
responses.add(BroadcastEvent::WorkingColorChanged);
|
||||
responses.add(EventMessage::WorkingColorChanged);
|
||||
|
||||
// Send tool options to the frontend
|
||||
responses.add(ToolMessage::RefreshToolOptions);
|
||||
|
@ -173,11 +176,12 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
|
|||
tool_data.tools.get(&tool_data.active_tool_type).unwrap().deactivate(responses);
|
||||
|
||||
// Unsubscribe the transform layer to selection change events
|
||||
let message = Box::new(TransformLayerMessage::SelectionChanged.into());
|
||||
let on = BroadcastEvent::SelectionChanged;
|
||||
responses.add(BroadcastMessage::UnsubscribeEvent { message, on });
|
||||
responses.add(BroadcastMessage::UnsubscribeEvent {
|
||||
on: EventMessage::SelectionChanged,
|
||||
send: Box::new(TransformLayerMessage::SelectionChanged.into()),
|
||||
});
|
||||
|
||||
responses.add(OverlaysMessage::RemoveProvider(ARTBOARD_OVERLAY_PROVIDER));
|
||||
responses.add(OverlaysMessage::RemoveProvider { provider: ARTBOARD_OVERLAY_PROVIDER });
|
||||
|
||||
responses.add(FrontendMessage::UpdateInputHints { hint_data: Default::default() });
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor: Default::default() });
|
||||
|
@ -187,12 +191,12 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
|
|||
ToolMessage::InitTools => {
|
||||
// Subscribe the transform layer to selection change events
|
||||
responses.add(BroadcastMessage::SubscribeEvent {
|
||||
on: BroadcastEvent::SelectionChanged,
|
||||
on: EventMessage::SelectionChanged,
|
||||
send: Box::new(TransformLayerMessage::SelectionChanged.into()),
|
||||
});
|
||||
|
||||
responses.add(BroadcastMessage::SubscribeEvent {
|
||||
on: BroadcastEvent::SelectionChanged,
|
||||
on: EventMessage::SelectionChanged,
|
||||
send: Box::new(SelectToolMessage::SyncHistory.into()),
|
||||
});
|
||||
|
||||
|
@ -229,12 +233,12 @@ impl MessageHandler<ToolMessage, ToolMessageContext<'_>> for ToolMessageHandler
|
|||
tool_data.active_tool_mut().process_message(ToolMessage::UpdateHints, responses, &mut data);
|
||||
tool_data.active_tool_mut().process_message(ToolMessage::UpdateCursor, responses, &mut data);
|
||||
|
||||
responses.add(OverlaysMessage::AddProvider(ARTBOARD_OVERLAY_PROVIDER));
|
||||
responses.add(OverlaysMessage::AddProvider { provider: ARTBOARD_OVERLAY_PROVIDER });
|
||||
}
|
||||
ToolMessage::PreUndo => {
|
||||
let tool_data = &mut self.tool_state.tool_data;
|
||||
if tool_data.active_tool_type != ToolType::Pen {
|
||||
responses.add(BroadcastEvent::ToolAbort);
|
||||
responses.add(EventMessage::ToolAbort);
|
||||
}
|
||||
}
|
||||
ToolMessage::Redo => {
|
||||
|
|
|
@ -26,7 +26,7 @@ pub struct ArtboardTool {
|
|||
pub enum ArtboardToolMessage {
|
||||
// Standard messages
|
||||
Abort,
|
||||
Overlays(OverlayContext),
|
||||
Overlays { context: OverlayContext },
|
||||
|
||||
// Tool-specific messages
|
||||
UpdateSelectedArtboard,
|
||||
|
@ -83,7 +83,7 @@ impl ToolTransition for ArtboardTool {
|
|||
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||
EventToMessageMap {
|
||||
tool_abort: Some(ArtboardToolMessage::Abort.into()),
|
||||
overlay_provider: Some(|overlay_context| ArtboardToolMessage::Overlays(overlay_context).into()),
|
||||
overlay_provider: Some(|context| ArtboardToolMessage::Overlays { context }.into()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -227,7 +227,7 @@ impl Fsm for ArtboardToolFsmState {
|
|||
|
||||
let ToolMessage::Artboard(event) = event else { return self };
|
||||
match (self, event) {
|
||||
(state, ArtboardToolMessage::Overlays(mut overlay_context)) => {
|
||||
(state, ArtboardToolMessage::Overlays { context: mut overlay_context }) => {
|
||||
let display_transform_cage = overlay_context.visibility_settings.transform_cage();
|
||||
if display_transform_cage && state != ArtboardToolFsmState::Drawing {
|
||||
if let Some(bounds) = tool_data.selected_artboard.and_then(|layer| document.metadata().bounding_box_document(layer)) {
|
||||
|
|
|
@ -62,7 +62,7 @@ pub enum BrushToolMessage {
|
|||
DragStart,
|
||||
DragStop,
|
||||
PointerMove,
|
||||
UpdateOptions(BrushToolMessageOptionsUpdate),
|
||||
UpdateOptions { options: BrushToolMessageOptionsUpdate },
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
|
@ -106,7 +106,7 @@ impl LayoutHolder for BrushTool {
|
|||
.min(1.)
|
||||
.max(BRUSH_MAX_SIZE) /* Anything bigger would cause the application to be unresponsive and eventually die */
|
||||
.unit(" px")
|
||||
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Diameter(number_input.value.unwrap())).into())
|
||||
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions { options: BrushToolMessageOptionsUpdate::Diameter(number_input.value.unwrap()) }.into())
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
NumberInput::new(Some(self.options.hardness))
|
||||
|
@ -115,7 +115,12 @@ impl LayoutHolder for BrushTool {
|
|||
.max(100.)
|
||||
.mode_range()
|
||||
.unit("%")
|
||||
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Hardness(number_input.value.unwrap())).into())
|
||||
.on_update(|number_input: &NumberInput| {
|
||||
BrushToolMessage::UpdateOptions {
|
||||
options: BrushToolMessageOptionsUpdate::Hardness(number_input.value.unwrap()),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
NumberInput::new(Some(self.options.flow))
|
||||
|
@ -124,7 +129,12 @@ impl LayoutHolder for BrushTool {
|
|||
.max(100.)
|
||||
.mode_range()
|
||||
.unit("%")
|
||||
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Flow(number_input.value.unwrap())).into())
|
||||
.on_update(|number_input: &NumberInput| {
|
||||
BrushToolMessage::UpdateOptions {
|
||||
options: BrushToolMessageOptionsUpdate::Flow(number_input.value.unwrap()),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
Separator::new(SeparatorType::Related).widget_holder(),
|
||||
NumberInput::new(Some(self.options.spacing))
|
||||
|
@ -133,7 +143,12 @@ impl LayoutHolder for BrushTool {
|
|||
.max(100.)
|
||||
.mode_range()
|
||||
.unit("%")
|
||||
.on_update(|number_input: &NumberInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Spacing(number_input.value.unwrap())).into())
|
||||
.on_update(|number_input: &NumberInput| {
|
||||
BrushToolMessage::UpdateOptions {
|
||||
options: BrushToolMessageOptionsUpdate::Spacing(number_input.value.unwrap()),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder(),
|
||||
];
|
||||
|
||||
|
@ -142,9 +157,12 @@ impl LayoutHolder for BrushTool {
|
|||
let draw_mode_entries: Vec<_> = [DrawMode::Draw, DrawMode::Erase, DrawMode::Restore]
|
||||
.into_iter()
|
||||
.map(|draw_mode| {
|
||||
RadioEntryData::new(format!("{draw_mode:?}"))
|
||||
.label(format!("{draw_mode:?}"))
|
||||
.on_update(move |_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::DrawMode(draw_mode)).into())
|
||||
RadioEntryData::new(format!("{draw_mode:?}")).label(format!("{draw_mode:?}")).on_update(move |_| {
|
||||
BrushToolMessage::UpdateOptions {
|
||||
options: BrushToolMessageOptionsUpdate::DrawMode(draw_mode),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
widgets.push(RadioInput::new(draw_mode_entries).selected_index(Some(self.options.draw_mode as u32)).widget_holder());
|
||||
|
@ -154,9 +172,26 @@ impl LayoutHolder for BrushTool {
|
|||
widgets.append(&mut self.options.color.create_widgets(
|
||||
"Color",
|
||||
false,
|
||||
|_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Color(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::ColorType(color_type.clone())).into()),
|
||||
|color: &ColorInput| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::Color(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(),
|
||||
|_| {
|
||||
BrushToolMessage::UpdateOptions {
|
||||
options: BrushToolMessageOptionsUpdate::Color(None),
|
||||
}
|
||||
.into()
|
||||
},
|
||||
|color_type: ToolColorType| {
|
||||
WidgetCallback::new(move |_| {
|
||||
BrushToolMessage::UpdateOptions {
|
||||
options: BrushToolMessageOptionsUpdate::ColorType(color_type.clone()),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
},
|
||||
|color: &ColorInput| {
|
||||
BrushToolMessage::UpdateOptions {
|
||||
options: BrushToolMessageOptionsUpdate::Color(color.value.as_solid().map(|color| color.to_linear_srgb())),
|
||||
}
|
||||
.into()
|
||||
},
|
||||
));
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
|
||||
|
@ -167,9 +202,12 @@ impl LayoutHolder for BrushTool {
|
|||
section
|
||||
.iter()
|
||||
.map(|blend_mode| {
|
||||
MenuListEntry::new(format!("{blend_mode:?}"))
|
||||
.label(blend_mode.to_string())
|
||||
.on_commit(|_| BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::BlendMode(*blend_mode)).into())
|
||||
MenuListEntry::new(format!("{blend_mode:?}")).label(blend_mode.to_string()).on_commit(|_| {
|
||||
BrushToolMessage::UpdateOptions {
|
||||
options: BrushToolMessageOptionsUpdate::BlendMode(*blend_mode),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
|
@ -189,11 +227,11 @@ impl LayoutHolder for BrushTool {
|
|||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for BrushTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
|
||||
let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message else {
|
||||
let ToolMessage::Brush(BrushToolMessage::UpdateOptions { options }) = message else {
|
||||
self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, true);
|
||||
return;
|
||||
};
|
||||
match action {
|
||||
match options {
|
||||
BrushToolMessageOptionsUpdate::BlendMode(blend_mode) => self.options.blend_mode = blend_mode,
|
||||
BrushToolMessageOptionsUpdate::ChangeDiameter(change) => {
|
||||
let needs_rounding = ((self.options.diameter + change.abs() / 2.) % change.abs() - change.abs() / 2.).abs() > 0.5;
|
||||
|
@ -408,10 +446,9 @@ impl Fsm for BrushToolFsmState {
|
|||
BrushToolFsmState::Ready
|
||||
}
|
||||
(_, BrushToolMessage::WorkingColorChanged) => {
|
||||
responses.add(BrushToolMessage::UpdateOptions(BrushToolMessageOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
responses.add(BrushToolMessage::UpdateOptions {
|
||||
options: BrushToolMessageOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),
|
||||
});
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
|
|
|
@ -14,7 +14,7 @@ pub enum FillToolMessage {
|
|||
// Standard messages
|
||||
Abort,
|
||||
WorkingColorChanged,
|
||||
Overlays(OverlayContext),
|
||||
Overlays { context: OverlayContext },
|
||||
|
||||
// Tool-specific messages
|
||||
PointerMove,
|
||||
|
@ -67,7 +67,7 @@ impl ToolTransition for FillTool {
|
|||
EventToMessageMap {
|
||||
tool_abort: Some(FillToolMessage::Abort.into()),
|
||||
working_color_changed: Some(FillToolMessage::WorkingColorChanged.into()),
|
||||
overlay_provider: Some(|overlay_context| FillToolMessage::Overlays(overlay_context).into()),
|
||||
overlay_provider: Some(|context| FillToolMessage::Overlays { context }.into()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ impl Fsm for FillToolFsmState {
|
|||
|
||||
let ToolMessage::Fill(event) = event else { return self };
|
||||
match (self, event) {
|
||||
(_, FillToolMessage::Overlays(mut overlay_context)) => {
|
||||
(_, FillToolMessage::Overlays { context: mut overlay_context }) => {
|
||||
// Choose the working color to preview
|
||||
let use_secondary = input.keyboard.get(Key::Shift as usize);
|
||||
let preview_color = if use_secondary { global_tool_data.secondary_color } else { global_tool_data.primary_color };
|
||||
|
|
|
@ -40,7 +40,7 @@ impl Default for FreehandOptions {
|
|||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum FreehandToolMessage {
|
||||
// Standard messages
|
||||
Overlays(OverlayContext),
|
||||
Overlays { context: OverlayContext },
|
||||
Abort,
|
||||
WorkingColorChanged,
|
||||
|
||||
|
@ -48,7 +48,7 @@ pub enum FreehandToolMessage {
|
|||
DragStart { append_to_selected: Key },
|
||||
DragStop,
|
||||
PointerMove,
|
||||
UpdateOptions(FreehandOptionsUpdate),
|
||||
UpdateOptions { options: FreehandOptionsUpdate },
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
|
@ -86,7 +86,12 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder {
|
|||
.label("Weight")
|
||||
.min(1.)
|
||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
.on_update(|number_input: &NumberInput| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::LineWeight(number_input.value.unwrap())).into())
|
||||
.on_update(|number_input: &NumberInput| {
|
||||
FreehandToolMessage::UpdateOptions {
|
||||
options: FreehandOptionsUpdate::LineWeight(number_input.value.unwrap()),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder()
|
||||
}
|
||||
|
||||
|
@ -95,9 +100,26 @@ impl LayoutHolder for FreehandTool {
|
|||
let mut widgets = self.options.fill.create_widgets(
|
||||
"Fill",
|
||||
true,
|
||||
|_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColorType(color_type.clone())).into()),
|
||||
|color: &ColorInput| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(),
|
||||
|_| {
|
||||
FreehandToolMessage::UpdateOptions {
|
||||
options: FreehandOptionsUpdate::FillColor(None),
|
||||
}
|
||||
.into()
|
||||
},
|
||||
|color_type: ToolColorType| {
|
||||
WidgetCallback::new(move |_| {
|
||||
FreehandToolMessage::UpdateOptions {
|
||||
options: FreehandOptionsUpdate::FillColorType(color_type.clone()),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
},
|
||||
|color: &ColorInput| {
|
||||
FreehandToolMessage::UpdateOptions {
|
||||
options: FreehandOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb())),
|
||||
}
|
||||
.into()
|
||||
},
|
||||
);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
|
@ -105,9 +127,26 @@ impl LayoutHolder for FreehandTool {
|
|||
widgets.append(&mut self.options.stroke.create_widgets(
|
||||
"Stroke",
|
||||
true,
|
||||
|_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColorType(color_type.clone())).into()),
|
||||
|color: &ColorInput| FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(),
|
||||
|_| {
|
||||
FreehandToolMessage::UpdateOptions {
|
||||
options: FreehandOptionsUpdate::StrokeColor(None),
|
||||
}
|
||||
.into()
|
||||
},
|
||||
|color_type: ToolColorType| {
|
||||
WidgetCallback::new(move |_| {
|
||||
FreehandToolMessage::UpdateOptions {
|
||||
options: FreehandOptionsUpdate::StrokeColorType(color_type.clone()),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
},
|
||||
|color: &ColorInput| {
|
||||
FreehandToolMessage::UpdateOptions {
|
||||
options: FreehandOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb())),
|
||||
}
|
||||
.into()
|
||||
},
|
||||
));
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(create_weight_widget(self.options.line_weight));
|
||||
|
@ -119,11 +158,11 @@ impl LayoutHolder for FreehandTool {
|
|||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for FreehandTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
|
||||
let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message else {
|
||||
let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions { options }) = message else {
|
||||
self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, true);
|
||||
return;
|
||||
};
|
||||
match action {
|
||||
match options {
|
||||
FreehandOptionsUpdate::FillColor(color) => {
|
||||
self.options.fill.custom_color = color;
|
||||
self.options.fill.color_type = ToolColorType::Custom;
|
||||
|
@ -164,7 +203,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Free
|
|||
impl ToolTransition for FreehandTool {
|
||||
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||
EventToMessageMap {
|
||||
overlay_provider: Some(|overlay_context: OverlayContext| FreehandToolMessage::Overlays(overlay_context).into()),
|
||||
overlay_provider: Some(|context: OverlayContext| FreehandToolMessage::Overlays { context }.into()),
|
||||
tool_abort: Some(FreehandToolMessage::Abort.into()),
|
||||
working_color_changed: Some(FreehandToolMessage::WorkingColorChanged.into()),
|
||||
..Default::default()
|
||||
|
@ -203,7 +242,7 @@ impl Fsm for FreehandToolFsmState {
|
|||
|
||||
let ToolMessage::Freehand(event) = event else { return self };
|
||||
match (self, event) {
|
||||
(_, FreehandToolMessage::Overlays(mut overlay_context)) => {
|
||||
(_, FreehandToolMessage::Overlays { context: mut overlay_context }) => {
|
||||
path_endpoint_overlays(document, shape_editor, &mut overlay_context, tool_action_data.preferences);
|
||||
|
||||
self
|
||||
|
@ -287,10 +326,9 @@ impl Fsm for FreehandToolFsmState {
|
|||
FreehandToolFsmState::Ready
|
||||
}
|
||||
(_, FreehandToolMessage::WorkingColorChanged) => {
|
||||
responses.add(FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
responses.add(FreehandToolMessage::UpdateOptions {
|
||||
options: FreehandOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),
|
||||
});
|
||||
self
|
||||
}
|
||||
_ => self,
|
||||
|
@ -679,7 +717,9 @@ mod test_freehand {
|
|||
|
||||
let custom_line_weight = 5.;
|
||||
editor
|
||||
.handle_message(ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(FreehandOptionsUpdate::LineWeight(custom_line_weight))))
|
||||
.handle_message(ToolMessage::Freehand(FreehandToolMessage::UpdateOptions {
|
||||
options: FreehandOptionsUpdate::LineWeight(custom_line_weight),
|
||||
}))
|
||||
.await;
|
||||
|
||||
let points = [DVec2::new(100., 100.), DVec2::new(200., 200.), DVec2::new(300., 100.)];
|
||||
|
|
|
@ -24,7 +24,7 @@ pub struct GradientOptions {
|
|||
pub enum GradientToolMessage {
|
||||
// Standard messages
|
||||
Abort,
|
||||
Overlays(OverlayContext),
|
||||
Overlays { context: OverlayContext },
|
||||
|
||||
// Tool-specific messages
|
||||
DeleteStop,
|
||||
|
@ -33,7 +33,7 @@ pub enum GradientToolMessage {
|
|||
PointerMove { constrain_axis: Key },
|
||||
PointerOutsideViewport { constrain_axis: Key },
|
||||
PointerUp,
|
||||
UpdateOptions(GradientOptionsUpdate),
|
||||
UpdateOptions { options: GradientOptionsUpdate },
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
|
@ -56,11 +56,11 @@ impl ToolMetadata for GradientTool {
|
|||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for GradientTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
|
||||
let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message else {
|
||||
let ToolMessage::Gradient(GradientToolMessage::UpdateOptions { options }) = message else {
|
||||
self.fsm_state.process_event(message, &mut self.data, context, &self.options, responses, false);
|
||||
return;
|
||||
};
|
||||
match action {
|
||||
match options {
|
||||
GradientOptionsUpdate::Type(gradient_type) => {
|
||||
self.options.gradient_type = gradient_type;
|
||||
// Update the selected gradient if it exists
|
||||
|
@ -91,14 +91,18 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Grad
|
|||
impl LayoutHolder for GradientTool {
|
||||
fn layout(&self) -> Layout {
|
||||
let gradient_type = RadioInput::new(vec![
|
||||
RadioEntryData::new("Linear")
|
||||
.label("Linear")
|
||||
.tooltip("Linear gradient")
|
||||
.on_update(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Linear)).into()),
|
||||
RadioEntryData::new("Radial")
|
||||
.label("Radial")
|
||||
.tooltip("Radial gradient")
|
||||
.on_update(move |_| GradientToolMessage::UpdateOptions(GradientOptionsUpdate::Type(GradientType::Radial)).into()),
|
||||
RadioEntryData::new("Linear").label("Linear").tooltip("Linear gradient").on_update(move |_| {
|
||||
GradientToolMessage::UpdateOptions {
|
||||
options: GradientOptionsUpdate::Type(GradientType::Linear),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
RadioEntryData::new("Radial").label("Radial").tooltip("Radial gradient").on_update(move |_| {
|
||||
GradientToolMessage::UpdateOptions {
|
||||
options: GradientOptionsUpdate::Type(GradientType::Radial),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
])
|
||||
.selected_index(Some((self.selected_gradient().unwrap_or(self.options.gradient_type) == GradientType::Radial) as u32))
|
||||
.widget_holder();
|
||||
|
@ -204,7 +208,6 @@ impl SelectedGradient {
|
|||
|
||||
/// Update the layer fill to the current gradient
|
||||
pub fn render_gradient(&mut self, responses: &mut VecDeque<Message>) {
|
||||
self.gradient.transform = self.transform;
|
||||
if let Some(layer) = self.layer {
|
||||
responses.add(GraphOperationMessage::FillSet {
|
||||
layer,
|
||||
|
@ -225,7 +228,7 @@ impl ToolTransition for GradientTool {
|
|||
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||
EventToMessageMap {
|
||||
tool_abort: Some(GradientToolMessage::Abort.into()),
|
||||
overlay_provider: Some(|overlay_context| GradientToolMessage::Overlays(overlay_context).into()),
|
||||
overlay_provider: Some(|context| GradientToolMessage::Overlays { context }.into()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -257,7 +260,7 @@ impl Fsm for GradientToolFsmState {
|
|||
|
||||
let ToolMessage::Gradient(event) = event else { return self };
|
||||
match (self, event) {
|
||||
(_, GradientToolMessage::Overlays(mut overlay_context)) => {
|
||||
(_, GradientToolMessage::Overlays { context: mut overlay_context }) => {
|
||||
let selected = tool_data.selected_gradient.as_ref();
|
||||
|
||||
for layer in document.network_interface.selected_nodes().selected_visible_layers(&document.network_interface) {
|
||||
|
@ -436,14 +439,7 @@ impl Fsm for GradientToolFsmState {
|
|||
gradient.clone()
|
||||
} else {
|
||||
// Generate a new gradient
|
||||
Gradient::new(
|
||||
DVec2::ZERO,
|
||||
global_tool_data.secondary_color,
|
||||
DVec2::ONE,
|
||||
global_tool_data.primary_color,
|
||||
DAffine2::IDENTITY,
|
||||
tool_options.gradient_type,
|
||||
)
|
||||
Gradient::new(DVec2::ZERO, global_tool_data.secondary_color, DVec2::ONE, global_tool_data.primary_color, tool_options.gradient_type)
|
||||
};
|
||||
let selected_gradient = SelectedGradient::new(gradient, layer, document).with_gradient_start(input.mouse.position);
|
||||
|
||||
|
|
|
@ -21,14 +21,16 @@ use crate::messages::tool::common_functionality::shape_editor::{
|
|||
};
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager};
|
||||
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate, make_path_editable_is_allowed};
|
||||
use bezier_rs::{Bezier, BezierHandles, TValue};
|
||||
use graphene_std::Color;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::subpath::pathseg_points;
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use graphene_std::uuid::NodeId;
|
||||
use graphene_std::vector::algorithms::util::pathseg_tangent;
|
||||
use graphene_std::vector::click_target::ClickTargetType;
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point, point_to_dvec2};
|
||||
use graphene_std::vector::{HandleExt, NoHashBuilder, PointId, SegmentId, Vector, VectorModificationType};
|
||||
use kurbo::{DEFAULT_ACCURACY, ParamCurve, ParamCurveNearest, PathSeg, Rect};
|
||||
use std::vec;
|
||||
|
||||
#[derive(Default, ExtractField)]
|
||||
|
@ -49,8 +51,10 @@ pub struct PathToolOptions {
|
|||
pub enum PathToolMessage {
|
||||
// Standard messages
|
||||
Abort,
|
||||
Overlays(OverlayContext),
|
||||
SelectionChanged,
|
||||
Overlays {
|
||||
context: OverlayContext,
|
||||
},
|
||||
|
||||
// Tool-specific messages
|
||||
BreakPath,
|
||||
|
@ -121,10 +125,13 @@ pub enum PathToolMessage {
|
|||
position: ReferencePoint,
|
||||
},
|
||||
SwapSelectedHandles,
|
||||
UpdateOptions(PathOptionsUpdate),
|
||||
UpdateOptions {
|
||||
options: PathOptionsUpdate,
|
||||
},
|
||||
UpdateSelectedPointsStatus {
|
||||
overlay_context: OverlayContext,
|
||||
},
|
||||
StartSlidingPoint,
|
||||
Copy {
|
||||
clipboard: Clipboard,
|
||||
},
|
||||
|
@ -272,15 +279,30 @@ impl LayoutHolder for PathTool {
|
|||
RadioEntryData::new("all")
|
||||
.icon("HandleVisibilityAll")
|
||||
.tooltip("Show all handles regardless of selection")
|
||||
.on_update(move |_| PathToolMessage::UpdateOptions(PathOptionsUpdate::OverlayModeType(PathOverlayMode::AllHandles)).into()),
|
||||
.on_update(move |_| {
|
||||
PathToolMessage::UpdateOptions {
|
||||
options: PathOptionsUpdate::OverlayModeType(PathOverlayMode::AllHandles),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
RadioEntryData::new("selected")
|
||||
.icon("HandleVisibilitySelected")
|
||||
.tooltip("Show only handles of the segments connected to selected points")
|
||||
.on_update(move |_| PathToolMessage::UpdateOptions(PathOptionsUpdate::OverlayModeType(PathOverlayMode::SelectedPointHandles)).into()),
|
||||
.on_update(move |_| {
|
||||
PathToolMessage::UpdateOptions {
|
||||
options: PathOptionsUpdate::OverlayModeType(PathOverlayMode::SelectedPointHandles),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
RadioEntryData::new("frontier")
|
||||
.icon("HandleVisibilityFrontier")
|
||||
.tooltip("Show only handles at the frontiers of the segments connected to selected points")
|
||||
.on_update(move |_| PathToolMessage::UpdateOptions(PathOptionsUpdate::OverlayModeType(PathOverlayMode::FrontierHandles)).into()),
|
||||
.on_update(move |_| {
|
||||
PathToolMessage::UpdateOptions {
|
||||
options: PathOptionsUpdate::OverlayModeType(PathOverlayMode::FrontierHandles),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
])
|
||||
.selected_index(Some(self.options.path_overlay_mode as u32))
|
||||
.widget_holder();
|
||||
|
@ -342,7 +364,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Path
|
|||
let updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated);
|
||||
|
||||
match message {
|
||||
ToolMessage::Path(PathToolMessage::UpdateOptions(action)) => match action {
|
||||
ToolMessage::Path(PathToolMessage::UpdateOptions { options }) => match options {
|
||||
PathOptionsUpdate::OverlayModeType(overlay_mode_type) => {
|
||||
self.options.path_overlay_mode = overlay_mode_type;
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
@ -418,6 +440,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Path
|
|||
DeleteAndBreakPath,
|
||||
ClosePath,
|
||||
PointerMove,
|
||||
StartSlidingPoint,
|
||||
Copy,
|
||||
Cut,
|
||||
DeleteSelected,
|
||||
|
@ -436,6 +459,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Path
|
|||
BreakPath,
|
||||
DeleteAndBreakPath,
|
||||
SwapSelectedHandles,
|
||||
StartSlidingPoint,
|
||||
Copy,
|
||||
Cut,
|
||||
DeleteSelected,
|
||||
|
@ -454,6 +478,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Path
|
|||
DeleteAndBreakPath,
|
||||
Escape,
|
||||
RightClick,
|
||||
StartSlidingPoint,
|
||||
TogglePointEditing,
|
||||
ToggleSegmentEditing
|
||||
),
|
||||
|
@ -472,7 +497,7 @@ impl ToolTransition for PathTool {
|
|||
EventToMessageMap {
|
||||
tool_abort: Some(PathToolMessage::Abort.into()),
|
||||
selection_changed: Some(PathToolMessage::SelectionChanged.into()),
|
||||
overlay_provider: Some(|overlay_context| PathToolMessage::Overlays(overlay_context).into()),
|
||||
overlay_provider: Some(|context| PathToolMessage::Overlays { context }.into()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -494,7 +519,7 @@ pub enum PointSelectState {
|
|||
#[derive(Clone, Copy)]
|
||||
pub struct SlidingSegmentData {
|
||||
segment_id: SegmentId,
|
||||
bezier: Bezier,
|
||||
bezier: PathSeg,
|
||||
start: PointId,
|
||||
}
|
||||
|
||||
|
@ -840,13 +865,12 @@ impl PathToolData {
|
|||
responses.add(OverlaysMessage::Draw);
|
||||
PathToolFsmState::Dragging(self.dragging_state)
|
||||
} else {
|
||||
let start_pos = segment.bezier().start;
|
||||
let end_pos = segment.bezier().end;
|
||||
let points = pathseg_points(segment.pathseg());
|
||||
|
||||
let [pos1, pos2] = match segment.bezier().handles {
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [handle_start, handle_end],
|
||||
BezierHandles::Quadratic { handle } => [handle, end_pos],
|
||||
BezierHandles::Linear => [start_pos + (end_pos - start_pos) / 3., end_pos + (start_pos - end_pos) / 3.],
|
||||
let [pos1, pos2] = match (points.p1, points.p2) {
|
||||
(Some(p1), Some(p2)) => [p1, p2],
|
||||
(Some(p1), None) | (None, Some(p1)) => [p1, points.p3],
|
||||
(None, None) => [points.p0 + (points.p3 - points.p0) / 3., points.p3 + (points.p0 - points.p3) / 3.],
|
||||
};
|
||||
self.molding_info = Some((pos1, pos2));
|
||||
PathToolFsmState::Dragging(self.dragging_state)
|
||||
|
@ -1207,15 +1231,10 @@ impl PathToolData {
|
|||
};
|
||||
let Some(vector) = document.network_interface.compute_modified_vector(layer) else { return false };
|
||||
|
||||
// Check that the handles of anchor point are also colinear
|
||||
if !vector.colinear(*anchor) {
|
||||
return false;
|
||||
};
|
||||
|
||||
let Some(point_id) = anchor.as_anchor() else { return false };
|
||||
|
||||
let mut connected_segments = [None, None];
|
||||
for (segment, bezier, start, end) in vector.segment_bezier_iter() {
|
||||
for (segment, bezier, start, end) in vector.segment_iter() {
|
||||
if start == point_id || end == point_id {
|
||||
match (connected_segments[0], connected_segments[1]) {
|
||||
(None, None) => connected_segments[0] = Some(SlidingSegmentData { segment_id: segment, bezier, start }),
|
||||
|
@ -1255,13 +1274,13 @@ impl PathToolData {
|
|||
|
||||
let segments = sliding_point_info.connected_segments;
|
||||
|
||||
let t1 = segments[0].bezier.project(layer_pos);
|
||||
let position1 = segments[0].bezier.evaluate(TValue::Parametric(t1));
|
||||
let t1 = segments[0].bezier.nearest(dvec2_to_point(layer_pos), DEFAULT_ACCURACY).t;
|
||||
let position1 = point_to_dvec2(segments[0].bezier.eval(t1));
|
||||
|
||||
let t2 = segments[1].bezier.project(layer_pos);
|
||||
let position2 = segments[1].bezier.evaluate(TValue::Parametric(t2));
|
||||
let t2 = segments[1].bezier.nearest(dvec2_to_point(layer_pos), DEFAULT_ACCURACY).t;
|
||||
let position2 = point_to_dvec2(segments[1].bezier.eval(t2));
|
||||
|
||||
let (closer_segment, farther_segment, t_value, new_position) = if position2.distance(layer_pos) < position1.distance(layer_pos) {
|
||||
let (closer_segment, further_segment, t_value, new_position) = if position2.distance(layer_pos) < position1.distance(layer_pos) {
|
||||
(segments[1], segments[0], t2, position2)
|
||||
} else {
|
||||
(segments[0], segments[1], t1, position1)
|
||||
|
@ -1276,50 +1295,59 @@ impl PathToolData {
|
|||
shape_editor.move_anchor(anchor, &vector, delta, layer, None, responses);
|
||||
|
||||
// Make a split at the t_value
|
||||
let [first, second] = closer_segment.bezier.split(TValue::Parametric(t_value));
|
||||
let closer_segment_other_point = if anchor == closer_segment.start { closer_segment.bezier.end } else { closer_segment.bezier.start };
|
||||
let first = closer_segment.bezier.subsegment(0_f64..t_value);
|
||||
let second = closer_segment.bezier.subsegment(t_value..1.);
|
||||
|
||||
let (split_segment, other_segment) = if first.start == closer_segment_other_point { (first, second) } else { (second, first) };
|
||||
let closer_segment_other_point = if anchor == closer_segment.start {
|
||||
closer_segment.bezier.end()
|
||||
} else {
|
||||
closer_segment.bezier.start()
|
||||
};
|
||||
|
||||
let (split_segment, other_segment) = if first.start() == closer_segment_other_point { (first, second) } else { (second, first) };
|
||||
let split_segment_points = pathseg_points(split_segment);
|
||||
|
||||
// Primary handle maps to primary handle and secondary maps to secondary
|
||||
let closer_primary_handle = HandleId::primary(closer_segment.segment_id);
|
||||
let Some(handle_position) = split_segment.handle_start() else { return };
|
||||
let relative_position1 = handle_position - split_segment.start;
|
||||
let Some(handle_position) = split_segment_points.p1 else { return };
|
||||
let relative_position1 = handle_position - split_segment_points.p0;
|
||||
let modification_type = closer_primary_handle.set_relative_position(relative_position1);
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
let closer_secondary_handle = HandleId::end(closer_segment.segment_id);
|
||||
let Some(handle_position) = split_segment.handle_end() else { return };
|
||||
let relative_position2 = handle_position - split_segment.end;
|
||||
let Some(handle_position) = split_segment_points.p2 else { return };
|
||||
let relative_position2 = handle_position - split_segment_points.p3;
|
||||
let modification_type = closer_secondary_handle.set_relative_position(relative_position2);
|
||||
responses.add(GraphOperationMessage::Vector { layer, modification_type });
|
||||
|
||||
let end_handle_direction = if anchor == closer_segment.start { -relative_position1 } else { -relative_position2 };
|
||||
|
||||
let (farther_other_point, start_handle, end_handle, start_handle_pos) = if anchor == farther_segment.start {
|
||||
let further_segment_points = pathseg_points(further_segment.bezier);
|
||||
|
||||
let (further_other_point, start_handle, end_handle, start_handle_pos) = if anchor == further_segment.start {
|
||||
(
|
||||
farther_segment.bezier.end,
|
||||
HandleId::end(farther_segment.segment_id),
|
||||
HandleId::primary(farther_segment.segment_id),
|
||||
farther_segment.bezier.handle_end(),
|
||||
further_segment_points.p3,
|
||||
HandleId::end(further_segment.segment_id),
|
||||
HandleId::primary(further_segment.segment_id),
|
||||
further_segment_points.p2,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
farther_segment.bezier.start,
|
||||
HandleId::primary(farther_segment.segment_id),
|
||||
HandleId::end(farther_segment.segment_id),
|
||||
farther_segment.bezier.handle_start(),
|
||||
further_segment_points.p0,
|
||||
HandleId::primary(further_segment.segment_id),
|
||||
HandleId::end(further_segment.segment_id),
|
||||
further_segment_points.p1,
|
||||
)
|
||||
};
|
||||
let Some(start_handle_position) = start_handle_pos else { return };
|
||||
let start_handle_direction = start_handle_position - farther_other_point;
|
||||
let start_handle_direction = start_handle_position - further_other_point;
|
||||
|
||||
// Get normalized direction vectors, if cubic handle is zero then we consider corresponding tangent
|
||||
let d1 = start_handle_direction.try_normalize().unwrap_or({
|
||||
if anchor == farther_segment.start {
|
||||
-farther_segment.bezier.tangent(TValue::Parametric(0.99))
|
||||
if anchor == further_segment.start {
|
||||
-pathseg_tangent(further_segment.bezier, 1.)
|
||||
} else {
|
||||
farther_segment.bezier.tangent(TValue::Parametric(0.01))
|
||||
pathseg_tangent(further_segment.bezier, 0.)
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1328,7 +1356,7 @@ impl PathToolData {
|
|||
let min_len1 = start_handle_direction.length() * 0.4;
|
||||
let min_len2 = end_handle_direction.length() * 0.4;
|
||||
|
||||
let (relative_pos1, relative_pos2) = find_two_param_best_approximate(farther_other_point, new_position, d1, d2, min_len1, min_len2, farther_segment.bezier, other_segment);
|
||||
let (relative_pos1, relative_pos2) = find_two_param_best_approximate(further_other_point, new_position, d1, d2, min_len1, min_len2, further_segment.bezier, other_segment);
|
||||
|
||||
// Now set those handles to these handle lengths keeping the directions d1, d2
|
||||
let modification_type = start_handle.set_relative_position(relative_pos1);
|
||||
|
@ -1548,14 +1576,22 @@ impl Fsm for PathToolFsmState {
|
|||
|
||||
match (multiple_toggle, point_edit) {
|
||||
(true, true) => {
|
||||
responses.add(PathToolMessage::UpdateOptions(PathOptionsUpdate::PointEditingMode { enabled: false }));
|
||||
responses.add(PathToolMessage::UpdateOptions {
|
||||
options: PathOptionsUpdate::PointEditingMode { enabled: false },
|
||||
});
|
||||
}
|
||||
(true, false) => {
|
||||
responses.add(PathToolMessage::UpdateOptions(PathOptionsUpdate::PointEditingMode { enabled: true }));
|
||||
responses.add(PathToolMessage::UpdateOptions {
|
||||
options: PathOptionsUpdate::PointEditingMode { enabled: true },
|
||||
});
|
||||
}
|
||||
(_, _) => {
|
||||
responses.add(PathToolMessage::UpdateOptions(PathOptionsUpdate::PointEditingMode { enabled: true }));
|
||||
responses.add(PathToolMessage::UpdateOptions(PathOptionsUpdate::SegmentEditingMode { enabled: false }));
|
||||
responses.add(PathToolMessage::UpdateOptions {
|
||||
options: PathOptionsUpdate::PointEditingMode { enabled: true },
|
||||
});
|
||||
responses.add(PathToolMessage::UpdateOptions {
|
||||
options: PathOptionsUpdate::SegmentEditingMode { enabled: false },
|
||||
});
|
||||
|
||||
// Select all of the end points of selected segments
|
||||
let selected_layers = shape_editor.selected_layers().cloned().collect::<Vec<_>>();
|
||||
|
@ -1593,14 +1629,22 @@ impl Fsm for PathToolFsmState {
|
|||
|
||||
match (multiple_toggle, segment_edit) {
|
||||
(true, true) => {
|
||||
responses.add(PathToolMessage::UpdateOptions(PathOptionsUpdate::SegmentEditingMode { enabled: false }));
|
||||
responses.add(PathToolMessage::UpdateOptions {
|
||||
options: PathOptionsUpdate::SegmentEditingMode { enabled: false },
|
||||
});
|
||||
}
|
||||
(true, false) => {
|
||||
responses.add(PathToolMessage::UpdateOptions(PathOptionsUpdate::SegmentEditingMode { enabled: true }));
|
||||
responses.add(PathToolMessage::UpdateOptions {
|
||||
options: PathOptionsUpdate::SegmentEditingMode { enabled: true },
|
||||
});
|
||||
}
|
||||
(_, _) => {
|
||||
responses.add(PathToolMessage::UpdateOptions(PathOptionsUpdate::PointEditingMode { enabled: false }));
|
||||
responses.add(PathToolMessage::UpdateOptions(PathOptionsUpdate::SegmentEditingMode { enabled: true }));
|
||||
responses.add(PathToolMessage::UpdateOptions {
|
||||
options: PathOptionsUpdate::PointEditingMode { enabled: false },
|
||||
});
|
||||
responses.add(PathToolMessage::UpdateOptions {
|
||||
options: PathOptionsUpdate::SegmentEditingMode { enabled: true },
|
||||
});
|
||||
|
||||
// Select all the segments which have both of the ends selected
|
||||
let selected_layers = shape_editor.selected_layers().cloned().collect::<Vec<_>>();
|
||||
|
@ -1623,7 +1667,7 @@ impl Fsm for PathToolFsmState {
|
|||
|
||||
self
|
||||
}
|
||||
(_, PathToolMessage::Overlays(mut overlay_context)) => {
|
||||
(_, PathToolMessage::Overlays { context: mut overlay_context }) => {
|
||||
// Set this to show ghost line only if drag actually happened
|
||||
if matches!(self, Self::Dragging(_)) && tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD {
|
||||
for (outline, layer) in &tool_data.ghost_outline {
|
||||
|
@ -1737,13 +1781,13 @@ impl Fsm for PathToolFsmState {
|
|||
if tool_options.path_editing_mode.segment_editing_mode && !tool_data.segment_editing_modifier {
|
||||
let transform = document.metadata().transform_to_viewport_if_feeds(closest_segment.layer(), &document.network_interface);
|
||||
|
||||
overlay_context.outline_overlay_bezier(closest_segment.bezier(), transform);
|
||||
overlay_context.outline_overlay_bezier(closest_segment.pathseg(), transform);
|
||||
|
||||
// Draw the anchors again
|
||||
let display_anchors = overlay_context.visibility_settings.anchors();
|
||||
if display_anchors {
|
||||
let start_pos = transform.transform_point2(closest_segment.bezier().start);
|
||||
let end_pos = transform.transform_point2(closest_segment.bezier().end);
|
||||
let start_pos = transform.transform_point2(point_to_dvec2(closest_segment.pathseg().start()));
|
||||
let end_pos = transform.transform_point2(point_to_dvec2(closest_segment.pathseg().end()));
|
||||
let start_id = closest_segment.points()[0];
|
||||
let end_id = closest_segment.points()[1];
|
||||
if let Some(shape_state) = shape_editor.selected_shape_state.get_mut(&closest_segment.layer()) {
|
||||
|
@ -1820,7 +1864,7 @@ impl Fsm for PathToolFsmState {
|
|||
let (points_inside, segments_inside) = match selection_shape {
|
||||
SelectionShapeType::Box => {
|
||||
let previous_mouse = document.metadata().document_to_viewport.transform_point2(tool_data.previous_mouse_position);
|
||||
let bbox = [tool_data.drag_start_pos, previous_mouse];
|
||||
let bbox = Rect::new(tool_data.drag_start_pos.x, tool_data.drag_start_pos.y, previous_mouse.x, previous_mouse.y).abs();
|
||||
shape_editor.get_inside_points_and_segments(
|
||||
&document.network_interface,
|
||||
SelectionShape::Box(bbox),
|
||||
|
@ -1865,7 +1909,7 @@ impl Fsm for PathToolFsmState {
|
|||
|
||||
let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
|
||||
|
||||
for (segment, bezier, _, _) in vector.segment_bezier_iter() {
|
||||
for (segment, bezier, _, _) in vector.segment_iter() {
|
||||
if segments.contains(&segment) {
|
||||
overlay_context.outline_overlay_bezier(bezier, transform);
|
||||
}
|
||||
|
@ -2073,10 +2117,6 @@ impl Fsm for PathToolFsmState {
|
|||
}
|
||||
|
||||
if !tool_data.update_colinear(equidistant_state, toggle_colinear_state, tool_action_data.shape_editor, tool_action_data.document, responses) {
|
||||
if snap_angle_state && lock_angle_state && tool_data.start_sliding_point(tool_action_data.shape_editor, tool_action_data.document) {
|
||||
return PathToolFsmState::SlidingPoint;
|
||||
}
|
||||
|
||||
tool_data.drag(
|
||||
equidistant_state,
|
||||
lock_angle_state,
|
||||
|
@ -2241,7 +2281,8 @@ impl Fsm for PathToolFsmState {
|
|||
|
||||
match selection_shape {
|
||||
SelectionShapeType::Box => {
|
||||
let bbox = [tool_data.drag_start_pos, previous_mouse];
|
||||
let bbox = Rect::new(tool_data.drag_start_pos.x, tool_data.drag_start_pos.y, previous_mouse.x, previous_mouse.y).abs();
|
||||
|
||||
shape_editor.select_all_in_shape(
|
||||
&document.network_interface,
|
||||
SelectionShape::Box(bbox),
|
||||
|
@ -2337,7 +2378,8 @@ impl Fsm for PathToolFsmState {
|
|||
} else {
|
||||
match selection_shape {
|
||||
SelectionShapeType::Box => {
|
||||
let bbox = [tool_data.drag_start_pos, previous_mouse];
|
||||
let bbox = Rect::new(tool_data.drag_start_pos.x, tool_data.drag_start_pos.y, previous_mouse.x, previous_mouse.y).abs();
|
||||
|
||||
shape_editor.select_all_in_shape(
|
||||
&document.network_interface,
|
||||
SelectionShape::Box(bbox),
|
||||
|
@ -2370,6 +2412,8 @@ impl Fsm for PathToolFsmState {
|
|||
tool_data.ghost_outline.clear();
|
||||
let extend_selection = input.keyboard.get(extend_selection as usize);
|
||||
let drag_occurred = tool_data.drag_start_pos.distance(input.mouse.position) > DRAG_THRESHOLD;
|
||||
let mut segment_dissolved = false;
|
||||
let mut point_inserted = false;
|
||||
|
||||
let nearest_point = shape_editor.find_nearest_visible_point_indices(
|
||||
&document.network_interface,
|
||||
|
@ -2390,6 +2434,7 @@ impl Fsm for PathToolFsmState {
|
|||
if tool_data.delete_segment_pressed {
|
||||
if let Some(vector) = document.network_interface.compute_modified_vector(segment.layer()) {
|
||||
shape_editor.dissolve_segment(responses, segment.layer(), &vector, segment.segment(), segment.points());
|
||||
segment_dissolved = true;
|
||||
}
|
||||
} else {
|
||||
let is_segment_selected = shape_editor
|
||||
|
@ -2398,11 +2443,7 @@ impl Fsm for PathToolFsmState {
|
|||
.is_some_and(|state| state.is_segment_selected(segment.segment()));
|
||||
|
||||
segment.adjusted_insert_and_select(shape_editor, responses, extend_selection, point_mode, is_segment_selected);
|
||||
tool_data.segment = None;
|
||||
tool_data.molding_info = None;
|
||||
tool_data.molding_segment = false;
|
||||
tool_data.temporary_adjacent_handles_while_molding = None;
|
||||
return PathToolFsmState::Ready;
|
||||
point_inserted = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2410,6 +2451,11 @@ impl Fsm for PathToolFsmState {
|
|||
tool_data.molding_info = None;
|
||||
tool_data.molding_segment = false;
|
||||
tool_data.temporary_adjacent_handles_while_molding = None;
|
||||
|
||||
if segment_dissolved || point_inserted {
|
||||
responses.add(DocumentMessage::EndTransaction);
|
||||
return PathToolFsmState::Ready;
|
||||
}
|
||||
}
|
||||
|
||||
let segment_mode = tool_options.path_editing_mode.segment_editing_mode;
|
||||
|
@ -2566,6 +2612,14 @@ impl Fsm for PathToolFsmState {
|
|||
shape_editor.delete_point_and_break_path(document, responses);
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
(_, PathToolMessage::StartSlidingPoint) => {
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
if tool_data.start_sliding_point(shape_editor, document) {
|
||||
PathToolFsmState::SlidingPoint
|
||||
} else {
|
||||
PathToolFsmState::Ready
|
||||
}
|
||||
}
|
||||
(_, PathToolMessage::Copy { clipboard }) => {
|
||||
// TODO: Add support for selected segments
|
||||
|
||||
|
@ -2697,16 +2751,13 @@ impl Fsm for PathToolFsmState {
|
|||
|
||||
// Create new segment ids and add the segments into the existing vector content
|
||||
let mut segments_map = HashMap::new();
|
||||
for (segment_id, bezier, start, end) in new_vector.segment_bezier_iter() {
|
||||
for (segment_id, bezier, start, end) in new_vector.segment_iter() {
|
||||
let new_segment_id = SegmentId::generate();
|
||||
|
||||
segments_map.insert(segment_id, new_segment_id);
|
||||
|
||||
let handles = match bezier.handles {
|
||||
BezierHandles::Linear => [None, None],
|
||||
BezierHandles::Quadratic { handle } => [Some(handle - bezier.start), None],
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start), Some(handle_end - bezier.end)],
|
||||
};
|
||||
let points = pathseg_points(bezier);
|
||||
let handles = [points.p1, points.p2];
|
||||
|
||||
let points = [points_map[&start], points_map[&end]];
|
||||
let modification_type = VectorModificationType::InsertSegment { id: new_segment_id, points, handles };
|
||||
|
@ -2802,7 +2853,7 @@ impl Fsm for PathToolFsmState {
|
|||
|
||||
let mut segments_map = HashMap::new();
|
||||
|
||||
for (segment_id, bezier, start, end) in old_vector.segment_bezier_iter() {
|
||||
for (segment_id, bezier, start, end) in old_vector.segment_iter() {
|
||||
let both_ends_selected = layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(start)) && layer_selection_state.is_point_selected(ManipulatorPointId::Anchor(end));
|
||||
|
||||
let segment_selected = layer_selection_state.is_segment_selected(segment_id);
|
||||
|
@ -2811,11 +2862,8 @@ impl Fsm for PathToolFsmState {
|
|||
let new_id = SegmentId::generate();
|
||||
segments_map.insert(segment_id, new_id);
|
||||
|
||||
let handles = match bezier.handles {
|
||||
BezierHandles::Linear => [None, None],
|
||||
BezierHandles::Quadratic { handle } => [Some(handle - bezier.start), None],
|
||||
BezierHandles::Cubic { handle_start, handle_end } => [Some(handle_start - bezier.start), Some(handle_end - bezier.end)],
|
||||
};
|
||||
let points = pathseg_points(bezier);
|
||||
let handles = [points.p1, points.p2];
|
||||
|
||||
let points = [points_map[&start], points_map[&end]];
|
||||
let modification_type = VectorModificationType::InsertSegment { id: new_id, points, handles };
|
||||
|
@ -3291,7 +3339,7 @@ fn update_dynamic_hints(
|
|||
}
|
||||
}
|
||||
|
||||
let mut drag_selected_hints = vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")];
|
||||
let drag_selected_hints = vec![HintInfo::mouse(MouseMotion::LmbDrag, "Drag Selected")];
|
||||
let mut delete_selected_hints = vec![HintInfo::keys([Key::Delete], "Delete Selected")];
|
||||
|
||||
if at_least_one_anchor_selected {
|
||||
|
@ -3299,10 +3347,6 @@ fn update_dynamic_hints(
|
|||
delete_selected_hints.push(HintInfo::keys([Key::Shift], "Cut Anchor").prepend_plus());
|
||||
}
|
||||
|
||||
if single_colinear_anchor_selected {
|
||||
drag_selected_hints.push(HintInfo::multi_keys([[Key::Control], [Key::Shift]], "Slide").prepend_plus());
|
||||
}
|
||||
|
||||
let segment_edit = tool_options.path_editing_mode.segment_editing_mode;
|
||||
let point_edit = tool_options.path_editing_mode.point_editing_mode;
|
||||
|
||||
|
@ -3367,9 +3411,15 @@ fn update_dynamic_hints(
|
|||
let mut groups = vec![
|
||||
HintGroup(drag_selected_hints),
|
||||
HintGroup(vec![HintInfo::multi_keys([[Key::KeyG], [Key::KeyR], [Key::KeyS]], "Grab/Rotate/Scale Selected")]),
|
||||
HintGroup(vec![HintInfo::arrow_keys("Nudge Selected"), HintInfo::keys([Key::Shift], "10x").prepend_plus()]),
|
||||
HintGroup(delete_selected_hints),
|
||||
];
|
||||
|
||||
if single_colinear_anchor_selected {
|
||||
groups.push(HintGroup(vec![HintInfo::multi_keys([[Key::KeyG], [Key::KeyG]], "Slide")]));
|
||||
}
|
||||
|
||||
groups.push(HintGroup(vec![HintInfo::arrow_keys("Nudge Selected"), HintInfo::keys([Key::Shift], "10x").prepend_plus()]));
|
||||
groups.push(HintGroup(delete_selected_hints));
|
||||
|
||||
hint_data.append(&mut groups);
|
||||
}
|
||||
|
||||
|
|
|
@ -11,11 +11,12 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{self
|
|||
use crate::messages::tool::common_functionality::shape_editor::ShapeState;
|
||||
use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandidatePoint, SnapConstraint, SnapData, SnapManager, SnapTypeConfiguration};
|
||||
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, closest_point, should_extend};
|
||||
use bezier_rs::{Bezier, BezierHandles};
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::Color;
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId};
|
||||
use graphene_std::subpath::pathseg_points;
|
||||
use graphene_std::vector::misc::{HandleId, ManipulatorPointId, dvec2_to_point};
|
||||
use graphene_std::vector::{NoHashBuilder, PointId, SegmentId, StrokeId, Vector, VectorModificationType};
|
||||
use kurbo::{CubicBez, PathSeg};
|
||||
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct PenTool {
|
||||
|
@ -49,7 +50,9 @@ pub enum PenToolMessage {
|
|||
Abort,
|
||||
SelectionChanged,
|
||||
WorkingColorChanged,
|
||||
Overlays(OverlayContext),
|
||||
Overlays {
|
||||
context: OverlayContext,
|
||||
},
|
||||
|
||||
// Tool-specific messages
|
||||
|
||||
|
@ -79,7 +82,9 @@ pub enum PenToolMessage {
|
|||
},
|
||||
Redo,
|
||||
Undo,
|
||||
UpdateOptions(PenOptionsUpdate),
|
||||
UpdateOptions {
|
||||
options: PenOptionsUpdate,
|
||||
},
|
||||
RecalculateLatestPointsPosition,
|
||||
RemovePreviousHandle,
|
||||
GRS {
|
||||
|
@ -137,7 +142,12 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder {
|
|||
.label("Weight")
|
||||
.min(0.)
|
||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
.on_update(|number_input: &NumberInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::LineWeight(number_input.value.unwrap())).into())
|
||||
.on_update(|number_input: &NumberInput| {
|
||||
PenToolMessage::UpdateOptions {
|
||||
options: PenOptionsUpdate::LineWeight(number_input.value.unwrap()),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder()
|
||||
}
|
||||
|
||||
|
@ -146,9 +156,26 @@ impl LayoutHolder for PenTool {
|
|||
let mut widgets = self.options.fill.create_widgets(
|
||||
"Fill",
|
||||
true,
|
||||
|_| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColorType(color_type.clone())).into()),
|
||||
|color: &ColorInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(),
|
||||
|_| {
|
||||
PenToolMessage::UpdateOptions {
|
||||
options: PenOptionsUpdate::FillColor(None),
|
||||
}
|
||||
.into()
|
||||
},
|
||||
|color_type: ToolColorType| {
|
||||
WidgetCallback::new(move |_| {
|
||||
PenToolMessage::UpdateOptions {
|
||||
options: PenOptionsUpdate::FillColorType(color_type.clone()),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
},
|
||||
|color: &ColorInput| {
|
||||
PenToolMessage::UpdateOptions {
|
||||
options: PenOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb())),
|
||||
}
|
||||
.into()
|
||||
},
|
||||
);
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
|
@ -156,9 +183,26 @@ impl LayoutHolder for PenTool {
|
|||
widgets.append(&mut self.options.stroke.create_widgets(
|
||||
"Stroke",
|
||||
true,
|
||||
|_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColorType(color_type.clone())).into()),
|
||||
|color: &ColorInput| PenToolMessage::UpdateOptions(PenOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(),
|
||||
|_| {
|
||||
PenToolMessage::UpdateOptions {
|
||||
options: PenOptionsUpdate::StrokeColor(None),
|
||||
}
|
||||
.into()
|
||||
},
|
||||
|color_type: ToolColorType| {
|
||||
WidgetCallback::new(move |_| {
|
||||
PenToolMessage::UpdateOptions {
|
||||
options: PenOptionsUpdate::StrokeColorType(color_type.clone()),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
},
|
||||
|color: &ColorInput| {
|
||||
PenToolMessage::UpdateOptions {
|
||||
options: PenOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb())),
|
||||
}
|
||||
.into()
|
||||
},
|
||||
));
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
|
@ -172,11 +216,21 @@ impl LayoutHolder for PenTool {
|
|||
RadioEntryData::new("all")
|
||||
.icon("HandleVisibilityAll")
|
||||
.tooltip("Show all handles regardless of selection")
|
||||
.on_update(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::OverlayModeType(PenOverlayMode::AllHandles)).into()),
|
||||
.on_update(move |_| {
|
||||
PenToolMessage::UpdateOptions {
|
||||
options: PenOptionsUpdate::OverlayModeType(PenOverlayMode::AllHandles),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
RadioEntryData::new("frontier")
|
||||
.icon("HandleVisibilityFrontier")
|
||||
.tooltip("Show only handles at the frontiers of the segments connected to selected points")
|
||||
.on_update(move |_| PenToolMessage::UpdateOptions(PenOptionsUpdate::OverlayModeType(PenOverlayMode::FrontierHandles)).into()),
|
||||
.on_update(move |_| {
|
||||
PenToolMessage::UpdateOptions {
|
||||
options: PenOptionsUpdate::OverlayModeType(PenOverlayMode::FrontierHandles),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
])
|
||||
.selected_index(Some(self.options.pen_overlay_mode as u32))
|
||||
.widget_holder(),
|
||||
|
@ -189,12 +243,12 @@ impl LayoutHolder for PenTool {
|
|||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for PenTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
|
||||
let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message else {
|
||||
let ToolMessage::Pen(PenToolMessage::UpdateOptions { options }) = message else {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true);
|
||||
return;
|
||||
};
|
||||
|
||||
match action {
|
||||
match options {
|
||||
PenOptionsUpdate::OverlayModeType(overlay_mode_type) => {
|
||||
self.options.pen_overlay_mode = overlay_mode_type;
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
@ -252,7 +306,7 @@ impl ToolTransition for PenTool {
|
|||
tool_abort: Some(PenToolMessage::Abort.into()),
|
||||
selection_changed: Some(PenToolMessage::SelectionChanged.into()),
|
||||
working_color_changed: Some(PenToolMessage::WorkingColorChanged.into()),
|
||||
overlay_provider: Some(|overlay_context| PenToolMessage::Overlays(overlay_context).into()),
|
||||
overlay_provider: Some(|context| PenToolMessage::Overlays { context }.into()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -1257,7 +1311,7 @@ impl PenToolData {
|
|||
let vector = document.network_interface.compute_modified_vector(layer);
|
||||
let (handle_start, in_segment) = if let Some(vector) = &vector {
|
||||
vector
|
||||
.segment_bezier_iter()
|
||||
.segment_iter()
|
||||
.find_map(|(segment_id, bezier, start, end)| {
|
||||
let is_end = point == end;
|
||||
let is_start = point == start;
|
||||
|
@ -1265,15 +1319,11 @@ impl PenToolData {
|
|||
return None;
|
||||
}
|
||||
|
||||
let handle = match bezier.handles {
|
||||
BezierHandles::Cubic { handle_start, handle_end, .. } => {
|
||||
if is_start {
|
||||
handle_start
|
||||
} else {
|
||||
handle_end
|
||||
}
|
||||
}
|
||||
BezierHandles::Quadratic { handle } => handle,
|
||||
let points = pathseg_points(bezier);
|
||||
let handle = match (points.p1, points.p2) {
|
||||
(Some(p1), Some(_)) if is_start => p1,
|
||||
(Some(_), Some(p2)) if !is_start => p2,
|
||||
(Some(p1), None) | (None, Some(p1)) => p1,
|
||||
_ => return None,
|
||||
};
|
||||
Some((segment_id, is_end, handle))
|
||||
|
@ -1550,7 +1600,7 @@ impl Fsm for PenToolFsmState {
|
|||
responses.add(OverlaysMessage::Draw);
|
||||
self
|
||||
}
|
||||
(PenToolFsmState::Ready, PenToolMessage::Overlays(mut overlay_context)) => {
|
||||
(PenToolFsmState::Ready, PenToolMessage::Overlays { context: mut overlay_context }) => {
|
||||
match tool_options.pen_overlay_mode {
|
||||
PenOverlayMode::AllHandles => {
|
||||
path_overlays(document, DrawHandles::All, shape_editor, &mut overlay_context);
|
||||
|
@ -1578,7 +1628,7 @@ impl Fsm for PenToolFsmState {
|
|||
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
||||
self
|
||||
}
|
||||
(_, PenToolMessage::Overlays(mut overlay_context)) => {
|
||||
(_, PenToolMessage::Overlays { context: mut overlay_context }) => {
|
||||
let display_anchors = overlay_context.visibility_settings.anchors();
|
||||
let display_handles = overlay_context.visibility_settings.handles();
|
||||
|
||||
|
@ -1599,9 +1649,8 @@ impl Fsm for PenToolFsmState {
|
|||
let handle_start = tool_data.latest_point().map(|point| transform.transform_point2(point.handle_start));
|
||||
|
||||
if let (Some((start, handle_start)), Some(handle_end)) = (tool_data.latest_point().map(|point| (point.pos, point.handle_start)), tool_data.handle_end) {
|
||||
let handles = BezierHandles::Cubic { handle_start, handle_end };
|
||||
let end = tool_data.next_point;
|
||||
let bezier = Bezier { start, handles, end };
|
||||
let bezier = PathSeg::Cubic(CubicBez::new(dvec2_to_point(start), dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(end)));
|
||||
if (end - start).length_squared() > f64::EPSILON {
|
||||
// Draw the curve for the currently-being-placed segment
|
||||
overlay_context.outline_bezier(bezier, transform);
|
||||
|
@ -1723,7 +1772,7 @@ impl Fsm for PenToolFsmState {
|
|||
// We have the point. Join the 2 vertices and check if any path is closed.
|
||||
if let Some(end) = closest_point {
|
||||
let segment_id = SegmentId::generate();
|
||||
vector.push(segment_id, start, end, BezierHandles::Cubic { handle_start, handle_end }, StrokeId::ZERO);
|
||||
vector.push(segment_id, start, end, (Some(handle_start), Some(handle_end)), StrokeId::ZERO);
|
||||
|
||||
let grouped_segments = vector.auto_join_paths();
|
||||
let closed_paths = grouped_segments.iter().filter(|path| path.is_closed() && path.contains(segment_id));
|
||||
|
@ -1757,10 +1806,9 @@ impl Fsm for PenToolFsmState {
|
|||
self
|
||||
}
|
||||
(_, PenToolMessage::WorkingColorChanged) => {
|
||||
responses.add(PenToolMessage::UpdateOptions(PenOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
responses.add(PenToolMessage::UpdateOptions {
|
||||
options: PenOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),
|
||||
});
|
||||
self
|
||||
}
|
||||
(PenToolFsmState::Ready, PenToolMessage::DragStart { append_to_selected }) => {
|
||||
|
|
|
@ -20,12 +20,12 @@ use crate::messages::tool::common_functionality::shape_editor::SelectionShapeTyp
|
|||
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager};
|
||||
use crate::messages::tool::common_functionality::transformation_cage::*;
|
||||
use crate::messages::tool::common_functionality::utility_functions::{resize_bounds, rotate_bounds, skew_bounds, text_bounding_box, transforming_transform_cage};
|
||||
use bezier_rs::Subpath;
|
||||
use glam::DMat2;
|
||||
use graph_craft::document::NodeId;
|
||||
use graphene_std::path_bool::BooleanOperation;
|
||||
use graphene_std::renderer::Quad;
|
||||
use graphene_std::renderer::Rect;
|
||||
use graphene_std::subpath::Subpath;
|
||||
use graphene_std::transform::ReferencePoint;
|
||||
use std::fmt;
|
||||
|
||||
|
@ -78,7 +78,9 @@ pub struct SelectToolPointerKeys {
|
|||
pub enum SelectToolMessage {
|
||||
// Standard messages
|
||||
Abort,
|
||||
Overlays(OverlayContext),
|
||||
Overlays {
|
||||
context: OverlayContext,
|
||||
},
|
||||
|
||||
// Tool-specific messages
|
||||
DragStart {
|
||||
|
@ -92,10 +94,17 @@ pub enum SelectToolMessage {
|
|||
remove_from_selection: Key,
|
||||
},
|
||||
EditLayer,
|
||||
EditLayerExec,
|
||||
Enter,
|
||||
PointerMove(SelectToolPointerKeys),
|
||||
PointerOutsideViewport(SelectToolPointerKeys),
|
||||
SelectOptions(SelectOptionsUpdate),
|
||||
PointerMove {
|
||||
modifier_keys: SelectToolPointerKeys,
|
||||
},
|
||||
PointerOutsideViewport {
|
||||
modifier_keys: SelectToolPointerKeys,
|
||||
},
|
||||
SelectOptions {
|
||||
options: SelectOptionsUpdate,
|
||||
},
|
||||
SetPivot {
|
||||
position: ReferencePoint,
|
||||
},
|
||||
|
@ -126,9 +135,12 @@ impl SelectTool {
|
|||
let layer_selection_behavior_entries = [NestedSelectionBehavior::Shallowest, NestedSelectionBehavior::Deepest]
|
||||
.iter()
|
||||
.map(|mode| {
|
||||
MenuListEntry::new(format!("{mode:?}"))
|
||||
.label(mode.to_string())
|
||||
.on_commit(move |_| SelectToolMessage::SelectOptions(SelectOptionsUpdate::NestedSelectionBehavior(*mode)).into())
|
||||
MenuListEntry::new(format!("{mode:?}")).label(mode.to_string()).on_commit(move |_| {
|
||||
SelectToolMessage::SelectOptions {
|
||||
options: SelectOptionsUpdate::NestedSelectionBehavior(*mode),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
@ -277,7 +289,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Sele
|
|||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
|
||||
let mut redraw_reference_pivot = false;
|
||||
|
||||
if let ToolMessage::Select(SelectToolMessage::SelectOptions(ref option_update)) = message {
|
||||
if let ToolMessage::Select(SelectToolMessage::SelectOptions { options: ref option_update }) = message {
|
||||
match option_update {
|
||||
SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior) => {
|
||||
self.tool_data.nested_selection_behavior = *nested_selection_behavior;
|
||||
|
@ -323,6 +335,7 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for Sele
|
|||
PointerMove,
|
||||
Abort,
|
||||
EditLayer,
|
||||
EditLayerExec,
|
||||
Enter,
|
||||
);
|
||||
|
||||
|
@ -340,7 +353,7 @@ impl ToolTransition for SelectTool {
|
|||
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||
EventToMessageMap {
|
||||
tool_abort: Some(SelectToolMessage::Abort.into()),
|
||||
overlay_provider: Some(|overlay_context| SelectToolMessage::Overlays(overlay_context).into()),
|
||||
overlay_provider: Some(|context| SelectToolMessage::Overlays { context }.into()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
@ -589,7 +602,7 @@ impl Fsm for SelectToolFsmState {
|
|||
|
||||
let ToolMessage::Select(event) = event else { return self };
|
||||
match (self, event) {
|
||||
(_, SelectToolMessage::Overlays(mut overlay_context)) => {
|
||||
(_, SelectToolMessage::Overlays { context: mut overlay_context }) => {
|
||||
tool_data.snap_manager.draw_overlays(SnapData::new(document, input), &mut overlay_context);
|
||||
|
||||
let selected_layers_count = document.network_interface.selected_nodes().selected_unlocked_layers(&document.network_interface).count();
|
||||
|
@ -689,32 +702,28 @@ impl Fsm for SelectToolFsmState {
|
|||
// Measure with Alt held down
|
||||
// TODO: Don't use `Key::Alt` directly, instead take it as a variable from the input mappings list like in all other places
|
||||
if overlay_context.visibility_settings.quick_measurement() && !matches!(self, Self::ResizingBounds { .. }) && input.keyboard.get(Key::Alt as usize) {
|
||||
// Get all selected layers and compute their viewport-aligned AABB
|
||||
let selected_bounds_viewport = document
|
||||
// Compute document-space bounding box (AABB) of all selected visible & unlocked layers
|
||||
let selected_bounds_doc_space = document
|
||||
.network_interface
|
||||
.selected_nodes()
|
||||
.selected_visible_and_unlocked_layers(&document.network_interface)
|
||||
// Exclude layers that are artboards
|
||||
.filter(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))
|
||||
.filter_map(|layer| {
|
||||
// Get the layer's bounding box in its local space
|
||||
let local_bounds = document.metadata().bounding_box_with_transform(layer, DAffine2::IDENTITY)?;
|
||||
// Transform the bounds directly to viewport space
|
||||
let viewport_quad = document.metadata().transform_to_viewport(layer) * Quad::from_box(local_bounds);
|
||||
// Convert the quad to an AABB in viewport space
|
||||
Some(Rect::from_box(viewport_quad.bounding_box()))
|
||||
})
|
||||
// For each remaining layer, try to get its document-space bounding box and convert it to a Rect
|
||||
.filter_map(|layer| document.metadata().bounding_box_document(layer).map(Rect::from_box))
|
||||
// Combine all individual bounding boxes into one overall bounding box that contains all selected layers
|
||||
.reduce(Rect::combine_bounds);
|
||||
|
||||
// Get the hovered layer's viewport-aligned AABB
|
||||
let hovered_bounds_viewport = document.metadata().bounding_box_with_transform(layer, DAffine2::IDENTITY).map(|bounds| {
|
||||
let viewport_quad = document.metadata().transform_to_viewport(layer) * Quad::from_box(bounds);
|
||||
Rect::from_box(viewport_quad.bounding_box())
|
||||
});
|
||||
// Compute document-space bounding box (AABB) of the currently hovered layer
|
||||
let hovered_bounds_doc_space = document.metadata().bounding_box_document(layer);
|
||||
|
||||
// Use the viewport-aligned AABBs for measurement
|
||||
if let (Some(selected_bounds), Some(hovered_bounds)) = (selected_bounds_viewport, hovered_bounds_viewport) {
|
||||
// Since we're already in viewport space, use identity transform
|
||||
measure::overlay(selected_bounds, hovered_bounds, DAffine2::IDENTITY, DAffine2::IDENTITY, &mut overlay_context);
|
||||
// If both selected and hovered bounds exist, overlay measurement lines
|
||||
if let (Some(selected_bounds), Some(hovered_bounds)) = (selected_bounds_doc_space, hovered_bounds_doc_space.map(Rect::from_box)) {
|
||||
// Both `selected_bounds` and `hovered_bounds` are in document space.
|
||||
// To correctly render overlay lines in the UI (which is in viewport space), we need to transform both rectangles from document to viewport space.
|
||||
// Therefore, we pass `document_to_viewport` as both the `transform` and `document_to_viewport` parameters.
|
||||
let document_to_viewport = document.metadata().document_to_viewport;
|
||||
measure::overlay(selected_bounds, hovered_bounds, document_to_viewport, document_to_viewport, &mut overlay_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -985,14 +994,19 @@ impl Fsm for SelectToolFsmState {
|
|||
self
|
||||
}
|
||||
(_, SelectToolMessage::EditLayer) => {
|
||||
// Edit the clicked layer
|
||||
responses.add(DeferMessage::AfterGraphRun {
|
||||
messages: vec![SelectToolMessage::EditLayerExec.into()],
|
||||
});
|
||||
|
||||
self
|
||||
}
|
||||
(_, SelectToolMessage::EditLayerExec) => {
|
||||
if let Some(intersect) = document.click(input) {
|
||||
match tool_data.nested_selection_behavior {
|
||||
NestedSelectionBehavior::Shallowest => edit_layer_shallowest_manipulation(document, intersect, responses),
|
||||
NestedSelectionBehavior::Deepest => edit_layer_deepest_manipulation(intersect, &document.network_interface, responses),
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
(
|
||||
|
@ -1139,7 +1153,7 @@ impl Fsm for SelectToolFsmState {
|
|||
deepest,
|
||||
remove,
|
||||
},
|
||||
SelectToolMessage::PointerMove(modifier_keys),
|
||||
SelectToolMessage::PointerMove { modifier_keys },
|
||||
) => {
|
||||
if !has_dragged {
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
|
@ -1184,8 +1198,8 @@ impl Fsm for SelectToolFsmState {
|
|||
|
||||
// Auto-panning
|
||||
let messages = [
|
||||
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(),
|
||||
SelectToolMessage::PointerMove(modifier_keys).into(),
|
||||
SelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),
|
||||
SelectToolMessage::PointerMove { modifier_keys }.into(),
|
||||
];
|
||||
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
|
||||
|
||||
|
@ -1197,7 +1211,7 @@ impl Fsm for SelectToolFsmState {
|
|||
remove,
|
||||
}
|
||||
}
|
||||
(SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove(modifier_keys)) => {
|
||||
(SelectToolFsmState::ResizingBounds, SelectToolMessage::PointerMove { modifier_keys }) => {
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
resize_bounds(
|
||||
document,
|
||||
|
@ -1212,14 +1226,14 @@ impl Fsm for SelectToolFsmState {
|
|||
ToolType::Select,
|
||||
);
|
||||
let messages = [
|
||||
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(),
|
||||
SelectToolMessage::PointerMove(modifier_keys).into(),
|
||||
SelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),
|
||||
SelectToolMessage::PointerMove { modifier_keys }.into(),
|
||||
];
|
||||
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
|
||||
}
|
||||
SelectToolFsmState::ResizingBounds
|
||||
}
|
||||
(SelectToolFsmState::SkewingBounds { skew }, SelectToolMessage::PointerMove(_)) => {
|
||||
(SelectToolFsmState::SkewingBounds { skew }, SelectToolMessage::PointerMove { .. }) => {
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
skew_bounds(
|
||||
document,
|
||||
|
@ -1233,7 +1247,7 @@ impl Fsm for SelectToolFsmState {
|
|||
}
|
||||
SelectToolFsmState::SkewingBounds { skew }
|
||||
}
|
||||
(SelectToolFsmState::RotatingBounds, SelectToolMessage::PointerMove(_)) => {
|
||||
(SelectToolFsmState::RotatingBounds, SelectToolMessage::PointerMove { .. }) => {
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
rotate_bounds(
|
||||
document,
|
||||
|
@ -1249,7 +1263,7 @@ impl Fsm for SelectToolFsmState {
|
|||
|
||||
SelectToolFsmState::RotatingBounds
|
||||
}
|
||||
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove(modifier_keys)) => {
|
||||
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove { modifier_keys }) => {
|
||||
let mouse_position = input.mouse.position;
|
||||
let snapped_mouse_position = mouse_position;
|
||||
|
||||
|
@ -1259,14 +1273,14 @@ impl Fsm for SelectToolFsmState {
|
|||
|
||||
// Auto-panning
|
||||
let messages = [
|
||||
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(),
|
||||
SelectToolMessage::PointerMove(modifier_keys).into(),
|
||||
SelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),
|
||||
SelectToolMessage::PointerMove { modifier_keys }.into(),
|
||||
];
|
||||
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
|
||||
|
||||
SelectToolFsmState::DraggingPivot
|
||||
}
|
||||
(SelectToolFsmState::Drawing { selection_shape, has_drawn }, SelectToolMessage::PointerMove(modifier_keys)) => {
|
||||
(SelectToolFsmState::Drawing { selection_shape, has_drawn }, SelectToolMessage::PointerMove { modifier_keys }) => {
|
||||
if !has_drawn {
|
||||
responses.add(ToolMessage::UpdateHints);
|
||||
}
|
||||
|
@ -1280,14 +1294,14 @@ impl Fsm for SelectToolFsmState {
|
|||
|
||||
// Auto-panning
|
||||
let messages = [
|
||||
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(),
|
||||
SelectToolMessage::PointerMove(modifier_keys).into(),
|
||||
SelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),
|
||||
SelectToolMessage::PointerMove { modifier_keys }.into(),
|
||||
];
|
||||
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
|
||||
|
||||
SelectToolFsmState::Drawing { selection_shape, has_drawn: true }
|
||||
}
|
||||
(SelectToolFsmState::Ready { .. }, SelectToolMessage::PointerMove(_)) => {
|
||||
(SelectToolFsmState::Ready { .. }, SelectToolMessage::PointerMove { .. }) => {
|
||||
let dragging_bounds = tool_data
|
||||
.bounding_box_manager
|
||||
.as_mut()
|
||||
|
@ -1323,7 +1337,7 @@ impl Fsm for SelectToolFsmState {
|
|||
deepest,
|
||||
remove,
|
||||
},
|
||||
SelectToolMessage::PointerOutsideViewport(_),
|
||||
SelectToolMessage::PointerOutsideViewport { .. },
|
||||
) => {
|
||||
// Auto-panning
|
||||
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
|
||||
|
@ -1339,7 +1353,7 @@ impl Fsm for SelectToolFsmState {
|
|||
remove,
|
||||
}
|
||||
}
|
||||
(SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. }, SelectToolMessage::PointerOutsideViewport(_)) => {
|
||||
(SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. }, SelectToolMessage::PointerOutsideViewport { .. }) => {
|
||||
// Auto-panning
|
||||
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
|
@ -1350,13 +1364,13 @@ impl Fsm for SelectToolFsmState {
|
|||
|
||||
self
|
||||
}
|
||||
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerOutsideViewport(_)) => {
|
||||
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerOutsideViewport { .. }) => {
|
||||
// Auto-panning
|
||||
let _ = tool_data.auto_panning.shift_viewport(input, responses);
|
||||
|
||||
self
|
||||
}
|
||||
(SelectToolFsmState::Drawing { .. }, SelectToolMessage::PointerOutsideViewport(_)) => {
|
||||
(SelectToolFsmState::Drawing { .. }, SelectToolMessage::PointerOutsideViewport { .. }) => {
|
||||
// Auto-panning
|
||||
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
|
||||
tool_data.drag_start += shift;
|
||||
|
@ -1364,11 +1378,11 @@ impl Fsm for SelectToolFsmState {
|
|||
|
||||
self
|
||||
}
|
||||
(state, SelectToolMessage::PointerOutsideViewport(modifier_keys)) => {
|
||||
(state, SelectToolMessage::PointerOutsideViewport { modifier_keys }) => {
|
||||
// Auto-panning
|
||||
let messages = [
|
||||
SelectToolMessage::PointerOutsideViewport(modifier_keys.clone()).into(),
|
||||
SelectToolMessage::PointerMove(modifier_keys).into(),
|
||||
SelectToolMessage::PointerOutsideViewport { modifier_keys: modifier_keys.clone() }.into(),
|
||||
SelectToolMessage::PointerMove { modifier_keys }.into(),
|
||||
];
|
||||
tool_data.auto_panning.stop(&messages, responses);
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ use graphene_std::renderer::Quad;
|
|||
use graphene_std::vector::misc::{ArcType, GridType};
|
||||
use std::vec;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, ExtractField)]
|
||||
pub struct ShapeTool {
|
||||
fsm_state: ShapeToolFsmState,
|
||||
tool_data: ShapeToolData,
|
||||
|
@ -74,18 +74,18 @@ pub enum ShapeOptionsUpdate {
|
|||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
pub enum ShapeToolMessage {
|
||||
// Standard messages
|
||||
Overlays(OverlayContext),
|
||||
Overlays { context: OverlayContext },
|
||||
Abort,
|
||||
WorkingColorChanged,
|
||||
|
||||
// Tool-specific messages
|
||||
DragStart,
|
||||
DragStop,
|
||||
HideShapeTypeWidget(bool),
|
||||
PointerMove(ShapeToolModifierKey),
|
||||
PointerOutsideViewport(ShapeToolModifierKey),
|
||||
UpdateOptions(ShapeOptionsUpdate),
|
||||
SetShape(ShapeType),
|
||||
HideShapeTypeWidget { hide: bool },
|
||||
PointerMove { modifier: ShapeToolModifierKey },
|
||||
PointerOutsideViewport { modifier: ShapeToolModifierKey },
|
||||
UpdateOptions { options: ShapeOptionsUpdate },
|
||||
SetShape { shape: ShapeType },
|
||||
|
||||
IncreaseSides,
|
||||
DecreaseSides,
|
||||
|
@ -100,42 +100,71 @@ fn create_sides_widget(vertices: u32) -> WidgetHolder {
|
|||
.min(3.)
|
||||
.max(1000.)
|
||||
.mode(NumberInputMode::Increment)
|
||||
.on_update(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(number_input.value.unwrap() as u32)).into())
|
||||
.on_update(|number_input: &NumberInput| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::Vertices(number_input.value.unwrap() as u32),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder()
|
||||
}
|
||||
|
||||
fn create_shape_option_widget(shape_type: ShapeType) -> WidgetHolder {
|
||||
let entries = vec![vec![
|
||||
MenuListEntry::new("Polygon")
|
||||
.label("Polygon")
|
||||
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Polygon)).into()),
|
||||
MenuListEntry::new("Star")
|
||||
.label("Star")
|
||||
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Star)).into()),
|
||||
MenuListEntry::new("Circle")
|
||||
.label("Circle")
|
||||
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Circle)).into()),
|
||||
MenuListEntry::new("Arc")
|
||||
.label("Arc")
|
||||
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Arc)).into()),
|
||||
MenuListEntry::new("Grid")
|
||||
.label("Grid")
|
||||
.on_commit(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(ShapeType::Grid)).into()),
|
||||
MenuListEntry::new("Polygon").label("Polygon").on_commit(move |_| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::ShapeType(ShapeType::Polygon),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
MenuListEntry::new("Star").label("Star").on_commit(move |_| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::ShapeType(ShapeType::Star),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
MenuListEntry::new("Circle").label("Circle").on_commit(move |_| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::ShapeType(ShapeType::Circle),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
MenuListEntry::new("Arc").label("Arc").on_commit(move |_| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::ShapeType(ShapeType::Arc),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
MenuListEntry::new("Grid").label("Grid").on_commit(move |_| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::ShapeType(ShapeType::Grid),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
]];
|
||||
DropdownInput::new(entries).selected_index(Some(shape_type as u32)).widget_holder()
|
||||
}
|
||||
|
||||
fn create_arc_type_widget(arc_type: ArcType) -> WidgetHolder {
|
||||
let entries = vec![
|
||||
RadioEntryData::new("Open")
|
||||
.label("Open")
|
||||
.on_update(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ArcType(ArcType::Open)).into()),
|
||||
RadioEntryData::new("Closed")
|
||||
.label("Closed")
|
||||
.on_update(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ArcType(ArcType::Closed)).into()),
|
||||
RadioEntryData::new("Pie")
|
||||
.label("Pie")
|
||||
.on_update(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ArcType(ArcType::PieSlice)).into()),
|
||||
RadioEntryData::new("Open").label("Open").on_update(move |_| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::ArcType(ArcType::Open),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
RadioEntryData::new("Closed").label("Closed").on_update(move |_| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::ArcType(ArcType::Closed),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
RadioEntryData::new("Pie").label("Pie").on_update(move |_| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::ArcType(ArcType::PieSlice),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
];
|
||||
RadioInput::new(entries).selected_index(Some(arc_type as u32)).widget_holder()
|
||||
}
|
||||
|
@ -146,18 +175,29 @@ fn create_weight_widget(line_weight: f64) -> WidgetHolder {
|
|||
.label("Weight")
|
||||
.min(0.)
|
||||
.max((1_u64 << f64::MANTISSA_DIGITS) as f64)
|
||||
.on_update(|number_input: &NumberInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::LineWeight(number_input.value.unwrap())).into())
|
||||
.on_update(|number_input: &NumberInput| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::LineWeight(number_input.value.unwrap()),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
.widget_holder()
|
||||
}
|
||||
|
||||
fn create_grid_type_widget(grid_type: GridType) -> WidgetHolder {
|
||||
let entries = vec![
|
||||
RadioEntryData::new("Rectangular")
|
||||
.label("Rectangular")
|
||||
.on_update(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::GridType(GridType::Rectangular)).into()),
|
||||
RadioEntryData::new("Isometric")
|
||||
.label("Isometric")
|
||||
.on_update(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::GridType(GridType::Isometric)).into()),
|
||||
RadioEntryData::new("Rectangular").label("Rectangular").on_update(move |_| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::GridType(GridType::Rectangular),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
RadioEntryData::new("Isometric").label("Isometric").on_update(move |_| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::GridType(GridType::Isometric),
|
||||
}
|
||||
.into()
|
||||
}),
|
||||
];
|
||||
RadioInput::new(entries).selected_index(Some(grid_type as u32)).widget_holder()
|
||||
}
|
||||
|
@ -190,9 +230,26 @@ impl LayoutHolder for ShapeTool {
|
|||
widgets.append(&mut self.options.fill.create_widgets(
|
||||
"Fill",
|
||||
true,
|
||||
|_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColorType(color_type.clone())).into()),
|
||||
|color: &ColorInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(),
|
||||
|_| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::FillColor(None),
|
||||
}
|
||||
.into()
|
||||
},
|
||||
|color_type: ToolColorType| {
|
||||
WidgetCallback::new(move |_| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::FillColorType(color_type.clone()),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
},
|
||||
|color: &ColorInput| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::FillColor(color.value.as_solid().map(|color| color.to_linear_srgb())),
|
||||
}
|
||||
.into()
|
||||
},
|
||||
));
|
||||
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
|
@ -201,9 +258,26 @@ impl LayoutHolder for ShapeTool {
|
|||
widgets.append(&mut self.options.stroke.create_widgets(
|
||||
"Stroke",
|
||||
true,
|
||||
|_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColor(None)).into(),
|
||||
|color_type: ToolColorType| WidgetCallback::new(move |_| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColorType(color_type.clone())).into()),
|
||||
|color: &ColorInput| ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb()))).into(),
|
||||
|_| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::StrokeColor(None),
|
||||
}
|
||||
.into()
|
||||
},
|
||||
|color_type: ToolColorType| {
|
||||
WidgetCallback::new(move |_| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::StrokeColorType(color_type.clone()),
|
||||
}
|
||||
.into()
|
||||
})
|
||||
},
|
||||
|color: &ColorInput| {
|
||||
ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::StrokeColor(color.value.as_solid().map(|color| color.to_linear_srgb())),
|
||||
}
|
||||
.into()
|
||||
},
|
||||
));
|
||||
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
|
||||
widgets.push(create_weight_widget(self.options.line_weight));
|
||||
|
@ -212,13 +286,14 @@ impl LayoutHolder for ShapeTool {
|
|||
}
|
||||
}
|
||||
|
||||
#[message_handler_data]
|
||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for ShapeTool {
|
||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
|
||||
let ToolMessage::Shape(ShapeToolMessage::UpdateOptions(action)) = message else {
|
||||
let ToolMessage::Shape(ShapeToolMessage::UpdateOptions { options }) = message else {
|
||||
self.fsm_state.process_event(message, &mut self.tool_data, context, &self.options, responses, true);
|
||||
return;
|
||||
};
|
||||
match action {
|
||||
match options {
|
||||
ShapeOptionsUpdate::FillColor(color) => {
|
||||
self.options.fill.custom_color = color;
|
||||
self.options.fill.color_type = ToolColorType::Custom;
|
||||
|
@ -309,7 +384,7 @@ impl ToolMetadata for ShapeTool {
|
|||
impl ToolTransition for ShapeTool {
|
||||
fn event_to_message_map(&self) -> EventToMessageMap {
|
||||
EventToMessageMap {
|
||||
overlay_provider: Some(|overlay_context| ShapeToolMessage::Overlays(overlay_context).into()),
|
||||
overlay_provider: Some(|context| ShapeToolMessage::Overlays { context }.into()),
|
||||
tool_abort: Some(ShapeToolMessage::Abort.into()),
|
||||
working_color_changed: Some(ShapeToolMessage::WorkingColorChanged.into()),
|
||||
..Default::default()
|
||||
|
@ -426,7 +501,7 @@ impl Fsm for ShapeToolFsmState {
|
|||
let ToolMessage::Shape(event) = event else { return self };
|
||||
|
||||
match (self, event) {
|
||||
(_, ShapeToolMessage::Overlays(mut overlay_context)) => {
|
||||
(_, ShapeToolMessage::Overlays { context: mut overlay_context }) => {
|
||||
let mouse_position = tool_data
|
||||
.data
|
||||
.snap_manager
|
||||
|
@ -508,11 +583,15 @@ impl Fsm for ShapeToolFsmState {
|
|||
self
|
||||
}
|
||||
(ShapeToolFsmState::Ready(_), ShapeToolMessage::IncreaseSides) => {
|
||||
responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices(tool_options.vertices + 1)));
|
||||
responses.add(ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::Vertices(tool_options.vertices + 1),
|
||||
});
|
||||
self
|
||||
}
|
||||
(ShapeToolFsmState::Ready(_), ShapeToolMessage::DecreaseSides) => {
|
||||
responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::Vertices((tool_options.vertices - 1).max(3))));
|
||||
responses.add(ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::Vertices((tool_options.vertices - 1).max(3)),
|
||||
});
|
||||
self
|
||||
}
|
||||
(
|
||||
|
@ -579,7 +658,9 @@ impl Fsm for ShapeToolFsmState {
|
|||
tool_data.cursor = cursor;
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
|
||||
// Send a PointerMove message to refresh the cursor icon
|
||||
responses.add(ShapeToolMessage::PointerMove(ShapeToolData::shape_tool_modifier_keys()));
|
||||
responses.add(ShapeToolMessage::PointerMove {
|
||||
modifier: ShapeToolData::shape_tool_modifier_keys(),
|
||||
});
|
||||
|
||||
responses.add(DocumentMessage::StartTransaction);
|
||||
return ShapeToolFsmState::ModifyingGizmo;
|
||||
|
@ -607,7 +688,9 @@ impl Fsm for ShapeToolFsmState {
|
|||
let cursor = tool_data.transform_cage_mouse_icon(input);
|
||||
tool_data.cursor = cursor;
|
||||
responses.add(FrontendMessage::UpdateMouseCursor { cursor });
|
||||
responses.add(ShapeToolMessage::PointerMove(ShapeToolData::shape_tool_modifier_keys()));
|
||||
responses.add(ShapeToolMessage::PointerMove {
|
||||
modifier: ShapeToolData::shape_tool_modifier_keys(),
|
||||
});
|
||||
};
|
||||
|
||||
match (resize, rotate, skew) {
|
||||
|
@ -688,7 +771,7 @@ impl Fsm for ShapeToolFsmState {
|
|||
|
||||
ShapeToolFsmState::Drawing(tool_data.current_shape)
|
||||
}
|
||||
(ShapeToolFsmState::Drawing(shape), ShapeToolMessage::PointerMove(modifier)) => {
|
||||
(ShapeToolFsmState::Drawing(shape), ShapeToolMessage::PointerMove { modifier }) => {
|
||||
let Some(layer) = tool_data.data.layer else {
|
||||
return ShapeToolFsmState::Ready(shape);
|
||||
};
|
||||
|
@ -705,33 +788,33 @@ impl Fsm for ShapeToolFsmState {
|
|||
}
|
||||
|
||||
// Auto-panning
|
||||
let messages = [ShapeToolMessage::PointerOutsideViewport(modifier).into(), ShapeToolMessage::PointerMove(modifier).into()];
|
||||
let messages = [ShapeToolMessage::PointerOutsideViewport { modifier }.into(), ShapeToolMessage::PointerMove { modifier }.into()];
|
||||
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
|
||||
|
||||
self
|
||||
}
|
||||
(ShapeToolFsmState::DraggingLineEndpoints, ShapeToolMessage::PointerMove(modifier)) => {
|
||||
(ShapeToolFsmState::DraggingLineEndpoints, ShapeToolMessage::PointerMove { modifier }) => {
|
||||
let Some(layer) = tool_data.line_data.editing_layer else {
|
||||
return ShapeToolFsmState::Ready(tool_data.current_shape);
|
||||
};
|
||||
|
||||
Line::update_shape(document, input, layer, tool_data, modifier, responses);
|
||||
// Auto-panning
|
||||
let messages = [ShapeToolMessage::PointerOutsideViewport(modifier).into(), ShapeToolMessage::PointerMove(modifier).into()];
|
||||
let messages = [ShapeToolMessage::PointerOutsideViewport { modifier }.into(), ShapeToolMessage::PointerMove { modifier }.into()];
|
||||
tool_data.auto_panning.setup_by_mouse_position(input, &messages, responses);
|
||||
|
||||
self
|
||||
}
|
||||
(ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::PointerMove(..)) => {
|
||||
(ShapeToolFsmState::ModifyingGizmo, ShapeToolMessage::PointerMove { .. }) => {
|
||||
tool_data.gizmo_manager.handle_update(tool_data.data.viewport_drag_start(document), document, input, responses);
|
||||
|
||||
responses.add(OverlaysMessage::Draw);
|
||||
|
||||
ShapeToolFsmState::ModifyingGizmo
|
||||
}
|
||||
(ShapeToolFsmState::ResizingBounds, ShapeToolMessage::PointerMove(modifier)) => {
|
||||
(ShapeToolFsmState::ResizingBounds, ShapeToolMessage::PointerMove { modifier }) => {
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
let messages = [ShapeToolMessage::PointerOutsideViewport(modifier).into(), ShapeToolMessage::PointerMove(modifier).into()];
|
||||
let messages = [ShapeToolMessage::PointerOutsideViewport { modifier }.into(), ShapeToolMessage::PointerMove { modifier }.into()];
|
||||
resize_bounds(
|
||||
document,
|
||||
responses,
|
||||
|
@ -750,7 +833,7 @@ impl Fsm for ShapeToolFsmState {
|
|||
responses.add(OverlaysMessage::Draw);
|
||||
ShapeToolFsmState::ResizingBounds
|
||||
}
|
||||
(ShapeToolFsmState::RotatingBounds, ShapeToolMessage::PointerMove(modifier)) => {
|
||||
(ShapeToolFsmState::RotatingBounds, ShapeToolMessage::PointerMove { modifier }) => {
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
rotate_bounds(
|
||||
document,
|
||||
|
@ -766,7 +849,7 @@ impl Fsm for ShapeToolFsmState {
|
|||
|
||||
ShapeToolFsmState::RotatingBounds
|
||||
}
|
||||
(ShapeToolFsmState::SkewingBounds { skew }, ShapeToolMessage::PointerMove(_)) => {
|
||||
(ShapeToolFsmState::SkewingBounds { skew }, ShapeToolMessage::PointerMove { .. }) => {
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
skew_bounds(
|
||||
document,
|
||||
|
@ -782,7 +865,7 @@ impl Fsm for ShapeToolFsmState {
|
|||
ShapeToolFsmState::SkewingBounds { skew }
|
||||
}
|
||||
|
||||
(_, ShapeToolMessage::PointerMove(_)) => {
|
||||
(_, ShapeToolMessage::PointerMove { .. }) => {
|
||||
let dragging_bounds = tool_data
|
||||
.bounding_box_manager
|
||||
.as_mut()
|
||||
|
@ -804,7 +887,7 @@ impl Fsm for ShapeToolFsmState {
|
|||
responses.add(OverlaysMessage::Draw);
|
||||
self
|
||||
}
|
||||
(ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::SkewingBounds { .. }, ShapeToolMessage::PointerOutsideViewport(_)) => {
|
||||
(ShapeToolFsmState::ResizingBounds | ShapeToolFsmState::SkewingBounds { .. }, ShapeToolMessage::PointerOutsideViewport { .. }) => {
|
||||
// Auto-panning
|
||||
if let Some(shift) = tool_data.auto_panning.shift_viewport(input, responses) {
|
||||
if let Some(bounds) = &mut tool_data.bounding_box_manager {
|
||||
|
@ -815,7 +898,7 @@ impl Fsm for ShapeToolFsmState {
|
|||
|
||||
self
|
||||
}
|
||||
(ShapeToolFsmState::Ready(_), ShapeToolMessage::PointerOutsideViewport(..)) => self,
|
||||
(ShapeToolFsmState::Ready(_), ShapeToolMessage::PointerOutsideViewport { .. }) => self,
|
||||
(_, ShapeToolMessage::PointerOutsideViewport { .. }) => {
|
||||
// Auto-panning
|
||||
let _ = tool_data.auto_panning.shift_viewport(input, responses);
|
||||
|
@ -870,21 +953,22 @@ impl Fsm for ShapeToolFsmState {
|
|||
ShapeToolFsmState::Ready(tool_data.current_shape)
|
||||
}
|
||||
(_, ShapeToolMessage::WorkingColorChanged) => {
|
||||
responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::WorkingColors(
|
||||
Some(global_tool_data.primary_color),
|
||||
Some(global_tool_data.secondary_color),
|
||||
)));
|
||||
responses.add(ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::WorkingColors(Some(global_tool_data.primary_color), Some(global_tool_data.secondary_color)),
|
||||
});
|
||||
self
|
||||
}
|
||||
(_, ShapeToolMessage::SetShape(shape)) => {
|
||||
(_, ShapeToolMessage::SetShape { shape }) => {
|
||||
responses.add(DocumentMessage::AbortTransaction);
|
||||
tool_data.data.cleanup(responses);
|
||||
tool_data.current_shape = shape;
|
||||
|
||||
responses.add(ShapeToolMessage::UpdateOptions(ShapeOptionsUpdate::ShapeType(shape)));
|
||||
responses.add(ShapeToolMessage::UpdateOptions {
|
||||
options: ShapeOptionsUpdate::ShapeType(shape),
|
||||
});
|
||||
ShapeToolFsmState::Ready(shape)
|
||||
}
|
||||
(_, ShapeToolMessage::HideShapeTypeWidget(hide)) => {
|
||||
(_, ShapeToolMessage::HideShapeTypeWidget { hide }) => {
|
||||
tool_data.hide_shape_option_widget = hide;
|
||||
responses.add(ToolMessage::RefreshToolOptions);
|
||||
self
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue