mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-10-02 18:48:11 +08:00
Make the Data panel able to display data from selected nodes living in subgraphs (#4064)
Enable the Data panel to display data from selected nodes living in subgraphs
This commit is contained in:
@@ -27,7 +27,9 @@ pub struct DataPanelMessageContext<'a> {
|
|||||||
/// The data panel allows for graph data to be previewed.
|
/// The data panel allows for graph data to be previewed.
|
||||||
#[derive(Default, Debug, Clone, ExtractField)]
|
#[derive(Default, Debug, Clone, ExtractField)]
|
||||||
pub struct DataPanelMessageHandler {
|
pub struct DataPanelMessageHandler {
|
||||||
introspected_node: Option<NodeId>,
|
/// Full path from the root network to the introspected node, with the node itself as the last element.
|
||||||
|
/// Empty when nothing is being introspected.
|
||||||
|
introspected_node_path: Vec<NodeId>,
|
||||||
introspected_data: Option<Arc<dyn Any + Send + Sync>>,
|
introspected_data: Option<Arc<dyn Any + Send + Sync>>,
|
||||||
element_path: Vec<PathStep>,
|
element_path: Vec<PathStep>,
|
||||||
active_vector_table_tab: VectorTableTab,
|
active_vector_table_tab: VectorTableTab,
|
||||||
@@ -38,12 +40,12 @@ impl MessageHandler<DataPanelMessage, DataPanelMessageContext<'_>> for DataPanel
|
|||||||
fn process_message(&mut self, message: DataPanelMessage, responses: &mut VecDeque<Message>, context: DataPanelMessageContext) {
|
fn process_message(&mut self, message: DataPanelMessage, responses: &mut VecDeque<Message>, context: DataPanelMessageContext) {
|
||||||
match message {
|
match message {
|
||||||
DataPanelMessage::UpdateLayout { mut inspect_result } => {
|
DataPanelMessage::UpdateLayout { mut inspect_result } => {
|
||||||
self.introspected_node = Some(inspect_result.inspect_node);
|
|
||||||
self.introspected_data = inspect_result.take_data();
|
self.introspected_data = inspect_result.take_data();
|
||||||
|
self.introspected_node_path = inspect_result.inspect_node_path;
|
||||||
self.update_layout(responses, context);
|
self.update_layout(responses, context);
|
||||||
}
|
}
|
||||||
DataPanelMessage::ClearLayout => {
|
DataPanelMessage::ClearLayout => {
|
||||||
self.introspected_node = None;
|
self.introspected_node_path.clear();
|
||||||
self.introspected_data = None;
|
self.introspected_data = None;
|
||||||
self.element_path.clear();
|
self.element_path.clear();
|
||||||
self.active_vector_table_tab = VectorTableTab::default();
|
self.active_vector_table_tab = VectorTableTab::default();
|
||||||
@@ -93,8 +95,9 @@ impl DataPanelMessageHandler {
|
|||||||
let mut widgets = Vec::new();
|
let mut widgets = Vec::new();
|
||||||
|
|
||||||
// Selected layer/node name
|
// Selected layer/node name
|
||||||
if let Some(node_id) = self.introspected_node {
|
if let Some((node_id, parent_path)) = self.introspected_node_path.split_last() {
|
||||||
let is_layer = network_interface.is_layer(&node_id, &[]);
|
let node_id = *node_id;
|
||||||
|
let is_layer = network_interface.is_layer(&node_id, parent_path);
|
||||||
|
|
||||||
widgets.extend([
|
widgets.extend([
|
||||||
if is_layer {
|
if is_layer {
|
||||||
@@ -103,7 +106,7 @@ impl DataPanelMessageHandler {
|
|||||||
IconLabel::new("Node").tooltip_description("Name of the selected node.").widget_instance()
|
IconLabel::new("Node").tooltip_description("Name of the selected node.").widget_instance()
|
||||||
},
|
},
|
||||||
Separator::new(SeparatorStyle::Related).widget_instance(),
|
Separator::new(SeparatorStyle::Related).widget_instance(),
|
||||||
TextInput::new(network_interface.display_name(&node_id, &[]))
|
TextInput::new(network_interface.display_name(&node_id, parent_path))
|
||||||
.tooltip_description(if is_layer { "Name of the selected layer." } else { "Name of the selected node." })
|
.tooltip_description(if is_layer { "Name of the selected layer." } else { "Name of the selected node." })
|
||||||
.on_update(move |text_input| {
|
.on_update(move |text_input| {
|
||||||
NodeGraphMessage::SetDisplayName {
|
NodeGraphMessage::SetDisplayName {
|
||||||
|
|||||||
@@ -1686,6 +1686,12 @@ impl DocumentMessageHandler {
|
|||||||
self.network_interface.document_metadata()
|
self.network_interface.document_metadata()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Path to the subnetwork that the user's selection is currently scoped to.
|
||||||
|
/// Empty when the selection lives in the root document network.
|
||||||
|
pub fn selection_network_path(&self) -> &[NodeId] {
|
||||||
|
&self.selection_network_path
|
||||||
|
}
|
||||||
|
|
||||||
pub fn serialize_document(&self) -> String {
|
pub fn serialize_document(&self) -> String {
|
||||||
let val = serde_json::to_string(self);
|
let val = serde_json::to_string(self);
|
||||||
// We fully expect the serialization to succeed
|
// We fully expect the serialization to succeed
|
||||||
|
|||||||
@@ -1868,22 +1868,31 @@ impl PortfolioMessageHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the ID of the selected node that should be used as the current source for the Data panel.
|
/// Returns the full path from the root network to the selected node that should drive the Data panel.
|
||||||
pub fn node_to_inspect(&self) -> Option<NodeId> {
|
/// The last element is the node itself; preceding elements identify the nested subnetwork it lives in
|
||||||
|
/// so the Data panel can introspect nodes inside subgraphs. An empty `Vec` signals "nothing to inspect".
|
||||||
|
pub fn node_to_inspect(&self) -> Vec<NodeId> {
|
||||||
// Skip if the Data panel is not open
|
// Skip if the Data panel is not open
|
||||||
if !self.workspace_panel_layout.is_panel_visible(PanelType::Data) || self.workspace_panel_layout.focus_document {
|
if !self.workspace_panel_layout.is_panel_visible(PanelType::Data) || self.workspace_panel_layout.focus_document {
|
||||||
return None;
|
return Vec::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
let document = self.document(self.active_document_id?)?;
|
let Some(document) = self.active_document_id.and_then(|id| self.document(id)) else {
|
||||||
let selected_nodes = document.network_interface.selected_nodes().0;
|
return Vec::new();
|
||||||
|
};
|
||||||
|
let network_path = document.selection_network_path();
|
||||||
|
let Some(selected_nodes) = document.network_interface.selected_nodes_in_nested_network(network_path) else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
|
||||||
// Skip if there is not exactly one selected node
|
// Skip if there is not exactly one selected node
|
||||||
if selected_nodes.len() != 1 {
|
let [node_id] = selected_nodes.0.as_slice() else {
|
||||||
return None;
|
return Vec::new();
|
||||||
}
|
};
|
||||||
|
|
||||||
selected_nodes.first().copied()
|
let mut path = network_path.to_vec();
|
||||||
|
path.push(*node_id);
|
||||||
|
path
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a dockable panel type from whichever panel group currently contains it. Does not prune empty groups.
|
/// Remove a dockable panel type from whichever panel group currently contains it. Does not prune empty groups.
|
||||||
|
|||||||
@@ -53,7 +53,11 @@ pub struct NodeGraphExecutor {
|
|||||||
current_execution_id: u64,
|
current_execution_id: u64,
|
||||||
futures: VecDeque<(u64, ExecutionContext)>,
|
futures: VecDeque<(u64, ExecutionContext)>,
|
||||||
node_graph_hash: u64,
|
node_graph_hash: u64,
|
||||||
previous_node_to_inspect: Option<NodeId>,
|
/// Full path from the root document network to the node currently being inspected by the Data panel, or empty if nothing is selected.
|
||||||
|
/// The last element is the inspect target itself; preceding elements identify the nested subnetwork the node lives in,
|
||||||
|
/// so the runtime can splice its monitor node alongside the target rather than only at the top level.
|
||||||
|
/// Tracking the previously-sent value lets `update_node_graph` re-send the network when the inspection target changes.
|
||||||
|
previous_node_to_inspect: Vec<NodeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -75,7 +79,7 @@ impl NodeGraphExecutor {
|
|||||||
runtime_io: NodeRuntimeIO::with_channels(request_sender, response_receiver),
|
runtime_io: NodeRuntimeIO::with_channels(request_sender, response_receiver),
|
||||||
node_graph_hash: 0,
|
node_graph_hash: 0,
|
||||||
current_execution_id: 0,
|
current_execution_id: 0,
|
||||||
previous_node_to_inspect: None,
|
previous_node_to_inspect: Vec::new(),
|
||||||
};
|
};
|
||||||
(node_runtime, node_executor)
|
(node_runtime, node_executor)
|
||||||
}
|
}
|
||||||
@@ -109,18 +113,18 @@ impl NodeGraphExecutor {
|
|||||||
let instrumented = Instrumented::new(&mut network);
|
let instrumented = Instrumented::new(&mut network);
|
||||||
|
|
||||||
self.runtime_io
|
self.runtime_io
|
||||||
.send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, node_to_inspect: None }))
|
.send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, node_to_inspect: Vec::new() }))
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
Ok(instrumented)
|
Ok(instrumented)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the cached network if necessary.
|
/// Update the cached network if necessary.
|
||||||
fn update_node_graph(&mut self, document: &mut DocumentMessageHandler, node_to_inspect: Option<NodeId>, ignore_hash: bool) -> Result<(), String> {
|
fn update_node_graph(&mut self, document: &mut DocumentMessageHandler, node_to_inspect: Vec<NodeId>, ignore_hash: bool) -> Result<(), String> {
|
||||||
let network_hash = document.network_interface.network_hash();
|
let network_hash = document.network_interface.network_hash();
|
||||||
// Refresh the graph when it changes or the inspect node changes
|
// Refresh the graph when it changes or the inspect node changes
|
||||||
if network_hash != self.node_graph_hash || self.previous_node_to_inspect != node_to_inspect || ignore_hash {
|
if network_hash != self.node_graph_hash || self.previous_node_to_inspect != node_to_inspect || ignore_hash {
|
||||||
let network = document.network_interface.document_network().clone();
|
let network = document.network_interface.document_network().clone();
|
||||||
self.previous_node_to_inspect = node_to_inspect;
|
self.previous_node_to_inspect.clone_from(&node_to_inspect);
|
||||||
self.node_graph_hash = network_hash;
|
self.node_graph_hash = network_hash;
|
||||||
|
|
||||||
self.runtime_io
|
self.runtime_io
|
||||||
@@ -174,7 +178,7 @@ impl NodeGraphExecutor {
|
|||||||
viewport_resolution: UVec2,
|
viewport_resolution: UVec2,
|
||||||
viewport_scale: f64,
|
viewport_scale: f64,
|
||||||
time: TimingInformation,
|
time: TimingInformation,
|
||||||
node_to_inspect: Option<NodeId>,
|
node_to_inspect: Vec<NodeId>,
|
||||||
ignore_hash: bool,
|
ignore_hash: bool,
|
||||||
pointer: DVec2,
|
pointer: DVec2,
|
||||||
) -> Result<Message, String> {
|
) -> Result<Message, String> {
|
||||||
@@ -271,7 +275,7 @@ impl NodeGraphExecutor {
|
|||||||
|
|
||||||
// Execute the node graph
|
// Execute the node graph
|
||||||
self.runtime_io
|
self.runtime_io
|
||||||
.send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, node_to_inspect: None }))
|
.send(GraphRuntimeRequest::GraphUpdate(GraphUpdate { network, node_to_inspect: Vec::new() }))
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
let execution_id = self.queue_execution(render_config);
|
let execution_id = self.queue_execution(render_config);
|
||||||
self.futures.push_back((
|
self.futures.push_back((
|
||||||
@@ -338,7 +342,7 @@ impl NodeGraphExecutor {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Update the Data panel on the frontend using the value of the inspect result.
|
// Update the Data panel on the frontend using the value of the inspect result.
|
||||||
if let Some(inspect_result) = (self.previous_node_to_inspect.is_some()).then_some(inspect_result).flatten() {
|
if let Some(inspect_result) = (!self.previous_node_to_inspect.is_empty()).then_some(inspect_result).flatten() {
|
||||||
responses.add(DataPanelMessage::UpdateLayout { inspect_result });
|
responses.add(DataPanelMessage::UpdateLayout { inspect_result });
|
||||||
} else {
|
} else {
|
||||||
responses.add(DataPanelMessage::ClearLayout);
|
responses.add(DataPanelMessage::ClearLayout);
|
||||||
|
|||||||
@@ -76,8 +76,10 @@ pub enum GraphRuntimeRequest {
|
|||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct GraphUpdate {
|
pub struct GraphUpdate {
|
||||||
pub(super) network: NodeNetwork,
|
pub(super) network: NodeNetwork,
|
||||||
/// The node that should be temporary inspected during execution
|
/// Full path from the root network to the node that should be temporarily inspected during execution.
|
||||||
pub(super) node_to_inspect: Option<NodeId>,
|
/// The last element is the inspect target; preceding elements identify the nested subnetwork it lives in,
|
||||||
|
/// so the runtime can splice its monitor node alongside the target instead of only at the top level.
|
||||||
|
pub(super) node_to_inspect: Vec<NodeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
@@ -235,7 +237,7 @@ impl NodeRuntime {
|
|||||||
}
|
}
|
||||||
GraphRuntimeRequest::GraphUpdate(GraphUpdate { mut network, node_to_inspect }) => {
|
GraphRuntimeRequest::GraphUpdate(GraphUpdate { mut network, node_to_inspect }) => {
|
||||||
// Insert the monitor node to manage the inspection
|
// Insert the monitor node to manage the inspection
|
||||||
self.inspect_state = node_to_inspect.map(|inspect| InspectState::monitor_inspect_node(&mut network, inspect));
|
self.inspect_state = InspectState::monitor_inspect_node(&mut network, &node_to_inspect);
|
||||||
|
|
||||||
self.old_graph = Some(network.clone());
|
self.old_graph = Some(network.clone());
|
||||||
|
|
||||||
@@ -264,7 +266,7 @@ impl NodeRuntime {
|
|||||||
self.update_thumbnails = false;
|
self.update_thumbnails = false;
|
||||||
|
|
||||||
// Resolve the result from the inspection by accessing the monitor node
|
// Resolve the result from the inspection by accessing the monitor node
|
||||||
let inspect_result = self.inspect_state.and_then(|state| state.access(&self.executor));
|
let inspect_result = self.inspect_state.as_ref().and_then(|state| state.access(&self.executor));
|
||||||
|
|
||||||
let (result, texture) = match result {
|
let (result, texture) = match result {
|
||||||
Ok(TaggedValue::RenderOutput(RenderOutput {
|
Ok(TaggedValue::RenderOutput(RenderOutput {
|
||||||
@@ -408,7 +410,11 @@ impl NodeRuntime {
|
|||||||
|
|
||||||
for monitor_node_path in &self.monitor_nodes {
|
for monitor_node_path in &self.monitor_nodes {
|
||||||
// Skip the inspect monitor node
|
// Skip the inspect monitor node
|
||||||
if self.inspect_state.is_some_and(|inspect_state| monitor_node_path.last().copied() == Some(inspect_state.monitor_node)) {
|
if self
|
||||||
|
.inspect_state
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|inspect_state| monitor_node_path.last().copied() == Some(inspect_state.monitor_node))
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -540,16 +546,22 @@ pub async fn replace_application_io(application_io: PlatformApplicationIo) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Which node is inspected and which monitor node is used (if any) for the current execution
|
/// Which node is inspected and which monitor node is used (if any) for the current execution
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone)]
|
||||||
struct InspectState {
|
struct InspectState {
|
||||||
inspect_node: NodeId,
|
inspect_node: NodeId,
|
||||||
monitor_node: NodeId,
|
monitor_node: NodeId,
|
||||||
|
/// Path of the subnetwork the monitor was inserted into (i.e., the parent of `inspect_node`).
|
||||||
|
/// Used to construct the full node path when introspecting the monitor's value.
|
||||||
|
monitor_parent_path: Vec<NodeId>,
|
||||||
}
|
}
|
||||||
/// The resulting value from the temporary inspected during execution
|
/// The resulting value from the temporary inspected during execution
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
pub struct InspectResult {
|
pub struct InspectResult {
|
||||||
introspected_data: Option<Arc<dyn std::any::Any + Send + Sync + 'static>>,
|
introspected_data: Option<Arc<dyn std::any::Any + Send + Sync + 'static>>,
|
||||||
pub inspect_node: NodeId,
|
/// Full path from the root network to the inspected node, with the node itself as the last element.
|
||||||
|
/// The parent slice (`split_last().1`) is the network the node lives in, which downstream consumers
|
||||||
|
/// (e.g. the Data panel) need when looking the node up via `network_interface.is_layer(...)` etc.
|
||||||
|
pub inspect_node_path: Vec<NodeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InspectResult {
|
impl InspectResult {
|
||||||
@@ -561,17 +573,21 @@ impl InspectResult {
|
|||||||
// This is very ugly but is required to be inside a message
|
// This is very ugly but is required to be inside a message
|
||||||
impl PartialEq for InspectResult {
|
impl PartialEq for InspectResult {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.inspect_node == other.inspect_node
|
self.inspect_node_path == other.inspect_node_path
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InspectState {
|
impl InspectState {
|
||||||
/// Insert the monitor node to manage the inspection
|
/// Insert the monitor node alongside the inspect node identified by `inspect_path` (full path from root, last element is the target).
|
||||||
pub fn monitor_inspect_node(network: &mut NodeNetwork, inspect_node: NodeId) -> Self {
|
/// Returns `None` if the path is empty or doesn't resolve to a node inside a reachable subnetwork.
|
||||||
|
pub fn monitor_inspect_node(network: &mut NodeNetwork, inspect_path: &[NodeId]) -> Option<Self> {
|
||||||
|
let (inspect_node, parent_path) = inspect_path.split_last()?;
|
||||||
|
let inspect_node = *inspect_node;
|
||||||
|
let target_network = navigate_to_network_mut(network, parent_path)?;
|
||||||
let monitor_id = NodeId::new();
|
let monitor_id = NodeId::new();
|
||||||
|
|
||||||
// It is necessary to replace the inputs before inserting the monitor node to avoid changing the input of the new monitor node
|
// It is necessary to replace the inputs before inserting the monitor node to avoid changing the input of the new monitor node
|
||||||
for input in network.nodes.values_mut().flat_map(|node| node.inputs.iter_mut()).chain(&mut network.exports) {
|
for input in target_network.nodes.values_mut().flat_map(|node| node.inputs.iter_mut()).chain(&mut target_network.exports) {
|
||||||
let NodeInput::Node { node_id, output_index, .. } = input else { continue };
|
let NodeInput::Node { node_id, output_index, .. } = input else { continue };
|
||||||
// We only care about the primary output of our inspect node
|
// We only care about the primary output of our inspect node
|
||||||
if *output_index != 0 || *node_id != inspect_node {
|
if *output_index != 0 || *node_id != inspect_node {
|
||||||
@@ -588,21 +604,39 @@ impl InspectState {
|
|||||||
skip_deduplication: true,
|
skip_deduplication: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
network.nodes.insert(monitor_id, monitor_node);
|
target_network.nodes.insert(monitor_id, monitor_node);
|
||||||
|
|
||||||
Self {
|
Some(Self {
|
||||||
inspect_node,
|
inspect_node,
|
||||||
monitor_node: monitor_id,
|
monitor_node: monitor_id,
|
||||||
}
|
monitor_parent_path: parent_path.to_vec(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
/// Resolve the result from the inspection by accessing the monitor node
|
/// Resolve the result from the inspection by accessing the monitor node
|
||||||
fn access(&self, executor: &DynamicExecutor) -> Option<InspectResult> {
|
fn access(&self, executor: &DynamicExecutor) -> Option<InspectResult> {
|
||||||
let introspected_data = executor.introspect(&[self.monitor_node]).inspect_err(|e| warn!("Failed to introspect monitor node {e}")).ok();
|
// The executor's source map indexes by full path from root, so prepend the subnetwork path to the monitor ID.
|
||||||
|
let mut monitor_path = self.monitor_parent_path.clone();
|
||||||
|
monitor_path.push(self.monitor_node);
|
||||||
|
let introspected_data = executor.introspect(&monitor_path).inspect_err(|e| warn!("Failed to introspect monitor node {e}")).ok();
|
||||||
// TODO: Consider displaying the error instead of ignoring it
|
// TODO: Consider displaying the error instead of ignoring it
|
||||||
|
|
||||||
Some(InspectResult {
|
let mut inspect_node_path = self.monitor_parent_path.clone();
|
||||||
inspect_node: self.inspect_node,
|
inspect_node_path.push(self.inspect_node);
|
||||||
introspected_data,
|
Some(InspectResult { inspect_node_path, introspected_data })
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Walks `network` down through `path`, returning a mutable reference to the nested `NodeNetwork`
|
||||||
|
/// at the end. Each path element must name a `DocumentNode` whose implementation is `Network(...)`.
|
||||||
|
/// Returns `None` if any step is missing or doesn't refer to a subnetwork.
|
||||||
|
fn navigate_to_network_mut<'a>(network: &'a mut NodeNetwork, path: &[NodeId]) -> Option<&'a mut NodeNetwork> {
|
||||||
|
let mut current = network;
|
||||||
|
for node_id in path {
|
||||||
|
let node = current.nodes.get_mut(node_id)?;
|
||||||
|
current = match &mut node.implementation {
|
||||||
|
DocumentNodeImplementation::Network(nested) => nested,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Some(current)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user