Add a basic API and rudimentary frontend for node graph layers (#846)

* Node graph API stub

* Rename and fix SetInputValue

* Get list of links from network

* Test populating node graph UI

* Node properties

* Fix viewport bounds

* Slightly change promise usage

* A tiny bit of cleanup I did while reading code

* Cleanup and work towards hooking up node links in Vue template

* Add the brighten colour node

* Run cargo fmt

* Add to and from hsla

* GrayscaleImage node with small perf improvement

* Fix gutter panel resizing

* Display node links from backend

* Add support for connecting node links

* Use existing message

* Fix formatting error

* Add a (currently crashing) brighten node

* Replace brighten node with proto node implementation

* Add support for connecting node links

* Update watch dirs

* Add hue shift node

* Add create_node function to editor api

* Basic insert node UI

* Fix broken names

* Add log

* Fix positioning

* Set connector index to 0

* Add properties for Heu shift / brighten

* Allow deselecting nodes

* Redesign Properties panel collapsible sections

Co-authored-by: Keavon Chambers <keavon@keavon.com>
Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
0HyperCube
2022-11-12 21:23:28 +00:00
committed by Keavon Chambers
parent e8256dd350
commit 504136b61b
37 changed files with 1213 additions and 294 deletions

View File

@@ -1,7 +1,7 @@
use graphene::color::Color;
// Viewport
pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = 1. / 600.;
pub const VIEWPORT_ZOOM_WHEEL_RATE: f64 = (1. / 600.) * 3.;
pub const VIEWPORT_ZOOM_MOUSE_RATE: f64 = 1. / 400.;
pub const VIEWPORT_ZOOM_SCALE_MIN: f64 = 0.000_000_1;
pub const VIEWPORT_ZOOM_SCALE_MAX: f64 = 10_000.;

View File

@@ -2,6 +2,7 @@ use super::utility_types::{FrontendDocumentDetails, FrontendImageData, MouseCurs
use crate::messages::layout::utility_types::layout_widget::SubLayout;
use crate::messages::layout::utility_types::misc::LayoutTarget;
use crate::messages::layout::utility_types::widgets::menu_widgets::MenuBarEntry;
use crate::messages::portfolio::document::node_graph::{FrontendNode, FrontendNodeLink, FrontendNodeType};
use crate::messages::portfolio::document::utility_types::layer_panel::{LayerPanelEntry, RawBuffer};
use crate::messages::prelude::*;
use crate::messages::tool::utility_types::HintData;
@@ -198,9 +199,17 @@ pub enum FrontendMessage {
UpdateMouseCursor {
cursor: MouseCursorIcon,
},
UpdateNodeGraph {
nodes: Vec<FrontendNode>,
links: Vec<FrontendNodeLink>,
},
UpdateNodeGraphVisibility {
visible: bool,
},
UpdateNodeTypes {
#[serde(rename = "nodeTypes")]
node_types: Vec<FrontendNodeType>,
},
UpdateOpenDocumentsList {
#[serde(rename = "openDocuments")]
open_documents: Vec<FrontendDocumentDetails>,

View File

@@ -32,6 +32,9 @@ pub enum DocumentMessage {
#[remain::unsorted]
#[child]
PropertiesPanel(PropertiesPanelMessage),
#[remain::unsorted]
#[child]
NodeGraph(NodeGraphMessage),
// Messages
AbortTransaction,

View File

@@ -64,6 +64,8 @@ pub struct DocumentMessageHandler {
#[serde(skip)]
transform_layer_handler: TransformLayerMessageHandler,
properties_panel_message_handler: PropertiesPanelMessageHandler,
#[serde(skip)]
node_graph_handler: NodeGraphMessageHandler,
}
impl Default for DocumentMessageHandler {
@@ -91,6 +93,7 @@ impl Default for DocumentMessageHandler {
artboard_message_handler: ArtboardMessageHandler::default(),
transform_layer_handler: TransformLayerMessageHandler::default(),
properties_panel_message_handler: PropertiesPanelMessageHandler::default(),
node_graph_handler: Default::default(),
}
}
}
@@ -165,10 +168,15 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
artwork_document: &self.graphene_document,
artboard_document: &self.artboard_message_handler.artboards_graphene_document,
selected_layers: &mut self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then_some(path.as_slice())),
node_graph_message_handler: &self.node_graph_handler,
};
self.properties_panel_message_handler
.process_message(message, (persistent_data, properties_panel_message_handler_data), responses);
}
#[remain::unsorted]
NodeGraph(message) => {
self.node_graph_handler.process_message(message, (&mut self.graphene_document, ipp), responses);
}
// Messages
AbortTransaction => {

View File

@@ -3,6 +3,7 @@ mod document_message_handler;
pub mod artboard;
pub mod navigation;
pub mod node_graph;
pub mod overlays;
pub mod properties_panel;
pub mod transform_layer;

View File

@@ -0,0 +1,7 @@
mod node_graph_message;
mod node_graph_message_handler;
#[doc(inline)]
pub use node_graph_message::{NodeGraphMessage, NodeGraphMessageDiscriminant};
#[doc(inline)]
pub use node_graph_message_handler::*;

View File

@@ -0,0 +1,44 @@
use crate::messages::prelude::*;
use graph_craft::document::{value::TaggedValue, NodeId};
use graph_craft::proto::NodeIdentifier;
#[remain::sorted]
#[impl_message(Message, DocumentMessage, NodeGraph)]
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum NodeGraphMessage {
// Messages
AddLink {
from: NodeId,
to: NodeId,
to_index: usize,
},
CloseNodeGraph,
ConnectNodesByLink {
output_node: u64,
input_node: u64,
input_node_connector_index: u32,
},
CreateNode {
// Having the caller generate the id means that we don't have to return it. This can be a random u64.
node_id: NodeId,
// I don't really know what this is for (perhaps a user identifiable name).
name: String,
// The node identifier must mach that found in `node-graph/graph-craft/src/node_registry.rs` e.g. "graphene_core::raster::GrayscaleNode
identifier: NodeIdentifier,
num_inputs: u32,
},
DeleteNode {
node_id: NodeId,
},
OpenNodeGraph {
layer_path: Vec<graphene::LayerId>,
},
SelectNodes {
nodes: Vec<NodeId>,
},
SetInputValue {
node: NodeId,
input_index: usize,
value: TaggedValue,
},
}

View File

@@ -0,0 +1,321 @@
use crate::messages::layout::utility_types::layout_widget::{LayoutGroup, Widget, WidgetCallback, WidgetHolder};
use crate::messages::layout::utility_types::widgets::input_widgets::{NumberInput, NumberInputMode};
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel};
use crate::messages::prelude::*;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, NodeInput, NodeNetwork};
use graphene::document::Document;
use graphene::layers::layer_info::LayerDataType;
use graphene::layers::nodegraph_layer::NodeGraphFrameLayer;
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct FrontendNode {
pub id: graph_craft::document::NodeId,
#[serde(rename = "displayName")]
pub display_name: String,
}
// (link_start, link_end, link_end_input_index)
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct FrontendNodeLink {
#[serde(rename = "linkStart")]
pub link_start: u64,
#[serde(rename = "linkEnd")]
pub link_end: u64,
#[serde(rename = "linkEndInputIndex")]
pub link_end_input_index: u64,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct FrontendNodeType {
pub name: String,
}
impl FrontendNodeType {
pub fn new(name: &'static str) -> Self {
Self { name: name.to_string() }
}
}
#[derive(Debug, Clone, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize)]
pub struct NodeGraphMessageHandler {
pub layer_path: Option<Vec<graphene::LayerId>>,
pub selected_nodes: Vec<graph_craft::document::NodeId>,
}
impl NodeGraphMessageHandler {
/// 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.layer_path.as_ref().and_then(|path| document.layer_mut(path).ok()).and_then(|layer| match &mut layer.data {
LayerDataType::NodeGraphFrame(n) => Some(&mut n.network),
_ => None,
})
}
pub fn collate_properties(&self, node_graph_frame: &NodeGraphFrameLayer) -> Vec<LayoutGroup> {
let network = &node_graph_frame.network;
let mut section = Vec::new();
for node_id in &self.selected_nodes {
let node = *node_id;
let Some(document_node) = network.nodes.get(node_id) else {
continue;
};
let name = format!("Node {} Properties", document_node.name);
let layout = match &document_node.implementation {
DocumentNodeImplementation::Network(_) => match document_node.name.as_str() {
"Hue Shift Color" => vec![LayoutGroup::Row {
widgets: vec![
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Shift degrees".into(),
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: Some({
let NodeInput::Value (TaggedValue::F32(x)) = document_node.inputs[1] else {
panic!("Hue rotate should be f32")
};
x as f64
}),
unit: "°".into(),
mode: NumberInputMode::Range,
range_min: Some(-180.),
range_max: Some(180.),
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
NodeGraphMessage::SetInputValue {
node,
input_index: 1,
value: TaggedValue::F32(number_input.value.unwrap() as f32),
}
.into()
}),
..NumberInput::default()
})),
],
}],
"Brighten Color" => vec![LayoutGroup::Row {
widgets: vec![
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Brighten Amount".into(),
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::NumberInput(NumberInput {
value: Some({
let NodeInput::Value (TaggedValue::F32(x)) = document_node.inputs[1] else {
panic!("Brighten amount should be f32")
};
x as f64
}),
mode: NumberInputMode::Range,
range_min: Some(-255.),
range_max: Some(255.),
on_update: WidgetCallback::new(move |number_input: &NumberInput| {
NodeGraphMessage::SetInputValue {
node,
input_index: 1,
value: TaggedValue::F32(number_input.value.unwrap() as f32),
}
.into()
}),
..NumberInput::default()
})),
],
}],
_ => vec![LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: format!("Cannot currently display properties for network {}", document_node.name),
..Default::default()
}))],
}],
},
DocumentNodeImplementation::Unresolved(identifier) => match identifier.name.as_ref() {
"graphene_std::raster::MapImageNode" | "graphene_core::ops::IdNode" => vec![LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: format!("{} requires no properties", document_node.name),
..Default::default()
}))],
}],
unknown => {
vec![
LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: format!("TODO: {} properties", unknown),
..Default::default()
}))],
},
LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Add in editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs".to_string(),
..Default::default()
}))],
},
]
}
},
};
section.push(LayoutGroup::Section { name, layout });
}
section
}
fn send_graph(network: &NodeNetwork, responses: &mut VecDeque<Message>) {
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into());
info!("Opening node graph with nodes {:?}", network.nodes);
// List of links in format (link_start, link_end, link_end_input_index)
let links = network
.nodes
.iter()
.flat_map(|(link_end, node)| node.inputs.iter().enumerate().map(move |(index, input)| (input, link_end, index)))
.filter_map(|(input, &link_end, link_end_input_index)| {
if let NodeInput::Node(link_start) = *input {
Some(FrontendNodeLink {
link_start,
link_end,
link_end_input_index: link_end_input_index as u64,
})
} else {
None
}
})
.collect::<Vec<_>>();
let mut nodes = Vec::new();
for (id, node) in &network.nodes {
nodes.push(FrontendNode {
id: *id,
display_name: node.name.clone(),
})
}
log::debug!("Nodes:\n{:#?}\n\nFrontend Nodes:\n{:#?}\n\nLinks:\n{:#?}", network.nodes, nodes, links);
responses.push_back(FrontendMessage::UpdateNodeGraph { nodes, links }.into());
}
}
impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageHandler)> for NodeGraphMessageHandler {
#[remain::check]
fn process_message(&mut self, message: NodeGraphMessage, (document, _ipp): (&mut Document, &InputPreprocessorMessageHandler), responses: &mut VecDeque<Message>) {
#[remain::sorted]
match message {
NodeGraphMessage::AddLink { from, to, to_index } => {
log::debug!("Connect primary output from node {from} to input of index {to_index} on node {to}.");
if let Some(network) = self.get_active_network_mut(document) {
if let Some(to) = network.nodes.get_mut(&to) {
// Extend number of inputs if not already large enough
if to_index >= to.inputs.len() {
to.inputs.extend(((to.inputs.len() - 1)..to_index).map(|_| NodeInput::Network));
}
to.inputs[to_index] = NodeInput::Node(from);
}
}
}
NodeGraphMessage::CloseNodeGraph => {
if let Some(_old_layer_path) = self.layer_path.take() {
info!("Closing node graph");
responses.push_back(FrontendMessage::UpdateNodeGraphVisibility { visible: false }.into());
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into());
// TODO: Close UI and clean up old node graph
}
}
NodeGraphMessage::ConnectNodesByLink {
output_node,
input_node,
input_node_connector_index,
} => {
log::debug!("Connect primary output from node {output_node} to input of index {input_node_connector_index} on node {input_node}.");
}
NodeGraphMessage::CreateNode {
node_id,
name,
identifier,
num_inputs,
} => {
if let Some(network) = self.get_active_network_mut(document) {
let inner_network = NodeNetwork {
inputs: (0..num_inputs).map(|_| 0).collect(),
output: 0,
nodes: [(
node_id,
DocumentNode {
name: format!("{}_impl", name),
// TODO: Allow inserting nodes that contain other nodes.
implementation: DocumentNodeImplementation::Unresolved(identifier),
inputs: (0..num_inputs).map(|_| NodeInput::Network).collect(),
},
)]
.into_iter()
.collect(),
};
network.nodes.insert(
node_id,
DocumentNode {
name,
inputs: (0..num_inputs).map(|_| NodeInput::Network).collect(),
// TODO: Allow inserting nodes that contain other nodes.
implementation: DocumentNodeImplementation::Network(inner_network),
},
);
Self::send_graph(network, responses);
}
}
NodeGraphMessage::DeleteNode { node_id } => {
if let Some(network) = self.get_active_network_mut(document) {
network.nodes.remove(&node_id);
// TODO: Update UI if it is not already updated.
}
}
NodeGraphMessage::OpenNodeGraph { layer_path } => {
if let Some(_old_layer_path) = self.layer_path.replace(layer_path) {
// TODO: Necessary cleanup of old node graph
}
if let Some(network) = self.get_active_network_mut(document) {
self.selected_nodes.clear();
responses.push_back(FrontendMessage::UpdateNodeGraphVisibility { visible: true }.into());
Self::send_graph(network, responses);
// TODO: Dynamic node library
responses.push_back(
FrontendMessage::UpdateNodeTypes {
node_types: vec![
FrontendNodeType::new("Identity"),
FrontendNodeType::new("Grayscale Color"),
FrontendNodeType::new("Brighten Color"),
FrontendNodeType::new("Hue Shift Color"),
FrontendNodeType::new("Add"),
FrontendNodeType::new("Map Image"),
],
}
.into(),
);
}
}
NodeGraphMessage::SelectNodes { nodes } => {
self.selected_nodes = nodes;
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into());
}
NodeGraphMessage::SetInputValue { node, input_index, value } => {
if let Some(network) = self.get_active_network_mut(document) {
if let Some(node) = network.nodes.get_mut(&node) {
// Extend number of inputs if not already large enough
if input_index >= node.inputs.len() {
node.inputs.extend(((node.inputs.len() - 1)..input_index).map(|_| NodeInput::Network));
}
node.inputs[input_index] = NodeInput::Value(value);
}
}
}
}
}
advertise_actions!(NodeGraphMessageDiscriminant; DeleteNode,);
}

