Update graph UI and improve simplicity and robustness of sending graph and layer panel updates (#1564)

* WIP

* Fix loading the structure of layers

* Fix broken indents

* Remove debugging stuff

* Fix displaying errors and node graph UI fixes/improvements

* Fix compilation failure

---------

Co-authored-by: 0hypercube <0hypercube@gmail.com>
This commit is contained in:
Keavon Chambers
2024-01-13 04:15:36 -08:00
committed by GitHub
parent 83116aa744
commit aab0fcf84c
33 changed files with 836 additions and 813 deletions

View File

@@ -32,16 +32,15 @@ pub struct DispatcherMessageHandlers {
/// The last occurrence of the message in the message queue is sufficient to ensure correct behavior.
/// In addition, these messages do not change any state in the backend (aside from caches).
const SIDE_EFFECT_FREE_MESSAGES: &[MessageDiscriminant] = &[
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::RenderDocument)),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::NodeGraph(NodeGraphMessageDiscriminant::SendGraph))),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::PropertiesPanel(
PropertiesPanelMessageDiscriminant::Refresh,
))),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::DocumentStructureChanged)),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::Overlays(OverlaysMessageDiscriminant::Draw))),
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::DocumentIsDirty)),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::UpdateDocumentLayerStructure),
MessageDiscriminant::Frontend(FrontendMessageDiscriminant::TriggerFontLoad),
MessageDiscriminant::Broadcast(BroadcastMessageDiscriminant::TriggerEvent(BroadcastEventDiscriminant::DocumentIsDirty)),
MessageDiscriminant::Portfolio(PortfolioMessageDiscriminant::Document(DocumentMessageDiscriminant::Overlays(OverlaysMessageDiscriminant::Draw))),
];
impl Dispatcher {
@@ -119,7 +118,7 @@ impl Dispatcher {
}
Frontend(message) => {
// Handle these messages immediately by returning early
if let FrontendMessage::UpdateImageData { .. } | FrontendMessage::TriggerFontLoad { .. } | FrontendMessage::TriggerRefreshBoundsOfViewports = message {
if let FrontendMessage::TriggerFontLoad { .. } | FrontendMessage::TriggerRefreshBoundsOfViewports = message {
self.responses.push(message);
self.cleanup_queues(false);
@@ -307,8 +306,8 @@ mod test {
});
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
let layers_before_copy = document_before_copy.metadata.all_layers().collect::<Vec<_>>();
let layers_after_copy = document_after_copy.metadata.all_layers().collect::<Vec<_>>();
let layers_before_copy = document_before_copy.document_metadata.all_layers().collect::<Vec<_>>();
let layers_after_copy = document_after_copy.document_metadata.all_layers().collect::<Vec<_>>();
assert_eq!(layers_before_copy.len(), 3);
assert_eq!(layers_after_copy.len(), 4);
@@ -330,7 +329,7 @@ mod test {
let mut editor = create_editor_with_three_layers();
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
let shape_id = document_before_copy.metadata.all_layers().nth(1).unwrap();
let shape_id = document_before_copy.document_metadata.all_layers().nth(1).unwrap();
editor.handle_message(NodeGraphMessage::SelectedNodesSet { nodes: vec![shape_id.to_node()] });
editor.handle_message(PortfolioMessage::Copy { clipboard: Clipboard::Internal });
@@ -342,8 +341,8 @@ mod test {
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
let layers_before_copy = document_before_copy.metadata.all_layers().collect::<Vec<_>>();
let layers_after_copy = document_after_copy.metadata.all_layers().collect::<Vec<_>>();
let layers_before_copy = document_before_copy.document_metadata.all_layers().collect::<Vec<_>>();
let layers_after_copy = document_after_copy.document_metadata.all_layers().collect::<Vec<_>>();
assert_eq!(layers_before_copy.len(), 3);
assert_eq!(layers_after_copy.len(), 4);
@@ -385,8 +384,8 @@ mod test {
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
let layers_before_copy = document_before_copy.metadata.all_layers().collect::<Vec<_>>();
let layers_after_copy = document_after_copy.metadata.all_layers().collect::<Vec<_>>();
let layers_before_copy = document_before_copy.document_metadata.all_layers().collect::<Vec<_>>();
let layers_after_copy = document_after_copy.document_metadata.all_layers().collect::<Vec<_>>();
let [original_folder, original_freehand, original_line, original_ellipse, original_polygon, original_rect] = layers_before_copy[..] else {
panic!("Layers before incorrect");
};
@@ -414,7 +413,7 @@ mod test {
let mut editor = create_editor_with_three_layers();
let document_before_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
let mut layers = document_before_copy.metadata.all_layers();
let mut layers = document_before_copy.document_metadata.all_layers();
let rect_id = layers.next().expect("rectangle");
let shape_id = layers.next().expect("shape");
let ellipse_id = layers.next().expect("ellipse");
@@ -438,8 +437,8 @@ mod test {
let document_after_copy = editor.dispatcher.message_handlers.portfolio_message_handler.active_document().unwrap().clone();
let layers_before_copy = document_before_copy.metadata.all_layers().collect::<Vec<_>>();
let layers_after_copy = document_after_copy.metadata.all_layers().collect::<Vec<_>>();
let layers_before_copy = document_before_copy.document_metadata.all_layers().collect::<Vec<_>>();
let layers_after_copy = document_after_copy.document_metadata.all_layers().collect::<Vec<_>>();
assert_eq!(layers_before_copy.len(), 3);
assert_eq!(layers_after_copy.len(), 6);

View File

@@ -75,7 +75,7 @@ impl MessageHandler<DialogMessage, DialogData<'_>> for DialogMessageHandler {
if let Some(document) = portfolio.active_document() {
let mut index = 0;
let artboards = document
.metadata
.document_metadata
.all_layers()
.filter(|&layer| is_layer_fed_by_node_of_name(layer, &document.network, "Artboard"))
.map(|layer| {

View File

@@ -1,4 +1,4 @@
use super::utility_types::{FrontendDocumentDetails, FrontendImageData, MouseCursorIcon};
use super::utility_types::{FrontendDocumentDetails, MouseCursorIcon};
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::node_graph::{FrontendNode, FrontendNodeLink, FrontendNodeType};
use crate::messages::portfolio::document::utility_types::layer_panel::{JsRawBuffer, LayerPanelEntry, RawBuffer};
@@ -172,12 +172,6 @@ pub enum FrontendMessage {
#[serde(rename = "setColorChoice")]
set_color_choice: Option<String>,
},
UpdateImageData {
#[serde(rename = "documentId")]
document_id: DocumentId,
#[serde(rename = "imageData")]
image_data: Vec<FrontendImageData>,
},
UpdateInputHints {
#[serde(rename = "hintData")]
hint_data: HintData,

View File

@@ -13,13 +13,6 @@ pub struct FrontendDocumentDetails {
pub id: DocumentId,
}
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, specta::Type)]
pub struct FrontendImageData {
pub mime: String,
#[serde(skip)]
pub image_data: std::sync::Arc<Vec<u8>>,
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize, specta::Type)]
pub enum MouseCursorIcon {
#[default]

View File

@@ -87,7 +87,6 @@ pub enum DocumentMessage {
RenameDocument {
new_name: String,
},
RenderDocument,
RenderRulers,
RenderScrollbars,
SaveDocument,

View File

@@ -85,7 +85,7 @@ pub struct DocumentMessageHandler {
#[serde(skip)]
layer_range_selection_reference: Option<LayerNodeIdentifier>,
#[serde(skip)]
pub metadata: DocumentMetadata,
pub document_metadata: DocumentMetadata,
}
impl Default for DocumentMessageHandler {
@@ -121,7 +121,7 @@ impl Default for DocumentMessageHandler {
graph_view_overlay_open: false,
snapping_state: SnappingState::default(),
layer_range_selection_reference: None,
metadata: Default::default(),
document_metadata: Default::default(),
}
}
}
@@ -245,7 +245,13 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
self.navigation_handler.process_message(
message,
responses,
(&self.metadata, document_bounds, ipp, self.selected_visible_layers_bounding_box_viewport(), &mut self.navigation),
(
&self.document_metadata,
document_bounds,
ipp,
self.selected_visible_layers_bounding_box_viewport(),
&mut self.navigation,
),
);
}
#[remain::unsorted]
@@ -259,7 +265,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
executor,
document_name: self.name.as_str(),
document_network: &self.network,
document_metadata: &mut self.metadata,
document_metadata: &mut self.document_metadata,
};
self.properties_panel_message_handler
.process_message(message, responses, (persistent_data, properties_panel_message_handler_data));
@@ -271,7 +277,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses,
NodeGraphHandlerData {
document_network: &mut self.network,
document_metadata: &mut self.metadata,
document_metadata: &mut self.document_metadata,
document_id,
document_name: self.name.as_str(),
collapsed: &mut self.collapsed,
@@ -281,13 +287,13 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
);
}
#[remain::unsorted]
GraphOperation(message) => GraphOperationMessageHandler.process_message(message, responses, (&mut self.network, &mut self.metadata, &mut self.collapsed, &mut self.node_graph_handler)),
GraphOperation(message) => GraphOperationMessageHandler.process_message(message, responses, (&mut self.network, &mut self.document_metadata, &mut self.collapsed, &mut self.node_graph_handler)),
// Messages
AbortTransaction => {
if !self.undo_in_progress {
self.undo(responses);
responses.extend([RenderDocument.into(), DocumentStructureChanged.into()]);
responses.add(OverlaysMessage::Draw);
}
}
AlignSelectedLayers { axis, aggregate } => {
@@ -375,12 +381,9 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
DocumentStructureChanged => {
self.update_layers_panel_options_bar_widgets(responses);
self.document_metadata.load_structure(&self.network);
let data_buffer: RawBuffer = self.serialize_root();
responses.add(FrontendMessage::UpdateDocumentLayerStructure { data_buffer });
if self.graph_view_overlay_open {
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
}
}
DuplicateSelectedLayers => {
// TODO: Reimplement selected layer duplication
@@ -414,7 +417,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
self.graph_view_overlay_open = open;
if open {
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
responses.add(NodeGraphMessage::SendGraph);
}
responses.add(FrontendMessage::TriggerGraphViewOverlay { open });
}
@@ -503,7 +506,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
});
}
// Nudge resize
else if let Some([existing_top_left, existing_bottom_right]) = self.metadata.bounding_box_document(layer) {
else if let Some([existing_top_left, existing_bottom_right]) = self.document_metadata.bounding_box_document(layer) {
let size = existing_bottom_right - existing_top_left;
let new_size = size + if opposite_corner { -delta } else { delta };
let enlargement_factor = new_size / size;
@@ -572,19 +575,15 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
}
Redo => {
responses.add(SelectToolMessage::Abort);
responses.add(DocumentHistoryForward);
responses.add(DocumentMessage::DocumentHistoryForward);
responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(RenderDocument);
responses.add(DocumentStructureChanged);
responses.add(OverlaysMessage::Draw);
}
RenameDocument { new_name } => {
self.name = new_name;
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
}
RenderDocument => {
responses.add(OverlaysMessage::Draw);
}
RenderRulers => {
let document_transform_scale = self.navigation_handler.snapped_scale(self.navigation.zoom);
@@ -753,11 +752,10 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
Undo => {
self.undo_in_progress = true;
responses.add(BroadcastEvent::ToolAbort);
responses.add(DocumentHistoryBackward);
responses.add(DocumentMessage::DocumentHistoryBackward);
responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(RenderDocument);
responses.add(DocumentStructureChanged);
responses.add(UndoFinished);
responses.add(OverlaysMessage::Draw);
responses.add(DocumentMessage::UndoFinished);
}
UndoFinished => self.undo_in_progress = false,
UngroupSelectedLayers => {
@@ -786,7 +784,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
responses.add(DocumentMessage::CommitTransaction);
}
UpdateDocumentTransform { transform } => {
self.metadata.document_to_viewport = transform;
self.document_metadata.document_to_viewport = transform;
responses.add(DocumentMessage::RenderRulers);
responses.add(DocumentMessage::RenderScrollbars);
responses.add(NodeGraphMessage::RunDocumentGraph);
@@ -813,35 +811,43 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
impl DocumentMessageHandler {
pub fn layer_visible(&self, layer: LayerNodeIdentifier) -> bool {
!layer.ancestors(&self.metadata).any(|layer| self.network.disabled.contains(&layer.to_node()))
!layer.ancestors(&self.document_metadata).any(|layer| self.network.disabled.contains(&layer.to_node()))
}
pub fn selected_visible_layers(&self) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
self.metadata.selected_layers().filter(|&layer| self.layer_visible(layer))
self.document_metadata.selected_layers().filter(|&layer| self.layer_visible(layer))
}
/// Runs an intersection test with all layers and a viewport space quad
pub fn intersect_quad<'a>(&'a self, viewport_quad: graphene_core::renderer::Quad, network: &'a NodeNetwork) -> impl Iterator<Item = LayerNodeIdentifier> + 'a {
let document_quad = self.metadata.document_to_viewport.inverse() * viewport_quad;
self.metadata
let document_quad = self.document_metadata.document_to_viewport.inverse() * viewport_quad;
self.document_metadata
.root()
.decendants(&self.metadata)
.decendants(&self.document_metadata)
.filter(|&layer| self.layer_visible(layer))
.filter(|&layer| !is_artboard(layer, network))
.filter_map(|layer| self.metadata.click_target(layer).map(|targets| (layer, targets)))
.filter(move |(layer, target)| target.iter().any(move |target| target.intersect_rectangle(document_quad, self.metadata.transform_to_document(*layer))))
.filter_map(|layer| self.document_metadata.click_target(layer).map(|targets| (layer, targets)))
.filter(move |(layer, target)| {
target
.iter()
.any(move |target| target.intersect_rectangle(document_quad, self.document_metadata.transform_to_document(*layer)))
})
.map(|(layer, _)| layer)
}
/// Find all of the layers that were clicked on from a viewport space location
pub fn click_xray(&self, viewport_location: DVec2) -> impl Iterator<Item = LayerNodeIdentifier> + '_ {
let point = self.metadata.document_to_viewport.inverse().transform_point2(viewport_location);
self.metadata
let point = self.document_metadata.document_to_viewport.inverse().transform_point2(viewport_location);
self.document_metadata
.root()
.decendants(&self.metadata)
.decendants(&self.document_metadata)
.filter(|&layer| self.layer_visible(layer))
.filter_map(|layer| self.metadata.click_target(layer).map(|targets| (layer, targets)))
.filter(move |(layer, target)| target.iter().any(|target: &ClickTarget| target.intersect_point(point, self.metadata.transform_to_document(*layer))))
.filter_map(|layer| self.document_metadata.click_target(layer).map(|targets| (layer, targets)))
.filter(move |(layer, target)| {
target
.iter()
.any(|target: &ClickTarget| target.intersect_point(point, self.document_metadata.transform_to_document(*layer)))
})
.map(|(layer, _)| layer)
}
@@ -853,7 +859,7 @@ impl DocumentMessageHandler {
/// Get the combined bounding box of the click targets of the selected visible layers in viewport space
pub fn selected_visible_layers_bounding_box_viewport(&self) -> Option<[DVec2; 2]> {
self.selected_visible_layers()
.filter_map(|layer| self.metadata.bounding_box_viewport(layer))
.filter_map(|layer| self.document_metadata.bounding_box_viewport(layer))
.reduce(graphene_core::renderer::Quad::combine_bounds)
}
@@ -862,7 +868,7 @@ impl DocumentMessageHandler {
}
pub fn metadata(&self) -> &DocumentMetadata {
&self.metadata
&self.document_metadata
}
pub fn serialize_document(&self) -> String {
@@ -878,7 +884,7 @@ impl DocumentMessageHandler {
pub fn with_name(name: String, ipp: &InputPreprocessorMessageHandler, responses: &mut VecDeque<Message>) -> Self {
let mut document = Self { name, ..Self::default() };
let transform = document.navigation_handler.calculate_offset_transform(ipp.viewport_bounds.size() / 2., DVec2::ZERO, 0., 1.);
document.metadata.document_to_viewport = transform;
document.document_metadata.document_to_viewport = transform;
responses.add(DocumentMessage::UpdateDocumentTransform { transform });
document
@@ -897,23 +903,24 @@ impl DocumentMessageHandler {
std::iter::empty()
}
fn serialize_structure(&self, folder: LayerNodeIdentifier, structure: &mut Vec<LayerNodeIdentifier>, data: &mut Vec<LayerNodeIdentifier>, path: &mut Vec<LayerNodeIdentifier>) {
/// Called recursively by the entry function [`serialize_root`].
fn serialize_structure(&self, folder: LayerNodeIdentifier, structure_section: &mut Vec<u64>, data_section: &mut Vec<u64>, path: &mut Vec<LayerNodeIdentifier>) {
let mut space = 0;
for layer_node in folder.children(self.metadata()) {
data.push(layer_node);
data_section.push(layer_node.to_node().0);
space += 1;
if layer_node.has_children(self.metadata()) && !self.collapsed.contains(&layer_node) {
path.push(layer_node);
// TODO: Skip if folder is not expanded.
structure.push(LayerNodeIdentifier::new_unchecked(NodeId(space)));
self.serialize_structure(layer_node, structure, data, path);
structure_section.push(space);
self.serialize_structure(layer_node, structure_section, data_section, path);
space = 0;
path.pop();
}
}
structure.push(LayerNodeIdentifier::new_unchecked(NodeId(space | 1 << 63)));
structure_section.push(space | 1 << 63);
}
/// Serializes the layer structure into a condensed 1D structure.
@@ -921,17 +928,18 @@ impl DocumentMessageHandler {
/// # Format
/// It is a string of numbers broken into three sections:
///
/// | Data | Description | Length |
/// |--------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------|------------------|
/// | `4,` `2, 1, -2, -0,` `16533113728871998040,3427872634365736244,18115028555707261608,15878401910454357952,449479075714955186` | Encoded example data | |
/// | `L` = `4` = `structure.len()` | `L`, the length of the **Structure** section | First value |
/// | **Structure** section = `2, 1, -2, -0` | The **Structure** section | Next `L` values |
/// | **Data** section = `16533113728871998040, 3427872634365736244, 18115028555707261608, 15878401910454357952, 449479075714955186` | The **Data** section (layer IDs) | Remaining values |
/// | Data | Description | Length |
/// |------------------------------------------------------------------------------------------------------------------------------ |---------------------------------------------------------------|------------------|
/// | `4,` `2, 1, -2, -0,` `16533113728871998040,3427872634365736244,18115028555707261608,15878401910454357952,449479075714955186` | Encoded example data | |
/// | _____________________________________________________________________________________________________________________________ | _____________________________________________________________ | ________________ |
/// | **Length** section: `4` | Length of the **Structure** section (`L` = `structure.len()`) | First value |
/// | **Structure** section: `2, 1, -2, -0` | The **Structure** section | Next `L` values |
/// | **Data** section: `16533113728871998040, 3427872634365736244, 18115028555707261608, 15878401910454357952, 449479075714955186` | The **Data** section (layer IDs) | Remaining values |
///
/// The data section lists the layer IDs for all folders/layers in the tree as read from top to bottom.
/// The structure section lists signed numbers. The sign indicates a folder indentation change (`+` is down a level, `-` is up a level).
/// The numbers in the structure block encode the indentation. For example:
/// - `2` means read two element from the data section, then place a `[`.
/// - `2` means read two elements from the data section, then place a `[`.
/// - `-x` means read `x` elements from the data section and then insert a `]`.
///
/// ```text
@@ -949,14 +957,16 @@ impl DocumentMessageHandler {
/// [3427872634365736244,18115028555707261608,449479075714955186]
/// ```
pub fn serialize_root(&self) -> RawBuffer {
let mut structure = vec![LayerNodeIdentifier::ROOT];
let mut data = Vec::new();
self.serialize_structure(self.metadata().root(), &mut structure, &mut data, &mut vec![]);
let mut structure_section = vec![LayerNodeIdentifier::ROOT.to_node().0];
let mut data_section = Vec::new();
self.serialize_structure(self.metadata().root(), &mut structure_section, &mut data_section, &mut vec![]);
structure[0] = LayerNodeIdentifier::new_unchecked(NodeId(structure.len() as u64 - 1));
structure.extend(data);
// Remove the ROOT element. Prepend `L`, the length (excluding the ROOT) of the structure section (which happens to be where the ROOT element was).
structure_section[0] = structure_section.len() as u64 - 1;
// Append the data section to the end.
structure_section.extend(data_section);
structure.iter().map(|id| id.to_node().0).collect::<Vec<_>>().as_slice().into()
structure_section.as_slice().into()
}
/// Places a document into the history system
@@ -1000,9 +1010,6 @@ impl DocumentMessageHandler {
if self.document_redo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {
self.document_redo_history.pop_front();
}
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
}
pub fn redo(&mut self, responses: &mut VecDeque<Message>) {
@@ -1018,9 +1025,6 @@ impl DocumentMessageHandler {
if self.document_undo_history.len() > crate::consts::MAX_UNDO_HISTORY_LEN {
self.document_undo_history.pop_front();
}
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
}
pub fn current_hash(&self) -> Option<u64> {
@@ -1410,7 +1414,6 @@ impl DocumentMessageHandler {
Redo,
SelectAllLayers,
DeselectAllLayers,
RenderDocument,
SaveDocument,
SetSnapping,
DebugPrintDocument,

View File

@@ -83,8 +83,8 @@ impl<'a> ModifyInputsContext<'a> {
pub fn insert_node_before(&mut self, new_id: NodeId, node_id: NodeId, input_index: usize, mut document_node: DocumentNode, offset: IVec2) -> Option<NodeId> {
assert!(!self.document_network.nodes.contains_key(&new_id), "Creating already existing node");
let post_node = self.document_network.nodes.get_mut(&node_id)?;
let post_node = self.document_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.document_network.nodes.insert(new_id, document_node);
@@ -153,7 +153,6 @@ impl<'a> ModifyInputsContext<'a> {
};
let new_child = LayerNodeIdentifier::new(new_id, self.document_network);
parent.push_front_child(self.document_metadata, new_child);
self.responses.add(DocumentMessage::DocumentStructureChanged);
}
new_id
@@ -181,7 +180,7 @@ impl<'a> ModifyInputsContext<'a> {
],
Default::default(),
);
self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
self.responses.add(NodeGraphMessage::RunDocumentGraph);
self.insert_node_before(NodeId(generate_uuid()), layer, 0, artboard_node, IVec2::new(-8, 0))
}
@@ -202,7 +201,7 @@ impl<'a> ModifyInputsContext<'a> {
self.insert_node_before(transform_id, fill_id, 0, transform, IVec2::new(-8, 0));
let shape_id = NodeId(generate_uuid());
self.insert_node_before(shape_id, transform_id, 0, shape, IVec2::new(-8, 0));
self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
self.responses.add(NodeGraphMessage::RunDocumentGraph);
}
fn insert_text(&mut self, text: String, font: Font, size: f32, layer: NodeId) {
@@ -227,7 +226,7 @@ impl<'a> ModifyInputsContext<'a> {
self.insert_node_before(transform_id, fill_id, 0, transform, IVec2::new(-8, 0));
let text_id = NodeId(generate_uuid());
self.insert_node_before(text_id, transform_id, 0, text, IVec2::new(-8, 0));
self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
self.responses.add(NodeGraphMessage::RunDocumentGraph);
}
fn insert_image_data(&mut self, image_frame: ImageFrame<Color>, layer: NodeId) {
@@ -243,7 +242,7 @@ impl<'a> ModifyInputsContext<'a> {
let image_id = NodeId(generate_uuid());
self.insert_node_before(image_id, transform_id, 0, image, IVec2::new(-8, 0));
self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
self.responses.add(NodeGraphMessage::RunDocumentGraph);
}
fn shift_upstream(&mut self, node_id: NodeId, shift: IVec2) {
@@ -317,11 +316,6 @@ impl<'a> ModifyInputsContext<'a> {
if !skip_rerender {
self.responses.add(NodeGraphMessage::RunDocumentGraph);
} else {
// Code was removed from here which cleared the frame
}
if existing_node_id.is_none() {
self.responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
}
}
@@ -567,8 +561,7 @@ impl<'a> ModifyInputsContext<'a> {
self.document_metadata.retain_selected_nodes(|id| !delete_nodes.contains(id));
self.responses.add(BroadcastEvent::SelectionChanged);
self.responses.add(DocumentMessage::DocumentStructureChanged);
self.responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
self.responses.add(NodeGraphMessage::RunDocumentGraph);
}
}
@@ -702,7 +695,7 @@ impl MessageHandler<GraphOperationMessage, (&mut NodeNetwork, &mut DocumentMetad
}
}
modify_inputs.responses.add(NodeGraphMessage::SendGraph { should_rerender: true });
modify_inputs.responses.add(NodeGraphMessage::RunDocumentGraph);
}
load_network_structure(document_network, document_metadata, collapsed);

View File

@@ -35,7 +35,7 @@ pub enum NodeGraphMessage {
node_id: NodeId,
input_index: usize,
},
DoubleClickNode {
EnterNestedNetwork {
node: NodeId,
},
DuplicateSelectedNodes,
@@ -68,9 +68,7 @@ pub enum NodeGraphMessage {
SelectedNodesSet {
nodes: Vec<NodeId>,
},
SendGraph {
should_rerender: bool,
},
SendGraph,
SetInputValue {
node_id: NodeId,
input_index: usize,
@@ -86,6 +84,7 @@ pub enum NodeGraphMessage {
input_index: usize,
value: TaggedValue,
},
/// Move all the downstream nodes to the right in the graph to allow space for a newly inserted node
ShiftNode {
node_id: NodeId,
},

View File

@@ -4,7 +4,9 @@ use crate::application::generate_uuid;
use crate::messages::input_mapper::utility_types::macros::action_keys;
use crate::messages::layout::utility_types::widget_prelude::*;
use crate::messages::portfolio::document::utility_types::document_metadata::{DocumentMetadata, LayerNodeIdentifier};
use crate::messages::portfolio::document::utility_types::layer_panel::{LayerClassification, LayerPanelEntry};
use crate::messages::prelude::*;
use graph_craft::document::value::TaggedValue;
use graph_craft::document::{DocumentNode, NodeId, NodeInput, NodeNetwork, NodeOutput, Source};
use graph_craft::proto::GraphErrors;
@@ -68,6 +70,7 @@ pub struct FrontendGraphInput {
name: String,
#[serde(rename = "resolvedType")]
resolved_type: Option<String>,
connected: Option<NodeId>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
@@ -77,13 +80,14 @@ pub struct FrontendGraphOutput {
name: String,
#[serde(rename = "resolvedType")]
resolved_type: Option<String>,
connected: Option<NodeId>,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
pub struct FrontendNode {
pub id: graph_craft::document::NodeId,
#[serde(rename = "isLayer")]
pub is_layer: bool,
pub id: graph_craft::document::NodeId,
pub alias: String,
pub name: String,
#[serde(rename = "primaryInput")]
@@ -274,14 +278,9 @@ impl NodeGraphMessageHandler {
}
}
fn send_graph(&self, network: &NodeNetwork, graph_view_overlay_open: bool, responses: &mut VecDeque<Message>) {
responses.add(PropertiesPanelMessage::Refresh);
fn send_graph(&self, network: &NodeNetwork, graph_view_overlay_open: bool, document_metadata: &mut DocumentMetadata, collapsed: &Vec<LayerNodeIdentifier>, responses: &mut VecDeque<Message>) {
document_metadata.load_structure(&network);
if !graph_view_overlay_open {
return;
}
// List of links in format (link_start, link_end, link_end_input_index)
let links = network
.nodes
.iter()
@@ -306,52 +305,111 @@ impl NodeGraphMessageHandler {
})
.collect::<Vec<_>>();
let connected_node_to_output_lookup = links.iter().map(|link| ((link.link_start, link.link_start_output_index), link.link_end)).collect::<HashMap<_, _>>();
let mut nodes = Vec::new();
for (id, node) in &network.nodes {
let node_path = vec![*id];
for (&node_id, node) in &network.nodes {
let node_path = vec![node_id];
// TODO: This should be based on the graph runtime type inference system in order to change the colors of node connectors to match the data type in use
let Some(node_type) = document_node_types::resolve_document_node_type(&node.name) else {
let Some(document_node_definition) = document_node_types::resolve_document_node_type(&node.name) else {
warn!("Node '{}' does not exist in library", node.name);
continue;
};
// Inputs
let mut inputs = node.inputs.iter().zip(node_type.inputs.iter().enumerate().map(|(index, input_type)| {
let index = node.inputs.iter().take(index).filter(|input| input.is_exposed()).count();
FrontendGraphInput {
data_type: input_type.data_type,
name: input_type.name.to_string(),
resolved_type: self.resolved_types.inputs.get(&Source { node: node_path.clone(), index }).map(|input| format!("{input:?}")),
}
}));
let mut inputs = {
let frontend_graph_inputs = document_node_definition.inputs.iter().enumerate().map(|(index, input_type)| {
// Convert the index in all inputs to the index in only the exposed inputs
let index = node.inputs.iter().take(index).filter(|input| input.is_exposed()).count();
FrontendGraphInput {
data_type: input_type.data_type,
name: input_type.name.to_string(),
resolved_type: self.resolved_types.inputs.get(&Source { node: node_path.clone(), index }).map(|input| format!("{input:?}")),
connected: None,
}
});
node.inputs.iter().zip(frontend_graph_inputs).map(|(node_input, mut frontend_graph_input)| {
if let NodeInput::Node { node_id: connected_node_id, .. } = node_input {
frontend_graph_input.connected = Some(*connected_node_id);
}
(node_input, frontend_graph_input)
})
};
let primary_input = inputs.next().filter(|(input, _)| input.is_exposed()).map(|(_, input_type)| input_type);
let exposed_inputs = inputs.filter(|(input, _)| input.is_exposed()).map(|(_, input_type)| input_type).collect();
// Outputs
let mut outputs = node_type.outputs.iter().enumerate().map(|(index, output_type)| FrontendGraphOutput {
let mut outputs = document_node_definition.outputs.iter().enumerate().map(|(index, output_type)| FrontendGraphOutput {
data_type: output_type.data_type,
name: output_type.name.to_string(),
resolved_type: self.resolved_types.outputs.get(&Source { node: node_path.clone(), index }).map(|output| format!("{output:?}")),
connected: connected_node_to_output_lookup.get(&(node_id, index)).copied(),
});
let primary_output = if node.has_primary_output { outputs.next() } else { None };
let primary_output = node.has_primary_output.then(|| outputs.next()).flatten();
let exposed_outputs = outputs.collect::<Vec<_>>();
// Errors
let errors = self.node_graph_errors.iter().find(|error| error.node_path.starts_with(&node_path)).map(|error| error.error.clone());
nodes.push(FrontendNode {
id: node_id,
is_layer: node.is_layer(),
id: *id,
alias: node.alias.clone(),
name: node.name.clone(),
primary_input,
exposed_inputs,
primary_output,
exposed_outputs: outputs.collect::<Vec<_>>(),
exposed_outputs,
position: node.metadata.position.into(),
previewed: network.outputs_contain(*id),
disabled: network.disabled.contains(id),
previewed: network.outputs_contain(node_id),
disabled: network.disabled.contains(&node_id),
errors: errors.map(|e| format!("{e:?}")),
})
});
if node.is_layer() {
let layer = LayerNodeIdentifier::new(node_id, network);
let layer_classification = {
if document_metadata.is_artboard(layer) {
LayerClassification::Artboard
} else if document_metadata.is_folder(layer) {
LayerClassification::Folder
} else {
LayerClassification::Layer
}
// TODO: Maybe switch to this below if perhaps it's simpler?
// if node.is_artboard() {
// LayerClassification::Artboard
// } else if node.is_folder(network) {
// LayerClassification::Folder
// } else {
// LayerClassification::Layer
// }
};
let data = LayerPanelEntry {
id: node_id,
layer_classification,
expanded: layer.has_children(document_metadata) && !collapsed.contains(&layer),
depth: layer.ancestors(document_metadata).count() - 1,
parent_id: layer.parent(document_metadata).map(|parent| parent.to_node()),
// TODO: Remove and take this from the graph data in the frontend similar to thumbnail?
name: network.nodes.get(&node_id).map(|node| node.alias.clone()).unwrap_or_default(),
// TODO: Remove and take this from the graph data in the frontend similar to thumbnail?
tooltip: if cfg!(debug_assertions) { format!("Layer ID: {node_id}") } else { "".into() },
// TODO: Remove and take this from the graph data in the frontend similar to thumbnail?
disabled: network.disabled.contains(&node_id),
};
responses.add(FrontendMessage::UpdateDocumentLayerDetails { data });
}
}
responses.add(FrontendMessage::UpdateNodeGraph { nodes, links });
responses.add(DocumentMessage::DocumentStructureChanged);
if graph_view_overlay_open {
responses.add(FrontendMessage::UpdateNodeGraph { nodes, links });
}
responses.add(PropertiesPanelMessage::Refresh);
}
/// Updates the frontend's selection state in line with the backend
@@ -474,7 +532,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
fn process_message(&mut self, message: NodeGraphMessage, responses: &mut VecDeque<Message>, data: NodeGraphHandlerData<'a>) {
let NodeGraphHandlerData {
document_network,
document_metadata: metadata,
document_metadata,
document_id,
collapsed,
graph_view_overlay_open,
@@ -487,15 +545,14 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
on: BroadcastEvent::SelectionChanged,
send: Box::new(NodeGraphMessage::SelectedNodesUpdated.into()),
});
load_network_structure(document_network, metadata, collapsed);
responses.add(DocumentMessage::DocumentStructureChanged);
load_network_structure(document_network, document_metadata, collapsed);
}
NodeGraphMessage::SelectedNodesUpdated => {
self.update_selection_action_buttons(document_network, metadata, responses);
self.update_selected(document_network, metadata, responses);
if metadata.selected_layers().count() <= 1 {
self.update_selection_action_buttons(document_network, document_metadata, responses);
self.update_selected(document_network, document_metadata, responses);
if document_metadata.selected_layers().count() <= 1 {
responses.add(DocumentMessage::SetRangeSelectionLayer {
new_layer: metadata.selected_layers().next(),
new_layer: document_metadata.selected_layers().next(),
});
}
responses.add(NodeGraphMessage::RunDocumentGraph);
@@ -520,15 +577,15 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
error!("Failed to find actual index of connector index {input_node_connector_index} on node {input_node:#?}");
return;
};
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(DocumentMessage::StartTransaction);
let input = NodeInput::node(output_node, output_node_connector_index);
responses.add(NodeGraphMessage::SetNodeInput { node_id, input_index, input });
let should_rerender = network.connected_to_output(node_id);
responses.add(NodeGraphMessage::SendGraph { should_rerender });
if network.connected_to_output(node_id) {
responses.add(NodeGraphMessage::RunDocumentGraph);
}
}
NodeGraphMessage::Copy => {
let Some(network) = document_network.nested_network(&self.network) else {
@@ -537,7 +594,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
};
// Collect the selected nodes
let new_ids = &metadata.selected_nodes().copied().enumerate().map(|(new, old)| (old, NodeId(new as u64))).collect();
let new_ids = &document_metadata.selected_nodes().copied().enumerate().map(|(new, old)| (old, NodeId(new as u64))).collect();
let copied_nodes: Vec<_> = Self::copy_nodes(network, new_ids).collect();
// Prefix to show that this is nodes
@@ -564,28 +621,24 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
graph_craft::document::DocumentNodeMetadata::position((x, y)),
);
responses.add(NodeGraphMessage::InsertNode { node_id, document_node });
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
}
NodeGraphMessage::Cut => {
responses.add(NodeGraphMessage::Copy);
responses.add(NodeGraphMessage::DeleteSelectedNodes { reconnect: true });
}
NodeGraphMessage::DeleteNode { node_id, reconnect } => {
self.remove_node(document_network, metadata, node_id, responses, reconnect);
self.remove_node(document_network, document_metadata, node_id, responses, reconnect);
}
NodeGraphMessage::DeleteSelectedNodes { reconnect } => {
responses.add(DocumentMessage::StartTransaction);
for node_id in metadata.selected_nodes().copied() {
for node_id in document_metadata.selected_nodes().copied() {
responses.add(NodeGraphMessage::DeleteNode { node_id, reconnect });
}
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
if let Some(network) = document_network.nested_network(&self.network) {
// Only generate node graph if one of the selected nodes is connected to the output
if metadata.selected_nodes().any(|&node_id| network.connected_to_output(node_id)) {
if document_metadata.selected_nodes().any(|&node_id| network.connected_to_output(node_id)) {
responses.add(NodeGraphMessage::RunDocumentGraph);
}
}
@@ -615,34 +668,35 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
}
responses.add(NodeGraphMessage::SetNodeInput { node_id, input_index, input });
let should_rerender = network.connected_to_output(node_id);
responses.add(NodeGraphMessage::SendGraph { should_rerender });
if network.connected_to_output(node_id) {
responses.add(NodeGraphMessage::RunDocumentGraph);
}
}
NodeGraphMessage::DoubleClickNode { node } => {
NodeGraphMessage::EnterNestedNetwork { node } => {
if let Some(network) = document_network.nested_network(&self.network) {
if network.nodes.get(&node).and_then(|node| node.implementation.get_network()).is_some() {
self.network.push(node);
}
}
if let Some(network) = document_network.nested_network(&self.network) {
self.send_graph(network, graph_view_overlay_open, responses);
self.send_graph(network, graph_view_overlay_open, document_metadata, collapsed, responses);
}
self.update_selected(document_network, metadata, responses);
self.update_selected(document_network, document_metadata, responses);
}
NodeGraphMessage::DuplicateSelectedNodes => {
if let Some(network) = document_network.nested_network(&self.network) {
responses.add(DocumentMessage::StartTransaction);
let new_ids = &metadata.selected_nodes().map(|&id| (id, NodeId(generate_uuid()))).collect();
let new_ids = &document_metadata.selected_nodes().map(|&id| (id, NodeId(generate_uuid()))).collect();
metadata.clear_selected_nodes();
document_metadata.clear_selected_nodes();
responses.add(BroadcastEvent::SelectionChanged);
// Copy the selected nodes
let copied_nodes = Self::copy_nodes(network, new_ids).collect::<Vec<_>>();
// Select the new nodes
metadata.add_selected_nodes(copied_nodes.iter().map(|(node_id, _)| *node_id));
document_metadata.add_selected_nodes(copied_nodes.iter().map(|(node_id, _)| *node_id));
responses.add(BroadcastEvent::SelectionChanged);
for (node_id, mut document_node) in copied_nodes {
@@ -653,22 +707,20 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
responses.add(NodeGraphMessage::InsertNode { node_id, document_node });
}
self.send_graph(network, graph_view_overlay_open, responses);
self.update_selected(document_network, metadata, responses);
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
self.update_selected(document_network, document_metadata, responses);
}
}
NodeGraphMessage::ExitNestedNetwork { depth_of_nesting } => {
metadata.clear_selected_nodes();
document_metadata.clear_selected_nodes();
responses.add(BroadcastEvent::SelectionChanged);
for _ in 0..depth_of_nesting {
self.network.pop();
}
if let Some(network) = document_network.nested_network(&self.network) {
self.send_graph(network, graph_view_overlay_open, responses);
self.send_graph(network, graph_view_overlay_open, document_metadata, collapsed, responses);
}
self.update_selected(document_network, metadata, responses);
self.update_selected(document_network, document_metadata, responses);
}
NodeGraphMessage::ExposeInput { node_id, input_index, new_exposed } => {
let Some(network) = document_network.nested_network(&self.network) else {
@@ -696,8 +748,6 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
}
responses.add(NodeGraphMessage::SetNodeInput { node_id, input_index, input });
let should_rerender = network.connected_to_output(node_id);
responses.add(NodeGraphMessage::SendGraph { should_rerender });
responses.add(PropertiesPanelMessage::Refresh);
}
NodeGraphMessage::InsertNode { node_id, document_node } => {
@@ -711,12 +761,12 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
return;
};
for node_id in metadata.selected_nodes() {
for node_id in document_metadata.selected_nodes() {
if let Some(node) = network.nodes.get_mut(node_id) {
node.metadata.position += IVec2::new(displacement_x, displacement_y)
}
}
self.send_graph(network, graph_view_overlay_open, responses);
self.send_graph(network, graph_view_overlay_open, document_metadata, collapsed, responses);
}
NodeGraphMessage::PasteNodes { serialized_nodes } => {
let Some(network) = document_network.nested_network(&self.network) else {
@@ -762,29 +812,26 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
let nodes = new_ids.values().copied().collect();
responses.add(NodeGraphMessage::SelectedNodesSet { nodes });
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
}
NodeGraphMessage::RunDocumentGraph => responses.add(PortfolioMessage::SubmitGraphRender { document_id }),
NodeGraphMessage::RunDocumentGraph => {
responses.add(PortfolioMessage::SubmitGraphRender { document_id });
}
NodeGraphMessage::SelectedNodesAdd { nodes } => {
metadata.add_selected_nodes(nodes);
document_metadata.add_selected_nodes(nodes);
responses.add(BroadcastEvent::SelectionChanged);
}
NodeGraphMessage::SelectedNodesRemove { nodes } => {
metadata.retain_selected_nodes(|node| !nodes.contains(node));
document_metadata.retain_selected_nodes(|node| !nodes.contains(node));
responses.add(BroadcastEvent::SelectionChanged);
}
NodeGraphMessage::SelectedNodesSet { nodes } => {
metadata.set_selected_nodes(nodes);
document_metadata.set_selected_nodes(nodes);
responses.add(BroadcastEvent::SelectionChanged);
responses.add(PropertiesPanelMessage::Refresh);
}
NodeGraphMessage::SendGraph { should_rerender } => {
NodeGraphMessage::SendGraph => {
if let Some(network) = document_network.nested_network(&self.network) {
self.send_graph(network, graph_view_overlay_open, responses);
if should_rerender {
responses.add(NodeGraphMessage::RunDocumentGraph);
}
self.send_graph(network, graph_view_overlay_open, document_metadata, collapsed, responses);
}
}
NodeGraphMessage::SetInputValue { node_id, input_index, value } => {
@@ -811,7 +858,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
let structure_changed = node_input.as_node().is_some() || input.as_node().is_some();
*node_input = input;
if structure_changed {
load_network_structure(document_network, metadata, collapsed);
load_network_structure(document_network, document_metadata, collapsed);
}
}
}
@@ -837,6 +884,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
}
}
}
// Move all the downstream nodes to the right in the graph to allow space for a newly inserted node
NodeGraphMessage::ShiftNode { node_id } => {
let Some(network) = document_network.nested_network_mut(&self.network) else {
warn!("No network");
@@ -883,14 +931,15 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
stack.extend(outwards_links.get(&id).unwrap_or(&Vec::new()).iter().copied())
}
}
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
self.send_graph(network, graph_view_overlay_open, document_metadata, collapsed, responses);
}
NodeGraphMessage::ToggleSelectedHidden => {
if let Some(network) = document_network.nested_network(&self.network) {
responses.add(DocumentMessage::StartTransaction);
let new_hidden = !metadata.selected_nodes().any(|id| network.disabled.contains(id));
for &node_id in metadata.selected_nodes() {
let new_hidden = !document_metadata.selected_nodes().any(|id| network.disabled.contains(id));
for &node_id in document_metadata.selected_nodes() {
responses.add(NodeGraphMessage::SetHidden { node_id, hidden: new_hidden });
}
}
@@ -908,14 +957,13 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
} else if !network.inputs.contains(&node_id) && !network.original_outputs().iter().any(|output| output.node_id == node_id) {
network.disabled.push(node_id);
}
self.send_graph(network, graph_view_overlay_open, responses);
// Only generate node graph if one of the selected nodes is connected to the output
if network.connected_to_output(node_id) {
responses.add(NodeGraphMessage::RunDocumentGraph);
}
}
self.update_selection_action_buttons(document_network, metadata, responses);
self.update_selection_action_buttons(document_network, document_metadata, responses);
}
NodeGraphMessage::SetName { node_id, name } => {
responses.add(DocumentMessage::StartTransaction);
@@ -925,7 +973,8 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
if let Some(network) = document_network.nested_network_mut(&self.network) {
if let Some(node) = network.nodes.get_mut(&node_id) {
node.alias = name;
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
self.send_graph(network, graph_view_overlay_open, document_metadata, collapsed, responses);
}
}
}
@@ -944,37 +993,30 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
} else {
return;
}
self.send_graph(network, graph_view_overlay_open, responses);
}
self.update_selection_action_buttons(document_network, metadata, responses);
self.update_selection_action_buttons(document_network, document_metadata, responses);
responses.add(NodeGraphMessage::RunDocumentGraph);
}
NodeGraphMessage::UpdateNewNodeGraph => {
if let Some(network) = document_network.nested_network(&self.network) {
metadata.clear_selected_nodes();
document_metadata.clear_selected_nodes();
responses.add(BroadcastEvent::SelectionChanged);
self.send_graph(network, graph_view_overlay_open, responses);
self.send_graph(network, graph_view_overlay_open, document_metadata, collapsed, responses);
let node_types = document_node_types::collect_node_types();
responses.add(FrontendMessage::UpdateNodeTypes { node_types });
}
self.update_selected(document_network, metadata, responses);
self.update_selected(document_network, document_metadata, responses);
}
NodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors } => {
let changed = self.resolved_types != resolved_types || self.node_graph_errors != node_graph_errors;
self.resolved_types = resolved_types;
self.node_graph_errors = node_graph_errors;
if changed {
if let Some(network) = document_network.nested_network(&self.network) {
self.send_graph(network, graph_view_overlay_open, responses)
}
}
}
}
self.has_selection = metadata.has_selected_nodes();
self.has_selection = document_metadata.has_selected_nodes();
}
fn actions(&self) -> ActionList {

View File

@@ -39,15 +39,14 @@ pub enum LayerClassification {
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, specta::Type)]
pub struct LayerPanelEntry {
pub id: NodeId,
pub name: String,
pub tooltip: String,
#[serde(rename = "layerClassification")]
pub layer_classification: LayerClassification,
pub selected: bool,
pub expanded: bool,
pub disabled: bool,
#[serde(rename = "parentId")]
pub parent_id: Option<NodeId>,
pub id: NodeId,
pub depth: usize,
pub thumbnail: String,
}

View File

@@ -1,5 +1,3 @@
pub use super::layer_panel::LayerPanelEntry;
use glam::DVec2;
use serde::{Deserialize, Serialize};
use std::fmt;

View File

@@ -95,9 +95,6 @@ pub enum PortfolioMessage {
SelectDocument {
document_id: DocumentId,
},
SetActiveDocument {
document_id: DocumentId,
},
SubmitDocumentExport {
file_name: String,
file_type: FileType,

View File

@@ -139,8 +139,6 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
// Send the new list of document tab names
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(DocumentMessage::RenderDocument);
responses.add(DocumentMessage::DocumentStructureChanged);
}
PortfolioMessage::CloseDocumentWithConfirmation { document_id } => {
let target_document = self.documents.get(&document_id).unwrap();
@@ -420,6 +418,7 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
}
}
PortfolioMessage::SelectDocument { document_id } => {
// Auto-save the document we are leaving
if let Some(document) = self.active_document() {
if !document.is_auto_saved() {
responses.add(PortfolioMessage::AutoSaveDocument {
@@ -429,27 +428,20 @@ impl MessageHandler<PortfolioMessage, (&InputPreprocessorMessageHandler, &Prefer
}
}
if self.active_document().is_some() {
responses.add(BroadcastEvent::ToolAbort);
responses.add(OverlaysMessage::Draw);
}
// Set the new active document ID
self.active_document_id = Some(document_id);
// TODO: Remove this message in favor of having tools have specific data per document instance
responses.add(PortfolioMessage::SetActiveDocument { document_id });
responses.add(MenuBarMessage::SendLayout);
responses.add(PortfolioMessage::UpdateOpenDocumentsList);
responses.add(FrontendMessage::UpdateActiveDocument { document_id });
responses.add(DocumentMessage::RenderDocument);
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(OverlaysMessage::Draw);
responses.add(BroadcastEvent::ToolAbort);
responses.add(BroadcastEvent::SelectionChanged);
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);
responses.add(MenuBarMessage::SendLayout);
}
PortfolioMessage::SubmitDocumentExport {
file_name,
file_type,
@@ -619,7 +611,6 @@ impl PortfolioMessageHandler {
responses.add(ToolMessage::InitTools);
responses.add(NodeGraphMessage::Init);
responses.add(NavigationMessage::TranslateCanvas { delta: (0., 0.).into() });
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(PropertiesPanelMessage::Clear);
responses.add(NodeGraphMessage::UpdateNewNodeGraph);
}

View File

@@ -66,7 +66,7 @@ impl Pivot {
// If more than one layer is selected we use the AABB with the mean of the pivots
let xy_summation = document
.selected_visible_layers()
.map(|layer| graph_modification_utils::get_viewport_pivot(layer, &document.network, &document.metadata))
.map(|layer| graph_modification_utils::get_viewport_pivot(layer, &document.network, &document.document_metadata))
.reduce(|a, b| a + b)
.unwrap_or_default();

View File

@@ -144,9 +144,10 @@ impl MessageHandler<ToolMessage, (&DocumentMessageHandler, DocumentId, &InputPre
}
ToolMessage::InitTools => {
// Subscribe the transform layer to selection change events
let send = Box::new(TransformLayerMessage::SelectionChanged.into());
let on = BroadcastEvent::SelectionChanged;
responses.add(BroadcastMessage::SubscribeEvent { send, on });
responses.add(BroadcastMessage::SubscribeEvent {
on: BroadcastEvent::SelectionChanged,
send: Box::new(TransformLayerMessage::SelectionChanged.into()),
});
let tool_data = &mut self.tool_state.tool_data;
let document_data = &self.tool_state.document_tool_data;

View File

@@ -223,7 +223,7 @@ impl PathToolData {
let _selected_layers = shape_editor.selected_layers().cloned().collect::<Vec<_>>();
// Select the first point within the threshold (in pixels)
if let Some(selected_points) = shape_editor.select_point(&document.network, &document.metadata, input.mouse.position, SELECTION_THRESHOLD, shift) {
if let Some(selected_points) = shape_editor.select_point(&document.network, &document.document_metadata, input.mouse.position, SELECTION_THRESHOLD, shift) {
self.start_dragging_point(selected_points, input, document, responses);
responses.add(OverlaysMessage::Draw);
@@ -298,7 +298,7 @@ impl PathToolData {
// Move the selected points with the mouse
let snapped_position = self.snap_manager.snap_position(responses, document, input.mouse.position);
shape_editor.move_selected_points(&document.network, &document.metadata, snapped_position - self.previous_mouse_position, shift, responses);
shape_editor.move_selected_points(&document.network, &document.document_metadata, snapped_position - self.previous_mouse_position, shift, responses);
self.previous_mouse_position = snapped_position;
}
}
@@ -362,7 +362,12 @@ impl Fsm for PathToolFsmState {
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
} else {
shape_editor.select_all_in_quad(&document.network, &document.metadata, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
shape_editor.select_all_in_quad(
&document.network,
&document.document_metadata,
[tool_data.drag_start_pos, tool_data.previous_mouse_position],
!shift_pressed,
);
}
responses.add(OverlaysMessage::Draw);
@@ -376,7 +381,12 @@ impl Fsm for PathToolFsmState {
if tool_data.drag_start_pos == tool_data.previous_mouse_position {
responses.add(NodeGraphMessage::SelectedNodesSet { nodes: vec![] });
} else {
shape_editor.select_all_in_quad(&document.network, &document.metadata, [tool_data.drag_start_pos, tool_data.previous_mouse_position], !shift_pressed);
shape_editor.select_all_in_quad(
&document.network,
&document.document_metadata,
[tool_data.drag_start_pos, tool_data.previous_mouse_position],
!shift_pressed,
);
}
responses.add(OverlaysMessage::Draw);
responses.add(PathToolMessage::SelectedPointUpdated);
@@ -388,16 +398,16 @@ impl Fsm for PathToolFsmState {
let shift_pressed = input.keyboard.get(shift_mirror_distance as usize);
let nearest_point = shape_editor
.find_nearest_point_indices(&document.network, &document.metadata, input.mouse.position, SELECTION_THRESHOLD)
.find_nearest_point_indices(&document.network, &document.document_metadata, input.mouse.position, SELECTION_THRESHOLD)
.map(|(_, nearest_point)| nearest_point);
shape_editor.delete_selected_handles_with_zero_length(&document.network, &document.metadata, &tool_data.opposing_handle_lengths, responses);
shape_editor.delete_selected_handles_with_zero_length(&document.network, &document.document_metadata, &tool_data.opposing_handle_lengths, responses);
if tool_data.drag_start_pos.distance(input.mouse.position) <= DRAG_THRESHOLD && !shift_pressed {
let clicked_selected = shape_editor.selected_points().any(|&point| nearest_point == Some(point));
if clicked_selected {
shape_editor.deselect_all();
shape_editor.select_point(&document.network, &document.metadata, input.mouse.position, SELECTION_THRESHOLD, false);
shape_editor.select_point(&document.network, &document.document_metadata, input.mouse.position, SELECTION_THRESHOLD, false);
responses.add(OverlaysMessage::Draw);
}
}
@@ -418,9 +428,9 @@ impl Fsm for PathToolFsmState {
}
(_, PathToolMessage::InsertPoint) => {
// First we try and flip the sharpness (if they have clicked on an anchor)
if !shape_editor.flip_sharp(&document.network, &document.metadata, input.mouse.position, SELECTION_TOLERANCE, responses) {
if !shape_editor.flip_sharp(&document.network, &document.document_metadata, input.mouse.position, SELECTION_TOLERANCE, responses) {
// If not, then we try and split the path that may have been clicked upon
shape_editor.split(&document.network, &document.metadata, input.mouse.position, SELECTION_TOLERANCE, responses);
shape_editor.split(&document.network, &document.document_metadata, input.mouse.position, SELECTION_TOLERANCE, responses);
}
responses.add(PathToolMessage::SelectedPointUpdated);
@@ -433,7 +443,7 @@ impl Fsm for PathToolFsmState {
}
(_, PathToolMessage::PointerMove { .. }) => self,
(_, PathToolMessage::NudgeSelectedPoints { delta_x, delta_y }) => {
shape_editor.move_selected_points(&document.network, &document.metadata, (delta_x, delta_y).into(), true, responses);
shape_editor.move_selected_points(&document.network, &document.document_metadata, (delta_x, delta_y).into(), true, responses);
PathToolFsmState::Ready
}
@@ -444,18 +454,18 @@ impl Fsm for PathToolFsmState {
}
(_, PathToolMessage::SelectedPointXChanged { new_x }) => {
if let Some(&SingleSelectedPoint { coordinates, id, layer, .. }) = tool_data.selection_status.as_one() {
shape_editor.reposition_control_point(&id, responses, &document.network, &document.metadata, DVec2::new(new_x, coordinates.y), layer);
shape_editor.reposition_control_point(&id, responses, &document.network, &document.document_metadata, DVec2::new(new_x, coordinates.y), layer);
}
PathToolFsmState::Ready
}
(_, PathToolMessage::SelectedPointYChanged { new_y }) => {
if let Some(&SingleSelectedPoint { coordinates, id, layer, .. }) = tool_data.selection_status.as_one() {
shape_editor.reposition_control_point(&id, responses, &document.network, &document.metadata, DVec2::new(coordinates.x, new_y), layer);
shape_editor.reposition_control_point(&id, responses, &document.network, &document.document_metadata, DVec2::new(coordinates.x, new_y), layer);
}
PathToolFsmState::Ready
}
(_, PathToolMessage::SelectedPointUpdated) => {
tool_data.selection_status = get_selection_status(&document.network, &document.metadata, shape_editor);
tool_data.selection_status = get_selection_status(&document.network, &document.document_metadata, shape_editor);
self
}
(_, PathToolMessage::ManipulatorAngleMakeSmooth) => {

View File

@@ -491,7 +491,7 @@ impl Fsm for SelectToolFsmState {
&tool_data.layers_dragging,
responses,
&document.network,
&document.metadata,
&document.document_metadata,
None,
&ToolType::Select,
);
@@ -510,7 +510,7 @@ impl Fsm for SelectToolFsmState {
&selected,
responses,
&document.network,
&document.metadata,
&document.document_metadata,
None,
&ToolType::Select,
);
@@ -630,7 +630,7 @@ impl Fsm for SelectToolFsmState {
selected,
responses,
&document.network,
&document.metadata,
&document.document_metadata,
None,
&ToolType::Select,
);
@@ -665,7 +665,7 @@ impl Fsm for SelectToolFsmState {
&tool_data.layers_dragging,
responses,
&document.network,
&document.metadata,
&document.document_metadata,
None,
&ToolType::Select,
);
@@ -832,7 +832,7 @@ impl Fsm for SelectToolFsmState {
&tool_data.layers_dragging,
responses,
&document.network,
&document.metadata,
&document.document_metadata,
None,
&ToolType::Select,
);

View File

@@ -56,7 +56,7 @@ impl<'a> MessageHandler<TransformLayerMessage, TransformData<'a>> for TransformL
&selected_layers,
responses,
&document.network,
&document.metadata,
&document.document_metadata,
Some(shape_editor),
&tool_data.active_tool_type,
);

View File

@@ -1,10 +1,7 @@
use crate::consts::FILE_SAVE_SUFFIX;
use crate::messages::frontend::utility_types::FrontendImageData;
use crate::messages::frontend::utility_types::{ExportBounds, FileType};
use crate::messages::portfolio::document::node_graph::wrap_network_in_scope;
use crate::messages::portfolio::document::utility_types::document_metadata::LayerNodeIdentifier;
use crate::messages::portfolio::document::utility_types::layer_panel::LayerClassification;
use crate::messages::portfolio::document::utility_types::misc::LayerPanelEntry;
use crate::messages::prelude::*;
use graph_craft::concrete;
@@ -15,8 +12,9 @@ use graph_craft::imaginate_input::ImaginatePreferences;
use graph_craft::proto::GraphErrors;
use graphene_core::application_io::{NodeGraphUpdateMessage, NodeGraphUpdateSender, RenderConfig};
use graphene_core::memo::IORecord;
use graphene_core::raster::{Image, ImageFrame};
use graphene_core::renderer::{ClickTarget, GraphicElementRendered, SvgSegmentList};
use graphene_core::raster::ImageFrame;
use graphene_core::renderer::{ClickTarget, GraphicElementRendered, ImageRenderMode, RenderParams, SvgRender};
use graphene_core::renderer::{RenderSvgSegmentList, SvgSegment};
use graphene_core::text::FontCache;
use graphene_core::transform::{Footprint, Transform};
use graphene_core::vector::style::ViewMode;
@@ -27,28 +25,45 @@ use interpreted_executor::dynamic_executor::{DynamicExecutor, ResolvedDocumentNo
use glam::{DAffine2, DVec2, UVec2};
use std::cell::RefCell;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
use std::hash::Hasher;
use std::rc::Rc;
use std::sync::mpsc::{Receiver, Sender};
use std::sync::Arc;
/// Persistent data between graph executions. It's updated via message passing from the editor thread with [`NodeRuntimeMessage`]`.
/// Some of these fields are put into a [`WasmEditorApi`] which is passed to the final compiled graph network upon each execution.
/// Once the implementation is finished, this will live in a separate thread. Right now it's part of the main JS thread, but its own separate JS stack frame independent from the editor.
pub struct NodeRuntime {
pub(crate) executor: DynamicExecutor,
font_cache: FontCache,
executor: DynamicExecutor,
receiver: Receiver<NodeRuntimeMessage>,
sender: InternalNodeGraphUpdateSender,
wasm_io: Option<WasmApplicationIo>,
/// Font data (for rendering text) made available to the graph through the [`WasmEditorApi`].
font_cache: FontCache,
/// Imaginate preferences made available to the graph through the [`WasmEditorApi`].
imaginate_preferences: ImaginatePreferences,
pub(crate) thumbnails: HashMap<NodeId, SvgSegmentList>,
pub(crate) click_targets: HashMap<NodeId, Vec<ClickTarget>>,
pub(crate) upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
pub(crate) resolved_types: ResolvedDocumentNodeTypes,
pub(crate) node_graph_errors: GraphErrors,
/// Gives access to APIs like a rendering surface (native window handle or HTML5 canvas) and WGPU (which becomes WebGPU on web).
wasm_application_io: Option<WasmApplicationIo>,
graph_hash: Option<u64>,
node_graph_errors: GraphErrors,
resolved_types: ResolvedDocumentNodeTypes,
monitor_nodes: Vec<Vec<NodeId>>,
// TODO: Remove, it doesn't need to be persisted anymore
/// The current renders of the thumbnails for layer nodes.
thumbnail_renders: HashMap<NodeId, Vec<SvgSegment>>,
/// The current click targets for layer nodes.
click_targets: HashMap<NodeId, Vec<ClickTarget>>,
/// The current upstream transforms for nodes.
upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
}
/// Messages passed from the editor thread to the node runtime thread.
enum NodeRuntimeMessage {
GenerationRequest(GenerationRequest),
ExecutionRequest(ExecutionRequest),
FontCacheUpdate(FontCache),
ImaginatePreferencesUpdate(ImaginatePreferences),
}
@@ -63,17 +78,16 @@ pub struct ExportConfig {
pub size: DVec2,
}
pub(crate) struct GenerationRequest {
generation_id: u64,
pub(crate) struct ExecutionRequest {
execution_id: u64,
graph: NodeNetwork,
render_config: RenderConfig,
}
pub(crate) struct GenerationResponse {
generation_id: u64,
pub(crate) struct ExecutionResponse {
execution_id: u64,
result: Result<TaggedValue, String>,
updates: VecDeque<Message>,
new_thumbnails: HashMap<NodeId, SvgSegmentList>,
responses: VecDeque<Message>,
new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
new_upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
resolved_types: ResolvedDocumentNodeTypes,
@@ -82,15 +96,15 @@ pub(crate) struct GenerationResponse {
}
enum NodeGraphUpdate {
GenerationResponse(GenerationResponse),
ExecutionResponse(ExecutionResponse),
NodeGraphUpdateMessage(NodeGraphUpdateMessage),
}
struct InternalNodeGraphUpdateSender(Sender<NodeGraphUpdate>);
impl InternalNodeGraphUpdateSender {
fn send_generation_response(&self, response: GenerationResponse) {
self.0.send(NodeGraphUpdate::GenerationResponse(response)).expect("Failed to send response")
fn send_generation_response(&self, response: ExecutionResponse) {
self.0.send(NodeGraphUpdate::ExecutionResponse(response)).expect("Failed to send response")
}
}
@@ -106,30 +120,33 @@ thread_local! {
impl NodeRuntime {
fn new(receiver: Receiver<NodeRuntimeMessage>, sender: Sender<NodeGraphUpdate>) -> Self {
let executor = DynamicExecutor::default();
Self {
executor,
executor: DynamicExecutor::default(),
receiver,
sender: InternalNodeGraphUpdateSender(sender),
font_cache: FontCache::default(),
imaginate_preferences: Default::default(),
thumbnails: Default::default(),
wasm_io: None,
click_targets: HashMap::new(),
wasm_application_io: None,
graph_hash: None,
upstream_transforms: HashMap::new(),
resolved_types: ResolvedDocumentNodeTypes::default(),
node_graph_errors: Vec::new(),
resolved_types: ResolvedDocumentNodeTypes::default(),
monitor_nodes: Vec::new(),
thumbnail_renders: Default::default(),
click_targets: HashMap::new(),
upstream_transforms: HashMap::new(),
}
}
pub async fn run(&mut self) {
let mut requests = self.receiver.try_iter().collect::<Vec<_>>();
// TODO: Currently we still render the document after we submit the node graph execution request.
// This should be avoided in the future.
requests.reverse();
requests.dedup_by_key(|x| match x {
NodeRuntimeMessage::GenerationRequest(x) => Some(x.graph.current_hash()),
NodeRuntimeMessage::ExecutionRequest(x) => Some(x.graph.current_hash()),
_ => None,
});
requests.reverse();
@@ -137,49 +154,45 @@ impl NodeRuntime {
match request {
NodeRuntimeMessage::FontCacheUpdate(font_cache) => self.font_cache = font_cache,
NodeRuntimeMessage::ImaginatePreferencesUpdate(preferences) => self.imaginate_preferences = preferences,
NodeRuntimeMessage::GenerationRequest(GenerationRequest {
generation_id, graph, render_config, ..
NodeRuntimeMessage::ExecutionRequest(ExecutionRequest {
execution_id, graph, render_config, ..
}) => {
let transform = render_config.viewport.transform;
let result = self.execute_network(graph, render_config).await;
let mut responses = VecDeque::new();
self.process_monitor_nodes(&mut responses);
self.update_thumbnails(&mut responses);
self.update_upstream_transforms();
let response = GenerationResponse {
generation_id,
self.sender.send_generation_response(ExecutionResponse {
execution_id,
result,
updates: responses,
new_thumbnails: self.thumbnails.clone(),
responses,
new_click_targets: self.click_targets.clone().into_iter().map(|(id, targets)| (LayerNodeIdentifier::new_unchecked(id), targets)).collect(),
new_upstream_transforms: self.upstream_transforms.clone(),
resolved_types: self.resolved_types.clone(),
node_graph_errors: core::mem::take(&mut self.node_graph_errors),
transform,
};
self.sender.send_generation_response(response);
});
}
}
}
}
async fn execute_network<'a>(&'a mut self, graph: NodeNetwork, render_config: RenderConfig) -> Result<TaggedValue, String> {
if self.wasm_io.is_none() {
self.wasm_io = Some(WasmApplicationIo::new().await);
if self.wasm_application_io.is_none() {
self.wasm_application_io = Some(WasmApplicationIo::new().await);
}
let editor_api = WasmEditorApi {
font_cache: &self.font_cache,
application_io: self.wasm_io.as_ref().unwrap(),
node_graph_message_sender: &self.sender,
imaginate_preferences: &self.imaginate_preferences,
application_io: self.wasm_application_io.as_ref().unwrap(),
node_graph_message_sender: &self.sender,
render_config,
image_frame: None,
};
use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
use std::hash::Hasher;
// Required to ensure that the appropriate protonodes are reinserted when the Editor API changes.
let mut graph_input_hash = DefaultHasher::new();
editor_api.font_cache.hash(&mut graph_input_hash);
@@ -243,85 +256,85 @@ impl NodeRuntime {
Ok(result)
}
/// Recomputes the thumbnails for the layers in the graph, modifying the state and updating the UI.
pub fn update_thumbnails(&mut self, responses: &mut VecDeque<Message>) {
let mut image_data: Vec<_> = Vec::new();
self.thumbnails.retain(|id, _| self.monitor_nodes.iter().any(|node_path| node_path.contains(id)));
for node_path in &self.monitor_nodes {
let Some(node_id) = node_path.get(node_path.len() - 2).copied() else {
/// Updates state data
pub fn process_monitor_nodes(&mut self, responses: &mut VecDeque<Message>) {
// TODO: Consider optimizing this since it's currently O(m*n^2), with a sort it could be made O(m * n*log(n))
self.thumbnail_renders.retain(|id, _| self.monitor_nodes.iter().any(|monitor_node_path| monitor_node_path.contains(id)));
for monitor_node_path in &self.monitor_nodes {
// The monitor nodes are located within a document node, and are thus children in that network, so this gets the parent document node's ID
let Some(parent_network_node_id) = monitor_node_path.get(monitor_node_path.len() - 2).copied() else {
warn!("Monitor node has invalid node id");
continue;
};
let Some(value) = self.executor.introspect(node_path).flatten() else {
warn!("Failed to introspect monitor node for thumbnail");
continue;
};
let Some(io_data) = value.downcast_ref::<IORecord<Footprint, graphene_core::GraphicElement>>() else {
// Extract the monitor node's stored `GraphicElement` data.
let Some(introspected_data) = self.executor.introspect(monitor_node_path).flatten() else {
// TODO: Fix the root of the issue causing the spam of this warning (this at least temporarily disables it in release builds)
#[cfg(debug_assertions)]
warn!("Failed to introspect monitor node");
continue;
};
let graphic_element = &io_data.output;
use graphene_core::renderer::*;
let bounds = graphic_element.bounding_box(DAffine2::IDENTITY);
let render_params = RenderParams::new(ViewMode::Normal, ImageRenderMode::BlobUrl, bounds, true, false, false);
let mut render = SvgRender::new();
graphic_element.render_svg(&mut render, &render_params);
let [min, max] = bounds.unwrap_or_default();
render.format_svg(min, max);
let click_targets = self.click_targets.entry(node_id).or_default();
click_targets.clear();
// Add the graphic element data's click targets to the click targets vector
graphic_element.add_click_targets(click_targets);
// If this is `GraphicElement` data:
// Regenerate click targets and thumbnails for the layers in the graph, modifying the state and updating the UI.
if let Some(io_data) = introspected_data.downcast_ref::<IORecord<Footprint, graphene_core::GraphicElement>>() {
let graphic_element = &io_data.output;
let old_thumbnail = self.thumbnails.entry(node_id).or_default();
if *old_thumbnail != render.svg {
responses.add(FrontendMessage::UpdateNodeThumbnail {
id: node_id,
value: render.svg.to_string(),
});
*old_thumbnail = render.svg;
// UPDATE CLICK TARGETS
// Get the previously stored click targets and wipe them out, then regenerate them
let click_targets = self.click_targets.entry(parent_network_node_id).or_default();
click_targets.clear();
graphic_element.add_click_targets(click_targets);
// RENDER THUMBNAIL
let bounds = graphic_element.bounding_box(DAffine2::IDENTITY);
// Render the thumbnail from a `GraphicElement` into an SVG string
let render_params = RenderParams::new(ViewMode::Normal, ImageRenderMode::Base64, bounds, true, false, false);
let mut render = SvgRender::new();
graphic_element.render_svg(&mut render, &render_params);
// And give the SVG a viewbox and outer <svg>...</svg> wrapper tag
let [min, max] = bounds.unwrap_or_default();
render.format_svg(min, max);
// UPDATE FRONTEND THUMBNAIL
let new_thumbnail_svg = render.svg;
let old_thumbnail_svg = self.thumbnail_renders.entry(parent_network_node_id).or_default();
if old_thumbnail_svg != &new_thumbnail_svg {
responses.add(FrontendMessage::UpdateNodeThumbnail {
id: parent_network_node_id,
value: new_thumbnail_svg.to_svg_string(),
});
*old_thumbnail_svg = new_thumbnail_svg;
}
}
let resize = Some(DVec2::splat(100.));
image_data.extend(render.image_data.into_iter().filter_map(|(_, image)| NodeGraphExecutor::to_frontend_image_data(image, resize).ok()))
}
if !image_data.is_empty() {
responses.add(FrontendMessage::UpdateImageData {
document_id: DocumentId(0),
image_data,
});
}
}
pub fn update_upstream_transforms(&mut self) {
for node_path in &self.monitor_nodes {
let Some(node_id) = node_path.get(node_path.len() - 2).copied() else {
warn!("Monitor node has invalid node id");
continue;
};
let Some(value) = self.executor.introspect(node_path).flatten() else {
warn!("Failed to introspect monitor node for upstream transforms");
continue;
};
fn try_downcast<T: Transform + 'static>(value: &dyn std::any::Any) -> Option<(Footprint, DAffine2)> {
let io_data = value.downcast_ref::<IORecord<Footprint, T>>()?;
let transform = io_data.output.transform();
Some((io_data.input, transform))
// If this is `VectorData`, `ImageFrame`, or `GraphicElement` data:
// Update the stored upstream transforms for this layer/node.
if let Some(transform) = {
fn try_downcast<T: Transform + 'static>(value: &dyn std::any::Any) -> Option<(Footprint, DAffine2)> {
let io_data = value.downcast_ref::<IORecord<Footprint, T>>()?;
let transform = io_data.output.transform();
Some((io_data.input, transform))
}
None.or_else(|| try_downcast::<VectorData>(introspected_data.as_ref()))
.or_else(|| try_downcast::<ImageFrame<Color>>(introspected_data.as_ref()))
.or_else(|| try_downcast::<GraphicElement>(introspected_data.as_ref()))
} {
self.upstream_transforms.insert(parent_network_node_id, transform);
}
let Some(transform) = try_downcast::<VectorData>(value.as_ref())
.or_else(|| try_downcast::<ImageFrame<Color>>(value.as_ref()))
.or_else(|| try_downcast::<GraphicElement>(value.as_ref()))
else {
warn!("Failed to downcast transform input");
continue;
};
self.upstream_transforms.insert(node_id, transform);
}
}
}
pub fn introspect_node(path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
NODE_RUNTIME
.try_with(|runtime| {
@@ -384,15 +397,15 @@ impl Default for NodeGraphExecutor {
impl NodeGraphExecutor {
/// Execute the network by flattening it and creating a borrow stack.
fn queue_execution(&self, network: NodeNetwork, render_config: RenderConfig) -> u64 {
let generation_id = generate_uuid();
let request = GenerationRequest {
let execution_id = generate_uuid();
let request = ExecutionRequest {
graph: network,
generation_id,
execution_id,
render_config,
};
self.sender.send(NodeRuntimeMessage::GenerationRequest(request)).expect("Failed to send generation request");
self.sender.send(NodeRuntimeMessage::ExecutionRequest(request)).expect("Failed to send generation request");
generation_id
execution_id
}
pub fn introspect_node(&self, path: &[NodeId]) -> Option<Arc<dyn std::any::Any>> {
@@ -429,36 +442,6 @@ impl NodeGraphExecutor {
Some(extract_data(downcasted))
}
/// Encodes an image into a format using the image crate
fn encode_img(image: Image<Color>, resize: Option<DVec2>, format: image::ImageOutputFormat) -> Result<(Vec<u8>, (u32, u32)), String> {
use image::{ImageBuffer, Rgba};
use std::io::Cursor;
let (result_bytes, width, height) = image.to_flat_u8();
let mut output: ImageBuffer<Rgba<u8>, _> = image::ImageBuffer::from_raw(width, height, result_bytes).ok_or_else(|| "Invalid image size".to_string())?;
if let Some(size) = resize {
let size = size.as_uvec2();
if size.x > 0 && size.y > 0 {
output = image::imageops::resize(&output, size.x, size.y, image::imageops::Triangle);
}
}
let size = output.dimensions();
let mut image_data: Vec<u8> = Vec::new();
output.write_to(&mut Cursor::new(&mut image_data), format).map_err(|e| e.to_string())?;
Ok::<_, String>((image_data, size))
}
/// Generate a new [`FrontendImageData`] from the [`Image`].
fn to_frontend_image_data(image: Image<Color>, resize: Option<DVec2>) -> Result<FrontendImageData, String> {
let (image_data, _size) = Self::encode_img(image, resize, image::ImageOutputFormat::Bmp)?;
let mime = "image/bmp".to_string();
let image_data = std::sync::Arc::new(image_data);
Ok(FrontendImageData { image_data, mime })
}
/// Evaluates a node graph, computing the entire graph
pub fn submit_node_graph_evaluation(&mut self, document: &mut DocumentMessageHandler, viewport_resolution: UVec2) -> Result<(), String> {
// Get the node graph layer
@@ -466,7 +449,7 @@ impl NodeGraphExecutor {
let render_config = RenderConfig {
viewport: Footprint {
transform: document.metadata.document_to_viewport,
transform: document.document_metadata.document_to_viewport,
resolution: viewport_resolution,
..Default::default()
},
@@ -480,9 +463,9 @@ impl NodeGraphExecutor {
};
// Execute the node graph
let generation_id = self.queue_execution(network, render_config);
let execution_id = self.queue_execution(network, render_config);
self.futures.insert(generation_id, ExecutionContext { export_config: None });
self.futures.insert(execution_id, ExecutionContext { export_config: None });
Ok(())
}
@@ -515,9 +498,9 @@ impl NodeGraphExecutor {
export_config.size = size;
// Execute the node graph
let generation_id = self.queue_execution(network, render_config);
let execution_id = self.queue_execution(network, render_config);
let execution_context = ExecutionContext { export_config: Some(export_config) };
self.futures.insert(generation_id, execution_context);
self.futures.insert(execution_id, execution_context);
Ok(())
}
@@ -552,89 +535,59 @@ impl NodeGraphExecutor {
}
pub fn poll_node_graph_evaluation(&mut self, document: &mut DocumentMessageHandler, responses: &mut VecDeque<Message>) -> Result<(), String> {
let DocumentMessageHandler {
network: document_network,
metadata: document_metadata,
collapsed,
..
} = document;
let results = self.receiver.try_iter().collect::<Vec<_>>();
for response in results {
match response {
NodeGraphUpdate::GenerationResponse(GenerationResponse {
generation_id,
result,
updates,
new_thumbnails,
new_click_targets,
new_upstream_transforms,
resolved_types,
node_graph_errors,
transform,
}) => {
NodeGraphUpdate::ExecutionResponse(execution_response) => {
let ExecutionResponse {
execution_id,
result,
responses: existing_responses,
new_click_targets,
new_upstream_transforms,
resolved_types,
node_graph_errors,
transform,
} = execution_response;
responses.extend(existing_responses);
responses.add(NodeGraphMessage::UpdateTypes { resolved_types, node_graph_errors });
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())?;
if let Some(export_config) = execution_context.export_config {
return self.export(node_graph_output, export_config, responses);
}
for (&node_id, svg) in &new_thumbnails {
if !document_network.nodes.contains_key(&node_id) {
warn!("Missing node");
continue;
}
let layer = LayerNodeIdentifier::new(node_id, document_network);
responses.add(FrontendMessage::UpdateDocumentLayerDetails {
data: LayerPanelEntry {
name: document_network.nodes.get(&node_id).map(|node| node.alias.clone()).unwrap_or_default(),
tooltip: if cfg!(debug_assertions) { format!("Layer ID: {node_id}") } else { "".into() },
layer_classification: if document_metadata.is_artboard(layer) {
LayerClassification::Artboard
} else if document_metadata.is_folder(layer) {
LayerClassification::Folder
} else {
LayerClassification::Layer
},
expanded: layer.has_children(document_metadata) && !collapsed.contains(&layer),
selected: document_metadata.selected_layers_contains(layer),
parent_id: layer.parent(document_metadata).map(|parent| parent.to_node()),
id: node_id,
depth: layer.ancestors(document_metadata).count() - 1,
thumbnail: svg.to_string(),
},
});
}
document_metadata.update_transforms(new_upstream_transforms);
document_metadata.update_click_targets(new_click_targets);
responses.extend(updates);
self.process_node_graph_output(node_graph_output, transform, responses)?;
responses.add(DocumentMessage::RenderDocument);
responses.add(DocumentMessage::DocumentStructureChanged);
responses.add(NodeGraphMessage::SendGraph);
responses.add(BroadcastEvent::DocumentIsDirty);
responses.add(OverlaysMessage::Draw);
let node_graph_output = result.map_err(|e| format!("Node graph evaluation failed: {e:?}"))?;
document.document_metadata.update_transforms(new_upstream_transforms);
document.document_metadata.update_click_targets(new_click_targets);
let execution_context = self.futures.remove(&execution_id).ok_or_else(|| "Invalid generation ID".to_string())?;
if let Some(export_config) = execution_context.export_config {
// Special handling for exporting the artwork
self.export(node_graph_output, export_config, responses)?
} else {
self.process_node_graph_output(node_graph_output, transform, responses)?
}
}
NodeGraphUpdate::NodeGraphUpdateMessage(NodeGraphUpdateMessage::ImaginateStatusUpdate) => {
responses.add(DocumentMessage::PropertiesPanel(PropertiesPanelMessage::Refresh));
}
NodeGraphUpdate::NodeGraphUpdateMessage(NodeGraphUpdateMessage::ImaginateStatusUpdate) => responses.add(DocumentMessage::PropertiesPanel(PropertiesPanelMessage::Refresh)),
}
}
Ok(())
}
fn render(render_object: impl GraphicElementRendered, transform: DAffine2, responses: &mut VecDeque<Message>) {
use graphene_core::renderer::{ImageRenderMode, RenderParams, SvgRender};
fn debug_render(render_object: impl GraphicElementRendered, transform: DAffine2, responses: &mut VecDeque<Message>) {
// Setup rendering
let mut render = SvgRender::new();
let render_params = RenderParams::new(ViewMode::Normal, ImageRenderMode::BlobUrl, None, false, false, false);
let render_params = RenderParams::new(ViewMode::Normal, ImageRenderMode::Base64, None, false, false, false);
// Render SVG
render_object.render_svg(&mut render, &render_params);
// Concatenate the defs and the SVG into one string
render.wrap_with_transform(transform, None);
let svg = render.svg.to_string();
let svg = render.svg.to_svg_string();
// Send to frontend
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
@@ -667,16 +620,16 @@ impl NodeGraphExecutor {
);
responses.add(FrontendMessage::UpdateDocumentArtwork { svg });
}
TaggedValue::Bool(render_object) => Self::render(render_object, transform, responses),
TaggedValue::String(render_object) => Self::render(render_object, transform, responses),
TaggedValue::F32(render_object) => Self::render(render_object, transform, responses),
TaggedValue::F64(render_object) => Self::render(render_object, transform, responses),
TaggedValue::OptionalColor(render_object) => Self::render(render_object, transform, responses),
TaggedValue::VectorData(render_object) => Self::render(render_object, transform, responses),
TaggedValue::GraphicGroup(render_object) => Self::render(render_object, transform, responses),
TaggedValue::Artboard(render_object) => Self::render(render_object, transform, responses),
TaggedValue::ImageFrame(render_object) => Self::render(render_object, transform, responses),
TaggedValue::Palette(render_object) => Self::render(render_object, transform, responses),
TaggedValue::Bool(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::String(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::F32(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::F64(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::OptionalColor(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::VectorData(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::GraphicGroup(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::Artboard(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::ImageFrame(render_object) => Self::debug_render(render_object, transform, responses),
TaggedValue::Palette(render_object) => Self::debug_render(render_object, transform, responses),
_ => {
return Err(format!("Invalid node graph output type: {node_graph_output:#?}"));
}