mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-23 12:08:12 +08:00
Add graph type error diagnostics to the UI (#1535)
* Fontend input types * Fix index of errors / types * Bug fixes, styling improvements, and code review * Improvements to the error box --------- Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
co-authored by
Keavon Chambers
parent
96b5d7b520
commit
947a131a4b
@@ -65,7 +65,7 @@ pub struct DocumentMessageHandler {
|
||||
#[serde(default = "default_rulers_visible")]
|
||||
pub rulers_visible: bool,
|
||||
#[serde(default = "default_collapsed")]
|
||||
pub collapsed: Vec<LayerNodeIdentifier>, // TODO: Is this actually used? Maybe or maybe not. Investigate and potentially remove.
|
||||
pub collapsed: Vec<LayerNodeIdentifier>,
|
||||
// =============================================
|
||||
// Fields omitted from the saved document format
|
||||
// =============================================
|
||||
@@ -265,7 +265,7 @@ impl MessageHandler<DocumentMessage, DocumentInputs<'_>> for DocumentMessageHand
|
||||
node_graph_message_handler: &self.node_graph_handler,
|
||||
executor,
|
||||
document_name: self.name.as_str(),
|
||||
document_network: &mut self.network,
|
||||
document_network: &self.network,
|
||||
document_metadata: &mut self.metadata,
|
||||
};
|
||||
self.properties_panel_message_handler
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::messages::prelude::*;
|
||||
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput};
|
||||
use graph_craft::proto::GraphErrors;
|
||||
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
|
||||
|
||||
#[impl_message(Message, DocumentMessage, NodeGraph)]
|
||||
#[derive(PartialEq, Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
@@ -111,4 +112,10 @@ pub enum NodeGraphMessage {
|
||||
node_id: NodeId,
|
||||
},
|
||||
UpdateNewNodeGraph,
|
||||
UpdateTypes {
|
||||
#[serde(skip)]
|
||||
resolved_types: ResolvedDocumentNodeTypes,
|
||||
#[serde(skip)]
|
||||
node_graph_errors: GraphErrors,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ 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::prelude::*;
|
||||
|
||||
use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput, NodeNetwork, NodeOutput};
|
||||
use graph_craft::document::{DocumentNode, NodeId, NodeInput, NodeNetwork, NodeOutput, Source};
|
||||
use graph_craft::proto::GraphErrors;
|
||||
use graphene_core::*;
|
||||
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
|
||||
mod document_node_types;
|
||||
mod node_properties;
|
||||
|
||||
@@ -23,22 +24,22 @@ pub enum FrontendGraphDataType {
|
||||
Raster,
|
||||
#[serde(rename = "color")]
|
||||
Color,
|
||||
#[serde(rename = "number")]
|
||||
#[serde(rename = "general")]
|
||||
Text,
|
||||
#[serde(rename = "vector")]
|
||||
Subpath,
|
||||
#[serde(rename = "number")]
|
||||
Number,
|
||||
#[serde(rename = "number")]
|
||||
#[serde(rename = "general")]
|
||||
Boolean,
|
||||
/// Refers to the mathematical vector, with direction and magnitude.
|
||||
#[serde(rename = "vec2")]
|
||||
#[serde(rename = "number")]
|
||||
Vector,
|
||||
#[serde(rename = "graphic")]
|
||||
#[serde(rename = "raster")]
|
||||
GraphicGroup,
|
||||
#[serde(rename = "artboard")]
|
||||
Artboard,
|
||||
#[serde(rename = "palette")]
|
||||
#[serde(rename = "color")]
|
||||
Palette,
|
||||
}
|
||||
impl FrontendGraphDataType {
|
||||
@@ -65,6 +66,8 @@ pub struct FrontendGraphInput {
|
||||
#[serde(rename = "dataType")]
|
||||
data_type: FrontendGraphDataType,
|
||||
name: String,
|
||||
#[serde(rename = "resolvedType")]
|
||||
resolved_type: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
@@ -72,6 +75,8 @@ pub struct FrontendGraphOutput {
|
||||
#[serde(rename = "dataType")]
|
||||
data_type: FrontendGraphDataType,
|
||||
name: String,
|
||||
#[serde(rename = "resolvedType")]
|
||||
resolved_type: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, specta::Type)]
|
||||
@@ -92,6 +97,7 @@ pub struct FrontendNode {
|
||||
pub position: (i32, i32),
|
||||
pub disabled: bool,
|
||||
pub previewed: bool,
|
||||
pub errors: Option<String>,
|
||||
}
|
||||
|
||||
// (link_start, link_end, link_end_input_index)
|
||||
@@ -124,6 +130,8 @@ impl FrontendNodeType {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct NodeGraphMessageHandler {
|
||||
pub network: Vec<NodeId>,
|
||||
pub resolved_types: ResolvedDocumentNodeTypes,
|
||||
pub node_graph_errors: GraphErrors,
|
||||
has_selection: bool,
|
||||
widgets: [LayoutGroup; 2],
|
||||
}
|
||||
@@ -144,6 +152,8 @@ impl Default for NodeGraphMessageHandler {
|
||||
|
||||
Self {
|
||||
network: Vec::new(),
|
||||
resolved_types: ResolvedDocumentNodeTypes::default(),
|
||||
node_graph_errors: Vec::new(),
|
||||
has_selection: false,
|
||||
widgets: [LayoutGroup::Row { widgets: Vec::new() }, LayoutGroup::Row { widgets: right_side_widgets }],
|
||||
}
|
||||
@@ -264,7 +274,7 @@ impl NodeGraphMessageHandler {
|
||||
}
|
||||
}
|
||||
|
||||
fn send_graph(network: &NodeNetwork, graph_view_overlay_open: bool, responses: &mut VecDeque<Message>) {
|
||||
fn send_graph(&self, network: &NodeNetwork, graph_view_overlay_open: bool, responses: &mut VecDeque<Message>) {
|
||||
responses.add(PropertiesPanelMessage::Refresh);
|
||||
|
||||
if !graph_view_overlay_open {
|
||||
@@ -298,6 +308,7 @@ impl NodeGraphMessageHandler {
|
||||
|
||||
let mut nodes = Vec::new();
|
||||
for (id, node) in &network.nodes {
|
||||
let node_path = vec![*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 {
|
||||
warn!("Node '{}' does not exist in library", node.name);
|
||||
@@ -305,20 +316,26 @@ impl NodeGraphMessageHandler {
|
||||
};
|
||||
|
||||
// Inputs
|
||||
let mut inputs = node.inputs.iter().zip(node_type.inputs.iter().map(|input_type| FrontendGraphInput {
|
||||
data_type: input_type.data_type,
|
||||
name: input_type.name.to_string(),
|
||||
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 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().map(|output_type| FrontendGraphOutput {
|
||||
let mut outputs = node_type.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:?}")),
|
||||
});
|
||||
let primary_output = if node.has_primary_output { outputs.next() } else { None };
|
||||
|
||||
let errors = self.node_graph_errors.iter().find(|error| error.node_path.starts_with(&node_path)).map(|error| error.error.clone());
|
||||
nodes.push(FrontendNode {
|
||||
is_layer: node.is_layer(),
|
||||
id: *id,
|
||||
@@ -331,6 +348,7 @@ impl NodeGraphMessageHandler {
|
||||
position: node.metadata.position.into(),
|
||||
previewed: network.outputs_contain(*id),
|
||||
disabled: network.disabled.contains(id),
|
||||
errors: errors.map(|e| format!("{e:?}")),
|
||||
})
|
||||
}
|
||||
responses.add(FrontendMessage::UpdateNodeGraph { nodes, links });
|
||||
@@ -607,7 +625,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
}
|
||||
}
|
||||
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, responses);
|
||||
}
|
||||
self.update_selected(document_network, metadata, responses);
|
||||
}
|
||||
@@ -635,7 +653,7 @@ 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.send_graph(network, graph_view_overlay_open, responses);
|
||||
self.update_selected(document_network, metadata, responses);
|
||||
responses.add(NodeGraphMessage::SendGraph { should_rerender: false });
|
||||
}
|
||||
@@ -648,7 +666,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
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, responses);
|
||||
}
|
||||
self.update_selected(document_network, metadata, responses);
|
||||
}
|
||||
@@ -698,7 +716,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
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, responses);
|
||||
}
|
||||
NodeGraphMessage::PasteNodes { serialized_nodes } => {
|
||||
let Some(network) = document_network.nested_network(&self.network) else {
|
||||
@@ -763,7 +781,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
}
|
||||
NodeGraphMessage::SendGraph { should_rerender } => {
|
||||
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, responses);
|
||||
if should_rerender {
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
}
|
||||
@@ -890,7 +908,7 @@ 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);
|
||||
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) {
|
||||
@@ -926,7 +944,7 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
Self::send_graph(network, graph_view_overlay_open, responses);
|
||||
self.send_graph(network, graph_view_overlay_open, responses);
|
||||
}
|
||||
self.update_selection_action_buttons(document_network, metadata, responses);
|
||||
responses.add(NodeGraphMessage::RunDocumentGraph);
|
||||
@@ -936,13 +954,25 @@ impl<'a> MessageHandler<NodeGraphMessage, NodeGraphHandlerData<'a>> for NodeGrap
|
||||
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, responses);
|
||||
|
||||
let node_types = document_node_types::collect_node_types();
|
||||
responses.add(FrontendMessage::UpdateNodeTypes { node_types });
|
||||
}
|
||||
self.update_selected(document_network, 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();
|
||||
}
|
||||
|
||||
+4
-6
@@ -214,7 +214,7 @@ fn vec2_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name
|
||||
}
|
||||
|
||||
fn vec_f32_input(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, text_props: TextInput, blank_assist: bool) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Color, blank_assist);
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Vector, blank_assist);
|
||||
|
||||
let from_string = |string: &str| {
|
||||
string
|
||||
@@ -243,7 +243,7 @@ fn vec_f32_input(document_node: &DocumentNode, node_id: NodeId, index: usize, na
|
||||
}
|
||||
|
||||
fn vec_dvec2_input(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, text_props: TextInput, blank_assist: bool) -> Vec<WidgetHolder> {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Color, blank_assist);
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Vector, blank_assist);
|
||||
|
||||
let from_string = |string: &str| {
|
||||
string
|
||||
@@ -739,7 +739,7 @@ fn gradient_positions(rows: &mut Vec<LayoutGroup>, document_node: &DocumentNode,
|
||||
}
|
||||
|
||||
fn color_widget(document_node: &DocumentNode, node_id: NodeId, index: usize, name: &str, color_props: ColorButton, blank_assist: bool) -> LayoutGroup {
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Number, blank_assist);
|
||||
let mut widgets = start_widgets(document_node, node_id, index, name, FrontendGraphDataType::Color, blank_assist);
|
||||
|
||||
if let NodeInput::Value { tagged_value, exposed: false } = &document_node.inputs[index] {
|
||||
if let &TaggedValue::Color(x) = tagged_value {
|
||||
@@ -1452,9 +1452,7 @@ pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId, _cont
|
||||
|
||||
let scale = vec2_widget(document_node, node_id, 3, "Scale", "W", "H", "x", None, add_blank_assist);
|
||||
|
||||
let vector_data = start_widgets(document_node, node_id, 0, "Data", FrontendGraphDataType::Vector, false);
|
||||
let vector_data = LayoutGroup::Row { widgets: vector_data };
|
||||
vec![vector_data, translation, rotation, scale]
|
||||
vec![translation, rotation, scale]
|
||||
}
|
||||
|
||||
pub fn node_section_font(document_node: &DocumentNode, node_id: NodeId, _context: &mut NodePropertiesContext) -> Vec<LayoutGroup> {
|
||||
|
||||
@@ -6,7 +6,7 @@ use graph_craft::document::NodeNetwork;
|
||||
|
||||
pub struct PropertiesPanelMessageHandlerData<'a> {
|
||||
pub document_name: &'a str,
|
||||
pub document_network: &'a mut NodeNetwork,
|
||||
pub document_network: &'a NodeNetwork,
|
||||
pub document_metadata: &'a mut DocumentMetadata,
|
||||
pub node_graph_message_handler: &'a NodeGraphMessageHandler,
|
||||
pub executor: &'a mut NodeGraphExecutor,
|
||||
|
||||
@@ -12,6 +12,7 @@ use graph_craft::document::value::TaggedValue;
|
||||
use graph_craft::document::{generate_uuid, DocumentNodeImplementation, NodeId, NodeNetwork};
|
||||
use graph_craft::graphene_compiler::Compiler;
|
||||
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};
|
||||
@@ -22,7 +23,7 @@ use graphene_core::vector::style::ViewMode;
|
||||
use graphene_core::vector::VectorData;
|
||||
use graphene_core::{Color, GraphicElement, SurfaceFrame};
|
||||
use graphene_std::wasm_application_io::{WasmApplicationIo, WasmEditorApi};
|
||||
use interpreted_executor::dynamic_executor::DynamicExecutor;
|
||||
use interpreted_executor::dynamic_executor::{DynamicExecutor, ResolvedDocumentNodeTypes};
|
||||
|
||||
use glam::{DAffine2, DVec2, UVec2};
|
||||
use std::cell::RefCell;
|
||||
@@ -40,6 +41,8 @@ pub struct NodeRuntime {
|
||||
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,
|
||||
graph_hash: Option<u64>,
|
||||
monitor_nodes: Vec<Vec<NodeId>>,
|
||||
}
|
||||
@@ -73,6 +76,8 @@ pub(crate) struct GenerationResponse {
|
||||
new_thumbnails: HashMap<NodeId, SvgSegmentList>,
|
||||
new_click_targets: HashMap<LayerNodeIdentifier, Vec<ClickTarget>>,
|
||||
new_upstream_transforms: HashMap<NodeId, (Footprint, DAffine2)>,
|
||||
resolved_types: ResolvedDocumentNodeTypes,
|
||||
node_graph_errors: GraphErrors,
|
||||
transform: DAffine2,
|
||||
}
|
||||
|
||||
@@ -113,6 +118,8 @@ impl NodeRuntime {
|
||||
click_targets: HashMap::new(),
|
||||
graph_hash: None,
|
||||
upstream_transforms: HashMap::new(),
|
||||
resolved_types: ResolvedDocumentNodeTypes::default(),
|
||||
node_graph_errors: Vec::new(),
|
||||
monitor_nodes: Vec::new(),
|
||||
}
|
||||
}
|
||||
@@ -147,6 +154,8 @@ impl NodeRuntime {
|
||||
new_thumbnails: self.thumbnails.clone(),
|
||||
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);
|
||||
@@ -188,7 +197,7 @@ impl NodeRuntime {
|
||||
self.monitor_nodes = scoped_network
|
||||
.recursive_nodes()
|
||||
.filter(|(_, node)| node.implementation == DocumentNodeImplementation::proto("graphene_core::memo::MonitorNode<_, _, _>"))
|
||||
.map(|(_, node)| node.path.clone().unwrap_or_default())
|
||||
.map(|(_, node)| node.original_location.path.clone().unwrap_or_default())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// We assume only one output
|
||||
@@ -201,11 +210,11 @@ impl NodeRuntime {
|
||||
|
||||
assert_ne!(proto_network.nodes.len(), 0, "No protonodes exist?");
|
||||
if let Err(e) = self.executor.update(proto_network).await {
|
||||
error!("Failed to update executor:\n{e}");
|
||||
return Err(e);
|
||||
self.node_graph_errors = e;
|
||||
} else {
|
||||
self.graph_hash = Some(hash_code);
|
||||
}
|
||||
|
||||
self.graph_hash = Some(hash_code);
|
||||
self.resolved_types = self.executor.document_node_types();
|
||||
}
|
||||
|
||||
use graph_craft::graphene_compiler::Executor;
|
||||
@@ -560,8 +569,11 @@ impl NodeGraphExecutor {
|
||||
new_thumbnails,
|
||||
new_click_targets,
|
||||
new_upstream_transforms,
|
||||
resolved_types,
|
||||
node_graph_errors,
|
||||
transform,
|
||||
}) => {
|
||||
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())?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user