View File

@@ -24,6 +24,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
artwork_document,
artboard_document,
selected_layers,
node_graph_message_handler,
} = data;
let get_document = |document_selector: TargetDocument| match document_selector {
TargetDocument::Artboard => artboard_document,
@@ -34,11 +35,15 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
SetActiveLayers { paths, document } => {
if paths.len() != 1 {
// TODO: Allow for multiple selected layers
responses.push_back(PropertiesPanelMessage::ClearSelection.into())
responses.push_back(PropertiesPanelMessage::ClearSelection.into());
responses.push_back(NodeGraphMessage::CloseNodeGraph.into());
} else {
let path = paths.into_iter().next().unwrap();
self.active_selection = Some((path, document));
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into())
if Some((path.clone(), document)) != self.active_selection {
self.active_selection = Some((path, document));
responses.push_back(PropertiesPanelMessage::ResendActiveProperties.into());
responses.push_back(NodeGraphMessage::CloseNodeGraph.into());
}
}
}
ClearSelection => {
@@ -138,7 +143,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
let layer = get_document(target_document).layer(&path).unwrap();
match target_document {
TargetDocument::Artboard => register_artboard_layer_properties(layer, responses, persistent_data),
TargetDocument::Artwork => register_artwork_layer_properties(layer, responses, persistent_data),
TargetDocument::Artwork => register_artwork_layer_properties(path, layer, responses, persistent_data, node_graph_message_handler),
}
}
}

