mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-27 11:48:12 +08:00
Refactor the TypeScript data flow for full type safety and auto-generation of Rust types (#3865)
* Migrate Specta to Tsify to auto-generate messages.ts, working except colors and widgets * Adopt the generated FillColor/Color/GradientStops * Fix widget typing * Separate WidgetGroup enum variants into wrapper structs * Small rename * Simplify widgets further * Clean up message type references * Switch type imports to the auto-generated file * Remove lowercase serde rename * Fix FillChoice deserialization * Fix small regression from #3837 * Improve type safety * Make WidgetSpan type-safe * More cleanup and type safety * More type safety * More type safety * Get the rest to type-check without errors; improve widget builder macro to have optional icons; improve Svelte 5 configs * Cargo fmt * Fix imports * Update outdated readme info * Fix lint command rename references * Fix typos * One more typos fix * Remove unnecessary dep: prefix from the edited Cargo.toml files * Remove excess parts from Cargo.toml * Fix compiling on desktop * Revert "Remove excess parts from Cargo.toml" This reverts commit 6b711117b3a5d5d8a3ee20f36a43bc74930b7c82. * Update dev docs with simpler, more accurate instructions
This commit is contained in:
@@ -117,7 +117,7 @@ jobs:
|
|||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
run: |
|
run: |
|
||||||
cd frontend
|
cd frontend
|
||||||
npm run lint
|
npm run check
|
||||||
|
|
||||||
# Run the Rust tests on the self-hosted native runner
|
# Run the Rust tests on the self-hosted native runner
|
||||||
test:
|
test:
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd website
|
cd website
|
||||||
npm ci
|
npm ci
|
||||||
npm run lint
|
npm run check
|
||||||
zola --config config.toml build --minify
|
zola --config config.toml build --minify
|
||||||
|
|
||||||
- name: 📤 Publish to Cloudflare Pages
|
- name: 📤 Publish to Cloudflare Pages
|
||||||
|
|||||||
Vendored
+1
-14
@@ -39,25 +39,12 @@
|
|||||||
"rust-analyzer.check.command": "clippy",
|
"rust-analyzer.check.command": "clippy",
|
||||||
"rust-analyzer.cargo.allTargets": false,
|
"rust-analyzer.cargo.allTargets": false,
|
||||||
"rust-analyzer.procMacro.ignored": {
|
"rust-analyzer.procMacro.ignored": {
|
||||||
"serde_derive": ["Serialize", "Deserialize"],
|
"serde_derive": ["Serialize", "Deserialize"]
|
||||||
"specta_macros": ["Type"] // Disabled because of: https://github.com/specta-rs/specta/issues/387
|
|
||||||
},
|
},
|
||||||
// ESLint config
|
// ESLint config
|
||||||
"eslint.format.enable": true,
|
"eslint.format.enable": true,
|
||||||
"eslint.workingDirectories": ["./frontend", "./website"],
|
"eslint.workingDirectories": ["./frontend", "./website"],
|
||||||
"eslint.validate": ["javascript", "typescript", "svelte"],
|
"eslint.validate": ["javascript", "typescript", "svelte"],
|
||||||
// Svelte config
|
|
||||||
"svelte.plugin.svelte.compilerWarnings": {
|
|
||||||
"css-unused-selector": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
|
||||||
"vite-plugin-svelte-css-no-scopable-elements": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
|
||||||
"a11y-no-static-element-interactions": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
|
||||||
"a11y-no-noninteractive-element-interactions": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
|
||||||
"a11y-click-events-have-key-events": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
|
||||||
"a11y_consider_explicit_label": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
|
||||||
"a11y_click_events_have_key_events": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
|
||||||
"a11y_no_noninteractive_element_interactions": "ignore", // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
|
||||||
"a11y_no_static_element_interactions": "ignore" // NOTICE: Keep this list in sync with the list in `frontend/vite.config.ts`
|
|
||||||
},
|
|
||||||
// Git Graph config
|
// Git Graph config
|
||||||
"git-graph.repository.fetchAndPrune": true,
|
"git-graph.repository.fetchAndPrune": true,
|
||||||
"git-graph.repository.showRemoteHeads": false,
|
"git-graph.repository.showRemoteHeads": false,
|
||||||
|
|||||||
Generated
+67
-39
@@ -2,12 +2,6 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "Inflector"
|
|
||||||
version = "0.11.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ab_glyph"
|
name = "ab_glyph"
|
||||||
version = "0.2.31"
|
version = "0.2.31"
|
||||||
@@ -1157,9 +1151,10 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"skrifa 0.40.0",
|
"skrifa 0.40.0",
|
||||||
"specta",
|
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tsify",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2290,9 +2285,9 @@ dependencies = [
|
|||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"specta",
|
|
||||||
"text-nodes",
|
"text-nodes",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tsify",
|
||||||
"url",
|
"url",
|
||||||
"vector-nodes",
|
"vector-nodes",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -2348,7 +2343,8 @@ dependencies = [
|
|||||||
"raster-types",
|
"raster-types",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"specta",
|
"tsify",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2414,8 +2410,8 @@ dependencies = [
|
|||||||
"raster-types",
|
"raster-types",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"specta",
|
|
||||||
"vector-types",
|
"vector-types",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2538,11 +2534,12 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"preprocessor",
|
"preprocessor",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_bytes",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"specta",
|
|
||||||
"spin",
|
"spin",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tsify",
|
||||||
"usvg",
|
"usvg",
|
||||||
"vello",
|
"vello",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -3740,8 +3737,9 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
"num_enum",
|
"num_enum",
|
||||||
"serde",
|
"serde",
|
||||||
"specta",
|
|
||||||
"spirv-std",
|
"spirv-std",
|
||||||
|
"tsify",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4295,8 +4293,9 @@ dependencies = [
|
|||||||
"node-macro",
|
"node-macro",
|
||||||
"path-bool",
|
"path-bool",
|
||||||
"serde",
|
"serde",
|
||||||
"specta",
|
"tsify",
|
||||||
"vector-types",
|
"vector-types",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4919,10 +4918,11 @@ dependencies = [
|
|||||||
"raster-nodes-shaders",
|
"raster-nodes-shaders",
|
||||||
"raster-types",
|
"raster-types",
|
||||||
"serde",
|
"serde",
|
||||||
"specta",
|
|
||||||
"spirv-std",
|
"spirv-std",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tsify",
|
||||||
"vector-types",
|
"vector-types",
|
||||||
|
"wasm-bindgen",
|
||||||
"wgpu-executor",
|
"wgpu-executor",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -4955,7 +4955,8 @@ dependencies = [
|
|||||||
"node-macro",
|
"node-macro",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"specta",
|
"tsify",
|
||||||
|
"wasm-bindgen",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -5639,6 +5640,16 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_bytes"
|
||||||
|
version = "0.11.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_core"
|
name = "serde_core"
|
||||||
version = "1.0.228"
|
version = "1.0.228"
|
||||||
@@ -5659,6 +5670,17 @@ dependencies = [
|
|||||||
"syn 2.0.106",
|
"syn 2.0.106",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive_internals"
|
||||||
|
version = "0.29.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.106",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.143"
|
version = "1.0.143"
|
||||||
@@ -5905,29 +5927,6 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "specta"
|
|
||||||
version = "2.0.0-rc.22"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ab7f01e9310a820edd31c80fde3cae445295adde21a3f9416517d7d65015b971"
|
|
||||||
dependencies = [
|
|
||||||
"glam",
|
|
||||||
"specta-macros",
|
|
||||||
"thiserror 1.0.69",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "specta-macros"
|
|
||||||
version = "2.0.0-rc.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c0074b9e30ed84c6924eb63ad8d2fe71cdc82628525d84b1fcb1f2fd40676517"
|
|
||||||
dependencies = [
|
|
||||||
"Inflector",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.106",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "spin"
|
name = "spin"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@@ -6245,7 +6244,9 @@ dependencies = [
|
|||||||
"parley",
|
"parley",
|
||||||
"serde",
|
"serde",
|
||||||
"skrifa 0.40.0",
|
"skrifa 0.40.0",
|
||||||
|
"tsify",
|
||||||
"vector-types",
|
"vector-types",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6665,6 +6666,30 @@ version = "0.2.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tsify"
|
||||||
|
version = "0.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2ec91b85e6c6592ed28636cb1dd1fac377ecbbeb170ff1d79f97aac5e38926d"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde-wasm-bindgen",
|
||||||
|
"tsify-macros",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tsify-macros"
|
||||||
|
version = "0.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9fc2c44dc9fe4baf55b88e032621b7a11b215a1f0a7de8d0aa04367207d915bc"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"serde_derive_internals",
|
||||||
|
"syn 2.0.106",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ttf-parser"
|
name = "ttf-parser"
|
||||||
version = "0.25.1"
|
version = "0.25.1"
|
||||||
@@ -6909,7 +6934,9 @@ dependencies = [
|
|||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
"serde",
|
"serde",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tsify",
|
||||||
"vector-types",
|
"vector-types",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6931,8 +6958,9 @@ dependencies = [
|
|||||||
"polycool",
|
"polycool",
|
||||||
"rustc-hash 2.1.1",
|
"rustc-hash 2.1.1",
|
||||||
"serde",
|
"serde",
|
||||||
"specta",
|
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
|
"tsify",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
+2
-6
@@ -98,6 +98,7 @@ rustc-hash = "2.0"
|
|||||||
bytemuck = { version = "1.13", features = ["derive", "min_const_generics"] }
|
bytemuck = { version = "1.13", features = ["derive", "min_const_generics"] }
|
||||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
serde_bytes = "0.11"
|
||||||
serde-wasm-bindgen = "0.6"
|
serde-wasm-bindgen = "0.6"
|
||||||
reqwest = { version = "0.13", features = ["blocking", "json"] }
|
reqwest = { version = "0.13", features = ["blocking", "json"] }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
@@ -176,11 +177,7 @@ fern = { version = "0.7", features = ["colored"] }
|
|||||||
num_enum = { version = "0.7", default-features = false }
|
num_enum = { version = "0.7", default-features = false }
|
||||||
num-derive = "0.4"
|
num-derive = "0.4"
|
||||||
num-traits = { version = "0.2", default-features = false, features = ["libm"] }
|
num-traits = { version = "0.2", default-features = false, features = ["libm"] }
|
||||||
specta = { version = "2.0.0-rc.22", features = [
|
tsify = { version = "0.5", default-features = false, features = ["js"] }
|
||||||
"glam",
|
|
||||||
"derive",
|
|
||||||
# "typescript",
|
|
||||||
] }
|
|
||||||
syn = { version = "2.0", default-features = false, features = [
|
syn = { version = "2.0", default-features = false, features = [
|
||||||
"full",
|
"full",
|
||||||
"derive",
|
"derive",
|
||||||
@@ -230,7 +227,6 @@ graphite-proc-macros = { opt-level = 1 }
|
|||||||
image = { opt-level = 2 }
|
image = { opt-level = 2 }
|
||||||
rustc-hash = { opt-level = 3 }
|
rustc-hash = { opt-level = 3 }
|
||||||
serde_derive = { opt-level = 1 }
|
serde_derive = { opt-level = 1 }
|
||||||
specta-macros = { opt-level = 1 }
|
|
||||||
syn = { opt-level = 1 }
|
syn = { opt-level = 1 }
|
||||||
node-macro = { opt-level = 2 }
|
node-macro = { opt-level = 2 }
|
||||||
|
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ allow-git = []
|
|||||||
|
|
||||||
[sources.allow-org]
|
[sources.allow-org]
|
||||||
# 1 or more github.com organizations to allow git sources for
|
# 1 or more github.com organizations to allow git sources for
|
||||||
github = ["linebender", "Rust-GPU", "specta-rs"]
|
github = ["linebender", "Rust-GPU"]
|
||||||
# 1 or more gitlab.com organizations to allow git sources for
|
# 1 or more gitlab.com organizations to allow git sources for
|
||||||
#gitlab = [""]
|
#gitlab = [""]
|
||||||
# 1 or more bitbucket.org organizations to allow git sources for
|
# 1 or more bitbucket.org organizations to allow git sources for
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
FrontendMessage::TriggerSaveDocument { document_id, name, path, content } => {
|
FrontendMessage::TriggerSaveDocument { document_id, name, path, content } => {
|
||||||
|
let content = content.into_vec();
|
||||||
if let Some(path) = path {
|
if let Some(path) = path {
|
||||||
dispatcher.respond(DesktopFrontendMessage::WriteFile { path, content });
|
dispatcher.respond(DesktopFrontendMessage::WriteFile { path, content });
|
||||||
} else {
|
} else {
|
||||||
@@ -42,6 +43,7 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
FrontendMessage::TriggerSaveFile { name, content } => {
|
FrontendMessage::TriggerSaveFile { name, content } => {
|
||||||
|
let content = content.into_vec();
|
||||||
dispatcher.respond(DesktopFrontendMessage::SaveFileDialog {
|
dispatcher.respond(DesktopFrontendMessage::SaveFileDialog {
|
||||||
title: "Save File".to_string(),
|
title: "Save File".to_string(),
|
||||||
default_filename: name,
|
default_filename: name,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ pub(crate) mod menu {
|
|||||||
use graphite_editor::messages::input_mapper::utility_types::input_keyboard::{Key, LabeledKeyOrMouseMotion, LabeledShortcut};
|
use graphite_editor::messages::input_mapper::utility_types::input_keyboard::{Key, LabeledKeyOrMouseMotion, LabeledShortcut};
|
||||||
use graphite_editor::messages::input_mapper::utility_types::misc::ActionShortcut;
|
use graphite_editor::messages::input_mapper::utility_types::misc::ActionShortcut;
|
||||||
use graphite_editor::messages::layout::LayoutMessage;
|
use graphite_editor::messages::layout::LayoutMessage;
|
||||||
use graphite_editor::messages::tool::tool_messages::tool_prelude::{Layout, LayoutGroup, LayoutTarget, MenuListEntry, Widget, WidgetId};
|
use graphite_editor::messages::tool::tool_messages::tool_prelude::{Layout, LayoutGroup, LayoutTarget, MenuListEntry, Widget, WidgetId, WidgetRow};
|
||||||
|
|
||||||
use crate::messages::{EditorMessage, KeyCode, MenuItem, Modifiers, Shortcut};
|
use crate::messages::{EditorMessage, KeyCode, MenuItem, Modifiers, Shortcut};
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ pub(crate) mod menu {
|
|||||||
[layout_group] => layout_group,
|
[layout_group] => layout_group,
|
||||||
_ => panic!("Menu bar layout is supposed to have exactly one layout group"),
|
_ => panic!("Menu bar layout is supposed to have exactly one layout group"),
|
||||||
};
|
};
|
||||||
let LayoutGroup::Row { widgets } = layout_group else {
|
let LayoutGroup::Row(WidgetRow { widgets }) = layout_group else {
|
||||||
panic!("Menu bar layout group is supposed to be a row");
|
panic!("Menu bar layout group is supposed to be a row");
|
||||||
};
|
};
|
||||||
widgets
|
widgets
|
||||||
@@ -88,8 +88,8 @@ pub(crate) mod menu {
|
|||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
match icon.as_str() {
|
match icon.as_deref() {
|
||||||
"CheckboxChecked" => {
|
Some("CheckboxChecked") => {
|
||||||
return MenuItem::Checkbox {
|
return MenuItem::Checkbox {
|
||||||
id,
|
id,
|
||||||
text,
|
text,
|
||||||
@@ -98,7 +98,7 @@ pub(crate) mod menu {
|
|||||||
checked: true,
|
checked: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
"CheckboxUnchecked" => {
|
Some("CheckboxUnchecked") => {
|
||||||
return MenuItem::Checkbox {
|
return MenuItem::Checkbox {
|
||||||
id,
|
id,
|
||||||
text,
|
text,
|
||||||
|
|||||||
+2
-1
@@ -29,12 +29,13 @@ log = { workspace = true }
|
|||||||
bitflags = { workspace = true }
|
bitflags = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
|
serde_bytes = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
kurbo = { workspace = true }
|
kurbo = { workspace = true }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
glam = { workspace = true }
|
glam = { workspace = true }
|
||||||
derivative = { workspace = true }
|
derivative = { workspace = true }
|
||||||
specta = { workspace = true }
|
tsify = { workspace = true }
|
||||||
dyn-any = { workspace = true }
|
dyn-any = { workspace = true }
|
||||||
num_enum = { workspace = true }
|
num_enum = { workspace = true }
|
||||||
usvg = { workspace = true }
|
usvg = { workspace = true }
|
||||||
|
|||||||
@@ -596,7 +596,7 @@ mod test {
|
|||||||
} = response
|
} = response
|
||||||
{
|
{
|
||||||
if let DiffUpdate::Layout(sub_layout) = &diff[0].new_value {
|
if let DiffUpdate::Layout(sub_layout) = &diff[0].new_value {
|
||||||
if let LayoutGroup::Row { widgets } = &sub_layout.0[0] {
|
if let LayoutGroup::Row(WidgetRow { widgets }) = &sub_layout.0[0] {
|
||||||
if let Widget::TextLabel(TextLabel { value, .. }) = &*widgets[0].widget {
|
if let Widget::TextLabel(TextLabel { value, .. }) = &*widgets[0].widget {
|
||||||
print_problem_to_terminal_on_failure(value);
|
print_problem_to_terminal_on_failure(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
/// Running this test will generate a `types.ts` file at the root of the repo,
|
|
||||||
/// containing every type annotated with `specta::Type`
|
|
||||||
// #[cfg(all(test, feature = "specta-export"))]
|
|
||||||
#[ignore]
|
|
||||||
#[test]
|
|
||||||
fn generate_ts_types() {
|
|
||||||
// TODO: Un-comment this out when we figure out how to reenable the "typescript` Specta feature flag
|
|
||||||
|
|
||||||
// use crate::messages::prelude::FrontendMessage;
|
|
||||||
// use specta::ts::{export_named_datatype, BigIntExportBehavior, ExportConfig};
|
|
||||||
// use specta::{NamedType, TypeMap};
|
|
||||||
// use std::fs::File;
|
|
||||||
// use std::io::Write;
|
|
||||||
|
|
||||||
// let config = ExportConfig::new().bigint(BigIntExportBehavior::Number);
|
|
||||||
|
|
||||||
// let mut type_map = TypeMap::default();
|
|
||||||
|
|
||||||
// let datatype = FrontendMessage::definition_named_data_type(&mut type_map);
|
|
||||||
|
|
||||||
// let mut export = String::new();
|
|
||||||
|
|
||||||
// export += &export_named_datatype(&config, &datatype, &type_map).unwrap();
|
|
||||||
|
|
||||||
// type_map
|
|
||||||
// .iter()
|
|
||||||
// .map(|(_, v)| v)
|
|
||||||
// .flat_map(|v| export_named_datatype(&config, v, &type_map))
|
|
||||||
// .for_each(|e| export += &format!("\n\n{e}"));
|
|
||||||
|
|
||||||
// let mut file = File::create("../types.ts").unwrap();
|
|
||||||
|
|
||||||
// write!(file, "{export}").ok();
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,6 @@ extern crate graphite_proc_macros;
|
|||||||
// `macro_use` puts these macros into scope for all descendant code files
|
// `macro_use` puts these macros into scope for all descendant code files
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
mod generate_ts_types;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
|
|||||||
@@ -11,37 +11,46 @@ impl MessageHandler<AppWindowMessage, ()> for AppWindowMessageHandler {
|
|||||||
fn process_message(&mut self, message: AppWindowMessage, responses: &mut std::collections::VecDeque<Message>, _: ()) {
|
fn process_message(&mut self, message: AppWindowMessage, responses: &mut std::collections::VecDeque<Message>, _: ()) {
|
||||||
match message {
|
match message {
|
||||||
AppWindowMessage::PointerLock => {
|
AppWindowMessage::PointerLock => {
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
responses.add(FrontendMessage::WindowPointerLock);
|
responses.add(FrontendMessage::WindowPointerLock);
|
||||||
}
|
}
|
||||||
AppWindowMessage::PointerLockMove { x, y } => {
|
AppWindowMessage::PointerLockMove { x, y } => {
|
||||||
responses.add(FrontendMessage::WindowPointerLockMove { x, y });
|
responses.add(FrontendMessage::WindowPointerLockMove { position: (x, y) });
|
||||||
}
|
}
|
||||||
AppWindowMessage::Close => {
|
AppWindowMessage::Close => {
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
responses.add(FrontendMessage::WindowClose);
|
responses.add(FrontendMessage::WindowClose);
|
||||||
}
|
}
|
||||||
AppWindowMessage::Minimize => {
|
AppWindowMessage::Minimize => {
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
responses.add(FrontendMessage::WindowMinimize);
|
responses.add(FrontendMessage::WindowMinimize);
|
||||||
}
|
}
|
||||||
AppWindowMessage::Maximize => {
|
AppWindowMessage::Maximize => {
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
responses.add(FrontendMessage::WindowMaximize);
|
responses.add(FrontendMessage::WindowMaximize);
|
||||||
}
|
}
|
||||||
AppWindowMessage::Fullscreen => {
|
AppWindowMessage::Fullscreen => {
|
||||||
responses.add(FrontendMessage::WindowFullscreen);
|
responses.add(FrontendMessage::WindowFullscreen);
|
||||||
}
|
}
|
||||||
AppWindowMessage::Drag => {
|
AppWindowMessage::Drag => {
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
responses.add(FrontendMessage::WindowDrag);
|
responses.add(FrontendMessage::WindowDrag);
|
||||||
}
|
}
|
||||||
AppWindowMessage::Hide => {
|
AppWindowMessage::Hide => {
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
responses.add(FrontendMessage::WindowHide);
|
responses.add(FrontendMessage::WindowHide);
|
||||||
}
|
}
|
||||||
AppWindowMessage::HideOthers => {
|
AppWindowMessage::HideOthers => {
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
responses.add(FrontendMessage::WindowHideOthers);
|
responses.add(FrontendMessage::WindowHideOthers);
|
||||||
}
|
}
|
||||||
AppWindowMessage::ShowAll => {
|
AppWindowMessage::ShowAll => {
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
responses.add(FrontendMessage::WindowShowAll);
|
responses.add(FrontendMessage::WindowShowAll);
|
||||||
}
|
}
|
||||||
AppWindowMessage::Restart => {
|
AppWindowMessage::Restart => {
|
||||||
responses.add(PortfolioMessage::AutoSaveAllDocuments);
|
responses.add(PortfolioMessage::AutoSaveAllDocuments);
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
responses.add(FrontendMessage::WindowRestart);
|
responses.add(FrontendMessage::WindowRestart);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,7 +66,8 @@ impl MessageHandler<AppWindowMessage, ()> for AppWindowMessageHandler {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum AppWindowPlatform {
|
pub enum AppWindowPlatform {
|
||||||
#[default]
|
#[default]
|
||||||
Web,
|
Web,
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ impl DialogLayoutHolder for ExportDialogMessageHandler {
|
|||||||
TextButton::new("Cancel").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(),
|
TextButton::new("Cancel").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(),
|
||||||
];
|
];
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,10 +170,10 @@ impl LayoutHolder for ExportDialogMessageHandler {
|
|||||||
];
|
];
|
||||||
|
|
||||||
Layout(vec![
|
Layout(vec![
|
||||||
LayoutGroup::Row { widgets: export_type },
|
LayoutGroup::row(export_type),
|
||||||
LayoutGroup::Row { widgets: resolution },
|
LayoutGroup::row(resolution),
|
||||||
LayoutGroup::Row { widgets: export_area },
|
LayoutGroup::row(export_area),
|
||||||
LayoutGroup::Row { widgets: transparent_background },
|
LayoutGroup::row(transparent_background),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -71,7 +71,7 @@ impl DialogLayoutHolder for NewDocumentDialogMessageHandler {
|
|||||||
TextButton::new("Cancel").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(),
|
TextButton::new("Cancel").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(),
|
||||||
];
|
];
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +122,6 @@ impl LayoutHolder for NewDocumentDialogMessageHandler {
|
|||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
];
|
];
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets: name }, LayoutGroup::Row { widgets: infinite }, LayoutGroup::Row { widgets: scale }])
|
Layout(vec![LayoutGroup::row(name), LayoutGroup::row(infinite), LayoutGroup::row(scale)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -389,7 +389,7 @@ impl PreferencesDialogMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Layout(rows.into_iter().map(|r| LayoutGroup::Row { widgets: r }).collect())
|
Layout(rows.into_iter().map(|r| LayoutGroup::row(r)).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_layout(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget, preferences: &PreferencesMessageHandler) {
|
pub fn send_layout(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget, preferences: &PreferencesMessageHandler) {
|
||||||
@@ -416,7 +416,7 @@ impl PreferencesDialogMessageHandler {
|
|||||||
TextButton::new("Reset to Defaults").on_update(|_| PreferencesMessage::ResetToDefaults.into()).widget_instance(),
|
TextButton::new("Reset to Defaults").on_update(|_| PreferencesMessage::ResetToDefaults.into()).widget_instance(),
|
||||||
];
|
];
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_layout_buttons(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget) {
|
fn send_layout_buttons(&self, responses: &mut VecDeque<Message>, layout_target: LayoutTarget) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ impl DialogLayoutHolder for AboutGraphiteDialog {
|
|||||||
fn layout_buttons(&self) -> Layout {
|
fn layout_buttons(&self) -> Layout {
|
||||||
let widgets = vec![TextButton::new("OK").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()];
|
let widgets = vec![TextButton::new("OK").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()];
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout_column_2(&self) -> Layout {
|
fn layout_column_2(&self) -> Layout {
|
||||||
@@ -29,7 +29,7 @@ impl DialogLayoutHolder for AboutGraphiteDialog {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(icon, label, url)| {
|
.map(|(icon, label, url)| {
|
||||||
TextButton::new(label)
|
TextButton::new(label)
|
||||||
.icon(Some(icon.into()))
|
.icon(icon)
|
||||||
.flush(true)
|
.flush(true)
|
||||||
.on_update(|_| FrontendMessage::TriggerVisitLink { url: url.into() }.into())
|
.on_update(|_| FrontendMessage::TriggerVisitLink { url: url.into() }.into())
|
||||||
.widget_instance()
|
.widget_instance()
|
||||||
@@ -40,7 +40,7 @@ impl DialogLayoutHolder for AboutGraphiteDialog {
|
|||||||
let localized_commit_year = self.localized_commit_year.clone();
|
let localized_commit_year = self.localized_commit_year.clone();
|
||||||
widgets.push(
|
widgets.push(
|
||||||
TextButton::new("Licenses")
|
TextButton::new("Licenses")
|
||||||
.icon(Some("License".into()))
|
.icon("License")
|
||||||
.flush(true)
|
.flush(true)
|
||||||
.on_update(move |_| {
|
.on_update(move |_| {
|
||||||
DialogMessage::RequestLicensesDialogWithLocalizedCommitDate {
|
DialogMessage::RequestLicensesDialogWithLocalizedCommitDate {
|
||||||
@@ -51,22 +51,16 @@ impl DialogLayoutHolder for AboutGraphiteDialog {
|
|||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Column { widgets }])
|
Layout(vec![LayoutGroup::column(widgets)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutHolder for AboutGraphiteDialog {
|
impl LayoutHolder for AboutGraphiteDialog {
|
||||||
fn layout(&self) -> Layout {
|
fn layout(&self) -> Layout {
|
||||||
Layout(vec![
|
Layout(vec![
|
||||||
LayoutGroup::Row {
|
LayoutGroup::row(vec![TextLabel::new("About this release").bold(true).widget_instance()]),
|
||||||
widgets: vec![TextLabel::new("About this release").bold(true).widget_instance()],
|
LayoutGroup::row(vec![TextLabel::new(commit_info_localized(&self.localized_commit_date)).multiline(true).widget_instance()]),
|
||||||
},
|
LayoutGroup::row(vec![TextLabel::new(format!("Copyright © {} Graphite contributors", self.localized_commit_year)).widget_instance()]),
|
||||||
LayoutGroup::Row {
|
|
||||||
widgets: vec![TextLabel::new(commit_info_localized(&self.localized_commit_date)).multiline(true).widget_instance()],
|
|
||||||
},
|
|
||||||
LayoutGroup::Row {
|
|
||||||
widgets: vec![TextLabel::new(format!("Copyright © {} Graphite contributors", self.localized_commit_year)).widget_instance()],
|
|
||||||
},
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ impl DialogLayoutHolder for CloseAllDocumentsDialog {
|
|||||||
TextButton::new("Cancel").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(),
|
TextButton::new("Cancel").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(),
|
||||||
];
|
];
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,12 +33,8 @@ impl LayoutHolder for CloseAllDocumentsDialog {
|
|||||||
let unsaved_list = "• ".to_string() + &self.unsaved_document_names.join("\n• ");
|
let unsaved_list = "• ".to_string() + &self.unsaved_document_names.join("\n• ");
|
||||||
|
|
||||||
Layout(vec![
|
Layout(vec![
|
||||||
LayoutGroup::Row {
|
LayoutGroup::row(vec![TextLabel::new("Save documents before closing them?").bold(true).multiline(true).widget_instance()]),
|
||||||
widgets: vec![TextLabel::new("Save documents before closing them?").bold(true).multiline(true).widget_instance()],
|
LayoutGroup::row(vec![TextLabel::new(format!("Documents with unsaved changes:\n{unsaved_list}")).multiline(true).widget_instance()]),
|
||||||
},
|
|
||||||
LayoutGroup::Row {
|
|
||||||
widgets: vec![TextLabel::new(format!("Documents with unsaved changes:\n{unsaved_list}")).multiline(true).widget_instance()],
|
|
||||||
},
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ impl DialogLayoutHolder for CloseDocumentDialog {
|
|||||||
TextButton::new("Cancel").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(),
|
TextButton::new("Cancel").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(),
|
||||||
];
|
];
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,12 +52,8 @@ impl LayoutHolder for CloseDocumentDialog {
|
|||||||
let break_lines = if self.document_name.len() > max_one_line_length { '\n' } else { ' ' };
|
let break_lines = if self.document_name.len() > max_one_line_length { '\n' } else { ' ' };
|
||||||
|
|
||||||
Layout(vec![
|
Layout(vec![
|
||||||
LayoutGroup::Row {
|
LayoutGroup::row(vec![TextLabel::new("Save document before closing it?").bold(true).widget_instance()]),
|
||||||
widgets: vec![TextLabel::new("Save document before closing it?").bold(true).widget_instance()],
|
LayoutGroup::row(vec![TextLabel::new(format!("\"{name}{ellipsis}\"{break_lines}has unsaved changes")).multiline(true).widget_instance()]),
|
||||||
},
|
|
||||||
LayoutGroup::Row {
|
|
||||||
widgets: vec![TextLabel::new(format!("\"{name}{ellipsis}\"{break_lines}has unsaved changes")).multiline(true).widget_instance()],
|
|
||||||
},
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ impl DialogLayoutHolder for ConfirmRestartDialog {
|
|||||||
TextButton::new("Later").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(),
|
TextButton::new("Later").on_update(|_| FrontendMessage::DialogClose.into()).widget_instance(),
|
||||||
];
|
];
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,27 +33,23 @@ impl LayoutHolder for ConfirmRestartDialog {
|
|||||||
let changed_settings = "• ".to_string() + &self.changed_settings.join("\n• ");
|
let changed_settings = "• ".to_string() + &self.changed_settings.join("\n• ");
|
||||||
|
|
||||||
Layout(vec![
|
Layout(vec![
|
||||||
LayoutGroup::Row {
|
LayoutGroup::row(vec![TextLabel::new("Restart to apply changes?").bold(true).multiline(true).widget_instance()]),
|
||||||
widgets: vec![TextLabel::new("Restart to apply changes?").bold(true).multiline(true).widget_instance()],
|
LayoutGroup::row(vec![
|
||||||
},
|
TextLabel::new(
|
||||||
LayoutGroup::Row {
|
format!(
|
||||||
widgets: vec![
|
"
|
||||||
TextLabel::new(
|
|
||||||
format!(
|
|
||||||
"
|
|
||||||
Settings that only take effect on next launch:\n\
|
Settings that only take effect on next launch:\n\
|
||||||
{changed_settings}\n\
|
{changed_settings}\n\
|
||||||
\n\
|
\n\
|
||||||
This only takes a few seconds. Open documents,\n\
|
This only takes a few seconds. Open documents,\n\
|
||||||
even unsaved ones, will be automatically restored.
|
even unsaved ones, will be automatically restored.
|
||||||
"
|
"
|
||||||
)
|
|
||||||
.trim(),
|
|
||||||
)
|
)
|
||||||
.multiline(true)
|
.trim(),
|
||||||
.widget_instance(),
|
)
|
||||||
],
|
.multiline(true)
|
||||||
},
|
.widget_instance(),
|
||||||
|
]),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ impl DialogLayoutHolder for DemoArtworkDialog {
|
|||||||
fn layout_buttons(&self) -> Layout {
|
fn layout_buttons(&self) -> Layout {
|
||||||
let widgets = vec![TextButton::new("Close").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()];
|
let widgets = vec![TextButton::new("Close").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()];
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ impl LayoutHolder for DemoArtworkDialog {
|
|||||||
|
|
||||||
let images = chunk
|
let images = chunk
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, thumbnail, filename)| ImageButton::new(*thumbnail).width(Some("256px".into())).on_update(|_| make_dialog(name, filename)).widget_instance())
|
.map(|(name, thumbnail, filename)| ImageButton::new(*thumbnail).width("256px").on_update(|_| make_dialog(name, filename)).widget_instance())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let buttons = chunk
|
let buttons = chunk
|
||||||
@@ -54,7 +54,7 @@ impl LayoutHolder for DemoArtworkDialog {
|
|||||||
.map(|(name, _, filename)| TextButton::new(*name).min_width(256).flush(true).on_update(|_| make_dialog(name, filename)).widget_instance())
|
.map(|(name, _, filename)| TextButton::new(*name).min_width(256).flush(true).on_update(|_| make_dialog(name, filename)).widget_instance())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
vec![LayoutGroup::Row { widgets: images }, LayoutGroup::Row { widgets: buttons }, LayoutGroup::Row { widgets: vec![] }]
|
vec![LayoutGroup::row(images), LayoutGroup::row(buttons), LayoutGroup::row(vec![])]
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let _ = rows_of_images_with_buttons.pop();
|
let _ = rows_of_images_with_buttons.pop();
|
||||||
|
|||||||
@@ -14,19 +14,15 @@ impl DialogLayoutHolder for ErrorDialog {
|
|||||||
fn layout_buttons(&self) -> Layout {
|
fn layout_buttons(&self) -> Layout {
|
||||||
let widgets = vec![TextButton::new("OK").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()];
|
let widgets = vec![TextButton::new("OK").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()];
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutHolder for ErrorDialog {
|
impl LayoutHolder for ErrorDialog {
|
||||||
fn layout(&self) -> Layout {
|
fn layout(&self) -> Layout {
|
||||||
Layout(vec![
|
Layout(vec![
|
||||||
LayoutGroup::Row {
|
LayoutGroup::row(vec![TextLabel::new(&self.title).bold(true).widget_instance()]),
|
||||||
widgets: vec![TextLabel::new(&self.title).bold(true).widget_instance()],
|
LayoutGroup::row(vec![TextLabel::new(&self.description).multiline(true).widget_instance()]),
|
||||||
},
|
|
||||||
LayoutGroup::Row {
|
|
||||||
widgets: vec![TextLabel::new(&self.description).multiline(true).widget_instance()],
|
|
||||||
},
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ impl DialogLayoutHolder for LicensesDialog {
|
|||||||
fn layout_buttons(&self) -> Layout {
|
fn layout_buttons(&self) -> Layout {
|
||||||
let widgets = vec![TextButton::new("OK").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()];
|
let widgets = vec![TextButton::new("OK").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()];
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layout_column_2(&self) -> Layout {
|
fn layout_column_2(&self) -> Layout {
|
||||||
@@ -34,10 +34,10 @@ impl DialogLayoutHolder for LicensesDialog {
|
|||||||
];
|
];
|
||||||
let widgets = button_definitions
|
let widgets = button_definitions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&(icon, label, message_factory)| TextButton::new(label).icon(Some((icon).into())).flush(true).on_update(move |_| message_factory()).widget_instance())
|
.map(|&(icon, label, message_factory)| TextButton::new(label).icon(icon).flush(true).on_update(move |_| message_factory()).widget_instance())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Column { widgets }])
|
Layout(vec![LayoutGroup::column(widgets)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,12 +56,8 @@ impl LayoutHolder for LicensesDialog {
|
|||||||
let description = description.trim();
|
let description = description.trim();
|
||||||
|
|
||||||
Layout(vec![
|
Layout(vec![
|
||||||
LayoutGroup::Row {
|
LayoutGroup::row(vec![TextLabel::new("Graphite is free, open source software").bold(true).widget_instance()]),
|
||||||
widgets: vec![TextLabel::new("Graphite is free, open source software").bold(true).widget_instance()],
|
LayoutGroup::row(vec![TextLabel::new(description).multiline(true).widget_instance()]),
|
||||||
},
|
|
||||||
LayoutGroup::Row {
|
|
||||||
widgets: vec![TextLabel::new(description).multiline(true).widget_instance()],
|
|
||||||
},
|
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ impl DialogLayoutHolder for LicensesThirdPartyDialog {
|
|||||||
fn layout_buttons(&self) -> Layout {
|
fn layout_buttons(&self) -> Layout {
|
||||||
let widgets = vec![TextButton::new("OK").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()];
|
let widgets = vec![TextButton::new("OK").emphasized(true).on_update(|_| FrontendMessage::DialogClose.into()).widget_instance()];
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,14 +31,12 @@ impl LayoutHolder for LicensesThirdPartyDialog {
|
|||||||
// Two characters (one before, one after) the sequence of underscore characters, plus one additional column to provide a space between the text and the scrollbar
|
// Two characters (one before, one after) the sequence of underscore characters, plus one additional column to provide a space between the text and the scrollbar
|
||||||
let non_wrapping_column_width = license_text.split('\n').map(|line| line.chars().filter(|&c| c == '_').count() as u32).max().unwrap_or(0) + 2 + 1;
|
let non_wrapping_column_width = license_text.split('\n').map(|line| line.chars().filter(|&c| c == '_').count() as u32).max().unwrap_or(0) + 2 + 1;
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row {
|
Layout(vec![LayoutGroup::row(vec![
|
||||||
widgets: vec![
|
TextLabel::new(license_text)
|
||||||
TextLabel::new(license_text)
|
.monospace(true)
|
||||||
.monospace(true)
|
.multiline(true)
|
||||||
.multiline(true)
|
.min_width_characters(non_wrapping_column_width)
|
||||||
.min_width_characters(non_wrapping_column_width)
|
.widget_instance(),
|
||||||
.widget_instance(),
|
])])
|
||||||
],
|
|
||||||
}])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
|
use super::IconName;
|
||||||
use super::utility_types::{DocumentDetails, MouseCursorIcon, OpenDocument};
|
use super::utility_types::{DocumentDetails, MouseCursorIcon, OpenDocument};
|
||||||
use crate::messages::app_window::app_window_message_handler::AppWindowPlatform;
|
use crate::messages::app_window::app_window_message_handler::AppWindowPlatform;
|
||||||
use crate::messages::frontend::utility_types::EyedropperPreviewImage;
|
use crate::messages::frontend::utility_types::EyedropperPreviewImage;
|
||||||
use crate::messages::input_mapper::utility_types::misc::ActionShortcut;
|
use crate::messages::input_mapper::utility_types::misc::ActionShortcut;
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::portfolio::document::node_graph::utility_types::{
|
use crate::messages::portfolio::document::node_graph::utility_types::{
|
||||||
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, NodeGraphErrorDiagnostic, Transform,
|
BoxSelection, ContextMenuInformation, FrontendClickTargets, FrontendGraphInput, FrontendGraphOutput, FrontendNode, FrontendNodeType, NodeGraphErrorDiagnostic,
|
||||||
};
|
};
|
||||||
use crate::messages::portfolio::document::utility_types::nodes::{LayerPanelEntry, LayerStructureEntry};
|
use crate::messages::portfolio::document::utility_types::nodes::{LayerPanelEntry, LayerStructureEntry};
|
||||||
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};
|
use crate::messages::portfolio::document::utility_types::wires::{WirePath, WirePathUpdate};
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use glam::IVec2;
|
use crate::messages::tool::tool_messages::eyedropper_tool::PrimarySecondary;
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graphene_std::raster::Image;
|
use graphene_std::raster::Image;
|
||||||
use graphene_std::raster::color::Color;
|
use graphene_std::raster::color::Color;
|
||||||
@@ -20,13 +21,14 @@ use std::path::PathBuf;
|
|||||||
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
|
||||||
|
|
||||||
#[impl_message(Message, Frontend)]
|
#[impl_message(Message, Frontend)]
|
||||||
#[derive(derivative::Derivative, Clone, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(derivative::Derivative, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
#[derivative(Debug, PartialEq)]
|
#[derivative(Debug, PartialEq)]
|
||||||
pub enum FrontendMessage {
|
pub enum FrontendMessage {
|
||||||
// Display prefix: make the frontend show something, like a dialog
|
// Display prefix: make the frontend show something, like a dialog
|
||||||
DisplayDialog {
|
DisplayDialog {
|
||||||
title: String,
|
title: String,
|
||||||
icon: String,
|
icon: IconName,
|
||||||
},
|
},
|
||||||
DialogClose,
|
DialogClose,
|
||||||
DisplayDialogPanic {
|
DisplayDialogPanic {
|
||||||
@@ -41,7 +43,7 @@ pub enum FrontendMessage {
|
|||||||
font_size: f64,
|
font_size: f64,
|
||||||
color: String,
|
color: String,
|
||||||
#[serde(rename = "fontData")]
|
#[serde(rename = "fontData")]
|
||||||
font_data: Vec<u8>,
|
font_data: serde_bytes::ByteBuf,
|
||||||
transform: [f64; 6],
|
transform: [f64; 6],
|
||||||
#[serde(rename = "maxWidth")]
|
#[serde(rename = "maxWidth")]
|
||||||
max_width: Option<f64>,
|
max_width: Option<f64>,
|
||||||
@@ -51,7 +53,7 @@ pub enum FrontendMessage {
|
|||||||
},
|
},
|
||||||
DisplayEditableTextboxUpdateFontData {
|
DisplayEditableTextboxUpdateFontData {
|
||||||
#[serde(rename = "fontData")]
|
#[serde(rename = "fontData")]
|
||||||
font_data: Vec<u8>,
|
font_data: serde_bytes::ByteBuf,
|
||||||
},
|
},
|
||||||
DisplayEditableTextboxTransform {
|
DisplayEditableTextboxTransform {
|
||||||
transform: [f64; 6],
|
transform: [f64; 6],
|
||||||
@@ -87,11 +89,11 @@ pub enum FrontendMessage {
|
|||||||
document_id: DocumentId,
|
document_id: DocumentId,
|
||||||
name: String,
|
name: String,
|
||||||
path: Option<PathBuf>,
|
path: Option<PathBuf>,
|
||||||
content: Vec<u8>,
|
content: serde_bytes::ByteBuf,
|
||||||
},
|
},
|
||||||
TriggerSaveFile {
|
TriggerSaveFile {
|
||||||
name: String,
|
name: String,
|
||||||
content: Vec<u8>,
|
content: serde_bytes::ByteBuf,
|
||||||
},
|
},
|
||||||
TriggerExportImage {
|
TriggerExportImage {
|
||||||
svg: String,
|
svg: String,
|
||||||
@@ -125,6 +127,7 @@ pub enum FrontendMessage {
|
|||||||
TriggerOpen,
|
TriggerOpen,
|
||||||
TriggerImport,
|
TriggerImport,
|
||||||
TriggerSavePreferences {
|
TriggerSavePreferences {
|
||||||
|
#[tsify(type = "unknown")]
|
||||||
preferences: PreferencesMessageHandler,
|
preferences: PreferencesMessageHandler,
|
||||||
},
|
},
|
||||||
TriggerSaveActiveDocument {
|
TriggerSaveActiveDocument {
|
||||||
@@ -152,9 +155,8 @@ pub enum FrontendMessage {
|
|||||||
document_id: DocumentId,
|
document_id: DocumentId,
|
||||||
},
|
},
|
||||||
UpdateGradientStopColorPickerPosition {
|
UpdateGradientStopColorPickerPosition {
|
||||||
color: Color,
|
color: Color, // TODO: Color (without `none`) -> Color (with `none`)
|
||||||
x: f64,
|
position: (f64, f64),
|
||||||
y: f64,
|
|
||||||
},
|
},
|
||||||
UpdateImportsExports {
|
UpdateImportsExports {
|
||||||
/// If the primary import is not visible, then it is None.
|
/// If the primary import is not visible, then it is None.
|
||||||
@@ -163,10 +165,10 @@ pub enum FrontendMessage {
|
|||||||
exports: Vec<Option<FrontendGraphInput>>,
|
exports: Vec<Option<FrontendGraphInput>>,
|
||||||
/// The primary import location.
|
/// The primary import location.
|
||||||
#[serde(rename = "importPosition")]
|
#[serde(rename = "importPosition")]
|
||||||
import_position: IVec2,
|
import_position: (i32, i32),
|
||||||
/// The primary export location.
|
/// The primary export location.
|
||||||
#[serde(rename = "exportPosition")]
|
#[serde(rename = "exportPosition")]
|
||||||
export_position: IVec2,
|
export_position: (i32, i32),
|
||||||
/// The document network does not have an add import or export button.
|
/// The document network does not have an add import or export button.
|
||||||
#[serde(rename = "addImportExport")]
|
#[serde(rename = "addImportExport")]
|
||||||
add_import_export: bool,
|
add_import_export: bool,
|
||||||
@@ -202,7 +204,7 @@ pub enum FrontendMessage {
|
|||||||
UpdateLayout {
|
UpdateLayout {
|
||||||
#[serde(rename = "layoutTarget")]
|
#[serde(rename = "layoutTarget")]
|
||||||
layout_target: LayoutTarget,
|
layout_target: LayoutTarget,
|
||||||
diff: Vec<WidgetDiff>,
|
diff: Vec<WidgetDiff>, // TODO: Align this with what's generated
|
||||||
},
|
},
|
||||||
UpdateImportReorderIndex {
|
UpdateImportReorderIndex {
|
||||||
#[serde(rename = "importIndex")]
|
#[serde(rename = "importIndex")]
|
||||||
@@ -223,6 +225,8 @@ pub enum FrontendMessage {
|
|||||||
UpdateDocumentArtwork {
|
UpdateDocumentArtwork {
|
||||||
svg: String,
|
svg: String,
|
||||||
},
|
},
|
||||||
|
// This message is intercepted before being sent to the frontend
|
||||||
|
#[serde(skip)]
|
||||||
UpdateImageData {
|
UpdateImageData {
|
||||||
image_data: Vec<(u64, Image<Color>)>,
|
image_data: Vec<(u64, Image<Color>)>,
|
||||||
},
|
},
|
||||||
@@ -253,7 +257,7 @@ pub enum FrontendMessage {
|
|||||||
#[serde(rename = "secondaryColor")]
|
#[serde(rename = "secondaryColor")]
|
||||||
secondary_color: String,
|
secondary_color: String,
|
||||||
#[serde(rename = "setColorChoice")]
|
#[serde(rename = "setColorChoice")]
|
||||||
set_color_choice: Option<String>,
|
set_color_choice: Option<PrimarySecondary>,
|
||||||
},
|
},
|
||||||
UpdateGraphFadeArtwork {
|
UpdateGraphFadeArtwork {
|
||||||
percentage: f64,
|
percentage: f64,
|
||||||
@@ -278,7 +282,8 @@ pub enum FrontendMessage {
|
|||||||
selected: Vec<NodeId>,
|
selected: Vec<NodeId>,
|
||||||
},
|
},
|
||||||
UpdateNodeGraphTransform {
|
UpdateNodeGraphTransform {
|
||||||
transform: Transform,
|
translation: (f64, f64),
|
||||||
|
scale: f64,
|
||||||
},
|
},
|
||||||
UpdateNodeThumbnail {
|
UpdateNodeThumbnail {
|
||||||
id: NodeId,
|
id: NodeId,
|
||||||
@@ -304,6 +309,7 @@ pub enum FrontendMessage {
|
|||||||
UpdateViewportHolePunch {
|
UpdateViewportHolePunch {
|
||||||
active: bool,
|
active: bool,
|
||||||
},
|
},
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
UpdateViewportPhysicalBounds {
|
UpdateViewportPhysicalBounds {
|
||||||
x: f64,
|
x: f64,
|
||||||
y: f64,
|
y: f64,
|
||||||
@@ -322,18 +328,26 @@ pub enum FrontendMessage {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Window prefix: cause the application window to do something
|
// Window prefix: cause the application window to do something
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
WindowPointerLock,
|
WindowPointerLock,
|
||||||
WindowPointerLockMove {
|
WindowPointerLockMove {
|
||||||
x: f64,
|
position: (f64, f64),
|
||||||
y: f64,
|
|
||||||
},
|
},
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
WindowClose,
|
WindowClose,
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
WindowMinimize,
|
WindowMinimize,
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
WindowMaximize,
|
WindowMaximize,
|
||||||
WindowFullscreen,
|
WindowFullscreen,
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
WindowDrag,
|
WindowDrag,
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
WindowHide,
|
WindowHide,
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
WindowHideOthers,
|
WindowHideOthers,
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
WindowShowAll,
|
WindowShowAll,
|
||||||
|
#[cfg(not(target_family = "wasm"))]
|
||||||
WindowRestart,
|
WindowRestart,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,3 +4,7 @@ pub mod utility_types;
|
|||||||
|
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use frontend_message::{FrontendMessage, FrontendMessageDiscriminant};
|
pub use frontend_message::{FrontendMessage, FrontendMessageDiscriminant};
|
||||||
|
|
||||||
|
// TODO: Make this an enum with the actual icon names, somehow derived from or tied to the frontend icon set.
|
||||||
|
// TODO: Then remove `#[widget_builder(string)]` from all icon fields.
|
||||||
|
pub type IconName = String;
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ use std::path::PathBuf;
|
|||||||
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct OpenDocument {
|
pub struct OpenDocument {
|
||||||
pub id: DocumentId,
|
pub id: DocumentId,
|
||||||
pub details: DocumentDetails,
|
pub details: DocumentDetails,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct DocumentDetails {
|
pub struct DocumentDetails {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub path: Option<PathBuf>,
|
pub path: Option<PathBuf>,
|
||||||
@@ -19,7 +21,8 @@ pub struct DocumentDetails {
|
|||||||
pub is_auto_saved: bool,
|
pub is_auto_saved: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum MouseCursorIcon {
|
pub enum MouseCursorIcon {
|
||||||
#[default]
|
#[default]
|
||||||
Default,
|
Default,
|
||||||
@@ -37,7 +40,8 @@ pub enum MouseCursorIcon {
|
|||||||
Rotate,
|
Rotate,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum FileType {
|
pub enum FileType {
|
||||||
#[default]
|
#[default]
|
||||||
Png,
|
Png,
|
||||||
@@ -55,7 +59,8 @@ impl FileType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum ExportBounds {
|
pub enum ExportBounds {
|
||||||
#[default]
|
#[default]
|
||||||
AllArtwork,
|
AllArtwork,
|
||||||
@@ -63,9 +68,10 @@ pub enum ExportBounds {
|
|||||||
Artboard(LayerNodeIdentifier),
|
Artboard(LayerNodeIdentifier),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct EyedropperPreviewImage {
|
pub struct EyedropperPreviewImage {
|
||||||
pub data: Vec<u8>,
|
pub data: serde_bytes::ByteBuf,
|
||||||
pub width: u32,
|
pub width: u32,
|
||||||
pub height: u32,
|
pub height: u32,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ bitflags! {
|
|||||||
// (although we ignore the shift key, so the user doesn't have to press `Ctrl Shift +` on a US keyboard), even if the keyboard layout
|
// (although we ignore the shift key, so the user doesn't have to press `Ctrl Shift +` on a US keyboard), even if the keyboard layout
|
||||||
// is for a different locale where the `+` key is somewhere entirely different, shifted or not. This would then also work for numpad `+`.
|
// is for a different locale where the `+` key is somewhere entirely different, shifted or not. This would then also work for numpad `+`.
|
||||||
#[impl_message(Message, InputMapperMessage, KeyDown)]
|
#[impl_message(Message, InputMapperMessage, KeyDown)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type, num_enum::TryFromPrimitive)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, num_enum::TryFromPrimitive)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum Key {
|
pub enum Key {
|
||||||
// Writing system keys
|
// Writing system keys
|
||||||
@@ -379,7 +380,8 @@ impl fmt::Display for KeysGroup {
|
|||||||
// LabeledKey
|
// LabeledKey
|
||||||
// ==========
|
// ==========
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct LabeledKey {
|
pub struct LabeledKey {
|
||||||
key: Key,
|
key: Key,
|
||||||
label: String,
|
label: String,
|
||||||
@@ -395,7 +397,8 @@ impl LabeledKey {
|
|||||||
// MouseMotion
|
// MouseMotion
|
||||||
// ===========
|
// ===========
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum MouseMotion {
|
pub enum MouseMotion {
|
||||||
None,
|
None,
|
||||||
Lmb,
|
Lmb,
|
||||||
@@ -415,7 +418,8 @@ pub enum MouseMotion {
|
|||||||
// LabeledKeyOrMouseMotion
|
// LabeledKeyOrMouseMotion
|
||||||
// =======================
|
// =======================
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum LabeledKeyOrMouseMotion {
|
pub enum LabeledKeyOrMouseMotion {
|
||||||
Key(LabeledKey),
|
Key(LabeledKey),
|
||||||
@@ -437,7 +441,8 @@ impl From<Key> for LabeledKeyOrMouseMotion {
|
|||||||
// LabeledShortcut
|
// LabeledShortcut
|
||||||
// ===============
|
// ===============
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct LabeledShortcut(pub Vec<LabeledKeyOrMouseMotion>);
|
pub struct LabeledShortcut(pub Vec<LabeledKeyOrMouseMotion>);
|
||||||
|
|
||||||
impl From<KeysGroup> for LabeledShortcut {
|
impl From<KeysGroup> for LabeledShortcut {
|
||||||
|
|||||||
@@ -110,7 +110,8 @@ bitflags! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, InputMapperMessage, DoubleClick)]
|
#[impl_message(Message, InputMapperMessage, DoubleClick)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type, num_enum::TryFromPrimitive)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, num_enum::TryFromPrimitive)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum MouseButton {
|
pub enum MouseButton {
|
||||||
Left,
|
Left,
|
||||||
|
|||||||
@@ -106,8 +106,10 @@ pub struct MappingEntry {
|
|||||||
pub disabled: bool,
|
pub disabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum ActionShortcut {
|
pub enum ActionShortcut {
|
||||||
|
#[serde(skip)]
|
||||||
Action(MessageDiscriminant),
|
Action(MessageDiscriminant),
|
||||||
#[serde(rename = "shortcut")]
|
#[serde(rename = "shortcut")]
|
||||||
Shortcut(LabeledShortcut),
|
Shortcut(LabeledShortcut),
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
|
use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use graphene_std::raster::color::Color;
|
use graphene_std::vector::style::FillChoice;
|
||||||
use graphene_std::vector::style::{FillChoice, GradientStop, GradientStops};
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -63,7 +62,7 @@ impl LayoutMessageHandler {
|
|||||||
while let Some((mut widget_path, layout_group)) = stack.pop() {
|
while let Some((mut widget_path, layout_group)) = stack.pop() {
|
||||||
match layout_group {
|
match layout_group {
|
||||||
// Check if any of the widgets in the current column or row have the correct id
|
// Check if any of the widgets in the current column or row have the correct id
|
||||||
LayoutGroup::Column { widgets } | LayoutGroup::Row { widgets } => {
|
LayoutGroup::Column(WidgetColumn { widgets }) | LayoutGroup::Row(WidgetRow { widgets }) => {
|
||||||
for (index, widget) in widgets.iter().enumerate() {
|
for (index, widget) in widgets.iter().enumerate() {
|
||||||
// Return if this is the correct ID
|
// Return if this is the correct ID
|
||||||
if widget.widget_id == widget_id {
|
if widget.widget_id == widget_id {
|
||||||
@@ -84,10 +83,10 @@ impl LayoutMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// A section contains more LayoutGroups which we add to the stack.
|
// A section contains more LayoutGroups which we add to the stack.
|
||||||
LayoutGroup::Section { layout, .. } => {
|
LayoutGroup::Section(WidgetSection { layout, .. }) => {
|
||||||
stack.extend(layout.0.iter().enumerate().map(|(index, val)| ([widget_path.as_slice(), &[index]].concat(), val)));
|
stack.extend(layout.0.iter().enumerate().map(|(index, val)| ([widget_path.as_slice(), &[index]].concat(), val)));
|
||||||
}
|
}
|
||||||
LayoutGroup::Table { rows, .. } => {
|
LayoutGroup::Table(WidgetTable { rows, .. }) => {
|
||||||
for (row_index, row) in rows.iter().enumerate() {
|
for (row_index, row) in rows.iter().enumerate() {
|
||||||
for (cell_index, cell) in row.iter().enumerate() {
|
for (cell_index, cell) in row.iter().enumerate() {
|
||||||
// Return if this is the correct ID
|
// Return if this is the correct ID
|
||||||
@@ -158,60 +157,12 @@ impl LayoutMessageHandler {
|
|||||||
let callback_message = match action {
|
let callback_message = match action {
|
||||||
WidgetValueAction::Commit => (color_button.on_commit.callback)(&()),
|
WidgetValueAction::Commit => (color_button.on_commit.callback)(&()),
|
||||||
WidgetValueAction::Update => {
|
WidgetValueAction::Update => {
|
||||||
// Decodes the colors in gamma, not linear
|
let Ok(fill_choice) = serde_json::from_value::<FillChoice>(value) else {
|
||||||
let decode_color = |color: &serde_json::map::Map<String, serde_json::value::Value>| -> Option<Color> {
|
warn!("ColorInput update was not able to be parsed as FillChoice: {color_button:?}");
|
||||||
let red = color.get("red").and_then(|x| x.as_f64()).map(|x| x as f32);
|
return;
|
||||||
let green = color.get("green").and_then(|x| x.as_f64()).map(|x| x as f32);
|
|
||||||
let blue = color.get("blue").and_then(|x| x.as_f64()).map(|x| x as f32);
|
|
||||||
let alpha = color.get("alpha").and_then(|x| x.as_f64()).map(|x| x as f32);
|
|
||||||
|
|
||||||
if let (Some(red), Some(green), Some(blue), Some(alpha)) = (red, green, blue, alpha)
|
|
||||||
&& let Some(color) = Color::from_rgbaf32(red, green, blue, alpha)
|
|
||||||
{
|
|
||||||
return Some(color);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
};
|
};
|
||||||
|
color_button.value = fill_choice;
|
||||||
(|| {
|
(color_button.on_update.callback)(color_button)
|
||||||
let Some(update_value) = value.as_object() else {
|
|
||||||
warn!("ColorInput update was not of type: object");
|
|
||||||
return Message::NoOp;
|
|
||||||
};
|
|
||||||
|
|
||||||
// None
|
|
||||||
let is_none = update_value.get("none").and_then(|x| x.as_bool());
|
|
||||||
if is_none == Some(true) {
|
|
||||||
color_button.value = FillChoice::None;
|
|
||||||
return (color_button.on_update.callback)(color_button);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Solid
|
|
||||||
if let Some(color) = decode_color(update_value) {
|
|
||||||
color_button.value = FillChoice::Solid(color);
|
|
||||||
return (color_button.on_update.callback)(color_button);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gradient
|
|
||||||
let positions = update_value.get("position").and_then(|x| x.as_array());
|
|
||||||
let midpoints = update_value.get("midpoint").and_then(|x| x.as_array());
|
|
||||||
let colors = update_value.get("color").and_then(|x| x.as_array());
|
|
||||||
|
|
||||||
if let (Some(positions), Some(midpoints), Some(colors)) = (positions, midpoints, colors) {
|
|
||||||
let gradient_stops = positions.iter().zip(midpoints.iter()).zip(colors.iter()).filter_map(|((pos, mid), col)| {
|
|
||||||
let position = pos.as_f64()?;
|
|
||||||
let midpoint = mid.as_f64()?;
|
|
||||||
let color = col.as_object().and_then(decode_color)?;
|
|
||||||
Some(GradientStop { position, midpoint, color })
|
|
||||||
});
|
|
||||||
|
|
||||||
color_button.value = FillChoice::Gradient(GradientStops::new(gradient_stops));
|
|
||||||
return (color_button.on_update.callback)(color_button);
|
|
||||||
}
|
|
||||||
|
|
||||||
warn!("ColorInput update was not able to be parsed with color data: {color_button:?}");
|
|
||||||
Message::NoOp
|
|
||||||
})()
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ use std::hash::{Hash, Hasher};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct WidgetId(pub u64);
|
pub struct WidgetId(pub u64);
|
||||||
|
|
||||||
impl core::fmt::Display for WidgetId {
|
impl core::fmt::Display for WidgetId {
|
||||||
@@ -19,7 +20,8 @@ impl core::fmt::Display for WidgetId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug, Hash, Eq, Copy, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, Hash, Eq, Copy, serde::Serialize, serde::Deserialize)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum LayoutTarget {
|
pub enum LayoutTarget {
|
||||||
/// The spreadsheet panel allows for the visualisation of data in the graph.
|
/// The spreadsheet panel allows for the visualisation of data in the graph.
|
||||||
@@ -59,6 +61,7 @@ pub enum LayoutTarget {
|
|||||||
|
|
||||||
// KEEP THIS ENUM LAST
|
// KEEP THIS ENUM LAST
|
||||||
// This is a marker that is used to define an array that is used to hold widgets
|
// This is a marker that is used to define an array that is used to hold widgets
|
||||||
|
#[serde(skip)]
|
||||||
_LayoutTargetLength,
|
_LayoutTargetLength,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,7 +154,8 @@ fn compute_checkbox_id(layout_target: LayoutTarget, widget_path: &[usize], widge
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Contains an arrangement of widgets mounted somewhere specific in the frontend.
|
/// Contains an arrangement of widgets mounted somewhere specific in the frontend.
|
||||||
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, PartialEq, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
|
||||||
pub struct Layout(pub Vec<LayoutGroup>);
|
pub struct Layout(pub Vec<LayoutGroup>);
|
||||||
|
|
||||||
impl Layout {
|
impl Layout {
|
||||||
@@ -241,19 +245,19 @@ impl<'a> Iterator for WidgetIter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match self.stack.pop() {
|
match self.stack.pop() {
|
||||||
Some(LayoutGroup::Column { widgets }) => {
|
Some(LayoutGroup::Column(WidgetColumn { widgets })) => {
|
||||||
self.current_slice = Some(widgets);
|
self.current_slice = Some(widgets);
|
||||||
self.next()
|
self.next()
|
||||||
}
|
}
|
||||||
Some(LayoutGroup::Row { widgets }) => {
|
Some(LayoutGroup::Row(WidgetRow { widgets })) => {
|
||||||
self.current_slice = Some(widgets);
|
self.current_slice = Some(widgets);
|
||||||
self.next()
|
self.next()
|
||||||
}
|
}
|
||||||
Some(LayoutGroup::Table { rows, .. }) => {
|
Some(LayoutGroup::Table(WidgetTable { rows, .. })) => {
|
||||||
self.table.extend(rows.iter().flatten().rev());
|
self.table.extend(rows.iter().flatten().rev());
|
||||||
self.next()
|
self.next()
|
||||||
}
|
}
|
||||||
Some(LayoutGroup::Section { layout, .. }) => {
|
Some(LayoutGroup::Section(WidgetSection { layout, .. })) => {
|
||||||
for layout_row in &layout.0 {
|
for layout_row in &layout.0 {
|
||||||
self.stack.push(layout_row);
|
self.stack.push(layout_row);
|
||||||
}
|
}
|
||||||
@@ -293,19 +297,19 @@ impl<'a> Iterator for WidgetIterMut<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
match self.stack.pop() {
|
match self.stack.pop() {
|
||||||
Some(LayoutGroup::Column { widgets }) => {
|
Some(LayoutGroup::Column(WidgetColumn { widgets })) => {
|
||||||
self.current_slice = Some(widgets);
|
self.current_slice = Some(widgets);
|
||||||
self.next()
|
self.next()
|
||||||
}
|
}
|
||||||
Some(LayoutGroup::Row { widgets }) => {
|
Some(LayoutGroup::Row(WidgetRow { widgets })) => {
|
||||||
self.current_slice = Some(widgets);
|
self.current_slice = Some(widgets);
|
||||||
self.next()
|
self.next()
|
||||||
}
|
}
|
||||||
Some(LayoutGroup::Table { rows, .. }) => {
|
Some(LayoutGroup::Table(WidgetTable { rows, .. })) => {
|
||||||
self.table.extend(rows.iter_mut().flatten().rev());
|
self.table.extend(rows.iter_mut().flatten().rev());
|
||||||
self.next()
|
self.next()
|
||||||
}
|
}
|
||||||
Some(LayoutGroup::Section { layout, .. }) => {
|
Some(LayoutGroup::Section(WidgetSection { layout, .. })) => {
|
||||||
for layout_row in &mut layout.0 {
|
for layout_row in &mut layout.0 {
|
||||||
self.stack.push(layout_row);
|
self.stack.push(layout_row);
|
||||||
}
|
}
|
||||||
@@ -316,52 +320,88 @@ impl<'a> Iterator for WidgetIterMut<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum LayoutGroup {
|
pub enum LayoutGroup {
|
||||||
#[serde(rename = "column")]
|
Column(WidgetColumn),
|
||||||
Column {
|
Row(WidgetRow),
|
||||||
#[serde(rename = "columnWidgets")]
|
Table(WidgetTable),
|
||||||
widgets: Vec<WidgetInstance>,
|
Section(WidgetSection),
|
||||||
},
|
}
|
||||||
#[serde(rename = "row")]
|
|
||||||
Row {
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
#[serde(rename = "rowWidgets")]
|
#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
widgets: Vec<WidgetInstance>,
|
pub struct WidgetColumn {
|
||||||
},
|
#[serde(rename = "columnWidgets")]
|
||||||
#[serde(rename = "table")]
|
pub widgets: Vec<WidgetInstance>,
|
||||||
Table {
|
}
|
||||||
#[serde(rename = "tableWidgets")]
|
|
||||||
rows: Vec<Vec<WidgetInstance>>,
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
unstyled: bool,
|
#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
},
|
pub struct WidgetRow {
|
||||||
#[serde(rename = "section")]
|
#[serde(rename = "rowWidgets")]
|
||||||
Section {
|
pub widgets: Vec<WidgetInstance>,
|
||||||
name: String,
|
}
|
||||||
description: String,
|
|
||||||
visible: bool,
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
pinned: bool,
|
#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
id: u64,
|
pub struct WidgetTable {
|
||||||
layout: Layout,
|
#[serde(rename = "tableWidgets")]
|
||||||
},
|
pub rows: Vec<Vec<WidgetInstance>>,
|
||||||
|
pub unstyled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct WidgetSection {
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub visible: bool,
|
||||||
|
pub pinned: bool,
|
||||||
|
pub id: u64,
|
||||||
|
pub layout: Layout,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LayoutGroup {
|
impl Default for LayoutGroup {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Row { widgets: Vec::new() }
|
Self::Row(Default::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl From<Vec<WidgetInstance>> for LayoutGroup {
|
impl From<Vec<WidgetInstance>> for LayoutGroup {
|
||||||
fn from(widgets: Vec<WidgetInstance>) -> LayoutGroup {
|
fn from(widgets: Vec<WidgetInstance>) -> LayoutGroup {
|
||||||
LayoutGroup::Row { widgets }
|
LayoutGroup::Row(WidgetRow { widgets })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutGroup {
|
impl LayoutGroup {
|
||||||
|
pub fn row(widgets: Vec<WidgetInstance>) -> Self {
|
||||||
|
Self::Row(WidgetRow { widgets })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn column(widgets: Vec<WidgetInstance>) -> Self {
|
||||||
|
Self::Column(WidgetColumn { widgets })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn table(rows: Vec<Vec<WidgetInstance>>, unstyled: bool) -> Self {
|
||||||
|
Self::Table(WidgetTable { rows, unstyled })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn section(name: impl Into<String>, description: impl Into<String>, visible: bool, pinned: bool, id: u64, layout: Layout) -> Self {
|
||||||
|
Self::Section(WidgetSection {
|
||||||
|
name: name.into(),
|
||||||
|
description: description.into(),
|
||||||
|
visible,
|
||||||
|
pinned,
|
||||||
|
id,
|
||||||
|
layout,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Applies a tooltip description to all widgets without a tooltip in this row or column.
|
/// Applies a tooltip description to all widgets without a tooltip in this row or column.
|
||||||
pub fn with_tooltip_description(self, description: impl Into<String>) -> Self {
|
pub fn with_tooltip_description(self, description: impl Into<String>) -> Self {
|
||||||
let (is_col, mut widgets) = match self {
|
let (is_col, mut widgets) = match self {
|
||||||
LayoutGroup::Column { widgets } => (true, widgets),
|
LayoutGroup::Column(WidgetColumn { widgets }) => (true, widgets),
|
||||||
LayoutGroup::Row { widgets } => (false, widgets),
|
LayoutGroup::Row(WidgetRow { widgets }) => (false, widgets),
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
};
|
};
|
||||||
let description = description.into();
|
let description = description.into();
|
||||||
@@ -394,7 +434,7 @@ impl LayoutGroup {
|
|||||||
val.clone_from(&description);
|
val.clone_from(&description);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if is_col { Self::Column { widgets } } else { Self::Row { widgets } }
|
if is_col { Self::Column(WidgetColumn { widgets }) } else { Self::Row(WidgetRow { widgets }) }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iter_mut(&mut self) -> WidgetIterMut<'_> {
|
pub fn iter_mut(&mut self) -> WidgetIterMut<'_> {
|
||||||
@@ -413,7 +453,8 @@ impl Diffable for LayoutGroup {
|
|||||||
fn diff(&mut self, new: Self, widget_path: &mut Vec<usize>, widget_diffs: &mut Vec<WidgetDiff>) {
|
fn diff(&mut self, new: Self, widget_path: &mut Vec<usize>, widget_diffs: &mut Vec<WidgetDiff>) {
|
||||||
let is_column = matches!(new, Self::Column { .. });
|
let is_column = matches!(new, Self::Column { .. });
|
||||||
match (self, new) {
|
match (self, new) {
|
||||||
(Self::Column { widgets: current_widgets }, Self::Column { widgets: new_widgets }) | (Self::Row { widgets: current_widgets }, Self::Row { widgets: new_widgets }) => {
|
(Self::Column(WidgetColumn { widgets: current_widgets }), Self::Column(WidgetColumn { widgets: new_widgets }))
|
||||||
|
| (Self::Row(WidgetRow { widgets: current_widgets }), Self::Row(WidgetRow { widgets: new_widgets })) => {
|
||||||
// If the lengths are different then resend the entire panel
|
// If the lengths are different then resend the entire panel
|
||||||
// TODO: Diff insersion and deletion of items
|
// TODO: Diff insersion and deletion of items
|
||||||
if current_widgets.len() != new_widgets.len() {
|
if current_widgets.len() != new_widgets.len() {
|
||||||
@@ -421,7 +462,12 @@ impl Diffable for LayoutGroup {
|
|||||||
current_widgets.clone_from(&new_widgets);
|
current_widgets.clone_from(&new_widgets);
|
||||||
|
|
||||||
// Push back a LayoutGroup update to the diff
|
// Push back a LayoutGroup update to the diff
|
||||||
let new_value = (if is_column { Self::Column { widgets: new_widgets } } else { Self::Row { widgets: new_widgets } }).into_diff_update();
|
let new_value = (if is_column {
|
||||||
|
Self::Column(WidgetColumn { widgets: new_widgets })
|
||||||
|
} else {
|
||||||
|
Self::Row(WidgetRow { widgets: new_widgets })
|
||||||
|
})
|
||||||
|
.into_diff_update();
|
||||||
let widget_path = widget_path.to_vec();
|
let widget_path = widget_path.to_vec();
|
||||||
widget_diffs.push(WidgetDiff { widget_path, new_value });
|
widget_diffs.push(WidgetDiff { widget_path, new_value });
|
||||||
return;
|
return;
|
||||||
@@ -434,22 +480,22 @@ impl Diffable for LayoutGroup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
(
|
(
|
||||||
Self::Section {
|
Self::Section(WidgetSection {
|
||||||
name: current_name,
|
name: current_name,
|
||||||
description: current_description,
|
description: current_description,
|
||||||
visible: current_visible,
|
visible: current_visible,
|
||||||
pinned: current_pinned,
|
pinned: current_pinned,
|
||||||
id: current_id,
|
id: current_id,
|
||||||
layout: current_layout,
|
layout: current_layout,
|
||||||
},
|
}),
|
||||||
Self::Section {
|
Self::Section(WidgetSection {
|
||||||
name: new_name,
|
name: new_name,
|
||||||
description: new_description,
|
description: new_description,
|
||||||
visible: new_visible,
|
visible: new_visible,
|
||||||
pinned: new_pinned,
|
pinned: new_pinned,
|
||||||
id: new_id,
|
id: new_id,
|
||||||
layout: new_layout,
|
layout: new_layout,
|
||||||
},
|
}),
|
||||||
) => {
|
) => {
|
||||||
// Resend the entire panel if the lengths, names, visibility, or node IDs are different
|
// Resend the entire panel if the lengths, names, visibility, or node IDs are different
|
||||||
// TODO: Diff insersion and deletion of items
|
// TODO: Diff insersion and deletion of items
|
||||||
@@ -469,14 +515,14 @@ impl Diffable for LayoutGroup {
|
|||||||
current_layout.clone_from(&new_layout);
|
current_layout.clone_from(&new_layout);
|
||||||
|
|
||||||
// Push an update layout group to the diff
|
// Push an update layout group to the diff
|
||||||
let new_value = Self::Section {
|
let new_value = Self::Section(WidgetSection {
|
||||||
name: new_name,
|
name: new_name,
|
||||||
description: new_description,
|
description: new_description,
|
||||||
visible: new_visible,
|
visible: new_visible,
|
||||||
pinned: new_pinned,
|
pinned: new_pinned,
|
||||||
id: new_id,
|
id: new_id,
|
||||||
layout: new_layout,
|
layout: new_layout,
|
||||||
}
|
})
|
||||||
.into_diff_update();
|
.into_diff_update();
|
||||||
let widget_path = widget_path.to_vec();
|
let widget_path = widget_path.to_vec();
|
||||||
widget_diffs.push(WidgetDiff { widget_path, new_value });
|
widget_diffs.push(WidgetDiff { widget_path, new_value });
|
||||||
@@ -501,14 +547,14 @@ impl Diffable for LayoutGroup {
|
|||||||
|
|
||||||
fn collect_checkbox_ids(&self, layout_target: LayoutTarget, widget_path: &mut Vec<usize>, checkbox_map: &mut HashMap<CheckboxId, CheckboxId>) {
|
fn collect_checkbox_ids(&self, layout_target: LayoutTarget, widget_path: &mut Vec<usize>, checkbox_map: &mut HashMap<CheckboxId, CheckboxId>) {
|
||||||
match self {
|
match self {
|
||||||
Self::Column { widgets } | Self::Row { widgets } => {
|
Self::Column(WidgetColumn { widgets }) | Self::Row(WidgetRow { widgets }) => {
|
||||||
for (index, widget) in widgets.iter().enumerate() {
|
for (index, widget) in widgets.iter().enumerate() {
|
||||||
widget_path.push(index);
|
widget_path.push(index);
|
||||||
widget.collect_checkbox_ids(layout_target, widget_path, checkbox_map);
|
widget.collect_checkbox_ids(layout_target, widget_path, checkbox_map);
|
||||||
widget_path.pop();
|
widget_path.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Table { rows, .. } => {
|
Self::Table(WidgetTable { rows, .. }) => {
|
||||||
for (row_idx, row) in rows.iter().enumerate() {
|
for (row_idx, row) in rows.iter().enumerate() {
|
||||||
for (col_idx, widget) in row.iter().enumerate() {
|
for (col_idx, widget) in row.iter().enumerate() {
|
||||||
widget_path.push(row_idx);
|
widget_path.push(row_idx);
|
||||||
@@ -519,7 +565,7 @@ impl Diffable for LayoutGroup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Section { layout, .. } => {
|
Self::Section(WidgetSection { layout, .. }) => {
|
||||||
layout.collect_checkbox_ids(layout_target, widget_path, checkbox_map);
|
layout.collect_checkbox_ids(layout_target, widget_path, checkbox_map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -527,14 +573,14 @@ impl Diffable for LayoutGroup {
|
|||||||
|
|
||||||
fn replace_widget_ids(&mut self, layout_target: LayoutTarget, widget_path: &mut Vec<usize>, checkbox_map: &HashMap<CheckboxId, CheckboxId>) {
|
fn replace_widget_ids(&mut self, layout_target: LayoutTarget, widget_path: &mut Vec<usize>, checkbox_map: &HashMap<CheckboxId, CheckboxId>) {
|
||||||
match self {
|
match self {
|
||||||
Self::Column { widgets } | Self::Row { widgets } => {
|
Self::Column(WidgetColumn { widgets }) | Self::Row(WidgetRow { widgets }) => {
|
||||||
for (index, widget) in widgets.iter_mut().enumerate() {
|
for (index, widget) in widgets.iter_mut().enumerate() {
|
||||||
widget_path.push(index);
|
widget_path.push(index);
|
||||||
widget.replace_widget_ids(layout_target, widget_path, checkbox_map);
|
widget.replace_widget_ids(layout_target, widget_path, checkbox_map);
|
||||||
widget_path.pop();
|
widget_path.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Table { rows, .. } => {
|
Self::Table(WidgetTable { rows, .. }) => {
|
||||||
for (row_idx, row) in rows.iter_mut().enumerate() {
|
for (row_idx, row) in rows.iter_mut().enumerate() {
|
||||||
for (col_idx, widget) in row.iter_mut().enumerate() {
|
for (col_idx, widget) in row.iter_mut().enumerate() {
|
||||||
widget_path.push(row_idx);
|
widget_path.push(row_idx);
|
||||||
@@ -545,14 +591,15 @@ impl Diffable for LayoutGroup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self::Section { layout, .. } => {
|
Self::Section(WidgetSection { layout, .. }) => {
|
||||||
layout.replace_widget_ids(layout_target, widget_path, checkbox_map);
|
layout.replace_widget_ids(layout_target, widget_path, checkbox_map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct WidgetInstance {
|
pub struct WidgetInstance {
|
||||||
#[serde(rename = "widgetId")]
|
#[serde(rename = "widgetId")]
|
||||||
pub widget_id: WidgetId,
|
pub widget_id: WidgetId,
|
||||||
@@ -674,9 +721,8 @@ impl Diffable for WidgetInstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, specta::Type)]
|
#[derive(Clone)]
|
||||||
pub struct WidgetCallback<T> {
|
pub struct WidgetCallback<T> {
|
||||||
#[specta(skip)]
|
|
||||||
pub callback: Arc<dyn Fn(&T) -> Message + 'static + Send + Sync>,
|
pub callback: Arc<dyn Fn(&T) -> Message + 'static + Send + Sync>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,7 +738,8 @@ impl<T> Default for WidgetCallback<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum Widget {
|
pub enum Widget {
|
||||||
BreadcrumbTrailButtons(BreadcrumbTrailButtons),
|
BreadcrumbTrailButtons(BreadcrumbTrailButtons),
|
||||||
CheckboxInput(CheckboxInput),
|
CheckboxInput(CheckboxInput),
|
||||||
@@ -719,7 +766,8 @@ pub enum Widget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A single change to part of the UI, containing the location of the change and the new value.
|
/// A single change to part of the UI, containing the location of the change and the new value.
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct WidgetDiff {
|
pub struct WidgetDiff {
|
||||||
/// A path to the change
|
/// A path to the change
|
||||||
/// e.g. [0, 1, 2] in the properties panel is the first section, second row and third widget.
|
/// e.g. [0, 1, 2] in the properties panel is the first section, second row and third widget.
|
||||||
@@ -732,7 +780,8 @@ pub struct WidgetDiff {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The new value of the UI, sent as part of a diff.
|
/// The new value of the UI, sent as part of a diff.
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum DiffUpdate {
|
pub enum DiffUpdate {
|
||||||
#[serde(rename = "layout")]
|
#[serde(rename = "layout")]
|
||||||
Layout(Layout),
|
Layout(Layout),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::messages::frontend::IconName;
|
||||||
use crate::messages::input_mapper::utility_types::misc::ActionShortcut;
|
use crate::messages::input_mapper::utility_types::misc::ActionShortcut;
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
|
use crate::messages::portfolio::document::node_graph::utility_types::FrontendGraphDataType;
|
||||||
@@ -6,12 +7,14 @@ use derivative::*;
|
|||||||
use graphene_std::vector::style::FillChoice;
|
use graphene_std::vector::style::FillChoice;
|
||||||
use graphite_proc_macros::WidgetBuilder;
|
use graphite_proc_macros::WidgetBuilder;
|
||||||
|
|
||||||
#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq)]
|
#[derivative(Debug, PartialEq)]
|
||||||
pub struct IconButton {
|
pub struct IconButton {
|
||||||
// Content
|
// Content
|
||||||
#[widget_builder(constructor)]
|
#[widget_builder(constructor)]
|
||||||
pub icon: String,
|
#[widget_builder(string)]
|
||||||
|
pub icon: IconName,
|
||||||
#[serde(rename = "hoverIcon")]
|
#[serde(rename = "hoverIcon")]
|
||||||
pub hover_icon: Option<String>,
|
pub hover_icon: Option<String>,
|
||||||
#[widget_builder(constructor)]
|
#[widget_builder(constructor)]
|
||||||
@@ -38,12 +41,14 @@ pub struct IconButton {
|
|||||||
pub on_commit: WidgetCallback<()>,
|
pub on_commit: WidgetCallback<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq, Default)]
|
#[derivative(Debug, PartialEq, Default)]
|
||||||
pub struct PopoverButton {
|
pub struct PopoverButton {
|
||||||
// Content
|
// Content
|
||||||
pub style: Option<String>,
|
pub style: Option<String>,
|
||||||
pub icon: Option<String>,
|
#[widget_builder(string)]
|
||||||
|
pub icon: Option<IconName>,
|
||||||
pub disabled: bool,
|
pub disabled: bool,
|
||||||
|
|
||||||
// Children
|
// Children
|
||||||
@@ -63,7 +68,8 @@ pub struct PopoverButton {
|
|||||||
pub tooltip_shortcut: Option<ActionShortcut>,
|
pub tooltip_shortcut: Option<ActionShortcut>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Default, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum MenuDirection {
|
pub enum MenuDirection {
|
||||||
Top,
|
Top,
|
||||||
#[default]
|
#[default]
|
||||||
@@ -77,7 +83,8 @@ pub enum MenuDirection {
|
|||||||
Center,
|
Center,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq)]
|
#[derivative(Debug, PartialEq)]
|
||||||
pub struct ParameterExposeButton {
|
pub struct ParameterExposeButton {
|
||||||
// Content
|
// Content
|
||||||
@@ -102,13 +109,15 @@ pub struct ParameterExposeButton {
|
|||||||
pub on_commit: WidgetCallback<()>,
|
pub on_commit: WidgetCallback<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq)]
|
#[derivative(Debug, PartialEq)]
|
||||||
pub struct TextButton {
|
pub struct TextButton {
|
||||||
// Content
|
// Content
|
||||||
#[widget_builder(constructor)]
|
#[widget_builder(constructor)]
|
||||||
pub label: String,
|
pub label: String,
|
||||||
pub icon: Option<String>,
|
#[widget_builder(string)]
|
||||||
|
pub icon: Option<IconName>,
|
||||||
#[serde(rename = "hoverIcon")]
|
#[serde(rename = "hoverIcon")]
|
||||||
pub hover_icon: Option<String>,
|
pub hover_icon: Option<String>,
|
||||||
pub disabled: bool,
|
pub disabled: bool,
|
||||||
@@ -146,7 +155,8 @@ pub struct TextButton {
|
|||||||
pub on_commit: WidgetCallback<()>,
|
pub on_commit: WidgetCallback<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq)]
|
#[derivative(Debug, PartialEq)]
|
||||||
pub struct ImageButton {
|
pub struct ImageButton {
|
||||||
// Content
|
// Content
|
||||||
@@ -172,7 +182,8 @@ pub struct ImageButton {
|
|||||||
pub on_commit: WidgetCallback<()>,
|
pub on_commit: WidgetCallback<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq, Default)]
|
#[derivative(Debug, PartialEq, Default)]
|
||||||
pub struct ColorInput {
|
pub struct ColorInput {
|
||||||
// Content
|
// Content
|
||||||
@@ -207,7 +218,8 @@ pub struct ColorInput {
|
|||||||
pub on_commit: WidgetCallback<()>,
|
pub on_commit: WidgetCallback<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq)]
|
#[derivative(Debug, PartialEq)]
|
||||||
pub struct BreadcrumbTrailButtons {
|
pub struct BreadcrumbTrailButtons {
|
||||||
// Content
|
// Content
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::messages::frontend::IconName;
|
||||||
use crate::messages::input_mapper::utility_types::misc::ActionShortcut;
|
use crate::messages::input_mapper::utility_types::misc::ActionShortcut;
|
||||||
use crate::messages::layout::utility_types::widget_prelude::*;
|
use crate::messages::layout::utility_types::widget_prelude::*;
|
||||||
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
|
use crate::messages::portfolio::document::node_graph::document_node_definitions::DefinitionIdentifier;
|
||||||
@@ -7,14 +8,15 @@ use graphene_std::raster::curve::Curve;
|
|||||||
use graphene_std::transform::ReferencePoint;
|
use graphene_std::transform::ReferencePoint;
|
||||||
use graphite_proc_macros::WidgetBuilder;
|
use graphite_proc_macros::WidgetBuilder;
|
||||||
|
|
||||||
#[derive(Clone, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder)]
|
||||||
#[derivative(Debug, Default, PartialEq)]
|
#[derivative(Debug, Default, PartialEq)]
|
||||||
pub struct CheckboxInput {
|
pub struct CheckboxInput {
|
||||||
// Content
|
// Content
|
||||||
#[widget_builder(constructor)]
|
#[widget_builder(constructor)]
|
||||||
pub checked: bool,
|
pub checked: bool,
|
||||||
#[derivative(Default(value = "\"Checkmark\".to_string()"))]
|
#[widget_builder(string)]
|
||||||
pub icon: String,
|
pub icon: Option<IconName>,
|
||||||
#[serde(rename = "forLabel")]
|
#[serde(rename = "forLabel")]
|
||||||
pub for_label: CheckboxId,
|
pub for_label: CheckboxId,
|
||||||
pub disabled: bool,
|
pub disabled: bool,
|
||||||
@@ -36,6 +38,7 @@ pub struct CheckboxInput {
|
|||||||
pub on_commit: WidgetCallback<()>,
|
pub on_commit: WidgetCallback<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct CheckboxId(pub u64);
|
pub struct CheckboxId(pub u64);
|
||||||
|
|
||||||
@@ -49,14 +52,9 @@ impl Default for CheckboxId {
|
|||||||
Self::new()
|
Self::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl specta::Type for CheckboxId {
|
|
||||||
fn inline(_type_map: &mut specta::TypeCollection, _generics: specta::Generics) -> specta::datatype::DataType {
|
|
||||||
// TODO: This might not be right, but it works for now. We just need the type `bigint | undefined`.
|
|
||||||
specta::datatype::DataType::Primitive(specta::datatype::PrimitiveType::u64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq, Default)]
|
#[derivative(Debug, PartialEq, Default)]
|
||||||
pub struct DropdownInput {
|
pub struct DropdownInput {
|
||||||
// Content
|
// Content
|
||||||
@@ -102,7 +100,8 @@ pub struct DropdownInput {
|
|||||||
|
|
||||||
pub type MenuListEntrySections = Vec<Vec<MenuListEntry>>;
|
pub type MenuListEntrySections = Vec<Vec<MenuListEntry>>;
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq)]
|
#[derivative(Debug, PartialEq)]
|
||||||
#[widget_builder(not_widget_instance)]
|
#[widget_builder(not_widget_instance)]
|
||||||
pub struct MenuListEntry {
|
pub struct MenuListEntry {
|
||||||
@@ -110,7 +109,8 @@ pub struct MenuListEntry {
|
|||||||
#[widget_builder(constructor)]
|
#[widget_builder(constructor)]
|
||||||
pub value: String,
|
pub value: String,
|
||||||
pub label: String,
|
pub label: String,
|
||||||
pub icon: String,
|
#[widget_builder(string)]
|
||||||
|
pub icon: Option<IconName>,
|
||||||
pub disabled: bool,
|
pub disabled: bool,
|
||||||
|
|
||||||
// Children
|
// Children
|
||||||
@@ -120,7 +120,7 @@ pub struct MenuListEntry {
|
|||||||
pub children_hash: u64,
|
pub children_hash: u64,
|
||||||
|
|
||||||
// Styling
|
// Styling
|
||||||
pub font: String,
|
pub font: Option<String>,
|
||||||
|
|
||||||
// Tooltips
|
// Tooltips
|
||||||
#[serde(rename = "tooltipLabel")]
|
#[serde(rename = "tooltipLabel")]
|
||||||
@@ -148,7 +148,8 @@ impl std::hash::Hash for MenuListEntry {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq, Default)]
|
#[derivative(Debug, PartialEq, Default)]
|
||||||
pub struct NumberInput {
|
pub struct NumberInput {
|
||||||
// Content
|
// Content
|
||||||
@@ -246,22 +247,30 @@ impl NumberInput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Debug, Default, PartialEq, Eq, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Debug, Default, PartialEq, Eq)]
|
||||||
pub enum NumberInputIncrementBehavior {
|
pub enum NumberInputIncrementBehavior {
|
||||||
|
/// The value is added by `step`.
|
||||||
#[default]
|
#[default]
|
||||||
Add,
|
Add,
|
||||||
|
/// The value is multiplied by `step`.
|
||||||
Multiply,
|
Multiply,
|
||||||
|
/// The functions `incrementCallbackIncrease` and `incrementCallbackDecrease` call custom behavior.
|
||||||
Callback,
|
Callback,
|
||||||
|
/// The increment arrows are not shown.
|
||||||
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Debug, Default, PartialEq, Eq, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Debug, Default, PartialEq, Eq)]
|
||||||
pub enum NumberInputMode {
|
pub enum NumberInputMode {
|
||||||
#[default]
|
#[default]
|
||||||
Increment,
|
Increment,
|
||||||
Range,
|
Range,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq, Default)]
|
#[derivative(Debug, PartialEq, Default)]
|
||||||
pub struct NodeCatalog {
|
pub struct NodeCatalog {
|
||||||
// Content
|
// Content
|
||||||
@@ -280,7 +289,8 @@ pub struct NodeCatalog {
|
|||||||
pub on_commit: WidgetCallback<()>,
|
pub on_commit: WidgetCallback<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq)]
|
#[derivative(Debug, PartialEq)]
|
||||||
pub struct RadioInput {
|
pub struct RadioInput {
|
||||||
// Content
|
// Content
|
||||||
@@ -303,7 +313,8 @@ pub struct RadioInput {
|
|||||||
// Callbacks exists on the `RadioEntryData` children, not this parent `RadioInput`
|
// Callbacks exists on the `RadioEntryData` children, not this parent `RadioInput`
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq)]
|
#[derivative(Debug, PartialEq)]
|
||||||
#[widget_builder(not_widget_instance)]
|
#[widget_builder(not_widget_instance)]
|
||||||
pub struct RadioEntryData {
|
pub struct RadioEntryData {
|
||||||
@@ -311,7 +322,8 @@ pub struct RadioEntryData {
|
|||||||
#[widget_builder(constructor)]
|
#[widget_builder(constructor)]
|
||||||
pub value: String,
|
pub value: String,
|
||||||
pub label: String,
|
pub label: String,
|
||||||
pub icon: String,
|
#[widget_builder(string)]
|
||||||
|
pub icon: Option<IconName>,
|
||||||
|
|
||||||
// Tooltips
|
// Tooltips
|
||||||
#[serde(rename = "tooltipLabel")]
|
#[serde(rename = "tooltipLabel")]
|
||||||
@@ -330,7 +342,8 @@ pub struct RadioEntryData {
|
|||||||
pub on_commit: WidgetCallback<()>,
|
pub on_commit: WidgetCallback<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq, Default)]
|
#[derivative(Debug, PartialEq, Default)]
|
||||||
pub struct WorkingColorsInput {
|
pub struct WorkingColorsInput {
|
||||||
// Content
|
// Content
|
||||||
@@ -340,7 +353,8 @@ pub struct WorkingColorsInput {
|
|||||||
pub secondary: Color,
|
pub secondary: Color,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq, Default)]
|
#[derivative(Debug, PartialEq, Default)]
|
||||||
pub struct TextAreaInput {
|
pub struct TextAreaInput {
|
||||||
// Content
|
// Content
|
||||||
@@ -366,7 +380,8 @@ pub struct TextAreaInput {
|
|||||||
pub on_commit: WidgetCallback<()>,
|
pub on_commit: WidgetCallback<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq, Default)]
|
#[derivative(Debug, PartialEq, Default)]
|
||||||
pub struct TextInput {
|
pub struct TextInput {
|
||||||
// Content
|
// Content
|
||||||
@@ -403,7 +418,8 @@ pub struct TextInput {
|
|||||||
pub on_commit: WidgetCallback<()>,
|
pub on_commit: WidgetCallback<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq, Default)]
|
#[derivative(Debug, PartialEq, Default)]
|
||||||
pub struct CurveInput {
|
pub struct CurveInput {
|
||||||
// Content
|
// Content
|
||||||
@@ -427,7 +443,8 @@ pub struct CurveInput {
|
|||||||
pub on_commit: WidgetCallback<()>,
|
pub on_commit: WidgetCallback<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Default, Derivative, serde::Serialize, serde::Deserialize, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq)]
|
#[derivative(Debug, PartialEq)]
|
||||||
pub struct ReferencePointInput {
|
pub struct ReferencePointInput {
|
||||||
// Content
|
// Content
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
use super::input_widgets::CheckboxId;
|
use super::input_widgets::CheckboxId;
|
||||||
use crate::messages::input_mapper::utility_types::misc::ActionShortcut;
|
use crate::messages::{frontend::IconName, input_mapper::utility_types::misc::ActionShortcut};
|
||||||
use derivative::*;
|
use derivative::*;
|
||||||
use graphite_proc_macros::WidgetBuilder;
|
use graphite_proc_macros::WidgetBuilder;
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Debug, Default, PartialEq, Eq, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Debug, Default, PartialEq, Eq, WidgetBuilder)]
|
||||||
pub struct IconLabel {
|
pub struct IconLabel {
|
||||||
// Content
|
// Content
|
||||||
#[widget_builder(constructor)]
|
#[widget_builder(constructor)]
|
||||||
pub icon: String,
|
#[widget_builder(string)]
|
||||||
|
pub icon: IconName,
|
||||||
pub disabled: bool,
|
pub disabled: bool,
|
||||||
|
|
||||||
// Tooltips
|
// Tooltips
|
||||||
@@ -19,7 +21,8 @@ pub struct IconLabel {
|
|||||||
pub tooltip_shortcut: Option<ActionShortcut>,
|
pub tooltip_shortcut: Option<ActionShortcut>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, WidgetBuilder)]
|
||||||
pub struct Separator {
|
pub struct Separator {
|
||||||
// Content
|
// Content
|
||||||
pub direction: SeparatorDirection,
|
pub direction: SeparatorDirection,
|
||||||
@@ -27,14 +30,16 @@ pub struct Separator {
|
|||||||
pub style: SeparatorStyle,
|
pub style: SeparatorStyle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum SeparatorDirection {
|
pub enum SeparatorDirection {
|
||||||
#[default]
|
#[default]
|
||||||
Horizontal,
|
Horizontal,
|
||||||
Vertical,
|
Vertical,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum SeparatorStyle {
|
pub enum SeparatorStyle {
|
||||||
Related,
|
Related,
|
||||||
#[default]
|
#[default]
|
||||||
@@ -42,7 +47,8 @@ pub enum SeparatorStyle {
|
|||||||
Section,
|
Section,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Debug, Eq, Default, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Debug, Eq, Default, WidgetBuilder)]
|
||||||
#[derivative(PartialEq)]
|
#[derivative(PartialEq)]
|
||||||
pub struct TextLabel {
|
pub struct TextLabel {
|
||||||
// Content
|
// Content
|
||||||
@@ -78,7 +84,8 @@ pub struct TextLabel {
|
|||||||
pub tooltip_shortcut: Option<ActionShortcut>,
|
pub tooltip_shortcut: Option<ActionShortcut>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq)]
|
#[derivative(Debug, PartialEq)]
|
||||||
pub struct ImageLabel {
|
pub struct ImageLabel {
|
||||||
// Content
|
// Content
|
||||||
@@ -96,7 +103,8 @@ pub struct ImageLabel {
|
|||||||
pub tooltip_shortcut: Option<ActionShortcut>,
|
pub tooltip_shortcut: Option<ActionShortcut>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, serde::Serialize, serde::Deserialize, Derivative, Default, WidgetBuilder)]
|
||||||
#[derivative(Debug, PartialEq)]
|
#[derivative(Debug, PartialEq)]
|
||||||
pub struct ShortcutLabel {
|
pub struct ShortcutLabel {
|
||||||
// Content
|
// Content
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ impl LayoutHolder for MenuBarMessageHandler {
|
|||||||
TextButton::new("Graphite")
|
TextButton::new("Graphite")
|
||||||
.label("")
|
.label("")
|
||||||
.flush(true)
|
.flush(true)
|
||||||
.icon(Some("GraphiteLogo".into()))
|
.icon("GraphiteLogo")
|
||||||
.on_commit(|_| FrontendMessage::TriggerVisitLink { url: "https://graphite.art".into() }.into())
|
.on_commit(|_| FrontendMessage::TriggerVisitLink { url: "https://graphite.art".into() }.into())
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@@ -750,6 +750,6 @@ impl LayoutHolder for MenuBarMessageHandler {
|
|||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
];
|
];
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets: menu_bar_buttons }])
|
Layout(vec![LayoutGroup::row(menu_bar_buttons)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,14 +45,6 @@ pub enum Message {
|
|||||||
NoOp,
|
NoOp,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provides an impl of `specta::Type` for `MessageDiscriminant`, the struct created by `impl_message`.
|
|
||||||
/// Specta isn't integrated with `impl_message`, so a remote impl must be provided using this struct.
|
|
||||||
impl specta::Type for MessageDiscriminant {
|
|
||||||
fn inline(_type_map: &mut specta::TypeCollection, _generics: specta::Generics) -> specta::DataType {
|
|
||||||
specta::DataType::Any
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Message {
|
impl Message {
|
||||||
pub fn message_tree() -> DebugMessageTree {
|
pub fn message_tree() -> DebugMessageTree {
|
||||||
Self::build_message_tree()
|
Self::build_message_tree()
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ impl DataPanelMessageHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !widgets.is_empty() {
|
if !widgets.is_empty() {
|
||||||
layout.0.insert(0, LayoutGroup::Row { widgets });
|
layout.0.insert(0, LayoutGroup::row(widgets));
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.add(LayoutMessage::SendLayout {
|
responses.add(LayoutMessage::SendLayout {
|
||||||
@@ -185,7 +185,7 @@ fn column_headings(value: &[&str]) -> Vec<WidgetInstance> {
|
|||||||
|
|
||||||
fn label(x: impl Into<String>) -> Vec<LayoutGroup> {
|
fn label(x: impl Into<String>) -> Vec<LayoutGroup> {
|
||||||
let error = vec![TextLabel::new(x).widget_instance()];
|
let error = vec![TextLabel::new(x).widget_instance()];
|
||||||
vec![LayoutGroup::Row { widgets: error }]
|
vec![LayoutGroup::row(error)]
|
||||||
}
|
}
|
||||||
|
|
||||||
trait TableRowLayout {
|
trait TableRowLayout {
|
||||||
@@ -234,7 +234,7 @@ impl<T: TableRowLayout> TableRowLayout for Vec<T> {
|
|||||||
|
|
||||||
rows.insert(0, column_headings(&["", "element"]));
|
rows.insert(0, column_headings(&["", "element"]));
|
||||||
|
|
||||||
vec![LayoutGroup::Table { rows, unstyled: false }]
|
vec![LayoutGroup::table(rows, false)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +276,7 @@ impl<T: TableRowLayout> TableRowLayout for Table<T> {
|
|||||||
|
|
||||||
rows.insert(0, column_headings(&["", "element", "transform", "alpha_blending", "source_node_id"]));
|
rows.insert(0, column_headings(&["", "element", "transform", "alpha_blending", "source_node_id"]));
|
||||||
|
|
||||||
vec![LayoutGroup::Table { rows, unstyled: false }]
|
vec![LayoutGroup::table(rows, false)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,7 +488,7 @@ impl TableRowLayout for Vector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vec![LayoutGroup::Row { widgets: table_tabs }, LayoutGroup::Table { rows: table_rows, unstyled: false }]
|
vec![LayoutGroup::row(table_tabs), LayoutGroup::table(table_rows, false)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,7 +504,7 @@ impl TableRowLayout for Raster<CPU> {
|
|||||||
|
|
||||||
if raster.width == 0 || raster.height == 0 {
|
if raster.width == 0 || raster.height == 0 {
|
||||||
let widgets = vec![TextLabel::new("Image has no area").widget_instance()];
|
let widgets = vec![TextLabel::new("Image has no area").widget_instance()];
|
||||||
return vec![LayoutGroup::Row { widgets }];
|
return vec![LayoutGroup::row(widgets)];
|
||||||
}
|
}
|
||||||
|
|
||||||
let base64_string = raster.base64_string.clone().unwrap_or_else(|| {
|
let base64_string = raster.base64_string.clone().unwrap_or_else(|| {
|
||||||
@@ -519,7 +519,7 @@ impl TableRowLayout for Raster<CPU> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let widgets = vec![ImageLabel::new(base64_string).widget_instance()];
|
let widgets = vec![ImageLabel::new(base64_string).widget_instance()];
|
||||||
vec![LayoutGroup::Row { widgets }]
|
vec![LayoutGroup::row(widgets)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,7 +532,7 @@ impl TableRowLayout for Raster<GPU> {
|
|||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
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_instance()];
|
let widgets = vec![TextLabel::new("Raster is a texture on the GPU and cannot currently be displayed here").widget_instance()];
|
||||||
vec![LayoutGroup::Row { widgets }]
|
vec![LayoutGroup::row(widgets)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,7 +552,7 @@ impl TableRowLayout for Color {
|
|||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let widgets = vec![self.element_widget(0)];
|
let widgets = vec![self.element_widget(0)];
|
||||||
vec![LayoutGroup::Row { widgets }]
|
vec![LayoutGroup::row(widgets)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,7 +572,7 @@ impl TableRowLayout for GradientStops {
|
|||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let widgets = vec![self.element_widget(0)];
|
let widgets = vec![self.element_widget(0)];
|
||||||
vec![LayoutGroup::Row { widgets }]
|
vec![LayoutGroup::row(widgets)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,7 +585,7 @@ impl TableRowLayout for f64 {
|
|||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let widgets = vec![TextLabel::new(self.to_string()).widget_instance()];
|
let widgets = vec![TextLabel::new(self.to_string()).widget_instance()];
|
||||||
vec![LayoutGroup::Row { widgets }]
|
vec![LayoutGroup::row(widgets)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -598,7 +598,7 @@ impl TableRowLayout for u32 {
|
|||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let widgets = vec![TextLabel::new(self.to_string()).widget_instance()];
|
let widgets = vec![TextLabel::new(self.to_string()).widget_instance()];
|
||||||
vec![LayoutGroup::Row { widgets }]
|
vec![LayoutGroup::row(widgets)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -611,7 +611,7 @@ impl TableRowLayout for u64 {
|
|||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let widgets = vec![TextLabel::new(self.to_string()).widget_instance()];
|
let widgets = vec![TextLabel::new(self.to_string()).widget_instance()];
|
||||||
vec![LayoutGroup::Row { widgets }]
|
vec![LayoutGroup::row(widgets)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -624,7 +624,7 @@ impl TableRowLayout for bool {
|
|||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let widgets = vec![TextLabel::new(self.to_string()).widget_instance()];
|
let widgets = vec![TextLabel::new(self.to_string()).widget_instance()];
|
||||||
vec![LayoutGroup::Row { widgets }]
|
vec![LayoutGroup::row(widgets)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -643,7 +643,7 @@ impl TableRowLayout for String {
|
|||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let widgets = vec![TextAreaInput::new(self.to_string()).disabled(true).widget_instance()];
|
let widgets = vec![TextAreaInput::new(self.to_string()).disabled(true).widget_instance()];
|
||||||
vec![LayoutGroup::Row { widgets }]
|
vec![LayoutGroup::row(widgets)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -656,7 +656,7 @@ impl TableRowLayout for Option<f64> {
|
|||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let widgets = vec![TextLabel::new(format!("{self:?}")).widget_instance()];
|
let widgets = vec![TextLabel::new(format!("{self:?}")).widget_instance()];
|
||||||
vec![LayoutGroup::Row { widgets }]
|
vec![LayoutGroup::row(widgets)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -669,7 +669,7 @@ impl TableRowLayout for DVec2 {
|
|||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let widgets = vec![TextLabel::new(format!("({}, {})", self.x, self.y)).widget_instance()];
|
let widgets = vec![TextLabel::new(format!("({}, {})", self.x, self.y)).widget_instance()];
|
||||||
vec![LayoutGroup::Row { widgets }]
|
vec![LayoutGroup::row(widgets)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -682,7 +682,7 @@ impl TableRowLayout for Vec2 {
|
|||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let widgets = vec![TextLabel::new(format!("({}, {})", self.x, self.y)).widget_instance()];
|
let widgets = vec![TextLabel::new(format!("({}, {})", self.x, self.y)).widget_instance()];
|
||||||
vec![LayoutGroup::Row { widgets }]
|
vec![LayoutGroup::row(widgets)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,7 +695,7 @@ impl TableRowLayout for DAffine2 {
|
|||||||
}
|
}
|
||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let widgets = vec![TextLabel::new(format_transform_matrix(self)).widget_instance()];
|
let widgets = vec![TextLabel::new(format_transform_matrix(self)).widget_instance()];
|
||||||
vec![LayoutGroup::Row { widgets }]
|
vec![LayoutGroup::row(widgets)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,7 +709,7 @@ impl TableRowLayout for Affine2 {
|
|||||||
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
fn element_page(&self, _data: &mut LayoutData) -> Vec<LayoutGroup> {
|
||||||
let matrix = DAffine2::from_cols_array(&self.to_cols_array().map(|x| x as f64));
|
let matrix = DAffine2::from_cols_array(&self.to_cols_array().map(|x| x as f64));
|
||||||
let widgets = vec![TextLabel::new(format_transform_matrix(&matrix)).widget_instance()];
|
let widgets = vec![TextLabel::new(format_transform_matrix(&matrix)).widget_instance()];
|
||||||
vec![LayoutGroup::Row { widgets }]
|
vec![LayoutGroup::row(widgets)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use super::node_graph::document_node_definitions;
|
use super::node_graph::document_node_definitions;
|
||||||
use super::node_graph::utility_types::Transform;
|
|
||||||
use super::utility_types::error::EditorError;
|
use super::utility_types::error::EditorError;
|
||||||
use super::utility_types::misc::{GroupFolderType, SNAP_FUNCTIONS_FOR_BOUNDING_BOXES, SNAP_FUNCTIONS_FOR_PATHS, SnappingOptions, SnappingState};
|
use super::utility_types::misc::{GroupFolderType, SNAP_FUNCTIONS_FOR_BOUNDING_BOXES, SNAP_FUNCTIONS_FOR_PATHS, SnappingOptions, SnappingState};
|
||||||
use super::utility_types::network_interface::{self, NodeNetworkInterface, TransactionStatus};
|
use super::utility_types::network_interface::{self, NodeNetworkInterface, TransactionStatus};
|
||||||
@@ -848,7 +847,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
document_id,
|
document_id,
|
||||||
name: format!("{}.{}", self.name.clone(), FILE_EXTENSION),
|
name: format!("{}.{}", self.name.clone(), FILE_EXTENSION),
|
||||||
path: self.path.clone(),
|
path: self.path.clone(),
|
||||||
content: self.serialize_document().into_bytes(),
|
content: self.serialize_document().into_bytes().into(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
DocumentMessage::SavedDocument { path } => {
|
DocumentMessage::SavedDocument { path } => {
|
||||||
@@ -1332,11 +1331,8 @@ impl MessageHandler<DocumentMessage, DocumentMessageContext<'_>> for DocumentMes
|
|||||||
responses.add(NodeGraphMessage::UpdateImportsExports);
|
responses.add(NodeGraphMessage::UpdateImportsExports);
|
||||||
|
|
||||||
responses.add(FrontendMessage::UpdateNodeGraphTransform {
|
responses.add(FrontendMessage::UpdateNodeGraphTransform {
|
||||||
transform: Transform {
|
translation: transform.translation.into(),
|
||||||
scale: transform.matrix2.x_axis.x,
|
scale: transform.matrix2.x_axis.x,
|
||||||
x: transform.translation.x,
|
|
||||||
y: transform.translation.y,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2216,256 +2212,222 @@ impl DocumentMessageHandler {
|
|||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
PopoverButton::new()
|
PopoverButton::new()
|
||||||
.popover_layout(Layout(vec![
|
.popover_layout(Layout(vec![
|
||||||
LayoutGroup::Row {
|
LayoutGroup::row(vec![TextLabel::new("Overlays").bold(true).widget_instance()]),
|
||||||
widgets: vec![TextLabel::new("Overlays").bold(true).widget_instance()],
|
LayoutGroup::row(vec![TextLabel::new("General").widget_instance()]),
|
||||||
},
|
LayoutGroup::row({
|
||||||
LayoutGroup::Row {
|
let checkbox_id = CheckboxId::new();
|
||||||
widgets: vec![TextLabel::new("General").widget_instance()],
|
vec![
|
||||||
},
|
CheckboxInput::new(self.overlays_visibility_settings.artboard_name)
|
||||||
LayoutGroup::Row {
|
.on_update(|optional_input: &CheckboxInput| {
|
||||||
widgets: {
|
DocumentMessage::SetOverlaysVisibility {
|
||||||
let checkbox_id = CheckboxId::new();
|
visible: optional_input.checked,
|
||||||
vec![
|
overlays_type: Some(OverlaysType::ArtboardName),
|
||||||
CheckboxInput::new(self.overlays_visibility_settings.artboard_name)
|
}
|
||||||
.on_update(|optional_input: &CheckboxInput| {
|
.into()
|
||||||
DocumentMessage::SetOverlaysVisibility {
|
})
|
||||||
visible: optional_input.checked,
|
.for_label(checkbox_id)
|
||||||
overlays_type: Some(OverlaysType::ArtboardName),
|
.widget_instance(),
|
||||||
}
|
TextLabel::new("Artboard Name".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
||||||
.into()
|
]
|
||||||
})
|
}),
|
||||||
.for_label(checkbox_id)
|
LayoutGroup::row({
|
||||||
.widget_instance(),
|
let checkbox_id = CheckboxId::new();
|
||||||
TextLabel::new("Artboard Name".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
vec![
|
||||||
]
|
CheckboxInput::new(self.overlays_visibility_settings.transform_measurement)
|
||||||
},
|
.on_update(|optional_input: &CheckboxInput| {
|
||||||
},
|
DocumentMessage::SetOverlaysVisibility {
|
||||||
LayoutGroup::Row {
|
visible: optional_input.checked,
|
||||||
widgets: {
|
overlays_type: Some(OverlaysType::TransformMeasurement),
|
||||||
let checkbox_id = CheckboxId::new();
|
}
|
||||||
vec![
|
.into()
|
||||||
CheckboxInput::new(self.overlays_visibility_settings.transform_measurement)
|
})
|
||||||
.on_update(|optional_input: &CheckboxInput| {
|
.for_label(checkbox_id)
|
||||||
DocumentMessage::SetOverlaysVisibility {
|
.widget_instance(),
|
||||||
visible: optional_input.checked,
|
TextLabel::new("G/R/S Measurement".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
||||||
overlays_type: Some(OverlaysType::TransformMeasurement),
|
]
|
||||||
}
|
}),
|
||||||
.into()
|
LayoutGroup::row(vec![TextLabel::new("Select Tool").widget_instance()]),
|
||||||
})
|
LayoutGroup::row({
|
||||||
.for_label(checkbox_id)
|
let checkbox_id = CheckboxId::new();
|
||||||
.widget_instance(),
|
vec![
|
||||||
TextLabel::new("G/R/S Measurement".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
CheckboxInput::new(self.overlays_visibility_settings.quick_measurement)
|
||||||
]
|
.on_update(|optional_input: &CheckboxInput| {
|
||||||
},
|
DocumentMessage::SetOverlaysVisibility {
|
||||||
},
|
visible: optional_input.checked,
|
||||||
LayoutGroup::Row {
|
overlays_type: Some(OverlaysType::QuickMeasurement),
|
||||||
widgets: vec![TextLabel::new("Select Tool").widget_instance()],
|
}
|
||||||
},
|
.into()
|
||||||
LayoutGroup::Row {
|
})
|
||||||
widgets: {
|
.for_label(checkbox_id)
|
||||||
let checkbox_id = CheckboxId::new();
|
.widget_instance(),
|
||||||
vec![
|
TextLabel::new("Quick Measurement".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
||||||
CheckboxInput::new(self.overlays_visibility_settings.quick_measurement)
|
]
|
||||||
.on_update(|optional_input: &CheckboxInput| {
|
}),
|
||||||
DocumentMessage::SetOverlaysVisibility {
|
LayoutGroup::row({
|
||||||
visible: optional_input.checked,
|
let checkbox_id = CheckboxId::new();
|
||||||
overlays_type: Some(OverlaysType::QuickMeasurement),
|
vec![
|
||||||
}
|
CheckboxInput::new(self.overlays_visibility_settings.transform_cage)
|
||||||
.into()
|
.on_update(|optional_input: &CheckboxInput| {
|
||||||
})
|
DocumentMessage::SetOverlaysVisibility {
|
||||||
.for_label(checkbox_id)
|
visible: optional_input.checked,
|
||||||
.widget_instance(),
|
overlays_type: Some(OverlaysType::TransformCage),
|
||||||
TextLabel::new("Quick Measurement".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
}
|
||||||
]
|
.into()
|
||||||
},
|
})
|
||||||
},
|
.for_label(checkbox_id)
|
||||||
LayoutGroup::Row {
|
.widget_instance(),
|
||||||
widgets: {
|
TextLabel::new("Transform Cage".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
||||||
let checkbox_id = CheckboxId::new();
|
]
|
||||||
vec![
|
}),
|
||||||
CheckboxInput::new(self.overlays_visibility_settings.transform_cage)
|
LayoutGroup::row({
|
||||||
.on_update(|optional_input: &CheckboxInput| {
|
let checkbox_id = CheckboxId::new();
|
||||||
DocumentMessage::SetOverlaysVisibility {
|
vec![
|
||||||
visible: optional_input.checked,
|
CheckboxInput::new(self.overlays_visibility_settings.compass_rose)
|
||||||
overlays_type: Some(OverlaysType::TransformCage),
|
.on_update(|optional_input: &CheckboxInput| {
|
||||||
}
|
DocumentMessage::SetOverlaysVisibility {
|
||||||
.into()
|
visible: optional_input.checked,
|
||||||
})
|
overlays_type: Some(OverlaysType::CompassRose),
|
||||||
.for_label(checkbox_id)
|
}
|
||||||
.widget_instance(),
|
.into()
|
||||||
TextLabel::new("Transform Cage".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
})
|
||||||
]
|
.for_label(checkbox_id)
|
||||||
},
|
.widget_instance(),
|
||||||
},
|
TextLabel::new("Transform Dial".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
||||||
LayoutGroup::Row {
|
]
|
||||||
widgets: {
|
}),
|
||||||
let checkbox_id = CheckboxId::new();
|
LayoutGroup::row({
|
||||||
vec![
|
let checkbox_id = CheckboxId::new();
|
||||||
CheckboxInput::new(self.overlays_visibility_settings.compass_rose)
|
vec![
|
||||||
.on_update(|optional_input: &CheckboxInput| {
|
CheckboxInput::new(self.overlays_visibility_settings.pivot)
|
||||||
DocumentMessage::SetOverlaysVisibility {
|
.on_update(|optional_input: &CheckboxInput| {
|
||||||
visible: optional_input.checked,
|
DocumentMessage::SetOverlaysVisibility {
|
||||||
overlays_type: Some(OverlaysType::CompassRose),
|
visible: optional_input.checked,
|
||||||
}
|
overlays_type: Some(OverlaysType::Pivot),
|
||||||
.into()
|
}
|
||||||
})
|
.into()
|
||||||
.for_label(checkbox_id)
|
})
|
||||||
.widget_instance(),
|
.for_label(checkbox_id)
|
||||||
TextLabel::new("Transform Dial".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
.widget_instance(),
|
||||||
]
|
TextLabel::new("Transform Pivot".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
||||||
},
|
]
|
||||||
},
|
}),
|
||||||
LayoutGroup::Row {
|
LayoutGroup::row({
|
||||||
widgets: {
|
let checkbox_id = CheckboxId::new();
|
||||||
let checkbox_id = CheckboxId::new();
|
vec![
|
||||||
vec![
|
CheckboxInput::new(self.overlays_visibility_settings.origin)
|
||||||
CheckboxInput::new(self.overlays_visibility_settings.pivot)
|
.on_update(|optional_input: &CheckboxInput| {
|
||||||
.on_update(|optional_input: &CheckboxInput| {
|
DocumentMessage::SetOverlaysVisibility {
|
||||||
DocumentMessage::SetOverlaysVisibility {
|
visible: optional_input.checked,
|
||||||
visible: optional_input.checked,
|
overlays_type: Some(OverlaysType::Origin),
|
||||||
overlays_type: Some(OverlaysType::Pivot),
|
}
|
||||||
}
|
.into()
|
||||||
.into()
|
})
|
||||||
})
|
.for_label(checkbox_id)
|
||||||
.for_label(checkbox_id)
|
.widget_instance(),
|
||||||
.widget_instance(),
|
TextLabel::new("Transform Origin".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
||||||
TextLabel::new("Transform Pivot".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
]
|
||||||
]
|
}),
|
||||||
},
|
LayoutGroup::row({
|
||||||
},
|
let checkbox_id = CheckboxId::new();
|
||||||
LayoutGroup::Row {
|
vec![
|
||||||
widgets: {
|
CheckboxInput::new(self.overlays_visibility_settings.hover_outline)
|
||||||
let checkbox_id = CheckboxId::new();
|
.on_update(|optional_input: &CheckboxInput| {
|
||||||
vec![
|
DocumentMessage::SetOverlaysVisibility {
|
||||||
CheckboxInput::new(self.overlays_visibility_settings.pivot)
|
visible: optional_input.checked,
|
||||||
.on_update(|optional_input: &CheckboxInput| {
|
overlays_type: Some(OverlaysType::HoverOutline),
|
||||||
DocumentMessage::SetOverlaysVisibility {
|
}
|
||||||
visible: optional_input.checked,
|
.into()
|
||||||
overlays_type: Some(OverlaysType::Origin),
|
})
|
||||||
}
|
.for_label(checkbox_id)
|
||||||
.into()
|
.widget_instance(),
|
||||||
})
|
TextLabel::new("Hover Outline".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
||||||
.for_label(checkbox_id)
|
]
|
||||||
.widget_instance(),
|
}),
|
||||||
TextLabel::new("Transform Origin".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
LayoutGroup::row({
|
||||||
]
|
let checkbox_id = CheckboxId::new();
|
||||||
},
|
vec![
|
||||||
},
|
CheckboxInput::new(self.overlays_visibility_settings.selection_outline)
|
||||||
LayoutGroup::Row {
|
.on_update(|optional_input: &CheckboxInput| {
|
||||||
widgets: {
|
DocumentMessage::SetOverlaysVisibility {
|
||||||
let checkbox_id = CheckboxId::new();
|
visible: optional_input.checked,
|
||||||
vec![
|
overlays_type: Some(OverlaysType::SelectionOutline),
|
||||||
CheckboxInput::new(self.overlays_visibility_settings.hover_outline)
|
}
|
||||||
.on_update(|optional_input: &CheckboxInput| {
|
.into()
|
||||||
DocumentMessage::SetOverlaysVisibility {
|
})
|
||||||
visible: optional_input.checked,
|
.for_label(checkbox_id)
|
||||||
overlays_type: Some(OverlaysType::HoverOutline),
|
.widget_instance(),
|
||||||
}
|
TextLabel::new("Selection Outline".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
||||||
.into()
|
]
|
||||||
})
|
}),
|
||||||
.for_label(checkbox_id)
|
LayoutGroup::row({
|
||||||
.widget_instance(),
|
let checkbox_id = CheckboxId::new();
|
||||||
TextLabel::new("Hover Outline".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
vec![
|
||||||
]
|
CheckboxInput::new(self.overlays_visibility_settings.layer_origin_cross)
|
||||||
},
|
.on_update(|optional_input: &CheckboxInput| {
|
||||||
},
|
DocumentMessage::SetOverlaysVisibility {
|
||||||
LayoutGroup::Row {
|
visible: optional_input.checked,
|
||||||
widgets: {
|
overlays_type: Some(OverlaysType::LayerOriginCross),
|
||||||
let checkbox_id = CheckboxId::new();
|
}
|
||||||
vec![
|
.into()
|
||||||
CheckboxInput::new(self.overlays_visibility_settings.selection_outline)
|
})
|
||||||
.on_update(|optional_input: &CheckboxInput| {
|
.for_label(checkbox_id)
|
||||||
DocumentMessage::SetOverlaysVisibility {
|
.widget_instance(),
|
||||||
visible: optional_input.checked,
|
TextLabel::new("Layer Origin".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
||||||
overlays_type: Some(OverlaysType::SelectionOutline),
|
]
|
||||||
}
|
}),
|
||||||
.into()
|
LayoutGroup::row(vec![TextLabel::new("Pen & Path Tools").widget_instance()]),
|
||||||
})
|
LayoutGroup::row({
|
||||||
.for_label(checkbox_id)
|
let checkbox_id = CheckboxId::new();
|
||||||
.widget_instance(),
|
vec![
|
||||||
TextLabel::new("Selection Outline".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
CheckboxInput::new(self.overlays_visibility_settings.path)
|
||||||
]
|
.on_update(|optional_input: &CheckboxInput| {
|
||||||
},
|
DocumentMessage::SetOverlaysVisibility {
|
||||||
},
|
visible: optional_input.checked,
|
||||||
LayoutGroup::Row {
|
overlays_type: Some(OverlaysType::Path),
|
||||||
widgets: {
|
}
|
||||||
let checkbox_id = CheckboxId::new();
|
.into()
|
||||||
vec![
|
})
|
||||||
CheckboxInput::new(self.overlays_visibility_settings.layer_origin_cross)
|
.for_label(checkbox_id)
|
||||||
.on_update(|optional_input: &CheckboxInput| {
|
.widget_instance(),
|
||||||
DocumentMessage::SetOverlaysVisibility {
|
TextLabel::new("Path".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
||||||
visible: optional_input.checked,
|
]
|
||||||
overlays_type: Some(OverlaysType::LayerOriginCross),
|
}),
|
||||||
}
|
LayoutGroup::row({
|
||||||
.into()
|
let checkbox_id = CheckboxId::new();
|
||||||
})
|
vec![
|
||||||
.for_label(checkbox_id)
|
CheckboxInput::new(self.overlays_visibility_settings.anchors)
|
||||||
.widget_instance(),
|
.on_update(|optional_input: &CheckboxInput| {
|
||||||
TextLabel::new("Layer Origin".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
DocumentMessage::SetOverlaysVisibility {
|
||||||
]
|
visible: optional_input.checked,
|
||||||
},
|
overlays_type: Some(OverlaysType::Anchors),
|
||||||
},
|
}
|
||||||
LayoutGroup::Row {
|
.into()
|
||||||
widgets: vec![TextLabel::new("Pen & Path Tools").widget_instance()],
|
})
|
||||||
},
|
.for_label(checkbox_id)
|
||||||
LayoutGroup::Row {
|
.widget_instance(),
|
||||||
widgets: {
|
TextLabel::new("Anchors".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
||||||
let checkbox_id = CheckboxId::new();
|
]
|
||||||
vec![
|
}),
|
||||||
CheckboxInput::new(self.overlays_visibility_settings.path)
|
LayoutGroup::row({
|
||||||
.on_update(|optional_input: &CheckboxInput| {
|
let checkbox_id = CheckboxId::new();
|
||||||
DocumentMessage::SetOverlaysVisibility {
|
vec![
|
||||||
visible: optional_input.checked,
|
CheckboxInput::new(self.overlays_visibility_settings.handles)
|
||||||
overlays_type: Some(OverlaysType::Path),
|
.disabled(!self.overlays_visibility_settings.anchors)
|
||||||
}
|
.on_update(|optional_input: &CheckboxInput| {
|
||||||
.into()
|
DocumentMessage::SetOverlaysVisibility {
|
||||||
})
|
visible: optional_input.checked,
|
||||||
.for_label(checkbox_id)
|
overlays_type: Some(OverlaysType::Handles),
|
||||||
.widget_instance(),
|
}
|
||||||
TextLabel::new("Path".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
.into()
|
||||||
]
|
})
|
||||||
},
|
.for_label(checkbox_id)
|
||||||
},
|
.widget_instance(),
|
||||||
LayoutGroup::Row {
|
TextLabel::new("Handles".to_string())
|
||||||
widgets: {
|
.disabled(!self.overlays_visibility_settings.anchors)
|
||||||
let checkbox_id = CheckboxId::new();
|
.for_checkbox(checkbox_id)
|
||||||
vec![
|
.widget_instance(),
|
||||||
CheckboxInput::new(self.overlays_visibility_settings.anchors)
|
]
|
||||||
.on_update(|optional_input: &CheckboxInput| {
|
}),
|
||||||
DocumentMessage::SetOverlaysVisibility {
|
|
||||||
visible: optional_input.checked,
|
|
||||||
overlays_type: Some(OverlaysType::Anchors),
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
.for_label(checkbox_id)
|
|
||||||
.widget_instance(),
|
|
||||||
TextLabel::new("Anchors".to_string()).for_checkbox(checkbox_id).widget_instance(),
|
|
||||||
]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
LayoutGroup::Row {
|
|
||||||
widgets: {
|
|
||||||
let checkbox_id = CheckboxId::new();
|
|
||||||
vec![
|
|
||||||
CheckboxInput::new(self.overlays_visibility_settings.handles)
|
|
||||||
.disabled(!self.overlays_visibility_settings.anchors)
|
|
||||||
.on_update(|optional_input: &CheckboxInput| {
|
|
||||||
DocumentMessage::SetOverlaysVisibility {
|
|
||||||
visible: optional_input.checked,
|
|
||||||
overlays_type: Some(OverlaysType::Handles),
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
})
|
|
||||||
.for_label(checkbox_id)
|
|
||||||
.widget_instance(),
|
|
||||||
TextLabel::new("Handles".to_string())
|
|
||||||
.disabled(!self.overlays_visibility_settings.anchors)
|
|
||||||
.for_checkbox(checkbox_id)
|
|
||||||
.widget_instance(),
|
|
||||||
]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]))
|
]))
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
@@ -2484,16 +2446,12 @@ impl DocumentMessageHandler {
|
|||||||
PopoverButton::new()
|
PopoverButton::new()
|
||||||
.popover_layout(Layout(
|
.popover_layout(Layout(
|
||||||
[
|
[
|
||||||
LayoutGroup::Row {
|
LayoutGroup::row(vec![TextLabel::new("Snapping").bold(true).widget_instance()]),
|
||||||
widgets: vec![TextLabel::new("Snapping").bold(true).widget_instance()],
|
LayoutGroup::row(vec![TextLabel::new(SnappingOptions::BoundingBoxes.to_string()).widget_instance()]),
|
||||||
},
|
|
||||||
LayoutGroup::Row {
|
|
||||||
widgets: vec![TextLabel::new(SnappingOptions::BoundingBoxes.to_string()).widget_instance()],
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(SNAP_FUNCTIONS_FOR_BOUNDING_BOXES.into_iter().map(|(name, closure, description)| LayoutGroup::Row {
|
.chain(SNAP_FUNCTIONS_FOR_BOUNDING_BOXES.into_iter().map(|(name, closure, description)| {
|
||||||
widgets: {
|
LayoutGroup::row({
|
||||||
let checkbox_id = CheckboxId::new();
|
let checkbox_id = CheckboxId::new();
|
||||||
vec![
|
vec![
|
||||||
CheckboxInput::new(*closure(&mut snapping_state))
|
CheckboxInput::new(*closure(&mut snapping_state))
|
||||||
@@ -2510,13 +2468,11 @@ impl DocumentMessageHandler {
|
|||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
TextLabel::new(name).tooltip_label(name).tooltip_description(description).for_checkbox(checkbox_id).widget_instance(),
|
TextLabel::new(name).tooltip_label(name).tooltip_description(description).for_checkbox(checkbox_id).widget_instance(),
|
||||||
]
|
]
|
||||||
},
|
})
|
||||||
}))
|
}))
|
||||||
.chain([LayoutGroup::Row {
|
.chain([LayoutGroup::row(vec![TextLabel::new(SnappingOptions::Paths.to_string()).widget_instance()])])
|
||||||
widgets: vec![TextLabel::new(SnappingOptions::Paths.to_string()).widget_instance()],
|
.chain(SNAP_FUNCTIONS_FOR_PATHS.into_iter().map(|(name, closure, description)| {
|
||||||
}])
|
LayoutGroup::row({
|
||||||
.chain(SNAP_FUNCTIONS_FOR_PATHS.into_iter().map(|(name, closure, description)| LayoutGroup::Row {
|
|
||||||
widgets: {
|
|
||||||
let checkbox_id = CheckboxId::new();
|
let checkbox_id = CheckboxId::new();
|
||||||
vec![
|
vec![
|
||||||
CheckboxInput::new(*closure(&mut snapping_state2))
|
CheckboxInput::new(*closure(&mut snapping_state2))
|
||||||
@@ -2533,7 +2489,7 @@ impl DocumentMessageHandler {
|
|||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
TextLabel::new(name).tooltip_label(name).tooltip_description(description).for_checkbox(checkbox_id).widget_instance(),
|
TextLabel::new(name).tooltip_label(name).tooltip_description(description).for_checkbox(checkbox_id).widget_instance(),
|
||||||
]
|
]
|
||||||
},
|
})
|
||||||
}))
|
}))
|
||||||
.collect(),
|
.collect(),
|
||||||
))
|
))
|
||||||
@@ -2630,8 +2586,8 @@ impl DocumentMessageHandler {
|
|||||||
widgets.extend([
|
widgets.extend([
|
||||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||||
TextButton::new("Node Graph")
|
TextButton::new("Node Graph")
|
||||||
.icon(Some((if self.graph_view_overlay_open { "GraphViewOpen" } else { "GraphViewClosed" }).into()))
|
.icon(if self.graph_view_overlay_open { "GraphViewOpen" } else { "GraphViewClosed" })
|
||||||
.hover_icon(Some((if self.graph_view_overlay_open { "GraphViewClosed" } else { "GraphViewOpen" }).into()))
|
.hover_icon(if self.graph_view_overlay_open { "GraphViewClosed" } else { "GraphViewOpen" })
|
||||||
.tooltip_label(if self.graph_view_overlay_open { "Hide Node Graph" } else { "Show Node Graph" })
|
.tooltip_label(if self.graph_view_overlay_open { "Hide Node Graph" } else { "Show Node Graph" })
|
||||||
.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
|
.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
|
||||||
.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())
|
.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())
|
||||||
@@ -2639,7 +2595,7 @@ impl DocumentMessageHandler {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
responses.add(LayoutMessage::SendLayout {
|
responses.add(LayoutMessage::SendLayout {
|
||||||
layout: Layout(vec![LayoutGroup::Row { widgets }]),
|
layout: Layout(vec![LayoutGroup::row(widgets)]),
|
||||||
layout_target: LayoutTarget::DocumentBar,
|
layout_target: LayoutTarget::DocumentBar,
|
||||||
});
|
});
|
||||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||||
@@ -2777,25 +2733,25 @@ impl DocumentMessageHandler {
|
|||||||
.tooltip_label("Fill")
|
.tooltip_label("Fill")
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
];
|
];
|
||||||
let layers_panel_control_bar_left = Layout(vec![LayoutGroup::Row { widgets }]);
|
let layers_panel_control_bar_left = Layout(vec![LayoutGroup::row(widgets)]);
|
||||||
|
|
||||||
let widgets = vec![
|
let widgets = vec![
|
||||||
IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24)
|
IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24)
|
||||||
.hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into()))
|
.hover_icon(if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" })
|
||||||
.tooltip_label(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
|
.tooltip_label(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
|
||||||
.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleSelectedLocked))
|
.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleSelectedLocked))
|
||||||
.on_update(|_| NodeGraphMessage::ToggleSelectedLocked.into())
|
.on_update(|_| NodeGraphMessage::ToggleSelectedLocked.into())
|
||||||
.disabled(!has_selection)
|
.disabled(!has_selection)
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
IconButton::new(if selection_all_visible { "EyeVisible" } else { "EyeHidden" }, 24)
|
IconButton::new(if selection_all_visible { "EyeVisible" } else { "EyeHidden" }, 24)
|
||||||
.hover_icon(Some((if selection_all_visible { "EyeHide" } else { "EyeShow" }).into()))
|
.hover_icon(if selection_all_visible { "EyeHide" } else { "EyeShow" })
|
||||||
.tooltip_label(if selection_all_visible { "Hide Selected" } else { "Show Selected" })
|
.tooltip_label(if selection_all_visible { "Hide Selected" } else { "Show Selected" })
|
||||||
.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleSelectedVisibility))
|
.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::ToggleSelectedVisibility))
|
||||||
.on_update(|_| DocumentMessage::ToggleSelectedVisibility.into())
|
.on_update(|_| DocumentMessage::ToggleSelectedVisibility.into())
|
||||||
.disabled(!has_selection)
|
.disabled(!has_selection)
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
];
|
];
|
||||||
let layers_panel_control_bar_right = Layout(vec![LayoutGroup::Row { widgets }]);
|
let layers_panel_control_bar_right = Layout(vec![LayoutGroup::row(widgets)]);
|
||||||
|
|
||||||
responses.add(LayoutMessage::SendLayout {
|
responses.add(LayoutMessage::SendLayout {
|
||||||
layout: layers_panel_control_bar_left,
|
layout: layers_panel_control_bar_left,
|
||||||
@@ -2821,7 +2777,7 @@ impl DocumentMessageHandler {
|
|||||||
|
|
||||||
let widgets = vec![
|
let widgets = vec![
|
||||||
PopoverButton::new()
|
PopoverButton::new()
|
||||||
.icon(Some("Node".to_string()))
|
.icon("Node")
|
||||||
.menu_direction(Some(MenuDirection::Top))
|
.menu_direction(Some(MenuDirection::Top))
|
||||||
.tooltip_description("Add an operation to the end of this layer's chain of nodes.")
|
.tooltip_description("Add an operation to the end of this layer's chain of nodes.")
|
||||||
.disabled(!has_selection || has_multiple_selection)
|
.disabled(!has_selection || has_multiple_selection)
|
||||||
@@ -2849,7 +2805,7 @@ impl DocumentMessageHandler {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.widget_instance();
|
.widget_instance();
|
||||||
Layout(vec![LayoutGroup::Row { widgets: vec![node_chooser] }])
|
Layout(vec![LayoutGroup::row(vec![node_chooser])])
|
||||||
})
|
})
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||||
@@ -2875,7 +2831,7 @@ impl DocumentMessageHandler {
|
|||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
];
|
];
|
||||||
responses.add(LayoutMessage::SendLayout {
|
responses.add(LayoutMessage::SendLayout {
|
||||||
layout: Layout(vec![LayoutGroup::Row { widgets }]),
|
layout: Layout(vec![LayoutGroup::row(widgets)]),
|
||||||
layout_target: LayoutTarget::LayersPanelBottomBar,
|
layout_target: LayoutTarget::LayersPanelBottomBar,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ impl NodePropertiesContext<'_> {
|
|||||||
/// The key used to access definitions for a network node or proto node.
|
/// The key used to access definitions for a network node or proto node.
|
||||||
/// For proto nodes, this is their [`ProtoNodeIdentifier`].
|
/// For proto nodes, this is their [`ProtoNodeIdentifier`].
|
||||||
/// For network nodes, it doesn't necessarily have to be the same as the network's display name, but it often is.
|
/// For network nodes, it doesn't necessarily have to be the same as the network's display name, but it often is.
|
||||||
#[derive(Debug, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(tag = "type", content = "data")]
|
#[serde(tag = "type", content = "data")]
|
||||||
pub enum DefinitionIdentifier {
|
pub enum DefinitionIdentifier {
|
||||||
ProtoNode(ProtoNodeIdentifier),
|
ProtoNode(ProtoNodeIdentifier),
|
||||||
@@ -2191,9 +2192,10 @@ fn static_input_properties() -> InputProperties {
|
|||||||
true
|
true
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(vec![LayoutGroup::Row {
|
Ok(vec![LayoutGroup::row(node_properties::number_widget(
|
||||||
widgets: node_properties::number_widget(ParameterWidgetsInfo::new(node_id, index, blank_assist, context), number_input),
|
ParameterWidgetsInfo::new(node_id, index, blank_assist, context),
|
||||||
}])
|
number_input,
|
||||||
|
))])
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
map.insert(
|
map.insert(
|
||||||
@@ -2227,10 +2229,12 @@ fn static_input_properties() -> InputProperties {
|
|||||||
number_input = number_input.step(number_step);
|
number_input = number_input.step(number_step);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(vec![LayoutGroup::Row {
|
// NOTE: The bool input MUST be at the input index directly before the f64 input!
|
||||||
// NOTE: The bool input MUST be at the input index directly before the f64 input!
|
Ok(vec![LayoutGroup::row(node_properties::optional_f64_widget(
|
||||||
widgets: node_properties::optional_f64_widget(ParameterWidgetsInfo::new(node_id, index, false, context), index - 1, number_input),
|
ParameterWidgetsInfo::new(node_id, index, false, context),
|
||||||
}])
|
index - 1,
|
||||||
|
number_input,
|
||||||
|
))])
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
map.insert(
|
map.insert(
|
||||||
@@ -2298,7 +2302,7 @@ fn static_input_properties() -> InputProperties {
|
|||||||
"noise_properties_noise_type".to_string(),
|
"noise_properties_noise_type".to_string(),
|
||||||
Box::new(|node_id, index, context| {
|
Box::new(|node_id, index, context| {
|
||||||
let noise_type_row = enum_choice::<NoiseType>().for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)).property_row();
|
let noise_type_row = enum_choice::<NoiseType>().for_socket(ParameterWidgetsInfo::new(node_id, index, true, context)).property_row();
|
||||||
Ok(vec![noise_type_row, LayoutGroup::Row { widgets: Vec::new() }])
|
Ok(vec![noise_type_row, LayoutGroup::row(Vec::new())])
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
map.insert(
|
map.insert(
|
||||||
@@ -2320,7 +2324,7 @@ fn static_input_properties() -> InputProperties {
|
|||||||
ParameterWidgetsInfo::new(node_id, index, true, context),
|
ParameterWidgetsInfo::new(node_id, index, true, context),
|
||||||
NumberInput::default().min(0.).disabled(!coherent_noise_active || !domain_warp_active),
|
NumberInput::default().min(0.).disabled(!coherent_noise_active || !domain_warp_active),
|
||||||
);
|
);
|
||||||
Ok(vec![domain_warp_amplitude.into(), LayoutGroup::Row { widgets: Vec::new() }])
|
Ok(vec![domain_warp_amplitude.into(), LayoutGroup::row(Vec::new())])
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
map.insert(
|
map.insert(
|
||||||
@@ -2408,7 +2412,7 @@ fn static_input_properties() -> InputProperties {
|
|||||||
.range_max(Some(10.))
|
.range_max(Some(10.))
|
||||||
.disabled(!ping_pong_active || !coherent_noise_active || !fractal_active || domain_warp_only_fractal_type_wrongly_active),
|
.disabled(!ping_pong_active || !coherent_noise_active || !fractal_active || domain_warp_only_fractal_type_wrongly_active),
|
||||||
);
|
);
|
||||||
Ok(vec![fractal_ping_pong_strength.into(), LayoutGroup::Row { widgets: Vec::new() }])
|
Ok(vec![fractal_ping_pong_strength.into(), LayoutGroup::row(Vec::new())])
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
map.insert(
|
map.insert(
|
||||||
@@ -2504,7 +2508,7 @@ fn static_input_properties() -> InputProperties {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(vec![LayoutGroup::Row { widgets }])
|
Ok(vec![LayoutGroup::row(widgets)])
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
// Skew has a custom override that maps to degrees
|
// Skew has a custom override that maps to degrees
|
||||||
@@ -2548,24 +2552,20 @@ fn static_input_properties() -> InputProperties {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(vec![LayoutGroup::Row { widgets }])
|
Ok(vec![LayoutGroup::row(widgets)])
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
map.insert(
|
map.insert(
|
||||||
"text_area".to_string(),
|
"text_area".to_string(),
|
||||||
Box::new(|node_id, index, context| {
|
Box::new(|node_id, index, context| Ok(vec![LayoutGroup::row(node_properties::text_area_widget(ParameterWidgetsInfo::new(node_id, index, true, context)))])),
|
||||||
Ok(vec![LayoutGroup::Row {
|
|
||||||
widgets: node_properties::text_area_widget(ParameterWidgetsInfo::new(node_id, index, true, context)),
|
|
||||||
}])
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
map.insert(
|
map.insert(
|
||||||
"text_font".to_string(),
|
"text_font".to_string(),
|
||||||
Box::new(|node_id, index, context| {
|
Box::new(|node_id, index, context| {
|
||||||
let (font, style) = node_properties::font_inputs(ParameterWidgetsInfo::new(node_id, index, true, context));
|
let (font, style) = node_properties::font_inputs(ParameterWidgetsInfo::new(node_id, index, true, context));
|
||||||
let mut result = vec![LayoutGroup::Row { widgets: font }];
|
let mut result = vec![LayoutGroup::row(font)];
|
||||||
if let Some(style) = style {
|
if let Some(style) = style {
|
||||||
result.push(LayoutGroup::Row { widgets: style });
|
result.push(LayoutGroup::row(style));
|
||||||
}
|
}
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -847,7 +847,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.context_menu = Some(ContextMenuInformation {
|
self.context_menu = Some(ContextMenuInformation {
|
||||||
context_menu_coordinates: (node_graph_point + node_graph_shift).as_ivec2(),
|
context_menu_coordinates: (node_graph_point + node_graph_shift).as_ivec2().into(),
|
||||||
context_menu_data,
|
context_menu_data,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1280,7 +1280,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
let compatible_type = network_interface.output_type(&output_connector, selection_network_path).add_node_string();
|
let compatible_type = network_interface.output_type(&output_connector, selection_network_path).add_node_string();
|
||||||
|
|
||||||
self.context_menu = Some(ContextMenuInformation {
|
self.context_menu = Some(ContextMenuInformation {
|
||||||
context_menu_coordinates: (point + node_graph_shift).as_ivec2(),
|
context_menu_coordinates: (point + node_graph_shift).as_ivec2().into(),
|
||||||
context_menu_data: ContextMenuData::CreateNode { compatible_type },
|
context_menu_data: ContextMenuData::CreateNode { compatible_type },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2050,8 +2050,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphMessageContext<'a>> for NodeG
|
|||||||
responses.add(FrontendMessage::UpdateImportsExports {
|
responses.add(FrontendMessage::UpdateImportsExports {
|
||||||
imports,
|
imports,
|
||||||
exports,
|
exports,
|
||||||
import_position,
|
import_position: import_position.into(),
|
||||||
export_position,
|
export_position: export_position.into(),
|
||||||
add_import_export,
|
add_import_export,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2181,7 +2181,7 @@ impl NodeGraphMessageHandler {
|
|||||||
|
|
||||||
let mut widgets = vec![
|
let mut widgets = vec![
|
||||||
PopoverButton::new()
|
PopoverButton::new()
|
||||||
.icon(Some("Node".to_string()))
|
.icon("Node")
|
||||||
.tooltip_label("New Node")
|
.tooltip_label("New Node")
|
||||||
.tooltip_description("To add a node at the pointer location, perform the shortcut in an open area of the graph.")
|
.tooltip_description("To add a node at the pointer location, perform the shortcut in an open area of the graph.")
|
||||||
.tooltip_shortcut(action_shortcut_manual!(Key::MouseRight))
|
.tooltip_shortcut(action_shortcut_manual!(Key::MouseRight))
|
||||||
@@ -2222,7 +2222,7 @@ impl NodeGraphMessageHandler {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.widget_instance();
|
.widget_instance();
|
||||||
Layout(vec![LayoutGroup::Row { widgets: vec![node_chooser] }])
|
Layout(vec![LayoutGroup::row(vec![node_chooser])])
|
||||||
})
|
})
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
//
|
//
|
||||||
@@ -2252,14 +2252,14 @@ impl NodeGraphMessageHandler {
|
|||||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||||
//
|
//
|
||||||
IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24)
|
IconButton::new(if selection_all_locked { "PadlockLocked" } else { "PadlockUnlocked" }, 24)
|
||||||
.hover_icon(Some((if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" }).into()))
|
.hover_icon(if selection_all_locked { "PadlockUnlocked" } else { "PadlockLocked" })
|
||||||
.tooltip_label(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
|
.tooltip_label(if selection_all_locked { "Unlock Selected" } else { "Lock Selected" })
|
||||||
.tooltip_shortcut(action_shortcut!(NodeGraphMessageDiscriminant::ToggleSelectedLocked))
|
.tooltip_shortcut(action_shortcut!(NodeGraphMessageDiscriminant::ToggleSelectedLocked))
|
||||||
.on_update(|_| NodeGraphMessage::ToggleSelectedLocked.into())
|
.on_update(|_| NodeGraphMessage::ToggleSelectedLocked.into())
|
||||||
.disabled(!has_selection || !selection_includes_layers)
|
.disabled(!has_selection || !selection_includes_layers)
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
IconButton::new(if selection_all_visible { "EyeVisible" } else { "EyeHidden" }, 24)
|
IconButton::new(if selection_all_visible { "EyeVisible" } else { "EyeHidden" }, 24)
|
||||||
.hover_icon(Some((if selection_all_visible { "EyeHide" } else { "EyeShow" }).into()))
|
.hover_icon(if selection_all_visible { "EyeHide" } else { "EyeShow" })
|
||||||
.tooltip_label(if selection_all_visible { "Hide Selected" } else { "Show Selected" })
|
.tooltip_label(if selection_all_visible { "Hide Selected" } else { "Show Selected" })
|
||||||
.tooltip_shortcut(action_shortcut!(NodeGraphMessageDiscriminant::ToggleSelectedVisibility))
|
.tooltip_shortcut(action_shortcut!(NodeGraphMessageDiscriminant::ToggleSelectedVisibility))
|
||||||
.on_update(|_| NodeGraphMessage::ToggleSelectedVisibility.into())
|
.on_update(|_| NodeGraphMessage::ToggleSelectedVisibility.into())
|
||||||
@@ -2286,7 +2286,7 @@ impl NodeGraphMessageHandler {
|
|||||||
// If only one node is selected then show the preview or stop previewing button
|
// If only one node is selected then show the preview or stop previewing button
|
||||||
if let Some(node_id) = previewing {
|
if let Some(node_id) = previewing {
|
||||||
let button = TextButton::new("End Preview")
|
let button = TextButton::new("End Preview")
|
||||||
.icon(Some("FrameAll".to_string()))
|
.icon("FrameAll")
|
||||||
.tooltip_description("Restore preview to the graph output.")
|
.tooltip_description("Restore preview to the graph output.")
|
||||||
.on_update(move |_| NodeGraphMessage::TogglePreview { node_id }.into())
|
.on_update(move |_| NodeGraphMessage::TogglePreview { node_id }.into())
|
||||||
.widget_instance();
|
.widget_instance();
|
||||||
@@ -2298,7 +2298,7 @@ impl NodeGraphMessageHandler {
|
|||||||
.any(|export| matches!(export, NodeInput::Node { node_id: export_node_id, .. } if *export_node_id == node_id));
|
.any(|export| matches!(export, NodeInput::Node { node_id: export_node_id, .. } if *export_node_id == node_id));
|
||||||
if selection_is_not_already_the_output && no_other_selections {
|
if selection_is_not_already_the_output && no_other_selections {
|
||||||
let button = TextButton::new("Preview")
|
let button = TextButton::new("Preview")
|
||||||
.icon(Some("FrameAll".to_string()))
|
.icon("FrameAll")
|
||||||
.tooltip_label("Preview")
|
.tooltip_label("Preview")
|
||||||
.tooltip_description("Temporarily set the graph output to the selected node or layer. Perform the shortcut on a node or layer for quick access.")
|
.tooltip_description("Temporarily set the graph output to the selected node or layer. Perform the shortcut on a node or layer for quick access.")
|
||||||
.tooltip_shortcut(action_shortcut_manual!(Key::Alt, Key::MouseLeft))
|
.tooltip_shortcut(action_shortcut_manual!(Key::Alt, Key::MouseLeft))
|
||||||
@@ -2323,7 +2323,7 @@ impl NodeGraphMessageHandler {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.widgets[0] = LayoutGroup::Row { widgets };
|
self.widgets[0] = LayoutGroup::row(widgets);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_graph_bar_right(
|
fn update_graph_bar_right(
|
||||||
@@ -2357,15 +2357,15 @@ impl NodeGraphMessageHandler {
|
|||||||
widgets.extend([
|
widgets.extend([
|
||||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||||
TextButton::new("Node Graph")
|
TextButton::new("Node Graph")
|
||||||
.icon(Some("GraphViewOpen".into()))
|
.icon("GraphViewOpen")
|
||||||
.hover_icon(Some("GraphViewClosed".into()))
|
.hover_icon("GraphViewClosed")
|
||||||
.tooltip_label("Hide Node Graph")
|
.tooltip_label("Hide Node Graph")
|
||||||
.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
|
.tooltip_shortcut(action_shortcut!(DocumentMessageDiscriminant::GraphViewOverlayToggle))
|
||||||
.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())
|
.on_update(move |_| DocumentMessage::GraphViewOverlayToggle.into())
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
self.widgets[1] = LayoutGroup::Row { widgets };
|
self.widgets[1] = LayoutGroup::row(widgets);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collate the properties panel sections for a node graph
|
/// Collate the properties panel sections for a node graph
|
||||||
@@ -2407,25 +2407,23 @@ impl NodeGraphMessageHandler {
|
|||||||
let mut properties = Vec::new();
|
let mut properties = Vec::new();
|
||||||
|
|
||||||
if let [node_id] = *nodes.as_slice() {
|
if let [node_id] = *nodes.as_slice() {
|
||||||
properties.push(LayoutGroup::Row {
|
properties.push(LayoutGroup::row(vec![
|
||||||
widgets: vec![
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
IconLabel::new("Node").tooltip_description("Name of the selected node.").widget_instance(),
|
||||||
IconLabel::new("Node").tooltip_description("Name of the selected node.").widget_instance(),
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
TextInput::new(context.network_interface.display_name(&node_id, context.selection_network_path))
|
||||||
TextInput::new(context.network_interface.display_name(&node_id, context.selection_network_path))
|
.tooltip_description("Name of the selected node.")
|
||||||
.tooltip_description("Name of the selected node.")
|
.on_update(move |text_input| {
|
||||||
.on_update(move |text_input| {
|
NodeGraphMessage::SetDisplayName {
|
||||||
NodeGraphMessage::SetDisplayName {
|
node_id,
|
||||||
node_id,
|
alias: text_input.value.clone(),
|
||||||
alias: text_input.value.clone(),
|
skip_adding_history_step: false,
|
||||||
skip_adding_history_step: false,
|
}
|
||||||
}
|
.into()
|
||||||
.into()
|
})
|
||||||
})
|
.widget_instance(),
|
||||||
.widget_instance(),
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
]));
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
properties.extend(selected_nodes);
|
properties.extend(selected_nodes);
|
||||||
@@ -2435,18 +2433,16 @@ impl NodeGraphMessageHandler {
|
|||||||
|
|
||||||
// TODO: Display properties for encapsulating node when no nodes are selected in a nested network
|
// TODO: Display properties for encapsulating node when no nodes are selected in a nested network
|
||||||
// This may require store a separate path for the properties panel
|
// This may require store a separate path for the properties panel
|
||||||
let mut properties = vec![LayoutGroup::Row {
|
let mut properties = vec![LayoutGroup::row(vec![
|
||||||
widgets: vec![
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
IconLabel::new("File").tooltip_description("Name of the current document.").widget_instance(),
|
||||||
IconLabel::new("File").tooltip_description("Name of the current document.").widget_instance(),
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
TextInput::new(context.document_name)
|
||||||
TextInput::new(context.document_name)
|
.tooltip_description("Name of the current document.")
|
||||||
.tooltip_description("Name of the current document.")
|
.on_update(|text_input| DocumentMessage::RenameDocument { new_name: text_input.value.clone() }.into())
|
||||||
.on_update(|text_input| DocumentMessage::RenameDocument { new_name: text_input.value.clone() }.into())
|
.widget_instance(),
|
||||||
.widget_instance(),
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
])];
|
||||||
],
|
|
||||||
}];
|
|
||||||
|
|
||||||
let Some(network) = context.network_interface.nested_network(context.selection_network_path) else {
|
let Some(network) = context.network_interface.nested_network(context.selection_network_path) else {
|
||||||
warn!("No network in collate_properties");
|
warn!("No network in collate_properties");
|
||||||
@@ -2482,50 +2478,48 @@ impl NodeGraphMessageHandler {
|
|||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut layer_properties = vec![LayoutGroup::Row {
|
let mut layer_properties = vec![LayoutGroup::row(vec![
|
||||||
widgets: vec![
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
IconLabel::new("Layer").tooltip_description("Name of the selected layer.").widget_instance(),
|
||||||
IconLabel::new("Layer").tooltip_description("Name of the selected layer.").widget_instance(),
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
TextInput::new(context.network_interface.display_name(&layer, context.selection_network_path))
|
||||||
TextInput::new(context.network_interface.display_name(&layer, context.selection_network_path))
|
.tooltip_description("Name of the selected layer.")
|
||||||
.tooltip_description("Name of the selected layer.")
|
.on_update(move |text_input| {
|
||||||
.on_update(move |text_input| {
|
NodeGraphMessage::SetDisplayName {
|
||||||
NodeGraphMessage::SetDisplayName {
|
node_id: layer,
|
||||||
node_id: layer,
|
alias: text_input.value.clone(),
|
||||||
alias: text_input.value.clone(),
|
skip_adding_history_step: false,
|
||||||
skip_adding_history_step: false,
|
}
|
||||||
}
|
.into()
|
||||||
.into()
|
})
|
||||||
})
|
.widget_instance(),
|
||||||
.widget_instance(),
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
PopoverButton::new()
|
||||||
PopoverButton::new()
|
.icon("Node")
|
||||||
.icon(Some("Node".to_string()))
|
.tooltip_description("Add an operation to the end of this layer's chain of nodes.")
|
||||||
.tooltip_description("Add an operation to the end of this layer's chain of nodes.")
|
.popover_layout({
|
||||||
.popover_layout({
|
let compatible_type = context
|
||||||
let compatible_type = context
|
.network_interface
|
||||||
.network_interface
|
.upstream_output_connector(&InputConnector::node(layer, 1), &[])
|
||||||
.upstream_output_connector(&InputConnector::node(layer, 1), &[])
|
.and_then(|upstream_output| context.network_interface.output_type(&upstream_output, &[]).add_node_string());
|
||||||
.and_then(|upstream_output| context.network_interface.output_type(&upstream_output, &[]).add_node_string());
|
|
||||||
|
|
||||||
let mut node_chooser = NodeCatalog::new();
|
let mut node_chooser = NodeCatalog::new();
|
||||||
node_chooser.intial_search = compatible_type.unwrap_or("".to_string());
|
node_chooser.intial_search = compatible_type.unwrap_or("".to_string());
|
||||||
|
|
||||||
let node_chooser = node_chooser
|
let node_chooser = node_chooser
|
||||||
.on_update(move |node_type| {
|
.on_update(move |node_type| {
|
||||||
NodeGraphMessage::CreateNodeInLayerWithTransaction {
|
NodeGraphMessage::CreateNodeInLayerWithTransaction {
|
||||||
node_type: node_type.clone(),
|
node_type: node_type.clone(),
|
||||||
layer: LayerNodeIdentifier::new_unchecked(layer),
|
layer: LayerNodeIdentifier::new_unchecked(layer),
|
||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
})
|
})
|
||||||
.widget_instance();
|
.widget_instance();
|
||||||
Layout(vec![LayoutGroup::Row { widgets: vec![node_chooser] }])
|
Layout(vec![LayoutGroup::row(vec![node_chooser])])
|
||||||
})
|
})
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
],
|
])];
|
||||||
}];
|
|
||||||
|
|
||||||
// Iterate through all the upstream nodes, but stop when we reach another layer (since that's a point where we switch from horizontal to vertical flow)
|
// Iterate through all the upstream nodes, but stop when we reach another layer (since that's a point where we switch from horizontal to vertical flow)
|
||||||
let node_properties = context
|
let node_properties = context
|
||||||
@@ -2651,7 +2645,7 @@ impl NodeGraphMessageHandler {
|
|||||||
exposed_outputs,
|
exposed_outputs,
|
||||||
primary_output_connected_to_layer,
|
primary_output_connected_to_layer,
|
||||||
primary_input_connected_to_layer,
|
primary_input_connected_to_layer,
|
||||||
position,
|
position: position.into(),
|
||||||
previewed,
|
previewed,
|
||||||
visible,
|
visible,
|
||||||
locked,
|
locked,
|
||||||
@@ -2695,6 +2689,7 @@ impl NodeGraphMessageHandler {
|
|||||||
if network_interface.is_layer(&error_node, breadcrumb_network_path) {
|
if network_interface.is_layer(&error_node, breadcrumb_network_path) {
|
||||||
position += IVec2::new(12, -12)
|
position += IVec2::new(12, -12)
|
||||||
}
|
}
|
||||||
|
let position = position.into();
|
||||||
|
|
||||||
Some(NodeGraphErrorDiagnostic { position, error })
|
Some(NodeGraphErrorDiagnostic { position, error })
|
||||||
}
|
}
|
||||||
@@ -2841,7 +2836,7 @@ impl Default for NodeGraphMessageHandler {
|
|||||||
Self {
|
Self {
|
||||||
network: Vec::new(),
|
network: Vec::new(),
|
||||||
has_selection: false,
|
has_selection: false,
|
||||||
widgets: [LayoutGroup::Row { widgets: Vec::new() }, LayoutGroup::Row { widgets: Vec::new() }],
|
widgets: [LayoutGroup::row(Vec::new()), LayoutGroup::row(Vec::new())],
|
||||||
drag_start: None,
|
drag_start: None,
|
||||||
begin_dragging: false,
|
begin_dragging: false,
|
||||||
node_has_moved_in_drag: false,
|
node_has_moved_in_drag: false,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ use graphene_std::vector::style::{Fill, FillChoice, FillType, GradientStops, Gra
|
|||||||
|
|
||||||
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
|
pub(crate) fn string_properties(text: &str) -> Vec<LayoutGroup> {
|
||||||
let widget = TextLabel::new(text).widget_instance();
|
let widget = TextLabel::new(text).widget_instance();
|
||||||
vec![LayoutGroup::Row { widgets: vec![widget] }]
|
vec![LayoutGroup::row(vec![widget])]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn optionally_update_value<T>(value: impl Fn(&T) -> Option<TaggedValue> + 'static + Send + Sync, node_id: NodeId, input_index: usize) -> impl Fn(&T) -> Message + 'static + Send + Sync {
|
fn optionally_update_value<T>(value: impl Fn(&T) -> Option<TaggedValue> + 'static + Send + Sync, node_id: NodeId, input_index: usize) -> impl Fn(&T) -> Message + 'static + Send + Sync {
|
||||||
@@ -538,11 +538,7 @@ pub fn footprint_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let widgets = [
|
let widgets = [LayoutGroup::row(location_widgets), LayoutGroup::row(scale_widgets), LayoutGroup::row(resolution_widgets)];
|
||||||
LayoutGroup::Row { widgets: location_widgets },
|
|
||||||
LayoutGroup::Row { widgets: scale_widgets },
|
|
||||||
LayoutGroup::Row { widgets: resolution_widgets },
|
|
||||||
];
|
|
||||||
let (last, rest) = widgets.split_last().expect("Footprint widget should return multiple rows");
|
let (last, rest) = widgets.split_last().expect("Footprint widget should return multiple rows");
|
||||||
*extra_widgets = rest.to_vec();
|
*extra_widgets = rest.to_vec();
|
||||||
last.clone()
|
last.clone()
|
||||||
@@ -651,13 +647,9 @@ pub fn transform_widget(parameter_widgets_info: ParameterWidgetsInfo, extra_widg
|
|||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
vec![
|
vec![LayoutGroup::row(location_widgets), LayoutGroup::row(rotation_widgets), LayoutGroup::row(scale_widgets)]
|
||||||
LayoutGroup::Row { widgets: location_widgets },
|
|
||||||
LayoutGroup::Row { widgets: rotation_widgets },
|
|
||||||
LayoutGroup::Row { widgets: scale_widgets },
|
|
||||||
]
|
|
||||||
} else {
|
} else {
|
||||||
vec![LayoutGroup::Row { widgets: location_widgets }]
|
vec![LayoutGroup::row(location_widgets)]
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((last, rest)) = widgets.split_last() {
|
if let Some((last, rest)) = widgets.split_last() {
|
||||||
@@ -676,7 +668,7 @@ pub fn vec2_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &st
|
|||||||
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||||
let Some(input) = document_node.inputs.get(index) else {
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return LayoutGroup::Row { widgets: vec![] };
|
return LayoutGroup::row(vec![]);
|
||||||
};
|
};
|
||||||
match input.as_non_exposed_value() {
|
match input.as_non_exposed_value() {
|
||||||
Some(&TaggedValue::DVec2(dvec2)) => {
|
Some(&TaggedValue::DVec2(dvec2)) => {
|
||||||
@@ -730,7 +722,7 @@ pub fn vec2_widget(parameter_widgets_info: ParameterWidgetsInfo, x: &str, y: &st
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
LayoutGroup::Row { widgets }
|
LayoutGroup::row(widgets)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text_input: TextInput) -> Vec<WidgetInstance> {
|
pub fn array_of_number_widget(parameter_widgets_info: ParameterWidgetsInfo, text_input: TextInput) -> Vec<WidgetInstance> {
|
||||||
@@ -1101,7 +1093,7 @@ pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Layout
|
|||||||
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||||
let Some(input) = document_node.inputs.get(index) else {
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return LayoutGroup::Row { widgets: vec![] };
|
return LayoutGroup::row(vec![]);
|
||||||
};
|
};
|
||||||
if let Some(&TaggedValue::BlendMode(blend_mode)) = input.as_non_exposed_value() {
|
if let Some(&TaggedValue::BlendMode(blend_mode)) = input.as_non_exposed_value() {
|
||||||
let entries = BlendMode::list_svg_subset()
|
let entries = BlendMode::list_svg_subset()
|
||||||
@@ -1126,7 +1118,7 @@ pub fn blend_mode_widget(parameter_widgets_info: ParameterWidgetsInfo) -> Layout
|
|||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
LayoutGroup::Row { widgets }.with_tooltip_description("Formula used for blending.")
|
LayoutGroup::row(widgets).with_tooltip_description("Formula used for blending.")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button: ColorInput) -> LayoutGroup {
|
pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button: ColorInput) -> LayoutGroup {
|
||||||
@@ -1137,7 +1129,7 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
|
|||||||
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||||
// Return early with just the label if the input is exposed to the graph, meaning we don't want to show the color picker widget in the Properties panel
|
// Return early with just the label if the input is exposed to the graph, meaning we don't want to show the color picker widget in the Properties panel
|
||||||
let NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] else {
|
let NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] else {
|
||||||
return LayoutGroup::Row { widgets };
|
return LayoutGroup::row(widgets);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add a separator
|
// Add a separator
|
||||||
@@ -1176,7 +1168,7 @@ pub fn color_widget(parameter_widgets_info: ParameterWidgetsInfo, color_button:
|
|||||||
x => warn!("Color {x:?}"),
|
x => warn!("Color {x:?}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
LayoutGroup::Row { widgets }
|
LayoutGroup::row(widgets)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn font_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {
|
pub fn font_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup {
|
||||||
@@ -1192,7 +1184,7 @@ pub fn curve_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup
|
|||||||
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
let Some(document_node) = document_node else { return LayoutGroup::default() };
|
||||||
let Some(input) = document_node.inputs.get(index) else {
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return LayoutGroup::Row { widgets: vec![] };
|
return LayoutGroup::row(vec![]);
|
||||||
};
|
};
|
||||||
if let Some(TaggedValue::Curve(curve)) = &input.as_non_exposed_value() {
|
if let Some(TaggedValue::Curve(curve)) = &input.as_non_exposed_value() {
|
||||||
widgets.extend_from_slice(&[
|
widgets.extend_from_slice(&[
|
||||||
@@ -1203,7 +1195,7 @@ pub fn curve_widget(parameter_widgets_info: ParameterWidgetsInfo) -> LayoutGroup
|
|||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
LayoutGroup::Row { widgets }
|
LayoutGroup::row(widgets)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_document_node<'a>(node_id: NodeId, context: &'a NodePropertiesContext<'a>) -> Result<&'a DocumentNode, String> {
|
pub fn get_document_node<'a>(node_id: NodeId, context: &'a NodePropertiesContext<'a>) -> Result<&'a DocumentNode, String> {
|
||||||
@@ -1306,10 +1298,10 @@ pub(crate) fn brightness_contrast_properties(node_id: NodeId, context: &mut Node
|
|||||||
.range_max(Some(100.)),
|
.range_max(Some(100.)),
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut layout = vec![LayoutGroup::Row { widgets: brightness }, LayoutGroup::Row { widgets: contrast }];
|
let mut layout = vec![LayoutGroup::row(brightness), LayoutGroup::row(contrast)];
|
||||||
if includes_use_classic {
|
if includes_use_classic {
|
||||||
// TODO: When we no longer use this function in the temporary "Brightness/Contrast Classic" node, remove this conditional pushing and just always include this
|
// TODO: When we no longer use this function in the temporary "Brightness/Contrast Classic" node, remove this conditional pushing and just always include this
|
||||||
layout.push(LayoutGroup::Row { widgets: use_classic });
|
layout.push(LayoutGroup::row(use_classic));
|
||||||
}
|
}
|
||||||
|
|
||||||
layout
|
layout
|
||||||
@@ -1358,18 +1350,13 @@ pub(crate) fn channel_mixer_properties(node_id: NodeId, context: &mut NodeProper
|
|||||||
let constant = number_widget(ParameterWidgetsInfo::new(node_id, constant_output_index, true, context), number_input);
|
let constant = number_widget(ParameterWidgetsInfo::new(node_id, constant_output_index, true, context), number_input);
|
||||||
|
|
||||||
// Monochrome
|
// Monochrome
|
||||||
let mut layout = vec![LayoutGroup::Row { widgets: is_monochrome }];
|
let mut layout = vec![LayoutGroup::row(is_monochrome)];
|
||||||
// Output channel choice
|
// Output channel choice
|
||||||
if !is_monochrome_value {
|
if !is_monochrome_value {
|
||||||
layout.push(output_channel);
|
layout.push(output_channel);
|
||||||
}
|
}
|
||||||
// Channel values
|
// Channel values
|
||||||
layout.extend([
|
layout.extend([LayoutGroup::row(red), LayoutGroup::row(green), LayoutGroup::row(blue), LayoutGroup::row(constant)]);
|
||||||
LayoutGroup::Row { widgets: red },
|
|
||||||
LayoutGroup::Row { widgets: green },
|
|
||||||
LayoutGroup::Row { widgets: blue },
|
|
||||||
LayoutGroup::Row { widgets: constant },
|
|
||||||
]);
|
|
||||||
layout
|
layout
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1422,10 +1409,10 @@ pub(crate) fn selective_color_properties(node_id: NodeId, context: &mut NodeProp
|
|||||||
// Colors choice
|
// Colors choice
|
||||||
colors,
|
colors,
|
||||||
// CMYK
|
// CMYK
|
||||||
LayoutGroup::Row { widgets: cyan },
|
LayoutGroup::row(cyan),
|
||||||
LayoutGroup::Row { widgets: magenta },
|
LayoutGroup::row(magenta),
|
||||||
LayoutGroup::Row { widgets: yellow },
|
LayoutGroup::row(yellow),
|
||||||
LayoutGroup::Row { widgets: black },
|
LayoutGroup::row(black),
|
||||||
// Mode
|
// Mode
|
||||||
mode,
|
mode,
|
||||||
]
|
]
|
||||||
@@ -1458,12 +1445,10 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
widgets.push(spacing);
|
widgets.push(spacing);
|
||||||
}
|
}
|
||||||
GridType::Isometric => {
|
GridType::Isometric => {
|
||||||
let spacing = LayoutGroup::Row {
|
let spacing = LayoutGroup::row(number_widget(
|
||||||
widgets: number_widget(
|
ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context),
|
||||||
ParameterWidgetsInfo::new(node_id, SpacingInput::<f64>::INDEX, true, context),
|
NumberInput::default().label("H").min(0.).unit(" px"),
|
||||||
NumberInput::default().label("H").min(0.).unit(" px"),
|
));
|
||||||
),
|
|
||||||
};
|
|
||||||
let angles = vec2_widget(ParameterWidgetsInfo::new(node_id, AnglesInput::INDEX, true, context), "", "", "°", None, false);
|
let angles = vec2_widget(ParameterWidgetsInfo::new(node_id, AnglesInput::INDEX, true, context), "", "", "°", None, false);
|
||||||
widgets.extend([spacing, angles]);
|
widgets.extend([spacing, angles]);
|
||||||
}
|
}
|
||||||
@@ -1473,7 +1458,7 @@ pub(crate) fn grid_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
let columns = number_widget(ParameterWidgetsInfo::new(node_id, ColumnsInput::INDEX, true, context), NumberInput::default().min(1.));
|
let columns = number_widget(ParameterWidgetsInfo::new(node_id, ColumnsInput::INDEX, true, context), NumberInput::default().min(1.));
|
||||||
let rows = number_widget(ParameterWidgetsInfo::new(node_id, RowsInput::INDEX, true, context), NumberInput::default().min(1.));
|
let rows = number_widget(ParameterWidgetsInfo::new(node_id, RowsInput::INDEX, true, context), NumberInput::default().min(1.));
|
||||||
|
|
||||||
widgets.extend([LayoutGroup::Row { widgets: columns }, LayoutGroup::Row { widgets: rows }]);
|
widgets.extend([LayoutGroup::row(columns), LayoutGroup::row(rows)]);
|
||||||
|
|
||||||
widgets
|
widgets
|
||||||
}
|
}
|
||||||
@@ -1487,7 +1472,7 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon
|
|||||||
let turns = number_widget(ParameterWidgetsInfo::new(node_id, TurnsInput::INDEX, true, context), NumberInput::default().min(0.1));
|
let turns = number_widget(ParameterWidgetsInfo::new(node_id, TurnsInput::INDEX, true, context), NumberInput::default().min(0.1));
|
||||||
let start_angle = number_widget(ParameterWidgetsInfo::new(node_id, StartAngleInput::INDEX, true, context), NumberInput::default().unit("°"));
|
let start_angle = number_widget(ParameterWidgetsInfo::new(node_id, StartAngleInput::INDEX, true, context), NumberInput::default().unit("°"));
|
||||||
|
|
||||||
let mut widgets = vec![spiral_type, LayoutGroup::Row { widgets: turns }, LayoutGroup::Row { widgets: start_angle }];
|
let mut widgets = vec![spiral_type, LayoutGroup::row(turns), LayoutGroup::row(start_angle)];
|
||||||
|
|
||||||
let document_node = match get_document_node(node_id, context) {
|
let document_node = match get_document_node(node_id, context) {
|
||||||
Ok(document_node) => document_node,
|
Ok(document_node) => document_node,
|
||||||
@@ -1504,24 +1489,28 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon
|
|||||||
if let Some(&TaggedValue::SpiralType(spiral_type)) = spiral_type_input.as_non_exposed_value() {
|
if let Some(&TaggedValue::SpiralType(spiral_type)) = spiral_type_input.as_non_exposed_value() {
|
||||||
match spiral_type {
|
match spiral_type {
|
||||||
SpiralType::Archimedean => {
|
SpiralType::Archimedean => {
|
||||||
let inner_radius = LayoutGroup::Row {
|
let inner_radius = LayoutGroup::row(number_widget(
|
||||||
widgets: number_widget(ParameterWidgetsInfo::new(node_id, InnerRadiusInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px")),
|
ParameterWidgetsInfo::new(node_id, InnerRadiusInput::INDEX, true, context),
|
||||||
};
|
NumberInput::default().min(0.).unit(" px"),
|
||||||
|
));
|
||||||
|
|
||||||
let outer_radius = LayoutGroup::Row {
|
let outer_radius = LayoutGroup::row(number_widget(
|
||||||
widgets: number_widget(ParameterWidgetsInfo::new(node_id, OuterRadiusInput::INDEX, true, context), NumberInput::default().unit(" px")),
|
ParameterWidgetsInfo::new(node_id, OuterRadiusInput::INDEX, true, context),
|
||||||
};
|
NumberInput::default().unit(" px"),
|
||||||
|
));
|
||||||
|
|
||||||
widgets.extend([inner_radius, outer_radius]);
|
widgets.extend([inner_radius, outer_radius]);
|
||||||
}
|
}
|
||||||
SpiralType::Logarithmic => {
|
SpiralType::Logarithmic => {
|
||||||
let inner_radius = LayoutGroup::Row {
|
let inner_radius = LayoutGroup::row(number_widget(
|
||||||
widgets: number_widget(ParameterWidgetsInfo::new(node_id, InnerRadiusInput::INDEX, true, context), NumberInput::default().min(0.).unit(" px")),
|
ParameterWidgetsInfo::new(node_id, InnerRadiusInput::INDEX, true, context),
|
||||||
};
|
NumberInput::default().min(0.).unit(" px"),
|
||||||
|
));
|
||||||
|
|
||||||
let outer_radius = LayoutGroup::Row {
|
let outer_radius = LayoutGroup::row(number_widget(
|
||||||
widgets: number_widget(ParameterWidgetsInfo::new(node_id, OuterRadiusInput::INDEX, true, context), NumberInput::default().min(0.1).unit(" px")),
|
ParameterWidgetsInfo::new(node_id, OuterRadiusInput::INDEX, true, context),
|
||||||
};
|
NumberInput::default().min(0.1).unit(" px"),
|
||||||
|
));
|
||||||
|
|
||||||
widgets.extend([inner_radius, outer_radius]);
|
widgets.extend([inner_radius, outer_radius]);
|
||||||
}
|
}
|
||||||
@@ -1533,7 +1522,7 @@ pub(crate) fn spiral_properties(node_id: NodeId, context: &mut NodePropertiesCon
|
|||||||
NumberInput::default().min(1.).max(180.).unit("°"),
|
NumberInput::default().min(1.).max(180.).unit("°"),
|
||||||
);
|
);
|
||||||
|
|
||||||
widgets.push(LayoutGroup::Row { widgets: angular_resolution });
|
widgets.push(LayoutGroup::row(angular_resolution));
|
||||||
|
|
||||||
widgets
|
widgets
|
||||||
}
|
}
|
||||||
@@ -1574,13 +1563,13 @@ pub(crate) fn sample_polyline_properties(node_id: NodeId, context: &mut NodeProp
|
|||||||
vec![
|
vec![
|
||||||
spacing.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_SPACING),
|
spacing.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_SPACING),
|
||||||
match current_spacing {
|
match current_spacing {
|
||||||
Some(TaggedValue::PointSpacingType(PointSpacingType::Separation)) => LayoutGroup::Row { widgets: separation }.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_SEPARATION),
|
Some(TaggedValue::PointSpacingType(PointSpacingType::Separation)) => LayoutGroup::row(separation).with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_SEPARATION),
|
||||||
Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity)) => LayoutGroup::Row { widgets: quantity }.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_QUANTITY),
|
Some(TaggedValue::PointSpacingType(PointSpacingType::Quantity)) => LayoutGroup::row(quantity).with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_QUANTITY),
|
||||||
_ => LayoutGroup::Row { widgets: vec![] },
|
_ => LayoutGroup::row(vec![]),
|
||||||
},
|
},
|
||||||
LayoutGroup::Row { widgets: start_offset }.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_START_OFFSET),
|
LayoutGroup::row(start_offset).with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_START_OFFSET),
|
||||||
LayoutGroup::Row { widgets: stop_offset }.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_STOP_OFFSET),
|
LayoutGroup::row(stop_offset).with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_STOP_OFFSET),
|
||||||
LayoutGroup::Row { widgets: adaptive_spacing }.with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_ADAPTIVE_SPACING),
|
LayoutGroup::row(adaptive_spacing).with_tooltip_description(SAMPLE_POLYLINE_DESCRIPTION_ADAPTIVE_SPACING),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1594,11 +1583,7 @@ pub(crate) fn exposure_properties(node_id: NodeId, context: &mut NodePropertiesC
|
|||||||
NumberInput::default().min(0.01).max(9.99).increment_step(0.1),
|
NumberInput::default().min(0.01).max(9.99).increment_step(0.1),
|
||||||
);
|
);
|
||||||
|
|
||||||
vec![
|
vec![LayoutGroup::row(exposure), LayoutGroup::row(offset), LayoutGroup::row(gamma_correction)]
|
||||||
LayoutGroup::Row { widgets: exposure },
|
|
||||||
LayoutGroup::Row { widgets: offset },
|
|
||||||
LayoutGroup::Row { widgets: gamma_correction },
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
@@ -1722,11 +1707,11 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
|
|||||||
let clamped = bool_widget(ParameterWidgetsInfo::new(node_id, ClampedInput::INDEX, true, context), CheckboxInput::default());
|
let clamped = bool_widget(ParameterWidgetsInfo::new(node_id, ClampedInput::INDEX, true, context), CheckboxInput::default());
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
LayoutGroup::Row { widgets: size_x },
|
LayoutGroup::row(size_x),
|
||||||
LayoutGroup::Row { widgets: size_y },
|
LayoutGroup::row(size_y),
|
||||||
LayoutGroup::Row { widgets: corner_radius_row_1 },
|
LayoutGroup::row(corner_radius_row_1),
|
||||||
LayoutGroup::Row { widgets: corner_radius_row_2 },
|
LayoutGroup::row(corner_radius_row_2),
|
||||||
LayoutGroup::Row { widgets: clamped },
|
LayoutGroup::row(clamped),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1825,14 +1810,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
|
|||||||
let visible = context.network_interface.is_visible(&node_id, context.selection_network_path);
|
let visible = context.network_interface.is_visible(&node_id, context.selection_network_path);
|
||||||
let pinned = context.network_interface.is_pinned(&node_id, context.selection_network_path);
|
let pinned = context.network_interface.is_pinned(&node_id, context.selection_network_path);
|
||||||
|
|
||||||
LayoutGroup::Section {
|
LayoutGroup::section(name, description, visible, pinned, node_id.0, Layout(layout))
|
||||||
name,
|
|
||||||
description,
|
|
||||||
visible,
|
|
||||||
pinned,
|
|
||||||
id: node_id.0,
|
|
||||||
layout: Layout(layout),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fill Node Widgets LayoutGroup
|
/// Fill Node Widgets LayoutGroup
|
||||||
@@ -1856,7 +1834,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
) {
|
) {
|
||||||
(fill, backup_color, backup_gradient)
|
(fill, backup_color, backup_gradient)
|
||||||
} else {
|
} else {
|
||||||
return vec![LayoutGroup::Row { widgets: widgets_first_row }];
|
return vec![LayoutGroup::row(widgets_first_row)];
|
||||||
};
|
};
|
||||||
let fill2 = fill.clone();
|
let fill2 = fill.clone();
|
||||||
let backup_color_fill: Fill = backup_color.clone().into();
|
let backup_color_fill: Fill = backup_color.clone().into();
|
||||||
@@ -1899,7 +1877,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
.on_commit(commit_value)
|
.on_commit(commit_value)
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
);
|
);
|
||||||
let mut widgets = vec![LayoutGroup::Row { widgets: widgets_first_row }];
|
let mut widgets = vec![LayoutGroup::row(widgets_first_row)];
|
||||||
|
|
||||||
let fill_type_switch = {
|
let fill_type_switch = {
|
||||||
let mut row = vec![TextLabel::new("").widget_instance()];
|
let mut row = vec![TextLabel::new("").widget_instance()];
|
||||||
@@ -1942,7 +1920,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
RadioInput::new(entries).selected_index(Some(if fill.as_gradient().is_some() { 1 } else { 0 })).widget_instance(),
|
RadioInput::new(entries).selected_index(Some(if fill.as_gradient().is_some() { 1 } else { 0 })).widget_instance(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
LayoutGroup::Row { widgets: row }
|
LayoutGroup::row(row)
|
||||||
};
|
};
|
||||||
widgets.push(fill_type_switch);
|
widgets.push(fill_type_switch);
|
||||||
|
|
||||||
@@ -2011,7 +1989,7 @@ pub(crate) fn fill_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
RadioInput::new(entries).selected_index(Some(gradient.gradient_type as u32)).widget_instance(),
|
RadioInput::new(entries).selected_index(Some(gradient.gradient_type as u32)).widget_instance(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
widgets.push(LayoutGroup::Row { widgets: row });
|
widgets.push(LayoutGroup::row(row));
|
||||||
}
|
}
|
||||||
|
|
||||||
widgets
|
widgets
|
||||||
@@ -2069,14 +2047,14 @@ pub fn stroke_properties(node_id: NodeId, context: &mut NodePropertiesContext) -
|
|||||||
|
|
||||||
vec![
|
vec![
|
||||||
color,
|
color,
|
||||||
LayoutGroup::Row { widgets: weight },
|
LayoutGroup::row(weight),
|
||||||
align,
|
align,
|
||||||
cap,
|
cap,
|
||||||
join,
|
join,
|
||||||
LayoutGroup::Row { widgets: miter_limit },
|
LayoutGroup::row(miter_limit),
|
||||||
paint_order,
|
paint_order,
|
||||||
LayoutGroup::Row { widgets: dash_lengths },
|
LayoutGroup::row(dash_lengths),
|
||||||
LayoutGroup::Row { widgets: dash_offset },
|
LayoutGroup::row(dash_offset),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2106,7 +2084,7 @@ pub fn offset_path_properties(node_id: NodeId, context: &mut NodePropertiesConte
|
|||||||
});
|
});
|
||||||
let miter_limit = number_widget(ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context), number_input);
|
let miter_limit = number_widget(ParameterWidgetsInfo::new(node_id, MiterLimitInput::INDEX, true, context), number_input);
|
||||||
|
|
||||||
vec![LayoutGroup::Row { widgets: distance }, join, LayoutGroup::Row { widgets: miter_limit }]
|
vec![LayoutGroup::row(distance), join, LayoutGroup::row(miter_limit)]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||||
@@ -2158,9 +2136,9 @@ pub fn math_properties(node_id: NodeId, context: &mut NodePropertiesContext) ->
|
|||||||
let operand_a_hint = vec![TextLabel::new("(Operand A is the primary input)").widget_instance()];
|
let operand_a_hint = vec![TextLabel::new("(Operand A is the primary input)").widget_instance()];
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
LayoutGroup::Row { widgets: expression }.with_tooltip_description(r#"A math expression that may incorporate "A" and/or "B", such as "sqrt(A + B) - B^2"."#),
|
LayoutGroup::row(expression).with_tooltip_description(r#"A math expression that may incorporate "A" and/or "B", such as "sqrt(A + B) - B^2"."#),
|
||||||
LayoutGroup::Row { widgets: operand_b }.with_tooltip_description(r#"The value of "B" when calculating the expression."#),
|
LayoutGroup::row(operand_b).with_tooltip_description(r#"The value of "B" when calculating the expression."#),
|
||||||
LayoutGroup::Row { widgets: operand_a_hint }.with_tooltip_description(r#""A" is fed by the value from the previous node in the primary data flow, or it is 0 if disconnected."#),
|
LayoutGroup::row(operand_a_hint).with_tooltip_description(r#""A" is fed by the value from the previous node in the primary data flow, or it is 0 if disconnected."#),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2348,14 +2326,14 @@ pub mod choice {
|
|||||||
let ParameterWidgetsInfo { document_node, node_id, index, .. } = self.parameter_info;
|
let ParameterWidgetsInfo { document_node, node_id, index, .. } = self.parameter_info;
|
||||||
let Some(document_node) = document_node else {
|
let Some(document_node) = document_node else {
|
||||||
log::error!("Could not get document node when building property row for node {node_id:?}");
|
log::error!("Could not get document node when building property row for node {node_id:?}");
|
||||||
return LayoutGroup::Row { widgets: Vec::new() };
|
return LayoutGroup::row(Vec::new());
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut widgets = super::start_widgets(self.parameter_info);
|
let mut widgets = super::start_widgets(self.parameter_info);
|
||||||
|
|
||||||
let Some(input) = document_node.inputs.get(index) else {
|
let Some(input) = document_node.inputs.get(index) else {
|
||||||
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
log::warn!("A widget failed to be built because its node's input index is invalid.");
|
||||||
return LayoutGroup::Row { widgets: vec![] };
|
return LayoutGroup::row(vec![]);
|
||||||
};
|
};
|
||||||
|
|
||||||
let input: Option<W::Value> = input.as_non_exposed_value().and_then(|v| <&W::Value as TryFrom<&TaggedValue>>::try_from(v).ok()).cloned();
|
let input: Option<W::Value> = input.as_non_exposed_value().and_then(|v| <&W::Value as TryFrom<&TaggedValue>>::try_from(v).ok()).cloned();
|
||||||
@@ -2367,7 +2345,7 @@ pub mod choice {
|
|||||||
widgets.extend_from_slice(&[Separator::new(SeparatorStyle::Unrelated).widget_instance(), widget]);
|
widgets.extend_from_slice(&[Separator::new(SeparatorStyle::Unrelated).widget_instance(), widget]);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut row = LayoutGroup::Row { widgets };
|
let mut row = LayoutGroup::row(widgets);
|
||||||
if let Some(desc) = self.widget_factory.description() {
|
if let Some(desc) = self.widget_factory.description() {
|
||||||
row = row.with_tooltip_description(desc);
|
row = row.with_tooltip_description(desc);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use glam::IVec2;
|
|
||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graphene_std::Type;
|
use graphene_std::Type;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum FrontendGraphDataType {
|
pub enum FrontendGraphDataType {
|
||||||
#[default]
|
#[default]
|
||||||
General,
|
General,
|
||||||
@@ -42,7 +42,8 @@ impl FrontendGraphDataType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct FrontendGraphInput {
|
pub struct FrontendGraphInput {
|
||||||
#[serde(rename = "dataType")]
|
#[serde(rename = "dataType")]
|
||||||
pub data_type: FrontendGraphDataType,
|
pub data_type: FrontendGraphDataType,
|
||||||
@@ -57,21 +58,23 @@ pub struct FrontendGraphInput {
|
|||||||
pub connected_to: String,
|
pub connected_to: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct FrontendGraphOutput {
|
pub struct FrontendGraphOutput {
|
||||||
#[serde(rename = "dataType")]
|
#[serde(rename = "dataType")]
|
||||||
pub data_type: FrontendGraphDataType,
|
pub data_type: FrontendGraphDataType,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
#[serde(rename = "resolvedType")]
|
#[serde(rename = "resolvedType")]
|
||||||
pub resolved_type: String,
|
pub resolved_type: String,
|
||||||
pub description: String,
|
|
||||||
/// If connected to an export, it is "export index {index}".
|
/// If connected to an export, it is "export index {index}".
|
||||||
/// If connected to a node, it is "{node name} input {input_index}".
|
/// If connected to a node, it is "{node name} input {input_index}".
|
||||||
#[serde(rename = "connectedTo")]
|
#[serde(rename = "connectedTo")]
|
||||||
pub connected_to: Vec<String>,
|
pub connected_to: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct FrontendNode {
|
pub struct FrontendNode {
|
||||||
pub id: graph_craft::document::NodeId,
|
pub id: graph_craft::document::NodeId,
|
||||||
#[serde(rename = "isLayer")]
|
#[serde(rename = "isLayer")]
|
||||||
@@ -95,13 +98,14 @@ pub struct FrontendNode {
|
|||||||
pub primary_input_connected_to_layer: bool,
|
pub primary_input_connected_to_layer: bool,
|
||||||
#[serde(rename = "primaryOutputConnectedToLayer")]
|
#[serde(rename = "primaryOutputConnectedToLayer")]
|
||||||
pub primary_output_connected_to_layer: bool,
|
pub primary_output_connected_to_layer: bool,
|
||||||
pub position: IVec2,
|
pub position: (i32, i32),
|
||||||
pub previewed: bool,
|
pub previewed: bool,
|
||||||
pub visible: bool,
|
pub visible: bool,
|
||||||
pub locked: bool,
|
pub locked: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct FrontendNodeType {
|
pub struct FrontendNodeType {
|
||||||
pub identifier: String,
|
pub identifier: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -110,7 +114,8 @@ pub struct FrontendNodeType {
|
|||||||
pub input_types: Vec<String>,
|
pub input_types: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct DragStart {
|
pub struct DragStart {
|
||||||
pub start_x: f64,
|
pub start_x: f64,
|
||||||
pub start_y: f64,
|
pub start_y: f64,
|
||||||
@@ -118,14 +123,8 @@ pub struct DragStart {
|
|||||||
pub round_y: i32,
|
pub round_y: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
pub struct Transform {
|
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub scale: f64,
|
|
||||||
pub x: f64,
|
|
||||||
pub y: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
|
||||||
pub struct BoxSelection {
|
pub struct BoxSelection {
|
||||||
#[serde(rename = "startX")]
|
#[serde(rename = "startX")]
|
||||||
pub start_x: u32,
|
pub start_x: u32,
|
||||||
@@ -137,7 +136,8 @@ pub struct BoxSelection {
|
|||||||
pub end_y: u32,
|
pub end_y: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(tag = "type", content = "data")]
|
#[serde(tag = "type", content = "data")]
|
||||||
pub enum ContextMenuData {
|
pub enum ContextMenuData {
|
||||||
ModifyNode {
|
ModifyNode {
|
||||||
@@ -158,22 +158,25 @@ pub enum ContextMenuData {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct ContextMenuInformation {
|
pub struct ContextMenuInformation {
|
||||||
// Stores whether the context menu is open and its position in graph coordinates
|
// Stores whether the context menu is open and its position in graph coordinates
|
||||||
#[serde(rename = "contextMenuCoordinates")]
|
#[serde(rename = "contextMenuCoordinates")]
|
||||||
pub context_menu_coordinates: IVec2,
|
pub context_menu_coordinates: (i32, i32),
|
||||||
#[serde(rename = "contextMenuData")]
|
#[serde(rename = "contextMenuData")]
|
||||||
pub context_menu_data: ContextMenuData,
|
pub context_menu_data: ContextMenuData,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct NodeGraphErrorDiagnostic {
|
pub struct NodeGraphErrorDiagnostic {
|
||||||
pub position: IVec2,
|
pub position: (i32, i32),
|
||||||
pub error: String,
|
pub error: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct FrontendClickTargets {
|
pub struct FrontendClickTargets {
|
||||||
#[serde(rename = "nodeClickTargets")]
|
#[serde(rename = "nodeClickTargets")]
|
||||||
pub node_click_targets: Vec<String>,
|
pub node_click_targets: Vec<String>,
|
||||||
@@ -189,7 +192,8 @@ pub struct FrontendClickTargets {
|
|||||||
pub modify_import_export: Vec<String>,
|
pub modify_import_export: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
Up,
|
Up,
|
||||||
Down,
|
Down,
|
||||||
|
|||||||
@@ -228,42 +228,38 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
widgets.push(LayoutGroup::Row {
|
widgets.push(LayoutGroup::row(vec![TextLabel::new("Grid").bold(true).widget_instance()]));
|
||||||
widgets: vec![TextLabel::new("Grid").bold(true).widget_instance()],
|
|
||||||
});
|
|
||||||
|
|
||||||
widgets.push(LayoutGroup::Row {
|
widgets.push(LayoutGroup::row(vec![
|
||||||
widgets: vec![
|
TextLabel::new("Type").table_align(true).widget_instance(),
|
||||||
TextLabel::new("Type").table_align(true).widget_instance(),
|
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
RadioInput::new(vec![
|
||||||
RadioInput::new(vec![
|
RadioEntryData::new("rectangular").label("Rectangular").on_update(update_val(grid, |grid, _| {
|
||||||
RadioEntryData::new("rectangular").label("Rectangular").on_update(update_val(grid, |grid, _| {
|
if let GridType::Isometric { y_axis_spacing, angle_a, angle_b } = grid.grid_type {
|
||||||
if let GridType::Isometric { y_axis_spacing, angle_a, angle_b } = grid.grid_type {
|
grid.isometric_y_spacing = y_axis_spacing;
|
||||||
grid.isometric_y_spacing = y_axis_spacing;
|
grid.isometric_angle_a = angle_a;
|
||||||
grid.isometric_angle_a = angle_a;
|
grid.isometric_angle_b = angle_b;
|
||||||
grid.isometric_angle_b = angle_b;
|
}
|
||||||
}
|
grid.grid_type = GridType::Rectangular { spacing: grid.rectangular_spacing };
|
||||||
grid.grid_type = GridType::Rectangular { spacing: grid.rectangular_spacing };
|
})),
|
||||||
})),
|
RadioEntryData::new("isometric").label("Isometric").on_update(update_val(grid, |grid, _| {
|
||||||
RadioEntryData::new("isometric").label("Isometric").on_update(update_val(grid, |grid, _| {
|
if let GridType::Rectangular { spacing } = grid.grid_type {
|
||||||
if let GridType::Rectangular { spacing } = grid.grid_type {
|
grid.rectangular_spacing = spacing;
|
||||||
grid.rectangular_spacing = spacing;
|
}
|
||||||
}
|
grid.grid_type = GridType::Isometric {
|
||||||
grid.grid_type = GridType::Isometric {
|
y_axis_spacing: grid.isometric_y_spacing,
|
||||||
y_axis_spacing: grid.isometric_y_spacing,
|
angle_a: grid.isometric_angle_a,
|
||||||
angle_a: grid.isometric_angle_a,
|
angle_b: grid.isometric_angle_b,
|
||||||
angle_b: grid.isometric_angle_b,
|
};
|
||||||
};
|
})),
|
||||||
})),
|
])
|
||||||
])
|
.min_width(200)
|
||||||
.min_width(200)
|
.selected_index(Some(match grid.grid_type {
|
||||||
.selected_index(Some(match grid.grid_type {
|
GridType::Rectangular { .. } => 0,
|
||||||
GridType::Rectangular { .. } => 0,
|
GridType::Isometric { .. } => 1,
|
||||||
GridType::Isometric { .. } => 1,
|
}))
|
||||||
}))
|
.widget_instance(),
|
||||||
.widget_instance(),
|
]));
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut color_widgets = vec![
|
let mut color_widgets = vec![
|
||||||
TextLabel::new("Display").table_align(true).widget_instance(),
|
TextLabel::new("Display").table_align(true).widget_instance(),
|
||||||
@@ -288,80 +284,72 @@ pub fn overlay_options(grid: &GridSnapping) -> Vec<LayoutGroup> {
|
|||||||
}))
|
}))
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
);
|
);
|
||||||
widgets.push(LayoutGroup::Row { widgets: color_widgets });
|
widgets.push(LayoutGroup::row(color_widgets));
|
||||||
|
|
||||||
widgets.push(LayoutGroup::Row {
|
widgets.push(LayoutGroup::row(vec![
|
||||||
widgets: vec![
|
TextLabel::new("Origin").table_align(true).widget_instance(),
|
||||||
TextLabel::new("Origin").table_align(true).widget_instance(),
|
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
NumberInput::new(Some(grid.origin.x))
|
||||||
NumberInput::new(Some(grid.origin.x))
|
.label("X")
|
||||||
.label("X")
|
.unit(" px")
|
||||||
.unit(" px")
|
.min_width(98)
|
||||||
.min_width(98)
|
.on_update(update_origin(grid, |grid| Some(&mut grid.origin.x)))
|
||||||
.on_update(update_origin(grid, |grid| Some(&mut grid.origin.x)))
|
.widget_instance(),
|
||||||
.widget_instance(),
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
NumberInput::new(Some(grid.origin.y))
|
||||||
NumberInput::new(Some(grid.origin.y))
|
.label("Y")
|
||||||
.label("Y")
|
.unit(" px")
|
||||||
.unit(" px")
|
.min_width(98)
|
||||||
.min_width(98)
|
.on_update(update_origin(grid, |grid| Some(&mut grid.origin.y)))
|
||||||
.on_update(update_origin(grid, |grid| Some(&mut grid.origin.y)))
|
.widget_instance(),
|
||||||
.widget_instance(),
|
]));
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
match grid.grid_type {
|
match grid.grid_type {
|
||||||
GridType::Rectangular { spacing } => widgets.push(LayoutGroup::Row {
|
GridType::Rectangular { spacing } => widgets.push(LayoutGroup::row(vec![
|
||||||
widgets: vec![
|
TextLabel::new("Spacing").table_align(true).widget_instance(),
|
||||||
TextLabel::new("Spacing").table_align(true).widget_instance(),
|
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||||
|
NumberInput::new(Some(spacing.x))
|
||||||
|
.label("X")
|
||||||
|
.unit(" px")
|
||||||
|
.min(0.)
|
||||||
|
.min_width(98)
|
||||||
|
.on_update(update_origin(grid, |grid| grid.grid_type.rectangular_spacing().map(|spacing| &mut spacing.x)))
|
||||||
|
.widget_instance(),
|
||||||
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
|
NumberInput::new(Some(spacing.y))
|
||||||
|
.label("Y")
|
||||||
|
.unit(" px")
|
||||||
|
.min(0.)
|
||||||
|
.min_width(98)
|
||||||
|
.on_update(update_origin(grid, |grid| grid.grid_type.rectangular_spacing().map(|spacing| &mut spacing.y)))
|
||||||
|
.widget_instance(),
|
||||||
|
])),
|
||||||
|
GridType::Isometric { y_axis_spacing, angle_a, angle_b } => {
|
||||||
|
widgets.push(LayoutGroup::row(vec![
|
||||||
|
TextLabel::new("Y Spacing").table_align(true).widget_instance(),
|
||||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||||
NumberInput::new(Some(spacing.x))
|
NumberInput::new(Some(y_axis_spacing))
|
||||||
.label("X")
|
|
||||||
.unit(" px")
|
.unit(" px")
|
||||||
.min(0.)
|
.min(0.)
|
||||||
|
.min_width(200)
|
||||||
|
.on_update(update_origin(grid, |grid| grid.grid_type.isometric_y_spacing()))
|
||||||
|
.widget_instance(),
|
||||||
|
]));
|
||||||
|
widgets.push(LayoutGroup::row(vec![
|
||||||
|
TextLabel::new("Angles").table_align(true).widget_instance(),
|
||||||
|
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
||||||
|
NumberInput::new(Some(angle_a))
|
||||||
|
.unit("°")
|
||||||
.min_width(98)
|
.min_width(98)
|
||||||
.on_update(update_origin(grid, |grid| grid.grid_type.rectangular_spacing().map(|spacing| &mut spacing.x)))
|
.on_update(update_origin(grid, |grid| grid.grid_type.angle_a()))
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
NumberInput::new(Some(spacing.y))
|
NumberInput::new(Some(angle_b))
|
||||||
.label("Y")
|
.unit("°")
|
||||||
.unit(" px")
|
|
||||||
.min(0.)
|
|
||||||
.min_width(98)
|
.min_width(98)
|
||||||
.on_update(update_origin(grid, |grid| grid.grid_type.rectangular_spacing().map(|spacing| &mut spacing.y)))
|
.on_update(update_origin(grid, |grid| grid.grid_type.angle_b()))
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
],
|
]));
|
||||||
}),
|
|
||||||
GridType::Isometric { y_axis_spacing, angle_a, angle_b } => {
|
|
||||||
widgets.push(LayoutGroup::Row {
|
|
||||||
widgets: vec![
|
|
||||||
TextLabel::new("Y Spacing").table_align(true).widget_instance(),
|
|
||||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
|
||||||
NumberInput::new(Some(y_axis_spacing))
|
|
||||||
.unit(" px")
|
|
||||||
.min(0.)
|
|
||||||
.min_width(200)
|
|
||||||
.on_update(update_origin(grid, |grid| grid.grid_type.isometric_y_spacing()))
|
|
||||||
.widget_instance(),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
widgets.push(LayoutGroup::Row {
|
|
||||||
widgets: vec![
|
|
||||||
TextLabel::new("Angles").table_align(true).widget_instance(),
|
|
||||||
Separator::new(SeparatorStyle::Unrelated).widget_instance(),
|
|
||||||
NumberInput::new(Some(angle_a))
|
|
||||||
.unit("°")
|
|
||||||
.min_width(98)
|
|
||||||
.on_update(update_origin(grid, |grid| grid.grid_type.angle_a()))
|
|
||||||
.widget_instance(),
|
|
||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
|
||||||
NumberInput::new(Some(angle_b))
|
|
||||||
.unit("°")
|
|
||||||
.min_width(98)
|
|
||||||
.on_update(update_origin(grid, |grid| grid.grid_type.angle_b()))
|
|
||||||
.widget_instance(),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ pub enum GizmoEmphasis {
|
|||||||
|
|
||||||
// TODO Remove duplicated definition of this in `utility_types_web.rs`
|
// TODO Remove duplicated definition of this in `utility_types_web.rs`
|
||||||
/// Types of overlays used by DocumentMessage to enable/disable the selected set of viewport overlays.
|
/// Types of overlays used by DocumentMessage to enable/disable the selected set of viewport overlays.
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum OverlaysType {
|
pub enum OverlaysType {
|
||||||
ArtboardName,
|
ArtboardName,
|
||||||
CompassRose,
|
CompassRose,
|
||||||
@@ -60,7 +61,8 @@ pub enum OverlaysType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO Remove duplicated definition of this in `utility_types_web.rs`
|
// TODO Remove duplicated definition of this in `utility_types_web.rs`
|
||||||
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct OverlaysVisibilitySettings {
|
pub struct OverlaysVisibilitySettings {
|
||||||
pub all: bool,
|
pub all: bool,
|
||||||
@@ -160,11 +162,11 @@ impl OverlaysVisibilitySettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
pub struct OverlayContext {
|
pub struct OverlayContext {
|
||||||
// Serde functionality isn't used but is required by the message system macros
|
// Serde functionality isn't used but is required by the message system macros
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
#[specta(skip)]
|
|
||||||
internal: Arc<Mutex<OverlayContextInternal>>,
|
internal: Arc<Mutex<OverlayContextInternal>>,
|
||||||
pub viewport: ViewportMessageHandler,
|
pub viewport: ViewportMessageHandler,
|
||||||
pub visibility_settings: OverlaysVisibilitySettings,
|
pub visibility_settings: OverlaysVisibilitySettings,
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ pub enum GizmoEmphasis {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Types of overlays used by DocumentMessage to enable/disable the selected set of viewport overlays.
|
/// Types of overlays used by DocumentMessage to enable/disable the selected set of viewport overlays.
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum OverlaysType {
|
pub enum OverlaysType {
|
||||||
ArtboardName,
|
ArtboardName,
|
||||||
CompassRose,
|
CompassRose,
|
||||||
@@ -52,7 +53,8 @@ pub enum OverlaysType {
|
|||||||
Handles,
|
Handles,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct OverlaysVisibilitySettings {
|
pub struct OverlaysVisibilitySettings {
|
||||||
pub all: bool,
|
pub all: bool,
|
||||||
@@ -150,11 +152,11 @@ impl OverlaysVisibilitySettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct OverlayContext {
|
pub struct OverlayContext {
|
||||||
// Serde functionality isn't used but is required by the message system macros
|
// Serde functionality isn't used but is required by the message system macros
|
||||||
#[serde(skip, default = "overlay_canvas_context")]
|
#[serde(skip, default = "overlay_canvas_context")]
|
||||||
#[specta(skip)]
|
|
||||||
pub render_context: web_sys::CanvasRenderingContext2d,
|
pub render_context: web_sys::CanvasRenderingContext2d,
|
||||||
pub viewport: ViewportMessageHandler,
|
pub viewport: ViewportMessageHandler,
|
||||||
pub visibility_settings: OverlaysVisibilitySettings,
|
pub visibility_settings: OverlaysVisibilitySettings,
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ use super::network_interface::NodeTemplate;
|
|||||||
use graph_craft::document::NodeId;
|
use graph_craft::document::NodeId;
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Debug, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
pub enum Clipboard {
|
pub enum Clipboard {
|
||||||
Internal,
|
Internal,
|
||||||
Device,
|
Device,
|
||||||
|
|||||||
@@ -234,7 +234,8 @@ impl DocumentMetadata {
|
|||||||
// ===================
|
// ===================
|
||||||
|
|
||||||
/// ID of a layer node
|
/// ID of a layer node
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct LayerNodeIdentifier(NonZeroU64);
|
pub struct LayerNodeIdentifier(NonZeroU64);
|
||||||
|
|
||||||
impl core::fmt::Debug for LayerNodeIdentifier {
|
impl core::fmt::Debug for LayerNodeIdentifier {
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ use glam::DVec2;
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify), tsify(large_number_types_as_bigints))]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct DocumentId(pub u64);
|
pub struct DocumentId(pub u64);
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Hash)]
|
#[derive(PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Hash)]
|
||||||
@@ -12,13 +13,15 @@ pub enum FlipAxis {
|
|||||||
Y,
|
Y,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Hash, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Hash)]
|
||||||
pub enum AlignAxis {
|
pub enum AlignAxis {
|
||||||
X,
|
X,
|
||||||
Y,
|
Y,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Hash, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize, Hash)]
|
||||||
pub enum AlignAggregate {
|
pub enum AlignAggregate {
|
||||||
Min,
|
Min,
|
||||||
Max,
|
Max,
|
||||||
|
|||||||
@@ -5723,14 +5723,16 @@ impl Iterator for FlowIter<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum ImportOrExport {
|
pub enum ImportOrExport {
|
||||||
Import(usize),
|
Import(usize),
|
||||||
Export(usize),
|
Export(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents an input connector with index based on the [`DocumentNode::inputs`] index, not the visible input index
|
/// Represents an input connector with index based on the [`DocumentNode::inputs`] index, not the visible input index
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum InputConnector {
|
pub enum InputConnector {
|
||||||
#[serde(rename = "node")]
|
#[serde(rename = "node")]
|
||||||
Node {
|
Node {
|
||||||
@@ -5770,7 +5772,8 @@ impl InputConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Represents an output connector
|
/// Represents an output connector
|
||||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum OutputConnector {
|
pub enum OutputConnector {
|
||||||
#[serde(rename = "node")]
|
#[serde(rename = "node")]
|
||||||
Node {
|
Node {
|
||||||
|
|||||||
@@ -1,25 +1,28 @@
|
|||||||
use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
|
||||||
use super::network_interface::NodeNetworkInterface;
|
use super::network_interface::NodeNetworkInterface;
|
||||||
|
use crate::messages::frontend::IconName;
|
||||||
use crate::messages::tool::common_functionality::graph_modification_utils;
|
use crate::messages::tool::common_functionality::graph_modification_utils;
|
||||||
use glam::DVec2;
|
use glam::DVec2;
|
||||||
use graph_craft::document::{NodeId, NodeNetwork};
|
use graph_craft::document::{NodeId, NodeNetwork};
|
||||||
|
|
||||||
/// Represents an entry in the layer tree hierarchy, sent to the frontend.
|
/// Represents an entry in the layer tree hierarchy, sent to the frontend.
|
||||||
/// Each entry contains its layer ID and a list of its visible children.
|
/// Each entry contains its layer ID and a list of its visible children.
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
||||||
pub struct LayerStructureEntry {
|
pub struct LayerStructureEntry {
|
||||||
#[serde(rename = "layerId")]
|
#[serde(rename = "layerId")]
|
||||||
pub layer_id: NodeId,
|
pub layer_id: NodeId,
|
||||||
pub children: Vec<LayerStructureEntry>,
|
pub children: Vec<LayerStructureEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
||||||
pub struct LayerPanelEntry {
|
pub struct LayerPanelEntry {
|
||||||
pub id: NodeId,
|
pub id: NodeId,
|
||||||
#[serde(rename = "implementationName")]
|
#[serde(rename = "implementationName")]
|
||||||
pub implementation_name: String,
|
pub implementation_name: String,
|
||||||
#[serde(rename = "iconName")]
|
#[serde(rename = "iconName")]
|
||||||
pub icon_name: Option<String>,
|
pub icon_name: Option<IconName>,
|
||||||
pub alias: String,
|
pub alias: String,
|
||||||
#[serde(rename = "inSelectedNetwork")]
|
#[serde(rename = "inSelectedNetwork")]
|
||||||
pub in_selected_network: bool,
|
pub in_selected_network: bool,
|
||||||
@@ -47,7 +50,8 @@ pub struct LayerPanelEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// IMPORTANT: the same node may appear multiple times.
|
/// IMPORTANT: the same node may appear multiple times.
|
||||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
||||||
pub struct SelectedNodes(pub Vec<NodeId>);
|
pub struct SelectedNodes(pub Vec<NodeId>);
|
||||||
|
|
||||||
impl SelectedNodes {
|
impl SelectedNodes {
|
||||||
@@ -157,5 +161,6 @@ impl SelectedNodes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
||||||
pub struct CollapsedLayers(pub Vec<LayerNodeIdentifier>);
|
pub struct CollapsedLayers(pub Vec<LayerNodeIdentifier>);
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ use glam::{DVec2, IVec2};
|
|||||||
use graphene_std::{uuid::NodeId, vector::misc::dvec2_to_point};
|
use graphene_std::{uuid::NodeId, vector::misc::dvec2_to_point};
|
||||||
use kurbo::{BezPath, DEFAULT_ACCURACY, Line, Point, Shape};
|
use kurbo::{BezPath, DEFAULT_ACCURACY, Line, Point, Shape};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct WirePath {
|
pub struct WirePath {
|
||||||
#[serde(rename = "pathString")]
|
#[serde(rename = "pathString")]
|
||||||
pub path_string: String,
|
pub path_string: String,
|
||||||
@@ -13,7 +14,8 @@ pub struct WirePath {
|
|||||||
pub dashed: bool,
|
pub dashed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct WirePathUpdate {
|
pub struct WirePathUpdate {
|
||||||
pub id: NodeId,
|
pub id: NodeId,
|
||||||
#[serde(rename = "inputIndex")]
|
#[serde(rename = "inputIndex")]
|
||||||
@@ -23,7 +25,8 @@ pub struct WirePathUpdate {
|
|||||||
pub wire_path_update: Option<WirePath>,
|
pub wire_path_update: Option<WirePath>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum GraphWireStyle {
|
pub enum GraphWireStyle {
|
||||||
#[default]
|
#[default]
|
||||||
Direct = 0,
|
Direct = 0,
|
||||||
|
|||||||
@@ -1011,12 +1011,11 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
PortfolioMessage::RequestWelcomeScreenButtonsLayout => {
|
PortfolioMessage::RequestWelcomeScreenButtonsLayout => {
|
||||||
let donate = "https://graphite.art/donate/";
|
let donate = "https://graphite.art/donate/";
|
||||||
|
|
||||||
let table = LayoutGroup::Table {
|
let table = LayoutGroup::table(
|
||||||
unstyled: true,
|
vec![
|
||||||
rows: vec![
|
|
||||||
vec![
|
vec![
|
||||||
TextButton::new("New Document")
|
TextButton::new("New Document")
|
||||||
.icon(Some("File".into()))
|
.icon("File")
|
||||||
.flush(true)
|
.flush(true)
|
||||||
.on_commit(|_| DialogMessage::RequestNewDocumentDialog.into())
|
.on_commit(|_| DialogMessage::RequestNewDocumentDialog.into())
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
@@ -1024,7 +1023,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
TextButton::new("Open Document")
|
TextButton::new("Open Document")
|
||||||
.icon(Some("Folder".into()))
|
.icon("Folder")
|
||||||
.flush(true)
|
.flush(true)
|
||||||
.on_commit(|_| PortfolioMessage::Open.into())
|
.on_commit(|_| PortfolioMessage::Open.into())
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
@@ -1032,20 +1031,21 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
TextButton::new("Open Demo Artwork")
|
TextButton::new("Open Demo Artwork")
|
||||||
.icon(Some("Image".into()))
|
.icon("Image")
|
||||||
.flush(true)
|
.flush(true)
|
||||||
.on_commit(|_| DialogMessage::RequestDemoArtworkDialog.into())
|
.on_commit(|_| DialogMessage::RequestDemoArtworkDialog.into())
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
TextButton::new("Support the Development Fund")
|
TextButton::new("Support the Development Fund")
|
||||||
.icon(Some("Heart".into()))
|
.icon("Heart")
|
||||||
.flush(true)
|
.flush(true)
|
||||||
.on_commit(move |_| FrontendMessage::TriggerVisitLink { url: donate.to_string() }.into())
|
.on_commit(move |_| FrontendMessage::TriggerVisitLink { url: donate.to_string() }.into())
|
||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
};
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
responses.add(LayoutMessage::DestroyLayout {
|
responses.add(LayoutMessage::DestroyLayout {
|
||||||
layout_target: LayoutTarget::WelcomeScreenButtons,
|
layout_target: LayoutTarget::WelcomeScreenButtons,
|
||||||
@@ -1061,7 +1061,7 @@ impl MessageHandler<PortfolioMessage, PortfolioMessageContext<'_>> for Portfolio
|
|||||||
#[cfg(target_family = "wasm")]
|
#[cfg(target_family = "wasm")]
|
||||||
let widgets = vec![];
|
let widgets = vec![];
|
||||||
|
|
||||||
let row = LayoutGroup::Row { widgets };
|
let row = LayoutGroup::row(widgets);
|
||||||
|
|
||||||
responses.add(LayoutMessage::SendLayout {
|
responses.add(LayoutMessage::SendLayout {
|
||||||
layout: Layout(vec![row]),
|
layout: Layout(vec![row]),
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ pub struct PreferencesMessageContext<'a> {
|
|||||||
pub tool_message_handler: &'a ToolMessageHandler,
|
pub tool_message_handler: &'a ToolMessageHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type, ExtractField)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, ExtractField)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct PreferencesMessageHandler {
|
pub struct PreferencesMessageHandler {
|
||||||
pub selection_mode: SelectionMode,
|
pub selection_mode: SelectionMode,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type, Hash)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash)]
|
||||||
pub enum SelectionMode {
|
pub enum SelectionMode {
|
||||||
#[default]
|
#[default]
|
||||||
Touched = 0,
|
Touched = 0,
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ use crate::messages::prelude::*;
|
|||||||
use graphene_std::Color;
|
use graphene_std::Color;
|
||||||
use graphene_std::vector::style::FillChoice;
|
use graphene_std::vector::style::FillChoice;
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum ToolColorType {
|
pub enum ToolColorType {
|
||||||
Primary,
|
Primary,
|
||||||
Secondary,
|
Secondary,
|
||||||
|
|||||||
@@ -150,7 +150,8 @@ impl PivotGizmo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum PivotGizmoType {
|
pub enum PivotGizmoType {
|
||||||
// Pivot
|
// Pivot
|
||||||
#[default]
|
#[default]
|
||||||
@@ -161,7 +162,8 @@ pub enum PivotGizmoType {
|
|||||||
// TODO: Add "Individual"
|
// TODO: Add "Individual"
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct PivotGizmoState {
|
pub struct PivotGizmoState {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub gizmo_type: PivotGizmoType,
|
pub gizmo_type: PivotGizmoType,
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ use kurbo::{BezPath, PathEl, Shape};
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::f64::consts::{PI, TAU};
|
use std::f64::consts::{PI, TAU};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum ShapeType {
|
pub enum ShapeType {
|
||||||
#[default]
|
#[default]
|
||||||
Polygon = 0,
|
Polygon = 0,
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ pub struct ArtboardTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Artboard)]
|
#[impl_message(Message, ToolMessage, Artboard)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum ArtboardToolMessage {
|
pub enum ArtboardToolMessage {
|
||||||
// Standard messages
|
// Standard messages
|
||||||
Abort,
|
Abort,
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ use graphene_std::raster::BlendMode;
|
|||||||
|
|
||||||
const BRUSH_MAX_SIZE: f64 = 5000.;
|
const BRUSH_MAX_SIZE: f64 = 5000.;
|
||||||
|
|
||||||
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum DrawMode {
|
pub enum DrawMode {
|
||||||
Draw = 0,
|
Draw = 0,
|
||||||
Erase,
|
Erase,
|
||||||
@@ -52,7 +53,8 @@ impl Default for BrushOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Brush)]
|
#[impl_message(Message, ToolMessage, Brush)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum BrushToolMessage {
|
pub enum BrushToolMessage {
|
||||||
// Standard messages
|
// Standard messages
|
||||||
Abort,
|
Abort,
|
||||||
@@ -65,7 +67,8 @@ pub enum BrushToolMessage {
|
|||||||
UpdateOptions { options: BrushToolMessageOptionsUpdate },
|
UpdateOptions { options: BrushToolMessageOptionsUpdate },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum BrushToolMessageOptionsUpdate {
|
pub enum BrushToolMessageOptionsUpdate {
|
||||||
BlendMode(BlendMode),
|
BlendMode(BlendMode),
|
||||||
ChangeDiameter(f64),
|
ChangeDiameter(f64),
|
||||||
@@ -220,7 +223,7 @@ impl LayoutHolder for BrushTool {
|
|||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ pub struct EyedropperTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Eyedropper)]
|
#[impl_message(Message, ToolMessage, Eyedropper)]
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum EyedropperToolMessage {
|
pub enum EyedropperToolMessage {
|
||||||
// Standard messages
|
// Standard messages
|
||||||
Abort,
|
Abort,
|
||||||
@@ -46,9 +47,9 @@ impl LayoutHolder for EyedropperTool {
|
|||||||
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for EyedropperTool {
|
impl<'a> MessageHandler<ToolMessage, &mut ToolActionMessageContext<'a>> for EyedropperTool {
|
||||||
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
|
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, context: &mut ToolActionMessageContext<'a>) {
|
||||||
if let ToolMessage::Eyedropper(EyedropperToolMessage::PreviewImage { data, width, height }) = message {
|
if let ToolMessage::Eyedropper(EyedropperToolMessage::PreviewImage { data, width, height }) = message {
|
||||||
let image = EyedropperPreviewImage { data, width, height };
|
let image = EyedropperPreviewImage { data: data.into(), width, height };
|
||||||
|
|
||||||
update_cursor_preview_common(responses, Some(image), context.input, context.global_tool_data, self.data.color_choice.clone());
|
update_cursor_preview_common(responses, Some(image), context.input, context.global_tool_data, self.data.color_choice);
|
||||||
|
|
||||||
if !self.data.preview {
|
if !self.data.preview {
|
||||||
disable_cursor_preview(responses, &mut self.data);
|
disable_cursor_preview(responses, &mut self.data);
|
||||||
@@ -87,10 +88,18 @@ enum EyedropperToolFsmState {
|
|||||||
SamplingSecondary,
|
SamplingSecondary,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum PrimarySecondary {
|
||||||
|
#[default]
|
||||||
|
Primary,
|
||||||
|
Secondary,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
struct EyedropperToolData {
|
struct EyedropperToolData {
|
||||||
preview: bool,
|
preview: bool,
|
||||||
color_choice: Option<String>,
|
color_choice: Option<PrimarySecondary>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fsm for EyedropperToolFsmState {
|
impl Fsm for EyedropperToolFsmState {
|
||||||
@@ -127,7 +136,11 @@ impl Fsm for EyedropperToolFsmState {
|
|||||||
}
|
}
|
||||||
// Sampling -> Ready
|
// Sampling -> Ready
|
||||||
(EyedropperToolFsmState::SamplingPrimary, EyedropperToolMessage::SamplePrimaryColorEnd) | (EyedropperToolFsmState::SamplingSecondary, EyedropperToolMessage::SampleSecondaryColorEnd) => {
|
(EyedropperToolFsmState::SamplingPrimary, EyedropperToolMessage::SamplePrimaryColorEnd) | (EyedropperToolFsmState::SamplingSecondary, EyedropperToolMessage::SampleSecondaryColorEnd) => {
|
||||||
let set_color_choice = if self == EyedropperToolFsmState::SamplingPrimary { "Primary" } else { "Secondary" }.to_string();
|
let set_color_choice = match self {
|
||||||
|
EyedropperToolFsmState::SamplingPrimary => PrimarySecondary::Primary,
|
||||||
|
EyedropperToolFsmState::SamplingSecondary => PrimarySecondary::Secondary,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
update_cursor_preview(responses, tool_data, input, global_tool_data, Some(set_color_choice));
|
update_cursor_preview(responses, tool_data, input, global_tool_data, Some(set_color_choice));
|
||||||
disable_cursor_preview(responses, tool_data);
|
disable_cursor_preview(responses, tool_data);
|
||||||
|
|
||||||
@@ -185,7 +198,7 @@ fn update_cursor_preview(
|
|||||||
tool_data: &mut EyedropperToolData,
|
tool_data: &mut EyedropperToolData,
|
||||||
_input: &InputPreprocessorMessageHandler,
|
_input: &InputPreprocessorMessageHandler,
|
||||||
_global_tool_data: &DocumentToolData,
|
_global_tool_data: &DocumentToolData,
|
||||||
set_color_choice: Option<String>,
|
set_color_choice: Option<PrimarySecondary>,
|
||||||
) {
|
) {
|
||||||
tool_data.preview = true;
|
tool_data.preview = true;
|
||||||
tool_data.color_choice = set_color_choice;
|
tool_data.color_choice = set_color_choice;
|
||||||
@@ -198,7 +211,7 @@ fn update_cursor_preview(
|
|||||||
tool_data: &mut EyedropperToolData,
|
tool_data: &mut EyedropperToolData,
|
||||||
input: &InputPreprocessorMessageHandler,
|
input: &InputPreprocessorMessageHandler,
|
||||||
global_tool_data: &DocumentToolData,
|
global_tool_data: &DocumentToolData,
|
||||||
set_color_choice: Option<String>,
|
set_color_choice: Option<PrimarySecondary>,
|
||||||
) {
|
) {
|
||||||
tool_data.preview = true;
|
tool_data.preview = true;
|
||||||
tool_data.color_choice = set_color_choice.clone();
|
tool_data.color_choice = set_color_choice.clone();
|
||||||
@@ -211,7 +224,7 @@ fn update_cursor_preview_common(
|
|||||||
image: Option<EyedropperPreviewImage>,
|
image: Option<EyedropperPreviewImage>,
|
||||||
input: &InputPreprocessorMessageHandler,
|
input: &InputPreprocessorMessageHandler,
|
||||||
global_tool_data: &DocumentToolData,
|
global_tool_data: &DocumentToolData,
|
||||||
set_color_choice: Option<String>,
|
set_color_choice: Option<PrimarySecondary>,
|
||||||
) {
|
) {
|
||||||
responses.add(FrontendMessage::UpdateEyedropperSamplingState {
|
responses.add(FrontendMessage::UpdateEyedropperSamplingState {
|
||||||
image,
|
image,
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ pub struct FillTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Fill)]
|
#[impl_message(Message, ToolMessage, Fill)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum FillToolMessage {
|
pub enum FillToolMessage {
|
||||||
// Standard messages
|
// Standard messages
|
||||||
Abort,
|
Abort,
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ impl Default for FreehandOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Freehand)]
|
#[impl_message(Message, ToolMessage, Freehand)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum FreehandToolMessage {
|
pub enum FreehandToolMessage {
|
||||||
// Standard messages
|
// Standard messages
|
||||||
Overlays { context: OverlayContext },
|
Overlays { context: OverlayContext },
|
||||||
@@ -51,7 +52,8 @@ pub enum FreehandToolMessage {
|
|||||||
UpdateOptions { options: FreehandOptionsUpdate },
|
UpdateOptions { options: FreehandOptionsUpdate },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum FreehandOptionsUpdate {
|
pub enum FreehandOptionsUpdate {
|
||||||
FillColor(Option<Color>),
|
FillColor(Option<Color>),
|
||||||
FillColorType(ToolColorType),
|
FillColorType(ToolColorType),
|
||||||
@@ -151,7 +153,7 @@ impl LayoutHolder for FreehandTool {
|
|||||||
widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
|
widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
|
||||||
widgets.push(create_weight_widget(self.options.line_weight));
|
widgets.push(create_weight_widget(self.options.line_weight));
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ pub struct GradientOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Gradient)]
|
#[impl_message(Message, ToolMessage, Gradient)]
|
||||||
#[derive(PartialEq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum GradientToolMessage {
|
pub enum GradientToolMessage {
|
||||||
// Standard messages
|
// Standard messages
|
||||||
Abort,
|
Abort,
|
||||||
@@ -46,7 +47,8 @@ pub enum GradientToolMessage {
|
|||||||
UpdateOptions { options: GradientOptionsUpdate },
|
UpdateOptions { options: GradientOptionsUpdate },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum GradientOptionsUpdate {
|
pub enum GradientOptionsUpdate {
|
||||||
Type(GradientType),
|
Type(GradientType),
|
||||||
ReverseStops,
|
ReverseStops,
|
||||||
@@ -199,7 +201,7 @@ impl LayoutHolder for GradientTool {
|
|||||||
widgets.push(reverse_direction);
|
widgets.push(reverse_direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -770,8 +772,8 @@ impl Fsm for GradientToolFsmState {
|
|||||||
if stop_index < gradient.stops.position.len() {
|
if stop_index < gradient.stops.position.len() {
|
||||||
let color = gradient.stops.color[stop_index].to_gamma_srgb();
|
let color = gradient.stops.color[stop_index].to_gamma_srgb();
|
||||||
let position = gradient.stops.position[stop_index];
|
let position = gradient.stops.position[stop_index];
|
||||||
let DVec2 { x, y } = transform.transform_point2(gradient.start.lerp(gradient.end, position));
|
let position = transform.transform_point2(gradient.start.lerp(gradient.end, position)).into();
|
||||||
responses.add(FrontendMessage::UpdateGradientStopColorPickerPosition { color, x, y });
|
responses.add(FrontendMessage::UpdateGradientStopColorPickerPosition { color, position });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -824,13 +826,10 @@ impl Fsm for GradientToolFsmState {
|
|||||||
let viewport_pos = selected_gradient
|
let viewport_pos = selected_gradient
|
||||||
.transform
|
.transform
|
||||||
.transform_point2(selected_gradient.gradient.start.lerp(selected_gradient.gradient.end, stop_pos));
|
.transform_point2(selected_gradient.gradient.start.lerp(selected_gradient.gradient.end, stop_pos));
|
||||||
|
let position = viewport_pos.into();
|
||||||
let color = selected_gradient.gradient.stops.color[stop_index].to_gamma_srgb();
|
let color = selected_gradient.gradient.stops.color[stop_index].to_gamma_srgb();
|
||||||
tool_data.color_picker_editing_color_stop = Some(stop_index);
|
tool_data.color_picker_editing_color_stop = Some(stop_index);
|
||||||
responses.add(FrontendMessage::UpdateGradientStopColorPickerPosition {
|
responses.add(FrontendMessage::UpdateGradientStopColorPickerPosition { color, position });
|
||||||
color,
|
|
||||||
x: viewport_pos.x,
|
|
||||||
y: viewport_pos.y,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ pub struct NavigateTool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Navigate)]
|
#[impl_message(Message, ToolMessage, Navigate)]
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum NavigateToolMessage {
|
pub enum NavigateToolMessage {
|
||||||
// Standard messages
|
// Standard messages
|
||||||
Abort,
|
Abort,
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ pub struct PathToolOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Path)]
|
#[impl_message(Message, ToolMessage, Path)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum PathToolMessage {
|
pub enum PathToolMessage {
|
||||||
// Standard messages
|
// Standard messages
|
||||||
Abort,
|
Abort,
|
||||||
@@ -155,7 +156,8 @@ pub enum PathToolMessage {
|
|||||||
ToggleSegmentEditing,
|
ToggleSegmentEditing,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, Default, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum PathOverlayMode {
|
pub enum PathOverlayMode {
|
||||||
AllHandles = 0,
|
AllHandles = 0,
|
||||||
#[default]
|
#[default]
|
||||||
@@ -178,7 +180,8 @@ impl Default for PathEditingMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum PathOptionsUpdate {
|
pub enum PathOptionsUpdate {
|
||||||
OverlayModeType(PathOverlayMode),
|
OverlayModeType(PathOverlayMode),
|
||||||
PointEditingMode { enabled: bool },
|
PointEditingMode { enabled: bool },
|
||||||
@@ -326,7 +329,7 @@ impl LayoutHolder for PathTool {
|
|||||||
|
|
||||||
// Works only if a single layer is selected and its type is Vector
|
// Works only if a single layer is selected and its type is Vector
|
||||||
let path_node_button = TextButton::new("Make Path Editable")
|
let path_node_button = TextButton::new("Make Path Editable")
|
||||||
.icon(Some("NodeShape".into()))
|
.icon("NodeShape")
|
||||||
.tooltip_label("Make Path Editable")
|
.tooltip_label("Make Path Editable")
|
||||||
.tooltip_description(
|
.tooltip_description(
|
||||||
"Enables the Pen and Path tools to directly edit layer geometry resulting from nondestructive operations. This inserts a 'Path' node as the last operation of the selected layer.",
|
"Enables the Pen and Path tools to directly edit layer geometry resulting from nondestructive operations. This inserts a 'Path' node as the last operation of the selected layer.",
|
||||||
@@ -349,32 +352,30 @@ impl LayoutHolder for PathTool {
|
|||||||
|
|
||||||
let _pin_pivot = pin_pivot_widget(self.tool_data.pivot_gizmo.pin_active(), false, PivotToolSource::Path);
|
let _pin_pivot = pin_pivot_widget(self.tool_data.pivot_gizmo.pin_active(), false, PivotToolSource::Path);
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row {
|
Layout(vec![LayoutGroup::row(vec![
|
||||||
widgets: vec![
|
x_location,
|
||||||
x_location,
|
related_seperator.clone(),
|
||||||
related_seperator.clone(),
|
y_location,
|
||||||
y_location,
|
unrelated_seperator.clone(),
|
||||||
unrelated_seperator.clone(),
|
colinear_handle_checkbox,
|
||||||
colinear_handle_checkbox,
|
related_seperator.clone(),
|
||||||
related_seperator.clone(),
|
colinear_handles_label,
|
||||||
colinear_handles_label,
|
unrelated_seperator.clone(),
|
||||||
unrelated_seperator.clone(),
|
point_editing_mode,
|
||||||
point_editing_mode,
|
related_seperator.clone(),
|
||||||
related_seperator.clone(),
|
segment_editing_mode,
|
||||||
segment_editing_mode,
|
unrelated_seperator.clone(),
|
||||||
unrelated_seperator.clone(),
|
path_overlay_mode_widget,
|
||||||
path_overlay_mode_widget,
|
unrelated_seperator.clone(),
|
||||||
unrelated_seperator.clone(),
|
path_node_button,
|
||||||
path_node_button,
|
// checkbox.clone(),
|
||||||
// checkbox.clone(),
|
// related_seperator.clone(),
|
||||||
// related_seperator.clone(),
|
// dropdown.clone(),
|
||||||
// dropdown.clone(),
|
// unrelated_seperator,
|
||||||
// unrelated_seperator,
|
// pivot_reference,
|
||||||
// pivot_reference,
|
// related_seperator.clone(),
|
||||||
// related_seperator.clone(),
|
// pin_pivot,
|
||||||
// pin_pivot,
|
])])
|
||||||
],
|
|
||||||
}])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ impl Default for PenOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Pen)]
|
#[impl_message(Message, ToolMessage, Pen)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum PenToolMessage {
|
pub enum PenToolMessage {
|
||||||
// Standard messages
|
// Standard messages
|
||||||
Abort,
|
Abort,
|
||||||
@@ -107,13 +108,15 @@ enum PenToolFsmState {
|
|||||||
GRSHandle,
|
GRSHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum PenOverlayMode {
|
pub enum PenOverlayMode {
|
||||||
AllHandles = 0,
|
AllHandles = 0,
|
||||||
FrontierHandles = 1,
|
FrontierHandles = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum PenOptionsUpdate {
|
pub enum PenOptionsUpdate {
|
||||||
FillColor(Option<Color>),
|
FillColor(Option<Color>),
|
||||||
FillColorType(ToolColorType),
|
FillColorType(ToolColorType),
|
||||||
@@ -238,7 +241,7 @@ impl LayoutHolder for PenTool {
|
|||||||
.widget_instance(),
|
.widget_instance(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ pub struct SelectOptions {
|
|||||||
nested_selection_behavior: NestedSelectionBehavior,
|
nested_selection_behavior: NestedSelectionBehavior,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum SelectOptionsUpdate {
|
pub enum SelectOptionsUpdate {
|
||||||
NestedSelectionBehavior(NestedSelectionBehavior),
|
NestedSelectionBehavior(NestedSelectionBehavior),
|
||||||
PivotGizmoType(PivotGizmoType),
|
PivotGizmoType(PivotGizmoType),
|
||||||
@@ -50,7 +51,8 @@ pub enum SelectOptionsUpdate {
|
|||||||
TogglePivotPinned,
|
TogglePivotPinned,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum NestedSelectionBehavior {
|
pub enum NestedSelectionBehavior {
|
||||||
#[default]
|
#[default]
|
||||||
Shallowest,
|
Shallowest,
|
||||||
@@ -66,7 +68,8 @@ impl fmt::Display for NestedSelectionBehavior {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct SelectToolPointerKeys {
|
pub struct SelectToolPointerKeys {
|
||||||
pub axis_align: Key,
|
pub axis_align: Key,
|
||||||
pub snap_angle: Key,
|
pub snap_angle: Key,
|
||||||
@@ -75,7 +78,8 @@ pub struct SelectToolPointerKeys {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Select)]
|
#[impl_message(Message, ToolMessage, Select)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum SelectToolMessage {
|
pub enum SelectToolMessage {
|
||||||
// Standard messages
|
// Standard messages
|
||||||
Abort,
|
Abort,
|
||||||
@@ -265,7 +269,7 @@ impl LayoutHolder for SelectTool {
|
|||||||
widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
|
widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
|
||||||
widgets.extend(self.boolean_widgets(self.tool_data.selected_layers_count));
|
widgets.extend(self.boolean_widgets(self.tool_data.selected_layers_count));
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ impl Default for ShapeToolOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum ShapeOptionsUpdate {
|
pub enum ShapeOptionsUpdate {
|
||||||
FillColor(Option<Color>),
|
FillColor(Option<Color>),
|
||||||
FillColorType(ToolColorType),
|
FillColorType(ToolColorType),
|
||||||
@@ -88,7 +89,8 @@ pub enum ShapeOptionsUpdate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Shape)]
|
#[impl_message(Message, ToolMessage, Shape)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum ShapeToolMessage {
|
pub enum ShapeToolMessage {
|
||||||
// Standard messages
|
// Standard messages
|
||||||
Overlays { context: OverlayContext },
|
Overlays { context: OverlayContext },
|
||||||
@@ -423,7 +425,7 @@ impl LayoutHolder for ShapeTool {
|
|||||||
widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
|
widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
|
||||||
widgets.push(create_weight_widget(self.options.line_weight));
|
widgets.push(create_weight_widget(self.options.line_weight));
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ impl Default for SplineOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Spline)]
|
#[impl_message(Message, ToolMessage, Spline)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum SplineToolMessage {
|
pub enum SplineToolMessage {
|
||||||
// Standard messages
|
// Standard messages
|
||||||
Overlays { context: OverlayContext },
|
Overlays { context: OverlayContext },
|
||||||
@@ -65,7 +66,8 @@ enum SplineToolFsmState {
|
|||||||
MergingEndpoints,
|
MergingEndpoints,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum SplineOptionsUpdate {
|
pub enum SplineOptionsUpdate {
|
||||||
FillColor(Option<Color>),
|
FillColor(Option<Color>),
|
||||||
FillColorType(ToolColorType),
|
FillColorType(ToolColorType),
|
||||||
@@ -158,7 +160,7 @@ impl LayoutHolder for SplineTool {
|
|||||||
widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
|
widgets.push(Separator::new(SeparatorStyle::Unrelated).widget_instance());
|
||||||
widgets.push(create_weight_widget(self.options.line_weight));
|
widgets.push(create_weight_widget(self.options.line_weight));
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ impl Default for TextOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[impl_message(Message, ToolMessage, Text)]
|
#[impl_message(Message, ToolMessage, Text)]
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum TextToolMessage {
|
pub enum TextToolMessage {
|
||||||
// Standard messages
|
// Standard messages
|
||||||
Abort,
|
Abort,
|
||||||
@@ -75,7 +76,8 @@ pub enum TextToolMessage {
|
|||||||
RefreshEditingFontData,
|
RefreshEditingFontData,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub enum TextOptionsUpdate {
|
pub enum TextOptionsUpdate {
|
||||||
FillColor(Option<Color>),
|
FillColor(Option<Color>),
|
||||||
FillColorType(ToolColorType),
|
FillColorType(ToolColorType),
|
||||||
@@ -271,7 +273,7 @@ impl TextTool {
|
|||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,7 +415,7 @@ impl TextToolData {
|
|||||||
line_height_ratio: editing_text.typesetting.line_height_ratio,
|
line_height_ratio: editing_text.typesetting.line_height_ratio,
|
||||||
font_size: editing_text.typesetting.font_size,
|
font_size: editing_text.typesetting.font_size,
|
||||||
color: editing_text.color.map_or("#000000".to_string(), |color| format!("#{}", color.to_rgba_hex_srgb())),
|
color: editing_text.color.map_or("#000000".to_string(), |color| format!("#{}", color.to_rgba_hex_srgb())),
|
||||||
font_data: font_cache.get(&editing_text.font).map(|(data, _)| data.clone()).unwrap_or_default(),
|
font_data: font_cache.get(&editing_text.font).map(|(data, _)| data.clone()).unwrap_or_default().into(),
|
||||||
transform: editing_text.transform.to_cols_array(),
|
transform: editing_text.transform.to_cols_array(),
|
||||||
max_width: editing_text.typesetting.max_width,
|
max_width: editing_text.typesetting.max_width,
|
||||||
max_height: editing_text.typesetting.max_height,
|
max_height: editing_text.typesetting.max_height,
|
||||||
@@ -925,7 +927,7 @@ impl Fsm for TextToolFsmState {
|
|||||||
(TextToolFsmState::Editing, TextToolMessage::RefreshEditingFontData) => {
|
(TextToolFsmState::Editing, TextToolMessage::RefreshEditingFontData) => {
|
||||||
let font = Font::new(tool_options.font.font_family.clone(), tool_options.font.font_style.clone());
|
let font = Font::new(tool_options.font.font_family.clone(), tool_options.font.font_style.clone());
|
||||||
responses.add(FrontendMessage::DisplayEditableTextboxUpdateFontData {
|
responses.add(FrontendMessage::DisplayEditableTextboxUpdateFontData {
|
||||||
font_data: font_cache.get(&font).map(|(data, _)| data.clone()).unwrap_or_default(),
|
font_data: font_cache.get(&font).map(|(data, _)| data.clone()).unwrap_or_default().into(),
|
||||||
});
|
});
|
||||||
|
|
||||||
TextToolFsmState::Editing
|
TextToolFsmState::Editing
|
||||||
|
|||||||
@@ -128,23 +128,21 @@ pub struct DocumentToolData {
|
|||||||
impl DocumentToolData {
|
impl DocumentToolData {
|
||||||
pub fn update_working_colors(&self, responses: &mut VecDeque<Message>) {
|
pub fn update_working_colors(&self, responses: &mut VecDeque<Message>) {
|
||||||
let layout = Layout(vec![
|
let layout = Layout(vec![
|
||||||
LayoutGroup::Row {
|
LayoutGroup::row(vec![
|
||||||
widgets: vec![WorkingColorsInput::new(self.primary_color.to_gamma_srgb(), self.secondary_color.to_gamma_srgb()).widget_instance()],
|
WorkingColorsInput::new(self.primary_color.to_gamma_srgb(), self.secondary_color.to_gamma_srgb()).widget_instance(),
|
||||||
},
|
]),
|
||||||
LayoutGroup::Row {
|
LayoutGroup::row(vec![
|
||||||
widgets: vec![
|
IconButton::new("SwapVertical", 16)
|
||||||
IconButton::new("SwapVertical", 16)
|
.tooltip_label("Swap Working Colors")
|
||||||
.tooltip_label("Swap Working Colors")
|
.tooltip_shortcut(action_shortcut!(ToolMessageDiscriminant::SwapColors))
|
||||||
.tooltip_shortcut(action_shortcut!(ToolMessageDiscriminant::SwapColors))
|
.on_update(|_| ToolMessage::SwapColors.into())
|
||||||
.on_update(|_| ToolMessage::SwapColors.into())
|
.widget_instance(),
|
||||||
.widget_instance(),
|
IconButton::new("WorkingColors", 16)
|
||||||
IconButton::new("WorkingColors", 16)
|
.tooltip_label("Reset Working Colors")
|
||||||
.tooltip_label("Reset Working Colors")
|
.tooltip_shortcut(action_shortcut!(ToolMessageDiscriminant::ResetColors))
|
||||||
.tooltip_shortcut(action_shortcut!(ToolMessageDiscriminant::ResetColors))
|
.on_update(|_| ToolMessage::ResetColors.into())
|
||||||
.on_update(|_| ToolMessage::ResetColors.into())
|
.widget_instance(),
|
||||||
.widget_instance(),
|
]),
|
||||||
],
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
responses.add(LayoutMessage::SendLayout {
|
responses.add(LayoutMessage::SendLayout {
|
||||||
@@ -308,7 +306,7 @@ impl ToolData {
|
|||||||
.skip(1)
|
.skip(1)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets: tool_groups_layout }])
|
Layout(vec![LayoutGroup::row(tool_groups_layout)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,7 +358,8 @@ impl ToolFsmState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[repr(usize)]
|
#[repr(usize)]
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Default, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize, Default)]
|
||||||
pub enum ToolType {
|
pub enum ToolType {
|
||||||
// General tool group
|
// General tool group
|
||||||
#[default]
|
#[default]
|
||||||
@@ -524,7 +523,8 @@ pub fn tool_type_to_activate_tool_message(tool_type: ToolType) -> ToolMessageDis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct HintData(pub Vec<HintGroup>);
|
pub struct HintData(pub Vec<HintGroup>);
|
||||||
|
|
||||||
impl HintData {
|
impl HintData {
|
||||||
@@ -558,7 +558,7 @@ impl HintData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Layout(vec![LayoutGroup::Row { widgets }])
|
Layout(vec![LayoutGroup::row(widgets)])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn send_layout(&self, responses: &mut VecDeque<Message>) {
|
pub fn send_layout(&self, responses: &mut VecDeque<Message>) {
|
||||||
@@ -576,10 +576,12 @@ impl HintData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct HintGroup(pub Vec<HintInfo>);
|
pub struct HintGroup(pub Vec<HintInfo>);
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct HintInfo {
|
pub struct HintInfo {
|
||||||
/// A `KeysGroup` specifies all the keys pressed simultaneously to perform an action (like "Ctrl C" to copy).
|
/// A `KeysGroup` specifies all the keys pressed simultaneously to perform an action (like "Ctrl C" to copy).
|
||||||
/// Usually at most one is given, but less commonly, multiple can be used to describe additional hotkeys not used simultaneously (like the four different arrow keys to nudge a layer).
|
/// Usually at most one is given, but less commonly, multiple can be used to describe additional hotkeys not used simultaneously (like the four different arrow keys to nudge a layer).
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ use std::ops::{Add, Div, Mul, Sub};
|
|||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use crate::messages::tool::tool_messages::tool_prelude::DVec2;
|
use crate::messages::tool::tool_messages::tool_prelude::DVec2;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type, ExtractField)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, ExtractField)]
|
||||||
pub struct ViewportMessageHandler {
|
pub struct ViewportMessageHandler {
|
||||||
bounds: Bounds,
|
bounds: Bounds,
|
||||||
// Ratio of logical pixels to physical pixels
|
// Ratio of logical pixels to physical pixels
|
||||||
@@ -157,7 +158,8 @@ pub trait Position {
|
|||||||
fn y(&self) -> f64;
|
fn y(&self) -> f64;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)]
|
||||||
struct Point {
|
struct Point {
|
||||||
x: f64,
|
x: f64,
|
||||||
y: f64,
|
y: f64,
|
||||||
@@ -183,7 +185,8 @@ impl Position for Point {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct LogicalPoint {
|
pub struct LogicalPoint {
|
||||||
inner: Point,
|
inner: Point,
|
||||||
scale: f64,
|
scale: f64,
|
||||||
@@ -217,7 +220,8 @@ impl FromWithScale<Point> for LogicalPoint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct PhysicalPoint {
|
pub struct PhysicalPoint {
|
||||||
inner: Point,
|
inner: Point,
|
||||||
scale: f64,
|
scale: f64,
|
||||||
@@ -258,7 +262,8 @@ pub trait Rect<P: Position>: Position {
|
|||||||
fn height(&self) -> f64;
|
fn height(&self) -> f64;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type, ExtractField)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, ExtractField)]
|
||||||
struct Bounds {
|
struct Bounds {
|
||||||
offset: Point,
|
offset: Point,
|
||||||
size: Point,
|
size: Point,
|
||||||
@@ -286,7 +291,8 @@ impl Rect<Point> for Bounds {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct LogicalBounds {
|
pub struct LogicalBounds {
|
||||||
offset: Point,
|
offset: Point,
|
||||||
size: Point,
|
size: Point,
|
||||||
@@ -338,7 +344,8 @@ impl FromWithScale<Bounds> for LogicalBounds {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize, specta::Type)]
|
#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct PhysicalBounds {
|
pub struct PhysicalBounds {
|
||||||
offset: Point,
|
offset: Point,
|
||||||
size: Point,
|
size: Point,
|
||||||
|
|||||||
@@ -450,7 +450,10 @@ impl NodeGraphExecutor {
|
|||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
if file_type == FileType::Svg {
|
if file_type == FileType::Svg {
|
||||||
responses.add(FrontendMessage::TriggerSaveFile { name, content: svg.into_bytes() });
|
responses.add(FrontendMessage::TriggerSaveFile {
|
||||||
|
name,
|
||||||
|
content: svg.into_bytes().into(),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
let mime = file_type.to_mime().to_string();
|
let mime = file_type.to_mime().to_string();
|
||||||
let size = (size * scale_factor).into();
|
let size = (size * scale_factor).into();
|
||||||
@@ -496,7 +499,7 @@ impl NodeGraphExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
responses.add(FrontendMessage::TriggerSaveFile { name, content: encoded });
|
responses.add(FrontendMessage::TriggerSaveFile { name, content: encoded.into() });
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(format!("Incorrect render type for exporting to an SVG ({file_type:?}, {node_graph_output})"));
|
return Err(format!("Incorrect render type for exporting to an SVG ({file_type:?}, {node_graph_output})"));
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ pub struct NodeRuntime {
|
|||||||
thumbnail_renders: HashMap<NodeId, Vec<SvgSegment>>,
|
thumbnail_renders: HashMap<NodeId, Vec<SvgSegment>>,
|
||||||
vector_modify: HashMap<NodeId, Vector>,
|
vector_modify: HashMap<NodeId, Vector>,
|
||||||
|
|
||||||
/// Cached surface for WASM viewport rendering (reused across frames)
|
/// Cached surface for Wasm viewport rendering (reused across frames)
|
||||||
#[cfg(all(target_family = "wasm", feature = "gpu"))]
|
#[cfg(all(target_family = "wasm", feature = "gpu"))]
|
||||||
wasm_viewport_surface: Option<wgpu_executor::WgpuSurface>,
|
wasm_viewport_surface: Option<wgpu_executor::WgpuSurface>,
|
||||||
}
|
}
|
||||||
@@ -309,7 +309,7 @@ impl NodeRuntime {
|
|||||||
data: RenderOutputType::Texture(image_texture),
|
data: RenderOutputType::Texture(image_texture),
|
||||||
metadata,
|
metadata,
|
||||||
})) if !render_config.for_export => {
|
})) if !render_config.for_export => {
|
||||||
// On WASM, for viewport rendering, blit the texture to a surface and return a CanvasFrame
|
// On Wasm, for viewport rendering, blit the texture to a surface and return a CanvasFrame
|
||||||
let app_io = self.editor_api.application_io.as_ref().unwrap();
|
let app_io = self.editor_api.application_io.as_ref().unwrap();
|
||||||
let executor = app_io.gpu_executor().expect("GPU executor should be available when we receive a texture");
|
let executor = app_io.gpu_executor().expect("GPU executor should be available when we receive a texture");
|
||||||
|
|
||||||
|
|||||||
+16
-15
@@ -4,11 +4,7 @@ The Graphite frontend is a web app that provides the presentation for the editor
|
|||||||
|
|
||||||
## Bundled assets: `assets/`
|
## Bundled assets: `assets/`
|
||||||
|
|
||||||
Icons and images that are used in components and embedded into the application bundle by the build system.
|
Images that are used in components and embedded into the application bundle by the build system.
|
||||||
|
|
||||||
## Public assets: `public/`
|
|
||||||
|
|
||||||
Static content like favicons that are copied directly into the root of the build output by the build system.
|
|
||||||
|
|
||||||
## Svelte/TypeScript source: `src/`
|
## Svelte/TypeScript source: `src/`
|
||||||
|
|
||||||
@@ -18,22 +14,27 @@ Source code for the web app in the form of Svelte components and [TypeScript](ht
|
|||||||
|
|
||||||
Wraps the editor backend codebase (`/editor`) and provides a JS-centric API for the web app to use as an entry point, unburdened by Rust's complex data types that are incompatible with JS data types. Bindings (JS functions that call into the Wasm module) are provided by [wasm-bindgen](https://rustwasm.github.io/docs/wasm-bindgen/) in concert with [wasm-pack](https://github.com/rustwasm/wasm-pack).
|
Wraps the editor backend codebase (`/editor`) and provides a JS-centric API for the web app to use as an entry point, unburdened by Rust's complex data types that are incompatible with JS data types. Bindings (JS functions that call into the Wasm module) are provided by [wasm-bindgen](https://rustwasm.github.io/docs/wasm-bindgen/) in concert with [wasm-pack](https://github.com/rustwasm/wasm-pack).
|
||||||
|
|
||||||
## ESLint configurations: `.eslintrc.cjs`
|
## ESLint configuration: `eslint.config.js`
|
||||||
|
|
||||||
[ESLint](https://eslint.org/) is the tool which enforces style rules on the JS, TS, and Svelte files in our frontend codebase. As it is set up in this config file, ESLint will complain about bad practices and often help reformat code automatically when (in VS Code) the file is saved or `npm run lint` is executed. (If you don't use VS Code, remember to run this command before committing!) This config file for ESLint sets our style preferences and configures our usage of extensions/plugins for Svelte support and [Prettier](https://prettier.io/)'s role as a code formatter.
|
When you use `npm run check`, [ESLint](https://eslint.org/) checks the code in the frontend project for code quality. (The command also reports TS and Svelte errors.) The tool enforces style rules on the JS, TS, and Svelte (including its HTML and SCSS) files in our frontend codebase. As it is set up in this config file, ESLint will complain about bad practices and often help reformat code automatically when the file is saved in VS Code, or manually when `npm run fix` is executed. (If you don't use VS Code, remember to run this command before committing!) This config file for ESLint sets our style preferences and configures our usage of extensions/plugins for Svelte support and [Prettier](https://prettier.io/)'s role as a code formatter.
|
||||||
|
|
||||||
|
## Svelte configuration: `svelte.config.js`
|
||||||
|
|
||||||
|
Configures the Svelte compiler, including the preprocessor setup for SCSS and TypeScript support, and compiler warning filters.
|
||||||
|
|
||||||
|
## TypeScript configuration: `tsconfig.json`
|
||||||
|
|
||||||
|
Basic configuration options for the TypeScript build tool to do its job in our repository.
|
||||||
|
|
||||||
|
## Vite configuration: `vite.config.ts`
|
||||||
|
|
||||||
|
We use the [Vite](https://vitejs.dev/) bundler/build system. This file is where we configure Vite to set up plugins (like the third-party license checker/generator). Part of the license checker plugin setup includes some functions to format web package licenses, as well as Rust package licenses provided by [cargo-about](https://github.com/EmbarkStudios/cargo-about), into a text file that's distributed with the application to provide license notices for third-party code.
|
||||||
|
|
||||||
## npm ecosystem packages: `package.json`
|
## npm ecosystem packages: `package.json`
|
||||||
|
|
||||||
While we don't use Node.js as a JS-based server, we do rely on its ecosystem of packages for our build system toolchain. If you're just getting started, make sure to install the latest LTS copy of [Node.js](https://nodejs.org/en/download). Our project's philosophy on third-party packages is to keep our dependency tree as light as possible, so adding anything new to our `package.json` should have overwhelming justification. Most of the packages are just development tooling (TypeScript, Vite, ESLint, Prettier, and [Sass](https://sass-lang.com/)) that run in your terminal during the build process.
|
While we don't use Node.js as a JS-based server, we do rely on its ecosystem of packages for our build system toolchain. Our project's philosophy on third-party packages is to keep our dependency tree as light as possible, so adding anything new to our `package.json` should have overwhelming justification. Most of the packages are just development tooling (TypeScript, Vite, ESLint, Prettier, Sass, etc.) that run in your terminal during the build process.
|
||||||
|
|
||||||
## npm package installed versions: `package-lock.json`
|
## npm package installed versions: `package-lock.json`
|
||||||
|
|
||||||
Specifies the exact versions of packages installed in the npm dependency tree. While `package.json` specifies which packages to install and their minimum/maximum acceptable version numbers, `package-lock.json` represents the exact versions of each dependency and sub-dependency. Running `npm ci` will grab these exact versions to ensure you are using the same packages as everyone else working on Graphite. `npm update` will modify `package-lock.json` to specify newer versions of any updated (sub-)dependencies and download those, as long as they don't exceed the maximum version allowed in `package.json`. To check for newer versions that exceed the max version, run `npm outdated` to see a list. Unless you know why you are doing it, try to avoid committing updates to `package-lock.json` by mistake if your code changes don't pertain to package updates. And never manually modify the file.
|
Specifies the exact versions of packages installed in the npm dependency tree. While `package.json` specifies which packages to install and their minimum/maximum acceptable version numbers, `package-lock.json` represents the exact versions of each dependency and sub-dependency. Running `npm ci` will grab these exact versions to ensure you are using the same packages as everyone else working on Graphite. `npm update` will modify `package-lock.json` to specify newer versions of any updated (sub-)dependencies and download those, as long as they don't exceed the maximum version allowed in `package.json`. To check for newer versions that exceed the max version, run `npm outdated` to see a list. Unless you know why you are doing it, try to avoid committing updates to `package-lock.json` by mistake if your code changes don't pertain to package updates. And never manually modify the file.
|
||||||
|
|
||||||
## TypeScript configurations: `tsconfig.json`
|
|
||||||
|
|
||||||
Basic configuration options for the TypeScript build tool to do its job in our repository.
|
|
||||||
|
|
||||||
## Vite configurations: `vite.config.ts`
|
|
||||||
|
|
||||||
We use the [Vite](https://vitejs.dev/) bundler/build system. This file is where we configure Vite to set up plugins (like the third-party license checker/generator). Part of the license checker plugin setup includes some functions to format web package licenses, as well as Rust package licenses provided by [cargo-about](https://github.com/EmbarkStudios/cargo-about), into a text file that's distributed with the application to provide license notices for third-party code.
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export default defineConfig([
|
|||||||
],
|
],
|
||||||
"@typescript-eslint/consistent-type-imports": "error",
|
"@typescript-eslint/consistent-type-imports": "error",
|
||||||
"@typescript-eslint/consistent-type-definitions": ["error", "type"],
|
"@typescript-eslint/consistent-type-definitions": ["error", "type"],
|
||||||
"@typescript-eslint/consistent-type-assertions": ["error", { assertionStyle: "as", objectLiteralTypeAssertions: "never" }],
|
"@typescript-eslint/consistent-type-assertions": ["error", { assertionStyle: "never" }],
|
||||||
"@typescript-eslint/consistent-indexed-object-style": ["error", "record"],
|
"@typescript-eslint/consistent-indexed-object-style": ["error", "record"],
|
||||||
"@typescript-eslint/consistent-generic-constructors": ["error", "constructor"],
|
"@typescript-eslint/consistent-generic-constructors": ["error", "constructor"],
|
||||||
"@typescript-eslint/no-restricted-types": ["error", { types: { null: "Use `undefined` instead." } }],
|
"@typescript-eslint/no-restricted-types": ["error", { types: { null: "Use `undefined` instead." } }],
|
||||||
|
|||||||
Generated
+48
@@ -31,6 +31,7 @@
|
|||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"sass": "^1.97.2",
|
"sass": "^1.97.2",
|
||||||
"svelte": "5.47.1",
|
"svelte": "5.47.1",
|
||||||
|
"svelte-check": "^4.4.4",
|
||||||
"svelte-preprocess": "^6.0.3",
|
"svelte-preprocess": "^6.0.3",
|
||||||
"tar": "^7.5.4",
|
"tar": "^7.5.4",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
@@ -5277,6 +5278,16 @@
|
|||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mri": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
@@ -6135,6 +6146,19 @@
|
|||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sade": {
|
||||||
|
"version": "1.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
|
||||||
|
"integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mri": "^1.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/safe-array-concat": {
|
"node_modules/safe-array-concat": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
|
||||||
@@ -6715,6 +6739,30 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/svelte-check": {
|
||||||
|
"version": "4.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.4.tgz",
|
||||||
|
"integrity": "sha512-F1pGqXc710Oi/wTI4d/x7d6lgPwwfx1U6w3Q35n4xsC2e8C/yN2sM1+mWxjlMcpAfWucjlq4vPi+P4FZ8a14sQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/trace-mapping": "^0.3.25",
|
||||||
|
"chokidar": "^4.0.1",
|
||||||
|
"fdir": "^6.2.0",
|
||||||
|
"picocolors": "^1.0.0",
|
||||||
|
"sade": "^1.7.4"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"svelte-check": "bin/svelte-check"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 18.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"svelte": "^4.0.0 || ^5.0.0-next.0",
|
||||||
|
"typescript": ">=5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/svelte-eslint-parser": {
|
"node_modules/svelte-eslint-parser": {
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-1.4.1.tgz",
|
||||||
|
|||||||
@@ -15,8 +15,8 @@
|
|||||||
"build-native": "npm run setup && npm run native:build-production",
|
"build-native": "npm run setup && npm run native:build-production",
|
||||||
"build-native-dev": "npm run setup && npm run native:build-dev",
|
"build-native-dev": "npm run setup && npm run native:build-dev",
|
||||||
"---------- UTILITIES ----------": "",
|
"---------- UTILITIES ----------": "",
|
||||||
"lint": "eslint . && tsc --noEmit",
|
"check": "svelte-check --fail-on-warnings && eslint",
|
||||||
"lint-fix": "eslint . --fix && tsc --noEmit",
|
"fix": "eslint --fix",
|
||||||
"---------- INTERNAL ----------": "",
|
"---------- INTERNAL ----------": "",
|
||||||
"setup": "node package-installer.js && node branding-installer.js",
|
"setup": "node package-installer.js && node branding-installer.js",
|
||||||
"native:build-dev": "wasm-pack build ./wasm --dev --target=web --no-default-features --features native && vite build --mode native",
|
"native:build-dev": "wasm-pack build ./wasm --dev --target=web --no-default-features --features native && vite build --mode native",
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
"eslint-plugin-prettier": "^5.5.5",
|
"eslint-plugin-prettier": "^5.5.5",
|
||||||
"eslint-plugin-svelte": "^3.14.0",
|
"eslint-plugin-svelte": "^3.14.0",
|
||||||
"globals": "^17.0.0",
|
"globals": "^17.0.0",
|
||||||
|
"svelte-check": "^4.4.4",
|
||||||
"license-checker-rseidelsohn": "^4.4.2",
|
"license-checker-rseidelsohn": "^4.4.2",
|
||||||
"postcss": "^8.5.6",
|
"postcss": "^8.5.6",
|
||||||
"prettier": "^3.8.0",
|
"prettier": "^3.8.0",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
// Destroy the WASM editor handle
|
// Destroy the Wasm editor handle
|
||||||
editor?.handle.free();
|
editor?.handle.free();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+12
-12
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Svelte components: `components/`
|
## Svelte components: `components/`
|
||||||
|
|
||||||
Svelte components that build the Graphite editor GUI, which are mounted in `App.svelte`. These each contain a Svelte-templated HTML section, an SCSS (Stylus CSS) section, and a script section. The aim is to avoid implementing much editor business logic here, just enough to make things interactive and communicate to the backend where the real business logic should occur.
|
Svelte components that build the Graphite editor GUI. These each contain a TypeScript section, a Svelte-templated HTML template section, and an SCSS stylesheet section. The aim is to avoid implementing much editor business logic here, just enough to make things interactive and communicate to the backend where the real business logic should occur.
|
||||||
|
|
||||||
## I/O managers: `io-managers/`
|
## I/O managers: `io-managers/`
|
||||||
|
|
||||||
@@ -18,27 +18,23 @@ TypeScript files which provide reactive state and importable functions to Svelte
|
|||||||
|
|
||||||
In `Editor.svelte`, an instance of each of these are given to Svelte's `setContext()` function. This allows any component to access the state provider instance using `const exampleStateProvider = getContext<ExampleStateProvider>("exampleStateProvider");`.
|
In `Editor.svelte`, an instance of each of these are given to Svelte's `setContext()` function. This allows any component to access the state provider instance using `const exampleStateProvider = getContext<ExampleStateProvider>("exampleStateProvider");`.
|
||||||
|
|
||||||
## _I/O managers vs. state providers_
|
## *I/O managers vs. state providers*
|
||||||
|
|
||||||
_Some state providers, similarly to I/O managers, may subscribe to backend events, call functions from `editor_api.rs` into the backend, and interact with browser APIs and user input. The difference is that state providers are meant to be made available to components via `getContext()` to use them for reactive state, while I/O managers are meant to be self-contained systems that operate for the lifetime of the application and aren't touched by Svelte components._
|
*Some state providers, similarly to I/O managers, may subscribe to backend events, call functions from `editor_api.rs` into the backend, and interact with browser APIs and user input. The difference is that state providers are meant to be made available to components via `getContext()` to use them for reactive state, while I/O managers are meant to be self-contained systems that operate for the lifetime of the application and aren't touched by Svelte components.*
|
||||||
|
|
||||||
## Utility functions: `utility-functions/`
|
## Utility functions: `utility-functions/`
|
||||||
|
|
||||||
TypeScript files which define and `export` individual helper functions for use elsewhere in the codebase. These files should not persist state outside each function.
|
TypeScript files which define and `export` individual helper functions for use elsewhere in the codebase. These files should not persist state outside each function.
|
||||||
|
|
||||||
## WASM editor: `editor.ts`
|
## Wasm editor: `editor.ts`
|
||||||
|
|
||||||
Instantiates the WASM and editor backend instances. The function `initWasm()` asynchronously constructs and initializes an instance of the WASM bindings JS module provided by wasm-bindgen/wasm-pack. The function `createEditor()` constructs an instance of the editor backend. In theory there could be multiple editor instances sharing the same WASM module instance. The function returns an object where `raw` is the WASM module, `instance` is the editor, and `subscriptions` is the subscription router (described below).
|
Instantiates the Wasm and editor backend instances. The function `initWasm()` asynchronously constructs and initializes an instance of the Wasm bindings JS module provided by wasm-bindgen/wasm-pack. The function `createEditor()` constructs an instance of the editor backend. In theory there could be multiple editor instances sharing the same Wasm module instance. The function returns an object where `raw` is the Wasm memory, `handle` provides access to callable backend functions, and `subscriptions` is the subscription router (described below).
|
||||||
|
|
||||||
`initWasm()` occurs in `main.ts` right before the Svelte application exists, then `createEditor()` is run in `Editor.svelte` during the Svelte app's creation. Similarly to the state providers described above, the editor is given via `setContext()` so other components can get it via `getContext` and call functions on `editor.raw`, `editor.handle`, or `editor.subscriptions`.
|
`initWasm()` occurs in `main.ts` right before the Svelte application is mounted, then `createEditor()` is run in `Editor.svelte` during the Svelte app's creation. Similarly to the state providers described above, the editor is given via `setContext()` so other components can get it via `getContext` and call functions on `editor.handle` or `editor.subscriptions`.
|
||||||
|
|
||||||
## Message definitions: `messages.ts`
|
|
||||||
|
|
||||||
Defines the message formats and data types received from the backend. Since Rust and JS support different styles of data representation, this bridges the gap from Rust into JS land. Messages (and the data contained within) are serialized in Rust by `serde` into JSON, and these definitions are manually kept up-to-date to parallel the message structs and their data types. (However, directives like `#[serde(skip)]` or `#[serde(rename = "someOtherName")]` may cause the TypeScript format to look slightly different from the Rust structs.) These definitions are basically just for the sake of TypeScript to understand the format, although in some cases we may perform data conversion here using translation functions that we can provide.
|
|
||||||
|
|
||||||
## Subscription router: `subscription-router.ts`
|
## Subscription router: `subscription-router.ts`
|
||||||
|
|
||||||
Associates messages from the backend with subscribers in the frontend, and routes messages to subscriber callbacks. This module provides a `subscribeFrontendMessage(messageType, callback)` function which JS code throughout the frontend can call to be registered as the exclusive handler for a chosen message type. This file's other exported function, `handleFrontendMessage(messageType, messageData, wasm, instance)`, is called in `editor.ts` by the associated editor instance when the backend sends a `FrontendMessage`. When this occurs, the subscription router delivers the message to the subscriber for given `messageType` by executing its registered `callback` function. As an argument to the function, it provides the `messageData` payload transformed into its TypeScript-friendly format defined in `messages.ts`.
|
Associates messages from the backend with subscribers in the frontend, and routes messages to subscriber callbacks. This module provides a `subscribeFrontendMessage(messageType, callback)` function which JS code throughout the frontend can call to be registered as the exclusive handler for a chosen message type. The router's other function, `handleFrontendMessage(messageType, messageData)`, is called via the callback passed to `EditorHandle.create()` in `editor.ts` when the backend sends a `FrontendMessage`. When this occurs, the subscription router delivers the message to the subscriber by executing its registered `callback` function.
|
||||||
|
|
||||||
## Svelte app entry point: `App.svelte`
|
## Svelte app entry point: `App.svelte`
|
||||||
|
|
||||||
@@ -48,6 +44,10 @@ The entry point for the Svelte application.
|
|||||||
|
|
||||||
This is where we define global CSS style rules, create/destroy the editor instance, construct/destruct the I/O managers, and construct and `setContext()` the state providers.
|
This is where we define global CSS style rules, create/destroy the editor instance, construct/destruct the I/O managers, and construct and `setContext()` the state providers.
|
||||||
|
|
||||||
|
## Global type augmentations: `global.d.ts`
|
||||||
|
|
||||||
|
Extends built-in browser type definitions using TypeScript's interface merging. This includes Graphite's custom properties on the `window` object, custom events like `pointerlockmove`, and experimental browser APIs not yet in TypeScript's standard library. New custom events or non-standard browser APIs used by the frontend should be declared here.
|
||||||
|
|
||||||
## JS bundle entry point: `main.ts`
|
## JS bundle entry point: `main.ts`
|
||||||
|
|
||||||
The entry point for the entire project's code bundle. Here we simply initialize the Svelte application with `export default new App({ target: document.body });`.
|
The entry point for the entire project's code bundle. Here we simply mount the Svelte application with `export default mount(App, { target: document.body });`.
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
import MainWindow from "@graphite/components/window/MainWindow.svelte";
|
import MainWindow from "@graphite/components/window/MainWindow.svelte";
|
||||||
|
|
||||||
// Graphite WASM editor
|
// Graphite Wasm editor
|
||||||
export let editor: Editor;
|
export let editor: Editor;
|
||||||
setContext("editor", editor);
|
setContext("editor", editor);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Overview of `/frontend/src/components/`
|
# Overview of `/frontend/src/components/`
|
||||||
|
|
||||||
Each component represents a (usually reusable) part of the Graphite editor GUI. These all get mounted in `Editor.svelte` (in the `/src` directory above this one).
|
Each component represents a (usually reusable) part of the Graphite editor GUI.
|
||||||
|
|
||||||
## Floating Menus: `floating-menus/`
|
## Floating Menus: `floating-menus/`
|
||||||
|
|
||||||
@@ -12,7 +12,11 @@ Useful containers that control the flow of content held within.
|
|||||||
|
|
||||||
## Panels: `panels/`
|
## Panels: `panels/`
|
||||||
|
|
||||||
The dockable tabbed regions like the Document, Properties, Layers, and Node Graph panels.
|
The dockable tabbed regions like the Document, Properties, Layers, Data, and Welcome panels.
|
||||||
|
|
||||||
|
## Views: `views/`
|
||||||
|
|
||||||
|
Content views rendered within panels, such as the node graph.
|
||||||
|
|
||||||
## Widgets: `widgets/`
|
## Widgets: `widgets/`
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, onDestroy, createEventDispatcher, tick } from "svelte";
|
import { getContext, onDestroy, createEventDispatcher, tick } from "svelte";
|
||||||
|
|
||||||
import type { FillChoice, MenuDirection } from "@graphite/messages";
|
import { isPlatformNative } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Color } from "@graphite/messages";
|
import type { FillChoice, MenuDirection, Color } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { TooltipState } from "@graphite/state-providers/tooltip";
|
import type { TooltipState } from "@graphite/state-providers/tooltip";
|
||||||
import {
|
import {
|
||||||
contrastingOutlineFactor,
|
contrastingOutlineFactor,
|
||||||
isColor,
|
fillChoiceColor,
|
||||||
isGradient,
|
fillChoiceGradientStops,
|
||||||
createColor,
|
createColor,
|
||||||
createNoneColor,
|
|
||||||
createColorFromHSVA,
|
createColorFromHSVA,
|
||||||
colorFromCSS,
|
colorFromCSS,
|
||||||
colorToRgb255,
|
colorToRgb255,
|
||||||
@@ -24,10 +23,8 @@
|
|||||||
} from "@graphite/utility-functions/colors";
|
} from "@graphite/utility-functions/colors";
|
||||||
import type { HSV, RGB } from "@graphite/utility-functions/colors";
|
import type { HSV, RGB } from "@graphite/utility-functions/colors";
|
||||||
import { clamp } from "@graphite/utility-functions/math";
|
import { clamp } from "@graphite/utility-functions/math";
|
||||||
import { isDesktop } from "@graphite/utility-functions/platform";
|
|
||||||
|
|
||||||
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
|
import FloatingMenu, { preventEscapeClosingParentFloatingMenu } from "@graphite/components/layout/FloatingMenu.svelte";
|
||||||
import { preventEscapeClosingParentFloatingMenu } from "@graphite/components/layout/FloatingMenu.svelte";
|
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte";
|
||||||
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
|
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
|
||||||
@@ -37,20 +34,20 @@
|
|||||||
import Separator from "@graphite/components/widgets/labels/Separator.svelte";
|
import Separator from "@graphite/components/widgets/labels/Separator.svelte";
|
||||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||||
|
|
||||||
type PresetColors = "none" | "black" | "white" | "red" | "yellow" | "green" | "cyan" | "blue" | "magenta";
|
type PresetColors = "None" | "Black" | "White" | "Red" | "Yellow" | "Green" | "Cyan" | "Blue" | "Magenta";
|
||||||
|
|
||||||
const PURE_COLORS: Record<PresetColors, [number, number, number]> = {
|
const PURE_COLORS: Record<PresetColors, [number, number, number]> = {
|
||||||
none: [0, 0, 0],
|
None: [0, 0, 0],
|
||||||
black: [0, 0, 0],
|
Black: [0, 0, 0],
|
||||||
white: [1, 1, 1],
|
White: [1, 1, 1],
|
||||||
red: [1, 0, 0],
|
Red: [1, 0, 0],
|
||||||
yellow: [1, 1, 0],
|
Yellow: [1, 1, 0],
|
||||||
green: [0, 1, 0],
|
Green: [0, 1, 0],
|
||||||
cyan: [0, 1, 1],
|
Cyan: [0, 1, 1],
|
||||||
blue: [0, 0, 1],
|
Blue: [0, 0, 1],
|
||||||
magenta: [1, 0, 1],
|
Magenta: [1, 0, 1],
|
||||||
};
|
};
|
||||||
const PURE_COLORS_GRAYABLE = [
|
const PURE_COLORS_GRAYABLE: [PresetColors, string, string][] = [
|
||||||
["Red", "#ff0000", "#4c4c4c"],
|
["Red", "#ff0000", "#4c4c4c"],
|
||||||
["Yellow", "#ffff00", "#e3e3e3"],
|
["Yellow", "#ffff00", "#e3e3e3"],
|
||||||
["Green", "#00ff00", "#969696"],
|
["Green", "#00ff00", "#969696"],
|
||||||
@@ -70,17 +67,19 @@
|
|||||||
// TODO: See if this should be made to follow the pattern of DropdownInput.svelte so this could be removed
|
// TODO: See if this should be made to follow the pattern of DropdownInput.svelte so this could be removed
|
||||||
export let open: boolean;
|
export let open: boolean;
|
||||||
|
|
||||||
const colorForHSVA = isColor(colorOrGradient) ? colorOrGradient : gradientFirstColor(colorOrGradient);
|
const initSolidColor = fillChoiceColor(colorOrGradient);
|
||||||
|
const initGradientStops = fillChoiceGradientStops(colorOrGradient);
|
||||||
|
const colorForHSVA = initSolidColor || (initGradientStops ? gradientFirstColor(initGradientStops) : undefined);
|
||||||
const hsvOrNone = colorForHSVA ? colorToHSV(colorForHSVA) : undefined;
|
const hsvOrNone = colorForHSVA ? colorToHSV(colorForHSVA) : undefined;
|
||||||
const hsv = hsvOrNone || { h: 0, s: 0, v: 0 };
|
const hsv = hsvOrNone || { h: 0, s: 0, v: 0 };
|
||||||
|
|
||||||
// Gradient color stops
|
// Gradient color stops
|
||||||
$: gradient = isGradient(colorOrGradient) ? colorOrGradient : undefined;
|
$: gradient = fillChoiceGradientStops(colorOrGradient);
|
||||||
let activeIndex = 0 as number | undefined;
|
let activeIndex: number | undefined = 0;
|
||||||
let activeIndexIsMidpoint = false;
|
let activeIndexIsMidpoint = false;
|
||||||
$: selectedGradientColor = (activeIndex !== undefined && gradient?.color[activeIndex]) || (colorFromCSS("black") as Color);
|
$: selectedGradientColor = (activeIndex !== undefined && gradient?.color[activeIndex]) || colorFromCSS("black") || createColor(0, 0, 0, 1);
|
||||||
// Currently viewed color
|
// Currently viewed color
|
||||||
$: color = isColor(colorOrGradient) ? colorOrGradient : selectedGradientColor;
|
$: color = fillChoiceColor(colorOrGradient) || selectedGradientColor;
|
||||||
// New color components
|
// New color components
|
||||||
let hue = hsv.h;
|
let hue = hsv.h;
|
||||||
let saturation = hsv.s;
|
let saturation = hsv.s;
|
||||||
@@ -115,14 +114,30 @@
|
|||||||
$: watchOpen(open);
|
$: watchOpen(open);
|
||||||
$: watchColor(color);
|
$: watchColor(color);
|
||||||
|
|
||||||
$: oldColor = oldIsNone ? createNoneColor() : createColorFromHSVA(oldHue, oldSaturation, oldValue, oldAlpha);
|
$: oldColor = oldIsNone ? undefined : createColorFromHSVA(oldHue, oldSaturation, oldValue, oldAlpha);
|
||||||
$: newColor = isNone ? createNoneColor() : createColorFromHSVA(hue, saturation, value, alpha);
|
$: newColor = isNone ? undefined : createColorFromHSVA(hue, saturation, value, alpha);
|
||||||
$: rgbChannels = Object.entries(colorToRgb255(newColor) || { r: undefined, g: undefined, b: undefined }) as [keyof RGB, number | undefined][];
|
$: rgbChannels = ((): [keyof RGB, number | undefined][] => {
|
||||||
$: hsvChannels = Object.entries(!isNone ? { h: hue * 360, s: saturation * 100, v: value * 100 } : { h: undefined, s: undefined, v: undefined }) as [keyof HSV, number | undefined][];
|
const rgb = newColor ? colorToRgb255(newColor) : undefined;
|
||||||
|
return [
|
||||||
|
["r", rgb?.r],
|
||||||
|
["g", rgb?.g],
|
||||||
|
["b", rgb?.b],
|
||||||
|
];
|
||||||
|
})();
|
||||||
|
$: hsvChannels = ((): [keyof HSV, number | undefined][] => {
|
||||||
|
return [
|
||||||
|
["h", isNone ? undefined : hue * 360],
|
||||||
|
["s", isNone ? undefined : saturation * 100],
|
||||||
|
["v", isNone ? undefined : value * 100],
|
||||||
|
];
|
||||||
|
})();
|
||||||
$: opaqueHueColor = createColorFromHSVA(hue, 1, 1, 1);
|
$: opaqueHueColor = createColorFromHSVA(hue, 1, 1, 1);
|
||||||
$: outlineFactor = Math.max(contrastingOutlineFactor(newColor, "--color-2-mildblack", 0.01), contrastingOutlineFactor(oldColor, "--color-2-mildblack", 0.01));
|
$: outlineFactor = Math.max(
|
||||||
|
contrastingOutlineFactor(newColor ? { Solid: newColor } : ("None" as const), "--color-2-mildblack", 0.01),
|
||||||
|
contrastingOutlineFactor(oldColor ? { Solid: oldColor } : ("None" as const), "--color-2-mildblack", 0.01),
|
||||||
|
);
|
||||||
$: outlined = outlineFactor > 0.0001;
|
$: outlined = outlineFactor > 0.0001;
|
||||||
$: transparency = newColor.alpha < 1 || oldColor.alpha < 1;
|
$: transparency = (newColor?.alpha ?? 1) < 1 || (oldColor?.alpha ?? 1) < 1;
|
||||||
|
|
||||||
async function watchOpen(open: boolean) {
|
async function watchOpen(open: boolean) {
|
||||||
if (open) {
|
if (open) {
|
||||||
@@ -136,11 +151,6 @@
|
|||||||
function watchColor(color: Color) {
|
function watchColor(color: Color) {
|
||||||
const hsv = colorToHSV(color);
|
const hsv = colorToHSV(color);
|
||||||
|
|
||||||
if (hsv === undefined) {
|
|
||||||
setNewHSVA(0, 0, 0, 1, true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the hue, but only if it is necessary so we don't:
|
// Update the hue, but only if it is necessary so we don't:
|
||||||
// - ...jump the user's hue from 360° (top) to the equivalent 0° (bottom)
|
// - ...jump the user's hue from 360° (top) to the equivalent 0° (bottom)
|
||||||
// - ...reset the hue to 0° if the color is fully desaturated, where all hues are equivalent
|
// - ...reset the hue to 0° if the color is fully desaturated, where all hues are equivalent
|
||||||
@@ -160,7 +170,7 @@
|
|||||||
function onPointerDown(e: PointerEvent) {
|
function onPointerDown(e: PointerEvent) {
|
||||||
if (disabled) return;
|
if (disabled) return;
|
||||||
|
|
||||||
const target = (e.target || undefined) as HTMLElement | undefined;
|
const target = e.target instanceof HTMLElement ? e.target : undefined;
|
||||||
draggingPickerTrack = target?.closest("[data-saturation-value-picker], [data-hue-picker], [data-alpha-picker]") || undefined;
|
draggingPickerTrack = target?.closest("[data-saturation-value-picker], [data-hue-picker], [data-alpha-picker]") || undefined;
|
||||||
|
|
||||||
hueBeforeDrag = hue;
|
hueBeforeDrag = hue;
|
||||||
@@ -301,14 +311,20 @@
|
|||||||
setColor(color);
|
setColor(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setColor(color?: Color) {
|
function setColor(color?: Color | "None") {
|
||||||
const colorToEmit = color || createColorFromHSVA(hue, saturation, value, alpha);
|
if (color === "None") {
|
||||||
|
dispatch("colorOrGradient", "None");
|
||||||
if (gradientSpectrumInputWidget && activeIndex !== undefined && gradient?.position[activeIndex] !== undefined && isGradient(colorOrGradient)) {
|
return;
|
||||||
colorOrGradient.color[activeIndex] = colorToEmit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch("colorOrGradient", gradient || colorToEmit);
|
const colorToEmit = color || createColorFromHSVA(hue, saturation, value, alpha);
|
||||||
|
|
||||||
|
if (gradientSpectrumInputWidget && activeIndex !== undefined && gradient && gradient.position[activeIndex] !== undefined) {
|
||||||
|
const gradientStops = fillChoiceGradientStops(colorOrGradient);
|
||||||
|
if (gradientStops) gradientStops.color[activeIndex] = colorToEmit;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch("colorOrGradient", gradient ? { Gradient: gradient } : { Solid: colorToEmit });
|
||||||
}
|
}
|
||||||
|
|
||||||
function swapNewWithOld() {
|
function swapNewWithOld() {
|
||||||
@@ -323,7 +339,7 @@
|
|||||||
setNewHSVA(oldHue, oldSaturation, oldValue, oldAlpha, oldIsNone);
|
setNewHSVA(oldHue, oldSaturation, oldValue, oldAlpha, oldIsNone);
|
||||||
setOldHSVA(tempHue, tempSaturation, tempValue, tempAlpha, tempIsNone);
|
setOldHSVA(tempHue, tempSaturation, tempValue, tempAlpha, tempIsNone);
|
||||||
|
|
||||||
setColor(old);
|
setColor(old || "None");
|
||||||
}
|
}
|
||||||
|
|
||||||
function setColorCode(colorCode: string) {
|
function setColorCode(colorCode: string) {
|
||||||
@@ -333,7 +349,7 @@
|
|||||||
|
|
||||||
function setColorRGB(channel: keyof RGB, strength: number | undefined) {
|
function setColorRGB(channel: keyof RGB, strength: number | undefined) {
|
||||||
// Do nothing if the given value is undefined
|
// Do nothing if the given value is undefined
|
||||||
if (strength === undefined) return undefined;
|
if (strength === undefined || !newColor) return undefined;
|
||||||
// Set the specified channel to the given value
|
// Set the specified channel to the given value
|
||||||
else if (channel === "r") setColor(createColor(strength / 255, newColor.green, newColor.blue, newColor.alpha));
|
else if (channel === "r") setColor(createColor(strength / 255, newColor.green, newColor.blue, newColor.alpha));
|
||||||
else if (channel === "g") setColor(createColor(newColor.red, strength / 255, newColor.blue, newColor.alpha));
|
else if (channel === "g") setColor(createColor(newColor.red, strength / 255, newColor.blue, newColor.alpha));
|
||||||
@@ -356,19 +372,12 @@
|
|||||||
setColor();
|
setColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setColorPresetSubtile(e: MouseEvent) {
|
|
||||||
const clickedTile = e.target as HTMLDivElement | undefined;
|
|
||||||
const tileColor = clickedTile?.getAttribute("data-pure-tile") || undefined;
|
|
||||||
|
|
||||||
if (tileColor) setColorPreset(tileColor as PresetColors);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setColorPreset(preset: PresetColors) {
|
function setColorPreset(preset: PresetColors) {
|
||||||
dispatch("startHistoryTransaction");
|
dispatch("startHistoryTransaction");
|
||||||
|
|
||||||
if (preset === "none") {
|
if (preset === "None") {
|
||||||
setNewHSVA(0, 0, 0, 1, true);
|
setNewHSVA(0, 0, 0, 1, true);
|
||||||
setColor(createNoneColor());
|
setColor("None");
|
||||||
} else {
|
} else {
|
||||||
const presetColor = createColor(...PURE_COLORS[preset], 1);
|
const presetColor = createColor(...PURE_COLORS[preset], 1);
|
||||||
const hsv = colorToHSV(presetColor);
|
const hsv = colorToHSV(presetColor);
|
||||||
@@ -398,18 +407,16 @@
|
|||||||
// TODO: Replace this temporary usage of the browser eyedropper API, that only works in Chromium-based browsers, with the custom color sampler system used by the Eyedropper tool
|
// TODO: Replace this temporary usage of the browser eyedropper API, that only works in Chromium-based browsers, with the custom color sampler system used by the Eyedropper tool
|
||||||
function eyedropperSupported(): boolean {
|
function eyedropperSupported(): boolean {
|
||||||
// TODO: Implement support in the desktop app for OS-level color picking
|
// TODO: Implement support in the desktop app for OS-level color picking
|
||||||
if (isDesktop()) return false;
|
if (isPlatformNative()) return false;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
return window.EyeDropper !== undefined;
|
||||||
return Boolean((window as any).EyeDropper);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function activateEyedropperSample() {
|
async function activateEyedropperSample() {
|
||||||
if (!eyedropperSupported()) return;
|
if (!eyedropperSupported()) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
const result = await new EyeDropper().open();
|
||||||
const result = await new (window as any).EyeDropper().open();
|
|
||||||
dispatch("startHistoryTransaction");
|
dispatch("startHistoryTransaction");
|
||||||
setColorCode(result.sRGBHex);
|
setColorCode(result.sRGBHex);
|
||||||
} catch {
|
} catch {
|
||||||
@@ -427,8 +434,8 @@
|
|||||||
|
|
||||||
setColor(color);
|
setColor(color);
|
||||||
|
|
||||||
setNewHSVA(hsv.h, hsv.s, hsv.v, color.alpha, color.none);
|
setNewHSVA(hsv.h, hsv.s, hsv.v, color.alpha, false);
|
||||||
setOldHSVA(hsv.h, hsv.s, hsv.v, color.alpha, color.none);
|
setOldHSVA(hsv.h, hsv.s, hsv.v, color.alpha, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function div(): HTMLDivElement | undefined {
|
export function div(): HTMLDivElement | undefined {
|
||||||
@@ -443,14 +450,14 @@
|
|||||||
<FloatingMenu class="color-picker" classes={{ disabled }} {open} on:open {strayCloses} escapeCloses={strayCloses && !gradientSpectrumDragging} {direction} type="Popover" bind:this={self}>
|
<FloatingMenu class="color-picker" classes={{ disabled }} {open} on:open {strayCloses} escapeCloses={strayCloses && !gradientSpectrumDragging} {direction} type="Popover" bind:this={self}>
|
||||||
<LayoutRow
|
<LayoutRow
|
||||||
styles={{
|
styles={{
|
||||||
"--new-color": colorToHexOptionalAlpha(newColor),
|
"--new-color": newColor ? colorToHexOptionalAlpha(newColor) : undefined,
|
||||||
"--new-color-contrasting": colorContrastingColor(newColor),
|
"--new-color-contrasting": colorContrastingColor(newColor),
|
||||||
"--old-color": colorToHexOptionalAlpha(oldColor),
|
"--old-color": oldColor ? colorToHexOptionalAlpha(oldColor) : undefined,
|
||||||
"--old-color-contrasting": colorContrastingColor(oldColor),
|
"--old-color-contrasting": colorContrastingColor(oldColor),
|
||||||
"--hue-color": colorToRgbCSS(opaqueHueColor),
|
"--hue-color": colorToRgbCSS(opaqueHueColor),
|
||||||
"--hue-color-contrasting": colorContrastingColor(opaqueHueColor),
|
"--hue-color-contrasting": colorContrastingColor(opaqueHueColor),
|
||||||
"--opaque-color": colorToHexNoAlpha(colorOpaque(newColor) || createColor(0, 0, 0, 1)),
|
"--opaque-color": colorToHexNoAlpha(newColor ? colorOpaque(newColor) : createColor(0, 0, 0, 1)),
|
||||||
"--opaque-color-contrasting": colorContrastingColor(colorOpaque(newColor) || createColor(0, 0, 0, 1)),
|
"--opaque-color-contrasting": colorContrastingColor(newColor ? colorOpaque(newColor) : createColor(0, 0, 0, 1)),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{@const hueDescription = "The shade along the spectrum of the rainbow."}
|
{@const hueDescription = "The shade along the spectrum of the rainbow."}
|
||||||
@@ -514,7 +521,7 @@
|
|||||||
<SpectrumInput
|
<SpectrumInput
|
||||||
{gradient}
|
{gradient}
|
||||||
{disabled}
|
{disabled}
|
||||||
on:gradient={() => dispatch("colorOrGradient", gradient)}
|
on:gradient={() => dispatch("colorOrGradient", gradient ? { Gradient: gradient } : "None")}
|
||||||
on:activeMarkerIndexChange={gradientActiveMarkerIndexChange}
|
on:activeMarkerIndexChange={gradientActiveMarkerIndexChange}
|
||||||
activeMarkerIndex={activeIndex}
|
activeMarkerIndex={activeIndex}
|
||||||
activeMarkerIsMidpoint={activeIndexIsMidpoint}
|
activeMarkerIsMidpoint={activeIndexIsMidpoint}
|
||||||
@@ -568,7 +575,7 @@
|
|||||||
<Separator style="Related" />
|
<Separator style="Related" />
|
||||||
<LayoutRow>
|
<LayoutRow>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={colorToHexOptionalAlpha(newColor) || "-"}
|
value={newColor ? colorToHexOptionalAlpha(newColor) : "-"}
|
||||||
{disabled}
|
{disabled}
|
||||||
on:commitText={({ detail }) => {
|
on:commitText={({ detail }) => {
|
||||||
dispatch("startHistoryTransaction");
|
dispatch("startHistoryTransaction");
|
||||||
@@ -680,7 +687,7 @@
|
|||||||
<button
|
<button
|
||||||
class="preset-color none"
|
class="preset-color none"
|
||||||
{disabled}
|
{disabled}
|
||||||
on:click={() => setColorPreset("none")}
|
on:click={() => setColorPreset("None")}
|
||||||
data-tooltip-label="Set to No Color"
|
data-tooltip-label="Set to No Color"
|
||||||
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
|
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -690,7 +697,7 @@
|
|||||||
<button
|
<button
|
||||||
class="preset-color black"
|
class="preset-color black"
|
||||||
{disabled}
|
{disabled}
|
||||||
on:click={() => setColorPreset("black")}
|
on:click={() => setColorPreset("Black")}
|
||||||
data-tooltip-label="Set to Black"
|
data-tooltip-label="Set to Black"
|
||||||
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
|
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
@@ -699,19 +706,19 @@
|
|||||||
<button
|
<button
|
||||||
class="preset-color white"
|
class="preset-color white"
|
||||||
{disabled}
|
{disabled}
|
||||||
on:click={() => setColorPreset("white")}
|
on:click={() => setColorPreset("White")}
|
||||||
data-tooltip-label="Set to White"
|
data-tooltip-label="Set to White"
|
||||||
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
|
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
></button>
|
></button>
|
||||||
<Separator style="Related" />
|
<Separator style="Related" />
|
||||||
<button class="preset-color pure" {disabled} on:click={setColorPresetSubtile} tabindex="-1">
|
<button class="preset-color pure" {disabled} tabindex="-1">
|
||||||
{#each PURE_COLORS_GRAYABLE as [name, color, gray]}
|
{#each PURE_COLORS_GRAYABLE as [preset, color, gray]}
|
||||||
<div
|
<div
|
||||||
data-pure-tile={name.toLowerCase()}
|
on:click={() => setColorPreset(preset)}
|
||||||
style:--pure-color={color}
|
style:--pure-color={color}
|
||||||
style:--pure-color-gray={gray}
|
style:--pure-color-gray={gray}
|
||||||
data-tooltip-label={`Set to ${name}`}
|
data-tooltip-label={`Set to ${preset}`}
|
||||||
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
|
data-tooltip-description={disabled ? "Disabled (read-only)." : ""}
|
||||||
></div>
|
></div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -20,7 +20,8 @@
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Focus the button which is marked as emphasized, or otherwise the first button, in the popup
|
// Focus the button which is marked as emphasized, or otherwise the first button, in the popup
|
||||||
const emphasizedOrFirstButton = (self?.div?.()?.querySelector("[data-emphasized]") || self?.div?.()?.querySelector("[data-text-button]") || undefined) as HTMLButtonElement | undefined;
|
const button = self?.div?.()?.querySelector("[data-emphasized]") || self?.div?.()?.querySelector("[data-text-button]");
|
||||||
|
const emphasizedOrFirstButton = button instanceof HTMLButtonElement ? button : undefined;
|
||||||
emphasizedOrFirstButton?.focus();
|
emphasizedOrFirstButton?.focus();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@@ -28,8 +29,9 @@
|
|||||||
<!-- TODO: Use https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog for improved accessibility -->
|
<!-- TODO: Use https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog for improved accessibility -->
|
||||||
<FloatingMenu open={true} class="dialog" type="Dialog" direction="Center" bind:this={self} data-dialog>
|
<FloatingMenu open={true} class="dialog" type="Dialog" direction="Center" bind:this={self} data-dialog>
|
||||||
<LayoutRow class="header-area">
|
<LayoutRow class="header-area">
|
||||||
<!-- `$dialog.icon` class exists to provide special sizing in CSS to specific icons -->
|
{#if $dialog.icon}
|
||||||
<IconLabel icon={$dialog.icon} class={$dialog.icon.toLowerCase()} />
|
<IconLabel icon={$dialog.icon} />
|
||||||
|
{/if}
|
||||||
<TextLabel>{$dialog.title}</TextLabel>
|
<TextLabel>{$dialog.title}</TextLabel>
|
||||||
</LayoutRow>
|
</LayoutRow>
|
||||||
<LayoutRow class={`content ${$dialog.title === "Demo Artwork" ? "center" : "" /* TODO: Replace this with a less hacky approach that's compatible with localization/translation */}`}>
|
<LayoutRow class={`content ${$dialog.title === "Demo Artwork" ? "center" : "" /* TODO: Replace this with a less hacky approach that's compatible with localization/translation */}`}>
|
||||||
@@ -104,10 +106,13 @@
|
|||||||
.icon-label {
|
.icon-label {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
|
|
||||||
|
+ .text-label {
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-label {
|
.text-label {
|
||||||
margin-left: 12px;
|
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,7 +139,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.text-label.multiline {
|
.text-label.multiline {
|
||||||
-webkit-user-select: text; // Still required by Safari as of 2025
|
-webkit-user-select: text; // Still required by Safari as of 2026
|
||||||
user-select: text;
|
user-select: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, tick, onDestroy, onMount } from "svelte";
|
import { createEventDispatcher, tick, onDestroy, onMount } from "svelte";
|
||||||
|
|
||||||
import type { MenuListEntry, MenuDirection } from "@graphite/messages";
|
import type { MenuListEntry, MenuDirection } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
|
|
||||||
import MenuList from "@graphite/components/floating-menus/MenuList.svelte";
|
import MenuList from "@graphite/components/floating-menus/MenuList.svelte";
|
||||||
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
|
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
|
||||||
@@ -45,7 +45,7 @@
|
|||||||
let openChildValue: string | undefined = undefined;
|
let openChildValue: string | undefined = undefined;
|
||||||
let search = "";
|
let search = "";
|
||||||
let reactiveEntries = entries;
|
let reactiveEntries = entries;
|
||||||
let highlighted = activeEntry as MenuListEntry | undefined;
|
let highlighted: MenuListEntry | undefined = activeEntry;
|
||||||
let virtualScrollingEntriesStart = 0;
|
let virtualScrollingEntriesStart = 0;
|
||||||
|
|
||||||
// `watchOpen` is called only when `open` is changed from outside this component
|
// `watchOpen` is called only when `open` is changed from outside this component
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
|
|
||||||
function onScroll(e: Event) {
|
function onScroll(e: Event) {
|
||||||
if (!virtualScrollingEntryHeight) return;
|
if (!virtualScrollingEntryHeight) return;
|
||||||
virtualScrollingEntriesStart = (e.target as HTMLElement)?.scrollTop || 0;
|
virtualScrollingEntriesStart = e.target instanceof HTMLElement ? e.target.scrollTop : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getChildReference(menuListEntry: MenuListEntry): MenuList | undefined {
|
function getChildReference(menuListEntry: MenuListEntry): MenuList | undefined {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import { createEventDispatcher, getContext, onMount } from "svelte";
|
import { createEventDispatcher, getContext, onMount } from "svelte";
|
||||||
import { SvelteMap } from "svelte/reactivity";
|
import { SvelteMap } from "svelte/reactivity";
|
||||||
|
|
||||||
import type { FrontendNodeType } from "@graphite/messages";
|
import type { FrontendNodeType } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
|
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
|
||||||
|
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
|
|
||||||
|
import type { LabeledShortcut } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { Editor } from "@graphite/editor";
|
||||||
import type { LabeledShortcut } from "@graphite/messages";
|
|
||||||
import type { TooltipState } from "@graphite/state-providers/tooltip";
|
import type { TooltipState } from "@graphite/state-providers/tooltip";
|
||||||
|
|
||||||
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
|
import FloatingMenu from "@graphite/components/layout/FloatingMenu.svelte";
|
||||||
@@ -21,7 +21,10 @@
|
|||||||
$: shortcut = ((shortcutJSON) => {
|
$: shortcut = ((shortcutJSON) => {
|
||||||
if (!shortcutJSON) return undefined;
|
if (!shortcutJSON) return undefined;
|
||||||
try {
|
try {
|
||||||
return JSON.parse(shortcutJSON) as LabeledShortcut;
|
const parsed: LabeledShortcut = JSON.parse(shortcutJSON);
|
||||||
|
if (!Array.isArray(parsed)) return undefined;
|
||||||
|
|
||||||
|
return parsed;
|
||||||
} catch {
|
} catch {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
<script lang="ts" context="module">
|
<script lang="ts" context="module">
|
||||||
export type MenuType = "Popover" | "Tooltip" | "Dropdown" | "Dialog" | "Cursor";
|
|
||||||
|
|
||||||
/// Prevents the escape key from closing the parent floating menu of the given element.
|
/// Prevents the escape key from closing the parent floating menu of the given element.
|
||||||
/// This works by momentarily setting the `data-escape-does-not-close` attribute on the parent floating menu element.
|
/// This works by momentarily setting the `data-escape-does-not-close` attribute on the parent floating menu element.
|
||||||
/// After checking for the Escape key, it checks (in one `setTimeout`) for the attribute and ignores the key if it's present.
|
/// After checking for the Escape key, it checks (in one `setTimeout`) for the attribute and ignores the key if it's present.
|
||||||
@@ -21,7 +19,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, afterUpdate, createEventDispatcher, tick } from "svelte";
|
import { onMount, afterUpdate, createEventDispatcher, tick } from "svelte";
|
||||||
|
|
||||||
import type { MenuDirection } from "@graphite/messages";
|
import type { MenuDirection } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import { browserVersion } from "@graphite/utility-functions/platform";
|
import { browserVersion } from "@graphite/utility-functions/platform";
|
||||||
|
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
@@ -38,7 +36,7 @@
|
|||||||
export { styleName as style };
|
export { styleName as style };
|
||||||
export let styles: Record<string, string | number | undefined> = {};
|
export let styles: Record<string, string | number | undefined> = {};
|
||||||
export let open: boolean;
|
export let open: boolean;
|
||||||
export let type: MenuType;
|
export let type: "Popover" | "Tooltip" | "Dropdown" | "Dialog" | "Cursor";
|
||||||
export let direction: MenuDirection = "Bottom";
|
export let direction: MenuDirection = "Bottom";
|
||||||
export let windowEdgeMargin = 6;
|
export let windowEdgeMargin = 6;
|
||||||
export let scrollableY = false;
|
export let scrollableY = false;
|
||||||
@@ -309,7 +307,7 @@
|
|||||||
|
|
||||||
function pointerMoveHandler(e: PointerEvent) {
|
function pointerMoveHandler(e: PointerEvent) {
|
||||||
// This element and the element being hovered over
|
// This element and the element being hovered over
|
||||||
const target = e.target as HTMLElement | undefined;
|
const target = e.target instanceof HTMLElement ? e.target : undefined;
|
||||||
|
|
||||||
// Get the spawner element (that which is clicked to spawn this floating menu)
|
// Get the spawner element (that which is clicked to spawn this floating menu)
|
||||||
// Assumes the spawner is a sibling of this FloatingMenu component
|
// Assumes the spawner is a sibling of this FloatingMenu component
|
||||||
@@ -398,9 +396,9 @@
|
|||||||
else {
|
else {
|
||||||
const foundTarget = filteredListOfDescendantSpawners.find((item: Element): boolean => item === targetSpawner);
|
const foundTarget = filteredListOfDescendantSpawners.find((item: Element): boolean => item === targetSpawner);
|
||||||
// If the currently hovered spawner is one of the found valid hover-transferrable spawners, swap to it by clicking on it
|
// If the currently hovered spawner is one of the found valid hover-transferrable spawners, swap to it by clicking on it
|
||||||
if (foundTarget) {
|
if (foundTarget instanceof HTMLElement) {
|
||||||
dispatch("open", false);
|
dispatch("open", false);
|
||||||
(foundTarget as HTMLElement).click();
|
foundTarget.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
// In either case, we are done searching
|
// In either case, we are done searching
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ActionShortcut } from "@graphite/messages";
|
import type { ActionShortcut } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
|
|
||||||
let className = "";
|
let className = "";
|
||||||
export { className as class };
|
export { className as class };
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ActionShortcut } from "@graphite/messages";
|
import type { ActionShortcut } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
|
|
||||||
let className = "";
|
let className = "";
|
||||||
export { className as class };
|
export { className as class };
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, onMount, onDestroy } from "svelte";
|
import { getContext, onMount, onDestroy } from "svelte";
|
||||||
|
|
||||||
|
import type { Layout } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { Editor } from "@graphite/editor";
|
||||||
import type { Layout } from "@graphite/messages";
|
|
||||||
import { patchLayout } from "@graphite/utility-functions/widgets";
|
import { patchLayout } from "@graphite/utility-functions/widgets";
|
||||||
|
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, onMount, onDestroy, tick } from "svelte";
|
import { getContext, onMount, onDestroy, tick } from "svelte";
|
||||||
|
|
||||||
|
import type { Color, MenuDirection, MouseCursorIcon } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { Editor } from "@graphite/editor";
|
||||||
import type { Color, FrontendMessages, MenuDirection } from "@graphite/messages";
|
|
||||||
import type { AppWindowState } from "@graphite/state-providers/app-window";
|
import type { AppWindowState } from "@graphite/state-providers/app-window";
|
||||||
import type { DocumentState } from "@graphite/state-providers/document";
|
import type { DocumentState } from "@graphite/state-providers/document";
|
||||||
import { isColor, createColor } from "@graphite/utility-functions/colors";
|
import type { MessageBody } from "@graphite/subscription-router";
|
||||||
|
import { fillChoiceColor, createColor } from "@graphite/utility-functions/colors";
|
||||||
import { pasteFile } from "@graphite/utility-functions/files";
|
import { pasteFile } from "@graphite/utility-functions/files";
|
||||||
import { textInputCleanup } from "@graphite/utility-functions/keyboard-entry";
|
import { textInputCleanup } from "@graphite/utility-functions/keyboard-entry";
|
||||||
import { rasterizeSVGCanvas } from "@graphite/utility-functions/rasterization";
|
import { rasterizeSVGCanvas } from "@graphite/utility-functions/rasterization";
|
||||||
import { setupViewportResizeObserver, cleanupViewportResizeObserver } from "@graphite/utility-functions/viewports";
|
import { setupViewportResizeObserver, cleanupViewportResizeObserver } from "@graphite/utility-functions/viewports";
|
||||||
import { isWidgetSpanRow } from "@graphite/utility-functions/widgets";
|
|
||||||
|
|
||||||
import ColorPicker from "@graphite/components/floating-menus/ColorPicker.svelte";
|
import ColorPicker from "@graphite/components/floating-menus/ColorPicker.svelte";
|
||||||
import EyedropperPreview, { ZOOM_WINDOW_DIMENSIONS } from "@graphite/components/floating-menus/EyedropperPreview.svelte";
|
import EyedropperPreview, { ZOOM_WINDOW_DIMENSIONS } from "@graphite/components/floating-menus/EyedropperPreview.svelte";
|
||||||
@@ -21,8 +21,6 @@
|
|||||||
import ScrollbarInput from "@graphite/components/widgets/inputs/ScrollbarInput.svelte";
|
import ScrollbarInput from "@graphite/components/widgets/inputs/ScrollbarInput.svelte";
|
||||||
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
import WidgetLayout from "@graphite/components/widgets/WidgetLayout.svelte";
|
||||||
|
|
||||||
type DisplayEditableTextbox = FrontendMessages["DisplayEditableTextbox"];
|
|
||||||
|
|
||||||
let rulerHorizontal: RulerInput | undefined;
|
let rulerHorizontal: RulerInput | undefined;
|
||||||
let rulerVertical: RulerInput | undefined;
|
let rulerVertical: RulerInput | undefined;
|
||||||
let viewport: HTMLDivElement | undefined;
|
let viewport: HTMLDivElement | undefined;
|
||||||
@@ -35,7 +33,7 @@
|
|||||||
// Interactive text editing
|
// Interactive text editing
|
||||||
let textInput: undefined | HTMLDivElement = undefined;
|
let textInput: undefined | HTMLDivElement = undefined;
|
||||||
let showTextInput: boolean;
|
let showTextInput: boolean;
|
||||||
let textInputMatrix: number[];
|
let textInputMatrix: [number, number, number, number, number, number];
|
||||||
|
|
||||||
// Scrollbars
|
// Scrollbars
|
||||||
let scrollbarPos = { x: 0.5, y: 0.5 };
|
let scrollbarPos = { x: 0.5, y: 0.5 };
|
||||||
@@ -93,7 +91,7 @@
|
|||||||
$: canvasHeightScaledRoundedToEven = canvasHeightScaled && (canvasHeightScaled % 2 === 1 ? canvasHeightScaled + 1 : canvasHeightScaled);
|
$: canvasHeightScaledRoundedToEven = canvasHeightScaled && (canvasHeightScaled % 2 === 1 ? canvasHeightScaled + 1 : canvasHeightScaled);
|
||||||
|
|
||||||
$: toolShelfTotalToolsAndSeparators = ((layoutGroup) => {
|
$: toolShelfTotalToolsAndSeparators = ((layoutGroup) => {
|
||||||
if (!isWidgetSpanRow(layoutGroup)) return undefined;
|
if (!layoutGroup || !("Row" in layoutGroup)) return undefined;
|
||||||
|
|
||||||
let totalSeparators = 0;
|
let totalSeparators = 0;
|
||||||
let totalToolRowsFor1Columns = 0;
|
let totalToolRowsFor1Columns = 0;
|
||||||
@@ -108,8 +106,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
let toolsInCurrentGroup = 0;
|
let toolsInCurrentGroup = 0;
|
||||||
layoutGroup.rowWidgets.forEach((widget) => {
|
layoutGroup.Row.rowWidgets.forEach((widget) => {
|
||||||
if (widget.props.kind === "Separator") {
|
if ("Separator" in widget.widget) {
|
||||||
totalSeparators += 1;
|
totalSeparators += 1;
|
||||||
tally();
|
tally();
|
||||||
} else {
|
} else {
|
||||||
@@ -176,8 +174,7 @@
|
|||||||
const canvasName = placeholder.getAttribute("data-canvas-placeholder");
|
const canvasName = placeholder.getAttribute("data-canvas-placeholder");
|
||||||
if (!canvasName) return;
|
if (!canvasName) return;
|
||||||
// Get the canvas element from the global storage
|
// Get the canvas element from the global storage
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
let canvas = window.imageCanvases[canvasName];
|
||||||
let canvas = (window as any).imageCanvases[canvasName];
|
|
||||||
|
|
||||||
// Get logical dimensions from foreignObject parent (set by backend)
|
// Get logical dimensions from foreignObject parent (set by backend)
|
||||||
const foreignObject = placeholder.parentElement;
|
const foreignObject = placeholder.parentElement;
|
||||||
@@ -295,10 +292,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update mouse cursor icon
|
// Update mouse cursor icon
|
||||||
export function updateMouseCursor(cursor: string) {
|
export function updateMouseCursor(cursor: MouseCursorIcon) {
|
||||||
const mouseCursorIconCSSNames: Record<string, string> = {
|
const mouseCursorIconCSSNames: Record<MouseCursorIcon, string> = {
|
||||||
Default: "default",
|
Default: "default",
|
||||||
Alias: "alias",
|
|
||||||
None: "none",
|
None: "none",
|
||||||
ZoomIn: "zoom-in",
|
ZoomIn: "zoom-in",
|
||||||
ZoomOut: "zoom-out",
|
ZoomOut: "zoom-out",
|
||||||
@@ -312,7 +308,7 @@
|
|||||||
NWSEResize: "nwse-resize",
|
NWSEResize: "nwse-resize",
|
||||||
Rotate: "custom-rotate",
|
Rotate: "custom-rotate",
|
||||||
};
|
};
|
||||||
let cursorString = mouseCursorIconCSSNames[cursor] || mouseCursorIconCSSNames["Alias"];
|
let cursorString = mouseCursorIconCSSNames[cursor] || "alias";
|
||||||
|
|
||||||
// This isn't very clean but it's good enough for now until we need more icons, then we can build something more robust (consider blob URLs)
|
// This isn't very clean but it's good enough for now until we need more icons, then we can build something more robust (consider blob URLs)
|
||||||
if (cursor === "Rotate") {
|
if (cursor === "Rotate") {
|
||||||
@@ -345,7 +341,7 @@
|
|||||||
editor.handle.onChangeText(textCleaned, false);
|
editor.handle.onChangeText(textCleaned, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function displayEditableTextbox(data: DisplayEditableTextbox) {
|
export async function displayEditableTextbox(data: MessageBody<"DisplayEditableTextbox">) {
|
||||||
showTextInput = true;
|
showTextInput = true;
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
@@ -377,9 +373,9 @@
|
|||||||
|
|
||||||
textInputMatrix = data.transform;
|
textInputMatrix = data.transform;
|
||||||
|
|
||||||
const bytes = new Uint8Array(data.fontData);
|
if (data.fontData.length > 0 && data.fontData.buffer instanceof ArrayBuffer) {
|
||||||
if (bytes.length > 0) {
|
const fontView = new Uint8Array(data.fontData.buffer, data.fontData.byteOffset, data.fontData.byteLength);
|
||||||
window.document.fonts.add(new FontFace("text-font", bytes));
|
window.document.fonts.add(new FontFace("text-font", fontView));
|
||||||
textInput.style.fontFamily = "text-font";
|
textInput.style.fontFamily = "text-font";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -423,7 +419,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function gradientStopPickerDirection(position: { x: number; y: number } | undefined, viewport: HTMLDivElement | undefined): MenuDirection {
|
function gradientStopPickerDirection(position: { x: number; y: number } | undefined, viewport: HTMLDivElement | undefined): MenuDirection {
|
||||||
const picker = (gradientStopPicker?.div()?.querySelector("[data-floating-menu-content]") || undefined) as HTMLElement | undefined;
|
const element = gradientStopPicker?.div()?.querySelector("[data-floating-menu-content]");
|
||||||
|
const picker = element instanceof HTMLElement ? element : undefined;
|
||||||
if (!picker || !position || !viewport) return "Bottom";
|
if (!picker || !position || !viewport) return "Bottom";
|
||||||
|
|
||||||
const roomRight = position.x + picker.offsetWidth - viewport.clientWidth;
|
const roomRight = position.x + picker.offsetWidth - viewport.clientWidth;
|
||||||
@@ -473,7 +470,7 @@
|
|||||||
// Gradient stop color picker
|
// Gradient stop color picker
|
||||||
editor.subscriptions.subscribeFrontendMessage("UpdateGradientStopColorPickerPosition", (data) => {
|
editor.subscriptions.subscribeFrontendMessage("UpdateGradientStopColorPickerPosition", (data) => {
|
||||||
gradientStopPickerColor = data.color;
|
gradientStopPickerColor = data.color;
|
||||||
gradientStopPickerPosition = { x: data.x, y: data.y };
|
gradientStopPickerPosition = { x: data.position[0], y: data.position[1] };
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update scrollbars and rulers
|
// Update scrollbars and rulers
|
||||||
@@ -511,9 +508,9 @@
|
|||||||
editor.subscriptions.subscribeFrontendMessage("DisplayEditableTextboxUpdateFontData", async (data) => {
|
editor.subscriptions.subscribeFrontendMessage("DisplayEditableTextboxUpdateFontData", async (data) => {
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
const fontData = new Uint8Array(data.fontData);
|
if (textInput && data.fontData.length > 0 && data.fontData.buffer instanceof ArrayBuffer) {
|
||||||
if (fontData.length > 0 && textInput) {
|
const fontView = new Uint8Array(data.fontData.buffer, data.fontData.byteOffset, data.fontData.byteLength);
|
||||||
window.document.fonts.add(new FontFace("text-font", fontData));
|
window.document.fonts.add(new FontFace("text-font", fontView));
|
||||||
textInput.style.fontFamily = "text-font";
|
textInput.style.fontFamily = "text-font";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -615,11 +612,10 @@
|
|||||||
gradientStopPickerColor = undefined;
|
gradientStopPickerColor = undefined;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
colorOrGradient={gradientStopPickerColor || createColor(0, 0, 0, 1)}
|
colorOrGradient={{ Solid: gradientStopPickerColor || createColor(0, 0, 0, 1) }}
|
||||||
on:colorOrGradient={({ detail }) => {
|
on:colorOrGradient={({ detail }) => {
|
||||||
if (isColor(detail)) {
|
const color = fillChoiceColor(detail);
|
||||||
editor.handle.updateGradientStopColor(detail.red, detail.green, detail.blue, detail.alpha);
|
if (color) editor.handle.updateGradientStopColor(color.red, color.green, color.blue, color.alpha);
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
on:startHistoryTransaction={() => editor.handle.startGradientStopColorTransaction()}
|
on:startHistoryTransaction={() => editor.handle.startGradientStopColorTransaction()}
|
||||||
on:commitHistoryTransaction={() => editor.handle.commitGradientStopColorTransaction()}
|
on:commitHistoryTransaction={() => editor.handle.commitGradientStopColorTransaction()}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
import { getContext, onMount, onDestroy, tick } from "svelte";
|
import { getContext, onMount, onDestroy, tick } from "svelte";
|
||||||
import { SvelteMap } from "svelte/reactivity";
|
import { SvelteMap } from "svelte/reactivity";
|
||||||
|
|
||||||
|
import type { LayerPanelEntry, LayerStructureEntry, Layout } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { Editor } from "@graphite/editor";
|
||||||
import type { LayerPanelEntry, LayerStructureEntry, Layout } from "@graphite/messages";
|
|
||||||
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
|
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
|
||||||
import type { TooltipState } from "@graphite/state-providers/tooltip";
|
import type { TooltipState } from "@graphite/state-providers/tooltip";
|
||||||
import { pasteFile } from "@graphite/utility-functions/files";
|
import { pasteFile } from "@graphite/utility-functions/files";
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, onMount, onDestroy } from "svelte";
|
import { getContext, onMount, onDestroy } from "svelte";
|
||||||
|
|
||||||
|
import type { Layout } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { Editor } from "@graphite/editor";
|
||||||
import type { Layout } from "@graphite/messages";
|
|
||||||
import { patchLayout } from "@graphite/utility-functions/widgets";
|
import { patchLayout } from "@graphite/utility-functions/widgets";
|
||||||
|
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext, onMount, onDestroy } from "svelte";
|
import { getContext, onMount, onDestroy } from "svelte";
|
||||||
|
|
||||||
|
import { isPlatformNative } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
|
import type { Layout } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { Editor } from "@graphite/editor";
|
||||||
import type { Layout } from "@graphite/messages";
|
|
||||||
import { pasteFile } from "@graphite/utility-functions/files";
|
import { pasteFile } from "@graphite/utility-functions/files";
|
||||||
import { isDesktop } from "@graphite/utility-functions/platform";
|
|
||||||
import { patchLayout } from "@graphite/utility-functions/widgets";
|
import { patchLayout } from "@graphite/utility-functions/widgets";
|
||||||
|
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
<LayoutCol class="bottom-message">
|
<LayoutCol class="bottom-message">
|
||||||
<TextLabel italic={true} disabled={true}>
|
<TextLabel italic={true} disabled={true}>
|
||||||
{#if isDesktop()}
|
{#if isPlatformNative()}
|
||||||
You are testing Release Candidate 3 of the 1.0 desktop release. Please regularly check Discord for the next testing build and report issues you encounter.
|
You are testing Release Candidate 3 of the 1.0 desktop release. Please regularly check Discord for the next testing build and report issues you encounter.
|
||||||
{/if}
|
{/if}
|
||||||
</TextLabel>
|
</TextLabel>
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
import { cubicInOut } from "svelte/easing";
|
import { cubicInOut } from "svelte/easing";
|
||||||
import { fade } from "svelte/transition";
|
import { fade } from "svelte/transition";
|
||||||
|
|
||||||
|
import type { FrontendGraphInput, FrontendGraphOutput, FrontendNode } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { Editor } from "@graphite/editor";
|
||||||
import type { FrontendGraphInput, FrontendGraphOutput, FrontendNode } from "@graphite/messages";
|
|
||||||
import type { DocumentState } from "@graphite/state-providers/document";
|
import type { DocumentState } from "@graphite/state-providers/document";
|
||||||
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
|
import type { NodeGraphState } from "@graphite/state-providers/node-graph";
|
||||||
|
|
||||||
@@ -80,7 +80,8 @@
|
|||||||
|
|
||||||
function setEditingImportName(event: Event) {
|
function setEditingImportName(event: Event) {
|
||||||
if (editingNameImportIndex !== undefined) {
|
if (editingNameImportIndex !== undefined) {
|
||||||
let text = (event.target as HTMLInputElement)?.value;
|
if (!(event.target instanceof HTMLInputElement)) return;
|
||||||
|
let text = event.target.value;
|
||||||
editor.handle.setImportName(editingNameImportIndex, text);
|
editor.handle.setImportName(editingNameImportIndex, text);
|
||||||
editingNameImportIndex = undefined;
|
editingNameImportIndex = undefined;
|
||||||
}
|
}
|
||||||
@@ -88,7 +89,8 @@
|
|||||||
|
|
||||||
function setEditingExportName(event: Event) {
|
function setEditingExportName(event: Event) {
|
||||||
if (editingNameExportIndex !== undefined) {
|
if (editingNameExportIndex !== undefined) {
|
||||||
let text = (event.target as HTMLInputElement)?.value;
|
if (!(event.target instanceof HTMLInputElement)) return;
|
||||||
|
let text = event.target.value;
|
||||||
editor.handle.setExportName(editingNameExportIndex, text);
|
editor.handle.setExportName(editingNameExportIndex, text);
|
||||||
editingNameExportIndex = undefined;
|
editingNameExportIndex = undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { Layout, LayoutTarget } from "@graphite/messages";
|
import type { Layout, LayoutTarget } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import { isWidgetSpanColumn, isWidgetSpanRow, isWidgetTable, isWidgetSection } from "@graphite/utility-functions/widgets";
|
|
||||||
|
|
||||||
import WidgetSection from "@graphite/components/widgets/WidgetSection.svelte";
|
import WidgetSection from "@graphite/components/widgets/WidgetSection.svelte";
|
||||||
import WidgetSpan from "@graphite/components/widgets/WidgetSpan.svelte";
|
import WidgetSpan from "@graphite/components/widgets/WidgetSpan.svelte";
|
||||||
@@ -14,12 +13,14 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#each layout as layoutGroup}
|
{#each layout as layoutGroup}
|
||||||
{#if isWidgetSpanRow(layoutGroup) || isWidgetSpanColumn(layoutGroup)}
|
{#if "Row" in layoutGroup}
|
||||||
<WidgetSpan widgetData={layoutGroup} {layoutTarget} class={className} {classes} />
|
<WidgetSpan direction="row" widgets={layoutGroup.Row.rowWidgets} {layoutTarget} class={className} {classes} />
|
||||||
{:else if isWidgetSection(layoutGroup)}
|
{:else if "Column" in layoutGroup}
|
||||||
<WidgetSection widgetData={layoutGroup} {layoutTarget} class={className} {classes} />
|
<WidgetSpan direction="column" widgets={layoutGroup.Column.columnWidgets} {layoutTarget} class={className} {classes} />
|
||||||
{:else if isWidgetTable(layoutGroup)}
|
{:else if "Section" in layoutGroup}
|
||||||
<WidgetTable widgetData={layoutGroup} {layoutTarget} unstyled={layoutGroup.unstyled} />
|
<WidgetSection widgetData={layoutGroup.Section} {layoutTarget} class={className} {classes} />
|
||||||
|
{:else if "Table" in layoutGroup}
|
||||||
|
<WidgetTable widgetData={layoutGroup.Table} {layoutTarget} />
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
|
|
||||||
|
import type { LayoutTarget, WidgetSection as WidgetSectionData } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { Editor } from "@graphite/editor";
|
||||||
import type { WidgetSection as WidgetSectionData, LayoutTarget } from "@graphite/messages";
|
|
||||||
import { isWidgetSpanRow, isWidgetSection } from "@graphite/utility-functions/widgets";
|
|
||||||
|
|
||||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||||
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
|
import IconButton from "@graphite/components/widgets/buttons/IconButton.svelte";
|
||||||
@@ -62,10 +61,10 @@
|
|||||||
{#if expanded}
|
{#if expanded}
|
||||||
<LayoutCol class="body" data-block-hover-transfer>
|
<LayoutCol class="body" data-block-hover-transfer>
|
||||||
{#each widgetData.layout as layoutGroup}
|
{#each widgetData.layout as layoutGroup}
|
||||||
{#if isWidgetSpanRow(layoutGroup)}
|
{#if "Row" in layoutGroup}
|
||||||
<WidgetSpan widgetData={layoutGroup} {layoutTarget} />
|
<WidgetSpan direction="row" widgets={layoutGroup.Row.rowWidgets} {layoutTarget} />
|
||||||
{:else if isWidgetSection(layoutGroup)}
|
{:else if "Section" in layoutGroup}
|
||||||
<svelte:self widgetData={layoutGroup} {layoutTarget} />
|
<svelte:self widgetData={layoutGroup.Section} {layoutTarget} />
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</LayoutCol>
|
</LayoutCol>
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getContext } from "svelte";
|
import { getContext } from "svelte";
|
||||||
|
|
||||||
|
import type { LayoutTarget, Widget, WidgetInstance } from "@graphite/../wasm/pkg/graphite_wasm";
|
||||||
import type { Editor } from "@graphite/editor";
|
import type { Editor } from "@graphite/editor";
|
||||||
import type { LayoutTarget, WidgetInstance, WidgetPropsNames, WidgetPropsSet, WidgetTypes, WidgetSpanColumn, WidgetSpanRow } from "@graphite/messages";
|
|
||||||
import { parseFillChoice } from "@graphite/utility-functions/colors";
|
import { parseFillChoice } from "@graphite/utility-functions/colors";
|
||||||
import { debouncer } from "@graphite/utility-functions/debounce";
|
import { debouncer } from "@graphite/utility-functions/debounce";
|
||||||
import { isWidgetSpanColumn, isWidgetSpanRow, createLayoutGroup } from "@graphite/utility-functions/widgets";
|
|
||||||
|
|
||||||
import NodeCatalog from "@graphite/components/floating-menus/NodeCatalog.svelte";
|
import NodeCatalog from "@graphite/components/floating-menus/NodeCatalog.svelte";
|
||||||
import BreadcrumbTrailButtons from "@graphite/components/widgets/buttons/BreadcrumbTrailButtons.svelte";
|
import BreadcrumbTrailButtons from "@graphite/components/widgets/buttons/BreadcrumbTrailButtons.svelte";
|
||||||
@@ -30,9 +29,17 @@
|
|||||||
import ShortcutLabel from "@graphite/components/widgets/labels/ShortcutLabel.svelte";
|
import ShortcutLabel from "@graphite/components/widgets/labels/ShortcutLabel.svelte";
|
||||||
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte";
|
||||||
|
|
||||||
|
// Extract the discriminant key names from the Widget tagged enum union (e.g. "TextButton" | "CheckboxInput" | ...)
|
||||||
|
type WidgetKind = Widget extends infer T ? (T extends Record<infer K, unknown> ? K & string : never) : never;
|
||||||
|
// Extract the props type for a specific widget kind (e.g. WidgetProps<"TextButton"> gives the Wasm-generated TextButton interface)
|
||||||
|
type WidgetProps<K extends WidgetKind> = Extract<Widget, Record<K, unknown>>[K];
|
||||||
|
// A Widget tagged enum unwrapped into a correlated [kind, props] tuple
|
||||||
|
type UnwrappedWidget = { [K in WidgetKind]: [kind: K, props: WidgetProps<K>] }[WidgetKind];
|
||||||
|
|
||||||
const editor = getContext<Editor>("editor");
|
const editor = getContext<Editor>("editor");
|
||||||
|
|
||||||
export let widgetData: WidgetSpanRow | WidgetSpanColumn;
|
export let widgets: WidgetInstance[];
|
||||||
|
export let direction: "row" | "column";
|
||||||
export let layoutTarget: LayoutTarget;
|
export let layoutTarget: LayoutTarget;
|
||||||
|
|
||||||
let className = "";
|
let className = "";
|
||||||
@@ -45,21 +52,6 @@
|
|||||||
.flatMap(([className, stateName]) => (stateName ? [className] : []))
|
.flatMap(([className, stateName]) => (stateName ? [className] : []))
|
||||||
.join(" ");
|
.join(" ");
|
||||||
|
|
||||||
$: direction = watchDirection(widgetData);
|
|
||||||
$: widgets = watchWidgets(widgetData);
|
|
||||||
|
|
||||||
function watchDirection(widgetData: WidgetSpanRow | WidgetSpanColumn): "row" | "column" | undefined {
|
|
||||||
if (isWidgetSpanRow(widgetData)) return "row";
|
|
||||||
if (isWidgetSpanColumn(widgetData)) return "column";
|
|
||||||
}
|
|
||||||
|
|
||||||
function watchWidgets(widgetData: WidgetSpanRow | WidgetSpanColumn): WidgetInstance[] {
|
|
||||||
let widgets: WidgetInstance[] = [];
|
|
||||||
if (isWidgetSpanRow(widgetData)) widgets = widgetData.rowWidgets;
|
|
||||||
else if (isWidgetSpanColumn(widgetData)) widgets = widgetData.columnWidgets;
|
|
||||||
return widgets;
|
|
||||||
}
|
|
||||||
|
|
||||||
function widgetValueCommit(widgetIndex: number, value: unknown) {
|
function widgetValueCommit(widgetIndex: number, value: unknown) {
|
||||||
editor.handle.widgetValueCommit(layoutTarget, widgets[widgetIndex].widgetId, value);
|
editor.handle.widgetValueCommit(layoutTarget, widgets[widgetIndex].widgetId, value);
|
||||||
}
|
}
|
||||||
@@ -72,32 +64,66 @@
|
|||||||
editor.handle.widgetValueCommitAndUpdate(layoutTarget, widgets[widgetIndex].widgetId, value, resendWidget);
|
editor.handle.widgetValueCommitAndUpdate(layoutTarget, widgets[widgetIndex].widgetId, value, resendWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// Extracts the kind and props from a Widget tagged enum, validated against the widget registry.
|
||||||
function exclude(props: WidgetPropsSet, additional?: string[]): Record<string, any> {
|
// The overload declares the precise correlated return type while the implementation uses broader types.
|
||||||
const exclusions = new Set(["kind", ...(additional || [])]);
|
function unwrapWidget(widgetInstance: WidgetInstance): UnwrappedWidget | undefined;
|
||||||
return Object.fromEntries(Object.entries(props).filter(([key]) => !exclusions.has(key)));
|
function unwrapWidget(widgetInstance: WidgetInstance) {
|
||||||
|
const entry = Object.entries(widgetInstance.widget)[0];
|
||||||
|
if (!entry || !(entry[0] in widgetResolvers)) return undefined;
|
||||||
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
type WidgetConfig = {
|
// Resolves the unwrapped widget through the registry to get its Svelte component and computed props.
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
function resolveWidget([kind, widgetProps]: UnwrappedWidget, widgetIndex: number) {
|
||||||
component: any;
|
const config = widgetResolvers[kind];
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
return {
|
||||||
getProps(props: WidgetPropsSet, widgetIndex: number): Record<string, any> | undefined;
|
component: config.component,
|
||||||
getSlotContent?(props: WidgetPropsSet): string;
|
props: config.getProps(widgetProps, widgetIndex),
|
||||||
|
slot: config.getSlotContent?.(widgetProps),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Svelte has no variance-safe base type for component constructors
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
type SvelteComponentAny = any;
|
||||||
|
|
||||||
|
type WidgetConfig<K extends WidgetKind> = {
|
||||||
|
component: SvelteComponentAny;
|
||||||
|
getProps(props: WidgetProps<K>, widgetIndex: number): Record<string, unknown> | undefined;
|
||||||
|
getSlotContent?(props: WidgetProps<K>): string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const widgetRegistry: Record<WidgetPropsNames, WidgetConfig> = {
|
// The union of all individual widget props types (distributed across each WidgetKind member)
|
||||||
|
type AnyWidgetProps = { [K in WidgetKind]: WidgetProps<K> }[WidgetKind];
|
||||||
|
|
||||||
|
// Uniform view for runtime lookup — widens the per-kind config types to a single type that
|
||||||
|
// accepts any widget props, avoiding the correlated unions problem at the call site
|
||||||
|
type WidgetResolver = {
|
||||||
|
component: SvelteComponentAny;
|
||||||
|
getProps(props: AnyWidgetProps, widgetIndex: number): Record<string, unknown> | undefined;
|
||||||
|
getSlotContent?(props: AnyWidgetProps): string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Overload: callers provide the precise mapped type (preserving per-entry type inference).
|
||||||
|
// Implementation: receives/returns the widened uniform type (no cast needed).
|
||||||
|
// Method syntax bivariance makes WidgetConfig<K> assignable to WidgetResolver in the overload check.
|
||||||
|
function createWidgetResolvers(registry: { [K in WidgetKind]: WidgetConfig<K> }): Record<WidgetKind, WidgetResolver>;
|
||||||
|
function createWidgetResolvers(registry: Record<WidgetKind, WidgetResolver>): Record<WidgetKind, WidgetResolver> {
|
||||||
|
return registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
const widgetResolvers = createWidgetResolvers({
|
||||||
CheckboxInput: {
|
CheckboxInput: {
|
||||||
component: CheckboxInput,
|
component: CheckboxInput,
|
||||||
getProps: (props: WidgetTypes["CheckboxInput"], index) => ({
|
getProps: (props, index) => ({
|
||||||
...exclude(props),
|
...props,
|
||||||
$$events: { checked: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, true) },
|
$$events: { checked: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, true) },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
ColorInput: {
|
ColorInput: {
|
||||||
component: ColorInput,
|
component: ColorInput,
|
||||||
getProps: (props: WidgetTypes["ColorInput"], index) => ({
|
getProps: (props, index) => ({
|
||||||
...exclude(props),
|
...props,
|
||||||
value: parseFillChoice(props.value),
|
value: parseFillChoice(props.value),
|
||||||
$$events: {
|
$$events: {
|
||||||
value: (e: CustomEvent) => widgetValueUpdate(index, e.detail, false),
|
value: (e: CustomEvent) => widgetValueUpdate(index, e.detail, false),
|
||||||
@@ -108,8 +134,8 @@
|
|||||||
CurveInput: {
|
CurveInput: {
|
||||||
// TODO: CurvesInput is currently unused
|
// TODO: CurvesInput is currently unused
|
||||||
component: CurveInput,
|
component: CurveInput,
|
||||||
getProps: (props: WidgetTypes["CurveInput"], index) => ({
|
getProps: (props, index) => ({
|
||||||
...exclude(props),
|
...props,
|
||||||
$$events: {
|
$$events: {
|
||||||
value: (e: CustomEvent) => debouncer((value: unknown) => widgetValueCommitAndUpdate(index, value, false), { debounceTime: 120 }).debounceUpdateValue(e.detail),
|
value: (e: CustomEvent) => debouncer((value: unknown) => widgetValueCommitAndUpdate(index, value, false), { debounceTime: 120 }).debounceUpdateValue(e.detail),
|
||||||
},
|
},
|
||||||
@@ -117,8 +143,8 @@
|
|||||||
},
|
},
|
||||||
DropdownInput: {
|
DropdownInput: {
|
||||||
component: DropdownInput,
|
component: DropdownInput,
|
||||||
getProps: (props: WidgetTypes["DropdownInput"], index) => ({
|
getProps: (props, index) => ({
|
||||||
...exclude(props),
|
...props,
|
||||||
$$events: {
|
$$events: {
|
||||||
hoverInEntry: (e: CustomEvent) => widgetValueUpdate(index, e.detail, false),
|
hoverInEntry: (e: CustomEvent) => widgetValueUpdate(index, e.detail, false),
|
||||||
hoverOutEntry: (e: CustomEvent) => widgetValueUpdate(index, e.detail, false),
|
hoverOutEntry: (e: CustomEvent) => widgetValueUpdate(index, e.detail, false),
|
||||||
@@ -128,51 +154,51 @@
|
|||||||
},
|
},
|
||||||
ParameterExposeButton: {
|
ParameterExposeButton: {
|
||||||
component: ParameterExposeButton,
|
component: ParameterExposeButton,
|
||||||
getProps: (props: WidgetTypes["ParameterExposeButton"], index) => ({
|
getProps: (props, index) => ({
|
||||||
...exclude(props),
|
...props,
|
||||||
action: () => widgetValueCommitAndUpdate(index, undefined, true),
|
action: () => widgetValueCommitAndUpdate(index, undefined, true),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
IconButton: {
|
IconButton: {
|
||||||
component: IconButton,
|
component: IconButton,
|
||||||
getProps: (props: WidgetTypes["IconButton"], index) => ({
|
getProps: (props, index) => ({
|
||||||
...exclude(props),
|
...props,
|
||||||
action: () => widgetValueCommitAndUpdate(index, undefined, true),
|
action: () => widgetValueCommitAndUpdate(index, undefined, true),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
IconLabel: {
|
IconLabel: {
|
||||||
component: IconLabel,
|
component: IconLabel,
|
||||||
getProps: (props: WidgetTypes["IconLabel"]) => exclude(props),
|
getProps: (props) => ({ ...props }),
|
||||||
},
|
},
|
||||||
ShortcutLabel: {
|
ShortcutLabel: {
|
||||||
component: ShortcutLabel,
|
component: ShortcutLabel,
|
||||||
getProps: (props: WidgetTypes["ShortcutLabel"]) => {
|
getProps: (props) => {
|
||||||
if (!props.shortcut) return undefined;
|
if (!props.shortcut) return undefined;
|
||||||
return exclude(props);
|
return { ...props };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ImageLabel: {
|
ImageLabel: {
|
||||||
component: ImageLabel,
|
component: ImageLabel,
|
||||||
getProps: (props: WidgetTypes["ImageLabel"]) => exclude(props),
|
getProps: (props) => ({ ...props }),
|
||||||
},
|
},
|
||||||
ImageButton: {
|
ImageButton: {
|
||||||
component: ImageButton,
|
component: ImageButton,
|
||||||
getProps: (props: WidgetTypes["ImageButton"], index) => ({
|
getProps: (props, index) => ({
|
||||||
...exclude(props),
|
...props,
|
||||||
action: () => widgetValueCommitAndUpdate(index, undefined, true),
|
action: () => widgetValueCommitAndUpdate(index, undefined, true),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
NodeCatalog: {
|
NodeCatalog: {
|
||||||
component: NodeCatalog,
|
component: NodeCatalog,
|
||||||
getProps: (props: WidgetTypes["NodeCatalog"], index) => ({
|
getProps: (props, index) => ({
|
||||||
...exclude(props),
|
...props,
|
||||||
$$events: { selectNodeType: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, false) },
|
$$events: { selectNodeType: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, false) },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
NumberInput: {
|
NumberInput: {
|
||||||
component: NumberInput,
|
component: NumberInput,
|
||||||
getProps: (props: WidgetTypes["NumberInput"], index) => ({
|
getProps: (props, index) => ({
|
||||||
...exclude(props),
|
...props,
|
||||||
incrementCallbackIncrease: () => widgetValueCommitAndUpdate(index, "Increment", false),
|
incrementCallbackIncrease: () => widgetValueCommitAndUpdate(index, "Increment", false),
|
||||||
incrementCallbackDecrease: () => widgetValueCommitAndUpdate(index, "Decrement", false),
|
incrementCallbackDecrease: () => widgetValueCommitAndUpdate(index, "Decrement", false),
|
||||||
$$events: {
|
$$events: {
|
||||||
@@ -183,80 +209,80 @@
|
|||||||
},
|
},
|
||||||
ReferencePointInput: {
|
ReferencePointInput: {
|
||||||
component: ReferencePointInput,
|
component: ReferencePointInput,
|
||||||
getProps: (props: WidgetTypes["ReferencePointInput"], index) => ({
|
getProps: (props, index) => ({
|
||||||
...exclude(props),
|
...props,
|
||||||
$$events: { value: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, true) },
|
$$events: { value: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, true) },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
PopoverButton: {
|
PopoverButton: {
|
||||||
component: PopoverButton,
|
component: PopoverButton,
|
||||||
getProps: (props: WidgetTypes["PopoverButton"]) => ({
|
getProps: (props) => ({
|
||||||
...exclude(props),
|
...props,
|
||||||
layoutTarget,
|
layoutTarget,
|
||||||
popoverLayout: props.popoverLayout.map(createLayoutGroup),
|
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
RadioInput: {
|
RadioInput: {
|
||||||
component: RadioInput,
|
component: RadioInput,
|
||||||
getProps: (props: WidgetTypes["RadioInput"], index) => ({
|
getProps: (props, index) => ({
|
||||||
...exclude(props),
|
...props,
|
||||||
$$events: { selectedIndex: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, true) },
|
$$events: { selectedIndex: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, true) },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
Separator: {
|
Separator: {
|
||||||
component: Separator,
|
component: Separator,
|
||||||
getProps: (props: WidgetTypes["Separator"]) => exclude(props),
|
getProps: (props) => ({ ...props }),
|
||||||
},
|
},
|
||||||
WorkingColorsInput: {
|
WorkingColorsInput: {
|
||||||
component: WorkingColorsInput,
|
component: WorkingColorsInput,
|
||||||
getProps: (props: WidgetTypes["WorkingColorsInput"]) => exclude(props),
|
getProps: (props) => ({ ...props }),
|
||||||
},
|
},
|
||||||
TextAreaInput: {
|
TextAreaInput: {
|
||||||
component: TextAreaInput,
|
component: TextAreaInput,
|
||||||
getProps: (props: WidgetTypes["TextAreaInput"], index) => ({
|
getProps: (props, index) => ({
|
||||||
...exclude(props),
|
...props,
|
||||||
$$events: { commitText: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, false) },
|
$$events: { commitText: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, false) },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
TextButton: {
|
TextButton: {
|
||||||
component: TextButton,
|
component: TextButton,
|
||||||
getProps: (props: WidgetTypes["TextButton"], index) => ({
|
getProps: (props, index) => ({
|
||||||
...exclude(props),
|
...props,
|
||||||
action: () => widgetValueCommitAndUpdate(index, [], true),
|
action: () => widgetValueCommitAndUpdate(index, [], true),
|
||||||
$$events: { selectedEntryValuePath: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, false) },
|
$$events: { selectedEntryValuePath: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, false) },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
BreadcrumbTrailButtons: {
|
BreadcrumbTrailButtons: {
|
||||||
component: BreadcrumbTrailButtons,
|
component: BreadcrumbTrailButtons,
|
||||||
getProps: (props: WidgetTypes["BreadcrumbTrailButtons"], index) => ({
|
getProps: (props, index) => ({
|
||||||
...exclude(props),
|
...props,
|
||||||
action: (breadcrumbIndex: number) => widgetValueCommitAndUpdate(index, breadcrumbIndex, true),
|
action: (breadcrumbIndex: number) => widgetValueCommitAndUpdate(index, breadcrumbIndex, true),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
TextInput: {
|
TextInput: {
|
||||||
component: TextInput,
|
component: TextInput,
|
||||||
getProps: (props: WidgetTypes["TextInput"], index) => ({
|
getProps: (props, index) => ({
|
||||||
...exclude(props),
|
...props,
|
||||||
$$events: { commitText: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, true) },
|
$$events: { commitText: (e: CustomEvent) => widgetValueCommitAndUpdate(index, e.detail, true) },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
TextLabel: {
|
TextLabel: {
|
||||||
component: TextLabel,
|
component: TextLabel,
|
||||||
getProps: (props: WidgetTypes["TextLabel"]) => exclude(props, ["value"]),
|
getProps: ({ value: _, ...rest }) => rest,
|
||||||
getSlotContent: (props: WidgetTypes["TextLabel"]) => props.value,
|
getSlotContent: (props) => props.value,
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class={`widget-span ${className} ${extraClasses}`.trim()} class:narrow class:row={direction === "row"} class:column={direction === "column"}>
|
<div class={`widget-span ${className} ${extraClasses}`.trim()} class:narrow class:row={direction === "row"} class:column={direction === "column"}>
|
||||||
{#each widgets as widget, widgetIndex}
|
{#each widgets as widget, widgetIndex}
|
||||||
{@const config = widgetRegistry[widget.props.kind]}
|
{@const unwrapped = unwrapWidget(widget)}
|
||||||
{@const props = config?.getProps(widget.props, widgetIndex)}
|
{#if unwrapped}
|
||||||
{@const slot = config?.getSlotContent?.(widget.props)}
|
{@const { component, props, slot } = resolveWidget(unwrapped, widgetIndex)}
|
||||||
{#if props !== undefined && slot !== undefined}
|
{#if props !== undefined && slot !== undefined}
|
||||||
<svelte:component this={config.component} {...props}>{slot}</svelte:component>
|
<svelte:component this={component} {...props}>{slot}</svelte:component>
|
||||||
{:else if props !== undefined}
|
{:else if props !== undefined}
|
||||||
<svelte:component this={config.component} {...props} />
|
<svelte:component this={component} {...props} />
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user