Artboard nodes (#1328)

* Create artboard nodes

* Update node when resizing artboard

* Render clipped artboards

* More stable feature

* Do not render old artboards

* Fix some issues with transforms

* Fix crash when drawing rectangle

* Format

* Allow renaming document from Properties panel

* Adjust artboard label styling

* Fix document graph refresh so artboards show up

* Make "Clear Artboards" coming soon

* Fix displaying an infinite canvas

* Show document name in node graph options bar

* info!() to debug!()

* Fix Properties panel not being cleared when all docs closed

* Remove dead code

* Remove debug logs added in this branch

---------

Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
0HyperCube
2023-07-27 07:35:17 +01:00
committed by Keavon Chambers
parent ad0dc3276e
commit 08f9be6aaf
26 changed files with 636 additions and 182 deletions

View File

@@ -5,7 +5,9 @@ use crate::messages::layout::utility_types::widgets::input_widgets::{CheckboxInp
use crate::messages::layout::utility_types::widgets::label_widgets::{Separator, SeparatorDirection, SeparatorType, TextLabel};
use crate::messages::prelude::*;
use glam::UVec2;
use graphene_core::uuid::generate_uuid;
use glam::{IVec2, UVec2};
/// A dialog to allow users to set some initial options about a new document.
#[derive(Debug, Clone, Default)]
@@ -27,13 +29,20 @@ impl MessageHandler<NewDocumentDialogMessage, ()> for NewDocumentDialogMessageHa
responses.add(PortfolioMessage::NewDocumentWithName { name: self.name.clone() });
if !self.infinite && self.dimensions.x > 0 && self.dimensions.y > 0 {
let id = generate_uuid();
responses.add(ArtboardMessage::AddArtboard {
id: None,
id: Some(id),
position: (0., 0.),
size: (self.dimensions.x as f64, self.dimensions.y as f64),
});
responses.add(GraphOperationMessage::NewArtboard {
id,
artboard: graphene_core::Artboard::new(IVec2::ZERO, self.dimensions.as_ivec2()),
});
responses.add(DocumentMessage::ZoomCanvasToFitAll);
}
responses.add(NodeGraphMessage::RunDocumentGraph);
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
}
}

View File

@@ -150,6 +150,9 @@ pub enum FrontendMessage {
layout_target: LayoutTarget,
diff: Vec<WidgetDiff>,
},
UpdateDocumentNodeRender {
svg: String,
},
UpdateDocumentOverlays {
svg: String,
},
@@ -163,6 +166,9 @@ pub enum FrontendMessage {
size: (f64, f64),
multiplier: (f64, f64),
},
UpdateDocumentTransform {
transform: String,
},
UpdateEyedropperSamplingState {
#[serde(rename = "mousePosition")]
mouse_position: Option<(f64, f64)>,

View File

@@ -32,17 +32,7 @@ impl MessageHandler<InputPreprocessorMessage, KeyboardPlatformLayout> for InputP
// TODO: Extend this to multiple viewports instead of setting it to the value of this last loop iteration
self.viewport_bounds = bounds;
responses.add(Operation::TransformLayer {
path: vec![],
transform: glam::DAffine2::from_translation(translation).to_cols_array(),
});
responses.add(DocumentMessage::Artboard(
Operation::TransformLayer {
path: vec![],
transform: glam::DAffine2::from_translation(translation).to_cols_array(),
}
.into(),
));
responses.add(NavigationMessage::TranslateCanvas { delta: DVec2::ZERO });
responses.add(FrontendMessage::TriggerViewportResize);
}
}

View File

