Nested networks UI (#885)
* Initial UI for nested nodes * Clean up deleting nodes * Print address of nested network * Add exiting network message * Implement the breadcrumb trail * Remove whitespace * Fix double click not registering in Chromium Co-authored-by: Keavon Chambers <keavon@keavon.com>
This commit is contained in:
parent
52117d642c
commit
9d40539dc7
|
|
@ -215,6 +215,11 @@ pub enum FrontendMessage {
|
||||||
nodes: Vec<FrontendNode>,
|
nodes: Vec<FrontendNode>,
|
||||||
links: Vec<FrontendNodeLink>,
|
links: Vec<FrontendNodeLink>,
|
||||||
},
|
},
|
||||||
|
UpdateNodeGraphBarLayout {
|
||||||
|
#[serde(rename = "layoutTarget")]
|
||||||
|
layout_target: LayoutTarget,
|
||||||
|
layout: SubLayout,
|
||||||
|
},
|
||||||
UpdateNodeGraphVisibility {
|
UpdateNodeGraphVisibility {
|
||||||
visible: bool,
|
visible: bool,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -233,6 +233,10 @@ impl LayoutMessageHandler {
|
||||||
layout_target,
|
layout_target,
|
||||||
layout: layout.clone().unwrap_menu_layout(action_input_mapping).layout,
|
layout: layout.clone().unwrap_menu_layout(action_input_mapping).layout,
|
||||||
},
|
},
|
||||||
|
LayoutTarget::NodeGraphBar => FrontendMessage::UpdateNodeGraphBarLayout {
|
||||||
|
layout_target,
|
||||||
|
layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout,
|
||||||
|
},
|
||||||
LayoutTarget::PropertiesOptions => FrontendMessage::UpdatePropertyPanelOptionsLayout {
|
LayoutTarget::PropertiesOptions => FrontendMessage::UpdatePropertyPanelOptionsLayout {
|
||||||
layout_target,
|
layout_target,
|
||||||
layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout,
|
layout: layout.clone().unwrap_widget_layout(action_input_mapping).layout,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ pub enum LayoutTarget {
|
||||||
DocumentMode,
|
DocumentMode,
|
||||||
LayerTreeOptions,
|
LayerTreeOptions,
|
||||||
MenuBar,
|
MenuBar,
|
||||||
|
NodeGraphBar,
|
||||||
PropertiesOptions,
|
PropertiesOptions,
|
||||||
PropertiesSections,
|
PropertiesSections,
|
||||||
ToolOptions,
|
ToolOptions,
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,12 @@ pub enum NodeGraphMessage {
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
},
|
},
|
||||||
DeleteSelectedNodes,
|
DeleteSelectedNodes,
|
||||||
|
DoubleClickNode {
|
||||||
|
node: NodeId,
|
||||||
|
},
|
||||||
|
ExitNestedNetwork {
|
||||||
|
depth_of_nesting: usize,
|
||||||
|
},
|
||||||
ExposeInput {
|
ExposeInput {
|
||||||
node_id: NodeId,
|
node_id: NodeId,
|
||||||
input_index: usize,
|
input_index: usize,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::messages::layout::utility_types::layout_widget::LayoutGroup;
|
use crate::messages::layout::utility_types::layout_widget::{Layout, LayoutGroup, Widget, WidgetCallback, WidgetHolder, WidgetLayout};
|
||||||
|
use crate::messages::layout::utility_types::widgets::button_widgets::BreadcrumbTrailButtons;
|
||||||
use crate::messages::prelude::*;
|
use crate::messages::prelude::*;
|
||||||
use graph_craft::document::value::TaggedValue;
|
use graph_craft::document::value::TaggedValue;
|
||||||
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, DocumentNodeMetadata, NodeId, NodeInput, NodeNetwork};
|
use graph_craft::document::{DocumentNode, DocumentNodeImplementation, DocumentNodeMetadata, NodeId, NodeInput, NodeNetwork};
|
||||||
|
|
@ -94,20 +95,74 @@ impl FrontendNodeType {
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, Default, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct NodeGraphMessageHandler {
|
pub struct NodeGraphMessageHandler {
|
||||||
pub layer_path: Option<Vec<graphene::LayerId>>,
|
pub layer_path: Option<Vec<graphene::LayerId>>,
|
||||||
|
pub nested_path: Vec<graph_craft::document::NodeId>,
|
||||||
pub selected_nodes: Vec<graph_craft::document::NodeId>,
|
pub selected_nodes: Vec<graph_craft::document::NodeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NodeGraphMessageHandler {
|
impl NodeGraphMessageHandler {
|
||||||
/// Get the active graph_craft NodeNetwork struct
|
fn get_root_network<'a>(&self, document: &'a Document) -> Option<&'a graph_craft::document::NodeNetwork> {
|
||||||
fn get_active_network_mut<'a>(&self, document: &'a mut Document) -> Option<&'a mut graph_craft::document::NodeNetwork> {
|
self.layer_path.as_ref().and_then(|path| document.layer(path).ok()).and_then(|layer| match &layer.data {
|
||||||
|
LayerDataType::NodeGraphFrame(n) => Some(&n.network),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_root_network_mut<'a>(&self, document: &'a mut Document) -> Option<&'a mut graph_craft::document::NodeNetwork> {
|
||||||
self.layer_path.as_ref().and_then(|path| document.layer_mut(path).ok()).and_then(|layer| match &mut layer.data {
|
self.layer_path.as_ref().and_then(|path| document.layer_mut(path).ok()).and_then(|layer| match &mut layer.data {
|
||||||
LayerDataType::NodeGraphFrame(n) => Some(&mut n.network),
|
LayerDataType::NodeGraphFrame(n) => Some(&mut n.network),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the active graph_craft NodeNetwork struct
|
||||||
|
fn get_active_network_mut<'a>(&self, document: &'a mut Document) -> Option<&'a mut graph_craft::document::NodeNetwork> {
|
||||||
|
let mut network = self.get_root_network_mut(document);
|
||||||
|
|
||||||
|
for segement in &self.nested_path {
|
||||||
|
network = network.and_then(|network| network.nodes.get_mut(segement)).and_then(|node| node.implementation.get_network_mut());
|
||||||
|
}
|
||||||
|
network
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collect the addresses of the currently viewed nested node e.g. Root -> MyFunFilter -> Exposure
|
||||||
|
fn collect_nested_addresses(&self, document: &Document, responses: &mut VecDeque<Message>) {
|
||||||
|
let mut path = vec!["Root".to_string()];
|
||||||
|
let mut network = self.get_root_network(document);
|
||||||
|
for node_id in &self.nested_path {
|
||||||
|
let node = network.and_then(|network| network.nodes.get(node_id));
|
||||||
|
if let Some(DocumentNode { name, .. }) = node {
|
||||||
|
path.push(name.clone());
|
||||||
|
}
|
||||||
|
network = node.and_then(|node| node.implementation.get_network());
|
||||||
|
}
|
||||||
|
let nesting = path.len();
|
||||||
|
|
||||||
|
responses.push_back(
|
||||||
|
LayoutMessage::SendLayout {
|
||||||
|
layout: Layout::WidgetLayout(WidgetLayout::new(vec![LayoutGroup::Row {
|
||||||
|
widgets: vec![WidgetHolder::new(Widget::BreadcrumbTrailButtons(BreadcrumbTrailButtons {
|
||||||
|
labels: path,
|
||||||
|
on_update: WidgetCallback::new(move |input: &u64| {
|
||||||
|
NodeGraphMessage::ExitNestedNetwork {
|
||||||
|
depth_of_nesting: nesting - (*input as usize) - 1,
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}))],
|
||||||
|
}])),
|
||||||
|
layout_target: crate::messages::layout::utility_types::misc::LayoutTarget::NodeGraphBar,
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn collate_properties(&self, node_graph_frame: &NodeGraphFrameLayer) -> Vec<LayoutGroup> {
|
pub fn collate_properties(&self, node_graph_frame: &NodeGraphFrameLayer) -> Vec<LayoutGroup> {
|
||||||
let network = &node_graph_frame.network;
|
let mut network = &node_graph_frame.network;
|
||||||
|
for segement in &self.nested_path {
|
||||||
|
network = network.nodes.get(segement).and_then(|node| node.implementation.get_network()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
let mut section = Vec::new();
|
let mut section = Vec::new();
|
||||||
for node_id in &self.selected_nodes {
|
for node_id in &self.selected_nodes {
|
||||||
let Some(document_node) = network.nodes.get(node_id) else {
|
let Some(document_node) = network.nodes.get(node_id) else {
|
||||||
|
|
@ -176,8 +231,7 @@ impl NodeGraphMessageHandler {
|
||||||
responses.push_back(FrontendMessage::UpdateNodeGraph { nodes, links }.into());
|
responses.push_back(FrontendMessage::UpdateNodeGraph { nodes, links }.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_node(&mut self, network: &mut NodeNetwork, node_id: NodeId) -> bool {
|
fn remove_references_from_network(network: &mut NodeNetwork, node_id: NodeId) -> bool {
|
||||||
fn remove_from_network(network: &mut NodeNetwork, node_id: NodeId) -> bool {
|
|
||||||
if network.inputs.iter().any(|&id| id == node_id) {
|
if network.inputs.iter().any(|&id| id == node_id) {
|
||||||
warn!("Deleting input node");
|
warn!("Deleting input node");
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -198,7 +252,7 @@ impl NodeGraphMessageHandler {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let Some(node_type) = document_node_types::resolve_document_node_type(&node.name) else{
|
let Some(node_type) = document_node_types::resolve_document_node_type(&node.name) else {
|
||||||
warn!("Removing input of invalid node type '{}'", node.name);
|
warn!("Removing input of invalid node type '{}'", node.name);
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
@ -210,12 +264,14 @@ impl NodeGraphMessageHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let DocumentNodeImplementation::Network(network) = &mut node.implementation {
|
if let DocumentNodeImplementation::Network(network) = &mut node.implementation {
|
||||||
remove_from_network(network, node_id);
|
Self::remove_references_from_network(network, node_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
if remove_from_network(network, node_id) {
|
|
||||||
|
fn remove_node(&mut self, network: &mut NodeNetwork, node_id: NodeId) -> bool {
|
||||||
|
if Self::remove_references_from_network(network, node_id) {
|
||||||
network.nodes.remove(&node_id);
|
network.nodes.remove(&node_id);
|
||||||
self.selected_nodes.retain(|&id| id != node_id);
|
self.selected_nodes.retain(|&id| id != node_id);
|
||||||
true
|
true
|
||||||
|
|
@ -326,6 +382,28 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
NodeGraphMessage::DoubleClickNode { node } => {
|
||||||
|
self.selected_nodes = Vec::new();
|
||||||
|
if let Some(network) = self.get_active_network_mut(document) {
|
||||||
|
if network.nodes.get(&node).and_then(|node| node.implementation.get_network()).is_some() {
|
||||||
|
self.nested_path.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(network) = self.get_active_network_mut(document) {
|
||||||
|
Self::send_graph(network, responses);
|
||||||
|
}
|
||||||
|
self.collect_nested_addresses(document, responses);
|
||||||
|
}
|
||||||
|
NodeGraphMessage::ExitNestedNetwork { depth_of_nesting } => {
|
||||||
|
self.selected_nodes = Vec::new();
|
||||||
|
for _ in 0..depth_of_nesting {
|
||||||
|
self.nested_path.pop();
|
||||||
|
}
|
||||||
|
if let Some(network) = self.get_active_network_mut(document) {
|
||||||
|
Self::send_graph(network, responses);
|
||||||
|
}
|
||||||
|
self.collect_nested_addresses(document, responses);
|
||||||
|
}
|
||||||
NodeGraphMessage::ExposeInput { node_id, input_index, new_exposed } => {
|
NodeGraphMessage::ExposeInput { node_id, input_index, new_exposed } => {
|
||||||
let Some(network) = self.get_active_network_mut(document) else{
|
let Some(network) = self.get_active_network_mut(document) else{
|
||||||
warn!("No network");
|
warn!("No network");
|
||||||
|
|
@ -378,6 +456,7 @@ impl MessageHandler<NodeGraphMessage, (&mut Document, &InputPreprocessorMessageH
|
||||||
let node_types = document_node_types::collect_node_types();
|
let node_types = document_node_types::collect_node_types();
|
||||||
responses.push_back(FrontendMessage::UpdateNodeTypes { node_types }.into());
|
responses.push_back(FrontendMessage::UpdateNodeTypes { node_types }.into());
|
||||||
}
|
}
|
||||||
|
self.collect_nested_addresses(document, responses);
|
||||||
}
|
}
|
||||||
NodeGraphMessage::SelectNodes { nodes } => {
|
NodeGraphMessage::SelectNodes { nodes } => {
|
||||||
self.selected_nodes = nodes;
|
self.selected_nodes = nodes;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,14 @@ impl DocumentInputType {
|
||||||
let default = NodeInput::value(tagged_value, exposed);
|
let default = NodeInput::value(tagged_value, exposed);
|
||||||
Self { name, data_type, default }
|
Self { name, data_type, default }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn _none() -> Self {
|
||||||
|
Self {
|
||||||
|
name: "None",
|
||||||
|
data_type: FrontendGraphDataType::General,
|
||||||
|
default: NodeInput::value(TaggedValue::None, false),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DocumentNodeType {
|
pub struct DocumentNodeType {
|
||||||
|
|
|
||||||
|
|
@ -178,7 +178,7 @@ pub fn add_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<Layo
|
||||||
vec![operand("Input", 0), operand("Addend", 1)]
|
vec![operand("Input", 0), operand("Addend", 1)]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transform_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
|
pub fn _transform_properties(document_node: &DocumentNode, node_id: NodeId) -> Vec<LayoutGroup> {
|
||||||
let translation = {
|
let translation = {
|
||||||
let index = 1;
|
let index = 1;
|
||||||
let input: &NodeInput = document_node.inputs.get(index).unwrap();
|
let input: &NodeInput = document_node.inputs.get(index).unwrap();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<LayoutCol class="node-graph">
|
<LayoutCol class="node-graph">
|
||||||
<LayoutRow class="options-bar"></LayoutRow>
|
<LayoutRow class="options-bar"><WidgetLayout :layout="nodeGraphBarLayout" /></LayoutRow>
|
||||||
<LayoutRow
|
<LayoutRow
|
||||||
class="graph"
|
class="graph"
|
||||||
ref="graph"
|
ref="graph"
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
@pointerdown="(e: PointerEvent) => pointerDown(e)"
|
@pointerdown="(e: PointerEvent) => pointerDown(e)"
|
||||||
@pointermove="(e: PointerEvent) => pointerMove(e)"
|
@pointermove="(e: PointerEvent) => pointerMove(e)"
|
||||||
@pointerup="(e: PointerEvent) => pointerUp(e)"
|
@pointerup="(e: PointerEvent) => pointerUp(e)"
|
||||||
|
@dblclick="(e: MouseEvent) => doubleClick(e)"
|
||||||
:style="{
|
:style="{
|
||||||
'--grid-spacing': `${gridSpacing}px`,
|
'--grid-spacing': `${gridSpacing}px`,
|
||||||
'--grid-offset-x': `${transform.x * transform.scale}px`,
|
'--grid-offset-x': `${transform.x * transform.scale}px`,
|
||||||
|
|
@ -15,7 +16,7 @@
|
||||||
'--dot-radius': `${dotRadius}px`,
|
'--dot-radius': `${dotRadius}px`,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<LayoutCol class="node-list" v-if="nodeListLocation" :style="{ marginLeft: `${nodeListX}px`, marginTop: `${nodeListY}px` }">
|
<LayoutCol class="node-list" data-node-list v-if="nodeListLocation" :style="{ marginLeft: `${nodeListX}px`, marginTop: `${nodeListY}px` }">
|
||||||
<TextInput placeholder="Search Nodes..." :value="searchTerm" @update:value="(val) => (searchTerm = val)" v-focus />
|
<TextInput placeholder="Search Nodes..." :value="searchTerm" @update:value="(val) => (searchTerm = val)" v-focus />
|
||||||
<LayoutCol v-for="nodeCategory in nodeCategories" :key="nodeCategory[0]">
|
<LayoutCol v-for="nodeCategory in nodeCategories" :key="nodeCategory[0]">
|
||||||
<TextLabel>{{ nodeCategory[0] }}</TextLabel>
|
<TextLabel>{{ nodeCategory[0] }}</TextLabel>
|
||||||
|
|
@ -305,6 +306,7 @@ import TextButton from "@/components/widgets/buttons/TextButton.vue";
|
||||||
import TextInput from "@/components/widgets/inputs/TextInput.vue";
|
import TextInput from "@/components/widgets/inputs/TextInput.vue";
|
||||||
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
import IconLabel from "@/components/widgets/labels/IconLabel.vue";
|
||||||
import TextLabel from "@/components/widgets/labels/TextLabel.vue";
|
import TextLabel from "@/components/widgets/labels/TextLabel.vue";
|
||||||
|
import WidgetLayout from "@/components/widgets/WidgetLayout.vue";
|
||||||
|
|
||||||
const WHEEL_RATE = (1 / 600) * 3;
|
const WHEEL_RATE = (1 / 600) * 3;
|
||||||
const GRID_COLLAPSE_SPACING = 10;
|
const GRID_COLLAPSE_SPACING = 10;
|
||||||
|
|
@ -343,6 +345,9 @@ export default defineComponent({
|
||||||
nodes() {
|
nodes() {
|
||||||
return this.nodeGraph.state.nodes;
|
return this.nodeGraph.state.nodes;
|
||||||
},
|
},
|
||||||
|
nodeGraphBarLayout() {
|
||||||
|
return this.nodeGraph.state.nodeGraphBarLayout;
|
||||||
|
},
|
||||||
nodeCategories() {
|
nodeCategories() {
|
||||||
const categories = new Map();
|
const categories = new Map();
|
||||||
this.nodeGraph.state.nodeTypes.forEach((node) => {
|
this.nodeGraph.state.nodeTypes.forEach((node) => {
|
||||||
|
|
@ -484,6 +489,7 @@ export default defineComponent({
|
||||||
this.transform.x -= scrollY / this.transform.scale;
|
this.transform.x -= scrollY / this.transform.scale;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// TODO: Move the event listener from the graph to the window so dragging outside the graph area (or even the browser window) works
|
||||||
pointerDown(e: PointerEvent) {
|
pointerDown(e: PointerEvent) {
|
||||||
if (e.button === 2) {
|
if (e.button === 2) {
|
||||||
const graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el;
|
const graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el;
|
||||||
|
|
@ -497,14 +503,22 @@ export default defineComponent({
|
||||||
|
|
||||||
const port = (e.target as HTMLDivElement).closest("[data-port]") as HTMLDivElement;
|
const port = (e.target as HTMLDivElement).closest("[data-port]") as HTMLDivElement;
|
||||||
const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined;
|
const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined;
|
||||||
const nodeList = (e.target as HTMLElement).closest(".node-list") as HTMLElement | undefined;
|
const nodeId = node?.getAttribute("data-node") || undefined;
|
||||||
|
const nodeList = (e.target as HTMLElement).closest("[data-node-list]") as HTMLElement | undefined;
|
||||||
|
|
||||||
|
// If the user is clicking on the add nodes list, exit here
|
||||||
|
if (nodeList) return;
|
||||||
|
|
||||||
|
// Clicked on a port dot
|
||||||
if (port) {
|
if (port) {
|
||||||
const isOutput = Boolean(port.getAttribute("data-port") === "output");
|
const isOutput = Boolean(port.getAttribute("data-port") === "output");
|
||||||
|
|
||||||
if (isOutput) this.linkInProgressFromConnector = port;
|
if (isOutput) this.linkInProgressFromConnector = port;
|
||||||
} else {
|
|
||||||
const nodeId = node?.getAttribute("data-node") || undefined;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clicked on a node
|
||||||
if (nodeId) {
|
if (nodeId) {
|
||||||
const id = BigInt(nodeId);
|
const id = BigInt(nodeId);
|
||||||
if (e.shiftKey || e.ctrlKey) {
|
if (e.shiftKey || e.ctrlKey) {
|
||||||
|
|
@ -518,19 +532,24 @@ export default defineComponent({
|
||||||
|
|
||||||
if (this.selected.includes(id)) {
|
if (this.selected.includes(id)) {
|
||||||
this.draggingNodes = { startX: e.x, startY: e.y, roundX: 0, roundY: 0 };
|
this.draggingNodes = { startX: e.x, startY: e.y, roundX: 0, roundY: 0 };
|
||||||
const graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el;
|
|
||||||
graphDiv?.setPointerCapture(e.pointerId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.editor.instance.selectNodes(new BigUint64Array(this.selected));
|
this.editor.instance.selectNodes(new BigUint64Array(this.selected));
|
||||||
} else if (!nodeList) {
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clicked on the graph background
|
||||||
|
this.panning = true;
|
||||||
this.selected = [];
|
this.selected = [];
|
||||||
this.editor.instance.selectNodes(new BigUint64Array(this.selected));
|
this.editor.instance.selectNodes(new BigUint64Array(this.selected));
|
||||||
const graphDiv: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el;
|
},
|
||||||
graphDiv?.setPointerCapture(e.pointerId);
|
doubleClick(e: MouseEvent) {
|
||||||
|
const node = (e.target as HTMLElement).closest("[data-node]") as HTMLElement | undefined;
|
||||||
this.panning = true;
|
const nodeId = node?.getAttribute("data-node") || undefined;
|
||||||
}
|
if (nodeId) {
|
||||||
|
const id = BigInt(nodeId);
|
||||||
|
this.editor.instance.doubleClickNode(id);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pointerMove(e: PointerEvent) {
|
pointerMove(e: PointerEvent) {
|
||||||
|
|
@ -556,9 +575,6 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pointerUp(e: PointerEvent) {
|
pointerUp(e: PointerEvent) {
|
||||||
const graph: HTMLDivElement | undefined = (this.$refs.graph as typeof LayoutCol | undefined)?.$el;
|
|
||||||
graph?.releasePointerCapture(e.pointerId);
|
|
||||||
|
|
||||||
this.panning = false;
|
this.panning = false;
|
||||||
|
|
||||||
if (this.linkInProgressToConnector instanceof HTMLDivElement && this.linkInProgressFromConnector) {
|
if (this.linkInProgressToConnector instanceof HTMLDivElement && this.linkInProgressFromConnector) {
|
||||||
|
|
@ -617,6 +633,7 @@ export default defineComponent({
|
||||||
TextLabel,
|
TextLabel,
|
||||||
TextButton,
|
TextButton,
|
||||||
TextInput,
|
TextInput,
|
||||||
|
WidgetLayout,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { reactive, readonly } from "vue";
|
import { reactive, readonly } from "vue";
|
||||||
|
|
||||||
import { type Editor } from "@/wasm-communication/editor";
|
import { type Editor } from "@/wasm-communication/editor";
|
||||||
import { type FrontendNode, type FrontendNodeLink, type FrontendNodeType, UpdateNodeGraph, UpdateNodeTypes } from "@/wasm-communication/messages";
|
import { type FrontendNode, type FrontendNodeLink, type FrontendNodeType, UpdateNodeGraph, UpdateNodeTypes, UpdateNodeGraphBarLayout, defaultWidgetLayout } from "@/wasm-communication/messages";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
export function createNodeGraphState(editor: Editor) {
|
export function createNodeGraphState(editor: Editor) {
|
||||||
|
|
@ -9,6 +9,7 @@ export function createNodeGraphState(editor: Editor) {
|
||||||
nodes: [] as FrontendNode[],
|
nodes: [] as FrontendNode[],
|
||||||
links: [] as FrontendNodeLink[],
|
links: [] as FrontendNodeLink[],
|
||||||
nodeTypes: [] as FrontendNodeType[],
|
nodeTypes: [] as FrontendNodeType[],
|
||||||
|
nodeGraphBarLayout: defaultWidgetLayout(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set up message subscriptions on creation
|
// Set up message subscriptions on creation
|
||||||
|
|
@ -19,6 +20,9 @@ export function createNodeGraphState(editor: Editor) {
|
||||||
editor.subscriptions.subscribeJsMessage(UpdateNodeTypes, (updateNodeTypes) => {
|
editor.subscriptions.subscribeJsMessage(UpdateNodeTypes, (updateNodeTypes) => {
|
||||||
state.nodeTypes = updateNodeTypes.nodeTypes;
|
state.nodeTypes = updateNodeTypes.nodeTypes;
|
||||||
});
|
});
|
||||||
|
editor.subscriptions.subscribeJsMessage(UpdateNodeGraphBarLayout, (updateNodeGraphBarLayout) => {
|
||||||
|
state.nodeGraphBarLayout = updateNodeGraphBarLayout;
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
state: readonly(state) as typeof state,
|
state: readonly(state) as typeof state,
|
||||||
|
|
|
||||||
|
|
@ -1313,6 +1313,15 @@ export class UpdateMenuBarLayout extends JsMessage {
|
||||||
layout!: MenuBarEntry[];
|
layout!: MenuBarEntry[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UpdateNodeGraphBarLayout extends JsMessage {
|
||||||
|
layoutTarget!: unknown;
|
||||||
|
|
||||||
|
// TODO: Replace `any` with correct typing
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
@Transform(({ value }: { value: any }) => createWidgetLayout(value))
|
||||||
|
layout!: LayoutGroup[];
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
function createMenuLayout(menuBarEntry: any[]): MenuBarEntry[] {
|
function createMenuLayout(menuBarEntry: any[]): MenuBarEntry[] {
|
||||||
return menuBarEntry.map((entry) => ({
|
return menuBarEntry.map((entry) => ({
|
||||||
|
|
@ -1382,6 +1391,7 @@ export const messageMakers: Record<string, MessageMaker> = {
|
||||||
UpdateMenuBarLayout,
|
UpdateMenuBarLayout,
|
||||||
UpdateMouseCursor,
|
UpdateMouseCursor,
|
||||||
UpdateNodeGraph,
|
UpdateNodeGraph,
|
||||||
|
UpdateNodeGraphBarLayout,
|
||||||
UpdateNodeTypes,
|
UpdateNodeTypes,
|
||||||
UpdateNodeGraphVisibility,
|
UpdateNodeGraphVisibility,
|
||||||
UpdateOpenDocumentsList,
|
UpdateOpenDocumentsList,
|
||||||
|
|
|
||||||
|
|
@ -598,6 +598,13 @@ impl JsEditorHandle {
|
||||||
self.dispatch(message);
|
self.dispatch(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Notifies the backend that the user double clicked a node
|
||||||
|
#[wasm_bindgen(js_name = doubleClickNode)]
|
||||||
|
pub fn double_click_node(&self, node: u64) {
|
||||||
|
let message = NodeGraphMessage::DoubleClickNode { node };
|
||||||
|
self.dispatch(message);
|
||||||
|
}
|
||||||
|
|
||||||
/// Notifies the backend that the selected nodes have been moved
|
/// Notifies the backend that the selected nodes have been moved
|
||||||
#[wasm_bindgen(js_name = moveSelectedNodes)]
|
#[wasm_bindgen(js_name = moveSelectedNodes)]
|
||||||
pub fn move_selected_nodes(&self, displacement_x: i32, displacement_y: i32) {
|
pub fn move_selected_nodes(&self, displacement_x: i32, displacement_y: i32) {
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,24 @@ pub enum DocumentNodeImplementation {
|
||||||
Unresolved(NodeIdentifier),
|
Unresolved(NodeIdentifier),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DocumentNodeImplementation {
|
||||||
|
pub fn get_network(&self) -> Option<&NodeNetwork> {
|
||||||
|
if let DocumentNodeImplementation::Network(n) = self {
|
||||||
|
Some(n)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_network_mut(&mut self) -> Option<&mut NodeNetwork> {
|
||||||
|
if let DocumentNodeImplementation::Network(n) = self {
|
||||||
|
Some(n)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, DynAny)]
|
#[derive(Clone, Debug, Default, PartialEq, DynAny)]
|
||||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub struct NodeNetwork {
|
pub struct NodeNetwork {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue