diff --git a/.github/workflows/build-dev-and-ci.yml b/.github/workflows/build-dev-and-ci.yml
index ff3f25151e..e62ea3bca0 100644
--- a/.github/workflows/build-dev-and-ci.yml
+++ b/.github/workflows/build-dev-and-ci.yml
@@ -4,7 +4,7 @@ on:
push:
branches:
- master
- pull_request:
+ pull_request: {}
env:
CARGO_TERM_COLOR: always
INDEX_HTML_HEAD_REPLACEMENT:
@@ -13,9 +13,10 @@ jobs:
build:
runs-on: self-hosted
permissions:
- contents: read
+ contents: write
deployments: write
pull-requests: write
+ actions: write
env:
RUSTC_WRAPPER: /usr/bin/sccache
CARGO_INCREMENTAL: 0
@@ -47,9 +48,11 @@ jobs:
rustc --version
- name: ✂ Replace template in
of index.html
+ if: github.ref != 'refs/heads/master'
+ env:
+ INDEX_HTML_HEAD_REPLACEMENT: ""
run: |
# Remove the INDEX_HTML_HEAD_REPLACEMENT environment variable for build links (not master deploys)
- git rev-parse --abbrev-ref HEAD | grep master > /dev/null || export INDEX_HTML_HEAD_REPLACEMENT=""
sed -i "s||$INDEX_HTML_HEAD_REPLACEMENT|" frontend/index.html
- name: 🌐 Build Graphite web code
@@ -70,6 +73,19 @@ jobs:
projectName: graphite-dev
directory: frontend/dist
+ - name: 💬 Comment build link URL to commit hash page on GitHub
+ if: github.ref == 'refs/heads/master'
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ gh api \
+ -X POST \
+ -H "Accept: application/vnd.github+json" \
+ /repos/${{ github.repository }}/commits/$(git rev-parse HEAD)/comments \
+ -f body="| 📦 **Build Complete for** $(git rev-parse HEAD) |
+ |-|
+ | ${{ steps.cloudflare.outputs.url }} |"
+
- name: 👕 Lint Graphite web formatting
env:
NODE_ENV: production
@@ -91,6 +107,51 @@ jobs:
run: |
mold -run cargo test --all-features --workspace
+ - name: 📃 Generate code documentation info for website
+ if: github.ref == 'refs/heads/master'
+ run: |
+ cargo test --package graphite-editor --lib -- messages::message::test::generate_message_tree
+ mkdir -p artifacts-generated
+ mv hierarchical_message_system_tree.txt artifacts-generated/hierarchical_message_system_tree.txt
+
+ - name: 💿 Obtain cache of auto-generated code docs artifacts, to check if they've changed
+ if: github.ref == 'refs/heads/master'
+ id: cache-website-code-docs
+ uses: actions/cache/restore@v3
+ with:
+ path: artifacts
+ key: website-code-docs
+
+ - name: 🔍 Check if auto-generated code docs artifacts changed
+ if: github.ref == 'refs/heads/master'
+ id: website-code-docs-changed
+ run: |
+ if ! diff --brief --recursive artifacts-generated artifacts; then
+ echo "Auto-generated code docs artifacts have changed."
+ rm -rf artifacts
+ mv artifacts-generated artifacts
+ echo "changed=true" >> $GITHUB_OUTPUT
+ else
+ echo "Auto-generated code docs artifacts have not changed."
+ rm -rf artifacts
+ rm -rf artifacts-generated
+ fi
+
+ - name: 💾 Save cache of auto-generated code docs artifacts
+ if: steps.website-code-docs-changed.outputs.changed == 'true'
+ uses: actions/cache/save@v3
+ with:
+ path: artifacts
+ key: ${{ steps.cache-website-code-docs.outputs.cache-primary-key }}
+
+ - name: ♻️ Trigger website rebuild if the auto-generated code docs artifacts have changed
+ if: steps.website-code-docs-changed.outputs.changed == 'true'
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ rm -rf artifacts
+ gh workflow run website.yml --ref master
+
# miri:
# runs-on: self-hosted
diff --git a/.github/workflows/comment-!build-commands.yml b/.github/workflows/comment-!build-commands.yml
index a8fa55e275..8d8a0ae096 100644
--- a/.github/workflows/comment-!build-commands.yml
+++ b/.github/workflows/comment-!build-commands.yml
@@ -73,9 +73,10 @@ jobs:
rustc --version
- name: ✂ Replace template in of index.html
+ env:
+ INDEX_HTML_HEAD_REPLACEMENT: ""
run: |
# Remove the INDEX_HTML_HEAD_REPLACEMENT environment variable for build links (not master deploys)
- export INDEX_HTML_HEAD_REPLACEMENT=""
sed -i "s||$INDEX_HTML_HEAD_REPLACEMENT|" frontend/index.html
- name: ⌨ Set build command based on comment
diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml
index 0451d4226e..4b9a6ed409 100644
--- a/.github/workflows/website.yml
+++ b/.github/workflows/website.yml
@@ -9,6 +9,7 @@ on:
pull_request:
paths:
- website/**
+ workflow_dispatch: {}
env:
CARGO_TERM_COLOR: always
INDEX_HTML_HEAD_INCLUSION:
@@ -30,6 +31,14 @@ jobs:
with:
tool: zola@0.20.0
+ - name: 🔍 Check if `website/other` directory changed
+ uses: dorny/paths-filter@v3
+ id: changes
+ with:
+ filters: |
+ website-other:
+ - "website/other/**"
+
- name: ✂ Replace template in of index.html
run: |
# Remove the INDEX_HTML_HEAD_INCLUSION environment variable for build links (not master deploys)
@@ -43,16 +52,8 @@ jobs:
npm run install-fonts
zola --config config.toml build --minify
- - name: 🔍 Check if `website/other` directory changed
- uses: dorny/paths-filter@v3
- id: changes
- with:
- filters: |
- other:
- - "website/other/**"
-
- name: 💿 Restore cache of `website/other/dist` directory, if available and `website/other` didn't change
- if: steps.changes.outputs.other != 'true'
+ if: steps.changes.outputs.website-other != 'true'
id: cache-website-other-dist
uses: actions/cache/restore@v3
with:
@@ -80,8 +81,32 @@ jobs:
- name: 🚚 Move `website/other/dist` contents to `website/public`
run: |
+ mkdir -p website/public
mv website/other/dist/* website/public
+ - name: 💿 Obtain cache of auto-generated code docs artifacts
+ id: cache-website-code-docs
+ uses: actions/cache/restore@v3
+ with:
+ path: artifacts
+ key: website-code-docs
+
+ - name: 📁 Fallback in case auto-generated code docs artifacts weren't cached
+ if: steps.cache-website-code-docs.outputs.cache-hit != 'true'
+ run: |
+ echo "🦀 Initial system version of Rust:"
+ rustc --version
+ rustup update stable
+ echo "🦀 Latest updated version of Rust:"
+ rustc --version
+ cargo test --package graphite-editor --lib -- messages::message::test::generate_message_tree
+ mkdir artifacts
+ mv hierarchical_message_system_tree.txt artifacts/hierarchical_message_system_tree.txt
+
+ - name: 🚚 Move `artifacts` contents to `website/public`
+ run: |
+ mv artifacts/* website/public
+
- name: 📤 Publish to Cloudflare Pages
id: cloudflare
uses: cloudflare/pages-action@1
diff --git a/.gitignore b/.gitignore
index 5700f4e89b..bdfa416126 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@ profile.json
flamegraph.svg
.idea/
.direnv
+hierarchical_message_system_tree.txt
diff --git a/editor/src/lib.rs b/editor/src/lib.rs
index 80d80b5305..2c59945136 100644
--- a/editor/src/lib.rs
+++ b/editor/src/lib.rs
@@ -15,3 +15,4 @@ pub mod node_graph_executor;
#[cfg(test)]
pub mod test_utils;
pub mod utility_traits;
+pub mod utility_types;
diff --git a/editor/src/messages/animation/animation_message_handler.rs b/editor/src/messages/animation/animation_message_handler.rs
index eb7ceba2e0..211d22c87c 100644
--- a/editor/src/messages/animation/animation_message_handler.rs
+++ b/editor/src/messages/animation/animation_message_handler.rs
@@ -24,7 +24,7 @@ enum AnimationState {
},
}
-#[derive(Default, Debug, Clone, PartialEq)]
+#[derive(Default, Debug, Clone, PartialEq, ExtractField)]
pub struct AnimationMessageHandler {
/// Used to re-send the UI on the next frame after playback starts
live_preview_recently_zero: bool,
@@ -57,6 +57,7 @@ impl AnimationMessageHandler {
}
}
+#[message_handler_data]
impl MessageHandler for AnimationMessageHandler {
fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque, _data: ()) {
match message {
diff --git a/editor/src/messages/broadcast/broadcast_message_handler.rs b/editor/src/messages/broadcast/broadcast_message_handler.rs
index 51d64b5852..489df47ab7 100644
--- a/editor/src/messages/broadcast/broadcast_message_handler.rs
+++ b/editor/src/messages/broadcast/broadcast_message_handler.rs
@@ -1,10 +1,11 @@
use crate::messages::prelude::*;
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone, Default, ExtractField)]
pub struct BroadcastMessageHandler {
listeners: HashMap>,
}
+#[message_handler_data]
impl MessageHandler for BroadcastMessageHandler {
fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque, _data: ()) {
match message {
diff --git a/editor/src/messages/debug/debug_message_handler.rs b/editor/src/messages/debug/debug_message_handler.rs
index 6044ce9108..064ad2510c 100644
--- a/editor/src/messages/debug/debug_message_handler.rs
+++ b/editor/src/messages/debug/debug_message_handler.rs
@@ -1,11 +1,12 @@
use super::utility_types::MessageLoggingVerbosity;
use crate::messages::prelude::*;
-#[derive(Debug, Default)]
+#[derive(Debug, Default, ExtractField)]
pub struct DebugMessageHandler {
pub message_logging_verbosity: MessageLoggingVerbosity,
}
+#[message_handler_data]
impl MessageHandler for DebugMessageHandler {
fn process_message(&mut self, message: DebugMessage, responses: &mut VecDeque, _data: ()) {
match message {
diff --git a/editor/src/messages/dialog/dialog_message_handler.rs b/editor/src/messages/dialog/dialog_message_handler.rs
index 3e9c80e46a..8e6bbde2f8 100644
--- a/editor/src/messages/dialog/dialog_message_handler.rs
+++ b/editor/src/messages/dialog/dialog_message_handler.rs
@@ -2,19 +2,21 @@ use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArt
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*;
+#[derive(ExtractField)]
pub struct DialogMessageData<'a> {
pub portfolio: &'a PortfolioMessageHandler,
pub preferences: &'a PreferencesMessageHandler,
}
/// Stores the dialogs which require state. These are the ones that have their own message handlers, and are not the ones defined in `simple_dialogs`.
-#[derive(Debug, Default, Clone)]
+#[derive(Debug, Default, Clone, ExtractField)]
pub struct DialogMessageHandler {
export_dialog: ExportDialogMessageHandler,
new_document_dialog: NewDocumentDialogMessageHandler,
preferences_dialog: PreferencesDialogMessageHandler,
}
+#[message_handler_data]
impl MessageHandler> for DialogMessageHandler {
fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque, data: DialogMessageData) {
let DialogMessageData { portfolio, preferences } = data;
diff --git a/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs b/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs
index 1bcf4b7003..179aab4f96 100644
--- a/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs
+++ b/editor/src/messages/dialog/export_dialog/export_dialog_message_handler.rs
@@ -3,12 +3,13 @@ use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::*;
+#[derive(ExtractField)]
pub struct ExportDialogMessageData<'a> {
pub portfolio: &'a PortfolioMessageHandler,
}
/// A dialog to allow users to customize their file export.
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, ExtractField)]
pub struct ExportDialogMessageHandler {
pub file_type: FileType,
pub scale_factor: f64,
@@ -31,6 +32,7 @@ impl Default for ExportDialogMessageHandler {
}
}
+#[message_handler_data]
impl MessageHandler> for ExportDialogMessageHandler {
fn process_message(&mut self, message: ExportDialogMessage, responses: &mut VecDeque, data: ExportDialogMessageData) {
let ExportDialogMessageData { portfolio } = data;
diff --git a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs
index 2e94f876c7..539180117c 100644
--- a/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs
+++ b/editor/src/messages/dialog/new_document_dialog/new_document_dialog_message_handler.rs
@@ -4,13 +4,14 @@ use glam::{IVec2, UVec2};
use graph_craft::document::NodeId;
/// A dialog to allow users to set some initial options about a new document.
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone, Default, ExtractField)]
pub struct NewDocumentDialogMessageHandler {
pub name: String,
pub infinite: bool,
pub dimensions: UVec2,
}
+#[message_handler_data]
impl MessageHandler for NewDocumentDialogMessageHandler {
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque, _data: ()) {
match message {
diff --git a/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs b/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs
index 6e92d60bcf..882fc6db5c 100644
--- a/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs
+++ b/editor/src/messages/dialog/preferences_dialog/preferences_dialog_message_handler.rs
@@ -4,14 +4,16 @@ use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
use crate::messages::preferences::SelectionMode;
use crate::messages::prelude::*;
+#[derive(ExtractField)]
pub struct PreferencesDialogMessageData<'a> {
pub preferences: &'a PreferencesMessageHandler,
}
/// A dialog to allow users to customize Graphite editor options
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone, Default, ExtractField)]
pub struct PreferencesDialogMessageHandler {}
+#[message_handler_data]
impl MessageHandler> for PreferencesDialogMessageHandler {
fn process_message(&mut self, message: PreferencesDialogMessage, responses: &mut VecDeque, data: PreferencesDialogMessageData) {
let PreferencesDialogMessageData { preferences } = data;
diff --git a/editor/src/messages/globals/globals_message_handler.rs b/editor/src/messages/globals/globals_message_handler.rs
index 5651555f60..9a72ffa6dc 100644
--- a/editor/src/messages/globals/globals_message_handler.rs
+++ b/editor/src/messages/globals/globals_message_handler.rs
@@ -1,8 +1,9 @@
use crate::messages::prelude::*;
-#[derive(Debug, Default)]
+#[derive(Debug, Default, ExtractField)]
pub struct GlobalsMessageHandler {}
+#[message_handler_data]
impl MessageHandler for GlobalsMessageHandler {
fn process_message(&mut self, message: GlobalsMessage, _responses: &mut VecDeque, _data: ()) {
match message {
diff --git a/editor/src/messages/input_mapper/input_mapper_message_handler.rs b/editor/src/messages/input_mapper/input_mapper_message_handler.rs
index eba3867213..b26a1b4c40 100644
--- a/editor/src/messages/input_mapper/input_mapper_message_handler.rs
+++ b/editor/src/messages/input_mapper/input_mapper_message_handler.rs
@@ -6,16 +6,18 @@ use crate::messages::portfolio::utility_types::KeyboardPlatformLayout;
use crate::messages::prelude::*;
use std::fmt::Write;
+#[derive(ExtractField)]
pub struct InputMapperMessageData<'a> {
pub input: &'a InputPreprocessorMessageHandler,
pub actions: ActionList,
}
-#[derive(Debug, Default)]
+#[derive(Debug, Default, ExtractField)]
pub struct InputMapperMessageHandler {
mapping: Mapping,
}
+#[message_handler_data]
impl MessageHandler> for InputMapperMessageHandler {
fn process_message(&mut self, message: InputMapperMessage, responses: &mut VecDeque, data: InputMapperMessageData) {
let InputMapperMessageData { input, actions } = data;
diff --git a/editor/src/messages/input_mapper/key_mapping/key_mapping_message_handler.rs b/editor/src/messages/input_mapper/key_mapping/key_mapping_message_handler.rs
index 53c626bf54..81f249ec5e 100644
--- a/editor/src/messages/input_mapper/key_mapping/key_mapping_message_handler.rs
+++ b/editor/src/messages/input_mapper/key_mapping/key_mapping_message_handler.rs
@@ -2,16 +2,18 @@ use crate::messages::input_mapper::input_mapper_message_handler::InputMapperMess
use crate::messages::input_mapper::utility_types::input_keyboard::KeysGroup;
use crate::messages::prelude::*;
+#[derive(ExtractField)]
pub struct KeyMappingMessageData<'a> {
pub input: &'a InputPreprocessorMessageHandler,
pub actions: ActionList,
}
-#[derive(Debug, Default)]
+#[derive(Debug, Default, ExtractField)]
pub struct KeyMappingMessageHandler {
mapping_handler: InputMapperMessageHandler,
}
+#[message_handler_data]
impl MessageHandler> for KeyMappingMessageHandler {
fn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque, data: KeyMappingMessageData) {
let KeyMappingMessageData { input, actions } = data;
diff --git a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs
index bf90e5881f..144652fdc5 100644
--- a/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs
+++ b/editor/src/messages/input_preprocessor/input_preprocessor_message_handler.rs
@@ -6,11 +6,12 @@ use crate::messages::prelude::*;
use glam::DVec2;
use std::time::Duration;
+#[derive(ExtractField)]
pub struct InputPreprocessorMessageData {
pub keyboard_platform: KeyboardPlatformLayout,
}
-#[derive(Debug, Default)]
+#[derive(Debug, Default, ExtractField)]
pub struct InputPreprocessorMessageHandler {
pub frame_time: FrameTimeInfo,
pub time: u64,
@@ -19,6 +20,7 @@ pub struct InputPreprocessorMessageHandler {
pub viewport_bounds: ViewportBounds,
}
+#[message_handler_data]
impl MessageHandler for InputPreprocessorMessageHandler {
fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque, data: InputPreprocessorMessageData) {
let InputPreprocessorMessageData { keyboard_platform } = data;
diff --git a/editor/src/messages/layout/layout_message_handler.rs b/editor/src/messages/layout/layout_message_handler.rs
index ee8e506ca8..7beec22fe9 100644
--- a/editor/src/messages/layout/layout_message_handler.rs
+++ b/editor/src/messages/layout/layout_message_handler.rs
@@ -6,7 +6,7 @@ use graphene_std::text::Font;
use graphene_std::vector::style::{FillChoice, GradientStops};
use serde_json::Value;
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone, Default, ExtractField)]
pub struct LayoutMessageHandler {
layouts: [Layout; LayoutTarget::LayoutTargetLength as usize],
}
@@ -342,6 +342,15 @@ impl LayoutMessageHandler {
}
}
+pub fn custom_data() -> MessageData {
+ // TODO: When is resolved and released,
+ // TODO: use to get
+ // TODO: the line number instead of hardcoding it to the magic number on the following line.
+ // TODO: Also, utilize the line number in the actual output, since it is currently unused.
+ MessageData::new(String::from("Function"), vec![(String::from("Fn(&MessageDiscriminant) -> Option"), 350)], file!())
+}
+
+#[message_handler_data(CustomData)]
impl Option> MessageHandler for LayoutMessageHandler {
fn process_message(&mut self, message: LayoutMessage, responses: &mut std::collections::VecDeque, action_input_mapping: F) {
match message {
diff --git a/editor/src/messages/message.rs b/editor/src/messages/message.rs
index 80323d9309..fbe4170023 100644
--- a/editor/src/messages/message.rs
+++ b/editor/src/messages/message.rs
@@ -45,3 +45,86 @@ impl specta::Type for MessageDiscriminant {
specta::DataType::Any
}
}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use std::io::Write;
+
+ #[test]
+ fn generate_message_tree() {
+ let result = Message::build_message_tree();
+ let mut file = std::fs::File::create("../hierarchical_message_system_tree.txt").unwrap();
+ file.write_all(format!("{} `{}`\n", result.name(), result.path()).as_bytes()).unwrap();
+ if let Some(variants) = result.variants() {
+ for (i, variant) in variants.iter().enumerate() {
+ let is_last = i == variants.len() - 1;
+ print_tree_node(variant, "", is_last, &mut file);
+ }
+ }
+ }
+
+ fn print_tree_node(tree: &DebugMessageTree, prefix: &str, is_last: bool, file: &mut std::fs::File) {
+ // Print the current node
+ let (branch, child_prefix) = if tree.has_message_handler_data_fields() || tree.has_message_handler_fields() {
+ ("├── ", format!("{}│ ", prefix))
+ } else {
+ if is_last {
+ ("└── ", format!("{} ", prefix))
+ } else {
+ ("├── ", format!("{}│ ", prefix))
+ }
+ };
+
+ if tree.path().is_empty() {
+ file.write_all(format!("{}{}{}\n", prefix, branch, tree.name()).as_bytes()).unwrap();
+ } else {
+ file.write_all(format!("{}{}{} `{}`\n", prefix, branch, tree.name(), tree.path()).as_bytes()).unwrap();
+ }
+
+ // Print children if any
+ if let Some(variants) = tree.variants() {
+ let len = variants.len();
+ for (i, variant) in variants.iter().enumerate() {
+ let is_last_child = i == len - 1;
+ print_tree_node(variant, &child_prefix, is_last_child, file);
+ }
+ }
+
+ // Print handler field if any
+ if let Some(data) = tree.message_handler_fields() {
+ let len = data.fields().len();
+ let (branch, child_prefix) = if tree.has_message_handler_data_fields() {
+ ("├── ", format!("{}│ ", prefix))
+ } else {
+ ("└── ", format!("{} ", prefix))
+ };
+ if data.path().is_empty() {
+ file.write_all(format!("{}{}{}\n", prefix, branch, data.name()).as_bytes()).unwrap();
+ } else {
+ file.write_all(format!("{}{}{} `{}`\n", prefix, branch, data.name(), data.path()).as_bytes()).unwrap();
+ }
+ for (i, field) in data.fields().iter().enumerate() {
+ let is_last_field = i == len - 1;
+ let branch = if is_last_field { "└── " } else { "├── " };
+
+ file.write_all(format!("{}{}{}\n", child_prefix, branch, field.0).as_bytes()).unwrap();
+ }
+ }
+
+ // Print data field if any
+ if let Some(data) = tree.message_handler_data_fields() {
+ let len = data.fields().len();
+ if data.path().is_empty() {
+ file.write_all(format!("{}{}{}\n", prefix, "└── ", data.name()).as_bytes()).unwrap();
+ } else {
+ file.write_all(format!("{}{}{} `{}`\n", prefix, "└── ", data.name(), data.path()).as_bytes()).unwrap();
+ }
+ for (i, field) in data.fields().iter().enumerate() {
+ let is_last_field = i == len - 1;
+ let branch = if is_last_field { "└── " } else { "├── " };
+ file.write_all(format!("{}{}{}\n", format!("{} ", prefix), branch, field.0).as_bytes()).unwrap();
+ }
+ }
+ }
+}
diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs
index 682053ec9f..b482625ee6 100644
--- a/editor/src/messages/portfolio/document/document_message_handler.rs
+++ b/editor/src/messages/portfolio/document/document_message_handler.rs
@@ -38,6 +38,7 @@ use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::style::ViewMode;
use std::time::Duration;
+#[derive(ExtractField)]
pub struct DocumentMessageData<'a> {
pub document_id: DocumentId,
pub ipp: &'a InputPreprocessorMessageHandler,
@@ -48,7 +49,7 @@ pub struct DocumentMessageData<'a> {
pub device_pixel_ratio: f64,
}
-#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
+#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)]
#[serde(default)]
pub struct DocumentMessageHandler {
// ======================
@@ -168,6 +169,7 @@ impl Default for DocumentMessageHandler {
}
}
+#[message_handler_data]
impl MessageHandler> for DocumentMessageHandler {
fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque, data: DocumentMessageData) {
let DocumentMessageData {
diff --git a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs
index 40653e4af4..931a15e3bf 100644
--- a/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs
+++ b/editor/src/messages/portfolio/document/graph_operation/graph_operation_message_handler.rs
@@ -21,17 +21,19 @@ struct ArtboardInfo {
merge_node: NodeId,
}
+#[derive(ExtractField)]
pub struct GraphOperationMessageData<'a> {
pub network_interface: &'a mut NodeNetworkInterface,
pub collapsed: &'a mut CollapsedLayers,
pub node_graph: &'a mut NodeGraphMessageHandler,
}
-#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)]
+#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize, ExtractField)]
pub struct GraphOperationMessageHandler {}
// GraphOperationMessageHandler always modified the document network. This is so changes to the layers panel will only affect the document network.
// For changes to the selected network, use NodeGraphMessageHandler. No NodeGraphMessage's should be added here, since they will affect the selected nested network.
+#[message_handler_data]
impl MessageHandler> for GraphOperationMessageHandler {
fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque, data: GraphOperationMessageData) {
let network_interface = data.network_interface;
diff --git a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs
index 929c850160..0d732edd04 100644
--- a/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs
+++ b/editor/src/messages/portfolio/document/navigation/navigation_message_handler.rs
@@ -13,6 +13,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use glam::{DAffine2, DVec2};
use graph_craft::document::NodeId;
+#[derive(ExtractField)]
pub struct NavigationMessageData<'a> {
pub network_interface: &'a mut NodeNetworkInterface,
pub breadcrumb_network_path: &'a [NodeId],
@@ -23,7 +24,7 @@ pub struct NavigationMessageData<'a> {
pub preferences: &'a PreferencesMessageHandler,
}
-#[derive(Debug, Clone, PartialEq, Default)]
+#[derive(Debug, Clone, PartialEq, Default, ExtractField)]
pub struct NavigationMessageHandler {
navigation_operation: NavigationOperation,
mouse_position: ViewportPosition,
@@ -31,6 +32,7 @@ pub struct NavigationMessageHandler {
abortable_pan_start: Option,
}
+#[message_handler_data]
impl MessageHandler> for NavigationMessageHandler {
fn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque, data: NavigationMessageData) {
let NavigationMessageData {
diff --git a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs
index 594f8125ee..3c85102098 100644
--- a/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs
+++ b/editor/src/messages/portfolio/document/node_graph/document_node_definitions.rs
@@ -946,7 +946,7 @@ fn static_nodes() -> Vec {
inputs: vec![
NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true),
NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false),
- NodeInput::value(TaggedValue::BrushCache(BrushCache::new_proto()), false),
+ NodeInput::value(TaggedValue::BrushCache(BrushCache::default()), false),
],
..Default::default()
},
diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs
index b3f1596cd2..828fb4a8ae 100644
--- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs
+++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs
@@ -27,7 +27,7 @@ use graphene_std::*;
use renderer::Quad;
use std::cmp::Ordering;
-#[derive(Debug)]
+#[derive(Debug, ExtractField)]
pub struct NodeGraphHandlerData<'a> {
pub network_interface: &'a mut NodeNetworkInterface,
pub selection_network_path: &'a [NodeId],
@@ -41,7 +41,7 @@ pub struct NodeGraphHandlerData<'a> {
pub preferences: &'a PreferencesMessageHandler,
}
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, ExtractField)]
pub struct NodeGraphMessageHandler {
// TODO: Remove network and move to NodeNetworkInterface
pub network: Vec,
@@ -92,6 +92,7 @@ pub struct NodeGraphMessageHandler {
}
/// NodeGraphMessageHandler always modifies the network which the selected nodes are in. No GraphOperationMessages should be added here, since those messages will always affect the document network.
+#[message_handler_data]
impl<'a> MessageHandler> for NodeGraphMessageHandler {
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque, data: NodeGraphHandlerData<'a>) {
let NodeGraphHandlerData {
diff --git a/editor/src/messages/portfolio/document/node_graph/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_properties.rs
index 807c6f9024..d0bae405ff 100644
--- a/editor/src/messages/portfolio/document/node_graph/node_properties.rs
+++ b/editor/src/messages/portfolio/document/node_graph/node_properties.rs
@@ -1280,6 +1280,7 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
let mut corner_radius_row_2 = vec![Separator::new(SeparatorType::Unrelated).widget_holder()];
corner_radius_row_2.push(TextLabel::new("").widget_holder());
+ add_blank_assist(&mut corner_radius_row_2);
let document_node = match get_document_node(node_id, context) {
Ok(document_node) => document_node,
@@ -1387,8 +1388,6 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
// Size Y
let size_y = number_widget(ParameterWidgetsInfo::new(node_id, HeightInput::INDEX, true, context), NumberInput::default());
- add_blank_assist(&mut corner_radius_row_2);
-
// Clamped
let clamped = bool_widget(ParameterWidgetsInfo::new(node_id, ClampedInput::INDEX, true, context), CheckboxInput::default());
@@ -1442,7 +1441,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
if let Some(field) = graphene_std::registry::NODE_METADATA
.lock()
.unwrap()
- .get(&proto_node_identifier)
+ .get(proto_node_identifier)
.and_then(|metadata| metadata.fields.get(input_index))
{
number_options = (field.number_min, field.number_max, field.number_hard_min, field.number_hard_max, field.number_mode_range);
diff --git a/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs b/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs
index 0db542da75..d4bb518e68 100644
--- a/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs
+++ b/editor/src/messages/portfolio/document/overlays/overlays_message_handler.rs
@@ -1,13 +1,14 @@
use super::utility_types::{OverlayProvider, OverlaysVisibilitySettings};
use crate::messages::prelude::*;
+#[derive(ExtractField)]
pub struct OverlaysMessageData<'a> {
pub visibility_settings: OverlaysVisibilitySettings,
pub ipp: &'a InputPreprocessorMessageHandler,
pub device_pixel_ratio: f64,
}
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone, Default, ExtractField)]
pub struct OverlaysMessageHandler {
pub overlay_providers: HashSet,
#[cfg(target_arch = "wasm32")]
@@ -16,6 +17,7 @@ pub struct OverlaysMessageHandler {
context: Option,
}
+#[message_handler_data]
impl MessageHandler> for OverlaysMessageHandler {
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque, data: OverlaysMessageData) {
let OverlaysMessageData { visibility_settings, ipp, .. } = data;
diff --git a/editor/src/messages/portfolio/document/overlays/utility_types.rs b/editor/src/messages/portfolio/document/overlays/utility_types.rs
index 6e25bb77b3..3d12ba4961 100644
--- a/editor/src/messages/portfolio/document/overlays/utility_types.rs
+++ b/editor/src/messages/portfolio/document/overlays/utility_types.rs
@@ -814,6 +814,36 @@ impl OverlayContext {
self.render_context.fill_text(text, 0., 0.).expect("Failed to draw the text at the calculated position");
self.render_context.reset_transform().expect("Failed to reset the render context transform");
}
+
+ pub fn translation_box(&mut self, translation: DVec2, quad: Quad, typed_string: Option) {
+ if translation.x.abs() > 1e-3 {
+ self.dashed_line(quad.top_left(), quad.top_right(), None, None, Some(2.), Some(2.), Some(0.5));
+
+ let width = match typed_string {
+ Some(ref typed_string) => typed_string,
+ None => &format!("{:.2}", translation.x).trim_end_matches('0').trim_end_matches('.').to_string(),
+ };
+ let x_transform = DAffine2::from_translation((quad.top_left() + quad.top_right()) / 2.);
+ self.text(width, COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]);
+ }
+
+ if translation.y.abs() > 1e-3 {
+ self.dashed_line(quad.top_left(), quad.bottom_left(), None, None, Some(2.), Some(2.), Some(0.5));
+
+ let height = match typed_string {
+ Some(ref typed_string) => typed_string,
+ None => &format!("{:.2}", translation.y).trim_end_matches('0').trim_end_matches('.').to_string(),
+ };
+ let y_transform = DAffine2::from_translation((quad.top_left() + quad.bottom_left()) / 2.);
+ let height_pivot = if translation.x > -1e-3 { Pivot::Start } else { Pivot::End };
+ self.text(height, COLOR_OVERLAY_BLUE, None, y_transform, 3., [height_pivot, Pivot::Middle]);
+ }
+
+ if translation.x.abs() > 1e-3 && translation.y.abs() > 1e-3 {
+ self.line(quad.top_right(), quad.bottom_right(), None, None);
+ self.line(quad.bottom_left(), quad.bottom_right(), None, None);
+ }
+ }
}
pub enum Pivot {
diff --git a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs
index a783b24aae..3ca9f350f3 100644
--- a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs
+++ b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs
@@ -4,9 +4,10 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions:
use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*;
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone, Default, ExtractField)]
pub struct PropertiesPanelMessageHandler {}
+#[message_handler_data]
impl MessageHandler)> for PropertiesPanelMessageHandler {
fn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque, (persistent_data, data): (&PersistentData, PropertiesPanelMessageHandlerData)) {
let PropertiesPanelMessageHandlerData {
diff --git a/editor/src/messages/portfolio/document_migration.rs b/editor/src/messages/portfolio/document_migration.rs
index e4db26d2ec..822801f04e 100644
--- a/editor/src/messages/portfolio/document_migration.rs
+++ b/editor/src/messages/portfolio/document_migration.rs
@@ -9,6 +9,7 @@ use bezier_rs::Subpath;
use glam::IVec2;
use graph_craft::document::DocumentNode;
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
+use graphene_std::ProtoNodeIdentifier;
use graphene_std::text::TypesettingConfig;
use graphene_std::uuid::NodeId;
use graphene_std::vector::style::{PaintOrder, StrokeAlign};
@@ -20,156 +21,440 @@ const TEXT_REPLACEMENTS: &[(&str, &str)] = &[
("graphene_core::vector::vector_nodes::SubpathSegmentLengthsNode", "graphene_core::vector::SubpathSegmentLengthsNode"),
];
-const REPLACEMENTS: &[(&str, &str)] = &[
- ("graphene_core::AddArtboardNode", "graphene_core::graphic_element::AppendArtboardNode"),
- ("graphene_core::ConstructArtboardNode", "graphene_core::graphic_element::ToArtboardNode"),
- ("graphene_core::ToGraphicElementNode", "graphene_core::graphic_element::ToElementNode"),
- ("graphene_core::ToGraphicGroupNode", "graphene_core::graphic_element::ToGroupNode"),
+pub struct NodeReplacement<'a> {
+ node: ProtoNodeIdentifier,
+ aliases: &'a [&'a str],
+}
+
+const NODE_REPLACEMENTS: &[NodeReplacement<'static>] = &[
+ // graphic element
+ NodeReplacement {
+ node: graphene_std::graphic_element::append_artboard::IDENTIFIER,
+ aliases: &["graphene_core::AddArtboardNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::graphic_element::to_artboard::IDENTIFIER,
+ aliases: &["graphene_core::ConstructArtboardNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::graphic_element::to_element::IDENTIFIER,
+ aliases: &["graphene_core::ToGraphicElementNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::graphic_element::to_group::IDENTIFIER,
+ aliases: &["graphene_core::ToGraphicGroupNode"],
+ },
// math_nodes
- ("graphene_core::ops::MathNode", "graphene_math_nodes::MathNode"),
- ("graphene_core::ops::AddNode", "graphene_math_nodes::AddNode"),
- ("graphene_core::ops::SubtractNode", "graphene_math_nodes::SubtractNode"),
- ("graphene_core::ops::MultiplyNode", "graphene_math_nodes::MultiplyNode"),
- ("graphene_core::ops::DivideNode", "graphene_math_nodes::DivideNode"),
- ("graphene_core::ops::ModuloNode", "graphene_math_nodes::ModuloNode"),
- ("graphene_core::ops::ExponentNode", "graphene_math_nodes::ExponentNode"),
- ("graphene_core::ops::RootNode", "graphene_math_nodes::RootNode"),
- ("graphene_core::ops::LogarithmNode", "graphene_math_nodes::LogarithmNode"),
- ("graphene_core::ops::SineNode", "graphene_math_nodes::SineNode"),
- ("graphene_core::ops::CosineNode", "graphene_math_nodes::CosineNode"),
- ("graphene_core::ops::TangentNode", "graphene_math_nodes::TangentNode"),
- ("graphene_core::ops::SineInverseNode", "graphene_math_nodes::SineInverseNode"),
- ("graphene_core::ops::CosineInverseNode", "graphene_math_nodes::CosineInverseNode"),
- ("graphene_core::ops::TangentInverseNode", "graphene_math_nodes::TangentInverseNode"),
- ("graphene_core::ops::RandomNode", "graphene_math_nodes::RandomNode"),
- ("graphene_core::ops::ToU32Node", "graphene_math_nodes::ToU32Node"),
- ("graphene_core::ops::ToU64Node", "graphene_math_nodes::ToU64Node"),
- ("graphene_core::ops::ToF64Node", "graphene_math_nodes::ToF64Node"),
- ("graphene_core::ops::RoundNode", "graphene_math_nodes::RoundNode"),
- ("graphene_core::ops::FloorNode", "graphene_math_nodes::FloorNode"),
- ("graphene_core::ops::CeilingNode", "graphene_math_nodes::CeilingNode"),
- ("graphene_core::ops::MinNode", "graphene_math_nodes::MinNode"),
- ("graphene_core::ops::MaxNode", "graphene_math_nodes::MaxNode"),
- ("graphene_core::ops::ClampNode", "graphene_math_nodes::ClampNode"),
- ("graphene_core::ops::EqualsNode", "graphene_math_nodes::EqualsNode"),
- ("graphene_core::ops::NotEqualsNode", "graphene_math_nodes::NotEqualsNode"),
- ("graphene_core::ops::LessThanNode", "graphene_math_nodes::LessThanNode"),
- ("graphene_core::ops::GreaterThanNode", "graphene_math_nodes::GreaterThanNode"),
- ("graphene_core::ops::LogicalOrNode", "graphene_math_nodes::LogicalOrNode"),
- ("graphene_core::ops::LogicalAndNode", "graphene_math_nodes::LogicalAndNode"),
- ("graphene_core::ops::LogicalNotNode", "graphene_math_nodes::LogicalNotNode"),
- ("graphene_core::ops::BoolValueNode", "graphene_math_nodes::BoolValueNode"),
- ("graphene_core::ops::NumberValueNode", "graphene_math_nodes::NumberValueNode"),
- ("graphene_core::ops::PercentageValueNode", "graphene_math_nodes::PercentageValueNode"),
- ("graphene_core::ops::CoordinateValueNode", "graphene_math_nodes::CoordinateValueNode"),
- ("graphene_core::ops::ConstructVector2", "graphene_math_nodes::CoordinateValueNode"),
- ("graphene_core::ops::Vector2ValueNode", "graphene_math_nodes::CoordinateValueNode"),
- ("graphene_core::ops::ColorValueNode", "graphene_math_nodes::ColorValueNode"),
- ("graphene_core::ops::GradientValueNode", "graphene_math_nodes::GradientValueNode"),
- ("graphene_core::ops::SampleGradientNode", "graphene_math_nodes::SampleGradientNode"),
- ("graphene_core::ops::StringValueNode", "graphene_math_nodes::StringValueNode"),
- ("graphene_core::ops::DotProductNode", "graphene_math_nodes::DotProductNode"),
+ NodeReplacement {
+ node: graphene_std::math_nodes::math::IDENTIFIER,
+ aliases: &["graphene_core::ops::MathNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::add::IDENTIFIER,
+ aliases: &["graphene_core::ops::AddNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::subtract::IDENTIFIER,
+ aliases: &["graphene_core::ops::SubtractNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::multiply::IDENTIFIER,
+ aliases: &["graphene_core::ops::MultiplyNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::divide::IDENTIFIER,
+ aliases: &["graphene_core::ops::DivideNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::modulo::IDENTIFIER,
+ aliases: &["graphene_core::ops::ModuloNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::exponent::IDENTIFIER,
+ aliases: &["graphene_core::ops::ExponentNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::root::IDENTIFIER,
+ aliases: &["graphene_core::ops::RootNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::logarithm::IDENTIFIER,
+ aliases: &["graphene_core::ops::LogarithmNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::sine::IDENTIFIER,
+ aliases: &["graphene_core::ops::SineNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::cosine::IDENTIFIER,
+ aliases: &["graphene_core::ops::CosineNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::tangent::IDENTIFIER,
+ aliases: &["graphene_core::ops::TangentNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::sine_inverse::IDENTIFIER,
+ aliases: &["graphene_core::ops::SineInverseNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::cosine_inverse::IDENTIFIER,
+ aliases: &["graphene_core::ops::CosineInverseNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::tangent_inverse::IDENTIFIER,
+ aliases: &["graphene_core::ops::TangentInverseNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::random::IDENTIFIER,
+ aliases: &["graphene_core::ops::RandomNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::to_u_32::IDENTIFIER,
+ aliases: &["graphene_core::ops::ToU32Node"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::to_u_64::IDENTIFIER,
+ aliases: &["graphene_core::ops::ToU64Node"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::to_f_64::IDENTIFIER,
+ aliases: &["graphene_core::ops::ToF64Node"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::round::IDENTIFIER,
+ aliases: &["graphene_core::ops::RoundNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::floor::IDENTIFIER,
+ aliases: &["graphene_core::ops::FloorNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::ceiling::IDENTIFIER,
+ aliases: &["graphene_core::ops::CeilingNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::min::IDENTIFIER,
+ aliases: &["graphene_core::ops::MinNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::max::IDENTIFIER,
+ aliases: &["graphene_core::ops::MaxNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::clamp::IDENTIFIER,
+ aliases: &["graphene_core::ops::ClampNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::equals::IDENTIFIER,
+ aliases: &["graphene_core::ops::EqualsNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::not_equals::IDENTIFIER,
+ aliases: &["graphene_core::ops::NotEqualsNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::less_than::IDENTIFIER,
+ aliases: &["graphene_core::ops::LessThanNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::greater_than::IDENTIFIER,
+ aliases: &["graphene_core::ops::GreaterThanNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::logical_or::IDENTIFIER,
+ aliases: &["graphene_core::ops::LogicalOrNode", "graphene_core::ops::LogicAndNode", "graphene_core::logic::LogicAndNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::logical_and::IDENTIFIER,
+ aliases: &["graphene_core::ops::LogicalAndNode", "graphene_core::ops::LogicNotNode", "graphene_core::logic::LogicNotNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::logical_not::IDENTIFIER,
+ aliases: &["graphene_core::ops::LogicalNotNode", "graphene_core::ops::LogicOrNode", "graphene_core::logic::LogicOrNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::bool_value::IDENTIFIER,
+ aliases: &["graphene_core::ops::BoolValueNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::number_value::IDENTIFIER,
+ aliases: &["graphene_core::ops::NumberValueNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::percentage_value::IDENTIFIER,
+ aliases: &["graphene_core::ops::PercentageValueNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::coordinate_value::IDENTIFIER,
+ aliases: &[
+ "graphene_core::ops::CoordinateValueNode",
+ "graphene_core::ops::ConstructVector2",
+ "graphene_core::ops::Vector2ValueNode",
+ ],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::color_value::IDENTIFIER,
+ aliases: &["graphene_core::ops::ColorValueNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::gradient_value::IDENTIFIER,
+ aliases: &["graphene_core::ops::GradientValueNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::sample_gradient::IDENTIFIER,
+ aliases: &["graphene_core::ops::SampleGradientNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::string_value::IDENTIFIER,
+ aliases: &["graphene_core::ops::StringValueNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::math_nodes::dot_product::IDENTIFIER,
+ aliases: &["graphene_core::ops::DotProductNode"],
+ },
// debug
- ("graphene_core::ops::SizeOfNode", "graphene_core::debug::SizeOfNode"),
- ("graphene_core::ops::SomeNode", "graphene_core::debug::SomeNode"),
- ("graphene_core::ops::UnwrapNode", "graphene_core::debug::UnwrapNode"),
- ("graphene_core::ops::CloneNode", "graphene_core::debug::CloneNode"),
+ NodeReplacement {
+ node: graphene_std::debug::size_of::IDENTIFIER,
+ aliases: &["graphene_core::ops::SizeOfNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::debug::some::IDENTIFIER,
+ aliases: &["graphene_core::ops::SomeNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::debug::unwrap::IDENTIFIER,
+ aliases: &["graphene_core::ops::UnwrapNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::debug::clone::IDENTIFIER,
+ aliases: &["graphene_core::ops::CloneNode"],
+ },
// ???
- ("graphene_core::ops::ExtractXyNode", "graphene_core::extract_xy::ExtractXyNode"),
- ("graphene_core::logic::LogicAndNode", "graphene_core::ops::LogicAndNode"),
- ("graphene_core::logic::LogicNotNode", "graphene_core::ops::LogicNotNode"),
- ("graphene_core::logic::LogicOrNode", "graphene_core::ops::LogicOrNode"),
- ("graphene_core::raster::BlendModeNode", "graphene_core::blending_nodes::BlendModeNode"),
- ("graphene_core::raster::OpacityNode", "graphene_core::blending_nodes::OpacityNode"),
- ("graphene_core::raster::BlendingNode", "graphene_core::blending_nodes::BlendingNode"),
- ("graphene_core::vector::GenerateHandlesNode", "graphene_core::vector::AutoTangentsNode"),
- ("graphene_core::vector::RemoveHandlesNode", "graphene_core::vector::AutoTangentsNode"),
+ NodeReplacement {
+ node: graphene_std::extract_xy::extract_xy::IDENTIFIER,
+ aliases: &["graphene_core::ops::ExtractXyNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::blending_nodes::blend_mode::IDENTIFIER,
+ aliases: &["graphene_core::raster::BlendModeNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::blending_nodes::opacity::IDENTIFIER,
+ aliases: &["graphene_core::raster::OpacityNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::blending_nodes::blending::IDENTIFIER,
+ aliases: &["graphene_core::raster::BlendingNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::vector::auto_tangents::IDENTIFIER,
+ aliases: &["graphene_core::vector::GenerateHandlesNode", "graphene_core::vector::RemoveHandlesNode"],
+ },
// raster::adjustments
- ("graphene_core::raster::adjustments::LuminanceNode", "graphene_raster_nodes::adjustments::LuminanceNode"),
- ("graphene_core::raster::LuminanceNode", "graphene_raster_nodes::adjustments::LuminanceNode"),
- ("graphene_core::raster::adjustments::ExtractChannelNode", "graphene_raster_nodes::adjustments::ExtractChannelNode"),
- ("graphene_core::raster::ExtractChannelNode", "graphene_raster_nodes::adjustments::ExtractChannelNode"),
- ("graphene_core::raster::adjustments::MakeOpaqueNode", "graphene_raster_nodes::adjustments::MakeOpaqueNode"),
- ("graphene_core::raster::ExtractOpaqueNode", "graphene_raster_nodes::adjustments::MakeOpaqueNode"),
- (
- "graphene_core::raster::adjustments::BrightnessContrastNode",
- "graphene_raster_nodes::adjustments::BrightnessContrastNode",
- ),
- ("graphene_core::raster::adjustments::LevelsNode", "graphene_raster_nodes::adjustments::LevelsNode"),
- ("graphene_core::raster::LevelsNode", "graphene_raster_nodes::adjustments::LevelsNode"),
- ("graphene_core::raster::adjustments::BlackAndWhiteNode", "graphene_raster_nodes::adjustments::BlackAndWhiteNode"),
- ("graphene_core::raster::BlackAndWhiteNode", "graphene_raster_nodes::adjustments::BlackAndWhiteNode"),
- ("graphene_core::raster::adjustments::HueSaturationNode", "graphene_raster_nodes::adjustments::HueSaturationNode"),
- ("graphene_core::raster::HueSaturationNode", "graphene_raster_nodes::adjustments::HueSaturationNode"),
- ("graphene_core::raster::adjustments::InvertNode", "graphene_raster_nodes::adjustments::InvertNode"),
- ("graphene_core::raster::InvertNode", "graphene_raster_nodes::adjustments::InvertNode"),
- ("graphene_core::raster::InvertRGBNode", "graphene_raster_nodes::adjustments::InvertNode"),
- ("graphene_core::raster::adjustments::ThresholdNode", "graphene_raster_nodes::adjustments::ThresholdNode"),
- ("graphene_core::raster::ThresholdNode", "graphene_raster_nodes::adjustments::ThresholdNode"),
- ("graphene_core::raster::adjustments::BlendNode", "graphene_raster_nodes::adjustments::BlendNode"),
- ("graphene_core::raster::BlendNode", "graphene_raster_nodes::adjustments::BlendNode"),
- ("graphene_core::raster::BlendColorPairNode", "graphene_raster_nodes::adjustments::BlendColorPairNode"),
- ("graphene_core::raster::adjustments::BlendColorsNode", "graphene_raster_nodes::adjustments::BlendColorsNode"),
- ("graphene_core::raster::BlendColorsNode", "graphene_raster_nodes::adjustments::BlendColorsNode"),
- ("graphene_core::raster::adjustments::GradientMapNode", "graphene_raster_nodes::adjustments::GradientMapNode"),
- ("graphene_core::raster::GradientMapNode", "graphene_raster_nodes::adjustments::GradientMapNode"),
- ("graphene_core::raster::adjustments::VibranceNode", "graphene_raster_nodes::adjustments::VibranceNode"),
- ("graphene_core::raster::VibranceNode", "graphene_raster_nodes::adjustments::VibranceNode"),
- ("graphene_core::raster::adjustments::ChannelMixerNode", "graphene_raster_nodes::adjustments::ChannelMixerNode"),
- ("graphene_core::raster::ChannelMixerNode", "graphene_raster_nodes::adjustments::ChannelMixerNode"),
- ("graphene_core::raster::adjustments::SelectiveColorNode", "graphene_raster_nodes::adjustments::SelectiveColorNode"),
- ("graphene_core::raster::adjustments::PosterizeNode", "graphene_raster_nodes::adjustments::PosterizeNode"),
- ("graphene_core::raster::PosterizeNode", "graphene_raster_nodes::adjustments::PosterizeNode"),
- ("graphene_core::raster::adjustments::ExposureNode", "graphene_raster_nodes::adjustments::ExposureNode"),
- ("graphene_core::raster::ExposureNode", "graphene_raster_nodes::adjustments::ExposureNode"),
- ("graphene_core::raster::adjustments::ColorOverlayNode", "graphene_raster_nodes::adjustments::ColorOverlayNode"),
- ("graphene_raster_nodes::generate_curves::ColorOverlayNode", "graphene_raster_nodes::adjustments::ColorOverlayNode"),
+ NodeReplacement {
+ node: graphene_std::raster_nodes::adjustments::luminance::IDENTIFIER,
+ aliases: &["graphene_core::raster::adjustments::LuminanceNode", "graphene_core::raster::LuminanceNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::adjustments::extract_channel::IDENTIFIER,
+ aliases: &["graphene_core::raster::adjustments::ExtractChannelNode", "graphene_core::raster::ExtractChannelNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::adjustments::make_opaque::IDENTIFIER,
+ aliases: &["graphene_core::raster::adjustments::MakeOpaqueNode", "graphene_core::raster::ExtractOpaqueNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::adjustments::brightness_contrast::IDENTIFIER,
+ aliases: &["graphene_core::raster::adjustments::BrightnessContrastNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::adjustments::levels::IDENTIFIER,
+ aliases: &["graphene_core::raster::adjustments::LevelsNode", "graphene_core::raster::LevelsNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::adjustments::black_and_white::IDENTIFIER,
+ aliases: &["graphene_core::raster::adjustments::BlackAndWhiteNode", "graphene_core::raster::BlackAndWhiteNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::adjustments::hue_saturation::IDENTIFIER,
+ aliases: &["graphene_core::raster::adjustments::HueSaturationNode", "graphene_core::raster::HueSaturationNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::adjustments::invert::IDENTIFIER,
+ aliases: &[
+ "graphene_core::raster::adjustments::InvertNode",
+ "graphene_core::raster::InvertNode",
+ "graphene_core::raster::InvertRGBNode",
+ ],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::adjustments::threshold::IDENTIFIER,
+ aliases: &["graphene_core::raster::adjustments::ThresholdNode", "graphene_core::raster::ThresholdNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::adjustments::blend::IDENTIFIER,
+ aliases: &["graphene_core::raster::adjustments::BlendNode", "graphene_core::raster::BlendNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::adjustments::blend_color_pair::IDENTIFIER,
+ aliases: &["graphene_core::raster::BlendColorPairNode"],
+ },
+ // this node doesn't seem to exist?
+ // (graphene_std::raster_nodes::adjustments::blend_color::IDENTIFIER, &["graphene_core::raster::adjustments::BlendColorsNode","graphene_core::raster::BlendColorsNode"]),
+ NodeReplacement {
+ node: graphene_std::raster_nodes::adjustments::gradient_map::IDENTIFIER,
+ aliases: &["graphene_core::raster::adjustments::GradientMapNode", "graphene_core::raster::GradientMapNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::adjustments::vibrance::IDENTIFIER,
+ aliases: &["graphene_core::raster::adjustments::VibranceNode", "graphene_core::raster::VibranceNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::adjustments::channel_mixer::IDENTIFIER,
+ aliases: &["graphene_core::raster::adjustments::ChannelMixerNode", "graphene_core::raster::ChannelMixerNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::adjustments::selective_color::IDENTIFIER,
+ aliases: &["graphene_core::raster::adjustments::SelectiveColorNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::adjustments::posterize::IDENTIFIER,
+ aliases: &["graphene_core::raster::adjustments::PosterizeNode", "graphene_core::raster::PosterizeNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::adjustments::exposure::IDENTIFIER,
+ aliases: &["graphene_core::raster::adjustments::ExposureNode", "graphene_core::raster::ExposureNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::adjustments::color_overlay::IDENTIFIER,
+ aliases: &["graphene_core::raster::adjustments::ColorOverlayNode", "graphene_raster_nodes::generate_curves::ColorOverlayNode"],
+ },
// raster
- ("graphene_core::raster::adjustments::GenerateCurvesNode", "graphene_raster_nodes::generate_curves::GenerateCurvesNode"),
- ("graphene_std::dehaze::DehazeNode", "graphene_raster_nodes::dehaze::DehazeNode"),
- ("graphene_std::filter::BlurNode", "graphene_raster_nodes::filter::BlurNode"),
- (
- "graphene_std::image_color_palette::ImageColorPaletteNode",
- "graphene_raster_nodes::image_color_palette::ImageColorPaletteNode",
- ),
- ("graphene_std::raster::SampleImageNode", "graphene_raster_nodes::std_nodes::SampleImageNode"),
- ("graphene_std::raster::CombineChannelsNode", "graphene_raster_nodes::std_nodes::CombineChannelsNode"),
- ("graphene_std::raster::MaskNode", "graphene_raster_nodes::std_nodes::MaskNode"),
- ("graphene_std::raster::ExtendImageToBoundsNode", "graphene_raster_nodes::std_nodes::ExtendImageToBoundsNode"),
- ("graphene_std::raster::EmptyImageNode", "graphene_raster_nodes::std_nodes::EmptyImageNode"),
- ("graphene_std::raster::ImageValueNode", "graphene_raster_nodes::std_nodes::ImageValueNode"),
- ("graphene_std::raster::NoisePatternNode", "graphene_raster_nodes::std_nodes::NoisePatternNode"),
- ("graphene_std::raster::MandelbrotNode", "graphene_raster_nodes::std_nodes::MandelbrotNode"),
+ NodeReplacement {
+ node: graphene_std::raster_nodes::generate_curves::generate_curves::IDENTIFIER,
+ aliases: &["graphene_core::raster::adjustments::GenerateCurvesNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::dehaze::dehaze::IDENTIFIER,
+ aliases: &["graphene_std::dehaze::DehazeNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::filter::blur::IDENTIFIER,
+ aliases: &["graphene_std::filter::BlurNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::image_color_palette::image_color_palette::IDENTIFIER,
+ aliases: &["graphene_std::image_color_palette::ImageColorPaletteNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::std_nodes::sample_image::IDENTIFIER,
+ aliases: &["graphene_std::raster::SampleImageNode", "graphene_std::raster::SampleNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::std_nodes::combine_channels::IDENTIFIER,
+ aliases: &["graphene_std::raster::CombineChannelsNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::std_nodes::mask::IDENTIFIER,
+ aliases: &["graphene_std::raster::MaskNode", "graphene_std::raster::MaskImageNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::std_nodes::extend_image_to_bounds::IDENTIFIER,
+ aliases: &["graphene_std::raster::ExtendImageToBoundsNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::std_nodes::empty_image::IDENTIFIER,
+ aliases: &["graphene_std::raster::EmptyImageNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::std_nodes::image_value::IDENTIFIER,
+ aliases: &["graphene_std::raster::ImageValueNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::std_nodes::noise_pattern::IDENTIFIER,
+ aliases: &["graphene_std::raster::NoisePatternNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::raster_nodes::std_nodes::mandelbrot::IDENTIFIER,
+ aliases: &["graphene_std::raster::MandelbrotNode"],
+ },
// text
- ("graphene_core::text::TextGeneratorNode", "graphene_core::text::TextNode"),
+ NodeReplacement {
+ node: graphene_std::text::text::IDENTIFIER,
+ aliases: &["graphene_core::text::TextGeneratorNode"],
+ },
// transform
- ("graphene_core::transform::SetTransformNode", "graphene_core::transform_nodes::ReplaceTransformNode"),
- ("graphene_core::transform::ReplaceTransformNode", "graphene_core::transform_nodes::ReplaceTransformNode"),
- ("graphene_core::transform::TransformNode", "graphene_core::transform_nodes::TransformNode"),
- ("graphene_core::transform::BoundlessFootprintNode", "graphene_core::transform_nodes::BoundlessFootprintNode"),
- ("graphene_core::transform::FreezeRealTimeNode", "graphene_core::transform_nodes::FreezeRealTimeNode"),
+ NodeReplacement {
+ node: graphene_std::transform_nodes::replace_transform::IDENTIFIER,
+ aliases: &["graphene_core::transform::SetTransformNode", "graphene_core::transform::ReplaceTransformNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::transform_nodes::transform::IDENTIFIER,
+ aliases: &["graphene_core::transform::TransformNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::transform_nodes::boundless_footprint::IDENTIFIER,
+ aliases: &["graphene_core::transform::BoundlessFootprintNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::transform_nodes::freeze_real_time::IDENTIFIER,
+ aliases: &["graphene_core::transform::FreezeRealTimeNode"],
+ },
// ???
- ("graphene_core::vector::SplinesFromPointsNode", "graphene_core::vector::SplineNode"),
- ("graphene_core::vector::generator_nodes::EllipseGenerator", "graphene_core::vector::generator_nodes::EllipseNode"),
- ("graphene_core::vector::generator_nodes::LineGenerator", "graphene_core::vector::generator_nodes::LineNode"),
- ("graphene_core::vector::generator_nodes::RectangleGenerator", "graphene_core::vector::generator_nodes::RectangleNode"),
- (
- "graphene_core::vector::generator_nodes::RegularPolygonGenerator",
- "graphene_core::vector::generator_nodes::RegularPolygonNode",
- ),
- ("graphene_core::vector::generator_nodes::StarGenerator", "graphene_core::vector::generator_nodes::StarNode"),
- ("graphene_std::executor::BlendGpuImageNode", "graphene_std::gpu_nodes::BlendGpuImageNode"),
- ("graphene_std::raster::SampleNode", "graphene_std::raster::SampleImageNode"),
- ("graphene_core::transform::CullNode", "graphene_core::ops::IdentityNode"),
- ("graphene_std::raster::MaskImageNode", "graphene_std::raster::MaskNode"),
- ("graphene_core::vector::FlattenVectorElementsNode", "graphene_core::vector::FlattenPathNode"),
- ("graphene_std::vector::BooleanOperationNode", "graphene_path_bool::BooleanOperationNode"),
+ NodeReplacement {
+ node: graphene_std::vector::spline::IDENTIFIER,
+ aliases: &["graphene_core::vector::SplinesFromPointsNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::vector::generator_nodes::ellipse::IDENTIFIER,
+ aliases: &["graphene_core::vector::generator_nodes::EllipseGenerator"],
+ },
+ NodeReplacement {
+ node: graphene_std::vector::generator_nodes::line::IDENTIFIER,
+ aliases: &["graphene_core::vector::generator_nodes::LineGenerator"],
+ },
+ NodeReplacement {
+ node: graphene_std::vector::generator_nodes::rectangle::IDENTIFIER,
+ aliases: &["graphene_core::vector::generator_nodes::RectangleGenerator"],
+ },
+ NodeReplacement {
+ node: graphene_std::vector::generator_nodes::regular_polygon::IDENTIFIER,
+ aliases: &["graphene_core::vector::generator_nodes::RegularPolygonGenerator"],
+ },
+ NodeReplacement {
+ node: graphene_std::vector::generator_nodes::star::IDENTIFIER,
+ aliases: &["graphene_core::vector::generator_nodes::StarGenerator"],
+ },
+ NodeReplacement {
+ node: graphene_std::ops::identity::IDENTIFIER,
+ aliases: &["graphene_core::transform::CullNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::vector::flatten_path::IDENTIFIER,
+ aliases: &["graphene_core::vector::FlattenVectorElementsNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::path_bool::boolean_operation::IDENTIFIER,
+ aliases: &["graphene_std::vector::BooleanOperationNode"],
+ },
// brush
- ("graphene_std::brush::BrushStampGeneratorNode", "graphene_brush::brush::BrushStampGeneratorNode"),
- ("graphene_std::brush::BlitNode", "graphene_brush::brush::BlitNode"),
- ("graphene_std::brush::BrushNode", "graphene_brush::brush::BrushNode"),
+ NodeReplacement {
+ node: graphene_std::brush::brush::brush_stamp_generator::IDENTIFIER,
+ aliases: &["graphene_std::brush::BrushStampGeneratorNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::brush::brush::blit::IDENTIFIER,
+ aliases: &["graphene_std::brush::BlitNode"],
+ },
+ NodeReplacement {
+ node: graphene_std::brush::brush::brush::IDENTIFIER,
+ aliases: &["graphene_std::brush::BrushNode"],
+ },
];
+const REPLACEMENTS: &[(&str, &str)] = &[];
+
pub fn document_migration_string_preprocessing(document_serialized_content: String) -> String {
TEXT_REPLACEMENTS
.iter()
@@ -195,17 +480,26 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
let network = document.network_interface.document_network().clone();
- // Apply string replacements to each node
+ // Apply string and node replacements to each node
+ let mut replacements = HashMap::<&str, ProtoNodeIdentifier>::new();
+ Iterator::chain(
+ NODE_REPLACEMENTS.iter().flat_map(|NodeReplacement { node, aliases }| aliases.iter().map(|old| (*old, node.clone()))),
+ REPLACEMENTS.iter().map(|(old, new)| (*old, ProtoNodeIdentifier::new(new))),
+ )
+ .for_each(|(old, new)| {
+ if replacements.insert(old, new).is_some() {
+ panic!("Duplicate old name `{old}`");
+ }
+ });
+
for (node_id, node, network_path) in network.recursive_nodes() {
if let DocumentNodeImplementation::ProtoNode(protonode_id) = &node.implementation {
- for (old, new) in REPLACEMENTS {
- let node_path_without_type_args = protonode_id.name.split('<').next();
+ let node_path_without_type_args = protonode_id.name.split('<').next();
+ if let Some(new) = node_path_without_type_args.and_then(|node_path| replacements.get(node_path)) {
let mut default_template = NodeTemplate::default();
- default_template.document_node.implementation = DocumentNodeImplementation::ProtoNode(new.to_string().into());
- if node_path_without_type_args == Some(old) {
- document.network_interface.replace_implementation(node_id, &network_path, &mut default_template);
- document.network_interface.set_manual_compostion(node_id, &network_path, Some(graph_craft::Type::Generic("T".into())));
- }
+ default_template.document_node.implementation = DocumentNodeImplementation::ProtoNode(new.clone());
+ document.network_interface.replace_implementation(node_id, &network_path, &mut default_template);
+ document.network_interface.set_manual_compostion(node_id, &network_path, Some(graph_craft::Type::Generic("T".into())));
}
}
}
@@ -691,3 +985,20 @@ fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId],
Some(())
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_no_duplicate_node_replacements() {
+ let mut hashmap = HashMap::::new();
+ NODE_REPLACEMENTS.iter().for_each(|node| {
+ *hashmap.entry(node.node.clone()).or_default() += 1;
+ });
+ let duplicates = hashmap.iter().filter(|(_, count)| **count > 1).map(|(node, _)| &node.name).collect::>();
+ if duplicates.len() > 0 {
+ panic!("Duplicate entries in `NODE_REPLACEMENTS`: {:?}", duplicates);
+ }
+ }
+}
diff --git a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs
index 50cfa632ac..26a9a739f3 100644
--- a/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs
+++ b/editor/src/messages/portfolio/menu_bar/menu_bar_message_handler.rs
@@ -6,7 +6,7 @@ use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate,
use crate::messages::prelude::*;
use graphene_std::path_bool::BooleanOperation;
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone, Default, ExtractField)]
pub struct MenuBarMessageHandler {
pub has_active_document: bool,
pub canvas_tilted: bool,
@@ -21,6 +21,7 @@ pub struct MenuBarMessageHandler {
pub reset_node_definitions_on_open: bool,
}
+#[message_handler_data]
impl MessageHandler for MenuBarMessageHandler {
fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque, _data: ()) {
match message {
diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs
index a6d9da338e..195768bd4b 100644
--- a/editor/src/messages/portfolio/portfolio_message_handler.rs
+++ b/editor/src/messages/portfolio/portfolio_message_handler.rs
@@ -25,6 +25,7 @@ use graphene_std::renderer::Quad;
use graphene_std::text::Font;
use std::vec;
+#[derive(ExtractField)]
pub struct PortfolioMessageData<'a> {
pub ipp: &'a InputPreprocessorMessageHandler,
pub preferences: &'a PreferencesMessageHandler,
@@ -35,7 +36,7 @@ pub struct PortfolioMessageData<'a> {
pub animation: &'a AnimationMessageHandler,
}
-#[derive(Debug, Default)]
+#[derive(Debug, Default, ExtractField)]
pub struct PortfolioMessageHandler {
menu_bar_message_handler: MenuBarMessageHandler,
pub documents: HashMap,
@@ -52,6 +53,7 @@ pub struct PortfolioMessageHandler {
pub reset_node_definitions_on_open: bool,
}
+#[message_handler_data]
impl MessageHandler> for PortfolioMessageHandler {
fn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque, data: PortfolioMessageData) {
let PortfolioMessageData {
diff --git a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs
index 378c8b1b52..72c44c5975 100644
--- a/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs
+++ b/editor/src/messages/portfolio/spreadsheet/spreadsheet_message_handler.rs
@@ -15,7 +15,7 @@ use std::any::Any;
use std::sync::Arc;
/// The spreadsheet UI allows for instance data to be previewed.
-#[derive(Default, Debug, Clone)]
+#[derive(Default, Debug, Clone, ExtractField)]
pub struct SpreadsheetMessageHandler {
/// Sets whether or not the spreadsheet is drawn.
pub spreadsheet_view_open: bool,
@@ -25,6 +25,7 @@ pub struct SpreadsheetMessageHandler {
viewing_vector_data_domain: VectorDataDomain,
}
+#[message_handler_data]
impl MessageHandler for SpreadsheetMessageHandler {
fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque, _data: ()) {
match message {
diff --git a/editor/src/messages/preferences/preferences_message_handler.rs b/editor/src/messages/preferences/preferences_message_handler.rs
index b52e4e49a8..1e52233fe3 100644
--- a/editor/src/messages/preferences/preferences_message_handler.rs
+++ b/editor/src/messages/preferences/preferences_message_handler.rs
@@ -5,7 +5,7 @@ use crate::messages::preferences::SelectionMode;
use crate::messages::prelude::*;
use graph_craft::wasm_application_io::EditorPreferences;
-#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type)]
+#[derive(Debug, PartialEq, Clone, serde::Serialize, serde::Deserialize, specta::Type, ExtractField)]
pub struct PreferencesMessageHandler {
pub selection_mode: SelectionMode,
pub zoom_with_scroll: bool,
@@ -44,6 +44,7 @@ impl Default for PreferencesMessageHandler {
}
}
+#[message_handler_data]
impl MessageHandler for PreferencesMessageHandler {
fn process_message(&mut self, message: PreferencesMessage, responses: &mut VecDeque, _data: ()) {
match message {
diff --git a/editor/src/messages/prelude.rs b/editor/src/messages/prelude.rs
index d33b7c235c..7517c80f48 100644
--- a/editor/src/messages/prelude.rs
+++ b/editor/src/messages/prelude.rs
@@ -1,6 +1,6 @@
// Root
-pub use crate::utility_traits::{ActionList, AsMessage, MessageHandler, ToDiscriminant, TransitiveChild};
-
+pub use crate::utility_traits::{ActionList, AsMessage, HierarchicalTree, MessageHandler, ToDiscriminant, TransitiveChild};
+pub use crate::utility_types::{DebugMessageTree, MessageData};
// Message, MessageData, MessageDiscriminant, MessageHandler
pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler};
pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};
diff --git a/editor/src/messages/tool/common_functionality/shape_editor.rs b/editor/src/messages/tool/common_functionality/shape_editor.rs
index 581c89908f..f98e50f079 100644
--- a/editor/src/messages/tool/common_functionality/shape_editor.rs
+++ b/editor/src/messages/tool/common_functionality/shape_editor.rs
@@ -1079,7 +1079,7 @@ impl ShapeState {
let mut normalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize()));
- if normalized.is_none() {
+ if normalized.is_none() || handle_directions.iter().any(|&d| d.is_some_and(|d| d.length_squared() < f64::EPSILON * 1e5)) {
handle_directions = anchor_positions.map(|relative_anchor| relative_anchor.map(|relative_anchor| (relative_anchor - anchor) / 3.));
normalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize()))
}
diff --git a/editor/src/messages/tool/tool_message_handler.rs b/editor/src/messages/tool/tool_message_handler.rs
index e20d0989b1..6000301a18 100644
--- a/editor/src/messages/tool/tool_message_handler.rs
+++ b/editor/src/messages/tool/tool_message_handler.rs
@@ -12,6 +12,7 @@ use graphene_std::raster::color::Color;
const ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |context| DocumentMessage::DrawArtboardOverlays(context).into();
+#[derive(ExtractField)]
pub struct ToolMessageData<'a> {
pub document_id: DocumentId,
pub document: &'a mut DocumentMessageHandler,
@@ -21,7 +22,7 @@ pub struct ToolMessageData<'a> {
pub preferences: &'a PreferencesMessageHandler,
}
-#[derive(Debug, Default)]
+#[derive(Debug, Default, ExtractField)]
pub struct ToolMessageHandler {
pub tool_state: ToolFsmState,
pub transform_layer_handler: TransformLayerMessageHandler,
@@ -29,6 +30,7 @@ pub struct ToolMessageHandler {
pub tool_is_active: bool,
}
+#[message_handler_data]
impl MessageHandler> for ToolMessageHandler {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, data: ToolMessageData) {
let ToolMessageData {
diff --git a/editor/src/messages/tool/tool_messages/artboard_tool.rs b/editor/src/messages/tool/tool_messages/artboard_tool.rs
index 0362b7f148..e14058c32c 100644
--- a/editor/src/messages/tool/tool_messages/artboard_tool.rs
+++ b/editor/src/messages/tool/tool_messages/artboard_tool.rs
@@ -13,7 +13,7 @@ use crate::messages::tool::common_functionality::transformation_cage::*;
use graph_craft::document::NodeId;
use graphene_std::renderer::Quad;
-#[derive(Default)]
+#[derive(Default, ExtractField)]
pub struct ArtboardTool {
fsm_state: ArtboardToolFsmState,
data: ArtboardToolData,
@@ -48,6 +48,7 @@ impl ToolMetadata for ArtboardTool {
}
}
+#[message_handler_data]
impl<'a> MessageHandler> for ArtboardTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, false);
diff --git a/editor/src/messages/tool/tool_messages/brush_tool.rs b/editor/src/messages/tool/tool_messages/brush_tool.rs
index b468603989..a625bdb9e6 100644
--- a/editor/src/messages/tool/tool_messages/brush_tool.rs
+++ b/editor/src/messages/tool/tool_messages/brush_tool.rs
@@ -1,6 +1,6 @@
use super::tool_prelude::*;
use crate::consts::DEFAULT_BRUSH_SIZE;
-use crate::messages::portfolio::document::graph_operation::transform_utils::{get_current_normalized_pivot, get_current_transform};
+use crate::messages::portfolio::document::graph_operation::transform_utils::get_current_transform;
use crate::messages::portfolio::document::node_graph::document_node_definitions::resolve_document_node_type;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::FlowType;
@@ -20,7 +20,7 @@ pub enum DrawMode {
Restore,
}
-#[derive(Default)]
+#[derive(Default, ExtractField)]
pub struct BrushTool {
fsm_state: BrushToolFsmState,
data: BrushToolData,
@@ -185,6 +185,7 @@ impl LayoutHolder for BrushTool {
}
}
+#[message_handler_data]
impl<'a> MessageHandler> for BrushTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) {
let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message else {
@@ -286,9 +287,7 @@ impl BrushToolData {
}
if *reference == Some("Transform".to_string()) {
- let upstream = document.metadata().upstream_transform(node_id);
- let pivot = DAffine2::from_translation(upstream.transform_point2(get_current_normalized_pivot(&node.inputs)));
- self.transform = pivot * get_current_transform(&node.inputs) * pivot.inverse() * self.transform;
+ self.transform = get_current_transform(&node.inputs) * self.transform;
}
}
diff --git a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs
index 3b3f324972..ce4b09d8e9 100644
--- a/editor/src/messages/tool/tool_messages/eyedropper_tool.rs
+++ b/editor/src/messages/tool/tool_messages/eyedropper_tool.rs
@@ -1,7 +1,7 @@
use super::tool_prelude::*;
use crate::messages::tool::utility_types::DocumentToolData;
-#[derive(Default)]
+#[derive(Default, ExtractField)]
pub struct EyedropperTool {
fsm_state: EyedropperToolFsmState,
data: EyedropperToolData,
@@ -39,6 +39,7 @@ impl LayoutHolder for EyedropperTool {
}
}
+#[message_handler_data]
impl<'a> MessageHandler> for EyedropperTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true);
diff --git a/editor/src/messages/tool/tool_messages/fill_tool.rs b/editor/src/messages/tool/tool_messages/fill_tool.rs
index 5aeb063d87..94ad2b8c21 100644
--- a/editor/src/messages/tool/tool_messages/fill_tool.rs
+++ b/editor/src/messages/tool/tool_messages/fill_tool.rs
@@ -3,7 +3,7 @@ use crate::messages::portfolio::document::overlays::utility_types::OverlayContex
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
use graphene_std::vector::style::Fill;
-#[derive(Default)]
+#[derive(Default, ExtractField)]
pub struct FillTool {
fsm_state: FillToolFsmState,
}
@@ -41,6 +41,7 @@ impl LayoutHolder for FillTool {
}
}
+#[message_handler_data]
impl<'a> MessageHandler> for FillTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut (), tool_data, &(), responses, true);
diff --git a/editor/src/messages/tool/tool_messages/freehand_tool.rs b/editor/src/messages/tool/tool_messages/freehand_tool.rs
index e2abddef2a..fb1539a818 100644
--- a/editor/src/messages/tool/tool_messages/freehand_tool.rs
+++ b/editor/src/messages/tool/tool_messages/freehand_tool.rs
@@ -13,7 +13,7 @@ use graphene_std::Color;
use graphene_std::vector::VectorModificationType;
use graphene_std::vector::{PointId, SegmentId};
-#[derive(Default)]
+#[derive(Default, ExtractField)]
pub struct FreehandTool {
fsm_state: FreehandToolFsmState,
data: FreehandToolData,
@@ -116,6 +116,7 @@ impl LayoutHolder for FreehandTool {
}
}
+#[message_handler_data]
impl<'a> MessageHandler> for FreehandTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) {
let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message else {
diff --git a/editor/src/messages/tool/tool_messages/gradient_tool.rs b/editor/src/messages/tool/tool_messages/gradient_tool.rs
index 030da8ec1d..0e84f9585f 100644
--- a/editor/src/messages/tool/tool_messages/gradient_tool.rs
+++ b/editor/src/messages/tool/tool_messages/gradient_tool.rs
@@ -7,7 +7,7 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{Node
use crate::messages::tool::common_functionality::snapping::SnapManager;
use graphene_std::vector::style::{Fill, Gradient, GradientType};
-#[derive(Default)]
+#[derive(Default, ExtractField)]
pub struct GradientTool {
fsm_state: GradientToolFsmState,
data: GradientToolData,
@@ -53,6 +53,7 @@ impl ToolMetadata for GradientTool {
}
}
+#[message_handler_data]
impl<'a> MessageHandler> for GradientTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) {
let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message else {
diff --git a/editor/src/messages/tool/tool_messages/navigate_tool.rs b/editor/src/messages/tool/tool_messages/navigate_tool.rs
index 0a76a331dc..9f9ef0307b 100644
--- a/editor/src/messages/tool/tool_messages/navigate_tool.rs
+++ b/editor/src/messages/tool/tool_messages/navigate_tool.rs
@@ -1,6 +1,6 @@
use super::tool_prelude::*;
-#[derive(Default)]
+#[derive(Default, ExtractField)]
pub struct NavigateTool {
fsm_state: NavigateToolFsmState,
tool_data: NavigateToolData,
@@ -38,6 +38,7 @@ impl LayoutHolder for NavigateTool {
}
}
+#[message_handler_data]
impl<'a> MessageHandler> for NavigateTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
diff --git a/editor/src/messages/tool/tool_messages/path_tool.rs b/editor/src/messages/tool/tool_messages/path_tool.rs
index 0642613cf0..8701ca91cd 100644
--- a/editor/src/messages/tool/tool_messages/path_tool.rs
+++ b/editor/src/messages/tool/tool_messages/path_tool.rs
@@ -24,7 +24,7 @@ use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, Vector
use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType};
use std::vec;
-#[derive(Default)]
+#[derive(Default, ExtractField)]
pub struct PathTool {
fsm_state: PathToolFsmState,
tool_data: PathToolData,
@@ -305,6 +305,7 @@ impl LayoutHolder for PathTool {
}
}
+#[message_handler_data]
impl<'a> MessageHandler> for PathTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) {
let updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated);
@@ -1700,9 +1701,9 @@ impl Fsm for PathToolFsmState {
break_molding,
tool_data.temporary_adjacent_handles_while_molding,
);
- }
- return PathToolFsmState::Dragging(tool_data.dragging_state);
+ return PathToolFsmState::Dragging(tool_data.dragging_state);
+ }
}
let anchor_and_handle_toggled = input.keyboard.get(move_anchor_with_handles as usize);
diff --git a/editor/src/messages/tool/tool_messages/pen_tool.rs b/editor/src/messages/tool/tool_messages/pen_tool.rs
index 14f95a48f7..53a9e4d17c 100644
--- a/editor/src/messages/tool/tool_messages/pen_tool.rs
+++ b/editor/src/messages/tool/tool_messages/pen_tool.rs
@@ -17,7 +17,7 @@ use graphene_std::Color;
use graphene_std::vector::{HandleId, ManipulatorPointId, NoHashBuilder, SegmentId, StrokeId, VectorData};
use graphene_std::vector::{PointId, VectorModificationType};
-#[derive(Default)]
+#[derive(Default, ExtractField)]
pub struct PenTool {
fsm_state: PenToolFsmState,
tool_data: PenToolData,
@@ -186,6 +186,7 @@ impl LayoutHolder for PenTool {
}
}
+#[message_handler_data]
impl<'a> MessageHandler> for PenTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) {
let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message else {
diff --git a/editor/src/messages/tool/tool_messages/select_tool.rs b/editor/src/messages/tool/tool_messages/select_tool.rs
index 9ae3459a7d..e6f4a5d7a3 100644
--- a/editor/src/messages/tool/tool_messages/select_tool.rs
+++ b/editor/src/messages/tool/tool_messages/select_tool.rs
@@ -29,7 +29,7 @@ use graphene_std::renderer::Rect;
use graphene_std::transform::ReferencePoint;
use std::fmt;
-#[derive(Default)]
+#[derive(Default, ExtractField)]
pub struct SelectTool {
fsm_state: SelectToolFsmState,
tool_data: SelectToolData,
@@ -272,6 +272,7 @@ impl LayoutHolder for SelectTool {
}
}
+#[message_handler_data]
impl<'a> MessageHandler> for SelectTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) {
let mut redraw_reference_pivot = false;
@@ -973,6 +974,14 @@ impl Fsm for SelectToolFsmState {
}
}
+ if let Self::Dragging { .. } = self {
+ let quad = Quad::from_box([tool_data.drag_start, tool_data.drag_current]);
+ let document_start = document.metadata().document_to_viewport.inverse().transform_point2(quad.top_left());
+ let document_current = document.metadata().document_to_viewport.inverse().transform_point2(quad.bottom_right());
+
+ overlay_context.translation_box(document_current - document_start, quad, None);
+ }
+
self
}
(_, SelectToolMessage::EditLayer) => {
diff --git a/editor/src/messages/tool/tool_messages/spline_tool.rs b/editor/src/messages/tool/tool_messages/spline_tool.rs
index 2ac541d702..2de6747f01 100644
--- a/editor/src/messages/tool/tool_messages/spline_tool.rs
+++ b/editor/src/messages/tool/tool_messages/spline_tool.rs
@@ -14,7 +14,7 @@ use graph_craft::document::{NodeId, NodeInput};
use graphene_std::Color;
use graphene_std::vector::{PointId, SegmentId, VectorModificationType};
-#[derive(Default)]
+#[derive(Default, ExtractField)]
pub struct SplineTool {
fsm_state: SplineToolFsmState,
tool_data: SplineToolData,
@@ -123,6 +123,7 @@ impl LayoutHolder for SplineTool {
}
}
+#[message_handler_data]
impl<'a> MessageHandler> for SplineTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) {
let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message else {
diff --git a/editor/src/messages/tool/tool_messages/text_tool.rs b/editor/src/messages/tool/tool_messages/text_tool.rs
index 6be22719cb..52430f09ca 100644
--- a/editor/src/messages/tool/tool_messages/text_tool.rs
+++ b/editor/src/messages/tool/tool_messages/text_tool.rs
@@ -20,7 +20,7 @@ use graphene_std::renderer::Quad;
use graphene_std::text::{Font, FontCache, TypesettingConfig, lines_clipping, load_font};
use graphene_std::vector::style::Fill;
-#[derive(Default)]
+#[derive(Default, ExtractField)]
pub struct TextTool {
fsm_state: TextToolFsmState,
tool_data: TextToolData,
@@ -170,6 +170,7 @@ impl LayoutHolder for TextTool {
}
}
+#[message_handler_data]
impl<'a> MessageHandler> for TextTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque, tool_data: &mut ToolActionHandlerData<'a>) {
let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message else {
diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs
index 7258163772..b9b9880e98 100644
--- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs
+++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs
@@ -21,7 +21,7 @@ const TRANSFORM_GRS_OVERLAY_PROVIDER: OverlayProvider = |context| TransformLayer
const SLOW_KEY: Key = Key::Shift;
const INCREMENTS_KEY: Key = Key::Control;
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone, Default, ExtractField)]
pub struct TransformLayerMessageHandler {
pub transform_operation: TransformOperation,
@@ -175,6 +175,26 @@ fn update_colinear_handles(selected_layers: &[LayerNodeIdentifier], document: &D
}
type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessageHandler, &'a ToolData, &'a mut ShapeState);
+
+pub fn custom_data() -> MessageData {
+ MessageData::new(
+ String::from("TransformData<'a>"),
+ // TODO: When is resolved and released,
+ // TODO: use to get
+ // TODO: the line number instead of hardcoding it to the magic number on the following lines
+ // TODO: which points to the line of the `type TransformData<'a> = ...` definition above.
+ // TODO: Also, utilize the line number in the actual output, since it is currently unused.
+ vec![
+ (String::from("&'a DocumentMessageHandler"), 177),
+ (String::from("&'a InputPreprocessorMessageHandler"), 177),
+ (String::from("&'a ToolData"), 177),
+ (String::from("&'a mut ShapeState"), 177),
+ ],
+ file!(),
+ )
+}
+
+#[message_handler_data(CustomData)]
impl MessageHandler> for TransformLayerMessageHandler {
fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque, (document, input, tool_data, shape_editor): TransformData) {
let using_path_tool = tool_data.active_tool_type == ToolType::Path;
@@ -299,37 +319,15 @@ impl MessageHandler> for TransformLayer
let translation = translation.to_dvec(self.initial_transform, self.increments);
let viewport_translate = document_to_viewport.transform_vector2(translation);
let pivot = document_to_viewport.transform_point2(self.grab_target);
- let quad = Quad::from_box([pivot, pivot + viewport_translate]).0;
- let e1 = (self.layer_bounding_box.0[1] - self.layer_bounding_box.0[0]).normalize_or(DVec2::X);
+ let quad = Quad::from_box([pivot, pivot + viewport_translate]);
responses.add(SelectToolMessage::PivotShift {
offset: Some(viewport_translate),
flush: false,
});
- if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. {
- let end = if self.local { (quad[1] - quad[0]).rotate(e1) + quad[0] } else { quad[1] };
- overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5));
-
- let x_transform = DAffine2::from_translation((quad[0] + end) / 2.);
- overlay_context.text(&format_rounded(translation.x, 3), COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]);
- }
-
- if matches!(axis_constraint, Axis::Both | Axis::Y) && translation.y != 0. {
- let end = if self.local { (quad[3] - quad[0]).rotate(e1) + quad[0] } else { quad[3] };
- overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5));
- let x_parameter = viewport_translate.x.clamp(-1., 1.);
- let y_transform = DAffine2::from_translation((quad[0] + end) / 2. + x_parameter * DVec2::X * 0.);
- let pivot_selection = if x_parameter >= -1e-3 { Pivot::Start } else { Pivot::End };
- if axis_constraint != Axis::Both || self.typing.digits.is_empty() || !self.transform_operation.can_begin_typing() {
- overlay_context.text(&format_rounded(translation.y, 2), COLOR_OVERLAY_BLUE, None, y_transform, 3., [pivot_selection, Pivot::Middle]);
- }
- }
-
- if matches!(axis_constraint, Axis::Both) && translation.x != 0. && translation.y != 0. {
- overlay_context.line(quad[1], quad[2], None, None);
- overlay_context.line(quad[3], quad[2], None, None);
- }
+ let typed_string = (!self.typing.digits.is_empty() && self.transform_operation.can_begin_typing()).then(|| self.typing.string.clone());
+ overlay_context.translation_box(translation, quad, typed_string);
}
TransformOperation::Scaling(scale) => {
let scale = scale.to_f64(self.increments);
diff --git a/editor/src/messages/tool/utility_types.rs b/editor/src/messages/tool/utility_types.rs
index fbbec768e4..fc06385f43 100644
--- a/editor/src/messages/tool/utility_types.rs
+++ b/editor/src/messages/tool/utility_types.rs
@@ -18,6 +18,7 @@ use graphene_std::text::FontCache;
use std::borrow::Cow;
use std::fmt::{self, Debug};
+#[derive(ExtractField)]
pub struct ToolActionHandlerData<'a> {
pub document: &'a mut DocumentMessageHandler,
pub document_id: DocumentId,
diff --git a/editor/src/messages/workspace/workspace_message_handler.rs b/editor/src/messages/workspace/workspace_message_handler.rs
index 397e7cf00b..47c81aab0e 100644
--- a/editor/src/messages/workspace/workspace_message_handler.rs
+++ b/editor/src/messages/workspace/workspace_message_handler.rs
@@ -1,10 +1,11 @@
use crate::messages::prelude::*;
-#[derive(Debug, Clone, Default)]
+#[derive(Debug, Clone, Default, ExtractField)]
pub struct WorkspaceMessageHandler {
node_graph_visible: bool,
}
+#[message_handler_data]
impl MessageHandler for WorkspaceMessageHandler {
fn process_message(&mut self, message: WorkspaceMessage, _responses: &mut VecDeque, _data: ()) {
match message {
diff --git a/editor/src/node_graph_executor/runtime.rs b/editor/src/node_graph_executor/runtime.rs
index 227999143c..da92ad313b 100644
--- a/editor/src/node_graph_executor/runtime.rs
+++ b/editor/src/node_graph_executor/runtime.rs
@@ -328,6 +328,18 @@ impl NodeRuntime {
return;
}
+ // Skip thumbnails if the layer is too complex (for performance)
+ if graphic_element.render_complexity() > 1000 {
+ let old = thumbnail_renders.insert(parent_network_node_id, Vec::new());
+ if old.is_none_or(|v| !v.is_empty()) {
+ responses.push_back(FrontendMessage::UpdateNodeThumbnail {
+ id: parent_network_node_id,
+ value: "".to_string(),
+ });
+ }
+ return;
+ }
+
let bounds = graphic_element.bounding_box(DAffine2::IDENTITY, true);
// Render the thumbnail from a `GraphicElement` into an SVG string
diff --git a/editor/src/utility_traits.rs b/editor/src/utility_traits.rs
index 711a8cc34b..850d730b13 100644
--- a/editor/src/utility_traits.rs
+++ b/editor/src/utility_traits.rs
@@ -45,3 +45,19 @@ pub trait TransitiveChild: Into + Into {
pub trait Hint {
fn hints(&self) -> HashMap;
}
+
+pub trait HierarchicalTree {
+ fn build_message_tree() -> DebugMessageTree;
+
+ fn message_handler_data_str() -> MessageData {
+ MessageData::new(String::new(), Vec::new(), "")
+ }
+
+ fn message_handler_str() -> MessageData {
+ MessageData::new(String::new(), Vec::new(), "")
+ }
+
+ fn path() -> &'static str {
+ ""
+ }
+}
diff --git a/editor/src/utility_types.rs b/editor/src/utility_types.rs
new file mode 100644
index 0000000000..6b5dc6de6b
--- /dev/null
+++ b/editor/src/utility_types.rs
@@ -0,0 +1,99 @@
+#[derive(Debug)]
+pub struct MessageData {
+ name: String,
+ fields: Vec<(String, usize)>,
+ path: &'static str,
+}
+
+impl MessageData {
+ pub fn new(name: String, fields: Vec<(String, usize)>, path: &'static str) -> MessageData {
+ MessageData { name, fields, path }
+ }
+
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn fields(&self) -> &Vec<(String, usize)> {
+ &self.fields
+ }
+
+ pub fn path(&self) -> &'static str {
+ self.path
+ }
+}
+
+#[derive(Debug)]
+pub struct DebugMessageTree {
+ name: String,
+ variants: Option>,
+ message_handler: Option,
+ message_handler_data: Option,
+ path: &'static str,
+}
+
+impl DebugMessageTree {
+ pub fn new(name: &str) -> DebugMessageTree {
+ DebugMessageTree {
+ name: name.to_string(),
+ variants: None,
+ message_handler: None,
+ message_handler_data: None,
+ path: "",
+ }
+ }
+
+ pub fn set_path(&mut self, path: &'static str) {
+ self.path = path;
+ }
+
+ pub fn add_variant(&mut self, variant: DebugMessageTree) {
+ if let Some(variants) = &mut self.variants {
+ variants.push(variant);
+ } else {
+ self.variants = Some(vec![variant]);
+ }
+ }
+
+ pub fn add_message_handler_data_field(&mut self, message_handler_data: MessageData) {
+ self.message_handler_data = Some(message_handler_data);
+ }
+
+ pub fn add_message_handler_field(&mut self, message_handler: MessageData) {
+ self.message_handler = Some(message_handler);
+ }
+
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn path(&self) -> &'static str {
+ self.path
+ }
+
+ pub fn variants(&self) -> Option<&Vec> {
+ self.variants.as_ref()
+ }
+
+ pub fn message_handler_data_fields(&self) -> Option<&MessageData> {
+ self.message_handler_data.as_ref()
+ }
+
+ pub fn message_handler_fields(&self) -> Option<&MessageData> {
+ self.message_handler.as_ref()
+ }
+
+ pub fn has_message_handler_data_fields(&self) -> bool {
+ match self.message_handler_data_fields() {
+ Some(_) => true,
+ None => false,
+ }
+ }
+
+ pub fn has_message_handler_fields(&self) -> bool {
+ match self.message_handler_fields() {
+ Some(_) => true,
+ None => false,
+ }
+ }
+}
diff --git a/node-graph/gbrush/src/brush.rs b/node-graph/gbrush/src/brush.rs
index b2782adc8a..3a085c0abe 100644
--- a/node-graph/gbrush/src/brush.rs
+++ b/node-graph/gbrush/src/brush.rs
@@ -403,7 +403,7 @@ mod test {
blend_mode: BlendMode::Normal,
},
}],
- BrushCache::new_proto(),
+ BrushCache::default(),
)
.await;
assert_eq!(image.instance_ref_iter().next().unwrap().instance.width, 20);
diff --git a/node-graph/gbrush/src/brush_cache.rs b/node-graph/gbrush/src/brush_cache.rs
index 853a13ca34..c5495534c1 100644
--- a/node-graph/gbrush/src/brush_cache.rs
+++ b/node-graph/gbrush/src/brush_cache.rs
@@ -6,11 +6,16 @@ use graphene_core::raster_types::CPU;
use graphene_core::raster_types::Raster;
use std::collections::HashMap;
use std::hash::Hash;
-use std::sync::Arc;
-use std::sync::Mutex;
+use std::hash::Hasher;
+use std::sync::atomic::{AtomicU64, Ordering};
+use std::sync::{Arc, Mutex};
-#[derive(Clone, Debug, PartialEq, DynAny, Default, serde::Serialize, serde::Deserialize)]
+// TODO: This is a temporary hack, be sure to not reuse this when the brush is being rewritten.
+static NEXT_BRUSH_CACHE_IMPL_ID: AtomicU64 = AtomicU64::new(0);
+
+#[derive(Clone, Debug, DynAny, serde::Serialize, serde::Deserialize)]
struct BrushCacheImpl {
+ unique_id: u64,
// The full previous input that was cached.
prev_input: Vec,
@@ -90,9 +95,29 @@ impl BrushCacheImpl {
}
}
+impl Default for BrushCacheImpl {
+ fn default() -> Self {
+ Self {
+ unique_id: NEXT_BRUSH_CACHE_IMPL_ID.fetch_add(1, Ordering::SeqCst),
+ prev_input: Vec::new(),
+ background: Default::default(),
+ blended_image: Default::default(),
+ last_stroke_texture: Default::default(),
+ brush_texture_cache: HashMap::new(),
+ }
+ }
+}
+
+impl PartialEq for BrushCacheImpl {
+ fn eq(&self, other: &Self) -> bool {
+ self.unique_id == other.unique_id
+ }
+}
+
impl Hash for BrushCacheImpl {
- // Zero hash.
- fn hash(&self, _state: &mut H) {}
+ fn hash(&self, state: &mut H) {
+ self.unique_id.hash(state);
+ }
}
#[derive(Clone, Debug, Default)]
@@ -103,46 +128,26 @@ pub struct BrushPlan {
pub first_stroke_point_skip: usize,
}
-#[derive(Debug, DynAny, serde::Serialize, serde::Deserialize)]
-pub struct BrushCache {
- inner: Arc>,
- proto: bool,
-}
-
-impl Default for BrushCache {
- fn default() -> Self {
- Self::new_proto()
- }
-}
+#[derive(Debug, Default, DynAny, serde::Serialize, serde::Deserialize)]
+pub struct BrushCache(Arc>);
// A bit of a cursed implementation to work around the current node system.
// The original object is a 'prototype' that when cloned gives you a independent
// new object. Any further clones however are all the same underlying cache object.
impl Clone for BrushCache {
fn clone(&self) -> Self {
- if self.proto {
- let inner_val = self.inner.lock().unwrap();
- Self {
- inner: Arc::new(Mutex::new(inner_val.clone())),
- proto: false,
- }
- } else {
- Self {
- inner: Arc::clone(&self.inner),
- proto: false,
- }
- }
+ Self(Arc::new(Mutex::new(self.0.lock().unwrap().clone())))
}
}
impl PartialEq for BrushCache {
fn eq(&self, other: &Self) -> bool {
- if Arc::ptr_eq(&self.inner, &other.inner) {
+ if Arc::ptr_eq(&self.0, &other.0) {
return true;
}
- let s = self.inner.lock().unwrap();
- let o = other.inner.lock().unwrap();
+ let s = self.0.lock().unwrap();
+ let o = other.0.lock().unwrap();
*s == *o
}
@@ -150,35 +155,28 @@ impl PartialEq for BrushCache {
impl Hash for BrushCache {
fn hash(&self, state: &mut H) {
- self.inner.lock().unwrap().hash(state);
+ self.0.lock().unwrap().hash(state);
}
}
impl BrushCache {
- pub fn new_proto() -> Self {
- Self {
- inner: Default::default(),
- proto: true,
- }
- }
-
pub fn compute_brush_plan(&self, background: Instance>, input: &[BrushStroke]) -> BrushPlan {
- let mut inner = self.inner.lock().unwrap();
+ let mut inner = self.0.lock().unwrap();
inner.compute_brush_plan(background, input)
}
pub fn cache_results(&self, input: Vec, blended_image: Instance>, last_stroke_texture: Instance>) {
- let mut inner = self.inner.lock().unwrap();
+ let mut inner = self.0.lock().unwrap();
inner.cache_results(input, blended_image, last_stroke_texture)
}
pub fn get_cached_brush(&self, style: &BrushStyle) -> Option> {
- let inner = self.inner.lock().unwrap();
+ let inner = self.0.lock().unwrap();
inner.brush_texture_cache.get(style).cloned()
}
pub fn store_brush(&self, style: BrushStyle, brush: Raster) {
- let mut inner = self.inner.lock().unwrap();
+ let mut inner = self.0.lock().unwrap();
inner.brush_texture_cache.insert(style, brush);
}
}
diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs
index 131c6c04b0..af8ae31230 100644
--- a/node-graph/gcore/src/graphic_element.rs
+++ b/node-graph/gcore/src/graphic_element.rs
@@ -490,7 +490,7 @@ async fn to_artboard + 'n>(
}
#[node_macro::node(category(""))]
-async fn append_artboard(_ctx: impl Ctx, mut artboards: ArtboardGroupTable, artboard: Artboard, node_path: Vec) -> ArtboardGroupTable {
+pub async fn append_artboard(_ctx: impl Ctx, mut artboards: ArtboardGroupTable, artboard: Artboard, node_path: Vec) -> ArtboardGroupTable {
// Get the penultimate element of the node path, or None if the path is too short.
// This is used to get the ID of the user-facing "Artboard" node (which encapsulates this internal "Append Artboard" node).
let encapsulating_node_id = node_path.get(node_path.len().wrapping_sub(2)).copied();
diff --git a/node-graph/gcore/src/lib.rs b/node-graph/gcore/src/lib.rs
index 1333ec7433..4f1f0aaf0b 100644
--- a/node-graph/gcore/src/lib.rs
+++ b/node-graph/gcore/src/lib.rs
@@ -22,6 +22,7 @@ pub mod ops;
pub mod raster;
pub mod raster_types;
pub mod registry;
+pub mod render_complexity;
pub mod structural;
pub mod text;
pub mod transform;
diff --git a/node-graph/gcore/src/render_complexity.rs b/node-graph/gcore/src/render_complexity.rs
new file mode 100644
index 0000000000..3c137c87e8
--- /dev/null
+++ b/node-graph/gcore/src/render_complexity.rs
@@ -0,0 +1,61 @@
+use crate::instances::Instances;
+use crate::raster_types::{CPU, GPU, Raster};
+use crate::vector::VectorData;
+use crate::{Artboard, Color, GraphicElement};
+use glam::DVec2;
+
+pub trait RenderComplexity {
+ fn render_complexity(&self) -> usize {
+ 0
+ }
+}
+
+impl RenderComplexity for Instances {
+ fn render_complexity(&self) -> usize {
+ self.instance_ref_iter().map(|instance| instance.instance.render_complexity()).fold(0, usize::saturating_add)
+ }
+}
+
+impl RenderComplexity for Artboard {
+ fn render_complexity(&self) -> usize {
+ self.graphic_group.render_complexity()
+ }
+}
+
+impl RenderComplexity for GraphicElement {
+ fn render_complexity(&self) -> usize {
+ match self {
+ Self::GraphicGroup(instances) => instances.render_complexity(),
+ Self::VectorData(instances) => instances.render_complexity(),
+ Self::RasterDataCPU(instances) => instances.render_complexity(),
+ Self::RasterDataGPU(instances) => instances.render_complexity(),
+ }
+ }
+}
+
+impl RenderComplexity for VectorData {
+ fn render_complexity(&self) -> usize {
+ self.segment_domain.ids().len()
+ }
+}
+
+impl RenderComplexity for Raster {
+ fn render_complexity(&self) -> usize {
+ (self.width * self.height / 500) as usize
+ }
+}
+
+impl RenderComplexity for Raster {
+ fn render_complexity(&self) -> usize {
+ // GPU textures currently can't have a thumbnail
+ usize::MAX
+ }
+}
+
+impl RenderComplexity for String {}
+impl RenderComplexity for bool {}
+impl RenderComplexity for f32 {}
+impl RenderComplexity for f64 {}
+impl RenderComplexity for DVec2 {}
+impl RenderComplexity for Option {}
+impl RenderComplexity for Vec {}
diff --git a/node-graph/gsvg-renderer/src/renderer.rs b/node-graph/gsvg-renderer/src/renderer.rs
index 70f7c18206..a71cdf1b56 100644
--- a/node-graph/gsvg-renderer/src/renderer.rs
+++ b/node-graph/gsvg-renderer/src/renderer.rs
@@ -10,6 +10,7 @@ use graphene_core::instances::Instance;
use graphene_core::math::quad::Quad;
use graphene_core::raster::Image;
use graphene_core::raster_types::{CPU, GPU, RasterDataTable};
+use graphene_core::render_complexity::RenderComplexity;
use graphene_core::transform::{Footprint, Transform};
use graphene_core::uuid::{NodeId, generate_uuid};
use graphene_core::vector::VectorDataTable;
@@ -204,7 +205,7 @@ pub struct RenderMetadata {
}
// TODO: Rename to "Graphical"
-pub trait GraphicElementRendered: BoundingBox {
+pub trait GraphicElementRendered: BoundingBox + RenderComplexity {
fn render_svg(&self, render: &mut SvgRender, render_params: &RenderParams);
#[cfg(feature = "vello")]
@@ -1151,7 +1152,7 @@ impl GraphicElementRendered for GraphicElement {
}
/// Used to stop rust complaining about upstream traits adding display implementations to `Option`. This would not be an issue as we control that crate.
-trait Primitive: std::fmt::Display + BoundingBox {}
+trait Primitive: std::fmt::Display + BoundingBox + RenderComplexity {}
impl Primitive for String {}
impl Primitive for bool {}
impl Primitive for f32 {}
diff --git a/proc-macros/src/combined_message_attrs.rs b/proc-macros/src/combined_message_attrs.rs
index aadca6384e..943457cd1c 100644
--- a/proc-macros/src/combined_message_attrs.rs
+++ b/proc-macros/src/combined_message_attrs.rs
@@ -61,7 +61,7 @@ pub fn combined_message_attrs_impl(attr: TokenStream, input_item: TokenStream) -
<#parent as ToDiscriminant>::Discriminant
};
- input.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant, TransitiveChild)] });
+ input.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant, TransitiveChild, HierarchicalTree)] });
input.attrs.push(syn::parse_quote! { #[parent(#parent, #parent::#variant)] });
if parent_is_top {
input.attrs.push(syn::parse_quote! { #[parent_is_top] });
@@ -97,7 +97,7 @@ pub fn combined_message_attrs_impl(attr: TokenStream, input_item: TokenStream) -
fn top_level_impl(input_item: TokenStream) -> syn::Result {
let mut input = syn::parse2::(input_item)?;
- input.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant)] });
+ input.attrs.push(syn::parse_quote! { #[derive(ToDiscriminant, HierarchicalTree)] });
input.attrs.push(syn::parse_quote! { #[discriminant_attr(derive(Debug, Copy, Clone, PartialEq, Eq, Hash, AsMessage))] });
for var in &mut input.variants {
diff --git a/proc-macros/src/extract_fields.rs b/proc-macros/src/extract_fields.rs
new file mode 100644
index 0000000000..606b3b8a37
--- /dev/null
+++ b/proc-macros/src/extract_fields.rs
@@ -0,0 +1,57 @@
+use crate::helpers::clean_rust_type_syntax;
+use proc_macro2::{Span, TokenStream};
+use quote::{ToTokens, format_ident, quote};
+use syn::{Data, DeriveInput, Fields, Type, parse2};
+
+pub fn derive_extract_field_impl(input: TokenStream) -> syn::Result {
+ let input = parse2::(input)?;
+ let struct_name = &input.ident;
+ let generics = &input.generics;
+ let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
+
+ let fields = match &input.data {
+ Data::Struct(data) => match &data.fields {
+ Fields::Named(fields) => &fields.named,
+ _ => return Err(syn::Error::new(Span::call_site(), "ExtractField only works on structs with named fields")),
+ },
+ _ => return Err(syn::Error::new(Span::call_site(), "ExtractField only works on structs")),
+ };
+
+ let mut field_line = Vec::new();
+ // Extract field names and types as strings at compile time
+ let field_info = fields
+ .iter()
+ .map(|field| {
+ let ident = field.ident.as_ref().unwrap();
+ let name = ident.to_string();
+ let ty = clean_rust_type_syntax(field.ty.to_token_stream().to_string());
+ let line = ident.span().start().line;
+ field_line.push(line);
+ (name, ty)
+ })
+ .collect::>();
+
+ let field_str = field_info.into_iter().map(|(name, ty)| (format!("{}: {}", name, ty)));
+
+ let res = quote! {
+ impl #impl_generics #struct_name #ty_generics #where_clause {
+ pub fn field_types() -> Vec<(String, usize)> {
+ vec![
+ #((String::from(#field_str), #field_line)),*
+ ]
+ }
+
+ pub fn print_field_types() {
+ for (field, line) in Self::field_types() {
+ println!("{} at line {}", field, line);
+ }
+ }
+
+ pub fn path() -> &'static str {
+ file!()
+ }
+ }
+ };
+
+ Ok(res)
+}
diff --git a/proc-macros/src/helpers.rs b/proc-macros/src/helpers.rs
index 11c18c8b41..230f792c0f 100644
--- a/proc-macros/src/helpers.rs
+++ b/proc-macros/src/helpers.rs
@@ -42,6 +42,58 @@ pub fn two_segment_path(left_ident: Ident, right_ident: Ident) -> Path {
Path { leading_colon: None, segments }
}
+pub fn clean_rust_type_syntax(input: String) -> String {
+ let mut result = String::new();
+ let mut chars = input.chars().peekable();
+
+ while let Some(c) = chars.next() {
+ match c {
+ '&' => {
+ result.push('&');
+ while let Some(' ') = chars.peek() {
+ chars.next();
+ }
+ }
+ '<' => {
+ while let Some(' ') = result.chars().rev().next() {
+ result.pop();
+ }
+ result.push('<');
+ while let Some(' ') = chars.peek() {
+ chars.next();
+ }
+ }
+ '>' => {
+ while let Some(' ') = result.chars().rev().next() {
+ result.pop();
+ }
+ result.push('>');
+ while let Some(' ') = chars.peek() {
+ chars.next();
+ }
+ }
+ ':' => {
+ if let Some(':') = chars.peek() {
+ while let Some(' ') = result.chars().rev().next() {
+ result.pop();
+ }
+ }
+ result.push(':');
+ chars.next();
+ result.push(':');
+ while let Some(' ') = chars.peek() {
+ chars.next();
+ }
+ }
+ _ => {
+ result.push(c);
+ }
+ }
+ }
+
+ result
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/proc-macros/src/hierarchical_tree.rs b/proc-macros/src/hierarchical_tree.rs
new file mode 100644
index 0000000000..e0d6fb6a71
--- /dev/null
+++ b/proc-macros/src/hierarchical_tree.rs
@@ -0,0 +1,73 @@
+use proc_macro2::{Span, TokenStream};
+use quote::{ToTokens, quote};
+use syn::{Data, DeriveInput, Fields, Type, parse2};
+
+pub fn generate_hierarchical_tree(input: TokenStream) -> syn::Result {
+ let input = parse2::(input)?;
+ let input_type = &input.ident;
+
+ let data = match &input.data {
+ Data::Enum(data) => data,
+ _ => return Err(syn::Error::new(Span::call_site(), "Tried to derive HierarchicalTree for non-enum")),
+ };
+
+ let build_message_tree = data.variants.iter().map(|variant| {
+ let variant_type = &variant.ident;
+
+ let has_child = variant
+ .attrs
+ .iter()
+ .any(|attr| attr.path().get_ident().is_some_and(|ident| ident == "sub_discriminant" || ident == "child"));
+
+ if has_child {
+ if let Fields::Unnamed(fields) = &variant.fields {
+ let field_type = &fields.unnamed.first().unwrap().ty;
+ quote! {
+ {
+ let mut variant_tree = DebugMessageTree::new(stringify!(#variant_type));
+ let field_name = stringify!(#field_type);
+ const message_string: &str = "Message";
+ if message_string == &field_name[field_name.len().saturating_sub(message_string.len())..] {
+ // The field is a Message type, recursively build its tree
+ let sub_tree = #field_type::build_message_tree();
+ variant_tree.add_variant(sub_tree);
+ }
+ message_tree.add_variant(variant_tree);
+ }
+ }
+ } else {
+ quote! {
+ message_tree.add_variant(DebugMessageTree::new(stringify!(#variant_type)));
+ }
+ }
+ } else {
+ quote! {
+ message_tree.add_variant(DebugMessageTree::new(stringify!(#variant_type)));
+ }
+ }
+ });
+
+ let res = quote! {
+ impl HierarchicalTree for #input_type {
+ fn build_message_tree() -> DebugMessageTree {
+ let mut message_tree = DebugMessageTree::new(stringify!(#input_type));
+ #(#build_message_tree)*
+ let message_handler_str = #input_type::message_handler_str();
+ if message_handler_str.fields().len() > 0 {
+ message_tree.add_message_handler_field(message_handler_str);
+ }
+
+ let message_handler_data_str = #input_type::message_handler_data_str();
+ if message_handler_data_str.fields().len() > 0 {
+ message_tree.add_message_handler_data_field(message_handler_data_str);
+ }
+
+ message_tree.set_path(file!());
+
+ message_tree
+ }
+ }
+ };
+
+ Ok(res)
+}
diff --git a/proc-macros/src/lib.rs b/proc-macros/src/lib.rs
index 02cd1caaf4..8d68df75c8 100644
--- a/proc-macros/src/lib.rs
+++ b/proc-macros/src/lib.rs
@@ -3,17 +3,23 @@
mod as_message;
mod combined_message_attrs;
mod discriminant;
+mod extract_fields;
mod helper_structs;
mod helpers;
+mod hierarchical_tree;
mod hint;
+mod message_handler_data_attr;
mod transitive_child;
mod widget_builder;
use crate::as_message::derive_as_message_impl;
use crate::combined_message_attrs::combined_message_attrs_impl;
use crate::discriminant::derive_discriminant_impl;
+use crate::extract_fields::derive_extract_field_impl;
use crate::helper_structs::AttrInnerSingleString;
+use crate::hierarchical_tree::generate_hierarchical_tree;
use crate::hint::derive_hint_impl;
+use crate::message_handler_data_attr::message_handler_data_attr_impl;
use crate::transitive_child::derive_transitive_child_impl;
use crate::widget_builder::derive_widget_builder_impl;
use proc_macro::TokenStream;
@@ -281,6 +287,21 @@ pub fn derive_widget_builder(input_item: TokenStream) -> TokenStream {
TokenStream::from(derive_widget_builder_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error()))
}
+#[proc_macro_derive(HierarchicalTree)]
+pub fn derive_hierarchical_tree(input_item: TokenStream) -> TokenStream {
+ TokenStream::from(generate_hierarchical_tree(input_item.into()).unwrap_or_else(|err| err.to_compile_error()))
+}
+
+#[proc_macro_derive(ExtractField)]
+pub fn derive_extract_field(input_item: TokenStream) -> TokenStream {
+ TokenStream::from(derive_extract_field_impl(input_item.into()).unwrap_or_else(|err| err.to_compile_error()))
+}
+
+#[proc_macro_attribute]
+pub fn message_handler_data(attr: TokenStream, input_item: TokenStream) -> TokenStream {
+ TokenStream::from(message_handler_data_attr_impl(attr.into(), input_item.into()).unwrap_or_else(|err| err.to_compile_error()))
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/proc-macros/src/message_handler_data_attr.rs b/proc-macros/src/message_handler_data_attr.rs
new file mode 100644
index 0000000000..b2170beac4
--- /dev/null
+++ b/proc-macros/src/message_handler_data_attr.rs
@@ -0,0 +1,118 @@
+use crate::helpers::{call_site_ident, clean_rust_type_syntax};
+use proc_macro2::{Span, TokenStream};
+use quote::{ToTokens, quote};
+use syn::{ItemImpl, Type, parse2, spanned::Spanned};
+
+pub fn message_handler_data_attr_impl(attr: TokenStream, input_item: TokenStream) -> syn::Result {
+ // Parse the input as an impl block
+ let impl_block = parse2::(input_item.clone())?;
+
+ let self_ty = &impl_block.self_ty;
+
+ let path = match &**self_ty {
+ Type::Path(path) => &path.path,
+ _ => return Err(syn::Error::new(Span::call_site(), "Expected impl implementation")),
+ };
+
+ let input_type = path.segments.last().map(|s| &s.ident).unwrap();
+
+ // Extract the message type from the trait path
+ let trait_path = match &impl_block.trait_ {
+ Some((_, path, _)) => path,
+ None => return Err(syn::Error::new(Span::call_site(), "Expected trait implementation")),
+ };
+
+ // Get the trait generics (should be MessageHandler)
+ if let Some(segment) = trait_path.segments.last() {
+ if segment.ident != "MessageHandler" {
+ return Err(syn::Error::new(segment.ident.span(), "Expected MessageHandler trait"));
+ }
+ if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
+ if args.args.len() >= 2 {
+ // Extract the message type (M) and data type (D) from the trait params
+ let message_type = &args.args[0];
+ let data_type = &args.args[1];
+
+ // Check if the attribute is "CustomData"
+ let is_custom_data = attr.to_string().contains("CustomData");
+
+ let impl_item = match data_type {
+ syn::GenericArgument::Type(t) => {
+ match t {
+ syn::Type::Path(type_path) if !type_path.path.segments.is_empty() => {
+ // Get just the base identifier (ToolMessageData) without generics
+ let type_name = &type_path.path.segments.first().unwrap().ident;
+
+ if is_custom_data {
+ quote! {
+ #input_item
+ impl #message_type {
+ pub fn message_handler_data_str() -> MessageData {
+ custom_data()
+ }
+ pub fn message_handler_str() -> MessageData {
+ MessageData::new(format!("{}",stringify!(#input_type)), #input_type::field_types(), #input_type::path())
+
+ }
+ }
+ }
+ } else {
+ quote! {
+ #input_item
+ impl #message_type {
+ pub fn message_handler_data_str() -> MessageData
+ {
+ MessageData::new(format!("{}",stringify!(#type_name)), #type_name::field_types(), #type_name::path())
+
+ }
+ pub fn message_handler_str() -> MessageData {
+ MessageData::new(format!("{}",stringify!(#input_type)), #input_type::field_types(), #input_type::path())
+
+ }
+ }
+ }
+ }
+ }
+ syn::Type::Tuple(_) => quote! {
+ #input_item
+ impl #message_type {
+ pub fn message_handler_str() -> MessageData {
+ MessageData::new(format!("{}",stringify!(#input_type)), #input_type::field_types(), #input_type::path())
+ }
+ }
+ },
+ syn::Type::Reference(type_reference) => {
+ let message_type = call_site_ident(format!("{input_type}Message"));
+ let type_ident = match &*type_reference.elem {
+ syn::Type::Path(type_path) => &type_path.path.segments.first().unwrap().ident,
+ _ => return Err(syn::Error::new(type_reference.elem.span(), "Expected type path")),
+ };
+ let tr = clean_rust_type_syntax(type_reference.to_token_stream().to_string());
+ quote! {
+ #input_item
+ impl #message_type {
+ pub fn message_handler_data_str() -> MessageData {
+ MessageData::new(format!("{}", #tr),#type_ident::field_types(), #type_ident::path())
+ }
+
+ pub fn message_handler_str() -> MessageData {
+ MessageData::new(format!("{}",stringify!(#input_type)), #input_type::field_types(), #input_type::path())
+
+ }
+ }
+ }
+ }
+ _ => return Err(syn::Error::new(t.span(), "Unsupported type format")),
+ }
+ }
+
+ _ => quote! {
+ #input_item
+ },
+ };
+ return Ok(impl_item);
+ }
+ }
+ }
+ Ok(input_item)
+}