Merge branch 'master' into masking

This commit is contained in:
mtvare6
2025-07-10 13:11:30 +05:30
101 changed files with 3411 additions and 2265 deletions
+25
View File
@@ -9,6 +9,7 @@ on:
pull_request: pull_request:
paths: paths:
- website/** - website/**
workflow_dispatch: {}
env: env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
INDEX_HTML_HEAD_INCLUSION: <script defer data-domain="graphite.rs" data-api="/visit/event" src="/visit/script.hash.js"></script> INDEX_HTML_HEAD_INCLUSION: <script defer data-domain="graphite.rs" data-api="/visit/event" src="/visit/script.hash.js"></script>
@@ -80,8 +81,32 @@ jobs:
- name: 🚚 Move `website/other/dist` contents to `website/public` - name: 🚚 Move `website/other/dist` contents to `website/public`
run: | run: |
mkdir -p website/public
mv website/other/dist/* website/public mv website/other/dist/* website/public
- name: 💿 Obtain cache of auto-generated code docs artifacts
id: cache-website-code-docs
uses: actions/cache/restore@v3
with:
path: artifacts
key: website-code-docs
- name: 📁 Fallback in case auto-generated code docs artifacts weren't cached
if: steps.cache-website-code-docs.outputs.cache-hit != 'true'
run: |
echo "🦀 Initial system version of Rust:"
rustc --version
rustup update stable
echo "🦀 Latest updated version of Rust:"
rustc --version
cargo test --package graphite-editor --lib -- messages::message::test::generate_message_tree
mkdir artifacts
mv hierarchical_message_system_tree.txt artifacts/hierarchical_message_system_tree.txt
- name: 🚚 Move `artifacts` contents to `website/public`
run: |
mv artifacts/* website/public
- name: 📤 Publish to Cloudflare Pages - name: 📤 Publish to Cloudflare Pages
id: cloudflare id: cloudflare
uses: cloudflare/pages-action@1 uses: cloudflare/pages-action@1
+1
View File
@@ -6,3 +6,4 @@ profile.json
flamegraph.svg flamegraph.svg
.idea/ .idea/
.direnv .direnv
hierarchical_message_system_tree.txt
Generated
+982 -769
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -61,6 +61,7 @@ pub const SELECTION_DRAG_ANGLE: f64 = 90.;
pub const PIVOT_CROSSHAIR_THICKNESS: f64 = 1.; pub const PIVOT_CROSSHAIR_THICKNESS: f64 = 1.;
pub const PIVOT_CROSSHAIR_LENGTH: f64 = 9.; pub const PIVOT_CROSSHAIR_LENGTH: f64 = 9.;
pub const PIVOT_DIAMETER: f64 = 5.; pub const PIVOT_DIAMETER: f64 = 5.;
pub const DOWEL_PIN_RADIUS: f64 = 4.;
// COMPASS ROSE // COMPASS ROSE
pub const COMPASS_ROSE_RING_INNER_DIAMETER: f64 = 13.; pub const COMPASS_ROSE_RING_INNER_DIAMETER: f64 = 13.;
@@ -133,8 +134,8 @@ pub const SCALE_EFFECT: f64 = 0.5;
// COLORS // COLORS
pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff"; pub const COLOR_OVERLAY_BLUE: &str = "#00a8ff";
pub const COLOR_OVERLAY_BLUE_50: &str = "rgba(0, 168, 255, 0.5)";
pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848"; pub const COLOR_OVERLAY_YELLOW: &str = "#ffc848";
pub const COLOR_OVERLAY_YELLOW_DULL: &str = "#d7ba8b";
pub const COLOR_OVERLAY_GREEN: &str = "#63ce63"; pub const COLOR_OVERLAY_GREEN: &str = "#63ce63";
pub const COLOR_OVERLAY_RED: &str = "#ef5454"; pub const COLOR_OVERLAY_RED: &str = "#ef5454";
pub const COLOR_OVERLAY_GRAY: &str = "#cccccc"; pub const COLOR_OVERLAY_GRAY: &str = "#cccccc";
+2
View File
@@ -140,6 +140,7 @@ impl Dispatcher {
let graphene_std::renderer::RenderMetadata { let graphene_std::renderer::RenderMetadata {
upstream_footprints: footprints, upstream_footprints: footprints,
local_transforms, local_transforms,
first_instance_source_id,
click_targets, click_targets,
clip_targets, clip_targets,
} = render_metadata; } = render_metadata;
@@ -149,6 +150,7 @@ impl Dispatcher {
DocumentMessage::UpdateUpstreamTransforms { DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints: footprints, upstream_footprints: footprints,
local_transforms, local_transforms,
first_instance_source_id,
}, },
DocumentMessage::UpdateClickTargets { click_targets }, DocumentMessage::UpdateClickTargets { click_targets },
DocumentMessage::UpdateClipTargets { clip_targets }, DocumentMessage::UpdateClipTargets { clip_targets },
+1
View File
@@ -15,3 +15,4 @@ pub mod node_graph_executor;
#[cfg(test)] #[cfg(test)]
pub mod test_utils; pub mod test_utils;
pub mod utility_traits; pub mod utility_traits;
pub mod utility_types;
@@ -24,7 +24,7 @@ enum AnimationState {
}, },
} }
#[derive(Default, Debug, Clone, PartialEq)] #[derive(Default, Debug, Clone, PartialEq, ExtractField)]
pub struct AnimationMessageHandler { pub struct AnimationMessageHandler {
/// Used to re-send the UI on the next frame after playback starts /// Used to re-send the UI on the next frame after playback starts
live_preview_recently_zero: bool, live_preview_recently_zero: bool,
@@ -57,6 +57,7 @@ impl AnimationMessageHandler {
} }
} }
#[message_handler_data]
impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler { impl MessageHandler<AnimationMessage, ()> for AnimationMessageHandler {
fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque<Message>, _data: ()) { fn process_message(&mut self, message: AnimationMessage, responses: &mut VecDeque<Message>, _data: ()) {
match message { match message {
@@ -1,10 +1,11 @@
use crate::messages::prelude::*; use crate::messages::prelude::*;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default, ExtractField)]
pub struct BroadcastMessageHandler { pub struct BroadcastMessageHandler {
listeners: HashMap<BroadcastEvent, Vec<Message>>, listeners: HashMap<BroadcastEvent, Vec<Message>>,
} }
#[message_handler_data]
impl MessageHandler<BroadcastMessage, ()> for BroadcastMessageHandler { impl MessageHandler<BroadcastMessage, ()> for BroadcastMessageHandler {
fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque<Message>, _data: ()) { fn process_message(&mut self, message: BroadcastMessage, responses: &mut VecDeque<Message>, _data: ()) {
match message { match message {
@@ -1,11 +1,12 @@
use super::utility_types::MessageLoggingVerbosity; use super::utility_types::MessageLoggingVerbosity;
use crate::messages::prelude::*; use crate::messages::prelude::*;
#[derive(Debug, Default)] #[derive(Debug, Default, ExtractField)]
pub struct DebugMessageHandler { pub struct DebugMessageHandler {
pub message_logging_verbosity: MessageLoggingVerbosity, pub message_logging_verbosity: MessageLoggingVerbosity,
} }
#[message_handler_data]
impl MessageHandler<DebugMessage, ()> for DebugMessageHandler { impl MessageHandler<DebugMessage, ()> for DebugMessageHandler {
fn process_message(&mut self, message: DebugMessage, responses: &mut VecDeque<Message>, _data: ()) { fn process_message(&mut self, message: DebugMessage, responses: &mut VecDeque<Message>, _data: ()) {
match message { match message {
@@ -2,19 +2,21 @@ use super::simple_dialogs::{self, AboutGraphiteDialog, ComingSoonDialog, DemoArt
use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::prelude::*; use crate::messages::prelude::*;
#[derive(ExtractField)]
pub struct DialogMessageData<'a> { pub struct DialogMessageData<'a> {
pub portfolio: &'a PortfolioMessageHandler, pub portfolio: &'a PortfolioMessageHandler,
pub preferences: &'a PreferencesMessageHandler, 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`. /// 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 { pub struct DialogMessageHandler {
export_dialog: ExportDialogMessageHandler, export_dialog: ExportDialogMessageHandler,
new_document_dialog: NewDocumentDialogMessageHandler, new_document_dialog: NewDocumentDialogMessageHandler,
preferences_dialog: PreferencesDialogMessageHandler, preferences_dialog: PreferencesDialogMessageHandler,
} }
#[message_handler_data]
impl MessageHandler<DialogMessage, DialogMessageData<'_>> for DialogMessageHandler { impl MessageHandler<DialogMessage, DialogMessageData<'_>> for DialogMessageHandler {
fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque<Message>, data: DialogMessageData) { fn process_message(&mut self, message: DialogMessage, responses: &mut VecDeque<Message>, data: DialogMessageData) {
let DialogMessageData { portfolio, preferences } = data; let DialogMessageData { portfolio, preferences } = data;
@@ -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::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::*; use crate::messages::prelude::*;
#[derive(ExtractField)]
pub struct ExportDialogMessageData<'a> { pub struct ExportDialogMessageData<'a> {
pub portfolio: &'a PortfolioMessageHandler, pub portfolio: &'a PortfolioMessageHandler,
} }
/// A dialog to allow users to customize their file export. /// A dialog to allow users to customize their file export.
#[derive(Debug, Clone)] #[derive(Debug, Clone, ExtractField)]
pub struct ExportDialogMessageHandler { pub struct ExportDialogMessageHandler {
pub file_type: FileType, pub file_type: FileType,
pub scale_factor: f64, pub scale_factor: f64,
@@ -31,6 +32,7 @@ impl Default for ExportDialogMessageHandler {
} }
} }
#[message_handler_data]
impl MessageHandler<ExportDialogMessage, ExportDialogMessageData<'_>> for ExportDialogMessageHandler { impl MessageHandler<ExportDialogMessage, ExportDialogMessageData<'_>> for ExportDialogMessageHandler {
fn process_message(&mut self, message: ExportDialogMessage, responses: &mut VecDeque<Message>, data: ExportDialogMessageData) { fn process_message(&mut self, message: ExportDialogMessage, responses: &mut VecDeque<Message>, data: ExportDialogMessageData) {
let ExportDialogMessageData { portfolio } = data; let ExportDialogMessageData { portfolio } = data;
@@ -4,13 +4,14 @@ use glam::{IVec2, UVec2};
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
/// A dialog to allow users to set some initial options about a new document. /// 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 struct NewDocumentDialogMessageHandler {
pub name: String, pub name: String,
pub infinite: bool, pub infinite: bool,
pub dimensions: UVec2, pub dimensions: UVec2,
} }
#[message_handler_data]
impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHandler { impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHandler {
fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, _data: ()) { fn process_message(&mut self, message: NewDocumentDialogMessage, responses: &mut VecDeque<Message>, _data: ()) {
match message { match message {
@@ -4,14 +4,16 @@ use crate::messages::portfolio::document::utility_types::wires::GraphWireStyle;
use crate::messages::preferences::SelectionMode; use crate::messages::preferences::SelectionMode;
use crate::messages::prelude::*; use crate::messages::prelude::*;
#[derive(ExtractField)]
pub struct PreferencesDialogMessageData<'a> { pub struct PreferencesDialogMessageData<'a> {
pub preferences: &'a PreferencesMessageHandler, pub preferences: &'a PreferencesMessageHandler,
} }
/// A dialog to allow users to customize Graphite editor options /// A dialog to allow users to customize Graphite editor options
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default, ExtractField)]
pub struct PreferencesDialogMessageHandler {} pub struct PreferencesDialogMessageHandler {}
#[message_handler_data]
impl MessageHandler<PreferencesDialogMessage, PreferencesDialogMessageData<'_>> for PreferencesDialogMessageHandler { impl MessageHandler<PreferencesDialogMessage, PreferencesDialogMessageData<'_>> for PreferencesDialogMessageHandler {
fn process_message(&mut self, message: PreferencesDialogMessage, responses: &mut VecDeque<Message>, data: PreferencesDialogMessageData) { fn process_message(&mut self, message: PreferencesDialogMessage, responses: &mut VecDeque<Message>, data: PreferencesDialogMessageData) {
let PreferencesDialogMessageData { preferences } = data; let PreferencesDialogMessageData { preferences } = data;
@@ -1,8 +1,9 @@
use crate::messages::prelude::*; use crate::messages::prelude::*;
#[derive(Debug, Default)] #[derive(Debug, Default, ExtractField)]
pub struct GlobalsMessageHandler {} pub struct GlobalsMessageHandler {}
#[message_handler_data]
impl MessageHandler<GlobalsMessage, ()> for GlobalsMessageHandler { impl MessageHandler<GlobalsMessage, ()> for GlobalsMessageHandler {
fn process_message(&mut self, message: GlobalsMessage, _responses: &mut VecDeque<Message>, _data: ()) { fn process_message(&mut self, message: GlobalsMessage, _responses: &mut VecDeque<Message>, _data: ()) {
match message { match message {
@@ -6,16 +6,18 @@ use crate::messages::portfolio::utility_types::KeyboardPlatformLayout;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use std::fmt::Write; use std::fmt::Write;
#[derive(ExtractField)]
pub struct InputMapperMessageData<'a> { pub struct InputMapperMessageData<'a> {
pub input: &'a InputPreprocessorMessageHandler, pub input: &'a InputPreprocessorMessageHandler,
pub actions: ActionList, pub actions: ActionList,
} }
#[derive(Debug, Default)] #[derive(Debug, Default, ExtractField)]
pub struct InputMapperMessageHandler { pub struct InputMapperMessageHandler {
mapping: Mapping, mapping: Mapping,
} }
#[message_handler_data]
impl MessageHandler<InputMapperMessage, InputMapperMessageData<'_>> for InputMapperMessageHandler { impl MessageHandler<InputMapperMessage, InputMapperMessageData<'_>> for InputMapperMessageHandler {
fn process_message(&mut self, message: InputMapperMessage, responses: &mut VecDeque<Message>, data: InputMapperMessageData) { fn process_message(&mut self, message: InputMapperMessage, responses: &mut VecDeque<Message>, data: InputMapperMessageData) {
let InputMapperMessageData { input, actions } = data; let InputMapperMessageData { input, actions } = data;
@@ -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::input_mapper::utility_types::input_keyboard::KeysGroup;
use crate::messages::prelude::*; use crate::messages::prelude::*;
#[derive(ExtractField)]
pub struct KeyMappingMessageData<'a> { pub struct KeyMappingMessageData<'a> {
pub input: &'a InputPreprocessorMessageHandler, pub input: &'a InputPreprocessorMessageHandler,
pub actions: ActionList, pub actions: ActionList,
} }
#[derive(Debug, Default)] #[derive(Debug, Default, ExtractField)]
pub struct KeyMappingMessageHandler { pub struct KeyMappingMessageHandler {
mapping_handler: InputMapperMessageHandler, mapping_handler: InputMapperMessageHandler,
} }
#[message_handler_data]
impl MessageHandler<KeyMappingMessage, KeyMappingMessageData<'_>> for KeyMappingMessageHandler { impl MessageHandler<KeyMappingMessage, KeyMappingMessageData<'_>> for KeyMappingMessageHandler {
fn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque<Message>, data: KeyMappingMessageData) { fn process_message(&mut self, message: KeyMappingMessage, responses: &mut VecDeque<Message>, data: KeyMappingMessageData) {
let KeyMappingMessageData { input, actions } = data; let KeyMappingMessageData { input, actions } = data;
@@ -6,11 +6,12 @@ use crate::messages::prelude::*;
use glam::DVec2; use glam::DVec2;
use std::time::Duration; use std::time::Duration;
#[derive(ExtractField)]
pub struct InputPreprocessorMessageData { pub struct InputPreprocessorMessageData {
pub keyboard_platform: KeyboardPlatformLayout, pub keyboard_platform: KeyboardPlatformLayout,
} }
#[derive(Debug, Default)] #[derive(Debug, Default, ExtractField)]
pub struct InputPreprocessorMessageHandler { pub struct InputPreprocessorMessageHandler {
pub frame_time: FrameTimeInfo, pub frame_time: FrameTimeInfo,
pub time: u64, pub time: u64,
@@ -19,6 +20,7 @@ pub struct InputPreprocessorMessageHandler {
pub viewport_bounds: ViewportBounds, pub viewport_bounds: ViewportBounds,
} }
#[message_handler_data]
impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageData> for InputPreprocessorMessageHandler { impl MessageHandler<InputPreprocessorMessage, InputPreprocessorMessageData> for InputPreprocessorMessageHandler {
fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque<Message>, data: InputPreprocessorMessageData) { fn process_message(&mut self, message: InputPreprocessorMessage, responses: &mut VecDeque<Message>, data: InputPreprocessorMessageData) {
let InputPreprocessorMessageData { keyboard_platform } = data; let InputPreprocessorMessageData { keyboard_platform } = data;
@@ -6,7 +6,7 @@ use graphene_std::text::Font;
use graphene_std::vector::style::{FillChoice, GradientStops}; use graphene_std::vector::style::{FillChoice, GradientStops};
use serde_json::Value; use serde_json::Value;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default, ExtractField)]
pub struct LayoutMessageHandler { pub struct LayoutMessageHandler {
layouts: [Layout; LayoutTarget::LayoutTargetLength as usize], layouts: [Layout; LayoutTarget::LayoutTargetLength as usize],
} }
@@ -342,6 +342,15 @@ impl LayoutMessageHandler {
} }
} }
pub fn custom_data() -> MessageData {
// TODO: When <https://github.com/dtolnay/proc-macro2/issues/503> is resolved and released,
// TODO: use <https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.line> to get
// TODO: the line number instead of hardcoding it to the magic number on the following line.
// TODO: Also, utilize the line number in the actual output, since it is currently unused.
MessageData::new(String::from("Function"), vec![(String::from("Fn(&MessageDiscriminant) -> Option<KeysGroup>"), 350)], file!())
}
#[message_handler_data(CustomData)]
impl<F: Fn(&MessageDiscriminant) -> Option<KeysGroup>> MessageHandler<LayoutMessage, F> for LayoutMessageHandler { impl<F: Fn(&MessageDiscriminant) -> Option<KeysGroup>> MessageHandler<LayoutMessage, F> for LayoutMessageHandler {
fn process_message(&mut self, message: LayoutMessage, responses: &mut std::collections::VecDeque<Message>, action_input_mapping: F) { fn process_message(&mut self, message: LayoutMessage, responses: &mut std::collections::VecDeque<Message>, action_input_mapping: F) {
match message { match message {
@@ -471,6 +471,8 @@ pub struct ReferencePointInput {
pub disabled: bool, pub disabled: bool,
pub tooltip: String,
// Callbacks // Callbacks
#[serde(skip)] #[serde(skip)]
#[derivative(Debug = "ignore", PartialEq = "ignore")] #[derivative(Debug = "ignore", PartialEq = "ignore")]
+83
View File
@@ -45,3 +45,86 @@ impl specta::Type for MessageDiscriminant {
specta::DataType::Any 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();
}
}
}
}
@@ -182,6 +182,7 @@ pub enum DocumentMessage {
UpdateUpstreamTransforms { UpdateUpstreamTransforms {
upstream_footprints: HashMap<NodeId, Footprint>, upstream_footprints: HashMap<NodeId, Footprint>,
local_transforms: HashMap<NodeId, DAffine2>, local_transforms: HashMap<NodeId, DAffine2>,
first_instance_source_id: HashMap<NodeId, Option<NodeId>>,
}, },
UpdateClickTargets { UpdateClickTargets {
click_targets: HashMap<NodeId, Vec<ClickTarget>>, click_targets: HashMap<NodeId, Vec<ClickTarget>>,
@@ -38,6 +38,7 @@ use graphene_std::vector::click_target::{ClickTarget, ClickTargetType};
use graphene_std::vector::style::ViewMode; use graphene_std::vector::style::ViewMode;
use std::time::Duration; use std::time::Duration;
#[derive(ExtractField)]
pub struct DocumentMessageData<'a> { pub struct DocumentMessageData<'a> {
pub document_id: DocumentId, pub document_id: DocumentId,
pub ipp: &'a InputPreprocessorMessageHandler, pub ipp: &'a InputPreprocessorMessageHandler,
@@ -48,7 +49,7 @@ pub struct DocumentMessageData<'a> {
pub device_pixel_ratio: f64, pub device_pixel_ratio: f64,
} }
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ExtractField)]
#[serde(default)] #[serde(default)]
pub struct DocumentMessageHandler { pub struct DocumentMessageHandler {
// ====================== // ======================
@@ -168,6 +169,7 @@ impl Default for DocumentMessageHandler {
} }
} }
#[message_handler_data]
impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessageHandler { impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessageHandler {
fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque<Message>, data: DocumentMessageData) { fn process_message(&mut self, message: DocumentMessage, responses: &mut VecDeque<Message>, data: DocumentMessageData) {
let DocumentMessageData { let DocumentMessageData {
@@ -750,6 +752,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
// Nudge translation without resizing // Nudge translation without resizing
if !resize { if !resize {
let transform = DAffine2::from_translation(DVec2::from_angle(-self.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y))); let transform = DAffine2::from_translation(DVec2::from_angle(-self.document_ptz.tilt()).rotate(DVec2::new(delta_x, delta_y)));
responses.add(SelectToolMessage::ShiftSelectedNodes { offset: transform.translation });
for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) { for layer in self.network_interface.shallowest_unique_layers(&[]).filter(|layer| can_move(*layer)) {
responses.add(GraphOperationMessage::TransformChange { responses.add(GraphOperationMessage::TransformChange {
@@ -1185,6 +1188,7 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
OverlaysType::HoverOutline => visibility_settings.hover_outline = visible, OverlaysType::HoverOutline => visibility_settings.hover_outline = visible,
OverlaysType::SelectionOutline => visibility_settings.selection_outline = visible, OverlaysType::SelectionOutline => visibility_settings.selection_outline = visible,
OverlaysType::Pivot => visibility_settings.pivot = visible, OverlaysType::Pivot => visibility_settings.pivot = visible,
OverlaysType::Origin => visibility_settings.origin = visible,
OverlaysType::Path => visibility_settings.path = visible, OverlaysType::Path => visibility_settings.path = visible,
OverlaysType::Anchors => { OverlaysType::Anchors => {
visibility_settings.anchors = visible; visibility_settings.anchors = visible;
@@ -1305,8 +1309,10 @@ impl MessageHandler<DocumentMessage, DocumentMessageData<'_>> for DocumentMessag
DocumentMessage::UpdateUpstreamTransforms { DocumentMessage::UpdateUpstreamTransforms {
upstream_footprints, upstream_footprints,
local_transforms, local_transforms,
first_instance_source_id,
} => { } => {
self.network_interface.update_transforms(upstream_footprints, local_transforms); self.network_interface.update_transforms(upstream_footprints, local_transforms);
self.network_interface.update_first_instance_source_id(first_instance_source_id);
} }
DocumentMessage::UpdateClickTargets { click_targets } => { DocumentMessage::UpdateClickTargets { click_targets } => {
// TODO: Allow non layer nodes to have click targets // TODO: Allow non layer nodes to have click targets
@@ -1714,6 +1720,14 @@ impl DocumentMessageHandler {
.reduce(graphene_std::renderer::Quad::combine_bounds) .reduce(graphene_std::renderer::Quad::combine_bounds)
} }
pub fn selected_visible_and_unlock_layers_bounding_box_document(&self) -> Option<[DVec2; 2]> {
self.network_interface
.selected_nodes()
.selected_visible_and_unlocked_layers(&self.network_interface)
.map(|layer| self.metadata().nonzero_bounding_box(layer))
.reduce(graphene_std::renderer::Quad::combine_bounds)
}
pub fn document_network(&self) -> &NodeNetwork { pub fn document_network(&self) -> &NodeNetwork {
self.network_interface.document_network() self.network_interface.document_network()
} }
@@ -2267,6 +2281,24 @@ impl DocumentMessageHandler {
] ]
}, },
}, },
LayoutGroup::Row {
widgets: {
let mut checkbox_id = CheckboxId::default();
vec![
CheckboxInput::new(self.overlays_visibility_settings.pivot)
.on_update(|optional_input: &CheckboxInput| {
DocumentMessage::SetOverlaysVisibility {
visible: optional_input.checked,
overlays_type: Some(OverlaysType::Origin),
}
.into()
})
.for_label(checkbox_id.clone())
.widget_holder(),
TextLabel::new("Transform Origin".to_string()).for_checkbox(&mut checkbox_id).widget_holder(),
]
},
},
LayoutGroup::Row { LayoutGroup::Row {
widgets: { widgets: {
let mut checkbox_id = CheckboxId::default(); let mut checkbox_id = CheckboxId::default();
@@ -3,7 +3,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate; use crate::messages::portfolio::document::utility_types::network_interface::NodeTemplate;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use bezier_rs::Subpath; use bezier_rs::Subpath;
use glam::{DAffine2, DVec2, IVec2}; use glam::{DAffine2, IVec2};
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
use graphene_std::Artboard; use graphene_std::Artboard;
use graphene_std::brush::brush_stroke::BrushStroke; use graphene_std::brush::brush_stroke::BrushStroke;
@@ -52,10 +52,6 @@ pub enum GraphOperationMessage {
transform_in: TransformIn, transform_in: TransformIn,
skip_rerender: bool, skip_rerender: bool,
}, },
TransformSetPivot {
layer: LayerNodeIdentifier,
pivot: DVec2,
},
Vector { Vector {
layer: LayerNodeIdentifier, layer: LayerNodeIdentifier,
modification_type: VectorModificationType, modification_type: VectorModificationType,
@@ -21,17 +21,19 @@ struct ArtboardInfo {
merge_node: NodeId, merge_node: NodeId,
} }
#[derive(ExtractField)]
pub struct GraphOperationMessageData<'a> { pub struct GraphOperationMessageData<'a> {
pub network_interface: &'a mut NodeNetworkInterface, pub network_interface: &'a mut NodeNetworkInterface,
pub collapsed: &'a mut CollapsedLayers, pub collapsed: &'a mut CollapsedLayers,
pub node_graph: &'a mut NodeGraphMessageHandler, pub node_graph: &'a mut NodeGraphMessageHandler,
} }
#[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)] #[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize, ExtractField)]
pub struct GraphOperationMessageHandler {} pub struct GraphOperationMessageHandler {}
// GraphOperationMessageHandler always modified the document network. This is so changes to the layers panel will only affect the document network. // GraphOperationMessageHandler always modified the document network. This is so changes to the layers panel will only affect the document network.
// For changes to the selected network, use NodeGraphMessageHandler. No NodeGraphMessage's should be added here, since they will affect the selected nested network. // For changes to the selected network, use NodeGraphMessageHandler. No NodeGraphMessage's should be added here, since they will affect the selected nested network.
#[message_handler_data]
impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for GraphOperationMessageHandler { impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for GraphOperationMessageHandler {
fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, data: GraphOperationMessageData) { fn process_message(&mut self, message: GraphOperationMessage, responses: &mut VecDeque<Message>, data: GraphOperationMessageData) {
let network_interface = data.network_interface; let network_interface = data.network_interface;
@@ -89,15 +91,6 @@ impl MessageHandler<GraphOperationMessage, GraphOperationMessageData<'_>> for Gr
modify_inputs.transform_set(transform, transform_in, skip_rerender); modify_inputs.transform_set(transform, transform_in, skip_rerender);
} }
} }
GraphOperationMessage::TransformSetPivot { layer, pivot } => {
if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run TransformSetPivot on ROOT_PARENT");
return;
}
if let Some(mut modify_inputs) = ModifyInputsContext::new_with_layer(layer, network_interface, responses) {
modify_inputs.pivot_set(pivot);
}
}
GraphOperationMessage::Vector { layer, modification_type } => { GraphOperationMessage::Vector { layer, modification_type } => {
if layer == LayerNodeIdentifier::ROOT_PARENT { if layer == LayerNodeIdentifier::ROOT_PARENT {
log::error!("Cannot run Vector on ROOT_PARENT"); log::error!("Cannot run Vector on ROOT_PARENT");
@@ -4,7 +4,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector}; use crate::messages::portfolio::document::utility_types::network_interface::{self, InputConnector, NodeNetworkInterface, OutputConnector};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use bezier_rs::Subpath; use bezier_rs::Subpath;
use glam::{DAffine2, DVec2, IVec2}; use glam::{DAffine2, IVec2};
use graph_craft::concrete; use graph_craft::concrete;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput}; use graph_craft::document::{NodeId, NodeInput};
@@ -458,12 +458,6 @@ impl<'a> ModifyInputsContext<'a> {
} }
} }
pub fn pivot_set(&mut self, new_pivot: DVec2) {
let Some(transform_node_id) = self.existing_node_id("Transform", true) else { return };
self.set_input_with_refresh(InputConnector::node(transform_node_id, 5), NodeInput::value(TaggedValue::DVec2(new_pivot), false), false);
}
pub fn vector_modify(&mut self, modification_type: VectorModificationType) { pub fn vector_modify(&mut self, modification_type: VectorModificationType) {
let Some(path_node_id) = self.existing_node_id("Path", true) else { return }; let Some(path_node_id) = self.existing_node_id("Path", true) else { return };
self.network_interface.vector_modify(&path_node_id, modification_type); self.network_interface.vector_modify(&path_node_id, modification_type);
@@ -13,6 +13,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
#[derive(ExtractField)]
pub struct NavigationMessageData<'a> { pub struct NavigationMessageData<'a> {
pub network_interface: &'a mut NodeNetworkInterface, pub network_interface: &'a mut NodeNetworkInterface,
pub breadcrumb_network_path: &'a [NodeId], pub breadcrumb_network_path: &'a [NodeId],
@@ -23,7 +24,7 @@ pub struct NavigationMessageData<'a> {
pub preferences: &'a PreferencesMessageHandler, pub preferences: &'a PreferencesMessageHandler,
} }
#[derive(Debug, Clone, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Default, ExtractField)]
pub struct NavigationMessageHandler { pub struct NavigationMessageHandler {
navigation_operation: NavigationOperation, navigation_operation: NavigationOperation,
mouse_position: ViewportPosition, mouse_position: ViewportPosition,
@@ -31,6 +32,7 @@ pub struct NavigationMessageHandler {
abortable_pan_start: Option<f64>, abortable_pan_start: Option<f64>,
} }
#[message_handler_data]
impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for NavigationMessageHandler { impl MessageHandler<NavigationMessage, NavigationMessageData<'_>> for NavigationMessageHandler {
fn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque<Message>, data: NavigationMessageData) { fn process_message(&mut self, message: NavigationMessage, responses: &mut VecDeque<Message>, data: NavigationMessageData) {
let NavigationMessageData { let NavigationMessageData {
@@ -21,6 +21,7 @@ use graphene_std::extract_xy::XY;
use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha}; use graphene_std::raster::{CellularDistanceFunction, CellularReturnType, Color, DomainWarpType, FractalType, NoiseType, RedGreenBlueAlpha};
use graphene_std::raster_types::{CPU, RasterDataTable}; use graphene_std::raster_types::{CPU, RasterDataTable};
use graphene_std::text::{Font, TypesettingConfig}; use graphene_std::text::{Font, TypesettingConfig};
#[allow(unused_imports)]
use graphene_std::transform::Footprint; use graphene_std::transform::Footprint;
use graphene_std::vector::VectorDataTable; use graphene_std::vector::VectorDataTable;
use graphene_std::*; use graphene_std::*;
@@ -88,7 +89,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
category: "General", category: "General",
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"), implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::None, true)], inputs: vec![NodeInput::value(TaggedValue::None, true)],
..Default::default() ..Default::default()
}, },
@@ -107,7 +108,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
category: "Debug", category: "Debug",
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::None, true)], inputs: vec![NodeInput::value(TaggedValue::None, true)],
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
skip_deduplication: true, skip_deduplication: true,
@@ -148,19 +149,19 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 0)], inputs: vec![NodeInput::network(generic!(T), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)], inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -230,21 +231,21 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// Secondary (left) input type coercion // Secondary (left) input type coercion
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 1)], inputs: vec![NodeInput::network(generic!(T), 1)],
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToElementNode"), implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_element::IDENTIFIER),
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
..Default::default() ..Default::default()
}, },
// Primary (bottom) input type coercion // Primary (bottom) input type coercion
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 0)], inputs: vec![NodeInput::network(generic!(T), 0)],
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToGroupNode"), implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_group::IDENTIFIER),
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
..Default::default() ..Default::default()
}, },
// The monitor node is used to display a thumbnail in the UI // The monitor node is used to display a thumbnail in the UI
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
@@ -256,7 +257,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::node(NodeId(2), 0), NodeInput::node(NodeId(2), 0),
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
], ],
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::LayerNode"), implementation: DocumentNodeImplementation::ProtoNode(graphic_element::layer::IDENTIFIER),
..Default::default() ..Default::default()
}, },
] ]
@@ -337,7 +338,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// Ensure this ID is kept in sync with the ID in set_alias so that the name input is kept in sync with the alias // Ensure this ID is kept in sync with the ID in set_alias so that the name input is kept in sync with the alias
DocumentNode { DocumentNode {
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::ToArtboardNode"), implementation: DocumentNodeImplementation::ProtoNode(graphic_element::to_artboard::IDENTIFIER),
inputs: vec![ inputs: vec![
NodeInput::network(concrete!(TaggedValue), 1), NodeInput::network(concrete!(TaggedValue), 1),
NodeInput::value(TaggedValue::String(String::from("Artboard")), false), NodeInput::value(TaggedValue::String(String::from("Artboard")), false),
@@ -352,7 +353,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
// TODO: Check if thumbnail is reversed // TODO: Check if thumbnail is reversed
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
@@ -364,7 +365,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::node(NodeId(1), 0), NodeInput::node(NodeId(1), 0),
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath), NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
], ],
implementation: DocumentNodeImplementation::proto("graphene_core::graphic_element::AppendArtboardNode"), implementation: DocumentNodeImplementation::ProtoNode(graphic_element::append_artboard::IDENTIFIER),
..Default::default() ..Default::default()
}, },
] ]
@@ -466,13 +467,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode { DocumentNode {
inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)], inputs: vec![NodeInput::value(TaggedValue::None, false), NodeInput::scope("editor-api"), NodeInput::network(concrete!(String), 1)],
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::LoadResourceNode")), implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::load_resource::IDENTIFIER),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::DecodeImageNode")), implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::decode_image::IDENTIFIER),
..Default::default() ..Default::default()
}, },
] ]
@@ -522,6 +523,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("Loads an image from a given URL"), description: Cow::Borrowed("Loads an image from a given URL"),
properties: None, properties: None,
}, },
#[cfg(feature = "gpu")]
DocumentNodeDefinition { DocumentNodeDefinition {
identifier: "Create Canvas", identifier: "Create Canvas",
category: "Debug: GPU", category: "Debug: GPU",
@@ -532,14 +534,14 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::scope("editor-api")], inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
..Default::default() ..Default::default()
}, },
] ]
@@ -587,99 +589,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("Creates a new canvas object."), description: Cow::Borrowed("Creates a new canvas object."),
properties: None, properties: None,
}, },
DocumentNodeDefinition { #[cfg(all(feature = "gpu", target_arch = "wasm32"))]
identifier: "Draw Canvas",
category: "Debug: GPU",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(3), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<_, RasterDataTable<SRGBA8>>")),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")),
skip_deduplication: true,
..Default::default()
},
DocumentNode {
manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::DrawImageFrameNode")),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("In", "TODO").into()],
output_names: vec!["Canvas".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Into".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Create Canvas".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 2)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Cache".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 2)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Draw Canvas".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
..Default::default()
},
},
description: Cow::Borrowed("Draws raster data to a canvas element."),
properties: None,
},
DocumentNodeDefinition { DocumentNodeDefinition {
identifier: "Rasterize", identifier: "Rasterize",
category: "Raster", category: "Raster",
@@ -690,20 +600,20 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::scope("editor-api")], inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::CreateSurfaceNode")), implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::create_surface::IDENTIFIER),
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::network(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)], inputs: vec![NodeInput::network(generic!(T), 0), NodeInput::network(concrete!(Footprint), 1), NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_std::wasm_application_io::RasterizeNode")), implementation: DocumentNodeImplementation::ProtoNode(wasm_application_io::rasterize::IDENTIFIER),
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
..Default::default() ..Default::default()
}, },
@@ -778,7 +688,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_raster_nodes::std_nodes::NoisePatternNode")), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::std_nodes::noise_pattern::IDENTIFIER),
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::None, false), NodeInput::value(TaggedValue::None, false),
NodeInput::value(TaggedValue::Bool(true), false), NodeInput::value(TaggedValue::Bool(true), false),
@@ -843,7 +753,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Red), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -852,7 +762,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Green), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -861,7 +771,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Blue), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -870,7 +780,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::network(concrete!(RasterDataTable<CPU>), 0),
NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false), NodeInput::value(TaggedValue::RedGreenBlueAlpha(RedGreenBlueAlpha::Alpha), false),
], ],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::raster::adjustments::ExtractChannelNode")), implementation: DocumentNodeImplementation::ProtoNode(raster_nodes::adjustments::extract_channel::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -948,13 +858,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)], inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::X), false)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::extract_xy::ExtractXyNode")), implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)], inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::value(TaggedValue::XY(XY::Y), false)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::extract_xy::ExtractXyNode")), implementation: DocumentNodeImplementation::ProtoNode(extract_xy::extract_xy::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -1024,7 +934,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(BrushCache), 2), NodeInput::network(concrete!(BrushCache), 2),
], ],
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_brush::brush::BrushNode")), implementation: DocumentNodeImplementation::ProtoNode(brush::brush::brush::IDENTIFIER),
..Default::default() ..Default::default()
}] }]
.into_iter() .into_iter()
@@ -1036,7 +946,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
inputs: vec![ inputs: vec![
NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true), NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true),
NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false), NodeInput::value(TaggedValue::BrushStrokes(Vec::new()), false),
NodeInput::value(TaggedValue::BrushCache(BrushCache::new_proto()), false), NodeInput::value(TaggedValue::BrushCache(BrushCache::default()), false),
], ],
..Default::default() ..Default::default()
}, },
@@ -1072,7 +982,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
category: "Debug", category: "Debug",
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MemoNode"), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)], inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
..Default::default() ..Default::default()
@@ -1091,7 +1001,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
category: "Debug", category: "Debug",
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::memo::ImpureMemoNode"), implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)], inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
..Default::default() ..Default::default()
@@ -1105,164 +1015,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("TODO"), description: Cow::Borrowed("TODO"),
properties: None, properties: None,
}, },
DocumentNodeDefinition {
identifier: "Storage",
category: "Debug: GPU",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(2), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode")),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::network(concrete!(Vec<u8>), 0), NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::StorageNode")),
..Default::default()
},
DocumentNode {
manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::None, true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("In", "TODO").into()],
output_names: vec!["Storage".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Extract Executor".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Create Storage".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Cache".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
DocumentNodeDefinition {
identifier: "Create Output Buffer",
category: "Debug: GPU",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(2), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode")),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::network(concrete!(usize), 0), NodeInput::node(NodeId(0), 0), NodeInput::network(concrete!(Type), 1)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateOutputBufferNode")),
..Default::default()
},
DocumentNode {
manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::None, true), NodeInput::value(TaggedValue::None, true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("In", "TODO").into(), ("In", "TODO").into()],
output_names: vec!["Output Buffer".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Extract Executor".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Create Output Buffer".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Cache".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
#[cfg(feature = "gpu")] #[cfg(feature = "gpu")]
DocumentNodeDefinition { DocumentNodeDefinition {
identifier: "Create GPU Surface", identifier: "Create GPU Surface",
@@ -1275,13 +1027,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
DocumentNode { DocumentNode {
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::scope("editor-api")], inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::CreateGpuSurfaceNode")), implementation: DocumentNodeImplementation::ProtoNode(wgpu_executor::create_gpu_surface::IDENTIFIER),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode")), implementation: DocumentNodeImplementation::ProtoNode(memo::impure_memo::IDENTIFIER),
..Default::default() ..Default::default()
}, },
] ]
@@ -1329,87 +1081,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
description: Cow::Borrowed("TODO"), description: Cow::Borrowed("TODO"),
properties: None, properties: None,
}, },
#[cfg(feature = "gpu")]
DocumentNodeDefinition {
identifier: "Upload Texture",
category: "Debug: GPU",
node_template: NodeTemplate {
document_node: DocumentNode {
implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(2), 0)],
nodes: [
DocumentNode {
inputs: vec![NodeInput::scope("editor-api")],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IntoNode<&WgpuExecutor>")),
..Default::default()
},
DocumentNode {
inputs: vec![NodeInput::network(concrete!(RasterDataTable<CPU>), 0), NodeInput::node(NodeId(0), 0)],
manual_composition: Some(generic!(T)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("wgpu_executor::UploadTextureNode")),
..Default::default()
},
DocumentNode {
manual_composition: Some(generic!(T)),
inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode")),
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
}),
inputs: vec![NodeInput::value(TaggedValue::RasterData(RasterDataTable::default()), true)],
..Default::default()
},
persistent_node_metadata: DocumentNodePersistentMetadata {
input_metadata: vec![("In", "TODO").into()],
output_names: vec!["Texture".to_string()],
network_metadata: Some(NodeNetworkMetadata {
persistent_metadata: NodeNetworkPersistentMetadata {
node_metadata: [
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Extract Executor".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(0, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Upload Texture".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(7, 0)),
..Default::default()
},
..Default::default()
},
DocumentNodeMetadata {
persistent_metadata: DocumentNodePersistentMetadata {
display_name: "Cache".to_string(),
node_type_metadata: NodeTypePersistentMetadata::node(IVec2::new(14, 0)),
..Default::default()
},
..Default::default()
},
]
.into_iter()
.enumerate()
.map(|(id, node)| (NodeId(id as u64), node))
.collect(),
..Default::default()
},
..Default::default()
}),
..Default::default()
},
},
description: Cow::Borrowed("TODO"),
properties: None,
},
DocumentNodeDefinition { DocumentNodeDefinition {
identifier: "Extract", identifier: "Extract",
category: "Debug", category: "Debug",
@@ -1466,15 +1137,19 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: vec![ nodes: vec![
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)], inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)],
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0), NodeInput::network(concrete!(graphene_std::vector::VectorModification), 1)], inputs: vec![
NodeInput::node(NodeId(0), 0),
NodeInput::network(concrete!(graphene_std::vector::VectorModification), 1),
NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath),
],
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::vector_data::modification::PathModifyNode")), implementation: DocumentNodeImplementation::ProtoNode(vector::path_modify::IDENTIFIER),
..Default::default() ..Default::default()
}, },
] ]
@@ -1532,7 +1207,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
category: "Text", category: "Text",
node_template: NodeTemplate { node_template: NodeTemplate {
document_node: DocumentNode { document_node: DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_std::text::TextNode"), implementation: DocumentNodeImplementation::ProtoNode(text::text::IDENTIFIER),
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
inputs: vec![ inputs: vec![
NodeInput::scope("editor-api"), NodeInput::scope("editor-api"),
@@ -1633,14 +1308,13 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::value(TaggedValue::F64(0.), false), NodeInput::value(TaggedValue::F64(0.), false),
NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false), NodeInput::value(TaggedValue::DVec2(DVec2::ONE), false),
NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false), NodeInput::value(TaggedValue::DVec2(DVec2::ZERO), false),
NodeInput::value(TaggedValue::DVec2(DVec2::splat(0.5)), false),
], ],
implementation: DocumentNodeImplementation::Network(NodeNetwork { implementation: DocumentNodeImplementation::Network(NodeNetwork {
exports: vec![NodeInput::node(NodeId(1), 0)], exports: vec![NodeInput::node(NodeId(1), 0)],
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)], inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0)],
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), implementation: DocumentNodeImplementation::ProtoNode(memo::monitor::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
@@ -1652,10 +1326,9 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(f64), 2), NodeInput::network(concrete!(f64), 2),
NodeInput::network(concrete!(DVec2), 3), NodeInput::network(concrete!(DVec2), 3),
NodeInput::network(concrete!(DVec2), 4), NodeInput::network(concrete!(DVec2), 4),
NodeInput::network(concrete!(DVec2), 5),
], ],
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::TransformNode")), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::transform::IDENTIFIER),
..Default::default() ..Default::default()
}, },
] ]
@@ -1720,7 +1393,6 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
}), }),
), ),
InputMetadata::with_name_description_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())), InputMetadata::with_name_description_override("Skew", "TODO", WidgetOverride::Custom("transform_skew".to_string())),
InputMetadata::with_name_description_override("Pivot", "TODO", WidgetOverride::Hidden),
], ],
output_names: vec!["Data".to_string()], output_names: vec!["Data".to_string()],
..Default::default() ..Default::default()
@@ -1739,25 +1411,25 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: vec![ nodes: vec![
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)], inputs: vec![NodeInput::network(concrete!(VectorDataTable), 0), NodeInput::network(concrete!(vector::style::Fill), 1)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_path_bool::BooleanOperationNode")), implementation: DocumentNodeImplementation::ProtoNode(path_bool::boolean_operation::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)], inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(2), 0)], inputs: vec![NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -1837,7 +1509,7 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
nodes: [ nodes: [
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)], inputs: vec![NodeInput::network(concrete!(graphene_std::vector::VectorDataTable), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SubpathSegmentLengthsNode")), implementation: DocumentNodeImplementation::ProtoNode(vector::subpath_segment_lengths::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -1852,25 +1524,25 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(bool), 6), NodeInput::network(concrete!(bool), 6),
NodeInput::node(NodeId(0), 0), NodeInput::node(NodeId(0), 0),
], ],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::vector::SamplePolylineNode")), implementation: DocumentNodeImplementation::ProtoNode(vector::sample_polyline::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)], inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(2), 0)], inputs: vec![NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(3), 0)], inputs: vec![NodeInput::node(NodeId(3), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -2008,24 +1680,24 @@ fn static_nodes() -> Vec<DocumentNodeDefinition> {
NodeInput::network(concrete!(u32), 2), NodeInput::network(concrete!(u32), 2),
], ],
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
implementation: DocumentNodeImplementation::proto("graphene_core::vector::PoissonDiskPointsNode"), implementation: DocumentNodeImplementation::ProtoNode(vector::poisson_disk_points::IDENTIFIER),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), implementation: DocumentNodeImplementation::ProtoNode(memo::memo::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(1), 0)], inputs: vec![NodeInput::node(NodeId(1), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::FreezeRealTimeNode")), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::freeze_real_time::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
DocumentNode { DocumentNode {
inputs: vec![NodeInput::node(NodeId(2), 0)], inputs: vec![NodeInput::node(NodeId(2), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::transform_nodes::BoundlessFootprintNode")), implementation: DocumentNodeImplementation::ProtoNode(transform_nodes::boundless_footprint::IDENTIFIER),
manual_composition: Some(generic!(T)), manual_composition: Some(generic!(T)),
..Default::default() ..Default::default()
}, },
@@ -2645,11 +2317,11 @@ pub fn resolve_document_node_type(identifier: &str) -> Option<&DocumentNodeDefin
pub fn collect_node_types() -> Vec<FrontendNodeType> { pub fn collect_node_types() -> Vec<FrontendNodeType> {
// Create a mapping from registry ID to document node identifier // Create a mapping from registry ID to document node identifier
let id_to_identifier_map: HashMap<String, &'static str> = DOCUMENT_NODE_TYPES let id_to_identifier_map: HashMap<ProtoNodeIdentifier, &'static str> = DOCUMENT_NODE_TYPES
.iter() .iter()
.filter_map(|definition| { .filter_map(|definition| {
if let DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) = &definition.node_template.document_node.implementation { if let DocumentNodeImplementation::ProtoNode(name) = &definition.node_template.document_node.implementation {
Some((name.to_string(), definition.identifier)) Some((name.clone(), definition.identifier))
} else { } else {
None None
} }
@@ -2657,28 +2329,28 @@ pub fn collect_node_types() -> Vec<FrontendNodeType> {
.collect(); .collect();
let mut extracted_node_types = Vec::new(); let mut extracted_node_types = Vec::new();
let node_registry = graphene_std::registry::NODE_REGISTRY.lock().unwrap(); let node_registry = registry::NODE_REGISTRY.lock().unwrap();
let node_metadata = graphene_std::registry::NODE_METADATA.lock().unwrap(); let node_metadata = registry::NODE_METADATA.lock().unwrap();
for (id, metadata) in node_metadata.iter() { for (id, metadata) in node_metadata.iter() {
if let Some(implementations) = node_registry.get(id) { if let Some(implementations) = node_registry.get(id) {
let identifier = match id_to_identifier_map.get(id) { let identifier = match id_to_identifier_map.get(id) {
Some(&id) => id.to_string(), Some(&id) => id,
None => continue, None => continue,
}; };
// Extract category from metadata (already creates an owned String) // Extract category from metadata (already creates an owned String)
let category = metadata.category.unwrap_or_default().to_string(); let category = metadata.category.unwrap_or_default();
// Extract input types (already creates owned Strings) // Extract input types (already creates owned Strings)
let input_types = implementations let input_types = implementations
.iter() .iter()
.flat_map(|(_, node_io)| node_io.inputs.iter().map(|ty| ty.nested_type().to_string())) .flat_map(|(_, node_io)| node_io.inputs.iter().map(|ty| ty.nested_type().to_cow_string()))
.collect::<HashSet<String>>() .collect::<HashSet<Cow<'static, str>>>()
.into_iter() .into_iter()
.collect::<Vec<String>>(); .collect::<Vec<Cow<'static, str>>>();
// Create a FrontendNodeType // Create a FrontendNodeType
let node_type = FrontendNodeType::with_owned_strings_and_input_types(identifier, category, input_types); let node_type = FrontendNodeType::with_input_types(identifier, category, input_types);
// Store the created node_type // Store the created node_type
extracted_node_types.push(node_type); extracted_node_types.push(node_type);
@@ -2694,8 +2366,8 @@ pub fn collect_node_types() -> Vec<FrontendNodeType> {
.document_node .document_node
.inputs .inputs
.iter() .iter()
.filter_map(|node_input| node_input.as_value().map(|node_value| node_value.ty().nested_type().to_string())) .filter_map(|node_input| node_input.as_value().map(|node_value| node_value.ty().nested_type().to_cow_string()))
.collect::<Vec<String>>(); .collect::<Vec<Cow<'static, str>>>();
FrontendNodeType::with_input_types(definition.identifier, definition.category, input_types) FrontendNodeType::with_input_types(definition.identifier, definition.category, input_types)
}) })
@@ -21,7 +21,7 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
}; };
} }
let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap(); let node_registry = NODE_REGISTRY.lock().unwrap();
'outer: for (id, metadata) in NODE_METADATA.lock().unwrap().iter() { 'outer: for (id, metadata) in NODE_METADATA.lock().unwrap().iter() {
for node in custom.iter() { for node in custom.iter() {
let DocumentNodeDefinition { let DocumentNodeDefinition {
@@ -32,7 +32,7 @@ pub(super) fn post_process_nodes(mut custom: Vec<DocumentNodeDefinition>) -> Vec
.. ..
} = node; } = node;
match implementation { match implementation {
DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier { name }) if name == id => continue 'outer, DocumentNodeImplementation::ProtoNode(name) if name == id => continue 'outer,
_ => (), _ => (),
} }
} }
@@ -27,7 +27,7 @@ use graphene_std::*;
use renderer::Quad; use renderer::Quad;
use std::cmp::Ordering; use std::cmp::Ordering;
#[derive(Debug)] #[derive(Debug, ExtractField)]
pub struct NodeGraphHandlerData<'a> { pub struct NodeGraphHandlerData<'a> {
pub network_interface: &'a mut NodeNetworkInterface, pub network_interface: &'a mut NodeNetworkInterface,
pub selection_network_path: &'a [NodeId], pub selection_network_path: &'a [NodeId],
@@ -41,7 +41,7 @@ pub struct NodeGraphHandlerData<'a> {
pub preferences: &'a PreferencesMessageHandler, pub preferences: &'a PreferencesMessageHandler,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, ExtractField)]
pub struct NodeGraphMessageHandler { pub struct NodeGraphMessageHandler {
// TODO: Remove network and move to NodeNetworkInterface // TODO: Remove network and move to NodeNetworkInterface
pub network: Vec<NodeId>, pub network: Vec<NodeId>,
@@ -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. /// 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<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGraphMessageHandler { impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGraphMessageHandler {
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, data: NodeGraphHandlerData<'a>) { fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, data: NodeGraphHandlerData<'a>) {
let NodeGraphHandlerData { let NodeGraphHandlerData {
@@ -1274,6 +1274,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()]; let mut corner_radius_row_2 = vec![Separator::new(SeparatorType::Unrelated).widget_holder()];
corner_radius_row_2.push(TextLabel::new("").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) { let document_node = match get_document_node(node_id, context) {
Ok(document_node) => document_node, Ok(document_node) => document_node,
@@ -1381,8 +1382,6 @@ pub(crate) fn rectangle_properties(node_id: NodeId, context: &mut NodeProperties
// Size Y // Size Y
let size_y = number_widget(ParameterWidgetsInfo::new(node_id, HeightInput::INDEX, true, context), NumberInput::default()); let size_y = number_widget(ParameterWidgetsInfo::new(node_id, HeightInput::INDEX, true, context), NumberInput::default());
add_blank_assist(&mut corner_radius_row_2);
// Clamped // Clamped
let clamped = bool_widget(ParameterWidgetsInfo::new(node_id, ClampedInput::INDEX, true, context), CheckboxInput::default()); let clamped = bool_widget(ParameterWidgetsInfo::new(node_id, ClampedInput::INDEX, true, context), CheckboxInput::default());
@@ -1436,7 +1435,7 @@ pub(crate) fn generate_node_properties(node_id: NodeId, context: &mut NodeProper
if let Some(field) = graphene_std::registry::NODE_METADATA if let Some(field) = graphene_std::registry::NODE_METADATA
.lock() .lock()
.unwrap() .unwrap()
.get(&proto_node_identifier.name.clone().into_owned()) .get(proto_node_identifier)
.and_then(|metadata| metadata.fields.get(input_index)) .and_then(|metadata| metadata.fields.get(input_index))
{ {
number_options = (field.number_min, field.number_max, field.number_mode_range); number_options = (field.number_min, field.number_max, field.number_mode_range);
@@ -2,6 +2,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Inp
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graphene_std::Type; use graphene_std::Type;
use std::borrow::Cow;
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum FrontendGraphDataType { pub enum FrontendGraphDataType {
@@ -98,33 +99,25 @@ pub struct FrontendNode {
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNodeType { pub struct FrontendNodeType {
pub name: String, pub name: Cow<'static, str>,
pub category: String, pub category: Cow<'static, str>,
#[serde(rename = "inputTypes")] #[serde(rename = "inputTypes")]
pub input_types: Option<Vec<String>>, pub input_types: Option<Vec<Cow<'static, str>>>,
} }
impl FrontendNodeType { impl FrontendNodeType {
pub fn new(name: &'static str, category: &'static str) -> Self { pub fn new(name: impl Into<Cow<'static, str>>, category: impl Into<Cow<'static, str>>) -> Self {
Self { Self {
name: name.to_string(), name: name.into(),
category: category.to_string(), category: category.into(),
input_types: None, input_types: None,
} }
} }
pub fn with_input_types(name: &'static str, category: &'static str, input_types: Vec<String>) -> Self { pub fn with_input_types(name: impl Into<Cow<'static, str>>, category: impl Into<Cow<'static, str>>, input_types: Vec<Cow<'static, str>>) -> Self {
Self { Self {
name: name.to_string(), name: name.into(),
category: category.to_string(), category: category.into(),
input_types: Some(input_types),
}
}
pub fn with_owned_strings_and_input_types(name: String, category: String, input_types: Vec<String>) -> Self {
Self {
name,
category,
input_types: Some(input_types), input_types: Some(input_types),
} }
} }
@@ -198,7 +191,7 @@ pub struct FrontendClickTargets {
pub modify_import_export: Vec<String>, pub modify_import_export: Vec<String>,
} }
#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum Direction { pub enum Direction {
Up, Up,
Down, Down,
@@ -1,13 +1,14 @@
use super::utility_types::{OverlayProvider, OverlaysVisibilitySettings}; use super::utility_types::{OverlayProvider, OverlaysVisibilitySettings};
use crate::messages::prelude::*; use crate::messages::prelude::*;
#[derive(ExtractField)]
pub struct OverlaysMessageData<'a> { pub struct OverlaysMessageData<'a> {
pub visibility_settings: OverlaysVisibilitySettings, pub visibility_settings: OverlaysVisibilitySettings,
pub ipp: &'a InputPreprocessorMessageHandler, pub ipp: &'a InputPreprocessorMessageHandler,
pub device_pixel_ratio: f64, pub device_pixel_ratio: f64,
} }
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default, ExtractField)]
pub struct OverlaysMessageHandler { pub struct OverlaysMessageHandler {
pub overlay_providers: HashSet<OverlayProvider>, pub overlay_providers: HashSet<OverlayProvider>,
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
@@ -16,6 +17,7 @@ pub struct OverlaysMessageHandler {
context: Option<web_sys::CanvasRenderingContext2d>, context: Option<web_sys::CanvasRenderingContext2d>,
} }
#[message_handler_data]
impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessageHandler { impl MessageHandler<OverlaysMessage, OverlaysMessageData<'_>> for OverlaysMessageHandler {
fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, data: OverlaysMessageData) { fn process_message(&mut self, message: OverlaysMessage, responses: &mut VecDeque<Message>, data: OverlaysMessageData) {
let OverlaysMessageData { visibility_settings, ipp, .. } = data; let OverlaysMessageData { visibility_settings, ipp, .. } = data;
@@ -119,7 +119,7 @@ pub fn path_overlays(document: &DocumentMessageHandler, draw_handles: DrawHandle
for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) { for layer in document.network_interface.selected_nodes().selected_layers(document.metadata()) {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue }; let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { continue };
let transform = document.metadata().transform_to_viewport(layer); let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
if display_path { if display_path {
overlay_context.outline_vector(&vector_data, transform); overlay_context.outline_vector(&vector_data, transform);
} }
@@ -196,7 +196,7 @@ pub fn path_endpoint_overlays(document: &DocumentMessageHandler, shape_editor: &
continue; continue;
}; };
//let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz); //let document_to_viewport = document.navigation_handler.calculate_offset_transform(overlay_context.size / 2., &document.document_ptz);
let transform = document.metadata().transform_to_viewport(layer); let transform = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
let selected = shape_editor.selected_shape_state.get(&layer); let selected = shape_editor.selected_shape_state.get(&layer);
let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point)); let is_selected = |selected: Option<&SelectedLayerState>, point: ManipulatorPointId| selected.is_some_and(|selected| selected.is_point_selected(point));
@@ -1,12 +1,12 @@
use super::utility_functions::overlay_canvas_context; use super::utility_functions::overlay_canvas_context;
use crate::consts::{ use crate::consts::{
COLOR_OVERLAY_BLUE, COLOR_OVERLAY_BLUE_50, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER, COLOR_OVERLAY_BLUE, COLOR_OVERLAY_GREEN, COLOR_OVERLAY_RED, COLOR_OVERLAY_WHITE, COLOR_OVERLAY_YELLOW, COLOR_OVERLAY_YELLOW_DULL, COMPASS_ROSE_ARROW_SIZE, COMPASS_ROSE_HOVER_RING_DIAMETER,
COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER, COMPASS_ROSE_MAIN_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER, DOWEL_PIN_RADIUS, MANIPULATOR_GROUP_MARKER_SIZE, PIVOT_CROSSHAIR_LENGTH, PIVOT_CROSSHAIR_THICKNESS, PIVOT_DIAMETER,
}; };
use crate::messages::prelude::Message; use crate::messages::prelude::Message;
use bezier_rs::{Bezier, Subpath}; use bezier_rs::{Bezier, Subpath};
use core::borrow::Borrow; use core::borrow::Borrow;
use core::f64::consts::{FRAC_PI_2, TAU}; use core::f64::consts::{FRAC_PI_2, PI, TAU};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graphene_std::Color; use graphene_std::Color;
use graphene_std::math::quad::Quad; use graphene_std::math::quad::Quad;
@@ -33,12 +33,14 @@ pub enum OverlaysType {
HoverOutline, HoverOutline,
SelectionOutline, SelectionOutline,
Pivot, Pivot,
Origin,
Path, Path,
Anchors, Anchors,
Handles, Handles,
} }
#[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(PartialEq, Copy, Clone, Debug, serde::Serialize, serde::Deserialize, specta::Type)]
#[serde(default)]
pub struct OverlaysVisibilitySettings { pub struct OverlaysVisibilitySettings {
pub all: bool, pub all: bool,
pub artboard_name: bool, pub artboard_name: bool,
@@ -49,6 +51,7 @@ pub struct OverlaysVisibilitySettings {
pub hover_outline: bool, pub hover_outline: bool,
pub selection_outline: bool, pub selection_outline: bool,
pub pivot: bool, pub pivot: bool,
pub origin: bool,
pub path: bool, pub path: bool,
pub anchors: bool, pub anchors: bool,
pub handles: bool, pub handles: bool,
@@ -66,6 +69,7 @@ impl Default for OverlaysVisibilitySettings {
hover_outline: true, hover_outline: true,
selection_outline: true, selection_outline: true,
pivot: true, pivot: true,
origin: true,
path: true, path: true,
anchors: true, anchors: true,
handles: true, handles: true,
@@ -110,6 +114,10 @@ impl OverlaysVisibilitySettings {
self.all && self.pivot self.all && self.pivot
} }
pub fn origin(&self) -> bool {
self.all && self.origin
}
pub fn path(&self) -> bool { pub fn path(&self) -> bool {
self.all && self.path self.all && self.path
} }
@@ -423,10 +431,7 @@ impl OverlayContext {
pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) { pub fn draw_scale(&mut self, start: DVec2, scale: f64, radius: f64, text: &str) {
let sign = scale.signum(); let sign = scale.signum();
let mut fill_color = graphene_std::Color::from_rgb_str(crate::consts::COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()) let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_WHITE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
.unwrap()
.with_alpha(0.05)
.to_rgba_hex_srgb();
fill_color.insert(0, '#'); fill_color.insert(0, '#');
let fill_color = Some(fill_color.as_str()); let fill_color = Some(fill_color.as_str());
self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None, None); self.line(start + DVec2::X * radius * sign, start + DVec2::X * (radius * scale), None, None);
@@ -463,10 +468,7 @@ impl OverlayContext {
// Hover ring // Hover ring
if show_hover_ring { if show_hover_ring {
let mut fill_color = graphene_std::Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()) let mut fill_color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.5).to_rgba_hex_srgb();
.unwrap()
.with_alpha(0.5)
.to_rgba_hex_srgb();
fill_color.insert(0, '#'); fill_color.insert(0, '#');
self.render_context.set_line_width(HOVER_RING_STROKE_WIDTH); self.render_context.set_line_width(HOVER_RING_STROKE_WIDTH);
@@ -550,6 +552,36 @@ impl OverlayContext {
self.end_dpi_aware_transform(); self.end_dpi_aware_transform();
} }
pub fn dowel_pin(&mut self, position: DVec2, angle: f64, color: Option<&str>) {
let (x, y) = (position.round() - DVec2::splat(0.5)).into();
let color = color.unwrap_or(COLOR_OVERLAY_YELLOW_DULL);
self.start_dpi_aware_transform();
// Draw the background circle with a white fill and blue outline
self.render_context.begin_path();
self.render_context.arc(x, y, DOWEL_PIN_RADIUS, 0., TAU).expect("Failed to draw the circle");
self.render_context.set_fill_style_str(COLOR_OVERLAY_WHITE);
self.render_context.fill();
self.render_context.set_stroke_style_str(color);
self.render_context.stroke();
// Draw the two blue filled sectors
self.render_context.begin_path();
// Top-left sector
self.render_context.move_to(x, y);
self.render_context.arc(x, y, DOWEL_PIN_RADIUS, FRAC_PI_2 + angle, PI + angle).expect("Failed to draw arc");
self.render_context.close_path();
// Bottom-right sector
self.render_context.move_to(x, y);
self.render_context.arc(x, y, DOWEL_PIN_RADIUS, PI + FRAC_PI_2 + angle, TAU + angle).expect("Failed to draw arc");
self.render_context.close_path();
self.render_context.set_fill_style_str(color);
self.render_context.fill();
self.end_dpi_aware_transform();
}
/// Used by the Pen and Path tools to outline the path of the shape. /// Used by the Pen and Path tools to outline the path of the shape.
pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) { pub fn outline_vector(&mut self, vector_data: &VectorData, transform: DAffine2) {
self.start_dpi_aware_transform(); self.start_dpi_aware_transform();
@@ -599,9 +631,11 @@ impl OverlayContext {
pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) { pub fn outline_overlay_bezier(&mut self, bezier: Bezier, transform: DAffine2) {
self.start_dpi_aware_transform(); self.start_dpi_aware_transform();
let color = Color::from_rgb_str(COLOR_OVERLAY_BLUE.strip_prefix('#').unwrap()).unwrap().with_alpha(0.05).to_rgba_hex_srgb();
self.render_context.begin_path(); self.render_context.begin_path();
self.bezier_command(bezier, transform, true); self.bezier_command(bezier, transform, true);
self.render_context.set_stroke_style_str(COLOR_OVERLAY_BLUE_50); self.render_context.set_stroke_style_str(&color);
self.render_context.set_line_width(4.); self.render_context.set_line_width(4.);
self.render_context.stroke(); self.render_context.stroke();
@@ -731,11 +765,11 @@ impl OverlayContext {
// └──┴──┴──┴──┘ // └──┴──┴──┴──┘
let pixels = [(0, 0), (2, 2)]; let pixels = [(0, 0), (2, 2)];
for &(x, y) in &pixels { for &(x, y) in &pixels {
let index = (x + y * PATTERN_WIDTH as usize) * 4; let index = (x + y * PATTERN_WIDTH) * 4;
data[index..index + 4].copy_from_slice(&color.to_rgba8_srgb()); data[index..index + 4].copy_from_slice(&color.to_rgba8_srgb());
} }
let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(wasm_bindgen::Clamped(&mut data), PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap(); let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(wasm_bindgen::Clamped(&data), PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap();
pattern_context.put_image_data(&image_data, 0., 0.).unwrap(); pattern_context.put_image_data(&image_data, 0., 0.).unwrap();
let pattern = self.render_context.create_pattern_with_offscreen_canvas(&pattern_canvas, "repeat").unwrap().unwrap(); let pattern = self.render_context.create_pattern_with_offscreen_canvas(&pattern_canvas, "repeat").unwrap().unwrap();
@@ -780,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.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"); 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<String>) {
if translation.x.abs() > 1e-3 {
self.dashed_line(quad.top_left(), quad.top_right(), None, None, Some(2.), Some(2.), Some(0.5));
let width = match typed_string {
Some(ref typed_string) => typed_string,
None => &format!("{:.2}", translation.x).trim_end_matches('0').trim_end_matches('.').to_string(),
};
let x_transform = DAffine2::from_translation((quad.top_left() + quad.top_right()) / 2.);
self.text(width, COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]);
}
if translation.y.abs() > 1e-3 {
self.dashed_line(quad.top_left(), quad.bottom_left(), None, None, Some(2.), Some(2.), Some(0.5));
let height = match typed_string {
Some(ref typed_string) => typed_string,
None => &format!("{:.2}", translation.y).trim_end_matches('0').trim_end_matches('.').to_string(),
};
let y_transform = DAffine2::from_translation((quad.top_left() + quad.bottom_left()) / 2.);
let height_pivot = if translation.x > -1e-3 { Pivot::Start } else { Pivot::End };
self.text(height, COLOR_OVERLAY_BLUE, None, y_transform, 3., [height_pivot, Pivot::Middle]);
}
if translation.x.abs() > 1e-3 && translation.y.abs() > 1e-3 {
self.line(quad.top_right(), quad.bottom_right(), None, None);
self.line(quad.bottom_left(), quad.bottom_right(), None, None);
}
}
} }
pub enum Pivot { pub enum Pivot {
@@ -4,9 +4,10 @@ use crate::messages::portfolio::document::node_graph::document_node_definitions:
use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::portfolio::utility_types::PersistentData;
use crate::messages::prelude::*; use crate::messages::prelude::*;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default, ExtractField)]
pub struct PropertiesPanelMessageHandler {} pub struct PropertiesPanelMessageHandler {}
#[message_handler_data]
impl MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPanelMessageHandlerData<'_>)> for PropertiesPanelMessageHandler { impl MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPanelMessageHandlerData<'_>)> for PropertiesPanelMessageHandler {
fn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque<Message>, (persistent_data, data): (&PersistentData, PropertiesPanelMessageHandlerData)) { fn process_message(&mut self, message: PropertiesPanelMessage, responses: &mut VecDeque<Message>, (persistent_data, data): (&PersistentData, PropertiesPanelMessageHandlerData)) {
let PropertiesPanelMessageHandlerData { let PropertiesPanelMessageHandlerData {
@@ -1,6 +1,8 @@
use super::network_interface::NodeNetworkInterface; use super::network_interface::NodeNetworkInterface;
use crate::messages::portfolio::document::graph_operation::transform_utils; use crate::messages::portfolio::document::graph_operation::transform_utils;
use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext; use crate::messages::portfolio::document::graph_operation::utility_types::ModifyInputsContext;
use crate::messages::portfolio::document::utility_types::network_interface::FlowType;
use crate::messages::tool::common_functionality::graph_modification_utils;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
use graphene_std::math::quad::Quad; use graphene_std::math::quad::Quad;
@@ -16,10 +18,11 @@ use std::num::NonZeroU64;
// TODO: To avoid storing a stateful snapshot of some other system's state (which is easily to accidentally get out of sync), // TODO: To avoid storing a stateful snapshot of some other system's state (which is easily to accidentally get out of sync),
// TODO: it might be better to have a system that can query the state of the node network on demand. // TODO: it might be better to have a system that can query the state of the node network on demand.
#[derive(Debug, Clone)] #[derive(Debug, Clone, Default)]
pub struct DocumentMetadata { pub struct DocumentMetadata {
pub upstream_footprints: HashMap<NodeId, Footprint>, pub upstream_footprints: HashMap<NodeId, Footprint>,
pub local_transforms: HashMap<NodeId, DAffine2>, pub local_transforms: HashMap<NodeId, DAffine2>,
pub first_instance_source_ids: HashMap<NodeId, Option<NodeId>>,
pub structure: HashMap<LayerNodeIdentifier, NodeRelations>, pub structure: HashMap<LayerNodeIdentifier, NodeRelations>,
pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>, pub click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
pub clip_targets: HashSet<NodeId>, pub clip_targets: HashSet<NodeId>,
@@ -28,20 +31,6 @@ pub struct DocumentMetadata {
pub document_to_viewport: DAffine2, pub document_to_viewport: DAffine2,
} }
impl Default for DocumentMetadata {
fn default() -> Self {
Self {
upstream_footprints: HashMap::new(),
local_transforms: HashMap::new(),
structure: HashMap::new(),
vector_modify: HashMap::new(),
click_targets: HashMap::new(),
clip_targets: HashSet::new(),
document_to_viewport: DAffine2::IDENTITY,
}
}
}
// ================================= // =================================
// DocumentMetadata: Layer iterators // DocumentMetadata: Layer iterators
// ================================= // =================================
@@ -91,6 +80,36 @@ impl DocumentMetadata {
footprint * local_transform footprint * local_transform
} }
pub fn transform_to_viewport_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
// We're not allowed to convert the root parent to a node id
if layer == LayerNodeIdentifier::ROOT_PARENT {
return self.document_to_viewport;
}
let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);
let mut use_local = true;
let graph_layer = graph_modification_utils::NodeGraphLayer::new(layer, network_interface);
if let Some(path_node) = graph_layer.upstream_node_id_from_name("Path") {
if let Some(&source) = self.first_instance_source_ids.get(&layer.to_node()) {
if !network_interface
.upstream_flow_back_from_nodes(vec![path_node], &[], FlowType::HorizontalFlow)
.any(|upstream| Some(upstream) == source)
{
use_local = false;
info!("Local transform is invalid — using the identity for the local transform instead")
}
}
}
let local_transform = use_local.then(|| self.local_transforms.get(&layer.to_node()).copied()).flatten().unwrap_or_default();
footprint * local_transform
}
pub fn transform_to_document_if_feeds(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
self.document_to_viewport.inverse() * self.transform_to_viewport_if_feeds(layer, network_interface)
}
pub fn transform_to_viewport_with_first_transform_node_if_group(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 { pub fn transform_to_viewport_with_first_transform_node_if_group(&self, layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DAffine2 {
let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport); let footprint = self.upstream_footprints.get(&layer.to_node()).map(|footprint| footprint.transform).unwrap_or(self.document_to_viewport);
let local_transform = self.local_transforms.get(&layer.to_node()).copied(); let local_transform = self.local_transforms.get(&layer.to_node()).copied();
@@ -60,6 +60,27 @@ impl PartialEq for NodeNetworkInterface {
} }
} }
impl NodeNetworkInterface {
/// Add DocumentNodePath input to the PathModifyNode protonode
pub fn migrate_path_modify_node(&mut self) {
fix_network(&mut self.network);
fn fix_network(network: &mut NodeNetwork) {
for node in network.nodes.values_mut() {
if let Some(network) = node.implementation.get_network_mut() {
fix_network(network);
}
if let DocumentNodeImplementation::ProtoNode(protonode) = &node.implementation {
if protonode.name.contains("PathModifyNode") {
if node.inputs.len() < 3 {
node.inputs.push(NodeInput::Reflection(graph_craft::document::DocumentNodeMetadata::DocumentNodePath));
}
}
}
}
}
}
}
// Public immutable getters for the network interface // Public immutable getters for the network interface
impl NodeNetworkInterface { impl NodeNetworkInterface {
// TODO: Make private and use .field_name getter methods // TODO: Make private and use .field_name getter methods
@@ -653,7 +674,7 @@ impl NodeNetworkInterface {
let input_type = self.input_type(&InputConnector::node(*node_id, iterator_index), network_path).0; let input_type = self.input_type(&InputConnector::node(*node_id, iterator_index), network_path).0;
// Value inputs are stored as concrete, so they are compared to the nested type. Node inputs are stored as fn, so they are compared to the entire type. // Value inputs are stored as concrete, so they are compared to the nested type. Node inputs are stored as fn, so they are compared to the entire type.
// For example a node input of (Footprint) -> VectorData would not be compatible with () -> VectorData // For example a node input of (Footprint) -> VectorData would not be compatible with () -> VectorData
node_io.inputs[iterator_index].clone().nested_type() == &input_type || node_io.inputs[iterator_index] == input_type node_io.inputs.get(iterator_index).map(|ty| ty.nested_type().clone()).as_ref() == Some(&input_type) || node_io.inputs.get(iterator_index) == Some(&input_type)
}); });
if valid_implementation { node_io.inputs.get(*input_index).cloned() } else { None } if valid_implementation { node_io.inputs.get(*input_index).cloned() } else { None }
}) })
@@ -3513,6 +3534,11 @@ impl NodeNetworkInterface {
self.document_metadata.local_transforms = local_transforms; self.document_metadata.local_transforms = local_transforms;
} }
/// Update the cached first instance source id of the layers
pub fn update_first_instance_source_id(&mut self, new: HashMap<NodeId, Option<NodeId>>) {
self.document_metadata.first_instance_source_ids = new;
}
/// Update the cached click targets of the layers /// Update the cached click targets of the layers
pub fn update_click_targets(&mut self, new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>) { pub fn update_click_targets(&mut self, new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>) {
self.document_metadata.click_targets = new_click_targets; self.document_metadata.click_targets = new_click_targets;
@@ -1,5 +1,7 @@
use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use super::network_interface::NodeNetworkInterface; use super::network_interface::NodeNetworkInterface;
use crate::messages::tool::common_functionality::graph_modification_utils;
use glam::DVec2;
use graph_craft::document::{NodeId, NodeNetwork}; use graph_craft::document::{NodeId, NodeNetwork};
use serde::ser::SerializeStruct; use serde::ser::SerializeStruct;
@@ -98,6 +100,22 @@ impl SelectedNodes {
.filter(move |&layer| self.layer_visible(layer, network_interface) && !self.layer_locked(layer, network_interface)) .filter(move |&layer| self.layer_visible(layer, network_interface) && !self.layer_locked(layer, network_interface))
} }
pub fn selected_visible_and_unlocked_layers_mean_average_origin<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> DVec2 {
let (sum, count) = self
.selected_visible_and_unlocked_layers(network_interface)
.map(|layer| graph_modification_utils::get_viewport_origin(layer, network_interface))
.fold((glam::DVec2::ZERO, 0), |(sum, count), item| (sum + item, count + 1));
if count == 0 { DVec2::ZERO } else { sum / count as f64 }
}
pub fn selected_visible_and_unlocked_median_points<'a>(&'a self, network_interface: &'a NodeNetworkInterface) -> DVec2 {
let (sum, count) = self
.selected_visible_and_unlocked_layers(network_interface)
.map(|layer| graph_modification_utils::get_viewport_center(layer, network_interface))
.fold((glam::DVec2::ZERO, 0), |(sum, count), item| (sum + item, count + 1));
if count == 0 { DVec2::ZERO } else { sum / count as f64 }
}
pub fn selected_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + 'a { pub fn selected_layers<'a>(&'a self, metadata: &'a DocumentMetadata) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
metadata.all_layers().filter(|layer| self.0.contains(&layer.to_node())) metadata.all_layers().filter(|layer| self.0.contains(&layer.to_node()))
} }
@@ -4,7 +4,6 @@ use crate::messages::portfolio::document::graph_operation::transform_utils;
use crate::messages::portfolio::document::graph_operation::utility_types::{ModifyInputsContext, TransformIn}; use crate::messages::portfolio::document::graph_operation::utility_types::{ModifyInputsContext, TransformIn};
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier}; use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::common_functionality::shape_editor::ShapeState;
use crate::messages::tool::utility_types::ToolType; use crate::messages::tool::utility_types::ToolType;
use glam::{DAffine2, DMat2, DVec2}; use glam::{DAffine2, DMat2, DVec2};
@@ -537,17 +536,6 @@ impl<'a> Selected<'a> {
} }
} }
pub fn mean_average_of_pivots(&mut self) -> DVec2 {
let xy_summation = self
.selected
.iter()
.map(|&layer| graph_modification_utils::get_viewport_pivot(layer, self.network_interface))
.reduce(|a, b| a + b)
.unwrap_or_default();
xy_summation / self.selected.len() as f64
}
pub fn center_of_aabb(&mut self) -> DVec2 { pub fn center_of_aabb(&mut self) -> DVec2 {
let [min, max] = self let [min, max] = self
.selected .selected
@@ -7,6 +7,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Inp
use crate::messages::prelude::DocumentMessageHandler; use crate::messages::prelude::DocumentMessageHandler;
use bezier_rs::Subpath; use bezier_rs::Subpath;
use glam::IVec2; use glam::IVec2;
use graph_craft::document::DocumentNode;
use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue}; use graph_craft::document::{DocumentNodeImplementation, NodeInput, value::TaggedValue};
use graphene_std::text::TypesettingConfig; use graphene_std::text::TypesettingConfig;
use graphene_std::uuid::NodeId; use graphene_std::uuid::NodeId;
@@ -190,6 +191,8 @@ pub fn document_migration_reset_node_definition(document_serialized_content: &st
} }
pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) { pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) {
document.network_interface.migrate_path_modify_node();
let network = document.network_interface.document_network().clone(); let network = document.network_interface.document_network().clone();
// Apply string replacements to each node // Apply string replacements to each node
@@ -215,9 +218,14 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
.map(|(node_id, node, path)| (*node_id, node.clone(), path)) .map(|(node_id, node, path)| (*node_id, node.clone(), path))
.collect::<Vec<(NodeId, graph_craft::document::DocumentNode, Vec<NodeId>)>>(); .collect::<Vec<(NodeId, graph_craft::document::DocumentNode, Vec<NodeId>)>>();
for (node_id, node, network_path) in &nodes { for (node_id, node, network_path) in &nodes {
migrate_node(node_id, node, network_path, document, reset_node_definitions_on_open);
}
}
fn migrate_node(node_id: &NodeId, node: &DocumentNode, network_path: &[NodeId], document: &mut DocumentMessageHandler, reset_node_definitions_on_open: bool) -> Option<()> {
if reset_node_definitions_on_open { if reset_node_definitions_on_open {
if let Some(Some(reference)) = document.network_interface.reference(node_id, network_path) { if let Some(Some(reference)) = document.network_interface.reference(node_id, network_path) {
let Some(node_definition) = resolve_document_node_type(reference) else { continue }; let node_definition = resolve_document_node_type(reference)?;
document.network_interface.replace_implementation(node_id, network_path, &mut node_definition.default_node_template()); document.network_interface.replace_implementation(node_id, network_path, &mut node_definition.default_node_template());
} }
} }
@@ -229,18 +237,16 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
.set_manual_compostion(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into()); .set_manual_compostion(node_id, network_path, graph_craft::concrete!(graphene_std::Context).into());
} }
let Some(Some(reference)) = document.network_interface.reference(node_id, network_path).cloned() else {
// Only nodes that have not been modified and still refer to a definition can be updated // Only nodes that have not been modified and still refer to a definition can be updated
continue; let reference = document.network_interface.reference(node_id, network_path).cloned().flatten()?;
};
let reference = &reference; let reference = &reference;
let inputs_count = node.inputs.len(); let inputs_count = node.inputs.len();
// Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644) // Upgrade Stroke node to reorder parameters and add "Align" and "Paint Order" (#2644)
if reference == "Stroke" && inputs_count == 8 { if reference == "Stroke" && inputs_count == 8 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); let mut node_template = resolve_document_node_type(reference)?.default_node_template();
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false); let align_input = NodeInput::value(TaggedValue::StrokeAlign(StrokeAlign::Center), false);
let paint_order_input = NodeInput::value(TaggedValue::PaintOrder(PaintOrder::StrokeAbove), false); let paint_order_input = NodeInput::value(TaggedValue::PaintOrder(PaintOrder::StrokeAbove), false);
@@ -270,48 +276,51 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
.implementation(node_id, network_path) .implementation(node_id, network_path)
.and_then(|implementation| implementation.get_proto_node()); .and_then(|implementation| implementation.get_proto_node());
if identifier.map(|identifier| &identifier.name) != Some(&"graphene_core::vector::generator_nodes::SplineNode".into()) { if identifier.map(|identifier| &identifier.name) != Some(&"graphene_core::vector::generator_nodes::SplineNode".into()) {
continue; return None;
} }
// Obtain the document node for the given node ID, extract the vector points, and create vector data from the list of points // Obtain the document node for the given node ID, extract the vector points, and create vector data from the list of points
let node = document.network_interface.document_node(node_id, network_path).unwrap(); let node = document.network_interface.document_node(node_id, network_path)?;
let Some(TaggedValue::VecDVec2(points)) = node.inputs.get(1).and_then(|tagged_value| tagged_value.as_value()) else { let Some(TaggedValue::VecDVec2(points)) = node.inputs.get(1).and_then(|tagged_value| tagged_value.as_value()) else {
log::error!("The old Spline node's input at index 1 is not a TaggedValue::VecDVec2"); log::error!("The old Spline node's input at index 1 is not a TaggedValue::VecDVec2");
continue; return None;
}; };
let vector_data = VectorData::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false)); let vector_data = VectorData::from_subpath(Subpath::from_anchors_linear(points.to_vec(), false));
// Retrieve the output connectors linked to the "Spline" node's output port // Retrieve the output connectors linked to the "Spline" node's output port
let spline_outputs = document let Some(spline_outputs) = document.network_interface.outward_wires(network_path)?.get(&OutputConnector::node(*node_id, 0)).cloned() else {
.network_interface log::error!("Vec of InputConnector Spline node is connected to its output port 0.");
.outward_wires(network_path) return None;
.unwrap() };
.get(&OutputConnector::node(*node_id, 0))
.expect("Vec of InputConnector Spline node is connected to its output port 0.")
.clone();
// Get the node's current position in the graph // Get the node's current position in the graph
let Some(node_position) = document.network_interface.position(node_id, network_path) else { let Some(node_position) = document.network_interface.position(node_id, network_path) else {
log::error!("Could not get position of spline node."); log::error!("Could not get position of spline node.");
continue; return None;
}; };
// Get the "Path" node definition and fill it in with the vector data and default vector modification // Get the "Path" node definition and fill it in with the vector data and default vector modification
let path_node_type = resolve_document_node_type("Path").expect("Path node does not exist."); let Some(path_node_type) = resolve_document_node_type("Path") else {
log::error!("Path node does not exist.");
return None;
};
let path_node = path_node_type.node_template_input_override([ let path_node = path_node_type.node_template_input_override([
Some(NodeInput::value(TaggedValue::VectorData(VectorDataTable::new(vector_data)), true)), Some(NodeInput::value(TaggedValue::VectorData(VectorDataTable::new(vector_data)), true)),
Some(NodeInput::value(TaggedValue::VectorModification(Default::default()), false)), Some(NodeInput::value(TaggedValue::VectorModification(Default::default()), false)),
]); ]);
// Get the "Spline" node definition and wire it up with the "Path" node as input // Get the "Spline" node definition and wire it up with the "Path" node as input
let spline_node_type = resolve_document_node_type("Spline").expect("Spline node does not exist."); let Some(spline_node_type) = resolve_document_node_type("Spline") else {
log::error!("Spline node does not exist.");
return None;
};
let spline_node = spline_node_type.node_template_input_override([Some(NodeInput::node(NodeId(1), 0))]); let spline_node = spline_node_type.node_template_input_override([Some(NodeInput::node(NodeId(1), 0))]);
// Create a new node group with the "Path" and "Spline" nodes and generate new node IDs for them // Create a new node group with the "Path" and "Spline" nodes and generate new node IDs for them
let nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)]; let nodes = vec![(NodeId(1), path_node), (NodeId(0), spline_node)];
let new_ids = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect::<HashMap<_, _>>(); let new_ids = nodes.iter().map(|(id, _)| (*id, NodeId::new())).collect::<HashMap<_, _>>();
let new_spline_id = *new_ids.get(&NodeId(0)).unwrap(); let new_spline_id = *new_ids.get(&NodeId(0))?;
let new_path_id = *new_ids.get(&NodeId(1)).unwrap(); let new_path_id = *new_ids.get(&NodeId(1))?;
// Remove the old "Spline" node from the document // Remove the old "Spline" node from the document
document.network_interface.delete_nodes(vec![*node_id], false, network_path); document.network_interface.delete_nodes(vec![*node_id], false, network_path);
@@ -333,9 +342,9 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
// Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016 // Upgrade Text node to include line height and character spacing, which were previously hardcoded to 1, from https://github.com/GraphiteEditor/Graphite/pull/2016
if reference == "Text" && inputs_count != 9 { if reference == "Text" && inputs_count != 9 {
let mut template = resolve_document_node_type(reference).unwrap().default_node_template(); let mut template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut template); document.network_interface.replace_implementation(node_id, network_path, &mut template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut template).unwrap(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut template)?;
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
@@ -390,10 +399,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
// Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default // Upgrade Sine, Cosine, and Tangent nodes to include a boolean input for whether the output should be in radians, which was previously the only option but is now not the default
if (reference == "Sine" || reference == "Cosine" || reference == "Tangent") && inputs_count == 1 { if (reference == "Sine" || reference == "Cosine" || reference == "Tangent") && inputs_count == 1 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); let mut node_template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document document
@@ -403,10 +412,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
// Upgrade the Modulo node to include a boolean input for whether the output should be always positive, which was previously not an option // Upgrade the Modulo node to include a boolean input for whether the output should be always positive, which was previously not an option
if reference == "Modulo" && inputs_count == 2 { if reference == "Modulo" && inputs_count == 2 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); let mut node_template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
@@ -417,10 +426,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
// Upgrade the Mirror node to add the `keep_original` boolean input // Upgrade the Mirror node to add the `keep_original` boolean input
if reference == "Mirror" && inputs_count == 3 { if reference == "Mirror" && inputs_count == 3 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); let mut node_template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
@@ -432,12 +441,12 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
// Upgrade the Mirror node to add the `reference_point` input and change `offset` from `DVec2` to `f64` // Upgrade the Mirror node to add the `reference_point` input and change `offset` from `DVec2` to `f64`
if reference == "Mirror" && inputs_count == 4 { if reference == "Mirror" && inputs_count == 4 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); let mut node_template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
let Some(&TaggedValue::DVec2(old_offset)) = old_inputs[1].as_value() else { return }; let Some(&TaggedValue::DVec2(old_offset)) = old_inputs[1].as_value() else { return None };
let old_offset = if old_offset.x.abs() > old_offset.y.abs() { old_offset.x } else { old_offset.y }; let old_offset = if old_offset.x.abs() > old_offset.y.abs() { old_offset.x } else { old_offset.y };
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
@@ -462,7 +471,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Image" && inputs_count == 1 { if reference == "Image" && inputs_count == 1 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); let mut node_template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
// Insert a new empty input for the image // Insert a new empty input for the image
@@ -471,10 +480,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Noise Pattern" && inputs_count == 15 { if reference == "Noise Pattern" && inputs_count == 15 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); let mut node_template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
document document
.network_interface .network_interface
@@ -485,20 +494,20 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Instance on Points" && inputs_count == 2 { if reference == "Instance on Points" && inputs_count == 2 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); let mut node_template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
} }
if reference == "Morph" && inputs_count == 4 { if reference == "Morph" && inputs_count == 4 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); let mut node_template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
@@ -507,10 +516,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Brush" && inputs_count == 4 { if reference == "Brush" && inputs_count == 4 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); let mut node_template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
// We have removed the second input ("bounds"), so we don't add index 1 and we shift the rest of the inputs down by one // We have removed the second input ("bounds"), so we don't add index 1 and we shift the rest of the inputs down by one
@@ -519,10 +528,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Flatten Vector Elements" { if reference == "Flatten Vector Elements" {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); let mut node_template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
@@ -530,10 +539,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Remove Handles" { if reference == "Remove Handles" {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); let mut node_template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document document
@@ -547,10 +556,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Generate Handles" { if reference == "Generate Handles" {
let mut node_template = resolve_document_node_type("Auto-Tangents").unwrap().default_node_template(); let mut node_template = resolve_document_node_type("Auto-Tangents")?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
@@ -562,10 +571,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Merge by Distance" && inputs_count == 2 { if reference == "Merge by Distance" && inputs_count == 2 {
let mut node_template = resolve_document_node_type(reference).unwrap().default_node_template(); let mut node_template = resolve_document_node_type(reference)?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
@@ -577,10 +586,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Spatial Merge by Distance" { if reference == "Spatial Merge by Distance" {
let mut node_template = resolve_document_node_type("Merge by Distance").unwrap().default_node_template(); let mut node_template = resolve_document_node_type("Merge by Distance")?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 0), old_inputs[0].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 1), old_inputs[1].clone(), network_path);
@@ -594,10 +603,10 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
} }
if reference == "Sample Points" && inputs_count == 5 { if reference == "Sample Points" && inputs_count == 5 {
let mut node_template = resolve_document_node_type("Sample Polyline").unwrap().default_node_template(); let mut node_template = resolve_document_node_type("Sample Polyline")?.default_node_template();
document.network_interface.replace_implementation(node_id, network_path, &mut node_template); document.network_interface.replace_implementation(node_id, network_path, &mut node_template);
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template).unwrap(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut node_template)?;
let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false); let new_spacing_value = NodeInput::value(TaggedValue::PointSpacingType(graphene_std::vector::misc::PointSpacingType::Separation), false);
let new_quantity_value = NodeInput::value(TaggedValue::U32(100), false); let new_quantity_value = NodeInput::value(TaggedValue::U32(100), false);
@@ -617,8 +626,7 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
// Get the inputs, obtain the quantity value, and put the inputs back // Get the inputs, obtain the quantity value, and put the inputs back
let quantity_value = document let quantity_value = document
.network_interface .network_interface
.input_from_connector(&InputConnector::Node { node_id: *node_id, input_index: 3 }, network_path) .input_from_connector(&InputConnector::Node { node_id: *node_id, input_index: 3 }, network_path)?;
.unwrap();
if let NodeInput::Value { tagged_value, exposed } = quantity_value { if let NodeInput::Value { tagged_value, exposed } = quantity_value {
if let TaggedValue::F64(value) = **tagged_value { if let TaggedValue::F64(value) = **tagged_value {
@@ -630,13 +638,15 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
// Make the "Grid" node, if its input of index 3 is a DVec2 for "angles" instead of a u32 for the "columns" input that now succeeds "angles", move the angle to index 5 (after "columns" and "rows") // Make the "Grid" node, if its input of index 3 is a DVec2 for "angles" instead of a u32 for the "columns" input that now succeeds "angles", move the angle to index 5 (after "columns" and "rows")
if reference == "Grid" && inputs_count == 6 { if reference == "Grid" && inputs_count == 6 {
let node_definition = resolve_document_node_type(reference).unwrap(); let node_definition = resolve_document_node_type(reference)?;
let mut new_node_template = node_definition.default_node_template(); let mut new_node_template = node_definition.default_node_template();
let mut current_node_template = document.network_interface.create_node_template(node_id, network_path).unwrap(); let mut current_node_template = document.network_interface.create_node_template(node_id, network_path)?;
let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template).unwrap(); let old_inputs = document.network_interface.replace_inputs(node_id, network_path, &mut new_node_template)?;
let index_3_value = old_inputs.get(3).cloned(); let index_3_value = old_inputs.get(3).cloned();
let mut upgraded = false;
if let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value { if let Some(NodeInput::Value { tagged_value, exposed: _ }) = index_3_value {
if matches!(*tagged_value, TaggedValue::DVec2(_)) { if matches!(*tagged_value, TaggedValue::DVec2(_)) {
// Move index 3 to the end // Move index 3 to the end
@@ -646,37 +656,38 @@ pub fn document_migration_upgrades(document: &mut DocumentMessageHandler, reset_
document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 3), old_inputs[4].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 4), old_inputs[5].clone(), network_path);
document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path); document.network_interface.set_input(&InputConnector::node(*node_id, 5), old_inputs[3].clone(), network_path);
} else {
// Swap it back if we're not changing anything upgraded = true;
let _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template);
} }
} }
if !upgraded {
let _ = document.network_interface.replace_inputs(node_id, network_path, &mut current_node_template);
}
} }
// Ensure layers are positioned as stacks if they are upstream siblings of another layer // Ensure layers are positioned as stacks if they are upstream siblings of another layer
document.network_interface.load_structure(); document.network_interface.load_structure();
let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::<Vec<_>>(); let all_layers = LayerNodeIdentifier::ROOT_PARENT.descendants(document.network_interface.document_metadata()).collect::<Vec<_>>();
for layer in all_layers { for layer in all_layers {
let Some((downstream_node, input_index)) = document let (downstream_node, input_index) = document
.network_interface .network_interface
.outward_wires(&[]) .outward_wires(&[])
.and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0))) .and_then(|outward_wires| outward_wires.get(&OutputConnector::node(layer.to_node(), 0)))
.and_then(|outward_wires| outward_wires.first()) .and_then(|outward_wires| outward_wires.first())
.and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index()))) .and_then(|input_connector| input_connector.node_id().map(|node_id| (node_id, input_connector.input_index())))?;
else {
continue;
};
// If the downstream node is a layer and the input is the first input and the current layer is not in a stack // If the downstream node is a layer and the input is the first input and the current layer is not in a stack
if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) { if input_index == 0 && document.network_interface.is_layer(&downstream_node, &[]) && !document.network_interface.is_stack(&layer.to_node(), &[]) {
// Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files // Ensure the layer is horizontally aligned with the downstream layer to prevent changing the layout of old files
let (Some(layer_position), Some(downstream_position)) = (document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[])) else { let (Some(layer_position), Some(downstream_position)) = (document.network_interface.position(&layer.to_node(), &[]), document.network_interface.position(&downstream_node, &[])) else {
log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node); log::error!("Could not get position for layer {:?} or downstream node {} when opening file", layer.to_node(), downstream_node);
continue; return None;
}; };
if layer_position.x == downstream_position.x { if layer_position.x == downstream_position.x {
document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]); document.network_interface.set_stack_position_calculated_offset(&layer.to_node(), &downstream_node, &[]);
} }
} }
} }
}
Some(())
} }
@@ -6,7 +6,7 @@ use crate::messages::portfolio::document::utility_types::misc::{AlignAggregate,
use crate::messages::prelude::*; use crate::messages::prelude::*;
use graphene_std::path_bool::BooleanOperation; use graphene_std::path_bool::BooleanOperation;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default, ExtractField)]
pub struct MenuBarMessageHandler { pub struct MenuBarMessageHandler {
pub has_active_document: bool, pub has_active_document: bool,
pub canvas_tilted: bool, pub canvas_tilted: bool,
@@ -21,6 +21,7 @@ pub struct MenuBarMessageHandler {
pub reset_node_definitions_on_open: bool, pub reset_node_definitions_on_open: bool,
} }
#[message_handler_data]
impl MessageHandler<MenuBarMessage, ()> for MenuBarMessageHandler { impl MessageHandler<MenuBarMessage, ()> for MenuBarMessageHandler {
fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque<Message>, _data: ()) { fn process_message(&mut self, message: MenuBarMessage, responses: &mut VecDeque<Message>, _data: ()) {
match message { match message {
@@ -25,6 +25,7 @@ use graphene_std::renderer::Quad;
use graphene_std::text::Font; use graphene_std::text::Font;
use std::vec; use std::vec;
#[derive(ExtractField)]
pub struct PortfolioMessageData<'a> { pub struct PortfolioMessageData<'a> {
pub ipp: &'a InputPreprocessorMessageHandler, pub ipp: &'a InputPreprocessorMessageHandler,
pub preferences: &'a PreferencesMessageHandler, pub preferences: &'a PreferencesMessageHandler,
@@ -35,7 +36,7 @@ pub struct PortfolioMessageData<'a> {
pub animation: &'a AnimationMessageHandler, pub animation: &'a AnimationMessageHandler,
} }
#[derive(Debug, Default)] #[derive(Debug, Default, ExtractField)]
pub struct PortfolioMessageHandler { pub struct PortfolioMessageHandler {
menu_bar_message_handler: MenuBarMessageHandler, menu_bar_message_handler: MenuBarMessageHandler,
pub documents: HashMap<DocumentId, DocumentMessageHandler>, pub documents: HashMap<DocumentId, DocumentMessageHandler>,
@@ -52,6 +53,7 @@ pub struct PortfolioMessageHandler {
pub reset_node_definitions_on_open: bool, pub reset_node_definitions_on_open: bool,
} }
#[message_handler_data]
impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMessageHandler { impl MessageHandler<PortfolioMessage, PortfolioMessageData<'_>> for PortfolioMessageHandler {
fn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque<Message>, data: PortfolioMessageData) { fn process_message(&mut self, message: PortfolioMessage, responses: &mut VecDeque<Message>, data: PortfolioMessageData) {
let PortfolioMessageData { let PortfolioMessageData {
@@ -15,7 +15,7 @@ use std::any::Any;
use std::sync::Arc; use std::sync::Arc;
/// The spreadsheet UI allows for instance data to be previewed. /// The spreadsheet UI allows for instance data to be previewed.
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone, ExtractField)]
pub struct SpreadsheetMessageHandler { pub struct SpreadsheetMessageHandler {
/// Sets whether or not the spreadsheet is drawn. /// Sets whether or not the spreadsheet is drawn.
pub spreadsheet_view_open: bool, pub spreadsheet_view_open: bool,
@@ -25,6 +25,7 @@ pub struct SpreadsheetMessageHandler {
viewing_vector_data_domain: VectorDataDomain, viewing_vector_data_domain: VectorDataDomain,
} }
#[message_handler_data]
impl MessageHandler<SpreadsheetMessage, ()> for SpreadsheetMessageHandler { impl MessageHandler<SpreadsheetMessage, ()> for SpreadsheetMessageHandler {
fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque<Message>, _data: ()) { fn process_message(&mut self, message: SpreadsheetMessage, responses: &mut VecDeque<Message>, _data: ()) {
match message { match message {
@@ -5,7 +5,7 @@ use crate::messages::preferences::SelectionMode;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use graph_craft::wasm_application_io::EditorPreferences; 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 struct PreferencesMessageHandler {
pub selection_mode: SelectionMode, pub selection_mode: SelectionMode,
pub zoom_with_scroll: bool, pub zoom_with_scroll: bool,
@@ -44,6 +44,7 @@ impl Default for PreferencesMessageHandler {
} }
} }
#[message_handler_data]
impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler { impl MessageHandler<PreferencesMessage, ()> for PreferencesMessageHandler {
fn process_message(&mut self, message: PreferencesMessage, responses: &mut VecDeque<Message>, _data: ()) { fn process_message(&mut self, message: PreferencesMessage, responses: &mut VecDeque<Message>, _data: ()) {
match message { match message {
+2 -2
View File
@@ -1,6 +1,6 @@
// Root // 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 // Message, MessageData, MessageDiscriminant, MessageHandler
pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler}; pub use crate::messages::animation::{AnimationMessage, AnimationMessageDiscriminant, AnimationMessageHandler};
pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler}; pub use crate::messages::broadcast::{BroadcastMessage, BroadcastMessageDiscriminant, BroadcastMessageHandler};
@@ -1,5 +1,4 @@
use crate::consts::{COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER}; use crate::consts::{COMPASS_ROSE_ARROW_CLICK_TARGET_ANGLE, COMPASS_ROSE_HOVER_RING_DIAMETER, COMPASS_ROSE_RING_INNER_DIAMETER};
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::DocumentMessageHandler; use crate::messages::prelude::DocumentMessageHandler;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use std::f64::consts::FRAC_PI_2; use std::f64::consts::FRAC_PI_2;
@@ -10,25 +9,32 @@ pub struct CompassRose {
} }
impl CompassRose { impl CompassRose {
fn get_layer_pivot_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 {
let [min, max] = document.metadata().nonzero_bounding_box(layer);
let bounds_transform = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
let layer_transform = document.metadata().transform_to_viewport(layer);
layer_transform * bounds_transform
}
pub fn refresh_position(&mut self, document: &DocumentMessageHandler) { pub fn refresh_position(&mut self, document: &DocumentMessageHandler) {
let selected_nodes = document.network_interface.selected_nodes(); let selected = document.network_interface.selected_nodes();
let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface);
let Some(first) = layers.next() else { return }; if !selected.has_selected_nodes() {
let count = layers.count() + 1; return;
let transform = if count == 1 { }
Self::get_layer_pivot_transform(first, document)
} else { let transform = selected
let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]); .selected_visible_and_unlocked_layers(&document.network_interface)
DAffine2::from_translation(min) * DAffine2::from_scale(max - min) .find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))
}; .map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface))
.unwrap_or_default();
let bounds = document
.network_interface
.selected_nodes()
.selected_visible_and_unlocked_layers(&document.network_interface)
.filter_map(|layer| {
document
.metadata()
.bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer))
})
.reduce(graphene_std::renderer::Quad::combine_bounds);
let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);
let transform = transform * DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
self.compass_center = transform.transform_point2(DVec2::splat(0.5)); self.compass_center = transform.transform_point2(DVec2::splat(0.5));
} }
@@ -5,9 +5,9 @@ use crate::messages::portfolio::document::utility_types::network_interface::{Flo
use crate::messages::prelude::*; use crate::messages::prelude::*;
use bezier_rs::Subpath; use bezier_rs::Subpath;
use glam::DVec2; use glam::DVec2;
use graph_craft::concrete;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput}; use graph_craft::document::{NodeId, NodeInput};
use graph_craft::{ProtoNodeIdentifier, concrete};
use graphene_std::Color; use graphene_std::Color;
use graphene_std::NodeInputDecleration; use graphene_std::NodeInputDecleration;
use graphene_std::raster::BlendMode; use graphene_std::raster::BlendMode;
@@ -243,20 +243,26 @@ pub fn new_custom(id: NodeId, nodes: Vec<(NodeId, NodeTemplate)>, parent: LayerN
LayerNodeIdentifier::new_unchecked(id) LayerNodeIdentifier::new_unchecked(id)
} }
/// Locate the final pivot from the transform (TODO: decide how the pivot should actually work) /// Locate the origin of the transform node
pub fn get_pivot(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<DVec2> { pub fn get_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> Option<DVec2> {
let pivot_node_input_index = 5; use graphene_std::transform_nodes::transform::TranslateInput;
if let TaggedValue::DVec2(pivot) = NodeGraphLayer::new(layer, network_interface).find_input("Transform", pivot_node_input_index)? {
Some(*pivot) if let TaggedValue::DVec2(origin) = NodeGraphLayer::new(layer, network_interface).find_input("Transform", TranslateInput::INDEX)? {
Some(*origin)
} else { } else {
None None
} }
} }
pub fn get_viewport_pivot(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 { pub fn get_viewport_origin(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 {
let origin = get_origin(layer, network_interface).unwrap_or_default();
network_interface.document_metadata().document_to_viewport.transform_point2(origin)
}
pub fn get_viewport_center(layer: LayerNodeIdentifier, network_interface: &NodeNetworkInterface) -> DVec2 {
let [min, max] = network_interface.document_metadata().nonzero_bounding_box(layer); let [min, max] = network_interface.document_metadata().nonzero_bounding_box(layer);
let pivot = get_pivot(layer, network_interface).unwrap_or(DVec2::splat(0.5)); let center = DVec2::splat(0.5);
network_interface.document_metadata().transform_to_viewport(layer).transform_point2(min + (max - min) * pivot) network_interface.document_metadata().transform_to_viewport(layer).transform_point2(min + (max - min) * center)
} }
/// Get the current gradient of a layer from the closest "Fill" node. /// Get the current gradient of a layer from the closest "Fill" node.
@@ -416,14 +422,14 @@ impl<'a> NodeGraphLayer<'a> {
} }
/// Node id of a protonode if it exists in the layer's primary flow /// Node id of a protonode if it exists in the layer's primary flow
pub fn upstream_node_id_from_protonode(&self, protonode_identifier: &'static str) -> Option<NodeId> { pub fn upstream_node_id_from_protonode(&self, protonode_identifier: ProtoNodeIdentifier) -> Option<NodeId> {
self.horizontal_layer_flow() self.horizontal_layer_flow()
// Take until a different layer is reached // Take until a different layer is reached
.take_while(|&node_id| node_id == self.layer_node || !self.network_interface.is_layer(&node_id, &[])) .take_while(|&node_id| node_id == self.layer_node || !self.network_interface.is_layer(&node_id, &[]))
.find(move |node_id| { .find(|node_id| {
self.network_interface self.network_interface
.implementation(node_id, &[]) .implementation(node_id, &[])
.is_some_and(move |implementation| *implementation == graph_craft::document::DocumentNodeImplementation::proto(protonode_identifier)) .is_some_and(|implementation| *implementation == graph_craft::document::DocumentNodeImplementation::ProtoNode(protonode_identifier.clone()))
}) })
} }
@@ -1,26 +1,184 @@
//! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale and origin of the layer. //! Handler for the pivot overlay visible on the selected layer(s) whilst using the Select tool which controls the center of rotation/scale.
use super::graph_modification_utils;
use crate::consts::PIVOT_DIAMETER; use crate::consts::PIVOT_DIAMETER;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier; use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::tool_messages::path_tool::PathOptionsUpdate;
use crate::messages::tool::tool_messages::select_tool::SelectOptionsUpdate;
use crate::messages::tool::tool_messages::tool_prelude::*;
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graphene_std::transform::ReferencePoint; use graphene_std::{transform::ReferencePoint, vector::ManipulatorPointId};
use std::collections::VecDeque; use std::fmt;
#[derive(Clone, Debug)] pub fn pin_pivot_widget(active: bool, enabled: bool, source: PivotToolSource) -> WidgetHolder {
IconButton::new(if active { "PinActive" } else { "PinInactive" }, 24)
.tooltip(String::from(if active { "Unpin Custom Pivot" } else { "Pin Custom Pivot" }) + "\n\nUnless pinned, the pivot will return to its prior reference point when a new selection is made.")
.disabled(!enabled)
.on_update(move |_| match source {
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotPinned).into(),
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotPinned).into(),
})
.widget_holder()
}
pub fn pivot_reference_point_widget(disabled: bool, reference_point: ReferencePoint, source: PivotToolSource) -> WidgetHolder {
ReferencePointInput::new(reference_point)
.tooltip("Custom Pivot Reference Point\n\nPlaces the pivot at a corner, edge, or center of the selection bounds, unless it is dragged elsewhere.")
.disabled(disabled)
.on_update(move |pivot_input: &ReferencePointInput| match source {
PivotToolSource::Select => SelectToolMessage::SetPivot { position: pivot_input.value }.into(),
PivotToolSource::Path => PathToolMessage::SetPivot { position: pivot_input.value }.into(),
})
.widget_holder()
}
pub fn pivot_gizmo_type_widget(state: PivotGizmoState, source: PivotToolSource) -> Vec<WidgetHolder> {
let gizmo_type_entries = [PivotGizmoType::Pivot, PivotGizmoType::Average, PivotGizmoType::Active]
.iter()
.map(|gizmo_type| {
MenuListEntry::new(format!("{gizmo_type:?}")).label(gizmo_type.to_string()).on_commit({
let value = source.clone();
move |_| match value {
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::PivotGizmoType(*gizmo_type)).into(),
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::PivotGizmoType(*gizmo_type)).into(),
}
})
})
.collect();
vec![
CheckboxInput::new(!state.disabled)
.tooltip(
"Pivot Gizmo\n\
\n\
Enabled: the chosen gizmo type is shown and used to control rotation and scaling.\n\
Disabled: rotation and scaling occurs about the center of the selection bounds.",
)
.on_update(move |optional_input: &CheckboxInput| match source {
PivotToolSource::Select => SelectToolMessage::SelectOptions(SelectOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(),
PivotToolSource::Path => PathToolMessage::UpdateOptions(PathOptionsUpdate::TogglePivotGizmoType(optional_input.checked)).into(),
})
.widget_holder(),
Separator::new(SeparatorType::Related).widget_holder(),
DropdownInput::new(vec![gizmo_type_entries])
.selected_index(Some(match state.gizmo_type {
PivotGizmoType::Pivot => 0,
PivotGizmoType::Average => 1,
PivotGizmoType::Active => 2,
}))
.tooltip(
"Pivot Gizmo Type\n\
\n\
Selects which gizmo type is shown and used as the center of rotation/scaling transformations.\n\
\n\
Custom Pivot: rotates and scales relative to the selection bounds, or elsewhere if dragged.\n\
Origin (Average Point): rotates and scales about the average point of all selected layer origins.\n\
Origin (Active Object): rotates and scales about the origin of the most recently selected layer.",
)
.disabled(state.disabled)
.widget_holder(),
]
}
#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub enum PivotToolSource {
Path,
#[default]
Select,
}
#[derive(PartialEq, Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct PivotGizmo {
pub pivot: Pivot,
pub state: PivotGizmoState,
pub layer: Option<LayerNodeIdentifier>,
pub point: Option<ManipulatorPointId>,
}
impl PivotGizmo {
pub fn position(&self, document: &DocumentMessageHandler) -> DVec2 {
let network = &document.network_interface;
(!self.state.disabled)
.then_some({
match self.state.gizmo_type {
PivotGizmoType::Average => Some(network.selected_nodes().selected_visible_and_unlocked_layers_mean_average_origin(network)),
PivotGizmoType::Pivot => self.pivot.pivot,
PivotGizmoType::Active => self.layer.map(|layer| graph_modification_utils::get_viewport_origin(layer, network)),
}
})
.flatten()
.unwrap_or_else(|| self.pivot.transform_from_normalized.transform_point2(DVec2::splat(0.5)))
}
pub fn recalculate_transform(&mut self, document: &DocumentMessageHandler) -> DAffine2 {
self.pivot.recalculate_pivot(document);
self.pivot.transform_from_normalized
}
pub fn pin_active(&self) -> bool {
self.pivot.pinned && self.state.is_pivot_type()
}
pub fn pivot_disconnected(&self) -> bool {
self.pivot.old_pivot_position == ReferencePoint::None
}
}
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum PivotGizmoType {
// Pivot
#[default]
Pivot,
// Origin
Average,
Active,
// TODO: Add "Individual"
}
#[derive(PartialEq, Eq, Clone, Copy, Default, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct PivotGizmoState {
pub disabled: bool,
pub gizmo_type: PivotGizmoType,
}
impl PivotGizmoState {
pub fn is_pivot_type(&self) -> bool {
self.gizmo_type == PivotGizmoType::Pivot || self.disabled
}
pub fn is_pivot(&self) -> bool {
self.gizmo_type == PivotGizmoType::Pivot && !self.disabled
}
}
impl fmt::Display for PivotGizmoType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PivotGizmoType::Pivot => write!(f, "Custom Pivot"),
PivotGizmoType::Average => write!(f, "Origin (Average Point)"),
PivotGizmoType::Active => write!(f, "Origin (Active Object)"),
// TODO: Add "Origin (Individual)"
}
}
}
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct Pivot { pub struct Pivot {
/// Pivot between (0,0) and (1,1) /// Pivot between (0,0) and (1,1)
normalized_pivot: DVec2, normalized_pivot: DVec2,
/// Transform to get from normalized pivot to viewspace /// Transform to get from normalized pivot to viewspace
transform_from_normalized: DAffine2, pub transform_from_normalized: DAffine2,
/// The viewspace pivot position (if applicable) /// The viewspace pivot position
pivot: Option<DVec2>, pub pivot: Option<DVec2>,
/// The old pivot position in the GUI, used to reduce refreshes of the document bar /// The old pivot position in the GUI, used to reduce refreshes of the document bar
old_pivot_position: ReferencePoint, pub old_pivot_position: ReferencePoint,
/// The last ReferencePoint which wasn't none
pub last_non_none_reference_point: ReferencePoint,
/// Used to enable and disable the pivot /// Used to enable and disable the pivot
active: bool, pub pinned: bool,
/// Had selected_visible_and_unlocked_layers
pub empty: bool,
} }
impl Default for Pivot { impl Default for Pivot {
@@ -30,84 +188,62 @@ impl Default for Pivot {
transform_from_normalized: Default::default(), transform_from_normalized: Default::default(),
pivot: Default::default(), pivot: Default::default(),
old_pivot_position: ReferencePoint::Center, old_pivot_position: ReferencePoint::Center,
active: true, last_non_none_reference_point: ReferencePoint::Center,
pinned: false,
empty: true,
} }
} }
} }
impl Pivot { impl Pivot {
/// Calculates the transform that gets from normalized pivot to viewspace.
fn get_layer_pivot_transform(layer: LayerNodeIdentifier, document: &DocumentMessageHandler) -> DAffine2 {
let [min, max] = document.metadata().nonzero_bounding_box(layer);
let bounds_transform = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
let layer_transform = document.metadata().transform_to_viewport(layer);
layer_transform * bounds_transform
}
/// Recomputes the pivot position and transform. /// Recomputes the pivot position and transform.
fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) { pub fn recalculate_pivot(&mut self, document: &DocumentMessageHandler) {
if !self.active { let selected = document.network_interface.selected_nodes();
self.empty = !selected.has_selected_nodes();
if !selected.has_selected_nodes() {
return; return;
} }
let selected_nodes = document.network_interface.selected_nodes(); let transform = selected
let mut layers = selected_nodes.selected_visible_and_unlocked_layers(&document.network_interface); .selected_visible_and_unlocked_layers(&document.network_interface)
let Some(first) = layers.next() else { .find(|layer| !document.network_interface.is_artboard(&layer.to_node(), &[]))
// If no layers are selected then we revert things back to default .map(|layer| document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface))
.unwrap_or_default();
let bounds = document
.network_interface
.selected_nodes()
.selected_visible_and_unlocked_layers(&document.network_interface)
.filter_map(|layer| {
document
.metadata()
.bounding_box_with_transform(layer, transform.inverse() * document.metadata().transform_to_viewport(layer))
})
.reduce(graphene_std::renderer::Quad::combine_bounds);
let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);
self.transform_from_normalized = transform * DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
if self.old_pivot_position != ReferencePoint::None {
self.pivot = Some(self.transform_from_normalized.transform_point2(self.normalized_pivot));
}
}
pub fn recalculate_pivot_for_layer(&mut self, document: &DocumentMessageHandler, bounds: Option<[DVec2; 2]>) {
let selected = document.network_interface.selected_nodes();
if !selected.has_selected_nodes() {
self.normalized_pivot = DVec2::splat(0.5); self.normalized_pivot = DVec2::splat(0.5);
self.pivot = None; self.pivot = None;
return; return;
}; };
// Add one because the first item is consumed above. let [min, max] = bounds.unwrap_or([DVec2::ZERO, DVec2::ONE]);
let selected_layers_count = layers.count() + 1;
// If just one layer is selected we can use its inner transform (as it accounts for rotation)
if selected_layers_count == 1 {
let normalized_pivot = graph_modification_utils::get_pivot(first, &document.network_interface).unwrap_or(DVec2::splat(0.5));
self.normalized_pivot = normalized_pivot;
self.transform_from_normalized = Self::get_layer_pivot_transform(first, document);
self.pivot = Some(self.transform_from_normalized.transform_point2(normalized_pivot));
} else {
// If more than one layer is selected we use the AABB with the mean of the pivots
let xy_summation = document
.network_interface
.selected_nodes()
.selected_visible_and_unlocked_layers(&document.network_interface)
.map(|layer| graph_modification_utils::get_viewport_pivot(layer, &document.network_interface))
.reduce(|a, b| a + b)
.unwrap_or_default();
let pivot = xy_summation / selected_layers_count as f64;
self.pivot = Some(pivot);
let [min, max] = document.selected_visible_and_unlock_layers_bounding_box_viewport().unwrap_or([DVec2::ZERO, DVec2::ONE]);
self.normalized_pivot = (pivot - min) / (max - min);
self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min); self.transform_from_normalized = DAffine2::from_translation(min) * DAffine2::from_scale(max - min);
} self.pivot = Some(self.transform_from_normalized.transform_point2(self.normalized_pivot));
}
pub fn update_pivot(&mut self, document: &DocumentMessageHandler, overlay_context: &mut OverlayContext, draw_data: Option<(f64,)>) {
if !overlay_context.visibility_settings.pivot() {
self.active = false;
return;
} else {
self.active = true;
}
self.recalculate_pivot(document);
if let (Some(pivot), Some(data)) = (self.pivot, draw_data) {
overlay_context.pivot(pivot, data.0);
}
} }
/// Answers if the pivot widget has changed (so we should refresh the tool bar at the top of the canvas). /// Answers if the pivot widget has changed (so we should refresh the tool bar at the top of the canvas).
pub fn should_refresh_pivot_position(&mut self) -> bool { pub fn should_refresh_pivot_position(&mut self) -> bool {
if !self.active {
return false;
}
let new = self.to_pivot_position(); let new = self.to_pivot_position();
let should_refresh = new != self.old_pivot_position; let should_refresh = new != self.old_pivot_position;
self.old_pivot_position = new; self.old_pivot_position = new;
@@ -118,37 +254,24 @@ impl Pivot {
self.normalized_pivot.into() self.normalized_pivot.into()
} }
/// Sets the viewport position of the pivot for all selected layers. /// Sets the viewport position of the pivot.
pub fn set_viewport_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) { pub fn set_viewport_position(&mut self, position: DVec2) {
if !self.active { if self.transform_from_normalized.matrix2.determinant().abs() <= f64::EPSILON {
return;
}
for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) {
let transform = Self::get_layer_pivot_transform(layer, document);
// Only update the pivot when computed position is finite.
if transform.matrix2.determinant().abs() <= f64::EPSILON {
return; return;
}; };
let pivot = transform.inverse().transform_point2(position);
responses.add(GraphOperationMessage::TransformSetPivot { layer, pivot }); self.normalized_pivot = self.transform_from_normalized.inverse().transform_point2(position);
} self.pivot = Some(position);
} }
/// Set the pivot using the normalized transform that is set above. /// Set the pivot using a normalized position.
pub fn set_normalized_position(&self, position: DVec2, document: &DocumentMessageHandler, responses: &mut VecDeque<Message>) { pub fn set_normalized_position(&mut self, position: DVec2) {
if !self.active { self.normalized_pivot = position;
return; self.pivot = Some(self.transform_from_normalized.transform_point2(position));
}
self.set_viewport_position(self.transform_from_normalized.transform_point2(position), document, responses);
} }
/// Answers if the pointer is currently positioned over the pivot. /// Answers if the pointer is currently positioned over the pivot.
pub fn is_over(&self, mouse: DVec2) -> bool { pub fn is_over(&self, mouse: DVec2) -> bool {
if !self.active {
return false;
}
self.pivot.filter(|&pivot| mouse.distance_squared(pivot) < (PIVOT_DIAMETER / 2.).powi(2)).is_some() self.pivot.filter(|&pivot| mouse.distance_squared(pivot) < (PIVOT_DIAMETER / 2.).powi(2)).is_some()
} }
} }
@@ -212,15 +212,15 @@ impl ClosestSegment {
self.bezier_point_to_viewport self.bezier_point_to_viewport
} }
pub fn closest_point(&self, document_metadata: &DocumentMetadata) -> DVec2 { pub fn closest_point(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> DVec2 {
let transform = document_metadata.transform_to_viewport(self.layer); let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
let bezier_point = self.bezier.evaluate(TValue::Parametric(self.t)); let bezier_point = self.bezier.evaluate(TValue::Parametric(self.t));
transform.transform_point2(bezier_point) transform.transform_point2(bezier_point)
} }
/// Updates this [`ClosestSegment`] with the viewport-space location of the closest point on the segment to the given mouse position. /// Updates this [`ClosestSegment`] with the viewport-space location of the closest point on the segment to the given mouse position.
pub fn update_closest_point(&mut self, document_metadata: &DocumentMetadata, mouse_position: DVec2) { pub fn update_closest_point(&mut self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface, mouse_position: DVec2) {
let transform = document_metadata.transform_to_viewport(self.layer); let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
let layer_mouse_pos = transform.inverse().transform_point2(mouse_position); let layer_mouse_pos = transform.inverse().transform_point2(mouse_position);
let t = self.bezier.project(layer_mouse_pos).clamp(0., 1.); let t = self.bezier.project(layer_mouse_pos).clamp(0., 1.);
@@ -239,9 +239,9 @@ impl ClosestSegment {
tolerance.powi(2) < self.distance_squared(mouse_position) tolerance.powi(2) < self.distance_squared(mouse_position)
} }
pub fn handle_positions(&self, document_metadata: &DocumentMetadata) -> (Option<DVec2>, Option<DVec2>) { pub fn handle_positions(&self, document_metadata: &DocumentMetadata, network_interface: &NodeNetworkInterface) -> (Option<DVec2>, Option<DVec2>) {
// Transform to viewport space // Transform to viewport space
let transform = document_metadata.transform_to_viewport(self.layer); let transform = document_metadata.transform_to_viewport_if_feeds(self.layer, network_interface);
// Split the Bezier at the parameter `t` // Split the Bezier at the parameter `t`
let [first, second] = self.bezier.split(TValue::Parametric(self.t)); let [first, second] = self.bezier.split(TValue::Parametric(self.t));
@@ -307,7 +307,7 @@ impl ClosestSegment {
} }
pub fn calculate_perp(&self, document: &DocumentMessageHandler) -> DVec2 { pub fn calculate_perp(&self, document: &DocumentMessageHandler) -> DVec2 {
let tangent = if let (Some(handle1), Some(handle2)) = self.handle_positions(document.metadata()) { let tangent = if let (Some(handle1), Some(handle2)) = self.handle_positions(document.metadata(), &document.network_interface) {
(handle1 - handle2).try_normalize() (handle1 - handle2).try_normalize()
} else { } else {
let [first_point, last_point] = self.points(); let [first_point, last_point] = self.points();
@@ -339,7 +339,7 @@ impl ClosestSegment {
break_colinear_molding: bool, break_colinear_molding: bool,
temporary_adjacent_handles_while_molding: Option<[Option<HandleId>; 2]>, temporary_adjacent_handles_while_molding: Option<[Option<HandleId>; 2]>,
) -> Option<[Option<HandleId>; 2]> { ) -> Option<[Option<HandleId>; 2]> {
let transform = document.metadata().transform_to_viewport(self.layer); let transform = document.metadata().transform_to_viewport_if_feeds(self.layer, &document.network_interface);
let start = self.bezier.start; let start = self.bezier.start;
let end = self.bezier.end; let end = self.bezier.end;
@@ -507,7 +507,7 @@ impl ShapeState {
continue; continue;
}; };
let to_document = document.metadata().transform_to_document(*layer); let to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);
for &selected in &state.selected_points { for &selected in &state.selected_points {
let source = match selected { let source = match selected {
@@ -564,7 +564,11 @@ impl ShapeState {
let already_selected = selected_shape_state.is_point_selected(manipulator_point_id); let already_selected = selected_shape_state.is_point_selected(manipulator_point_id);
// Offset to snap the selected point to the cursor // Offset to snap the selected point to the cursor
let offset = mouse_position - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(point_position); let offset = mouse_position
- network_interface
.document_metadata()
.transform_to_viewport_if_feeds(layer, network_interface)
.transform_point2(point_position);
// This is selecting the manipulator only for now, next to generalize to points // This is selecting the manipulator only for now, next to generalize to points
@@ -621,7 +625,11 @@ impl ShapeState {
let already_selected = selected_shape_state.is_point_selected(manipulator_point_id); let already_selected = selected_shape_state.is_point_selected(manipulator_point_id);
// Offset to snap the selected point to the cursor // Offset to snap the selected point to the cursor
let offset = mouse_position - network_interface.document_metadata().transform_to_viewport(layer).transform_point2(point_position); let offset = mouse_position
- network_interface
.document_metadata()
.transform_to_viewport_if_feeds(layer, network_interface)
.transform_point2(point_position);
// Gather current selection information // Gather current selection information
let points = self let points = self
@@ -653,7 +661,7 @@ impl ShapeState {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
return; return;
}; };
let to_viewport = document.metadata().transform_to_viewport(layer); let to_viewport = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
let layer_mouse = to_viewport.inverse().transform_point2(mouse); let layer_mouse = to_viewport.inverse().transform_point2(mouse);
let state = self.selected_shape_state.entry(layer).or_default(); let state = self.selected_shape_state.entry(layer).or_default();
@@ -875,7 +883,7 @@ impl ShapeState {
} }
let vector_data = network_interface.compute_modified_vector(layer)?; let vector_data = network_interface.compute_modified_vector(layer)?;
let transform = network_interface.document_metadata().transform_to_document(layer).inverse(); let transform = network_interface.document_metadata().transform_to_document_if_feeds(layer, network_interface).inverse();
let position = transform.transform_point2(new_position); let position = transform.transform_point2(new_position);
let current_position = point.get_position(&vector_data)?; let current_position = point.get_position(&vector_data)?;
let delta = position - current_position; let delta = position - current_position;
@@ -1026,7 +1034,7 @@ impl ShapeState {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
continue; continue;
}; };
let transform = document.metadata().transform_to_document(layer); let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
for &point in layer_state.selected_points.iter() { for &point in layer_state.selected_points.iter() {
let Some(handles) = point.get_handle_pair(&vector_data) else { continue }; let Some(handles) = point.get_handle_pair(&vector_data) else { continue };
@@ -1071,7 +1079,7 @@ impl ShapeState {
let mut normalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize())); 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.)); 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())) normalized = handle_directions[0].and_then(|a| handle_directions[1].and_then(|b| (a - b).try_normalize()))
} }
@@ -1116,8 +1124,8 @@ impl ShapeState {
let opposing_handles = handle_lengths.as_ref().and_then(|handle_lengths| handle_lengths.get(&layer)); let opposing_handles = handle_lengths.as_ref().and_then(|handle_lengths| handle_lengths.get(&layer));
let transform_to_viewport_space = document.metadata().transform_to_viewport(layer); let transform_to_viewport_space = document.metadata().transform_to_viewport_if_feeds(layer, &document.network_interface);
let transform_to_document_space = document.metadata().transform_to_document(layer); let transform_to_document_space = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
let delta_transform = if in_viewport_space { let delta_transform = if in_viewport_space {
transform_to_viewport_space transform_to_viewport_space
} else { } else {
@@ -1210,7 +1218,7 @@ impl ShapeState {
.iter() .iter()
.filter_map(|(&layer, state)| { .filter_map(|(&layer, state)| {
let vector_data = document.network_interface.compute_modified_vector(layer)?; let vector_data = document.network_interface.compute_modified_vector(layer)?;
let transform = document.metadata().transform_to_document(layer); let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
let opposing_handle_lengths = vector_data let opposing_handle_lengths = vector_data
.colinear_manipulators .colinear_manipulators
.iter() .iter()
@@ -1575,7 +1583,7 @@ impl ShapeState {
let mut manipulator_point = None; let mut manipulator_point = None;
let vector_data = network_interface.compute_modified_vector(layer)?; let vector_data = network_interface.compute_modified_vector(layer)?;
let viewspace = network_interface.document_metadata().transform_to_viewport(layer); let viewspace = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
// Handles // Handles
for (segment_id, bezier, _, _) in vector_data.segment_bezier_iter() { for (segment_id, bezier, _, _) in vector_data.segment_bezier_iter() {
@@ -1611,7 +1619,7 @@ impl ShapeState {
/// Find the `t` value along the path segment we have clicked upon, together with that segment ID. /// Find the `t` value along the path segment we have clicked upon, together with that segment ID.
fn closest_segment(&self, network_interface: &NodeNetworkInterface, layer: LayerNodeIdentifier, position: glam::DVec2, tolerance: f64) -> Option<ClosestSegment> { fn closest_segment(&self, network_interface: &NodeNetworkInterface, layer: LayerNodeIdentifier, position: glam::DVec2, tolerance: f64) -> Option<ClosestSegment> {
let transform = network_interface.document_metadata().transform_to_viewport(layer); let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
let layer_pos = transform.inverse().transform_point2(position); let layer_pos = transform.inverse().transform_point2(position);
let tolerance = tolerance + 0.5; let tolerance = tolerance + 0.5;
@@ -1785,7 +1793,7 @@ impl ShapeState {
pub fn flip_smooth_sharp(&self, network_interface: &NodeNetworkInterface, target: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool { pub fn flip_smooth_sharp(&self, network_interface: &NodeNetworkInterface, target: glam::DVec2, tolerance: f64, responses: &mut VecDeque<Message>) -> bool {
let mut process_layer = |layer| { let mut process_layer = |layer| {
let vector_data = network_interface.compute_modified_vector(layer)?; let vector_data = network_interface.compute_modified_vector(layer)?;
let transform_to_screenspace = network_interface.document_metadata().transform_to_viewport(layer); let transform_to_screenspace = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
let mut result = None; let mut result = None;
let mut closest_distance_squared = tolerance * tolerance; let mut closest_distance_squared = tolerance * tolerance;
@@ -1889,7 +1897,7 @@ impl ShapeState {
let vector_data = network_interface.compute_modified_vector(layer); let vector_data = network_interface.compute_modified_vector(layer);
let Some(vector_data) = vector_data else { continue }; let Some(vector_data) = vector_data else { continue };
let transform = network_interface.document_metadata().transform_to_viewport(layer); let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
assert_eq!(vector_data.segment_domain.ids().len(), vector_data.start_point().count()); assert_eq!(vector_data.segment_domain.ids().len(), vector_data.start_point().count());
assert_eq!(vector_data.segment_domain.ids().len(), vector_data.end_point().count()); assert_eq!(vector_data.segment_domain.ids().len(), vector_data.end_point().count());
@@ -77,7 +77,7 @@ mod test_ellipse {
layers layers
.filter_map(|layer| { .filter_map(|layer| {
let node_graph_layer = NodeGraphLayer::new(layer, &document.network_interface); let node_graph_layer = NodeGraphLayer::new(layer, &document.network_interface);
let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::protonode_identifier())?; let ellipse_node = node_graph_layer.upstream_node_id_from_protonode(ellipse::IDENTIFIER)?;
Some(ResolvedEllipse { Some(ResolvedEllipse {
radius_x: instrumented.grab_protonode_input::<ellipse::RadiusXInput>(&vec![ellipse_node], &editor.runtime).unwrap(), radius_x: instrumented.grab_protonode_input::<ellipse::RadiusXInput>(&vec![ellipse_node], &editor.runtime).unwrap(),
radius_y: instrumented.grab_protonode_input::<ellipse::RadiusYInput>(&vec![ellipse_node], &editor.runtime).unwrap(), radius_y: instrumented.grab_protonode_input::<ellipse::RadiusYInput>(&vec![ellipse_node], &editor.runtime).unwrap(),
@@ -393,6 +393,7 @@ pub fn transforming_transform_cage(
input: &InputPreprocessorMessageHandler, input: &InputPreprocessorMessageHandler,
responses: &mut VecDeque<Message>, responses: &mut VecDeque<Message>,
layers_dragging: &mut Vec<LayerNodeIdentifier>, layers_dragging: &mut Vec<LayerNodeIdentifier>,
center_of_transformation: Option<DVec2>,
) -> (bool, bool, bool) { ) -> (bool, bool, bool) {
let dragging_bounds = bounding_box_manager.as_mut().and_then(|bounding_box| { let dragging_bounds = bounding_box_manager.as_mut().and_then(|bounding_box| {
let edges = bounding_box.check_selected_edges(input.mouse.position); let edges = bounding_box.check_selected_edges(input.mouse.position);
@@ -429,17 +430,12 @@ pub fn transforming_transform_cage(
} }
}); });
let mut selected = Selected::new( bounds.center_of_transformation = center_of_transformation.unwrap_or_else(|| {
&mut bounds.original_transforms, document
&mut bounds.center_of_transformation, .network_interface
layers_dragging, .selected_nodes()
responses, .selected_visible_and_unlocked_layers_mean_average_origin(&document.network_interface)
&document.network_interface, });
None,
&ToolType::Select,
None,
);
bounds.center_of_transformation = selected.mean_average_of_pivots();
// Check if we're hovering over a skew triangle // Check if we're hovering over a skew triangle
let edges = bounds.check_selected_edges(input.mouse.position); let edges = bounds.check_selected_edges(input.mouse.position);
@@ -469,18 +465,12 @@ pub fn transforming_transform_cage(
} }
}); });
let mut selected = Selected::new( bounds.center_of_transformation = center_of_transformation.unwrap_or_else(|| {
&mut bounds.original_transforms, document
&mut bounds.center_of_transformation, .network_interface
&selected, .selected_nodes()
responses, .selected_visible_and_unlocked_layers_mean_average_origin(&document.network_interface)
&document.network_interface, });
None,
&ToolType::Select,
None,
);
bounds.center_of_transformation = selected.mean_average_of_pivots();
} }
*layers_dragging = selected; *layers_dragging = selected;
@@ -12,6 +12,7 @@ use graphene_std::raster::color::Color;
const ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |context| DocumentMessage::DrawArtboardOverlays(context).into(); const ARTBOARD_OVERLAY_PROVIDER: OverlayProvider = |context| DocumentMessage::DrawArtboardOverlays(context).into();
#[derive(ExtractField)]
pub struct ToolMessageData<'a> { pub struct ToolMessageData<'a> {
pub document_id: DocumentId, pub document_id: DocumentId,
pub document: &'a mut DocumentMessageHandler, pub document: &'a mut DocumentMessageHandler,
@@ -21,7 +22,7 @@ pub struct ToolMessageData<'a> {
pub preferences: &'a PreferencesMessageHandler, pub preferences: &'a PreferencesMessageHandler,
} }
#[derive(Debug, Default)] #[derive(Debug, Default, ExtractField)]
pub struct ToolMessageHandler { pub struct ToolMessageHandler {
pub tool_state: ToolFsmState, pub tool_state: ToolFsmState,
pub transform_layer_handler: TransformLayerMessageHandler, pub transform_layer_handler: TransformLayerMessageHandler,
@@ -29,6 +30,7 @@ pub struct ToolMessageHandler {
pub tool_is_active: bool, pub tool_is_active: bool,
} }
#[message_handler_data]
impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler { impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, data: ToolMessageData) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, data: ToolMessageData) {
let ToolMessageData { let ToolMessageData {
@@ -181,6 +183,11 @@ impl MessageHandler<ToolMessage, ToolMessageData<'_>> for ToolMessageHandler {
send: Box::new(TransformLayerMessage::SelectionChanged.into()), send: Box::new(TransformLayerMessage::SelectionChanged.into()),
}); });
responses.add(BroadcastMessage::SubscribeEvent {
on: BroadcastEvent::SelectionChanged,
send: Box::new(SelectToolMessage::SyncHistory.into()),
});
self.tool_is_active = true; self.tool_is_active = true;
let tool_data = &mut self.tool_state.tool_data; let tool_data = &mut self.tool_state.tool_data;
@@ -13,7 +13,7 @@ use crate::messages::tool::common_functionality::transformation_cage::*;
use graph_craft::document::NodeId; use graph_craft::document::NodeId;
use graphene_std::renderer::Quad; use graphene_std::renderer::Quad;
#[derive(Default)] #[derive(Default, ExtractField)]
pub struct ArtboardTool { pub struct ArtboardTool {
fsm_state: ArtboardToolFsmState, fsm_state: ArtboardToolFsmState,
data: ArtboardToolData, data: ArtboardToolData,
@@ -48,6 +48,7 @@ impl ToolMetadata for ArtboardTool {
} }
} }
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ArtboardTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for ArtboardTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, false); self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, false);
@@ -567,7 +568,7 @@ mod test_artboard {
Ok(instrumented) => instrumented, Ok(instrumented) => instrumented,
Err(e) => panic!("Failed to evaluate graph: {}", e), Err(e) => panic!("Failed to evaluate graph: {}", e),
}; };
instrumented.grab_all_input::<graphene_std::append_artboard::ArtboardInput>(&editor.runtime).collect() instrumented.grab_all_input::<graphene_std::graphic_element::append_artboard::ArtboardInput>(&editor.runtime).collect()
} }
#[tokio::test] #[tokio::test]
@@ -1,6 +1,6 @@
use super::tool_prelude::*; use super::tool_prelude::*;
use crate::consts::DEFAULT_BRUSH_SIZE; 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::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::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::network_interface::FlowType; use crate::messages::portfolio::document::utility_types::network_interface::FlowType;
@@ -20,7 +20,7 @@ pub enum DrawMode {
Restore, Restore,
} }
#[derive(Default)] #[derive(Default, ExtractField)]
pub struct BrushTool { pub struct BrushTool {
fsm_state: BrushToolFsmState, fsm_state: BrushToolFsmState,
data: BrushToolData, data: BrushToolData,
@@ -185,6 +185,7 @@ impl LayoutHolder for BrushTool {
} }
} }
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for BrushTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for BrushTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message else { let ToolMessage::Brush(BrushToolMessage::UpdateOptions(action)) = message else {
@@ -286,9 +287,7 @@ impl BrushToolData {
} }
if *reference == Some("Transform".to_string()) { if *reference == Some("Transform".to_string()) {
let upstream = document.metadata().upstream_transform(node_id); self.transform = get_current_transform(&node.inputs) * self.transform;
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;
} }
} }
@@ -1,7 +1,7 @@
use super::tool_prelude::*; use super::tool_prelude::*;
use crate::messages::tool::utility_types::DocumentToolData; use crate::messages::tool::utility_types::DocumentToolData;
#[derive(Default)] #[derive(Default, ExtractField)]
pub struct EyedropperTool { pub struct EyedropperTool {
fsm_state: EyedropperToolFsmState, fsm_state: EyedropperToolFsmState,
data: EyedropperToolData, data: EyedropperToolData,
@@ -39,6 +39,7 @@ impl LayoutHolder for EyedropperTool {
} }
} }
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for EyedropperTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for EyedropperTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true); self.fsm_state.process_event(message, &mut self.data, tool_data, &(), responses, true);
@@ -3,7 +3,7 @@ use crate::messages::portfolio::document::overlays::utility_types::OverlayContex
use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer; use crate::messages::tool::common_functionality::graph_modification_utils::NodeGraphLayer;
use graphene_std::vector::style::Fill; use graphene_std::vector::style::Fill;
#[derive(Default)] #[derive(Default, ExtractField)]
pub struct FillTool { pub struct FillTool {
fsm_state: FillToolFsmState, fsm_state: FillToolFsmState,
} }
@@ -41,6 +41,7 @@ impl LayoutHolder for FillTool {
} }
} }
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FillTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FillTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut (), tool_data, &(), responses, true); self.fsm_state.process_event(message, &mut (), tool_data, &(), responses, true);
@@ -13,7 +13,7 @@ use graphene_std::Color;
use graphene_std::vector::VectorModificationType; use graphene_std::vector::VectorModificationType;
use graphene_std::vector::{PointId, SegmentId}; use graphene_std::vector::{PointId, SegmentId};
#[derive(Default)] #[derive(Default, ExtractField)]
pub struct FreehandTool { pub struct FreehandTool {
fsm_state: FreehandToolFsmState, fsm_state: FreehandToolFsmState,
data: FreehandToolData, data: FreehandToolData,
@@ -116,6 +116,7 @@ impl LayoutHolder for FreehandTool {
} }
} }
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FreehandTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for FreehandTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message else { let ToolMessage::Freehand(FreehandToolMessage::UpdateOptions(action)) = message else {
@@ -7,7 +7,7 @@ use crate::messages::tool::common_functionality::graph_modification_utils::{Node
use crate::messages::tool::common_functionality::snapping::SnapManager; use crate::messages::tool::common_functionality::snapping::SnapManager;
use graphene_std::vector::style::{Fill, Gradient, GradientType}; use graphene_std::vector::style::{Fill, Gradient, GradientType};
#[derive(Default)] #[derive(Default, ExtractField)]
pub struct GradientTool { pub struct GradientTool {
fsm_state: GradientToolFsmState, fsm_state: GradientToolFsmState,
data: GradientToolData, data: GradientToolData,
@@ -53,6 +53,7 @@ impl ToolMetadata for GradientTool {
} }
} }
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for GradientTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for GradientTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message else { let ToolMessage::Gradient(GradientToolMessage::UpdateOptions(action)) = message else {
@@ -1,6 +1,6 @@
use super::tool_prelude::*; use super::tool_prelude::*;
#[derive(Default)] #[derive(Default, ExtractField)]
pub struct NavigateTool { pub struct NavigateTool {
fsm_state: NavigateToolFsmState, fsm_state: NavigateToolFsmState,
tool_data: NavigateToolData, tool_data: NavigateToolData,
@@ -38,6 +38,7 @@ impl LayoutHolder for NavigateTool {
} }
} }
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for NavigateTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for NavigateTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true); self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, true);
@@ -11,6 +11,7 @@ use crate::messages::portfolio::document::utility_types::network_interface::Node
use crate::messages::portfolio::document::utility_types::transformation::Axis; use crate::messages::portfolio::document::utility_types::transformation::Axis;
use crate::messages::preferences::SelectionMode; use crate::messages::preferences::SelectionMode;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget};
use crate::messages::tool::common_functionality::shape_editor::{ use crate::messages::tool::common_functionality::shape_editor::{
ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedLayerState, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState, ClosestSegment, ManipulatorAngle, OpposingHandleLengths, SelectedLayerState, SelectedPointsInfo, SelectionChange, SelectionShape, SelectionShapeType, ShapeState,
}; };
@@ -18,11 +19,12 @@ use crate::messages::tool::common_functionality::snapping::{SnapCache, SnapCandi
use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate}; use crate::messages::tool::common_functionality::utility_functions::{calculate_segment_angle, find_two_param_best_approximate};
use bezier_rs::{Bezier, BezierHandles, TValue}; use bezier_rs::{Bezier, BezierHandles, TValue};
use graphene_std::renderer::Quad; use graphene_std::renderer::Quad;
use graphene_std::transform::ReferencePoint;
use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, VectorData}; use graphene_std::vector::{HandleExt, HandleId, NoHashBuilder, SegmentId, VectorData};
use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType}; use graphene_std::vector::{ManipulatorPointId, PointId, VectorModificationType};
use std::vec; use std::vec;
#[derive(Default)] #[derive(Default, ExtractField)]
pub struct PathTool { pub struct PathTool {
fsm_state: PathToolFsmState, fsm_state: PathToolFsmState,
tool_data: PathToolData, tool_data: PathToolData,
@@ -106,6 +108,9 @@ pub enum PathToolMessage {
SelectedPointYChanged { SelectedPointYChanged {
new_y: f64, new_y: f64,
}, },
SetPivot {
position: ReferencePoint,
},
SwapSelectedHandles, SwapSelectedHandles,
UpdateOptions(PathOptionsUpdate), UpdateOptions(PathOptionsUpdate),
UpdateSelectedPointsStatus { UpdateSelectedPointsStatus {
@@ -141,6 +146,9 @@ pub enum PathOptionsUpdate {
OverlayModeType(PathOverlayMode), OverlayModeType(PathOverlayMode),
PointEditingMode { enabled: bool }, PointEditingMode { enabled: bool },
SegmentEditingMode { enabled: bool }, SegmentEditingMode { enabled: bool },
PivotGizmoType(PivotGizmoType),
TogglePivotGizmoType(bool),
TogglePivotPinned,
} }
impl ToolMetadata for PathTool { impl ToolMetadata for PathTool {
@@ -255,6 +263,20 @@ impl LayoutHolder for PathTool {
.selected_index(Some(self.options.path_overlay_mode as u32)) .selected_index(Some(self.options.path_overlay_mode as u32))
.widget_holder(); .widget_holder();
let [_checkbox, _dropdown] = {
let pivot_gizmo_type_widget = pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Path);
[pivot_gizmo_type_widget[0].clone(), pivot_gizmo_type_widget[2].clone()]
};
let has_something = !self.tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty();
let _pivot_reference = pivot_reference_point_widget(
has_something || !self.tool_data.pivot_gizmo.state.is_pivot(),
self.tool_data.pivot_gizmo.pivot.to_pivot_position(),
PivotToolSource::Path,
);
let _pin_pivot = pin_pivot_widget(self.tool_data.pivot_gizmo.pin_active(), false, PivotToolSource::Path);
Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row { Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
widgets: vec![ widgets: vec![
x_location, x_location,
@@ -268,13 +290,22 @@ impl LayoutHolder for PathTool {
point_editing_mode, point_editing_mode,
related_seperator.clone(), related_seperator.clone(),
segment_editing_mode, segment_editing_mode,
unrelated_seperator, unrelated_seperator.clone(),
path_overlay_mode_widget, path_overlay_mode_widget,
unrelated_seperator.clone(),
// checkbox.clone(),
// related_seperator.clone(),
// dropdown.clone(),
// unrelated_seperator,
// pivot_reference,
// related_seperator.clone(),
// pin_pivot,
], ],
}])) }]))
} }
} }
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
let updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated); let updating_point = message == ToolMessage::Path(PathToolMessage::SelectedPointUpdated);
@@ -293,6 +324,29 @@ impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PathToo
self.options.path_editing_mode.segment_editing_mode = enabled; self.options.path_editing_mode.segment_editing_mode = enabled;
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
} }
PathOptionsUpdate::PivotGizmoType(gizmo_type) => {
if !self.tool_data.pivot_gizmo.state.disabled {
self.tool_data.pivot_gizmo.state.gizmo_type = gizmo_type;
responses.add(ToolMessage::UpdateHints);
let pivot_gizmo = self.tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
responses.add(NodeGraphMessage::RunDocumentGraph);
self.send_layout(responses, LayoutTarget::ToolOptions);
}
}
PathOptionsUpdate::TogglePivotGizmoType(state) => {
self.tool_data.pivot_gizmo.state.disabled = !state;
responses.add(ToolMessage::UpdateHints);
responses.add(NodeGraphMessage::RunDocumentGraph);
self.send_layout(responses, LayoutTarget::ToolOptions);
}
PathOptionsUpdate::TogglePivotPinned => {
self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned;
responses.add(ToolMessage::UpdateHints);
responses.add(NodeGraphMessage::RunDocumentGraph);
self.send_layout(responses, LayoutTarget::ToolOptions);
}
}, },
ToolMessage::Path(PathToolMessage::ClosePath) => { ToolMessage::Path(PathToolMessage::ClosePath) => {
responses.add(DocumentMessage::AddTransaction); responses.add(DocumentMessage::AddTransaction);
@@ -443,6 +497,8 @@ struct PathToolData {
last_click_time: u64, last_click_time: u64,
dragging_state: DraggingState, dragging_state: DraggingState,
angle: f64, angle: f64,
pivot_gizmo: PivotGizmo,
ordered_points: Vec<ManipulatorPointId>,
opposite_handle_position: Option<DVec2>, opposite_handle_position: Option<DVec2>,
last_clicked_point_was_selected: bool, last_clicked_point_was_selected: bool,
last_clicked_segment_was_selected: bool, last_clicked_segment_was_selected: bool,
@@ -718,7 +774,7 @@ impl PathToolData {
let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else { let Some(vector_data) = document.network_interface.compute_modified_vector(layer) else {
continue; continue;
}; };
let transform = document.metadata().transform_to_document(layer); let transform = document.metadata().transform_to_document_if_feeds(layer, &document.network_interface);
let mut layer_manipulators = HashSet::with_hasher(NoHashBuilder); let mut layer_manipulators = HashSet::with_hasher(NoHashBuilder);
for point in state.selected_points() { for point in state.selected_points() {
@@ -828,7 +884,7 @@ impl PathToolData {
let selected_handle = selection.selected_points().next()?.as_handle()?; let selected_handle = selection.selected_points().next()?.as_handle()?;
let handle_id = selected_handle.to_manipulator_point(); let handle_id = selected_handle.to_manipulator_point();
let layer_to_document = document.metadata().transform_to_document(*layer); let layer_to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);
let vector_data = document.network_interface.compute_modified_vector(*layer)?; let vector_data = document.network_interface.compute_modified_vector(*layer)?;
let handle_position_local = selected_handle.to_manipulator_point().get_position(&vector_data)?; let handle_position_local = selected_handle.to_manipulator_point().get_position(&vector_data)?;
@@ -870,7 +926,7 @@ impl PathToolData {
let anchor = handle_id.get_anchor(&vector_data); let anchor = handle_id.get_anchor(&vector_data);
let (angle, anchor_position) = calculate_adjacent_anchor_tangent(handle_id, anchor, adjacent_anchor, &vector_data); let (angle, anchor_position) = calculate_adjacent_anchor_tangent(handle_id, anchor, adjacent_anchor, &vector_data);
let layer_to_document = document.metadata().transform_to_document(*layer); let layer_to_document = document.metadata().transform_to_document_if_feeds(*layer, &document.network_interface);
self.adjacent_anchor_offset = handle_id self.adjacent_anchor_offset = handle_id
.get_anchor_position(&vector_data) .get_anchor_position(&vector_data)
@@ -1017,7 +1073,7 @@ impl PathToolData {
} }
// If already hovering on a segment, then recalculate its closest point // If already hovering on a segment, then recalculate its closest point
else if let Some(closest_segment) = &mut self.segment { else if let Some(closest_segment) = &mut self.segment {
closest_segment.update_closest_point(document.metadata(), position); closest_segment.update_closest_point(document.metadata(), &document.network_interface, position);
if closest_segment.too_far(position, SEGMENT_INSERTION_DISTANCE) { if closest_segment.too_far(position, SEGMENT_INSERTION_DISTANCE) {
self.segment = None; self.segment = None;
@@ -1084,7 +1140,7 @@ impl PathToolData {
let layer = sliding_point_info.layer; let layer = sliding_point_info.layer;
let Some(vector_data) = network_interface.compute_modified_vector(layer) else { return }; let Some(vector_data) = network_interface.compute_modified_vector(layer) else { return };
let transform = network_interface.document_metadata().transform_to_viewport(layer); let transform = network_interface.document_metadata().transform_to_viewport_if_feeds(layer, network_interface);
let layer_pos = transform.inverse().transform_point2(target_position); let layer_pos = transform.inverse().transform_point2(target_position);
let segments = sliding_point_info.connected_segments; let segments = sliding_point_info.connected_segments;
@@ -1315,6 +1371,16 @@ impl PathToolData {
} }
} }
} }
fn pivot_gizmo(&self) -> PivotGizmo {
self.pivot_gizmo.clone()
}
fn sync_history(&mut self, points: &[ManipulatorPointId]) {
self.ordered_points.retain(|layer| points.contains(layer));
self.ordered_points.extend(points.iter().find(|&layer| !self.ordered_points.contains(layer)));
self.pivot_gizmo.point = self.ordered_points.last().copied()
}
} }
impl Fsm for PathToolFsmState { impl Fsm for PathToolFsmState {
@@ -1327,6 +1393,10 @@ impl Fsm for PathToolFsmState {
update_dynamic_hints(self, responses, shape_editor, document, tool_data, tool_options); update_dynamic_hints(self, responses, shape_editor, document, tool_data, tool_options);
let ToolMessage::Path(event) = event else { return self }; let ToolMessage::Path(event) = event else { return self };
// TODO(mTvare6): Remove once gizmos are implemented for path_tool
tool_data.pivot_gizmo.state.disabled = true;
match (self, event) { match (self, event) {
(_, PathToolMessage::SelectionChanged) => { (_, PathToolMessage::SelectionChanged) => {
// Set the newly targeted layers to visible // Set the newly targeted layers to visible
@@ -1343,6 +1413,9 @@ impl Fsm for PathToolFsmState {
shape_editor.update_selected_anchors_status(display_anchors); shape_editor.update_selected_anchors_status(display_anchors);
shape_editor.update_selected_handles_status(display_handles); shape_editor.update_selected_handles_status(display_handles);
let new_points = shape_editor.selected_points().copied().collect::<Vec<_>>();
tool_data.sync_history(&new_points);
self self
} }
(_, PathToolMessage::Overlays(mut overlay_context)) => { (_, PathToolMessage::Overlays(mut overlay_context)) => {
@@ -1412,7 +1485,7 @@ impl Fsm for PathToolFsmState {
if let Some(closest_segment) = &tool_data.segment { if let Some(closest_segment) = &tool_data.segment {
if tool_options.path_editing_mode.segment_editing_mode { if tool_options.path_editing_mode.segment_editing_mode {
let transform = document.metadata().transform_to_viewport(closest_segment.layer()); let transform = document.metadata().transform_to_viewport_if_feeds(closest_segment.layer(), &document.network_interface);
overlay_context.outline_overlay_bezier(closest_segment.bezier(), transform); overlay_context.outline_overlay_bezier(closest_segment.bezier(), transform);
@@ -1430,7 +1503,7 @@ impl Fsm for PathToolFsmState {
} }
} else { } else {
let perp = closest_segment.calculate_perp(document); let perp = closest_segment.calculate_perp(document);
let point = closest_segment.closest_point(document.metadata()); let point = closest_segment.closest_point(document.metadata(), &document.network_interface);
// Draw an X on the segment // Draw an X on the segment
if tool_data.delete_segment_pressed { if tool_data.delete_segment_pressed {
@@ -1710,14 +1783,8 @@ impl Fsm for PathToolFsmState {
} }
(PathToolFsmState::Ready, PathToolMessage::PointerMove { delete_segment, .. }) => { (PathToolFsmState::Ready, PathToolMessage::PointerMove { delete_segment, .. }) => {
tool_data.delete_segment_pressed = input.keyboard.get(delete_segment as usize); tool_data.delete_segment_pressed = input.keyboard.get(delete_segment as usize);
if !tool_data.saved_points_before_anchor_convert_smooth_sharp.is_empty() {
tool_data.saved_points_before_anchor_convert_smooth_sharp.clear(); tool_data.saved_points_before_anchor_convert_smooth_sharp.clear();
}
if tool_data.adjacent_anchor_offset.is_some() {
tool_data.adjacent_anchor_offset = None; tool_data.adjacent_anchor_offset = None;
}
tool_data.stored_selection = None; tool_data.stored_selection = None;
responses.add(OverlaysMessage::Draw); responses.add(OverlaysMessage::Draw);
@@ -2208,6 +2275,18 @@ impl Fsm for PathToolFsmState {
responses.add(DocumentMessage::EndTransaction); responses.add(DocumentMessage::EndTransaction);
PathToolFsmState::Ready PathToolFsmState::Ready
} }
(_, PathToolMessage::SetPivot { position }) => {
responses.add(DocumentMessage::StartTransaction);
tool_data.pivot_gizmo.pivot.last_non_none_reference_point = position;
let position: Option<DVec2> = position.into();
tool_data.pivot_gizmo.pivot.set_normalized_position(position.unwrap());
let pivot_gizmo = tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
responses.add(NodeGraphMessage::RunDocumentGraph);
self
}
(_, _) => PathToolFsmState::Ready, (_, _) => PathToolFsmState::Ready,
} }
} }
@@ -2280,7 +2359,10 @@ fn get_selection_status(network_interface: &NodeNetworkInterface, shape_state: &
return SelectionStatus::None; return SelectionStatus::None;
}; };
let coordinates = network_interface.document_metadata().transform_to_document(layer).transform_point2(local_position); let coordinates = network_interface
.document_metadata()
.transform_to_document_if_feeds(layer, network_interface)
.transform_point2(local_position);
let manipulator_angle = if vector_data.colinear(point) { ManipulatorAngle::Colinear } else { ManipulatorAngle::Free }; let manipulator_angle = if vector_data.colinear(point) { ManipulatorAngle::Colinear } else { ManipulatorAngle::Free };
return SelectionStatus::One(SingleSelectedPoint { return SelectionStatus::One(SingleSelectedPoint {
@@ -17,7 +17,7 @@ use graphene_std::Color;
use graphene_std::vector::{HandleId, ManipulatorPointId, NoHashBuilder, SegmentId, StrokeId, VectorData}; use graphene_std::vector::{HandleId, ManipulatorPointId, NoHashBuilder, SegmentId, StrokeId, VectorData};
use graphene_std::vector::{PointId, VectorModificationType}; use graphene_std::vector::{PointId, VectorModificationType};
#[derive(Default)] #[derive(Default, ExtractField)]
pub struct PenTool { pub struct PenTool {
fsm_state: PenToolFsmState, fsm_state: PenToolFsmState,
tool_data: PenToolData, tool_data: PenToolData,
@@ -186,6 +186,7 @@ impl LayoutHolder for PenTool {
} }
} }
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for PenTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message else { let ToolMessage::Pen(PenToolMessage::UpdateOptions(action)) = message else {
@@ -12,9 +12,10 @@ use crate::messages::portfolio::document::utility_types::nodes::SelectedNodes;
use crate::messages::preferences::SelectionMode; use crate::messages::preferences::SelectionMode;
use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose}; use crate::messages::tool::common_functionality::compass_rose::{Axis, CompassRose};
use crate::messages::tool::common_functionality::graph_modification_utils;
use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name; use crate::messages::tool::common_functionality::graph_modification_utils::is_layer_fed_by_node_of_name;
use crate::messages::tool::common_functionality::measure; use crate::messages::tool::common_functionality::measure;
use crate::messages::tool::common_functionality::pivot::Pivot; use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType, PivotToolSource, pin_pivot_widget, pivot_gizmo_type_widget, pivot_reference_point_widget};
use crate::messages::tool::common_functionality::shape_editor::SelectionShapeType; use crate::messages::tool::common_functionality::shape_editor::SelectionShapeType;
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager}; use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData, SnapManager};
use crate::messages::tool::common_functionality::transformation_cage::*; use crate::messages::tool::common_functionality::transformation_cage::*;
@@ -28,7 +29,7 @@ use graphene_std::renderer::Rect;
use graphene_std::transform::ReferencePoint; use graphene_std::transform::ReferencePoint;
use std::fmt; use std::fmt;
#[derive(Default)] #[derive(Default, ExtractField)]
pub struct SelectTool { pub struct SelectTool {
fsm_state: SelectToolFsmState, fsm_state: SelectToolFsmState,
tool_data: SelectToolData, tool_data: SelectToolData,
@@ -43,6 +44,9 @@ pub struct SelectOptions {
#[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(PartialEq, Eq, Clone, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
pub enum SelectOptionsUpdate { pub enum SelectOptionsUpdate {
NestedSelectionBehavior(NestedSelectionBehavior), NestedSelectionBehavior(NestedSelectionBehavior),
PivotGizmoType(PivotGizmoType),
TogglePivotGizmoType(bool),
TogglePivotPinned,
} }
#[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)] #[derive(Default, PartialEq, Eq, Clone, Copy, Debug, Hash, serde::Serialize, serde::Deserialize, specta::Type)]
@@ -95,6 +99,14 @@ pub enum SelectToolMessage {
SetPivot { SetPivot {
position: ReferencePoint, position: ReferencePoint,
}, },
SyncHistory,
ShiftSelectedNodes {
offset: DVec2,
},
PivotShift {
offset: Option<DVec2>,
flush: bool,
},
} }
impl ToolMetadata for SelectTool { impl ToolMetadata for SelectTool {
@@ -122,14 +134,12 @@ impl SelectTool {
DropdownInput::new(vec![layer_selection_behavior_entries]) DropdownInput::new(vec![layer_selection_behavior_entries])
.selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest) as u32)) .selected_index(Some((self.tool_data.nested_selection_behavior == NestedSelectionBehavior::Deepest) as u32))
.tooltip("Choose if clicking nested layers directly selects the deepest, or selects the shallowest and deepens by double clicking") .tooltip(
.widget_holder() "Selection Mode\n\
} \n\
Shallow Select: clicks initially select the least-nested layers and double clicks drill deeper into the folder hierarchy.\n\
fn pivot_reference_point_widget(&self, disabled: bool) -> WidgetHolder { Deep Select: clicks directly select the most-nested layers in the folder hierarchy.",
ReferencePointInput::new(self.tool_data.pivot.to_pivot_position()) )
.on_update(|pivot_input: &ReferencePointInput| SelectToolMessage::SetPivot { position: pivot_input.value }.into())
.disabled(disabled)
.widget_holder() .widget_holder()
} }
@@ -178,7 +188,7 @@ impl SelectTool {
fn boolean_widgets(&self, selected_count: usize) -> impl Iterator<Item = WidgetHolder> + use<> { fn boolean_widgets(&self, selected_count: usize) -> impl Iterator<Item = WidgetHolder> + use<> {
let list = <BooleanOperation as graphene_std::registry::ChoiceTypeStatic>::list(); let list = <BooleanOperation as graphene_std::registry::ChoiceTypeStatic>::list();
list.into_iter().map(|i| i.into_iter()).flatten().map(move |(operation, info)| { list.iter().flat_map(|i| i.iter()).map(move |(operation, info)| {
let mut tooltip = info.label.to_string(); let mut tooltip = info.label.to_string();
if let Some(doc) = info.docstring.as_deref() { if let Some(doc) = info.docstring.as_deref() {
tooltip.push_str("\n\n"); tooltip.push_str("\n\n");
@@ -203,9 +213,29 @@ impl LayoutHolder for SelectTool {
// Select mode (Deep/Shallow) // Select mode (Deep/Shallow)
widgets.push(self.deep_selection_widget()); widgets.push(self.deep_selection_widget());
// Pivot // Pivot gizmo type (checkbox + dropdown for pivot/origin)
widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder()); widgets.push(Separator::new(SeparatorType::Unrelated).widget_holder());
widgets.push(self.pivot_reference_point_widget(self.tool_data.selected_layers_count == 0)); widgets.extend(pivot_gizmo_type_widget(self.tool_data.pivot_gizmo.state, PivotToolSource::Select));
if self.tool_data.pivot_gizmo.state.is_pivot_type() {
// Nine-position reference point widget
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
widgets.push(pivot_reference_point_widget(
self.tool_data.selected_layers_count == 0 || !self.tool_data.pivot_gizmo.state.is_pivot(),
self.tool_data.pivot_gizmo.pivot.to_pivot_position(),
PivotToolSource::Select,
));
// Pivot pin button
widgets.push(Separator::new(SeparatorType::Related).widget_holder());
let pin_active = self.tool_data.pivot_gizmo.pin_active();
let pin_enabled = self.tool_data.pivot_gizmo.pivot.old_pivot_position == ReferencePoint::None && !self.tool_data.pivot_gizmo.state.disabled;
if pin_active || pin_enabled {
widgets.push(pin_pivot_widget(pin_active, pin_enabled, PivotToolSource::Select));
}
}
// Align // Align
let disabled = self.tool_data.selected_layers_count < 2; let disabled = self.tool_data.selected_layers_count < 2;
@@ -242,16 +272,46 @@ impl LayoutHolder for SelectTool {
} }
} }
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SelectTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SelectTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
if let ToolMessage::Select(SelectToolMessage::SelectOptions(SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior))) = message { let mut redraw_reference_pivot = false;
self.tool_data.nested_selection_behavior = nested_selection_behavior;
if let ToolMessage::Select(SelectToolMessage::SelectOptions(ref option_update)) = message {
match option_update {
SelectOptionsUpdate::NestedSelectionBehavior(nested_selection_behavior) => {
self.tool_data.nested_selection_behavior = *nested_selection_behavior;
responses.add(ToolMessage::UpdateHints); responses.add(ToolMessage::UpdateHints);
} }
SelectOptionsUpdate::PivotGizmoType(gizmo_type) => {
if !self.tool_data.pivot_gizmo.state.disabled {
self.tool_data.pivot_gizmo.state.gizmo_type = *gizmo_type;
responses.add(ToolMessage::UpdateHints);
let pivot_gizmo = self.tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
responses.add(NodeGraphMessage::RunDocumentGraph);
redraw_reference_pivot = true;
}
}
SelectOptionsUpdate::TogglePivotGizmoType(state) => {
self.tool_data.pivot_gizmo.state.disabled = !state;
responses.add(ToolMessage::UpdateHints);
responses.add(NodeGraphMessage::RunDocumentGraph);
redraw_reference_pivot = true;
}
SelectOptionsUpdate::TogglePivotPinned => {
self.tool_data.pivot_gizmo.pivot.pinned = !self.tool_data.pivot_gizmo.pivot.pinned;
responses.add(ToolMessage::UpdateHints);
responses.add(NodeGraphMessage::RunDocumentGraph);
redraw_reference_pivot = true;
}
}
}
self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, false); self.fsm_state.process_event(message, &mut self.tool_data, tool_data, &(), responses, false);
if self.tool_data.pivot.should_refresh_pivot_position() || self.tool_data.selected_layers_changed { if self.tool_data.pivot_gizmo.pivot.should_refresh_pivot_position() || self.tool_data.selected_layers_changed || redraw_reference_pivot {
// Send the layout containing the updated pivot position (a bit ugly to do it here not in the fsm but that doesn't have SelectTool) // Send the layout containing the updated pivot position (a bit ugly to do it here not in the fsm but that doesn't have SelectTool)
self.send_layout(responses, LayoutTarget::ToolOptions); self.send_layout(responses, LayoutTarget::ToolOptions);
self.tool_data.selected_layers_changed = false; self.tool_data.selected_layers_changed = false;
@@ -323,7 +383,8 @@ struct SelectToolData {
drag_current: ViewportPosition, drag_current: ViewportPosition,
lasso_polygon: Vec<ViewportPosition>, lasso_polygon: Vec<ViewportPosition>,
selection_mode: Option<SelectionMode>, selection_mode: Option<SelectionMode>,
layers_dragging: Vec<LayerNodeIdentifier>, layers_dragging: Vec<LayerNodeIdentifier>, // Unordered, often used as temporary buffer
ordered_layers: Vec<LayerNodeIdentifier>, // Ordered list of layers
layer_selected_on_start: Option<LayerNodeIdentifier>, layer_selected_on_start: Option<LayerNodeIdentifier>,
select_single_layer: Option<LayerNodeIdentifier>, select_single_layer: Option<LayerNodeIdentifier>,
axis_align: bool, axis_align: bool,
@@ -331,7 +392,9 @@ struct SelectToolData {
bounding_box_manager: Option<BoundingBoxManager>, bounding_box_manager: Option<BoundingBoxManager>,
snap_manager: SnapManager, snap_manager: SnapManager,
cursor: MouseCursorIcon, cursor: MouseCursorIcon,
pivot: Pivot, pivot_gizmo: PivotGizmo,
pivot_gizmo_start: Option<DVec2>,
pivot_gizmo_shift: Option<DVec2>,
compass_rose: CompassRose, compass_rose: CompassRose,
line_center: DVec2, line_center: DVec2,
skew_edge: EdgeBool, skew_edge: EdgeBool,
@@ -497,6 +560,24 @@ impl SelectToolData {
responses.add(NodeGraphMessage::SendGraph); responses.add(NodeGraphMessage::SendGraph);
self.layers_dragging = original; self.layers_dragging = original;
} }
fn state_from_pivot_gizmo(&self, mouse: DVec2) -> Option<SelectToolFsmState> {
match self.pivot_gizmo.state.gizmo_type {
PivotGizmoType::Pivot if self.pivot_gizmo.state.is_pivot() => self.pivot_gizmo.pivot.is_over(mouse).then_some(SelectToolFsmState::DraggingPivot),
_ => None,
}
}
fn pivot_gizmo(&self) -> PivotGizmo {
self.pivot_gizmo.clone()
}
fn sync_history(&mut self, document: &DocumentMessageHandler) {
let layers: Vec<_> = document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface).collect();
self.ordered_layers.retain(|layer| layers.contains(layer));
self.ordered_layers.extend(layers.iter().find(|&layer| !self.ordered_layers.contains(layer)));
self.pivot_gizmo.layer = self.ordered_layers.last().copied()
}
} }
impl Fsm for SelectToolFsmState { impl Fsm for SelectToolFsmState {
@@ -710,8 +791,63 @@ impl Fsm for SelectToolFsmState {
.flatten() .flatten()
}); });
// Update pivot let mut active_origin = None;
tool_data.pivot.update_pivot(document, &mut overlay_context, Some((angle,))); let mut origin_angle = 0.;
if overlay_context.visibility_settings.origin() && !tool_data.pivot_gizmo.state.is_pivot_type() {
let get_angle = |layer: LayerNodeIdentifier| -> f64 {
let quad = Quad::from_box([DVec2::ZERO, DVec2::ONE]);
let bounds = document.metadata().transform_to_viewport_with_first_transform_node_if_group(layer, &document.network_interface) * quad;
(bounds.top_left() - bounds.top_right()).to_angle()
};
if tool_data.pivot_gizmo.state.gizmo_type == PivotGizmoType::Average {
let mut count = 0_usize;
let sum: f64 = document
.network_interface
.selected_nodes()
.selected_visible_and_unlocked_layers(&document.network_interface)
.map(get_angle)
.inspect(|_| count += 1)
.sum();
if count > 0 {
origin_angle = sum / count as f64;
}
} else if tool_data.pivot_gizmo.state.gizmo_type == PivotGizmoType::Active {
origin_angle = document
.network_interface
.selected_nodes()
.selected_visible_and_unlocked_layers(&document.network_interface)
.find(|&layer| Some(layer) == tool_data.pivot_gizmo.layer)
.iter()
.map(|&layer| get_angle(layer))
.sum();
}
for layer in document.network_interface.selected_nodes().selected_visible_and_unlocked_layers(&document.network_interface) {
let origin = graph_modification_utils::get_viewport_origin(layer, &document.network_interface);
if Some(layer) == tool_data.pivot_gizmo.layer {
active_origin = Some(origin);
continue;
}
overlay_context.dowel_pin(origin, origin_angle, None);
}
}
if let Some(origin) = active_origin {
overlay_context.dowel_pin(origin, origin_angle, Some(COLOR_OVERLAY_YELLOW));
}
let has_layers = document.network_interface.selected_nodes().has_selected_nodes();
let draw_pivot = tool_data.pivot_gizmo.state.is_pivot() && overlay_context.visibility_settings.pivot() && has_layers;
tool_data.pivot_gizmo.pivot.recalculate_pivot(document);
let pivot = draw_pivot.then_some(tool_data.pivot_gizmo.pivot.pivot).flatten();
if let Some(pivot) = pivot {
let offset = tool_data
.pivot_gizmo_start
.map(|offset| tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - offset).unwrap_or_default())
.unwrap_or_default();
let shift = tool_data.pivot_gizmo_shift.unwrap_or_default();
overlay_context.pivot(pivot + offset + shift, angle);
}
// Update compass rose // Update compass rose
if overlay_context.visibility_settings.compass_rose() { if overlay_context.visibility_settings.compass_rose() {
@@ -837,6 +973,15 @@ impl Fsm for SelectToolFsmState {
(SelectionShapeType::Lasso, _) => overlay_context.polygon(polygon, None, fill_color), (SelectionShapeType::Lasso, _) => overlay_context.polygon(polygon, None, fill_color),
} }
} }
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 self
} }
(_, SelectToolMessage::EditLayer) => { (_, SelectToolMessage::EditLayer) => {
@@ -868,7 +1013,8 @@ impl Fsm for SelectToolFsmState {
let intersection_list = document.click_list(input).collect::<Vec<_>>(); let intersection_list = document.click_list(input).collect::<Vec<_>>();
let intersection = document.find_deepest(&intersection_list); let intersection = document.find_deepest(&intersection_list);
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging); let position = tool_data.pivot_gizmo().position(document);
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, Some(position));
// If the user is dragging the bounding box bounds, go into ResizingBounds mode. // If the user is dragging the bounding box bounds, go into ResizingBounds mode.
// If the user is dragging the rotate trigger, go into RotatingBounds mode. // If the user is dragging the rotate trigger, go into RotatingBounds mode.
@@ -883,20 +1029,17 @@ impl Fsm for SelectToolFsmState {
let angle = bounds.map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle()); let angle = bounds.map_or(0., |quad| (quad.top_left() - quad.top_right()).to_angle());
let mouse_position = input.mouse.position; let mouse_position = input.mouse.position;
let compass_rose_state = tool_data.compass_rose.compass_rose_state(mouse_position, angle); let compass_rose_state = tool_data.compass_rose.compass_rose_state(mouse_position, angle);
let is_over_pivot = tool_data.pivot.is_over(mouse_position);
let show_compass = bounds.is_some_and(|quad| quad.all_sides_at_least_width(COMPASS_ROSE_HOVER_RING_DIAMETER) && quad.contains(mouse_position)); let show_compass = bounds.is_some_and(|quad| quad.all_sides_at_least_width(COMPASS_ROSE_HOVER_RING_DIAMETER) && quad.contains(mouse_position));
let can_grab_compass_rose = compass_rose_state.can_grab() && (show_compass || bounds.is_none()); let can_grab_compass_rose = compass_rose_state.can_grab() && (show_compass || bounds.is_none());
let state = if is_over_pivot let state = if let Some(state) = tool_data.state_from_pivot_gizmo(input.mouse.position) {
// Dragging the pivot
{
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
// tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true); // tool_data.snap_manager.start_snap(document, input, document.bounding_boxes(), true, true);
// tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]); // tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
SelectToolFsmState::DraggingPivot state
} }
// Dragging one (or two, forming a corner) of the transform cage bounding box edges // Dragging one (or two, forming a corner) of the transform cage bounding box edges
else if resize { else if resize {
@@ -917,12 +1060,14 @@ impl Fsm for SelectToolFsmState {
} }
tool_data.layers_dragging = selected; tool_data.layers_dragging = selected;
tool_data.get_snap_candidates(document, input); tool_data.get_snap_candidates(document, input);
let (axis, using_compass) = { let (axis, using_compass) = {
let axis_state = compass_rose_state.axis_type().filter(|_| can_grab_compass_rose); let axis_state = compass_rose_state.axis_type().filter(|_| can_grab_compass_rose);
(axis_state.unwrap_or_default(), axis_state.is_some()) (axis_state.unwrap_or_default(), axis_state.is_some())
}; };
tool_data.pivot_gizmo_start = Some(tool_data.drag_current);
SelectToolFsmState::Dragging { SelectToolFsmState::Dragging {
axis, axis,
using_compass, using_compass,
@@ -941,6 +1086,12 @@ impl Fsm for SelectToolFsmState {
let extend = input.keyboard.key(extend_selection); let extend = input.keyboard.key(extend_selection);
if !extend && !input.keyboard.key(remove_from_selection) { if !extend && !input.keyboard.key(remove_from_selection) {
responses.add(DocumentMessage::DeselectAllLayers); responses.add(DocumentMessage::DeselectAllLayers);
if !tool_data.pivot_gizmo.pivot.pinned {
let position = tool_data.pivot_gizmo.pivot.last_non_none_reference_point;
responses.add(SelectToolMessage::SetPivot { position });
}
tool_data.layers_dragging.clear(); tool_data.layers_dragging.clear();
} }
@@ -955,6 +1106,9 @@ impl Fsm for SelectToolFsmState {
tool_data.get_snap_candidates(document, input); tool_data.get_snap_candidates(document, input);
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
tool_data.pivot_gizmo_start = Some(tool_data.drag_current);
SelectToolFsmState::Dragging { SelectToolFsmState::Dragging {
axis: Axis::None, axis: Axis::None,
using_compass: false, using_compass: false,
@@ -1098,7 +1252,10 @@ impl Fsm for SelectToolFsmState {
(SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove(modifier_keys)) => { (SelectToolFsmState::DraggingPivot, SelectToolMessage::PointerMove(modifier_keys)) => {
let mouse_position = input.mouse.position; let mouse_position = input.mouse.position;
let snapped_mouse_position = mouse_position; let snapped_mouse_position = mouse_position;
tool_data.pivot.set_viewport_position(snapped_mouse_position, document, responses);
tool_data.pivot_gizmo.pivot.set_viewport_position(snapped_mouse_position);
responses.add(NodeGraphMessage::RunDocumentGraph);
// Auto-panning // Auto-panning
let messages = [ let messages = [
@@ -1143,7 +1300,7 @@ impl Fsm for SelectToolFsmState {
.map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge))); .map_or(MouseCursorIcon::Default, |bounds| bounds.get_cursor(input, true, dragging_bounds, Some(tool_data.skew_edge)));
// Dragging the pivot overrules the other operations // Dragging the pivot overrules the other operations
if tool_data.pivot.is_over(input.mouse.position) { if tool_data.state_from_pivot_gizmo(input.mouse.position).is_some() {
cursor = MouseCursorIcon::Move; cursor = MouseCursorIcon::Move;
} }
@@ -1283,20 +1440,32 @@ impl Fsm for SelectToolFsmState {
tool_data.snap_manager.cleanup(responses); tool_data.snap_manager.cleanup(responses);
tool_data.select_single_layer = None; tool_data.select_single_layer = None;
if let Some(start) = tool_data.pivot_gizmo_start {
let offset = tool_data.pivot_gizmo.pivot_disconnected().then_some(tool_data.drag_current - start).unwrap_or_default();
if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() {
*v += offset;
}
}
tool_data.pivot_gizmo_start = None;
let pivot_gizmo = tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
let selection = tool_data.nested_selection_behavior; let selection = tool_data.nested_selection_behavior;
SelectToolFsmState::Ready { selection } SelectToolFsmState::Ready { selection }
} }
( (
SelectToolFsmState::ResizingBounds SelectToolFsmState::ResizingBounds | SelectToolFsmState::SkewingBounds { .. } | SelectToolFsmState::RotatingBounds | SelectToolFsmState::DraggingPivot,
| SelectToolFsmState::SkewingBounds { .. }
| SelectToolFsmState::RotatingBounds
| SelectToolFsmState::Dragging { .. }
| SelectToolFsmState::DraggingPivot,
SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter, SelectToolMessage::DragStop { .. } | SelectToolMessage::Enter,
) => { ) => {
let drag_too_small = input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON; let drag_too_small = input.mouse.position.distance(tool_data.drag_start) < 10. * f64::EPSILON;
let response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction }; let response = if drag_too_small { DocumentMessage::AbortTransaction } else { DocumentMessage::EndTransaction };
let pivot_gizmo = tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
responses.add(response); responses.add(response);
tool_data.axis_align = false; tool_data.axis_align = false;
tool_data.snap_manager.cleanup(responses); tool_data.snap_manager.cleanup(responses);
@@ -1432,8 +1601,48 @@ impl Fsm for SelectToolFsmState {
(_, SelectToolMessage::SetPivot { position }) => { (_, SelectToolMessage::SetPivot { position }) => {
responses.add(DocumentMessage::StartTransaction); responses.add(DocumentMessage::StartTransaction);
tool_data.pivot_gizmo.pivot.last_non_none_reference_point = position;
tool_data.pivot_gizmo.pivot.pinned = false;
let pos: Option<DVec2> = position.into(); let pos: Option<DVec2> = position.into();
tool_data.pivot.set_normalized_position(pos.unwrap(), document, responses);
tool_data.pivot_gizmo.pivot.set_normalized_position(pos.unwrap());
let pivot_gizmo = tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
responses.add(NodeGraphMessage::RunDocumentGraph);
self
}
(_, SelectToolMessage::SyncHistory) => {
tool_data.sync_history(document);
self
}
(_, SelectToolMessage::ShiftSelectedNodes { offset }) => {
let offset = document.metadata().document_to_viewport.transform_vector2(offset);
if tool_data.pivot_gizmo.pivot_disconnected() {
if let Some(v) = tool_data.pivot_gizmo.pivot.pivot.as_mut() {
*v += offset;
}
let pivot_gizmo = tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
}
self
}
(_, SelectToolMessage::PivotShift { offset, flush }) => {
if flush {
tool_data.pivot_gizmo.pivot.pivot.as_mut().map(|v| *v += tool_data.pivot_gizmo_shift.take().unwrap_or_default());
let pivot_gizmo = tool_data.pivot_gizmo();
responses.add(TransformLayerMessage::SetPivotGizmo { pivot_gizmo });
return self;
}
if tool_data.pivot_gizmo.pivot_disconnected() {
tool_data.pivot_gizmo_shift = offset;
}
self self
} }
@@ -1658,6 +1867,7 @@ fn drag_deepest_manipulation(responses: &mut VecDeque<Message>, selected: Vec<La
.next() .next()
.expect("ROOT_PARENT should have a layer child when clicking"), .expect("ROOT_PARENT should have a layer child when clicking"),
); );
if !remove { if !remove {
tool_data.layers_dragging.extend(vec![layer]); tool_data.layers_dragging.extend(vec![layer]);
} else { } else {
@@ -557,7 +557,7 @@ impl Fsm for ShapeToolFsmState {
} }
} }
let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging); let (resize, rotate, skew) = transforming_transform_cage(document, &mut tool_data.bounding_box_manager, input, responses, &mut tool_data.layers_dragging, None);
if !input.keyboard.key(Key::Control) { if !input.keyboard.key(Key::Control) {
match (resize, rotate, skew) { match (resize, rotate, skew) {
@@ -14,7 +14,7 @@ use graph_craft::document::{NodeId, NodeInput};
use graphene_std::Color; use graphene_std::Color;
use graphene_std::vector::{PointId, SegmentId, VectorModificationType}; use graphene_std::vector::{PointId, SegmentId, VectorModificationType};
#[derive(Default)] #[derive(Default, ExtractField)]
pub struct SplineTool { pub struct SplineTool {
fsm_state: SplineToolFsmState, fsm_state: SplineToolFsmState,
tool_data: SplineToolData, tool_data: SplineToolData,
@@ -123,6 +123,7 @@ impl LayoutHolder for SplineTool {
} }
} }
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SplineTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for SplineTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message else { let ToolMessage::Spline(SplineToolMessage::UpdateOptions(action)) = message else {
@@ -9,7 +9,6 @@ use crate::messages::portfolio::document::utility_types::network_interface::Inpu
use crate::messages::tool::common_functionality::auto_panning::AutoPanning; use crate::messages::tool::common_functionality::auto_panning::AutoPanning;
use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType}; use crate::messages::tool::common_functionality::color_selector::{ToolColorOptions, ToolColorType};
use crate::messages::tool::common_functionality::graph_modification_utils::{self, is_layer_fed_by_node_of_name}; use crate::messages::tool::common_functionality::graph_modification_utils::{self, is_layer_fed_by_node_of_name};
use crate::messages::tool::common_functionality::pivot::Pivot;
use crate::messages::tool::common_functionality::resize::Resize; use crate::messages::tool::common_functionality::resize::Resize;
use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData}; use crate::messages::tool::common_functionality::snapping::{self, SnapCandidatePoint, SnapData};
use crate::messages::tool::common_functionality::transformation_cage::*; use crate::messages::tool::common_functionality::transformation_cage::*;
@@ -21,7 +20,7 @@ use graphene_std::renderer::Quad;
use graphene_std::text::{Font, FontCache, TypesettingConfig, lines_clipping, load_font}; use graphene_std::text::{Font, FontCache, TypesettingConfig, lines_clipping, load_font};
use graphene_std::vector::style::Fill; use graphene_std::vector::style::Fill;
#[derive(Default)] #[derive(Default, ExtractField)]
pub struct TextTool { pub struct TextTool {
fsm_state: TextToolFsmState, fsm_state: TextToolFsmState,
tool_data: TextToolData, tool_data: TextToolData,
@@ -171,6 +170,7 @@ impl LayoutHolder for TextTool {
} }
} }
#[message_handler_data]
impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextTool { impl<'a> MessageHandler<ToolMessage, &mut ToolActionHandlerData<'a>> for TextTool {
fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) { fn process_message(&mut self, message: ToolMessage, responses: &mut VecDeque<Message>, tool_data: &mut ToolActionHandlerData<'a>) {
let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message else { let ToolMessage::Text(TextToolMessage::UpdateOptions(action)) = message else {
@@ -283,7 +283,6 @@ struct TextToolData {
// Since the overlays must be drawn without knowledge of the inputs // Since the overlays must be drawn without knowledge of the inputs
cached_resize_bounds: [DVec2; 2], cached_resize_bounds: [DVec2; 2],
bounding_box_manager: Option<BoundingBoxManager>, bounding_box_manager: Option<BoundingBoxManager>,
pivot: Pivot,
snap_candidates: Vec<SnapCandidatePoint>, snap_candidates: Vec<SnapCandidatePoint>,
// TODO: Handle multiple layers in the future // TODO: Handle multiple layers in the future
layer_dragging: Option<ResizingLayer>, layer_dragging: Option<ResizingLayer>,
@@ -526,7 +525,6 @@ impl Fsm for TextToolFsmState {
} }
bounding_box_manager.render_overlays(&mut overlay_context, false); bounding_box_manager.render_overlays(&mut overlay_context, false);
tool_data.pivot.update_pivot(document, &mut overlay_context, None);
} }
} else { } else {
tool_data.bounding_box_manager.take(); tool_data.bounding_box_manager.take();
@@ -2,6 +2,7 @@ use crate::messages::input_mapper::utility_types::input_keyboard::Key;
use crate::messages::portfolio::document::overlays::utility_types::OverlayContext; use crate::messages::portfolio::document::overlays::utility_types::OverlayContext;
use crate::messages::portfolio::document::utility_types::transformation::TransformType; use crate::messages::portfolio::document::utility_types::transformation::TransformType;
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::pivot::PivotGizmo;
use glam::DVec2; use glam::DVec2;
#[impl_message(Message, ToolMessage, TransformLayer)] #[impl_message(Message, ToolMessage, TransformLayer)]
@@ -29,4 +30,5 @@ pub enum TransformLayerMessage {
TypeDecimalPoint, TypeDecimalPoint,
TypeDigit { digit: u8 }, TypeDigit { digit: u8 },
TypeNegate, TypeNegate,
SetPivotGizmo { pivot_gizmo: PivotGizmo },
} }
@@ -5,6 +5,7 @@ use crate::messages::portfolio::document::utility_types::document_metadata::Laye
use crate::messages::portfolio::document::utility_types::misc::PTZ; use crate::messages::portfolio::document::utility_types::misc::PTZ;
use crate::messages::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, TransformType, Typing}; use crate::messages::portfolio::document::utility_types::transformation::{Axis, OriginalTransforms, Selected, TransformOperation, TransformType, Typing};
use crate::messages::prelude::*; use crate::messages::prelude::*;
use crate::messages::tool::common_functionality::pivot::{PivotGizmo, PivotGizmoType};
use crate::messages::tool::common_functionality::shape_editor::ShapeState; use crate::messages::tool::common_functionality::shape_editor::ShapeState;
use crate::messages::tool::tool_messages::tool_prelude::Key; use crate::messages::tool::tool_messages::tool_prelude::Key;
use crate::messages::tool::utility_types::{ToolData, ToolType}; use crate::messages::tool::utility_types::{ToolData, ToolType};
@@ -20,7 +21,7 @@ const TRANSFORM_GRS_OVERLAY_PROVIDER: OverlayProvider = |context| TransformLayer
const SLOW_KEY: Key = Key::Shift; const SLOW_KEY: Key = Key::Shift;
const INCREMENTS_KEY: Key = Key::Control; const INCREMENTS_KEY: Key = Key::Control;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default, ExtractField)]
pub struct TransformLayerMessageHandler { pub struct TransformLayerMessageHandler {
pub transform_operation: TransformOperation, pub transform_operation: TransformOperation,
@@ -34,8 +35,11 @@ pub struct TransformLayerMessageHandler {
start_mouse: ViewportPosition, start_mouse: ViewportPosition,
original_transforms: OriginalTransforms, original_transforms: OriginalTransforms,
pivot_gizmo: PivotGizmo,
pivot: ViewportPosition, pivot: ViewportPosition,
path_bounds: Option<[DVec2; 2]>,
local_pivot: DocumentPosition, local_pivot: DocumentPosition,
local_mouse_start: DocumentPosition, local_mouse_start: DocumentPosition,
grab_target: DocumentPosition, grab_target: DocumentPosition,
@@ -61,27 +65,64 @@ impl TransformLayerMessageHandler {
} }
} }
fn calculate_pivot(selected_points: &Vec<&ManipulatorPointId>, vector_data: &VectorData, viewspace: DAffine2, get_location: impl Fn(&ManipulatorPointId) -> Option<DVec2>) -> Option<(DVec2, DVec2)> { fn calculate_pivot(
document: &DocumentMessageHandler,
selected_points: &Vec<&ManipulatorPointId>,
vector_data: &VectorData,
viewspace: DAffine2,
get_location: impl Fn(&ManipulatorPointId) -> Option<DVec2>,
gizmo: &mut PivotGizmo,
) -> (Option<(DVec2, DVec2)>, Option<[DVec2; 2]>) {
let average_position = || {
let mut point_count = 0_usize;
selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64
};
let bounds = selected_points.iter().filter_map(|p| get_location(p)).fold(None, |acc: Option<[DVec2; 2]>, point| {
if let Some([mut min, mut max]) = acc {
min.x = min.x.min(point.x);
min.y = min.y.min(point.y);
max.x = max.x.max(point.x);
max.y = max.y.max(point.y);
Some([min, max])
} else {
Some([point, point])
}
});
gizmo.pivot.recalculate_pivot_for_layer(document, bounds);
let position = || {
(if !gizmo.state.disabled {
match gizmo.state.gizmo_type {
PivotGizmoType::Average => None,
PivotGizmoType::Active => gizmo.point.and_then(|p| get_location(&p)),
PivotGizmoType::Pivot => gizmo.pivot.pivot,
}
} else {
None
})
.unwrap_or_else(average_position)
};
let [point] = selected_points.as_slice() else { let [point] = selected_points.as_slice() else {
// Handle the case where there are multiple points // Handle the case where there are multiple points
let mut point_count = 0; let position = position();
let average_position = selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64; return (Some((position, position)), bounds);
return Some((average_position, average_position));
}; };
match point { match point {
ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => { ManipulatorPointId::PrimaryHandle(_) | ManipulatorPointId::EndHandle(_) => {
// Get the anchor position and transform it to the pivot // Get the anchor position and transform it to the pivot
let pivot_pos = point.get_anchor_position(vector_data).map(|anchor_position| viewspace.transform_point2(anchor_position))?; let (Some(pivot_position), Some(position)) = (
let target = viewspace.transform_point2(point.get_position(vector_data)?); point.get_anchor_position(vector_data).map(|anchor_position| viewspace.transform_point2(anchor_position)),
Some((pivot_pos, target)) point.get_position(vector_data),
) else {
return (None, None);
};
let target = viewspace.transform_point2(position);
(Some((pivot_position, target)), None)
} }
_ => { _ => {
// Calculate the average position of all selected points // Calculate the average position of all selected points
let mut point_count = 0; let position = position();
let average_position = selected_points.iter().filter_map(|p| get_location(p)).inspect(|_| point_count += 1).sum::<DVec2>() / point_count as f64; (Some((position, position)), bounds)
Some((average_position, average_position))
} }
} }
} }
@@ -134,6 +175,26 @@ fn update_colinear_handles(selected_layers: &[LayerNodeIdentifier], document: &D
} }
type TransformData<'a> = (&'a DocumentMessageHandler, &'a InputPreprocessorMessageHandler, &'a ToolData, &'a mut ShapeState); 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 <https://github.com/dtolnay/proc-macro2/issues/503> is resolved and released,
// TODO: use <https://doc.rust-lang.org/stable/proc_macro/struct.Span.html#method.line> to get
// TODO: the line number instead of hardcoding it to the magic number on the following 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<TransformLayerMessage, TransformData<'_>> for TransformLayerMessageHandler { impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayerMessageHandler {
fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, (document, input, tool_data, shape_editor): TransformData) { fn process_message(&mut self, message: TransformLayerMessage, responses: &mut VecDeque<Message>, (document, input, tool_data, shape_editor): TransformData) {
let using_path_tool = tool_data.active_tool_type == ToolType::Path; let using_path_tool = tool_data.active_tool_type == ToolType::Path;
@@ -177,18 +238,17 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
} }
if !using_path_tool { if !using_path_tool {
*selected.pivot = selected.mean_average_of_pivots(); self.pivot_gizmo.recalculate_transform(document);
*selected.pivot = self.pivot_gizmo.position(document);
self.local_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot); self.local_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot);
self.grab_target = document.metadata().document_to_viewport.inverse().transform_point2(selected.mean_average_of_pivots()); self.grab_target = self.local_pivot;
} }
// Here vector data from all layers is not considered which can be a problem in pivot calculation // Here vector data from all layers is not considered which can be a problem in pivot calculation
else if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) { else if let Some(vector_data) = selected_layers.first().and_then(|&layer| document.network_interface.compute_modified_vector(layer)) {
*selected.original_transforms = OriginalTransforms::default(); *selected.original_transforms = OriginalTransforms::default();
let viewspace = document.metadata().transform_to_viewport(selected_layers[0]); let viewspace = document.metadata().transform_to_viewport(selected_layers[0]);
let selected_segments = shape_editor.selected_segments().collect::<HashSet<_>>(); let selected_segments = shape_editor.selected_segments().collect::<HashSet<_>>();
let mut affected_points = shape_editor.selected_points().copied().collect::<Vec<_>>(); let mut affected_points = shape_editor.selected_points().copied().collect::<Vec<_>>();
for (segment_id, _, start, end) in vector_data.segment_bezier_iter() { for (segment_id, _, start, end) in vector_data.segment_bezier_iter() {
@@ -201,8 +261,16 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
let affected_point_refs = affected_points.iter().collect(); let affected_point_refs = affected_points.iter().collect();
let get_location = |point: &&ManipulatorPointId| point.get_position(&vector_data).map(|position| viewspace.transform_point2(position)); let get_location = |point: &&ManipulatorPointId| point.get_position(&vector_data).map(|position| viewspace.transform_point2(position));
if let Some((new_pivot, grab_target)) = calculate_pivot(&affected_point_refs, &vector_data, viewspace, |point: &ManipulatorPointId| get_location(&point)) { if let (Some((new_pivot, grab_target)), bounds) = calculate_pivot(
document,
&affected_point_refs,
&vector_data,
viewspace,
|point: &ManipulatorPointId| get_location(&point),
&mut self.pivot_gizmo,
) {
*selected.pivot = new_pivot; *selected.pivot = new_pivot;
self.path_bounds = bounds;
self.local_pivot = document_to_viewport.inverse().transform_point2(*selected.pivot); self.local_pivot = document_to_viewport.inverse().transform_point2(*selected.pivot);
self.grab_target = document_to_viewport.inverse().transform_point2(grab_target); self.grab_target = document_to_viewport.inverse().transform_point2(grab_target);
@@ -228,11 +296,6 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
return; return;
} }
for layer in document.metadata().all_layers() {
if !document.network_interface.is_artboard(&layer.to_node(), &[]) {
continue;
};
let viewport_box = input.viewport_bounds.size(); let viewport_box = input.viewport_bounds.size();
let axis_constraint = self.transform_operation.axis_constraint(); let axis_constraint = self.transform_operation.axis_constraint();
@@ -256,32 +319,15 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
let translation = translation.to_dvec(self.initial_transform, self.increments); let translation = translation.to_dvec(self.initial_transform, self.increments);
let viewport_translate = document_to_viewport.transform_vector2(translation); let viewport_translate = document_to_viewport.transform_vector2(translation);
let pivot = document_to_viewport.transform_point2(self.grab_target); let pivot = document_to_viewport.transform_point2(self.grab_target);
let quad = Quad::from_box([pivot, pivot + viewport_translate]).0; let quad = Quad::from_box([pivot, pivot + viewport_translate]);
let e1 = (self.layer_bounding_box.0[1] - self.layer_bounding_box.0[0]).normalize_or(DVec2::X);
if matches!(axis_constraint, Axis::Both | Axis::X) && translation.x != 0. { responses.add(SelectToolMessage::PivotShift {
let end = if self.local { (quad[1] - quad[0]).rotate(e1) + quad[0] } else { quad[1] }; offset: Some(viewport_translate),
overlay_context.dashed_line(quad[0], end, None, None, Some(2.), Some(2.), Some(0.5)); flush: false,
});
let x_transform = DAffine2::from_translation((quad[0] + end) / 2.); let typed_string = (!self.typing.digits.is_empty() && self.transform_operation.can_begin_typing()).then(|| self.typing.string.clone());
overlay_context.text(&format_rounded(translation.x, 3), COLOR_OVERLAY_BLUE, None, x_transform, 4., [Pivot::Middle, Pivot::End]); overlay_context.translation_box(translation, quad, typed_string);
}
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);
}
} }
TransformOperation::Scaling(scale) => { TransformOperation::Scaling(scale) => {
let scale = scale.to_f64(self.increments); let scale = scale.to_f64(self.increments);
@@ -340,7 +386,6 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
} }
} }
} }
}
// Messages // Messages
TransformLayerMessage::ApplyTransformOperation { final_transform } => { TransformLayerMessage::ApplyTransformOperation { final_transform } => {
@@ -364,6 +409,8 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
responses.add(NodeGraphMessage::RunDocumentGraph); responses.add(NodeGraphMessage::RunDocumentGraph);
} }
responses.add(SelectToolMessage::PivotShift { offset: None, flush: true });
if final_transform { if final_transform {
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER)); responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
} }
@@ -487,6 +534,7 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
responses.add(ToolMessage::UpdateHints); responses.add(ToolMessage::UpdateHints);
} }
responses.add(SelectToolMessage::PivotShift { offset: None, flush: false });
responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER)); responses.add(OverlaysMessage::RemoveProvider(TRANSFORM_GRS_OVERLAY_PROVIDER));
} }
TransformLayerMessage::ConstrainX => { TransformLayerMessage::ConstrainX => {
@@ -694,6 +742,9 @@ impl MessageHandler<TransformLayerMessage, TransformData<'_>> for TransformLayer
self.initial_transform, self.initial_transform,
) )
} }
TransformLayerMessage::SetPivotGizmo { pivot_gizmo } => {
self.pivot_gizmo = pivot_gizmo;
}
} }
} }
@@ -18,6 +18,7 @@ use graphene_std::text::FontCache;
use std::borrow::Cow; use std::borrow::Cow;
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
#[derive(ExtractField)]
pub struct ToolActionHandlerData<'a> { pub struct ToolActionHandlerData<'a> {
pub document: &'a mut DocumentMessageHandler, pub document: &'a mut DocumentMessageHandler,
pub document_id: DocumentId, pub document_id: DocumentId,
@@ -1,10 +1,11 @@
use crate::messages::prelude::*; use crate::messages::prelude::*;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default, ExtractField)]
pub struct WorkspaceMessageHandler { pub struct WorkspaceMessageHandler {
node_graph_visible: bool, node_graph_visible: bool,
} }
#[message_handler_data]
impl MessageHandler<WorkspaceMessage, ()> for WorkspaceMessageHandler { impl MessageHandler<WorkspaceMessage, ()> for WorkspaceMessageHandler {
fn process_message(&mut self, message: WorkspaceMessage, _responses: &mut VecDeque<Message>, _data: ()) { fn process_message(&mut self, message: WorkspaceMessage, _responses: &mut VecDeque<Message>, _data: ()) {
match message { match message {
+5 -4
View File
@@ -413,6 +413,7 @@ mod test {
use super::*; use super::*;
use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface; use crate::messages::portfolio::document::utility_types::network_interface::NodeNetworkInterface;
use crate::test_utils::test_prelude::{self, NodeGraphLayer}; use crate::test_utils::test_prelude::{self, NodeGraphLayer};
use graph_craft::ProtoNodeIdentifier;
use graph_craft::document::NodeNetwork; use graph_craft::document::NodeNetwork;
use graphene_std::Context; use graphene_std::Context;
use graphene_std::NodeInputDecleration; use graphene_std::NodeInputDecleration;
@@ -422,7 +423,7 @@ mod test {
/// Stores all of the monitor nodes that have been attached to a graph /// Stores all of the monitor nodes that have been attached to a graph
#[derive(Default)] #[derive(Default)]
pub struct Instrumented { pub struct Instrumented {
protonodes_by_name: HashMap<String, Vec<Vec<Vec<NodeId>>>>, protonodes_by_name: HashMap<ProtoNodeIdentifier, Vec<Vec<Vec<NodeId>>>>,
protonodes_by_path: HashMap<Vec<NodeId>, Vec<Vec<NodeId>>>, protonodes_by_path: HashMap<Vec<NodeId>, Vec<Vec<NodeId>>>,
} }
@@ -449,7 +450,7 @@ mod test {
} }
if let DocumentNodeImplementation::ProtoNode(identifier) = &mut node.implementation { if let DocumentNodeImplementation::ProtoNode(identifier) = &mut node.implementation {
path.push(*id); path.push(*id);
self.protonodes_by_name.entry(identifier.name.to_string()).or_default().push(monitor_node_ids.clone()); self.protonodes_by_name.entry(identifier.clone()).or_default().push(monitor_node_ids.clone());
self.protonodes_by_path.insert(path.clone(), monitor_node_ids); self.protonodes_by_path.insert(path.clone(), monitor_node_ids);
path.pop(); path.pop();
} }
@@ -457,7 +458,7 @@ mod test {
for (input, monitor_id) in monitor_nodes { for (input, monitor_id) in monitor_nodes {
let monitor_node = DocumentNode { let monitor_node = DocumentNode {
inputs: vec![input], inputs: vec![input],
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),
manual_composition: Some(graph_craft::generic!(T)), manual_composition: Some(graph_craft::generic!(T)),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
@@ -495,7 +496,7 @@ mod test {
Input::Result: Send + Sync + Clone + 'static, Input::Result: Send + Sync + Clone + 'static,
{ {
self.protonodes_by_name self.protonodes_by_name
.get(Input::identifier()) .get(&Input::identifier())
.map_or([].as_slice(), |x| x.as_slice()) .map_or([].as_slice(), |x| x.as_slice())
.iter() .iter()
.filter_map(|inputs| inputs.get(Input::INDEX)) .filter_map(|inputs| inputs.get(Input::INDEX))
+3 -3
View File
@@ -1,12 +1,12 @@
use super::*; use super::*;
use crate::messages::frontend::utility_types::{ExportBounds, FileType}; use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use glam::{DAffine2, DVec2}; use glam::{DAffine2, DVec2};
use graph_craft::concrete;
use graph_craft::document::value::TaggedValue; use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeNetwork}; use graph_craft::document::{NodeId, NodeNetwork};
use graph_craft::graphene_compiler::Compiler; use graph_craft::graphene_compiler::Compiler;
use graph_craft::proto::GraphErrors; use graph_craft::proto::GraphErrors;
use graph_craft::wasm_application_io::EditorPreferences; use graph_craft::wasm_application_io::EditorPreferences;
use graph_craft::{ProtoNodeIdentifier, concrete};
use graphene_std::Context; use graphene_std::Context;
use graphene_std::application_io::{NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig}; use graphene_std::application_io::{NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
use graphene_std::instances::Instance; use graphene_std::instances::Instance;
@@ -46,7 +46,7 @@ pub struct NodeRuntime {
inspect_state: Option<InspectState>, inspect_state: Option<InspectState>,
/// Mapping of the fully-qualified node paths to their preprocessor substitutions. /// Mapping of the fully-qualified node paths to their preprocessor substitutions.
substitutions: HashMap<String, DocumentNode>, substitutions: HashMap<ProtoNodeIdentifier, DocumentNode>,
// TODO: Remove, it doesn't need to be persisted anymore // TODO: Remove, it doesn't need to be persisted anymore
/// The current renders of the thumbnails for layer nodes. /// The current renders of the thumbnails for layer nodes.
@@ -435,7 +435,7 @@ impl InspectState {
let monitor_node = DocumentNode { let monitor_node = DocumentNode {
inputs: vec![NodeInput::node(inspect_node, 0)], // Connect to the primary output of the inspect node inputs: vec![NodeInput::node(inspect_node, 0)], // Connect to the primary output of the inspect node
implementation: DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode"), implementation: DocumentNodeImplementation::ProtoNode(graphene_std::memo::monitor::IDENTIFIER),
manual_composition: Some(graph_craft::generic!(T)), manual_composition: Some(graph_craft::generic!(T)),
skip_deduplication: true, skip_deduplication: true,
..Default::default() ..Default::default()
+16
View File
@@ -45,3 +45,19 @@ pub trait TransitiveChild: Into<Self::Parent> + Into<Self::TopParent> {
pub trait Hint { pub trait Hint {
fn hints(&self) -> HashMap<String, String>; fn hints(&self) -> HashMap<String, String>;
} }
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 {
""
}
}
+99
View File
@@ -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<Vec<DebugMessageTree>>,
message_handler: Option<MessageData>,
message_handler_data: Option<MessageData>,
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<DebugMessageTree>> {
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,
}
}
}
-4
View File
@@ -3,10 +3,6 @@
use axum::routing::get; use axum::routing::get;
use axum::Router; use axum::Router;
use fern::colors::{Color, ColoredLevelConfig}; use fern::colors::{Color, ColoredLevelConfig};
use graphite_editor::application::Editor;
use graphite_editor::messages::prelude::*;
use graphite_editor::node_graph_executor::GraphRuntimeRequest;
use graphite_editor::node_graph_executor::NODE_RUNTIME;
use graphite_editor::node_graph_executor::*; use graphite_editor::node_graph_executor::*;
use std::sync::Mutex; use std::sync::Mutex;
@@ -7,13 +7,14 @@
export let value: string; export let value: string;
export let disabled = false; export let disabled = false;
export let tooltip: string | undefined = undefined;
function setValue(newValue: ReferencePoint) { function setValue(newValue: ReferencePoint) {
dispatch("value", newValue); dispatch("value", newValue);
} }
</script> </script>
<div class="reference-point-input" class:disabled> <div class="reference-point-input" class:disabled title={tooltip}>
<button on:click={() => setValue("TopLeft")} class="row-1 col-1" class:active={value === "TopLeft"} tabindex="-1" {disabled}><div /></button> <button on:click={() => setValue("TopLeft")} class="row-1 col-1" class:active={value === "TopLeft"} tabindex="-1" {disabled}><div /></button>
<button on:click={() => setValue("TopCenter")} class="row-1 col-2" class:active={value === "TopCenter"} tabindex="-1" {disabled}><div /></button> <button on:click={() => setValue("TopCenter")} class="row-1 col-2" class:active={value === "TopCenter"} tabindex="-1" {disabled}><div /></button>
<button on:click={() => setValue("TopRight")} class="row-1 col-3" class:active={value === "TopRight"} tabindex="-1" {disabled}><div /></button> <button on:click={() => setValue("TopRight")} class="row-1 col-3" class:active={value === "TopRight"} tabindex="-1" {disabled}><div /></button>
+3
View File
@@ -1340,6 +1340,9 @@ export class ReferencePointInput extends WidgetProps {
value!: ReferencePoint; value!: ReferencePoint;
disabled!: boolean; disabled!: boolean;
@Transform(({ value }: { value: string }) => value || undefined)
tooltip!: string | undefined;
} }
// WIDGET // WIDGET
+1 -1
View File
@@ -403,7 +403,7 @@ mod test {
blend_mode: BlendMode::Normal, blend_mode: BlendMode::Normal,
}, },
}], }],
BrushCache::new_proto(), BrushCache::default(),
) )
.await; .await;
assert_eq!(image.instance_ref_iter().next().unwrap().instance.width, 20); assert_eq!(image.instance_ref_iter().next().unwrap().instance.width, 20);
+41 -43
View File
@@ -6,11 +6,16 @@ use graphene_core::raster_types::CPU;
use graphene_core::raster_types::Raster; use graphene_core::raster_types::Raster;
use std::collections::HashMap; use std::collections::HashMap;
use std::hash::Hash; use std::hash::Hash;
use std::sync::Arc; use std::hash::Hasher;
use std::sync::Mutex; 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 { struct BrushCacheImpl {
unique_id: u64,
// The full previous input that was cached. // The full previous input that was cached.
prev_input: Vec<BrushStroke>, prev_input: Vec<BrushStroke>,
@@ -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 { impl Hash for BrushCacheImpl {
// Zero hash. fn hash<H: Hasher>(&self, state: &mut H) {
fn hash<H: std::hash::Hasher>(&self, _state: &mut H) {} self.unique_id.hash(state);
}
} }
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
@@ -103,46 +128,26 @@ pub struct BrushPlan {
pub first_stroke_point_skip: usize, pub first_stroke_point_skip: usize,
} }
#[derive(Debug, DynAny, serde::Serialize, serde::Deserialize)] #[derive(Debug, Default, DynAny, serde::Serialize, serde::Deserialize)]
pub struct BrushCache { pub struct BrushCache(Arc<Mutex<BrushCacheImpl>>);
inner: Arc<Mutex<BrushCacheImpl>>,
proto: bool,
}
impl Default for BrushCache {
fn default() -> Self {
Self::new_proto()
}
}
// A bit of a cursed implementation to work around the current node system. // 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 // 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. // new object. Any further clones however are all the same underlying cache object.
impl Clone for BrushCache { impl Clone for BrushCache {
fn clone(&self) -> Self { fn clone(&self) -> Self {
if self.proto { Self(Arc::new(Mutex::new(self.0.lock().unwrap().clone())))
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,
}
}
} }
} }
impl PartialEq for BrushCache { impl PartialEq for BrushCache {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
if Arc::ptr_eq(&self.inner, &other.inner) { if Arc::ptr_eq(&self.0, &other.0) {
return true; return true;
} }
let s = self.inner.lock().unwrap(); let s = self.0.lock().unwrap();
let o = other.inner.lock().unwrap(); let o = other.0.lock().unwrap();
*s == *o *s == *o
} }
@@ -150,35 +155,28 @@ impl PartialEq for BrushCache {
impl Hash for BrushCache { impl Hash for BrushCache {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.inner.lock().unwrap().hash(state); self.0.lock().unwrap().hash(state);
} }
} }
impl BrushCache { impl BrushCache {
pub fn new_proto() -> Self {
Self {
inner: Default::default(),
proto: true,
}
}
pub fn compute_brush_plan(&self, background: Instance<Raster<CPU>>, input: &[BrushStroke]) -> BrushPlan { pub fn compute_brush_plan(&self, background: Instance<Raster<CPU>>, input: &[BrushStroke]) -> BrushPlan {
let mut inner = self.inner.lock().unwrap(); let mut inner = self.0.lock().unwrap();
inner.compute_brush_plan(background, input) inner.compute_brush_plan(background, input)
} }
pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: Instance<Raster<CPU>>, last_stroke_texture: Instance<Raster<CPU>>) { pub fn cache_results(&self, input: Vec<BrushStroke>, blended_image: Instance<Raster<CPU>>, last_stroke_texture: Instance<Raster<CPU>>) {
let mut inner = self.inner.lock().unwrap(); let mut inner = self.0.lock().unwrap();
inner.cache_results(input, blended_image, last_stroke_texture) inner.cache_results(input, blended_image, last_stroke_texture)
} }
pub fn get_cached_brush(&self, style: &BrushStyle) -> Option<Raster<CPU>> { pub fn get_cached_brush(&self, style: &BrushStyle) -> Option<Raster<CPU>> {
let inner = self.inner.lock().unwrap(); let inner = self.0.lock().unwrap();
inner.brush_texture_cache.get(style).cloned() inner.brush_texture_cache.get(style).cloned()
} }
pub fn store_brush(&self, style: BrushStyle, brush: Raster<CPU>) { pub fn store_brush(&self, style: BrushStyle, brush: Raster<CPU>) {
let mut inner = self.inner.lock().unwrap(); let mut inner = self.0.lock().unwrap();
inner.brush_texture_cache.insert(style, brush); inner.brush_texture_cache.insert(style, brush);
} }
} }
+1 -1
View File
@@ -356,7 +356,7 @@ pub struct ContextImpl<'a> {
} }
impl<'a> ContextImpl<'a> { impl<'a> ContextImpl<'a> {
pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl (Borrow<[DynRef<'f>]>)>) -> ContextImpl<'f> pub fn with_footprint<'f>(&self, new_footprint: &'f Footprint, varargs: Option<&'f impl Borrow<[DynRef<'f>]>>) -> ContextImpl<'f>
where where
'a: 'f, 'a: 'f,
{ {
+3 -3
View File
@@ -12,7 +12,7 @@ pub mod debug;
pub mod extract_xy; pub mod extract_xy;
pub mod generic; pub mod generic;
pub mod gradient; pub mod gradient;
mod graphic_element; pub mod graphic_element;
pub mod instances; pub mod instances;
pub mod logic; pub mod logic;
pub mod math; pub mod math;
@@ -35,7 +35,7 @@ pub use blending::*;
pub use context::*; pub use context::*;
pub use ctor; pub use ctor;
pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync}; pub use dyn_any::{StaticTypeSized, WasmNotSend, WasmNotSync};
pub use graphic_element::*; pub use graphic_element::{Artboard, ArtboardGroupTable, GraphicElement, GraphicGroupTable};
pub use memo::MemoHash; pub use memo::MemoHash;
pub use num_traits; pub use num_traits;
pub use raster::Color; pub use raster::Color;
@@ -161,7 +161,7 @@ where
pub trait NodeInputDecleration { pub trait NodeInputDecleration {
const INDEX: usize; const INDEX: usize;
fn identifier() -> &'static str; fn identifier() -> ProtoNodeIdentifier;
type Result; type Result;
} }
+13 -1
View File
@@ -2,6 +2,7 @@ use crate::{Node, WasmNotSend};
use dyn_any::DynFuture; use dyn_any::DynFuture;
use std::future::Future; use std::future::Future;
use std::hash::DefaultHasher; use std::hash::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::ops::Deref; use std::ops::Deref;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
@@ -49,6 +50,10 @@ impl<T, CachedNode> MemoNode<T, CachedNode> {
} }
} }
pub mod memo {
pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::MemoNode");
}
/// Caches the output of a given Node and acts as a proxy. /// Caches the output of a given Node and acts as a proxy.
/// In contrast to the regular `MemoNode`. This node ignores all input. /// In contrast to the regular `MemoNode`. This node ignores all input.
/// Using this node might result in the document not updating properly, /// Using this node might result in the document not updating properly,
@@ -98,6 +103,10 @@ impl<T, I, CachedNode> ImpureMemoNode<I, T, CachedNode> {
} }
} }
pub mod impure_memo {
pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::ImpureMemoNode");
}
/// Stores both what a node was called with and what it returned. /// Stores both what a node was called with and what it returned.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct IORecord<I, O> { pub struct IORecord<I, O> {
@@ -142,7 +151,10 @@ impl<I, T, N> MonitorNode<I, T, N> {
} }
} }
use std::hash::{Hash, Hasher}; pub mod monitor {
pub const IDENTIFIER: crate::ProtoNodeIdentifier = crate::ProtoNodeIdentifier::new("graphene_core::memo::MonitorNode");
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct MemoHash<T: Hash> { pub struct MemoHash<T: Hash> {
hash: u64, hash: u64,
+3 -3
View File
@@ -1,4 +1,4 @@
use crate::{Node, NodeIO, NodeIOTypes, Type, WasmNotSend}; use crate::{Node, NodeIO, NodeIOTypes, ProtoNodeIdentifier, Type, WasmNotSend};
use dyn_any::{DynAny, StaticType}; use dyn_any::{DynAny, StaticType};
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::HashMap; use std::collections::HashMap;
@@ -103,11 +103,11 @@ pub enum RegistryValueSource {
Scope(&'static str), Scope(&'static str),
} }
type NodeRegistry = LazyLock<Mutex<HashMap<String, Vec<(NodeConstructor, NodeIOTypes)>>>>; type NodeRegistry = LazyLock<Mutex<HashMap<ProtoNodeIdentifier, Vec<(NodeConstructor, NodeIOTypes)>>>>;
pub static NODE_REGISTRY: NodeRegistry = LazyLock::new(|| Mutex::new(HashMap::new())); pub static NODE_REGISTRY: NodeRegistry = LazyLock::new(|| Mutex::new(HashMap::new()));
pub static NODE_METADATA: LazyLock<Mutex<HashMap<String, NodeMetadata>>> = LazyLock::new(|| Mutex::new(HashMap::new())); pub static NODE_METADATA: LazyLock<Mutex<HashMap<ProtoNodeIdentifier, NodeMetadata>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub type DynFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n + Send>>; pub type DynFuture<'n, T> = Pin<Box<dyn Future<Output = T> + 'n + Send>>;
-1
View File
@@ -20,7 +20,6 @@ async fn transform<T: 'n + 'static>(
rotate: f64, rotate: f64,
scale: DVec2, scale: DVec2,
skew: DVec2, skew: DVec2,
_pivot: DVec2,
) -> Instances<T> { ) -> Instances<T> {
let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., skew.y, skew.x, 1., 0., 0.]); let matrix = DAffine2::from_scale_angle_translation(scale, rotate, translate) * DAffine2::from_cols_array(&[1., skew.y, skew.x, 1., 0., 0.]);
+33 -16
View File
@@ -1,6 +1,7 @@
use std::any::TypeId; use std::any::TypeId;
pub use std::borrow::Cow; pub use std::borrow::Cow;
use std::ops::Deref;
#[macro_export] #[macro_export]
macro_rules! concrete { macro_rules! concrete {
@@ -128,12 +129,37 @@ impl std::fmt::Debug for NodeIOTypes {
pub struct ProtoNodeIdentifier { pub struct ProtoNodeIdentifier {
pub name: Cow<'static, str>, pub name: Cow<'static, str>,
} }
impl From<String> for ProtoNodeIdentifier { impl From<String> for ProtoNodeIdentifier {
fn from(value: String) -> Self { fn from(value: String) -> Self {
Self { name: Cow::Owned(value) } Self { name: Cow::Owned(value) }
} }
} }
impl From<&'static str> for ProtoNodeIdentifier {
fn from(s: &'static str) -> Self {
ProtoNodeIdentifier { name: Cow::Borrowed(s) }
}
}
impl ProtoNodeIdentifier {
pub const fn new(name: &'static str) -> Self {
ProtoNodeIdentifier { name: Cow::Borrowed(name) }
}
pub const fn with_owned_string(name: String) -> Self {
ProtoNodeIdentifier { name: Cow::Owned(name) }
}
}
impl Deref for ProtoNodeIdentifier {
type Target = str;
fn deref(&self) -> &Self::Target {
self.name.as_ref()
}
}
fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Cow<'static, str>, D::Error> { fn migrate_type_descriptor_names<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Cow<'static, str>, D::Error> {
use serde::Deserialize; use serde::Deserialize;
@@ -306,6 +332,13 @@ impl Type {
Self::Future(output) => output.replace_nested(f), Self::Future(output) => output.replace_nested(f),
} }
} }
pub fn to_cow_string(&self) -> Cow<'static, str> {
match self {
Type::Generic(name) => name.clone(),
_ => Cow::Owned(self.to_string()),
}
}
} }
fn format_type(ty: &str) -> String { fn format_type(ty: &str) -> String {
@@ -343,19 +376,3 @@ impl std::fmt::Display for Type {
write!(f, "{}", result) write!(f, "{}", result)
} }
} }
impl From<&'static str> for ProtoNodeIdentifier {
fn from(s: &'static str) -> Self {
ProtoNodeIdentifier { name: Cow::Borrowed(s) }
}
}
impl ProtoNodeIdentifier {
pub const fn new(name: &'static str) -> Self {
ProtoNodeIdentifier { name: Cow::Borrowed(name) }
}
pub const fn with_owned_string(name: String) -> Self {
ProtoNodeIdentifier { name: Cow::Owned(name) }
}
}
@@ -1,7 +1,7 @@
use super::*; use super::*;
use crate::Ctx; use crate::Ctx;
use crate::instances::Instance; use crate::instances::Instance;
use crate::uuid::generate_uuid; use crate::uuid::{NodeId, generate_uuid};
use bezier_rs::BezierHandles; use bezier_rs::BezierHandles;
use dyn_any::DynAny; use dyn_any::DynAny;
use kurbo::{BezPath, PathEl, Point}; use kurbo::{BezPath, PathEl, Point};
@@ -420,12 +420,17 @@ impl Hash for VectorModification {
/// A node that applies a procedural modification to some [`VectorData`]. /// A node that applies a procedural modification to some [`VectorData`].
#[node_macro::node(category(""))] #[node_macro::node(category(""))]
async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>) -> VectorDataTable { async fn path_modify(_ctx: impl Ctx, mut vector_data: VectorDataTable, modification: Box<VectorModification>, node_path: Vec<NodeId>) -> VectorDataTable {
if vector_data.is_empty() { if vector_data.is_empty() {
vector_data.push(Instance::default()); vector_data.push(Instance::default());
} }
let vector_data_instance = vector_data.get_mut(0).expect("push should give one item"); let vector_data_instance = vector_data.get_mut(0).expect("push should give one item");
modification.apply(vector_data_instance.instance); modification.apply(vector_data_instance.instance);
// Update the source node id
let this_node_path = node_path.iter().rev().nth(1).copied();
*vector_data_instance.source_node_id = vector_data_instance.source_node_id.or(this_node_path);
if vector_data.len() > 1 { if vector_data.len() > 1 {
warn!("The path modify ran on {} instances of vector data. Only the first can be modified.", vector_data.len()); warn!("The path modify ran on {} instances of vector data. Only the first can be modified.", vector_data.len());
} }
+1 -2
View File
@@ -352,8 +352,7 @@ async fn copy_to_points<I: 'n + Send + Clone>(
let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation); let transform = DAffine2::from_scale_angle_translation(DVec2::splat(scale), rotation, translation);
for mut instance in instance.instance_ref_iter().map(|instance| instance.to_instance_cloned()) { for mut instance in instance.instance_ref_iter().map(|instance| instance.to_instance_cloned()) {
let local_matrix = DAffine2::from_mat2(instance.transform.matrix2); instance.transform = transform * instance.transform;
instance.transform = transform * local_matrix;
result_table.push(instance); result_table.push(instance);
} }
+20
View File
@@ -612,6 +612,20 @@ fn dot_product(_: impl Ctx, vector_a: DVec2, vector_b: DVec2) -> f64 {
vector_a.dot(vector_b) vector_a.dot(vector_b)
} }
/// Gets the length or magnitude of a vector.
#[node_macro::node(category("Math: Vector"))]
fn length(_: impl Ctx, vector: DVec2) -> f64 {
vector.length()
}
/// Scales the input vector to unit length while preserving it's direction. This is equivalent to dividing the input vector by it's own magnitude.
///
/// Returns zero when the input vector is zero.
#[node_macro::node(category("Math: Vector"))]
fn normalize(_: impl Ctx, vector: DVec2) -> DVec2 {
vector.normalize_or_zero()
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
@@ -625,6 +639,12 @@ mod test {
assert_eq!(dot_product((), vector_a, vector_b), 11.); assert_eq!(dot_product((), vector_a, vector_b), 11.);
} }
#[test]
pub fn length_function() {
let vector = DVec2::new(3., 4.);
assert_eq!(length((), vector), 5.);
}
#[test] #[test]
fn test_basic_expression() { fn test_basic_expression() {
let result = math((), 0., "2 + 2".to_string(), 0.); let result = math((), 0., "2 + 2".to_string(), 0.);
+3 -8
View File
@@ -486,10 +486,6 @@ impl DocumentNodeImplementation {
} }
} }
pub const fn proto(name: &'static str) -> Self {
Self::ProtoNode(ProtoNodeIdentifier::new(name))
}
pub fn output_count(&self) -> usize { pub fn output_count(&self) -> usize {
match self { match self {
DocumentNodeImplementation::Network(network) => network.exports.len(), DocumentNodeImplementation::Network(network) => network.exports.len(),
@@ -1268,7 +1264,6 @@ impl<'a> Iterator for RecursiveNodeIter<'a> {
mod test { mod test {
use super::*; use super::*;
use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput}; use crate::proto::{ConstructionArgs, ProtoNetwork, ProtoNode, ProtoNodeInput};
use graphene_core::ProtoNodeIdentifier;
use std::sync::atomic::AtomicU64; use std::sync::atomic::AtomicU64;
fn gen_node_id() -> NodeId { fn gen_node_id() -> NodeId {
@@ -1540,7 +1535,7 @@ mod test {
NodeId(1), NodeId(1),
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(u32), 0)], inputs: vec![NodeInput::network(concrete!(u32), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
..Default::default() ..Default::default()
}, },
), ),
@@ -1548,7 +1543,7 @@ mod test {
NodeId(2), NodeId(2),
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(u32), 1)], inputs: vec![NodeInput::network(concrete!(u32), 1)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
..Default::default() ..Default::default()
}, },
), ),
@@ -1575,7 +1570,7 @@ mod test {
NodeId(2), NodeId(2),
DocumentNode { DocumentNode {
inputs: vec![result_node_input], inputs: vec![result_node_input],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), implementation: DocumentNodeImplementation::ProtoNode(graphene_core::ops::identity::IDENTIFIER),
..Default::default() ..Default::default()
}, },
), ),
+3 -3
View File
@@ -97,7 +97,7 @@ macro_rules! tagged_value {
} }
} }
/// Attempts to downcast the dynamic type to a tagged value /// Attempts to downcast the dynamic type to a tagged value
pub fn try_from_std_any_ref(input: &(dyn std::any::Any)) -> Result<Self, String> { pub fn try_from_std_any_ref(input: &dyn std::any::Any) -> Result<Self, String> {
use std::any::TypeId; use std::any::TypeId;
match input.type_id() { match input.type_id() {
@@ -190,9 +190,9 @@ tagged_value! {
VectorData(graphene_core::vector::VectorDataTable), VectorData(graphene_core::vector::VectorDataTable),
#[cfg_attr(target_arch = "wasm32", serde(alias = "ImageFrame", deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] // TODO: Eventually remove this migration document upgrade code #[cfg_attr(target_arch = "wasm32", serde(alias = "ImageFrame", deserialize_with = "graphene_core::raster::image::migrate_image_frame"))] // TODO: Eventually remove this migration document upgrade code
RasterData(graphene_core::raster_types::RasterDataTable<CPU>), RasterData(graphene_core::raster_types::RasterDataTable<CPU>),
#[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code #[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::graphic_element::migrate_graphic_group"))] // TODO: Eventually remove this migration document upgrade code
GraphicGroup(graphene_core::GraphicGroupTable), GraphicGroup(graphene_core::GraphicGroupTable),
#[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::migrate_artboard_group"))] // TODO: Eventually remove this migration document upgrade code #[cfg_attr(target_arch = "wasm32", serde(deserialize_with = "graphene_core::graphic_element::migrate_artboard_group"))] // TODO: Eventually remove this migration document upgrade code
ArtboardGroup(graphene_core::ArtboardGroupTable), ArtboardGroup(graphene_core::ArtboardGroupTable),
// ============ // ============
// STRUCT TYPES // STRUCT TYPES
+1 -7
View File
@@ -18,7 +18,6 @@ use graphene_svg_renderer::{GraphicElementRendered, RenderParams, RenderSvgSegme
use base64::Engine; use base64::Engine;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use glam::DAffine2; use glam::DAffine2;
use std::collections::{HashMap, HashSet};
use std::sync::Arc; use std::sync::Arc;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
@@ -278,12 +277,7 @@ async fn render<'a: 'n, T: 'n + GraphicElementRendered + WasmNotSend>(
#[cfg(all(feature = "vello", not(test)))] #[cfg(all(feature = "vello", not(test)))]
let use_vello = use_vello && surface_handle.is_some(); let use_vello = use_vello && surface_handle.is_some();
let mut metadata = RenderMetadata { let mut metadata = RenderMetadata::default();
upstream_footprints: HashMap::new(),
local_transforms: HashMap::new(),
click_targets: HashMap::new(),
clip_targets: HashSet::new(),
};
data.collect_metadata(&mut metadata, footprint, None); data.collect_metadata(&mut metadata, footprint, None);
let output_format = render_config.export_format; let output_format = render_config.export_format;
+2
View File
@@ -198,6 +198,7 @@ pub fn to_transform(transform: DAffine2) -> usvg::Transform {
pub struct RenderMetadata { pub struct RenderMetadata {
pub upstream_footprints: HashMap<NodeId, Footprint>, pub upstream_footprints: HashMap<NodeId, Footprint>,
pub local_transforms: HashMap<NodeId, DAffine2>, pub local_transforms: HashMap<NodeId, DAffine2>,
pub first_instance_source_id: HashMap<NodeId, Option<NodeId>>,
pub click_targets: HashMap<NodeId, Vec<ClickTarget>>, pub click_targets: HashMap<NodeId, Vec<ClickTarget>>,
pub clip_targets: HashSet<NodeId>, pub clip_targets: HashSet<NodeId>,
} }
@@ -1192,6 +1193,7 @@ impl GraphicElementRendered for GraphicElement {
metadata.upstream_footprints.insert(element_id, footprint); metadata.upstream_footprints.insert(element_id, footprint);
// TODO: Find a way to handle more than one row of the graphical data table // TODO: Find a way to handle more than one row of the graphical data table
if let Some(vector_data) = vector_data.instance_ref_iter().next() { if let Some(vector_data) = vector_data.instance_ref_iter().next() {
metadata.first_instance_source_id.insert(element_id, *vector_data.source_node_id);
metadata.local_transforms.insert(element_id, *vector_data.transform); metadata.local_transforms.insert(element_id, *vector_data.transform);
} }
} }
+1 -1
View File
@@ -20,7 +20,7 @@ mod tests {
NodeId(0), NodeId(0),
DocumentNode { DocumentNode {
inputs: vec![NodeInput::network(concrete!(u32), 0)], inputs: vec![NodeInput::network(concrete!(u32), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode")), implementation: DocumentNodeImplementation::ProtoNode(ops::identity::IDENTIFIER),
..Default::default() ..Default::default()
}, },
), ),
+2 -2
View File
@@ -39,7 +39,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
DocumentNode { DocumentNode {
manual_composition: Some(concrete!(Context)), manual_composition: Some(concrete!(Context)),
inputs: vec![NodeInput::node(NodeId(0), 0)], inputs: vec![NodeInput::node(NodeId(0), 0)],
implementation: DocumentNodeImplementation::ProtoNode(ProtoNodeIdentifier::new("graphene_core::memo::MemoNode")), implementation: DocumentNodeImplementation::ProtoNode(graphene_core::memo::memo::IDENTIFIER),
..Default::default() ..Default::default()
}, },
// TODO: Add conversion step // TODO: Add conversion step
@@ -68,7 +68,7 @@ pub fn wrap_network_in_scope(mut network: NodeNetwork, editor_api: Arc<WasmEdito
inner_network, inner_network,
render_node, render_node,
DocumentNode { DocumentNode {
implementation: DocumentNodeImplementation::proto("graphene_core::ops::IdentityNode"), implementation: DocumentNodeImplementation::ProtoNode(graphene_std::ops::identity::IDENTIFIER),
inputs: vec![NodeInput::value(TaggedValue::EditorApi(editor_api), false)], inputs: vec![NodeInput::value(TaggedValue::EditorApi(editor_api), false)],
..Default::default() ..Default::default()
}, },
+25 -20
View File
@@ -2,7 +2,7 @@ use crate::parsing::*;
use convert_case::{Case, Casing}; use convert_case::{Case, Casing};
use proc_macro_crate::FoundCrate; use proc_macro_crate::FoundCrate;
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote, quote_spanned}; use quote::{ToTokens, format_ident, quote, quote_spanned};
use std::sync::atomic::AtomicU64; use std::sync::atomic::AtomicU64;
use syn::punctuated::Punctuated; use syn::punctuated::Punctuated;
use syn::spanned::Spanned; use syn::spanned::Spanned;
@@ -330,11 +330,15 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
}) })
} }
}; };
let path = match parsed.attributes.path {
Some(ref path) => quote!(stringify!(#path).replace(' ', "")), let identifier = format_ident!("{}_proto_ident", fn_name);
None => quote!(std::module_path!().rsplit_once("::").unwrap().0), let identifier_path = match parsed.attributes.path.as_ref() {
Some(path) => {
let path = path.to_token_stream().to_string().replace(' ', "");
quote!(#path)
}
None => quote!(std::module_path!()),
}; };
let identifier = quote!(format!("{}::{}", #path, stringify!(#struct_name)));
let register_node_impl = generate_register_node_impl(parsed, &field_names, &struct_name, &identifier)?; let register_node_impl = generate_register_node_impl(parsed, &field_names, &struct_name, &identifier)?;
let import_name = format_ident!("_IMPORT_STUB_{}", mod_name.to_string().to_case(Case::UpperSnake)); let import_name = format_ident!("_IMPORT_STUB_{}", mod_name.to_string().to_case(Case::UpperSnake));
@@ -354,6 +358,11 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
{ {
#eval_impl #eval_impl
} }
const fn #identifier() -> #graphene_core::ProtoNodeIdentifier {
#graphene_core::ProtoNodeIdentifier::new(std::concat!(#identifier_path, "::", std::stringify!(#struct_name)))
}
#[doc(inline)] #[doc(inline)]
pub use #mod_name::#struct_name; pub use #mod_name::#struct_name;
@@ -418,22 +427,20 @@ pub(crate) fn generate_node_code(parsed: &ParsedNodeFn) -> syn::Result<TokenStre
)* )*
], ],
}; };
NODE_METADATA.lock().unwrap().insert(#identifier, metadata); NODE_METADATA.lock().unwrap().insert(#identifier(), metadata);
} }
} }
}) })
} }
/// Generates strongly typed utilites to access inputs /// Generates strongly typed utilites to access inputs
fn generate_node_input_references(parsed: &ParsedNodeFn, fn_generics: &[crate::GenericParam], field_idents: &[&PatIdent], graphene_core: &TokenStream2, identifier: &TokenStream2) -> TokenStream2 { fn generate_node_input_references(parsed: &ParsedNodeFn, fn_generics: &[crate::GenericParam], field_idents: &[&PatIdent], graphene_core: &TokenStream2, identifier: &Ident) -> TokenStream2 {
if parsed.attributes.skip_impl {
return quote! {};
}
let inputs_module_name = format_ident!("{}", parsed.struct_name.to_string().to_case(Case::Snake)); let inputs_module_name = format_ident!("{}", parsed.struct_name.to_string().to_case(Case::Snake));
let mut generated_input_accessor = Vec::new();
if !parsed.attributes.skip_impl {
let (mut modified, mut generic_collector) = FilterUsedGenerics::new(fn_generics); let (mut modified, mut generic_collector) = FilterUsedGenerics::new(fn_generics);
let mut generated_input_accessor = Vec::new();
for (input_index, (parsed_input, input_ident)) in parsed.fields.iter().zip(field_idents).enumerate() { for (input_index, (parsed_input, input_ident)) in parsed.fields.iter().zip(field_idents).enumerate() {
let mut ty = match parsed_input { let mut ty = match parsed_input {
ParsedField::Regular { ty, .. } => ty, ParsedField::Regular { ty, .. } => ty,
@@ -462,23 +469,21 @@ fn generate_node_input_references(parsed: &ParsedNodeFn, fn_generics: &[crate::G
generated_input_accessor.push(quote! { generated_input_accessor.push(quote! {
impl <#(#used),*> #graphene_core::NodeInputDecleration for #struct_name <#(#fn_generic_params),*> { impl <#(#used),*> #graphene_core::NodeInputDecleration for #struct_name <#(#fn_generic_params),*> {
const INDEX: usize = #input_index; const INDEX: usize = #input_index;
fn identifier() -> &'static str { fn identifier() -> #graphene_core::ProtoNodeIdentifier {
protonode_identifier() #inputs_module_name::IDENTIFIER.clone()
} }
type Result = #ty; type Result = #ty;
} }
}) })
} }
}
quote! { quote! {
pub mod #inputs_module_name { pub mod #inputs_module_name {
use super::*; use super::*;
pub fn protonode_identifier() -> &'static str { /// The `ProtoNodeIdentifier` of this node without any generics attached to it
// Storing the string in a once lock should reduce allocations (since we call this in a loop)? pub const IDENTIFIER: #graphene_core::ProtoNodeIdentifier = #identifier();
static NODE_NAME: std::sync::OnceLock<String> = std::sync::OnceLock::new();
NODE_NAME.get_or_init(|| #identifier )
}
#(#generated_input_accessor)* #(#generated_input_accessor)*
} }
} }
@@ -511,7 +516,7 @@ fn generate_phantom_data<'a>(fn_generics: impl Iterator<Item = &'a crate::Generi
(fn_generic_params, phantom_data_declerations) (fn_generic_params, phantom_data_declerations)
} }
fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], struct_name: &Ident, identifier: &TokenStream2) -> Result<TokenStream2, Error> { fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], struct_name: &Ident, identifier: &Ident) -> Result<TokenStream2, Error> {
if parsed.attributes.skip_impl { if parsed.attributes.skip_impl {
return Ok(quote!()); return Ok(quote!());
} }
@@ -604,7 +609,7 @@ fn generate_register_node_impl(parsed: &ParsedNodeFn, field_names: &[&Ident], st
fn register_node() { fn register_node() {
let mut registry = NODE_REGISTRY.lock().unwrap(); let mut registry = NODE_REGISTRY.lock().unwrap();
registry.insert( registry.insert(
#identifier, #identifier(),
vec![ vec![
#(#constructors,)* #(#constructors,)*
] ]
+4 -4
View File
@@ -6,7 +6,7 @@ use graphene_std::registry::*;
use graphene_std::*; use graphene_std::*;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap<String, DocumentNode>) { pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap<ProtoNodeIdentifier, DocumentNode>) {
if network.generated { if network.generated {
return; return;
} }
@@ -15,7 +15,7 @@ pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap<String,
match &mut node.implementation { match &mut node.implementation {
DocumentNodeImplementation::Network(node_network) => expand_network(node_network, substitutions), DocumentNodeImplementation::Network(node_network) => expand_network(node_network, substitutions),
DocumentNodeImplementation::ProtoNode(proto_node_identifier) => { DocumentNodeImplementation::ProtoNode(proto_node_identifier) => {
if let Some(new_node) = substitutions.get(proto_node_identifier.name.as_ref()) { if let Some(new_node) = substitutions.get(proto_node_identifier) {
node.implementation = new_node.implementation.clone(); node.implementation = new_node.implementation.clone();
} }
} }
@@ -24,7 +24,7 @@ pub fn expand_network(network: &mut NodeNetwork, substitutions: &HashMap<String,
} }
} }
pub fn generate_node_substitutions() -> HashMap<String, DocumentNode> { pub fn generate_node_substitutions() -> HashMap<ProtoNodeIdentifier, DocumentNode> {
let mut custom = HashMap::new(); let mut custom = HashMap::new();
let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap(); let node_registry = graphene_core::registry::NODE_REGISTRY.lock().unwrap();
for (id, metadata) in graphene_core::registry::NODE_METADATA.lock().unwrap().iter() { for (id, metadata) in graphene_core::registry::NODE_METADATA.lock().unwrap().iter() {
@@ -49,7 +49,7 @@ pub fn generate_node_substitutions() -> HashMap<String, DocumentNode> {
let input_count = inputs.len(); let input_count = inputs.len();
let network_inputs = (0..input_count).map(|i| NodeInput::node(NodeId(i as u64), 0)).collect(); let network_inputs = (0..input_count).map(|i| NodeInput::node(NodeId(i as u64), 0)).collect();
let identity_node = ProtoNodeIdentifier::new("graphene_core::ops::IdentityNode"); let identity_node = ops::identity::IDENTIFIER;
let into_node_registry = &interpreted_executor::node_registry::NODE_REGISTRY; let into_node_registry = &interpreted_executor::node_registry::NODE_REGISTRY;
+2 -2
View File
@@ -61,7 +61,7 @@ pub fn combined_message_attrs_impl(attr: TokenStream, input_item: TokenStream) -
<#parent as ToDiscriminant>::Discriminant <#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)] }); input.attrs.push(syn::parse_quote! { #[parent(#parent, #parent::#variant)] });
if parent_is_top { if parent_is_top {
input.attrs.push(syn::parse_quote! { #[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<TokenStream> { fn top_level_impl(input_item: TokenStream) -> syn::Result<TokenStream> {
let mut input = syn::parse2::<ItemEnum>(input_item)?; let mut input = syn::parse2::<ItemEnum>(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))] }); input.attrs.push(syn::parse_quote! { #[discriminant_attr(derive(Debug, Copy, Clone, PartialEq, Eq, Hash, AsMessage))] });
for var in &mut input.variants { for var in &mut input.variants {
+57
View File
@@ -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<TokenStream> {
let input = parse2::<DeriveInput>(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::<Vec<_>>();
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)
}
+52
View File
@@ -42,6 +42,58 @@ pub fn two_segment_path(left_ident: Ident, right_ident: Ident) -> Path {
Path { leading_colon: None, segments } 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
+73
View File
@@ -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<TokenStream> {
let input = parse2::<DeriveInput>(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)
}
+21
View File
@@ -3,17 +3,23 @@
mod as_message; mod as_message;
mod combined_message_attrs; mod combined_message_attrs;
mod discriminant; mod discriminant;
mod extract_fields;
mod helper_structs; mod helper_structs;
mod helpers; mod helpers;
mod hierarchical_tree;
mod hint; mod hint;
mod message_handler_data_attr;
mod transitive_child; mod transitive_child;
mod widget_builder; mod widget_builder;
use crate::as_message::derive_as_message_impl; use crate::as_message::derive_as_message_impl;
use crate::combined_message_attrs::combined_message_attrs_impl; use crate::combined_message_attrs::combined_message_attrs_impl;
use crate::discriminant::derive_discriminant_impl; use crate::discriminant::derive_discriminant_impl;
use crate::extract_fields::derive_extract_field_impl;
use crate::helper_structs::AttrInnerSingleString; use crate::helper_structs::AttrInnerSingleString;
use crate::hierarchical_tree::generate_hierarchical_tree;
use crate::hint::derive_hint_impl; 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::transitive_child::derive_transitive_child_impl;
use crate::widget_builder::derive_widget_builder_impl; use crate::widget_builder::derive_widget_builder_impl;
use proc_macro::TokenStream; 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())) 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

Some files were not shown because too many files have changed in this diff Show More