From 15eb4df8d416888b2126836dafcb88397085e21e Mon Sep 17 00:00:00 2001 From: 0HyperCube <78500760+0HyperCube@users.noreply.github.com> Date: Fri, 26 May 2023 17:22:58 +0100 Subject: [PATCH] Add the document graph (#1217) * Thumbnails for the layer node * Raster node graph frames * Downscale to a random resolution * Cleanup and bug fixes * Generate paths before duplicating outputs * Fix stable id test * Add a document node graph * Fix merge conflict --------- Co-authored-by: Keavon Chambers --- document-legacy/src/document.rs | 15 ++ document-legacy/src/layers/layer_info.rs | 18 +++ .../src/messages/layout/utility_types/mod.rs | 2 +- .../document/document_message_handler.rs | 6 +- .../graph_operation_message_handler.rs | 2 +- .../document/node_graph/node_graph_message.rs | 2 + .../node_graph/node_graph_message_handler.rs | 143 ++++++++++-------- .../document_node_types.rs | 6 +- .../node_properties.rs | 121 ++++++++------- .../properties_panel_message_handler.rs | 13 +- .../properties_panel/utility_functions.rs | 30 +++- .../portfolio/portfolio_message_handler.rs | 1 - editor/src/node_graph_executor.rs | 18 ++- frontend/wasm/src/editor_api.rs | 4 +- node-graph/gcore/src/graphic_element.rs | 19 ++- .../gcore/src/graphic_element/renderer.rs | 4 +- node-graph/graph-craft/src/document/value.rs | 10 +- .../interpreted-executor/src/node_registry.rs | 2 +- 18 files changed, 264 insertions(+), 152 deletions(-) diff --git a/document-legacy/src/document.rs b/document-legacy/src/document.rs index 9dcd53519..a22640677 100644 --- a/document-legacy/src/document.rs +++ b/document-legacy/src/document.rs @@ -26,6 +26,8 @@ pub struct Document { /// This identifier is not a hash and is not guaranteed to be equal for equivalent documents. #[serde(skip)] pub state_identifier: DefaultHasher, + #[serde(default)] + pub document_network: graph_craft::document::NodeNetwork, } impl PartialEq for Document { @@ -39,6 +41,19 @@ impl Default for Document { Self { root: Layer::new(LayerDataType::Folder(FolderLayer::default()), DAffine2::IDENTITY.to_cols_array()), state_identifier: DefaultHasher::new(), + document_network: { + use graph_craft::document::{value::TaggedValue, NodeInput, NodeNetwork}; + let mut network = NodeNetwork::default(); + let node = graph_craft::document::DocumentNode { + name: "Output".into(), + inputs: vec![NodeInput::value(TaggedValue::GraphicGroup(Default::default()), true)], + implementation: graph_craft::document::DocumentNodeImplementation::Unresolved("graphene_core::ops::IdNode".into()), + metadata: graph_craft::document::DocumentNodeMetadata::position((8, 4)), + ..Default::default() + }; + network.push_node(node, false); + network + }, } } } diff --git a/document-legacy/src/layers/layer_info.rs b/document-legacy/src/layers/layer_info.rs index 3a84685e6..0a3f3525c 100644 --- a/document-legacy/src/layers/layer_info.rs +++ b/document-legacy/src/layers/layer_info.rs @@ -253,6 +253,24 @@ impl Layer { } } + /// Gets a child layer of this layer, by a path. If the layer with id 1 is inside a folder with id 0, the path will be [0, 1]. + pub fn child(&self, path: &[LayerId]) -> Option<&Layer> { + let mut layer = self; + for id in path { + layer = layer.as_folder().ok()?.layer(*id)?; + } + Some(layer) + } + + /// Gets a child layer of this layer, by a path. If the layer with id 1 is inside a folder with id 0, the path will be [0, 1]. + pub fn child_mut(&mut self, path: &[LayerId]) -> Option<&mut Layer> { + let mut layer = self; + for id in path { + layer = layer.as_folder_mut().ok()?.layer_mut(*id)?; + } + Some(layer) + } + /// Iterate over the layers encapsulated by this layer. /// If the [Layer type](Layer::data) is not a folder, the only item in the iterator will be the layer itself. /// If the [Layer type](Layer::data) wraps a [Folder](LayerDataType::Folder), the iterator will recursively yield all the layers contained in the folder as well as potential sub-folders. diff --git a/editor/src/messages/layout/utility_types/mod.rs b/editor/src/messages/layout/utility_types/mod.rs index 95fe0ab5d..0adb239b0 100644 --- a/editor/src/messages/layout/utility_types/mod.rs +++ b/editor/src/messages/layout/utility_types/mod.rs @@ -3,7 +3,7 @@ pub mod misc; pub mod widgets; pub mod widget_prelude { - pub use super::layout_widget::{LayoutGroup, Widget, WidgetHolder, WidgetLayout}; + pub use super::layout_widget::*; pub use super::widgets::assist_widgets::*; pub use super::widgets::button_widgets::*; pub use super::widgets::input_widgets::*; diff --git a/editor/src/messages/portfolio/document/document_message_handler.rs b/editor/src/messages/portfolio/document/document_message_handler.rs index 958bd30f6..6824bef2a 100644 --- a/editor/src/messages/portfolio/document/document_message_handler.rs +++ b/editor/src/messages/portfolio/document/document_message_handler.rs @@ -202,7 +202,7 @@ impl MessageHandler { - self.node_graph_handler.process_message(message, responses, (&mut self.document_legacy, executor)); + self.node_graph_handler.process_message(message, responses, (&mut self.document_legacy, executor, document_id)); } #[remain::unsorted] GraphOperation(message) => GraphOperationMessageHandler.process_message(message, responses, (&mut self.document_legacy, &mut self.node_graph_handler)), @@ -502,7 +502,9 @@ impl MessageHandler { - if let Some(message) = self.rasterize_region_below_layer(document_id, layer_path, preferences, persistent_data, None) { + if layer_path.is_empty() { + responses.add(NodeGraphMessage::RunDocumentGraph); + } else if let Some(message) = self.rasterize_region_below_layer(document_id, layer_path, preferences, persistent_data, None) { responses.add(message); } } diff --git a/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs index bd207b03a..28da73183 100644 --- a/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/graph_operation_message_handler.rs @@ -70,7 +70,7 @@ impl<'a> ModifyInputsContext<'a> { } else { self.modify_new_node(name, update_input); } - self.node_graph.layer_path = Some(self.layer.to_vec()); + self.node_graph.update_layer_path(Some(self.layer.to_vec()), self.responses); self.node_graph.nested_path.clear(); self.responses.add(PropertiesPanelMessage::ResendActiveProperties); let layer_path = self.layer.to_vec(); diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs index fecd988d8..6de3b228b 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message.rs @@ -59,6 +59,7 @@ pub enum NodeGraphMessage { PasteNodes { serialized_nodes: String, }, + RunDocumentGraph, SelectNodes { nodes: Vec, }, @@ -92,4 +93,5 @@ pub enum NodeGraphMessage { TogglePreviewImpl { node_id: NodeId, }, + UpdateNewNodeGraph, } diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index b29ddad3a..157a0a25d 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -1,12 +1,10 @@ pub use self::document_node_types::*; use crate::messages::input_mapper::utility_types::macros::action_keys; -use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, Widget, WidgetCallback, WidgetHolder, WidgetLayout}; -use crate::messages::layout::utility_types::widgets::button_widgets::TextButton; +use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::prelude::*; use crate::node_graph_executor::NodeGraphExecutor; use document_legacy::document::Document; -use document_legacy::layers::layer_layer::LayerLayer; use document_legacy::LayerId; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeId, NodeInput, NodeNetwork, NodeOutput}; @@ -33,6 +31,7 @@ pub enum FrontendGraphDataType { Number, #[serde(rename = "number")] Boolean, + /// Refers to the mathematical vector, with direction and magnitude. #[serde(rename = "vec2")] Vector, #[serde(rename = "graphic")] @@ -46,7 +45,7 @@ impl FrontendGraphDataType { TaggedValue::String(_) => Self::Text, TaggedValue::F32(_) | TaggedValue::F64(_) | TaggedValue::U32(_) | TaggedValue::DAffine2(_) => Self::Number, TaggedValue::Bool(_) => Self::Boolean, - TaggedValue::DVec2(_) => Self::Vector, + TaggedValue::DVec2(_) | TaggedValue::IVec2(_) => Self::Vector, TaggedValue::Image(_) => Self::Raster, TaggedValue::ImageFrame(_) => Self::Raster, TaggedValue::Color(_) => Self::Color, @@ -118,33 +117,43 @@ impl FrontendNodeType { #[derive(Debug, Clone, PartialEq, Default, serde::Serialize, serde::Deserialize)] pub struct NodeGraphMessageHandler { - pub layer_path: Option>, - pub nested_path: Vec, - pub selected_nodes: Vec, + pub layer_path: Option>, + pub nested_path: Vec, + pub selected_nodes: Vec, #[serde(skip)] pub widgets: [LayoutGroup; 2], } impl NodeGraphMessageHandler { - fn get_root_network<'a>(&self, document: &'a Document) -> Option<&'a graph_craft::document::NodeNetwork> { - self.layer_path.as_ref().and_then(|path| document.layer(path).ok()).and_then(|layer| layer.as_layer_network().ok()) + pub fn update_layer_path(&mut self, layer_path: Option>, responses: &mut VecDeque) { + self.layer_path = layer_path; + responses.add(NodeGraphMessage::UpdateNewNodeGraph); } - fn get_root_network_mut<'a>(&self, document: &'a mut Document) -> Option<&'a mut graph_craft::document::NodeNetwork> { + fn get_root_network<'a>(&self, document: &'a Document) -> &'a graph_craft::document::NodeNetwork { self.layer_path .as_ref() - .and_then(|path| document.layer_mut(path).ok()) + .and_then(|path| document.root.child(path)) + .and_then(|layer| layer.as_layer_network().ok()) + .unwrap_or(&document.document_network) + } + + fn get_root_network_mut<'a>(&self, document: &'a mut Document) -> &'a mut graph_craft::document::NodeNetwork { + self.layer_path + .as_ref() + .and_then(|path| document.root.child_mut(path)) .and_then(|layer| layer.as_layer_network_mut().ok()) + .unwrap_or(&mut document.document_network) } /// Get the active graph_craft NodeNetwork struct fn get_active_network<'a>(&self, document: &'a Document) -> Option<&'a graph_craft::document::NodeNetwork> { - self.get_root_network(document).and_then(|network| network.nested_network(&self.nested_path)) + self.get_root_network(document).nested_network(&self.nested_path) } /// Get the active graph_craft NodeNetwork struct fn get_active_network_mut<'a>(&self, document: &'a mut Document) -> Option<&'a mut graph_craft::document::NodeNetwork> { - self.get_root_network_mut(document).and_then(|network| network.nested_network_mut(&self.nested_path)) + self.get_root_network_mut(document).nested_network_mut(&self.nested_path) } /// Send the cached layout for the bar at the top of the node panel to the frontend @@ -156,38 +165,41 @@ impl NodeGraphMessageHandler { } /// Collect the addresses of the currently viewed nested node e.g. Root -> MyFunFilter -> Exposure - fn collect_nested_addresses(&mut self, _document: &Document, _responses: &mut VecDeque) { + fn collect_nested_addresses(&mut self, document: &Document, responses: &mut VecDeque) { // // Build path list - // let mut path = vec!["Root".to_string()]; - // let mut network = self.get_root_network(document); - // for node_id in &self.nested_path { - // let node = network.and_then(|network| network.nodes.get(node_id)); - // if let Some(DocumentNode { name, .. }) = node { - // path.push(name.clone()); - // } - // network = node.and_then(|node| node.implementation.get_network()); - // } - // let nesting = path.len(); + let mut path = vec![]; + if let Some(layer) = self.layer_path.as_ref().and_then(|path| document.layer(&path).ok()) { + path.push(format!("Layer: {}", layer.name.as_ref().map(|name| name.as_str()).unwrap_or("Untitled Layer"))); + } else { + path.push("Document Network".to_string()); + } + let mut network = Some(self.get_root_network(document)); + for node_id in &self.nested_path { + let node = network.and_then(|network| network.nodes.get(node_id)); + if let Some(DocumentNode { name, .. }) = node { + path.push(name.clone()); + } + network = node.and_then(|node| node.implementation.get_network()); + } + let nesting = path.len(); - // // Update UI - // self.widgets[0] = LayoutGroup::Row { - // widgets: vec![WidgetHolder::new(Widget::BreadcrumbTrailButtons(BreadcrumbTrailButtons { - // labels: path.clone(), - // on_update: WidgetCallback::new(move |input: &u64| { - // NodeGraphMessage::ExitNestedNetwork { - // depth_of_nesting: nesting - (*input as usize) - 1, - // } - // .into() - // }), - // ..Default::default() - // }))], - // }; + // Update UI + self.widgets[0] = LayoutGroup::Row { + widgets: vec![BreadcrumbTrailButtons::new(path.clone()) + .on_update(move |input: &u64| { + NodeGraphMessage::ExitNestedNetwork { + depth_of_nesting: nesting - (*input as usize) - 1, + } + .into() + }) + .widget_holder()], + }; - // self.send_node_bar_layout(responses); + self.send_node_bar_layout(responses); } /// Updates the buttons for disable and preview - fn update_selection_action_buttons(&mut self, document: &mut Document, responses: &mut VecDeque) { + fn update_selection_action_buttons(&mut self, document: &Document, responses: &mut VecDeque) { if let Some(network) = self.get_active_network(document) { let mut widgets = Vec::new(); @@ -235,8 +247,8 @@ impl NodeGraphMessageHandler { } /// Collate the properties panel sections for a node graph - pub fn collate_properties(&self, graph: &LayerLayer, context: &mut NodePropertiesContext, sections: &mut Vec) { - let mut network = &graph.network; + pub fn collate_properties(&self, context: &mut NodePropertiesContext, sections: &mut Vec) { + let mut network = context.network; for segment in &self.nested_path { network = network.nodes.get(segment).and_then(|node| node.implementation.get_network()).unwrap(); } @@ -411,27 +423,15 @@ impl NodeGraphMessageHandler { .filter_map(|(&id, &new)| network.nodes.get(&id).map(|node| (new, node.clone()))) .map(move |(new, node)| (new, node.map_ids(Self::default_node_input, new_ids))) } - fn clear_graph(responses: &mut VecDeque) { - let nodes = Vec::new(); - let links = Vec::new(); - responses.add(FrontendMessage::UpdateNodeGraph { nodes, links }); - responses.add(LayoutMessage::SendLayout { - layout: Layout::WidgetLayout(WidgetLayout::new(Vec::new())), - layout_target: crate::messages::layout::utility_types::misc::LayoutTarget::NodeGraphBar, - }); - } } -impl MessageHandler for NodeGraphMessageHandler { +impl MessageHandler for NodeGraphMessageHandler { #[remain::check] - fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque, (document, executor): (&mut Document, &NodeGraphExecutor)) { + fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque, (document, executor, document_id): (&mut Document, &NodeGraphExecutor, u64)) { #[remain::sorted] match message { NodeGraphMessage::CloseNodeGraph => { - if let Some(_old_layer_path) = self.layer_path.take() { - Self::clear_graph(responses); - responses.add(PropertiesPanelMessage::ResendActiveProperties); - } + self.update_layer_path(None, responses); } NodeGraphMessage::ConnectNodesByLink { output_node, @@ -703,6 +703,13 @@ impl MessageHandler for N responses.add(NodeGraphMessage::SendGraph { should_rerender: false }); } + NodeGraphMessage::RunDocumentGraph => responses.add(PortfolioMessage::RenderGraphUsingRasterizedRegionBelowLayer { + document_id, + layer_path: Vec::new(), + input_image_data: vec![], + size: (0, 0), + imaginate_node_path: None, + }), NodeGraphMessage::SelectNodes { nodes } => { self.selected_nodes = nodes; self.update_selection_action_buttons(document, responses); @@ -715,6 +722,8 @@ impl MessageHandler for N if should_rerender { if let Some(layer_path) = self.layer_path.clone() { responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); + } else { + responses.add(NodeGraphMessage::RunDocumentGraph); } } } @@ -731,6 +740,8 @@ impl MessageHandler for N if (node.name != "Imaginate" || input_index == 0) && network.connected_to_output(node_id, true) { if let Some(layer_path) = self.layer_path.clone() { responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); + } else { + responses.add(NodeGraphMessage::RunDocumentGraph); } } } @@ -754,11 +765,7 @@ impl MessageHandler for N return; }; - let network = document - .layer_mut(&layer_path) - .ok() - .and_then(|layer| layer.as_layer_network_mut().ok()) - .and_then(|network| network.nested_network_mut(node_path)); + let network = self.get_root_network_mut(document).nested_network_mut(node_path); if let Some(network) = network { if let Some(node) = network.nodes.get_mut(node_id) { @@ -871,11 +878,23 @@ impl MessageHandler for N responses.add(DocumentMessage::InputFrameRasterizeRegionBelowLayer { layer_path }); } } + NodeGraphMessage::UpdateNewNodeGraph => { + if let Some(network) = self.get_active_network(document) { + self.selected_nodes.clear(); + + Self::send_graph(network, executor, &self.layer_path, responses); + + let node_types = document_node_types::collect_node_types(); + responses.add(FrontendMessage::UpdateNodeTypes { node_types }); + } + self.collect_nested_addresses(document, responses); + self.update_selected(document, responses); + } } } fn actions(&self) -> ActionList { - if self.layer_path.is_some() && !self.selected_nodes.is_empty() { + if !self.selected_nodes.is_empty() { actions!(NodeGraphMessageDiscriminant; DeleteSelectedNodes, Cut, Copy, DuplicateSelectedNodes, ToggleHidden) } else { actions!(NodeGraphMessageDiscriminant;) diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs index 8b823e3c3..97dc7ef7d 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/document_node_types.rs @@ -171,10 +171,12 @@ fn static_nodes() -> Vec { DocumentNodeType { name: "Artboard", category: "General", - identifier: NodeImplementation::proto("graphene_core::ConstructArtboardNode<_>"), + identifier: NodeImplementation::proto("graphene_core::ConstructArtboardNode<_, _, _>"), inputs: vec![ DocumentInputType::value("Graphic Group", TaggedValue::GraphicGroup(GraphicGroup::EMPTY), true), - DocumentInputType::value("Bounds", TaggedValue::Optional2IVec2(None), false), + DocumentInputType::value("Location", TaggedValue::IVec2(glam::IVec2::ZERO), false), + DocumentInputType::value("Dimensions", TaggedValue::IVec2(glam::IVec2::new(1920, 1080)), false), + DocumentInputType::value("Background", TaggedValue::Color(Color::WHITE), false), ], outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::Artboard)], properties: node_properties::artboard_properties, diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs index df51b0293..5221ffc89 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler/node_properties.rs @@ -2,7 +2,9 @@ use crate::messages::layout::utility_types::widget_prelude::*; use crate::messages::prelude::*; -use glam::DVec2; +use document_legacy::layers::layer_info::LayerDataTypeDiscriminant; +use document_legacy::Operation; +use glam::{DVec2, IVec2}; use graph_craft::concrete; use graph_craft::document::value::TaggedValue; use graph_craft::document::{DocumentNode, NodeId, NodeInput}; @@ -127,6 +129,58 @@ fn bool_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name widgets } +fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, x: &str, y: &str, mut assist: impl FnMut(&mut Vec)) -> LayoutGroup { + let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Vector, false); + + assist(&mut widgets); + + if let NodeInput::Value { + tagged_value: TaggedValue::DVec2(vec2), + exposed: false, + } = document_node.inputs[index] + { + widgets.extend_from_slice(&[ + WidgetHolder::unrelated_separator(), + NumberInput::new(Some(vec2.x)) + .label(x) + .unit(" px") + .on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), vec2.y)), node_id, index)) + .widget_holder(), + WidgetHolder::related_separator(), + NumberInput::new(Some(vec2.y)) + .label(y) + .unit(" px") + .on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(vec2.x, input.value.unwrap())), node_id, index)) + .widget_holder(), + ]); + } else if let NodeInput::Value { + tagged_value: TaggedValue::IVec2(vec2), + exposed: false, + } = document_node.inputs[index] + { + let update_x = move |input: &NumberInput| TaggedValue::IVec2(IVec2::new(input.value.unwrap() as i32, vec2.y)); + let update_y = move |input: &NumberInput| TaggedValue::IVec2(IVec2::new(vec2.x, input.value.unwrap() as i32)); + widgets.extend_from_slice(&[ + WidgetHolder::unrelated_separator(), + NumberInput::new(Some(vec2.x as f64)) + .int() + .label(x) + .unit(" px") + .on_update(update_value(update_x, node_id, index)) + .widget_holder(), + WidgetHolder::related_separator(), + NumberInput::new(Some(vec2.y as f64)) + .int() + .label(y) + .unit(" px") + .on_update(update_value(update_y, node_id, index)) + .widget_holder(), + ]); + } + + LayoutGroup::Row { widgets } +} + fn vec_f32_input(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, text_props: TextInput, blank_assist: bool) -> Vec { let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Color, blank_assist); @@ -874,11 +928,7 @@ pub fn add_properties(document_node: &DocumentNode, node_id: NodeId, _context: & } pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let translation = { - let index = 1; - - let mut widgets = start_widgets(document_node, node_id, index, "Translation", FrontendGraphDataType::Vector, false); - + let translation_assist = |widgets: &mut Vec| { let pivot_index = 5; if let NodeInput::Value { tagged_value: TaggedValue::DVec2(pivot), @@ -892,32 +942,10 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont .widget_holder(), ); } else { - add_blank_assist(&mut widgets); + add_blank_assist(widgets); } - - if let NodeInput::Value { - tagged_value: TaggedValue::DVec2(vec2), - exposed: false, - } = document_node.inputs[index] - { - widgets.extend_from_slice(&[ - WidgetHolder::unrelated_separator(), - NumberInput::new(Some(vec2.x)) - .label("X") - .unit(" px") - .on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), vec2.y)), node_id, index)) - .widget_holder(), - WidgetHolder::related_separator(), - NumberInput::new(Some(vec2.y)) - .label("Y") - .unit(" px") - .on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(vec2.x, input.value.unwrap())), node_id, index)) - .widget_holder(), - ]); - } - - LayoutGroup::Row { widgets } }; + let translation = vec2_widget(document_node, node_id, 1, "Translation", "X", "Y", translation_assist); let rotation = { let index = 2; @@ -944,32 +972,7 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont LayoutGroup::Row { widgets } }; - let scale = { - let index = 3; - - let mut widgets = start_widgets(document_node, node_id, index, "Scale", FrontendGraphDataType::Vector, true); - - if let NodeInput::Value { - tagged_value: TaggedValue::DVec2(vec2), - exposed: false, - } = document_node.inputs[index] - { - widgets.extend_from_slice(&[ - WidgetHolder::unrelated_separator(), - NumberInput::new(Some(vec2.x)) - .label("X") - .on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(input.value.unwrap(), vec2.y)), node_id, index)) - .widget_holder(), - WidgetHolder::related_separator(), - NumberInput::new(Some(vec2.y)) - .label("Y") - .on_update(update_value(move |input: &NumberInput| TaggedValue::DVec2(DVec2::new(vec2.x, input.value.unwrap())), node_id, index)) - .widget_holder(), - ]); - } - - LayoutGroup::Row { widgets } - }; + let scale = vec2_widget(document_node, node_id, 3, "Scale", "X", "Y", add_blank_assist); vec![translation, rotation, scale] } @@ -1655,6 +1658,8 @@ pub fn layer_properties(document_node: &DocumentNode, node_id: NodeId, _context: ] } pub fn artboard_properties(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec { - let label = text_widget(document_node, node_id, 1, "Label", true); - vec![LayoutGroup::Row { widgets: label }] + let location = vec2_widget(document_node, node_id, 1, "Location", "X", "Y", add_blank_assist); + let dimensions = vec2_widget(document_node, node_id, 2, "Dimensions", "W", "H", add_blank_assist); + let background = color_widget(document_node, node_id, 3, "Background", ColorInput::default().allow_none(false), true); + vec![location, dimensions, background] } diff --git a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs index e9d520687..539822f0f 100644 --- a/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs +++ b/editor/src/messages/portfolio/document/properties_panel/properties_panel_message_handler.rs @@ -1,4 +1,4 @@ -use super::utility_functions::{register_artboard_layer_properties, register_artwork_layer_properties}; +use super::utility_functions::{register_artboard_layer_properties, register_artwork_layer_properties, register_document_graph_properties}; use super::utility_types::PropertiesPanelMessageHandlerData; use crate::messages::layout::utility_types::layout_widget::{Layout, WidgetLayout}; use crate::messages::layout::utility_types::misc::LayoutTarget; @@ -141,6 +141,17 @@ impl<'a> MessageHandler register_artboard_layer_properties(layer, responses, persistent_data), TargetDocument::Artwork => register_artwork_layer_properties(document, path, layer, responses, persistent_data, node_graph_message_handler, executor), } + } else { + let context = crate::messages::portfolio::document::node_graph::NodePropertiesContext { + persistent_data, + document: artwork_document, + responses, + nested_path: &node_graph_message_handler.nested_path, + layer_path: &[], + executor, + network: &artwork_document.document_network, + }; + register_document_graph_properties(context, node_graph_message_handler); } } UpdateSelectedDocumentProperties => responses.add(PropertiesPanelMessage::SetActiveLayers { diff --git a/editor/src/messages/portfolio/document/properties_panel/utility_functions.rs b/editor/src/messages/portfolio/document/properties_panel/utility_functions.rs index e0a59e37d..26abc936c 100644 --- a/editor/src/messages/portfolio/document/properties_panel/utility_functions.rs +++ b/editor/src/messages/portfolio/document/properties_panel/utility_functions.rs @@ -6,6 +6,7 @@ use crate::messages::layout::utility_types::widgets::assist_widgets::PivotAssist use crate::messages::layout::utility_types::widgets::button_widgets::{IconButton, PopoverButton, TextButton}; use crate::messages::layout::utility_types::widgets::input_widgets::{CheckboxInput, ColorInput, NumberInput, NumberInputMode, RadioEntryData, RadioInput, TextInput}; use crate::messages::layout::utility_types::widgets::label_widgets::{IconLabel, TextLabel}; +use crate::messages::portfolio::document::node_graph::NodePropertiesContext; use crate::messages::portfolio::utility_types::PersistentData; use crate::messages::prelude::*; use crate::node_graph_executor::NodeGraphExecutor; @@ -301,7 +302,7 @@ pub fn register_artwork_layer_properties( LayerDataType::Layer(layer) => { let mut properties_sections = Vec::new(); - let mut context = crate::messages::portfolio::document::node_graph::NodePropertiesContext { + let mut context = NodePropertiesContext { persistent_data, document, responses, @@ -310,7 +311,7 @@ pub fn register_artwork_layer_properties( executor, network: &layer.network, }; - node_graph_message_handler.collate_properties(layer, &mut context, &mut properties_sections); + node_graph_message_handler.collate_properties(&mut context, &mut properties_sections); properties_sections } @@ -329,6 +330,31 @@ pub fn register_artwork_layer_properties( }); } +pub fn register_document_graph_properties(mut context: NodePropertiesContext, node_graph_message_handler: &NodeGraphMessageHandler) { + let mut properties_sections = Vec::new(); + node_graph_message_handler.collate_properties(&mut context, &mut properties_sections); + let options_bar = vec![LayoutGroup::Row { + widgets: vec![ + IconLabel::new("File").widget_holder(), + WidgetHolder::unrelated_separator(), + TextLabel::new("Document graph").widget_holder(), + WidgetHolder::unrelated_separator(), + TextInput::new("No layer selected").disabled(true).widget_holder(), + WidgetHolder::related_separator(), + PopoverButton::new("Options Bar", "Coming soon").widget_holder(), + ], + }]; + + context.responses.add(LayoutMessage::SendLayout { + layout: Layout::WidgetLayout(WidgetLayout::new(options_bar)), + layout_target: LayoutTarget::PropertiesOptions, + }); + context.responses.add(LayoutMessage::SendLayout { + layout: Layout::WidgetLayout(WidgetLayout::new(properties_sections)), + layout_target: LayoutTarget::PropertiesSections, + }); +} + fn node_section_transform(layer: &Layer, persistent_data: &PersistentData) -> LayoutGroup { let render_data = RenderData::new(&persistent_data.font_cache, ViewMode::default(), None); let pivot = layer.transform.transform_vector2(layer.layerspace_pivot(&render_data)); diff --git a/editor/src/messages/portfolio/portfolio_message_handler.rs b/editor/src/messages/portfolio/portfolio_message_handler.rs index 55a9bdeef..7325b24e8 100644 --- a/editor/src/messages/portfolio/portfolio_message_handler.rs +++ b/editor/src/messages/portfolio/portfolio_message_handler.rs @@ -514,7 +514,6 @@ impl MessageHandler Ok(layer), + _ => Err("Invalid layer type".to_string()), + }?; + layer_layer.network.clone() + }; // Construct the input image frame let transform = DAffine2::IDENTITY; let image_frame = ImageFrame { image, transform }; - let layer_layer = match &layer.data { - LayerDataType::Layer(layer) => Ok(layer), - _ => Err("Invalid layer type".to_string()), - }?; - let network = layer_layer.network.clone(); - // Special execution path for generating Imaginate (as generation requires IO from outside node graph) /*if let Some(imaginate_node) = imaginate_node { responses.add(self.generate_imaginate(network, imaginate_node, (document, document_id), layer_path, editor_api, persistent_data)?); diff --git a/frontend/wasm/src/editor_api.rs b/frontend/wasm/src/editor_api.rs index 752bf6922..b8a84dfca 100644 --- a/frontend/wasm/src/editor_api.rs +++ b/frontend/wasm/src/editor_api.rs @@ -171,8 +171,8 @@ impl JsEditorHandle { } #[cfg(feature = "tauri")] { - let identifier = format!("http://localhost:3001/image/{:?}_{}", image.path, document_id); - fetchImage(image.path, image.node_id, image.mime, document_id, identifier); + let identifier = format!("http://localhost:3001/image/{:?}_{}", &image.path, document_id); + fetchImage(image.path.clone(), image.node_id, image.mime, document_id, identifier); } } return; diff --git a/node-graph/gcore/src/graphic_element.rs b/node-graph/gcore/src/graphic_element.rs index 0437ca273..979ef7d7d 100644 --- a/node-graph/gcore/src/graphic_element.rs +++ b/node-graph/gcore/src/graphic_element.rs @@ -46,7 +46,9 @@ pub struct GraphicElement { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Artboard { pub graphic_group: GraphicGroup, - pub bounds: Option<[IVec2; 2]>, + pub location: IVec2, + pub dimensions: IVec2, + pub background: Color, } pub struct ConstructLayerNode { @@ -82,13 +84,20 @@ fn construct_layer>( stack } -pub struct ConstructArtboardNode { - bounds: Bounds, +pub struct ConstructArtboardNode { + location: Location, + dimensions: Dimensions, + background: Background, } #[node_fn(ConstructArtboardNode)] -fn construct_artboard(graphic_group: GraphicGroup, bounds: Option<[IVec2; 2]>) -> Artboard { - Artboard { graphic_group, bounds } +fn construct_artboard(graphic_group: GraphicGroup, location: IVec2, dimensions: IVec2, background: Color) -> Artboard { + Artboard { + graphic_group, + location, + dimensions, + background, + } } impl From> for GraphicElementData { diff --git a/node-graph/gcore/src/graphic_element/renderer.rs b/node-graph/gcore/src/graphic_element/renderer.rs index 7ed833638..6ea7723f2 100644 --- a/node-graph/gcore/src/graphic_element/renderer.rs +++ b/node-graph/gcore/src/graphic_element/renderer.rs @@ -99,8 +99,8 @@ impl GraphicElementRendered for Artboard { self.graphic_group.render_svg(render, render_params) } fn bounding_box(&self, transform: DAffine2) -> Option<[DVec2; 2]> { - let artboard_bounds = self.bounds.map(|[a, b]| (transform * Quad::from_box([a.as_dvec2(), b.as_dvec2()])).bounding_box()); - [self.graphic_group.bounding_box(transform), artboard_bounds].into_iter().flatten().reduce(Quad::combine_bounds) + let artboard_bounds = (transform * Quad::from_box([self.location.as_dvec2(), self.location.as_dvec2() + self.dimensions.as_dvec2()])).bounding_box(); + [self.graphic_group.bounding_box(transform), Some(artboard_bounds)].into_iter().flatten().reduce(Quad::combine_bounds) } } diff --git a/node-graph/graph-craft/src/document/value.rs b/node-graph/graph-craft/src/document/value.rs index eaee966a7..0126e9c0b 100644 --- a/node-graph/graph-craft/src/document/value.rs +++ b/node-graph/graph-craft/src/document/value.rs @@ -59,7 +59,7 @@ pub enum TaggedValue { DocumentNode(DocumentNode), GraphicGroup(graphene_core::GraphicGroup), Artboard(graphene_core::Artboard), - Optional2IVec2(Option<[glam::IVec2; 2]>), + IVec2(glam::IVec2), } #[allow(clippy::derived_hash_with_manual_eq)] @@ -129,7 +129,7 @@ impl Hash for TaggedValue { Self::DocumentNode(document_node) => document_node.hash(state), Self::GraphicGroup(graphic_group) => graphic_group.hash(state), Self::Artboard(artboard) => artboard.hash(state), - Self::Optional2IVec2(v) => v.hash(state), + Self::IVec2(v) => v.hash(state), } } } @@ -181,7 +181,7 @@ impl<'a> TaggedValue { TaggedValue::DocumentNode(x) => Box::new(x), TaggedValue::GraphicGroup(x) => Box::new(x), TaggedValue::Artboard(x) => Box::new(x), - TaggedValue::Optional2IVec2(x) => Box::new(x), + TaggedValue::IVec2(x) => Box::new(x), } } @@ -244,7 +244,7 @@ impl<'a> TaggedValue { TaggedValue::DocumentNode(_) => concrete!(crate::document::DocumentNode), TaggedValue::GraphicGroup(_) => concrete!(graphene_core::GraphicGroup), TaggedValue::Artboard(_) => concrete!(graphene_core::Artboard), - TaggedValue::Optional2IVec2(_) => concrete!(Option<[glam::IVec2; 2]>), + TaggedValue::IVec2(_) => concrete!(glam::IVec2), } } @@ -296,7 +296,7 @@ impl<'a> TaggedValue { x if x == TypeId::of::() => Some(TaggedValue::DocumentNode(*downcast(input).unwrap())), x if x == TypeId::of::() => Some(TaggedValue::GraphicGroup(*downcast(input).unwrap())), x if x == TypeId::of::() => Some(TaggedValue::Artboard(*downcast(input).unwrap())), - x if x == TypeId::of::>() => Some(TaggedValue::Optional2IVec2(*downcast(input).unwrap())), + x if x == TypeId::of::() => Some(TaggedValue::IVec2(*downcast(input).unwrap())), _ => None, } } diff --git a/node-graph/interpreted-executor/src/node_registry.rs b/node-graph/interpreted-executor/src/node_registry.rs index 0990c18fd..b7f01ddb0 100644 --- a/node-graph/interpreted-executor/src/node_registry.rs +++ b/node-graph/interpreted-executor/src/node_registry.rs @@ -602,7 +602,7 @@ fn node_registry() -> HashMap, input: ImageFrame, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]), register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::GraphicGroup, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]), register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: graphene_core::Artboard, params: [String, BlendMode, f32, bool, bool, bool, graphene_core::GraphicGroup]), - register_node!(graphene_core::ConstructArtboardNode<_>, input: graphene_core::GraphicGroup, params: [Option<[glam::IVec2; 2]>]), + register_node!(graphene_core::ConstructArtboardNode<_, _, _>, input: graphene_core::GraphicGroup, params: [glam::IVec2, glam::IVec2, Color]), ]; let mut map: HashMap> = HashMap::new(); for (id, c, types) in node_types.into_iter().flatten() {