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 <keavon@keavon.com>
This commit is contained in:
0HyperCube 2023-05-26 17:22:58 +01:00 committed by Keavon Chambers
parent 8d778e4848
commit 15eb4df8d4
18 changed files with 264 additions and 152 deletions

View file

@ -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
},
}
}
}

View file

@ -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.

View file

@ -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::*;

View file

@ -202,7 +202,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
}
#[remain::unsorted]
NodeGraph(message) => {
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<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
});
}
InputFrameRasterizeRegionBelowLayer { layer_path } => {
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);
}
}

View file

@ -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();

View file

@ -59,6 +59,7 @@ pub enum NodeGraphMessage {
PasteNodes {
serialized_nodes: String,
},
RunDocumentGraph,
SelectNodes {
nodes: Vec<NodeId>,
},
@ -92,4 +93,5 @@ pub enum NodeGraphMessage {
TogglePreviewImpl {
node_id: NodeId,
},
UpdateNewNodeGraph,
}

View file

@ -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<Vec<document_legacy::LayerId>>,
pub nested_path: Vec<graph_craft::document::NodeId>,
pub selected_nodes: Vec<graph_craft::document::NodeId>,
pub layer_path: Option<Vec<LayerId>>,
pub nested_path: Vec<NodeId>,
pub selected_nodes: Vec<NodeId>,
#[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<Vec<LayerId>>, responses: &mut VecDeque<Message>) {
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<Message>) {
fn collect_nested_addresses(&mut self, document: &Document, responses: &mut VecDeque<Message>) {
// // 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<Message>) {
fn update_selection_action_buttons(&mut self, document: &Document, responses: &mut VecDeque<Message>) {
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<LayoutGroup>) {
let mut network = &graph.network;
pub fn collate_properties(&self, context: &mut NodePropertiesContext, sections: &mut Vec<LayoutGroup>) {
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<Message>) {
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<NodeGraphMessage, (&mut Document, &NodeGraphExecutor)> for NodeGraphMessageHandler {
impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64)> for NodeGraphMessageHandler {
#[remain::check]
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, (document, executor): (&mut Document, &NodeGraphExecutor)) {
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, (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<NodeGraphMessage, (&mut Document, &NodeGraphExecutor)> 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<NodeGraphMessage, (&mut Document, &NodeGraphExecutor)> 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<NodeGraphMessage, (&mut Document, &NodeGraphExecutor)> 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<NodeGraphMessage, (&mut Document, &NodeGraphExecutor)> 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<NodeGraphMessage, (&mut Document, &NodeGraphExecutor)> 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;)

View file

@ -171,10 +171,12 @@ fn static_nodes() -> Vec<DocumentNodeType> {
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,

View file

@ -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<WidgetHolder>)) -> 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<WidgetHolder> {
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<LayoutGroup> {
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<WidgetHolder>| {
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<LayoutGroup> {
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]
}

View file

@ -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<PropertiesPanelMessage, (&PersistentData, PropertiesPane
TargetDocument::Artboard => 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 {

View file

@ -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));

View file

@ -514,7 +514,6 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
self.executor.insert_thumbnail_blob_url(blob_url, layer_id, node_id, responses);
return;
}
let message = DocumentMessage::SetImageBlobUrl {
layer_path,
blob_url,

View file

@ -367,17 +367,21 @@ impl NodeGraphExecutor {
// Get the node graph layer
let document = documents.get_mut(&document_id).ok_or_else(|| "Invalid document".to_string())?;
let network = if layer_path.is_empty() {
document.document_legacy.document_network.clone()
} else {
let layer = document.document_legacy.layer(&layer_path).map_err(|e| format!("No layer: {e:?}"))?;
// 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();
layer_layer.network.clone()
};
// Construct the input image frame
let transform = DAffine2::IDENTITY;
let image_frame = ImageFrame { image, transform };
// Special execution path for generating Imaginate (as generation requires IO from outside node graph)
/*if let Some(imaginate_node) = imaginate_node {

View file

@ -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;

View file

@ -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<Name, BlendMode, Opacity, Visible, Locked, Collapsed, Stack> {
@ -82,13 +84,20 @@ fn construct_layer<Data: Into<GraphicElementData>>(
stack
}
pub struct ConstructArtboardNode<Bounds> {
bounds: Bounds,
pub struct ConstructArtboardNode<Location, Dimensions, Background> {
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<ImageFrame<Color>> for GraphicElementData {

View file

@ -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)
}
}

View file

@ -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::<crate::document::DocumentNode>() => Some(TaggedValue::DocumentNode(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::GraphicGroup>() => Some(TaggedValue::GraphicGroup(*downcast(input).unwrap())),
x if x == TypeId::of::<graphene_core::Artboard>() => Some(TaggedValue::Artboard(*downcast(input).unwrap())),
x if x == TypeId::of::<Option<[glam::IVec2; 2]>>() => Some(TaggedValue::Optional2IVec2(*downcast(input).unwrap())),
x if x == TypeId::of::<glam::IVec2>() => Some(TaggedValue::IVec2(*downcast(input).unwrap())),
_ => None,
}
}

View file

@ -602,7 +602,7 @@ fn node_registry() -> HashMap<NodeIdentifier, HashMap<NodeIOTypes, NodeConstruct
register_node!(graphene_core::ConstructLayerNode<_, _, _, _, _, _, _>, input: ImageFrame<Color>, 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<NodeIdentifier, HashMap<NodeIOTypes, NodeConstructor>> = HashMap::new();
for (id, c, types) in node_types.into_iter().flatten() {