View File

@@ -223,7 +223,13 @@ pub fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDequ
);
}
pub fn register_artwork_layer_properties(layer: &Layer, responses: &mut VecDeque<Message>, persistent_data: &PersistentData) {
pub fn register_artwork_layer_properties(
layer_path: Vec<graphene::LayerId>,
layer: &Layer,
responses: &mut VecDeque<Message>,
persistent_data: &PersistentData,
node_graph_message_handler: &NodeGraphMessageHandler,
) {
let options_bar = vec![LayoutGroup::Row {
widgets: vec![
match &layer.data {
@@ -314,7 +320,16 @@ pub fn register_artwork_layer_properties(layer: &Layer, responses: &mut VecDeque
vec![node_section_transform(layer, persistent_data), node_section_imaginate(imaginate, layer, persistent_data, responses)]
}
LayerDataType::NodeGraphFrame(node_graph_frame) => {
vec![node_section_transform(layer, persistent_data), node_section_node_graph_frame(node_graph_frame)]
let is_graph_open = node_graph_message_handler.layer_path.as_ref().filter(|node_graph| *node_graph == &layer_path).is_some();
let selected_nodes = &node_graph_message_handler.selected_nodes;
if !selected_nodes.is_empty() && is_graph_open {
node_graph_message_handler.collate_properties(&node_graph_frame)
} else {
vec![
node_section_transform(layer, persistent_data),
node_section_node_graph_frame(layer_path, node_graph_frame, is_graph_open),
]
}
}
LayerDataType::Folder(_) => {
vec![node_section_transform(layer, persistent_data)]
@@ -1045,35 +1060,50 @@ fn node_section_imaginate(imaginate_layer: &ImaginateLayer, layer: &Layer, persi
}
}
fn node_section_node_graph_frame(node_graph_frame: &NodeGraphFrameLayer) -> LayoutGroup {
fn node_section_node_graph_frame(layer_path: Vec<graphene::LayerId>, node_graph_frame: &NodeGraphFrameLayer, open_graph: bool) -> LayoutGroup {
LayoutGroup::Section {
name: "Node Graph Frame".into(),
layout: vec![
LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Temporary layer that applies a grayscale to the layers below it.".into(),
..TextLabel::default()
}))],
},
LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Powered by the node graph! :)".into(),
..TextLabel::default()
}))],
},
LayoutGroup::Row {
widgets: vec![WidgetHolder::new(Widget::TextButton(TextButton {
label: "Open Node Graph UI (coming soon)".into(),
tooltip: "Open the node graph associated with this layer".into(),
on_update: WidgetCallback::new(|_| DialogMessage::RequestComingSoonDialog { issue: Some(800) }.into()),
..Default::default()
}))],
widgets: vec![
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Network".into(),
tooltip: "Button to edit the node graph network for this layer".into(),
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::TextButton(TextButton {
label: if open_graph { "Close Node Graph".into() } else { "Open Node Graph".into() },
tooltip: format!("{} the node graph associated with this layer", if open_graph { "Close" } else { "Open" }),
on_update: WidgetCallback::new(move |_| {
let layer_path = layer_path.clone();
if open_graph {
NodeGraphMessage::CloseNodeGraph.into()
} else {
NodeGraphMessage::OpenNodeGraph { layer_path }.into()
}
}),
..Default::default()
})),
],
},
LayoutGroup::Row {
widgets: vec![
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Image".into(),
tooltip: "Buttons to render the node graph and clear the last rendered image".into(),
..Default::default()
})),
WidgetHolder::new(Widget::Separator(Separator {
separator_type: SeparatorType::Unrelated,
direction: SeparatorDirection::Horizontal,
})),
WidgetHolder::new(Widget::TextButton(TextButton {
label: "Generate".into(),
tooltip: "Fill layer frame by generating a new image".into(),
label: "Render".into(),
tooltip: "Fill layer frame by rendering the node graph".into(),
on_update: WidgetCallback::new(|_| DocumentMessage::NodeGraphFrameGenerate.into()),
..Default::default()
})),
@@ -1083,7 +1113,7 @@ fn node_section_node_graph_frame(node_graph_frame: &NodeGraphFrameLayer) -> Layo
})),
WidgetHolder::new(Widget::TextButton(TextButton {
label: "Clear".into(),
tooltip: "Remove generated image from the layer frame".into(),
tooltip: "Remove rendered node graph from the layer frame".into(),
disabled: node_graph_frame.blob_url.is_none(),
on_update: WidgetCallback::new(|_| DocumentMessage::FrameClear.into()),
..Default::default()

View File

@@ -3,10 +3,13 @@ use graphene::LayerId;
use serde::{Deserialize, Serialize};
use crate::messages::prelude::NodeGraphMessageHandler;
pub struct PropertiesPanelMessageHandlerData<'a> {
pub artwork_document: &'a GrapheneDocument,
pub artboard_document: &'a GrapheneDocument,
pub selected_layers: &'a mut dyn Iterator<Item = &'a [LayerId]>,
pub node_graph_message_handler: &'a NodeGraphMessageHandler,
}
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]

View File

@@ -221,33 +221,26 @@ impl PropertyHolder for MenuBarMessageHandler {
),
MenuBarEntry::new_root(
"View".into(),
MenuBarEntryChildren(vec![
vec![
MenuBarEntry {
label: "Zoom to Fit".into(),
shortcut: action_keys!(DocumentMessageDiscriminant::ZoomCanvasToFitAll),
action: MenuBarEntry::create_action(|_| DocumentMessage::ZoomCanvasToFitAll.into()),
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Zoom to 100%".into(),
shortcut: action_keys!(DocumentMessageDiscriminant::ZoomCanvasTo100Percent),
action: MenuBarEntry::create_action(|_| DocumentMessage::ZoomCanvasTo100Percent.into()),
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Zoom to 200%".into(),
shortcut: action_keys!(DocumentMessageDiscriminant::ZoomCanvasTo200Percent),
action: MenuBarEntry::create_action(|_| DocumentMessage::ZoomCanvasTo200Percent.into()),
..MenuBarEntry::default()
},
],
vec![MenuBarEntry {
label: "Node Graph (In Development)".into(),
action: MenuBarEntry::create_action(|_| WorkspaceMessage::NodeGraphToggleVisibility.into()),
MenuBarEntryChildren(vec![vec![
MenuBarEntry {
label: "Zoom to Fit".into(),
shortcut: action_keys!(DocumentMessageDiscriminant::ZoomCanvasToFitAll),
action: MenuBarEntry::create_action(|_| DocumentMessage::ZoomCanvasToFitAll.into()),
..MenuBarEntry::default()
}],
]),
},
MenuBarEntry {
label: "Zoom to 100%".into(),
shortcut: action_keys!(DocumentMessageDiscriminant::ZoomCanvasTo100Percent),
action: MenuBarEntry::create_action(|_| DocumentMessage::ZoomCanvasTo100Percent.into()),
..MenuBarEntry::default()
},
MenuBarEntry {
label: "Zoom to 200%".into(),
shortcut: action_keys!(DocumentMessageDiscriminant::ZoomCanvasTo200Percent),
action: MenuBarEntry::create_action(|_| DocumentMessage::ZoomCanvasTo200Percent.into()),
..MenuBarEntry::default()
},
]]),
),
MenuBarEntry::new_root(
"Help".into(),

View File

@@ -437,14 +437,18 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
// Execute the node graph
let mut network = node_graph_frame.network.clone();
info!("Network {network:?}");
let stack = borrow_stack::FixedSizeStack::new(256);
network.flatten(0);
for node_id in node_graph_frame.network.nodes.keys() {
network.flatten(*node_id);
}
let mut proto_network = network.into_proto_network();
proto_network.reorder_ids();
for (_id, node) in proto_network.nodes {
info!("Node {:?}", node);
graph_craft::node_registry::push_node(node, &stack);
}

View File

@@ -15,6 +15,7 @@ pub use crate::messages::input_preprocessor::{InputPreprocessorMessage, InputPre
pub use crate::messages::layout::{LayoutMessage, LayoutMessageDiscriminant, LayoutMessageHandler};
pub use crate::messages::portfolio::document::artboard::{ArtboardMessage, ArtboardMessageDiscriminant, ArtboardMessageHandler};
pub use crate::messages::portfolio::document::navigation::{NavigationMessage, NavigationMessageDiscriminant, NavigationMessageHandler};
pub use crate::messages::portfolio::document::node_graph::{NodeGraphMessage, NodeGraphMessageDiscriminant, NodeGraphMessageHandler};
pub use crate::messages::portfolio::document::overlays::{OverlaysMessage, OverlaysMessageDiscriminant, OverlaysMessageHandler};
pub use crate::messages::portfolio::document::properties_panel::{PropertiesPanelMessage, PropertiesPanelMessageDiscriminant, PropertiesPanelMessageHandler};
pub use crate::messages::portfolio::document::transform_layer::{TransformLayerMessage, TransformLayerMessageDiscriminant, TransformLayerMessageHandler};