@@ -61,9 +61,11 @@ impl MessageHandler<ArtboardMessage, &PersistentData> for ArtboardMessageHandler
responses.add(DocumentMessage::RenderDocument);
}
ClearArtboards => {
for &artboard in self.artboard_ids.iter() {
responses.add_front(ArtboardMessage::DeleteArtboard { artboard });
}
// TODO: Make this remove the artboard layers from the graph (and cleanly reconnect the artwork)
responses.add(DialogMessage::RequestComingSoonDialog { issue: None });
// for &artboard in self.artboard_ids.iter() {
// responses.add_front(ArtboardMessage::DeleteArtboard { artboard });
// }
}
DeleteArtboard { artboard } => {
self.artboard_ids.retain(|&id| id != artboard);
@@ -78,11 +80,13 @@ impl MessageHandler<ArtboardMessage, &PersistentData> for ArtboardMessageHandler
responses.add(FrontendMessage::UpdateDocumentArtboards {
svg: r##"<rect width="100%" height="100%" fill="#ffffff" />"##.to_string(),
})
} else {
let render_data = RenderData::new(&persistent_data.font_cache, ViewMode::Normal, None);
responses.add(FrontendMessage::UpdateDocumentArtboards {
svg: self.artboards_document.render_root(&render_data),
});
// TODO: Delete this whole legacy code path when cleaning up/removing the old (non-node based) artboard implementation
// TODO: The below code was used to draw the non-node based artboards, but we still need the above code to draw the infinite canvas until the refactor is complete and all this code can be removed
// } else {
// let render_data = RenderData::new(&persistent_data.font_cache, ViewMode::Normal, None);
// responses.add(FrontendMessage::UpdateDocumentArtboards {
// svg: self.artboards_document.render_root(&render_data),
// });
}
}
ResizeArtboard { artboard, position, mut size } => {

View File

@@ -124,6 +124,9 @@ pub enum DocumentMessage {
mouse: Option<(f64, f64)>,
},
Redo,
RenameDocument {
new_name: String,
},
RenameLayer {
layer_path: Vec<LayerId>,
new_name: String,

View File

@@ -197,6 +197,7 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
#[remain::unsorted]
PropertiesPanel(message) => {
let properties_panel_message_handler_data = PropertiesPanelMessageHandlerData {
document_name: &self.name.as_str(),
artwork_document: &self.document_legacy,
artboard_document: &self.artboard_message_handler.artboards_document,
selected_layers: &mut self.layer_metadata.iter().filter_map(|(path, data)| data.selected.then_some(path.as_slice())),
@@ -208,7 +209,8 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
}
#[remain::unsorted]
NodeGraph(message) => {
self.node_graph_handler.process_message(message, responses, (&mut self.document_legacy, executor, document_id));
self.node_graph_handler
.process_message(message, responses, (&mut self.document_legacy, executor, document_id, self.name.as_str()));
}
#[remain::unsorted]
GraphOperation(message) => GraphOperationMessageHandler.process_message(message, responses, (&mut self.document_legacy, &mut self.node_graph_handler)),
@@ -659,6 +661,11 @@ impl MessageHandler<DocumentMessage, (u64, &InputPreprocessorMessageHandler, &Pe
responses.add(RenderDocument);
responses.add(FolderChanged { affected_folder_path: vec![] });
}
RenameDocument { new_name } => {
self.name = new_name;
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
}
RenameLayer { layer_path, new_name } => responses.add(DocumentOperation::RenameLayer { layer_path, new_name }),
RenderDocument => {
responses.add(FrontendMessage::UpdateDocumentArtwork {
@@ -1096,6 +1103,7 @@ impl DocumentMessageHandler {
let starting_root_transform = document.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2.);
document.document_legacy.root.transform = starting_root_transform;
document.artboard_message_handler.artboards_document.root.transform = starting_root_transform;
document
}

View File

@@ -10,6 +10,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::document::Document;
use document_legacy::Operation as DocumentOperation;
use graphene_core::renderer::format_transform_matrix;
use glam::{DAffine2, DVec2};
use serde::{Deserialize, Serialize};
@@ -352,6 +353,9 @@ impl NavigationMessageHandler {
}
.into(),
));
let transform = format_transform_matrix(self.calculate_offset_transform(scaled_half_viewport));
responses.add(FrontendMessage::UpdateDocumentTransform { transform });
// TODO: Artboard pos
}
pub fn center_zoom(&self, viewport_bounds: DVec2, zoom_factor: f64, mouse: DVec2) -> Message {

View File

@@ -1,11 +1,13 @@
use crate::messages::prelude::*;
use graph_craft::document::NodeId;
use graphene_core::uuid::ManipulatorGroupId;
use graphene_core::vector::brush_stroke::BrushStroke;
use graphene_core::vector::style::{Fill, Stroke};
use graphene_core::vector::ManipulatorPointId;
use graphene_core::Artboard;
use glam::{DAffine2, DVec2};
use glam::{DAffine2, DVec2, IVec2};
pub type LayerIdentifier = Vec<document_legacy::LayerId>;
@@ -51,6 +53,20 @@ pub enum GraphOperationMessage {
layer: LayerIdentifier,
strokes: Vec<BrushStroke>,
},
NewArtboard {
id: NodeId,
artboard: Artboard,
},
ResizeArtboard {
id: NodeId,
location: IVec2,
dimensions: IVec2,
},
DeleteArtboard {
id: NodeId,
},
ClearArtboards,
}
#[derive(PartialEq, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]

View File

@@ -4,12 +4,13 @@ use crate::messages::prelude::*;
use document_legacy::document::Document;
use document_legacy::{LayerId, Operation};
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork};
use graph_craft::document::{generate_uuid, DocumentNode, DocumentNodeMetadata, NodeId, NodeInput, NodeNetwork, NodeOutput};
use graphene_core::vector::brush_stroke::BrushStroke;
use graphene_core::vector::style::{Fill, FillType, Stroke};
use graphene_core::Artboard;
use transform_utils::LayerBounds;
use glam::{DAffine2, DVec2};
use glam::{DAffine2, DVec2, IVec2};
pub mod transform_utils;
@@ -21,27 +22,152 @@ struct ModifyInputsContext<'a> {
node_graph: &'a mut NodeGraphMessageHandler,
responses: &'a mut VecDeque<Message>,
layer: &'a [LayerId],
outwards_links: HashMap<NodeId, Vec<NodeId>>,
layer_node: Option<NodeId>,
}
impl<'a> ModifyInputsContext<'a> {
/// Get the node network from the document
fn new(layer: &'a [LayerId], document: &'a mut Document, node_graph: &'a mut NodeGraphMessageHandler, responses: &'a mut VecDeque<Message>) -> Option<Self> {
document.layer_mut(layer).ok().and_then(|layer| layer.as_layer_network_mut().ok()).map(|network| Self {
outwards_links: network.collect_outwards_links(),
network,
node_graph,
responses,
layer,
layer_node: None,
})
}
/// Get the node network from the document
fn new_doc(document: &'a mut Document, node_graph: &'a mut NodeGraphMessageHandler, responses: &'a mut VecDeque<Message>) -> Self {
Self {
outwards_links: document.document_network.collect_outwards_links(),
network: &mut document.document_network,
node_graph,
responses,
layer: &[],
layer_node: None,
}
}
fn locate_layer(&mut self, mut id: NodeId) -> Option<NodeId> {
while self.network.nodes.get(&id)?.name != "Layer" {
id = self.outwards_links.get(&id)?.first().copied()?;
}
self.layer_node = Some(id);
Some(id)
}
/// Updates the input of an existing node
fn modify_existing_node_inputs(&mut self, node_id: NodeId, update_input: impl FnOnce(&mut Vec<NodeInput>)) {
let document_node = self.network.nodes.get_mut(&node_id).unwrap();
update_input(&mut document_node.inputs);
}
pub fn insert_between(&mut self, pre: NodeOutput, post: NodeOutput, mut node: DocumentNode, input: usize, output: usize) -> Option<NodeId> {
let id = generate_uuid();
let pre_node = self.network.nodes.get_mut(&pre.node_id)?;
node.metadata.position = pre_node.metadata.position;
let post_node = self.network.nodes.get_mut(&post.node_id)?;
node.inputs[input] = NodeInput::node(pre.node_id, pre.node_output_index);
post_node.inputs[post.node_output_index] = NodeInput::node(id, output);
self.network.nodes.insert(id, node);
self.shift_upstream(id, IVec2::new(-8, 0));
Some(id)
}
pub fn insert_layer_below(&mut self, node_id: NodeId, input_index: usize) -> Option<NodeId> {
let layer_node = resolve_document_node_type("Layer").expect("Layer node");
let new_id = generate_uuid();
let post_node = self.network.nodes.get_mut(&node_id)?;
post_node.inputs[input_index] = NodeInput::node(new_id, 0);
let document_node = layer_node.to_document_node_default_inputs([], DocumentNodeMetadata::position(post_node.metadata.position + IVec2::new(0, 2)));
self.network.nodes.insert(new_id, document_node);
Some(new_id)
}
pub fn insert_node_before(&mut self, new_id: NodeId, node_id: NodeId, input_index: usize, mut document_node: DocumentNode, offset: IVec2) -> Option<NodeId> {
let post_node = self.network.nodes.get_mut(&node_id)?;
post_node.inputs[input_index] = NodeInput::node(new_id, 0);
document_node.metadata.position = post_node.metadata.position + offset;
self.network.nodes.insert(new_id, document_node);
Some(new_id)
}
pub fn create_layer(&mut self, new_id: NodeId, output_node_id: NodeId) -> Option<NodeId> {
let mut current_node = output_node_id;
let mut input_index = 0;
let mut current_input = &self.network.nodes.get(&current_node)?.inputs[input_index];
while let NodeInput::Node { node_id, output_index, .. } = current_input {
let mut sibling_node = &self.network.nodes.get(node_id)?;
if sibling_node.name == "Layer" {
current_node = *node_id;
input_index = 7;
current_input = &self.network.nodes.get(&current_node)?.inputs[input_index];
} else {
// Insert a layer node between the output and the new
let layer_node = resolve_document_node_type("Layer").expect("Layer node");
let node = layer_node.to_document_node_default_inputs([], DocumentNodeMetadata::default());
let node_id = self.insert_between(NodeOutput::new(*node_id, *output_index), NodeOutput::new(current_node, input_index), node, 0, 0)?;
current_node = node_id;
input_index = 7;
current_input = &self.network.nodes.get(&current_node)?.inputs[input_index];
}
}
let layer_node = resolve_document_node_type("Layer").expect("Node").to_document_node_default_inputs([], Default::default());
let layer_node = self.insert_node_before(new_id, current_node, input_index, layer_node, IVec2::new(0, 3))?;
Some(layer_node)
}
fn insert_artboard(&mut self, artboard: Artboard, layer: NodeId) -> Option<NodeId> {
let artboard_node = resolve_document_node_type("Artboard").expect("Node").to_document_node_default_inputs(
[
None,
Some(NodeInput::value(TaggedValue::IVec2(artboard.location), false)),
Some(NodeInput::value(TaggedValue::IVec2(artboard.dimensions), false)),
Some(NodeInput::value(TaggedValue::Color(artboard.background), false)),
Some(NodeInput::value(TaggedValue::Bool(artboard.clip), false)),
],
Default::default(),
);
self.insert_node_before(generate_uuid(), layer, 0, artboard_node, IVec2::new(-8, 0))
}
fn shift_upstream(&mut self, node_id: NodeId, shift: IVec2) {
let mut shift_nodes = HashSet::new();
let mut stack = vec![node_id];
while let Some(node) = stack.pop() {
let Some(node) = self.network.nodes.get(&node_id) else { continue };
for input in &node.inputs {
let NodeInput::Node { node_id, .. } = input else { continue };
if shift_nodes.insert(*node_id) {
stack.push(*node_id);
}
}
}
for node_id in shift_nodes {
if let Some(node) = self.network.nodes.get_mut(&node_id) {
node.metadata.position += shift;
}
}
}
/// Inserts a new node and modifies the inputs
fn modify_new_node(&mut self, name: &'static str, update_input: impl FnOnce(&mut Vec<NodeInput>)) {
let output_node_id = self.network.outputs[0].node_id;
let output_node_id = self.layer_node.unwrap_or(self.network.outputs[0].node_id);
let Some(output_node) = self.network.nodes.get_mut(&output_node_id) else {
warn!("Output node doesn't exist");
return;
@@ -65,7 +191,7 @@ impl<'a> ModifyInputsContext<'a> {
/// Changes the inputs of a specific node
fn modify_inputs(&mut self, name: &'static str, skip_rerender: bool, update_input: impl FnOnce(&mut Vec<NodeInput>)) {
let existing_node_id = self.network.primary_flow().find(|(node, _)| node.name == name).map(|(_, id)| id);
let existing_node_id = self.network.primary_flow_from_opt(self.layer_node).find(|(node, _)| node.name == name).map(|(_, id)| id);
if let Some(node_id) = existing_node_id {
self.modify_existing_node_inputs(node_id, update_input);
} else {
@@ -229,6 +355,36 @@ impl<'a> ModifyInputsContext<'a> {
inputs[2] = NodeInput::value(TaggedValue::BrushStrokes(strokes), false);
});
}
fn resize_artboard(&mut self, location: IVec2, dimensions: IVec2) {
self.modify_inputs("Artboard", false, |inputs| {
inputs[1] = NodeInput::value(TaggedValue::IVec2(location), false);
inputs[2] = NodeInput::value(TaggedValue::IVec2(dimensions), false);
});
}
fn delete_layer(&mut self, id: NodeId) {
let mut new_input = None;
let post_node = self.outwards_links.get(&id).and_then(|links| links.first().copied());
let mut delete_nodes = vec![id];
for (node, id) in self.network.primary_flow_from_opt(Some(id)) {
delete_nodes.push(id);
if node.name == "Artboard" {
new_input = Some(node.inputs[0].clone());
break;
}
}
for node_id in delete_nodes {
self.network.nodes.remove(&node_id);
}
if let (Some(new_input), Some(post_node)) = (new_input, post_node) {
if let Some(node) = self.network.nodes.get_mut(&post_node) {
node.inputs[0] = new_input;
}
}
}
}
impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessageHandler)> for GraphOperationMessageHandler {
@@ -316,6 +472,31 @@ impl MessageHandler<GraphOperationMessage, (&mut Document, &mut NodeGraphMessage
modify_inputs.brush_modify(strokes);
}
}
GraphOperationMessage::NewArtboard { id, artboard } => {
let mut modify_inputs = ModifyInputsContext::new_doc(document, node_graph, responses);
if let Some(layer) = modify_inputs.create_layer(id, modify_inputs.network.outputs[0].node_id) {
modify_inputs.insert_artboard(artboard, layer);
}
//modify_inputs.brush_modify(strokes);
}
GraphOperationMessage::ResizeArtboard { id, location, dimensions } => {
let mut modify_inputs = ModifyInputsContext::new_doc(document, node_graph, responses);
if let Some(layer) = modify_inputs.locate_layer(id) {
modify_inputs.resize_artboard(location, dimensions);
}
}
GraphOperationMessage::DeleteArtboard { id } => {
let mut modify_inputs = ModifyInputsContext::new_doc(document, node_graph, responses);
modify_inputs.delete_layer(id);
}
GraphOperationMessage::ClearArtboards => {
let mut modify_inputs = ModifyInputsContext::new_doc(document, node_graph, responses);
let artboard_nodes = modify_inputs.network.nodes.iter().filter(|(_, node)| node.name == "Artboard").map(|(id, _)| *id).collect::<Vec<_>>();
for id in artboard_nodes {
modify_inputs.delete_layer(id);
}
}
}
}

View File

@@ -156,7 +156,7 @@ impl NodeGraphMessageHandler {
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
/// Send the cached layout to the frontend for the options bar at the top of the node panel
fn send_node_bar_layout(&self, responses: &mut VecDeque<Message>) {
responses.add(LayoutMessage::SendLayout {
layout: Layout::WidgetLayout(WidgetLayout::new(self.widgets.to_vec())),
@@ -165,34 +165,48 @@ 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>) {
// // Build path list
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_deref().unwrap_or("Untitled Layer")));
} else {
path.push("Document Network".to_string());
}
fn collect_nested_addresses(&mut self, document: &Document, document_name: &str, responses: &mut VecDeque<Message>) {
let layer_if_selected = self.layer_path.as_ref().and_then(|path| document.layer(path).ok());
// Build path list for the layer, or otherwise the root document
let path_root = match layer_if_selected {
Some(layer) => layer.name.as_deref().unwrap_or("Untitled Layer"),
None => document_name,
};
let mut path = vec![path_root.to_string()];
let (icon, tooltip) = match layer_if_selected {
Some(_) => ("Layer", "Layer"),
None => ("File", "Document"),
};
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![BreadcrumbTrailButtons::new(path.clone())
.on_update(move |input: &u64| {
NodeGraphMessage::ExitNestedNetwork {
depth_of_nesting: nesting - (*input as usize) - 1,
}
.into()
})
.widget_holder()],
widgets: vec![
IconLabel::new(icon).tooltip(tooltip).widget_holder(),
WidgetHolder::unrelated_separator(),
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);
@@ -425,9 +439,9 @@ impl NodeGraphMessageHandler {
}
}
impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64)> for NodeGraphMessageHandler {
impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64, &str)> for NodeGraphMessageHandler {
#[remain::check]
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, (document, executor, document_id): (&mut Document, &NodeGraphExecutor, u64)) {
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, (document, executor, document_id, document_name): (&mut Document, &NodeGraphExecutor, u64, &str)) {
#[remain::sorted]
match message {
NodeGraphMessage::CloseNodeGraph => {
@@ -564,7 +578,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64)>
if let Some(network) = self.get_active_network(document) {
Self::send_graph(network, executor, &self.layer_path, responses);
}
self.collect_nested_addresses(document, responses);
self.collect_nested_addresses(document, document_name, responses);
self.update_selected(document, responses);
}
NodeGraphMessage::DuplicateSelectedNodes => {
@@ -600,7 +614,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64)>
if let Some(network) = self.get_active_network(document) {
Self::send_graph(network, executor, &self.layer_path, responses);
}
self.collect_nested_addresses(document, responses);
self.collect_nested_addresses(document, document_name, responses);
self.update_selected(document, responses);
}
NodeGraphMessage::ExposeInput { node_id, input_index, new_exposed } => {
@@ -662,7 +676,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64)>
let node_types = document_node_types::collect_node_types();
responses.add(FrontendMessage::UpdateNodeTypes { node_types });
}
self.collect_nested_addresses(document, responses);
self.collect_nested_addresses(document, document_name, responses);
self.update_selected(document, responses);
}
NodeGraphMessage::PasteNodes { serialized_nodes } => {
@@ -895,7 +909,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &NodeGraphExecutor, u64)>
let node_types = document_node_types::collect_node_types();
responses.add(FrontendMessage::UpdateNodeTypes { node_types });
}
self.collect_nested_addresses(document, responses);
self.collect_nested_addresses(document, document_name, responses);
self.update_selected(document, responses);
}
}

View File

@@ -204,12 +204,13 @@ 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("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),
DocumentInputType::value("Clip", TaggedValue::Bool(false), false),
],
outputs: vec![DocumentOutputType::new("Out", FrontendGraphDataType::Artboard)],
properties: node_properties::artboard_properties,

View File

@@ -1623,5 +1623,8 @@ pub fn artboard_properties(document_node: &DocumentNode, node_id: NodeId, _conte
let location = vec2_widget(document_node, node_id, 1, "Location", "X", "Y", " px", add_blank_assist);
let dimensions = vec2_widget(document_node, node_id, 2, "Dimensions", "W", "H", " px", add_blank_assist);
let background = color_widget(document_node, node_id, 3, "Background", ColorInput::default().allow_none(false), true);
vec![location, dimensions, background]
let clip = LayoutGroup::Row {
widgets: bool_widget(document_node, node_id, 4, "Clip", true),
};
vec![location, dimensions, background, clip]
}

View File

@@ -24,6 +24,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
use PropertiesPanelMessage::*;
let PropertiesPanelMessageHandlerData {
document_name,
artwork_document,
artboard_document,
selected_layers,
@@ -63,6 +64,9 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
}
}
ClearSelection => {
// This causes the Properties panel to change, so this needs to happen before the following lines clear the Properties panel
responses.add(NodeGraphMessage::CloseNodeGraph);
responses.add(LayoutMessage::SendLayout {
layout: Layout::WidgetLayout(WidgetLayout::new(vec![])),
layout_target: LayoutTarget::PropertiesOptions,
@@ -71,7 +75,6 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
layout: Layout::WidgetLayout(WidgetLayout::new(vec![])),
layout_target: LayoutTarget::PropertiesSections,
});
responses.add(NodeGraphMessage::CloseNodeGraph);
self.active_selection = None;
}
Deactivate => responses.add(BroadcastMessage::UnsubscribeEvent {
@@ -151,7 +154,7 @@ impl<'a> MessageHandler<PropertiesPanelMessage, (&PersistentData, PropertiesPane
executor,
network: &artwork_document.document_network,
};
register_document_graph_properties(context, node_graph_message_handler);
register_document_graph_properties(context, node_graph_message_handler, document_name);
}
}
UpdateSelectedDocumentProperties => responses.add(PropertiesPanelMessage::SetActiveLayers {

View File

@@ -78,19 +78,14 @@ pub fn register_artboard_layer_properties(layer: &Layer, responses: &mut VecDequ
..Default::default()
})),
WidgetHolder::unrelated_separator(),
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: "Artboard".into(),
..TextLabel::default()
})),
WidgetHolder::unrelated_separator(),
WidgetHolder::new(Widget::TextInput(TextInput {
value: layer.name.clone().unwrap_or_else(|| "Untitled".to_string()),
value: layer.name.clone().unwrap_or_else(|| "Untitled Artboard".to_string()),
on_update: WidgetCallback::new(|text_input: &TextInput| PropertiesPanelMessage::ModifyName { name: text_input.value.clone() }.into()),
..Default::default()
})),
WidgetHolder::related_separator(),
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
header: "Options Bar".into(),
header: "Additional Options".into(),
text: "Coming soon".into(),
..Default::default()
})),
@@ -261,22 +256,14 @@ pub fn register_artwork_layer_properties(
})),
},
WidgetHolder::unrelated_separator(),
WidgetHolder::new(Widget::TextLabel(TextLabel {
value: match &layer.data {
LayerDataType::Layer(_) => "Layer".into(),
other => LayerDataTypeDiscriminant::from(other).to_string(),
},
..TextLabel::default()
})),
WidgetHolder::unrelated_separator(),
WidgetHolder::new(Widget::TextInput(TextInput {
value: layer.name.clone().unwrap_or_else(|| "Untitled".to_string()),
value: layer.name.clone().unwrap_or_else(|| "Untitled Layer".to_string()),
on_update: WidgetCallback::new(|text_input: &TextInput| PropertiesPanelMessage::ModifyName { name: text_input.value.clone() }.into()),
..Default::default()
})),
WidgetHolder::related_separator(),
WidgetHolder::new(Widget::PopoverButton(PopoverButton {
header: "Options Bar".into(),
header: "Additional Options".into(),
text: "Coming soon".into(),
..Default::default()
})),
@@ -326,18 +313,18 @@ pub fn register_artwork_layer_properties(
});
}
pub fn register_document_graph_properties(mut context: NodePropertiesContext, node_graph_message_handler: &NodeGraphMessageHandler) {
pub fn register_document_graph_properties(mut context: NodePropertiesContext, node_graph_message_handler: &NodeGraphMessageHandler, document_name: &str) {
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(),
IconLabel::new("File").tooltip("Document").widget_holder(),
WidgetHolder::unrelated_separator(),
TextLabel::new("Document graph").widget_holder(),
WidgetHolder::unrelated_separator(),
TextInput::new("No layer selected").disabled(true).widget_holder(),
TextInput::new(document_name)
.on_update(|text_input| DocumentMessage::RenameDocument { new_name: text_input.value.clone() }.into())
.widget_holder(),
WidgetHolder::related_separator(),
PopoverButton::new("Options Bar", "Coming soon").widget_holder(),
PopoverButton::new("Additional Options", "Coming soon").widget_holder(),
],
}];

View File

@@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::{messages::prelude::NodeGraphMessageHandler, node_graph_executor::NodeGraphExecutor};
pub struct PropertiesPanelMessageHandlerData<'a> {
pub document_name: &'a str,
pub artwork_document: &'a DocumentLegacy,
pub artboard_document: &'a DocumentLegacy,
pub selected_layers: &'a mut dyn Iterator<Item = &'a [LayerId]>,

View File

@@ -1,5 +1,3 @@
use std::sync::Arc;
use super::utility_types::PersistentData;
use crate::application::generate_uuid;
use crate::consts::{DEFAULT_DOCUMENT_NAME, GRAPHITE_DOCUMENT_VERSION};
@@ -19,6 +17,9 @@ use graph_craft::document::value::TaggedValue;
use graph_craft::document::{NodeId, NodeInput};
use graphene_core::text::Font;
use glam::DAffine2;
use std::sync::Arc;
#[derive(Debug, Default)]
pub struct PortfolioMessageHandler {
menu_bar_message_handler: MenuBarMessageHandler,
@@ -114,12 +115,13 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
// Actually delete the document (delay to delete document is required to let the document and properties panel messages above get processed)
responses.add(PortfolioMessage::DeleteDocument { document_id });
responses.add(FrontendMessage::TriggerIndexedDbRemoveDocument { document_id });
// Send the new list of document tab names
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(FrontendMessage::TriggerIndexedDbRemoveDocument { document_id });
responses.add(DocumentMessage::RenderDocument);
responses.add(DocumentMessage::DocumentStructureChanged);
if let Some(document) = self.active_document() {
for layer in document.layer_metadata.keys() {
responses.add(DocumentMessage::LayerChanged { affected_layer_path: layer.clone() });
@@ -246,7 +248,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
}
PortfolioMessage::ImaginatePreferences => self.executor.update_imaginate_preferences(preferences.get_imaginate_preferences()),
PortfolioMessage::ImaginateServerHostname => {
info!("setting imaginate persistent data");
debug!("setting imaginate persistent data");
self.persistent_data.imaginate.set_host_name(&preferences.imaginate_server_hostname);
}
PortfolioMessage::Import => {
@@ -466,6 +468,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(PortfolioMessage::UpdateDocumentWidgets);
responses.add(NavigationMessage::TranslateCanvas { delta: (0., 0.).into() });
responses.add(NodeGraphMessage::RunDocumentGraph);
}
PortfolioMessage::SetActiveDocument { document_id } => {
self.active_document_id = Some(document_id);
@@ -664,7 +667,8 @@ impl PortfolioMessageHandler {
}
pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) {
self.executor.poll_node_graph_evaluation(responses).unwrap_or_else(|e| {
let transform = self.active_document().map(|document| document.document_legacy.root.transform).unwrap_or(DAffine2::IDENTITY);
self.executor.poll_node_graph_evaluation(transform, responses).unwrap_or_else(|e| {
log::error!("Error while evaluating node graph: {}", e);
});
}

View File

@@ -13,7 +13,7 @@ use crate::messages::tool::utility_types::{HintData, HintGroup, HintInfo};
use document_legacy::intersection::Quad;
use document_legacy::LayerId;
use glam::{DVec2, Vec2Swizzles};
use glam::{DVec2, IVec2, Vec2Swizzles};
use serde::{Deserialize, Serialize};
#[derive(Default)]
@@ -133,10 +133,6 @@ impl Fsm for ArtboardToolFsmState {
tool_data.bounding_box_overlays = Some(bounding_box_overlays);
responses.add(OverlaysMessage::Rerender);
responses.add(PropertiesPanelMessage::SetActiveLayers {
paths: vec![vec![tool_data.selected_artboard.unwrap()]],
document: TargetDocument::Artboard,
});
}
_ => {}
};
@@ -196,11 +192,6 @@ impl Fsm for ArtboardToolFsmState {
.start_snap(document, input, document.bounding_boxes(None, Some(intersection[0]), render_data), true, true);
tool_data.snap_manager.add_all_document_handles(document, input, &[], &[], &[]);
responses.add(PropertiesPanelMessage::SetActiveLayers {
paths: vec![intersection.clone()],
document: TargetDocument::Artboard,
});
ArtboardToolFsmState::Dragging
} else {
tool_data.selected_artboard = None;
@@ -226,6 +217,11 @@ impl Fsm for ArtboardToolFsmState {
position: position.round().into(),
size: size.round().into(),
});
responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap(),
location: position.round().as_ivec2(),
dimensions: size.round().as_ivec2(),
});
responses.add(BroadcastEvent::DocumentIsDirty);
}
@@ -251,6 +247,11 @@ impl Fsm for ArtboardToolFsmState {
position: position.round().into(),
size: size.round().into(),
});
responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap(),
location: position.round().as_ivec2(),
dimensions: size.round().as_ivec2(),
});
responses.add(BroadcastEvent::DocumentIsDirty);
@@ -285,6 +286,11 @@ impl Fsm for ArtboardToolFsmState {
position: start.round().into(),
size: size.round().into(),
});
responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap(),
location: start.round().as_ivec2(),
dimensions: size.round().as_ivec2(),
});
} else {
let id = generate_uuid();
tool_data.selected_artboard = Some(id);
@@ -297,14 +303,20 @@ impl Fsm for ArtboardToolFsmState {
position: start.round().into(),
size: (1., 1.),
});
responses.add(GraphOperationMessage::NewArtboard {
id,
artboard: graphene_core::Artboard {
graphic_group: graphene_core::GraphicGroup::EMPTY,
location: start.round().as_ivec2(),
dimensions: IVec2::splat(1),
background: graphene_core::Color::WHITE,
clip: false,
},
})
}
// Have to put message here instead of when Artboard is created
// This might result in a few more calls but it is not reliant on the order of messages
responses.add(PropertiesPanelMessage::SetActiveLayers {
paths: vec![vec![tool_data.selected_artboard.unwrap()]],
document: TargetDocument::Artboard,
});
responses.add(BroadcastEvent::DocumentIsDirty);
@@ -352,6 +364,8 @@ impl Fsm for ArtboardToolFsmState {
(_, ArtboardToolMessage::DeleteSelected) => {
if let Some(artboard) = tool_data.selected_artboard.take() {
responses.add(ArtboardMessage::DeleteArtboard { artboard });
responses.add(GraphOperationMessage::DeleteArtboard { id: artboard });
responses.add(BroadcastEvent::DocumentIsDirty);
}
ArtboardToolFsmState::Ready
@@ -363,6 +377,11 @@ impl Fsm for ArtboardToolFsmState {
position: (bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y),
size: (bounds.bounds[1] - bounds.bounds[0]).round().into(),
});
responses.add(GraphOperationMessage::ResizeArtboard {
id: tool_data.selected_artboard.unwrap(),
location: DVec2::new(bounds.bounds[0].x + delta_x, bounds.bounds[0].y + delta_y).round().as_ivec2(),
dimensions: (bounds.bounds[1] - bounds.bounds[0]).round().as_ivec2(),
});
}
ArtboardToolFsmState::Ready

View File

@@ -214,7 +214,7 @@ impl NodeRuntime {
graphic_group.render_svg(&mut render, &render_params);
let [min, max] = bounds.unwrap_or_default();
render.format_svg(min, max);
info!("SVG {}", render.svg);
debug!("SVG {}", render.svg);
if let (Some(layer_id), Some(node_id)) = (layer_path.last().copied(), node_path.get(node_path.len() - 2).copied()) {
let old_thumbnail = self.thumbnails.entry(layer_id).or_default().entry(node_id).or_default();
@@ -283,16 +283,16 @@ struct ExecutionContext {
impl Default for NodeGraphExecutor {
fn default() -> Self {
let (request_sender, request_reciever) = std::sync::mpsc::channel();
let (response_sender, response_reciever) = std::sync::mpsc::channel();
let (request_sender, request_receiver) = std::sync::mpsc::channel();
let (response_sender, response_receiver) = std::sync::mpsc::channel();
NODE_RUNTIME.with(|runtime| {
runtime.borrow_mut().replace(NodeRuntime::new(request_reciever, response_sender));
runtime.borrow_mut().replace(NodeRuntime::new(request_receiver, response_sender));
});
Self {
futures: Default::default(),
sender: request_sender,
receiver: response_reciever,
receiver: response_receiver,
last_output_type: Default::default(),
thumbnails: Default::default(),
}
@@ -423,7 +423,7 @@ impl NodeGraphExecutor {
Ok(())
}
pub fn poll_node_graph_evaluation(&mut self, responses: &mut VecDeque<Message>) -> Result<(), String> {
pub fn poll_node_graph_evaluation(&mut self, transform: DAffine2, responses: &mut VecDeque<Message>) -> Result<(), String> {
let results = self.receiver.try_iter().collect::<Vec<_>>();
for response in results {
match response {
@@ -437,7 +437,7 @@ impl NodeGraphExecutor {
let node_graph_output = result.map_err(|e| format!("Node graph evaluation failed: {:?}", e))?;
let execution_context = self.futures.remove(&generation_id).ok_or_else(|| "Invalid generation ID".to_string())?;
responses.extend(updates);
self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), responses, execution_context.document_id)?;
self.process_node_graph_output(node_graph_output, execution_context.layer_path.clone(), transform, responses, execution_context.document_id)?;
responses.add(DocumentMessage::LayerChanged {
affected_layer_path: execution_context.layer_path,
});
@@ -456,7 +456,7 @@ impl NodeGraphExecutor {
Ok(())
}
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, layer_path: Vec<LayerId>, responses: &mut VecDeque<Message>, document_id: u64) -> Result<(), String> {
fn process_node_graph_output(&mut self, node_graph_output: TaggedValue, layer_path: Vec<LayerId>, transform: DAffine2, responses: &mut VecDeque<Message>, document_id: u64) -> Result<(), String> {
self.last_output_type.insert(layer_path.clone(), Some(node_graph_output.ty()));
match node_graph_output {
TaggedValue::VectorData(vector_data) => {
@@ -489,11 +489,30 @@ impl NodeGraphExecutor {
}
}
TaggedValue::Artboard(artboard) => {
info!("{artboard:#?}");
debug!("{artboard:#?}");
return Err("Artboard (see console)".to_string());
}
TaggedValue::GraphicGroup(graphic_group) => {
info!("{graphic_group:#?}");
debug!("{graphic_group:#?}");
use graphene_core::renderer::{format_transform_matrix, GraphicElementRendered, RenderParams, SvgRender};
// Setup rendering
let mut render = SvgRender::new();
let render_params = RenderParams::new(ViewMode::Normal, None, false);
// Render svg
graphic_group.render_svg(&mut render, &render_params);
// Conctenate the defs and the svg into one string
let mut svg = "<defs>".to_string();
svg.push_str(&render.svg_defs);
svg.push_str("</defs>");
use std::fmt::Write;
write!(svg, "{}", render.svg).unwrap();
// Send to frontend
responses.add(FrontendMessage::UpdateDocumentNodeRender { svg });
return Err("Graphic group (see console)".to_string());
}
_ => {