/// <summary> /// Shows the secondary context menu. /// </summary> /// <param name="location">The location in the Surface Space.</param> /// <param name="controlUnderMouse">The Surface Control that is under the cursor. Used to customize the menu.</param> public virtual void ShowSecondaryCM(Vector2 location, SurfaceControl controlUnderMouse) { var selection = SelectedNodes; if (selection.Count == 0) { return; } // Create secondary context menu var menu = new FlaxEditor.GUI.ContextMenu.ContextMenu(); if (_onSave != null) { menu.AddButton("Save", _onSave).Enabled = CanEdit; menu.AddSeparator(); } _cmCopyButton = menu.AddButton("Copy", Copy); menu.AddButton("Paste", Paste).Enabled = CanEdit && CanPaste(); _cmDuplicateButton = menu.AddButton("Duplicate", Duplicate); _cmDuplicateButton.Enabled = CanEdit; var canRemove = CanEdit && selection.All(node => (node.Archetype.Flags & NodeFlags.NoRemove) == 0); menu.AddButton("Cut", Cut).Enabled = canRemove; menu.AddButton("Delete", Delete).Enabled = canRemove; if (_supportsDebugging) { menu.AddSeparator(); if (selection.Count == 1) { menu.AddButton(selection[0].Breakpoint.Set ? "Delete breakpoint" : "Add breakpoint", () => { foreach (var node in Nodes) { if (node.IsSelected) { node.Breakpoint.Set = !node.Breakpoint.Set; node.Breakpoint.Enabled = true; OnNodeBreakpointEdited(node); break; } } }); menu.AddButton("Toggle breakpoint", () => { foreach (var node in Nodes) { if (node.IsSelected) { node.Breakpoint.Enabled = !node.Breakpoint.Enabled; OnNodeBreakpointEdited(node); break; } } }).Enabled = selection[0].Breakpoint.Set; } menu.AddSeparator(); menu.AddButton("Delete all breakpoints", () => { foreach (var node in Nodes) { if (node.Breakpoint.Set) { node.Breakpoint.Set = false; OnNodeBreakpointEdited(node); } } }).Enabled = Nodes.Any(x => x.Breakpoint.Set); menu.AddButton("Enable all breakpoints", () => { foreach (var node in Nodes) { if (!node.Breakpoint.Enabled) { node.Breakpoint.Enabled = true; OnNodeBreakpointEdited(node); } } }).Enabled = Nodes.Any(x => x.Breakpoint.Set && !x.Breakpoint.Enabled); menu.AddButton("Disable all breakpoints", () => { foreach (var node in Nodes) { if (node.Breakpoint.Enabled) { node.Breakpoint.Enabled = false; OnNodeBreakpointEdited(node); } } }).Enabled = Nodes.Any(x => x.Breakpoint.Set && x.Breakpoint.Enabled); } menu.AddSeparator(); _cmFormatNodesConnectionButton = menu.AddButton("Format node(s)", () => { FormatGraph(SelectedNodes); }); _cmFormatNodesConnectionButton.Enabled = CanEdit && HasNodesSelection; _cmRemoveNodeConnectionsButton = menu.AddButton("Remove all connections to that node(s)", () => { var nodes = ((List <SurfaceNode>)menu.Tag); if (Undo != null) { var actions = new List <IUndoAction>(nodes.Count); foreach (var node in nodes) { var action = new EditNodeConnections(Context, node); node.RemoveConnections(); action.End(); actions.Add(action); } Undo.AddAction(new MultiUndoAction(actions, actions[0].ActionString)); } else { foreach (var node in nodes) { node.RemoveConnections(); } } MarkAsEdited(); }); _cmRemoveNodeConnectionsButton.Enabled = CanEdit; _cmRemoveBoxConnectionsButton = menu.AddButton("Remove all connections to that box", () => { var boxUnderMouse = (Box)_cmRemoveBoxConnectionsButton.Tag; if (Undo != null) { var action = new EditNodeConnections(Context, boxUnderMouse.ParentNode); boxUnderMouse.RemoveConnections(); action.End(); Undo.AddAction(action); } else { boxUnderMouse.RemoveConnections(); } MarkAsEdited(); }); _cmRemoveBoxConnectionsButton.Enabled = CanEdit; { var boxUnderMouse = GetChildAtRecursive(location) as Box; _cmRemoveBoxConnectionsButton.Enabled = boxUnderMouse != null && boxUnderMouse.HasAnyConnection; _cmRemoveBoxConnectionsButton.Tag = boxUnderMouse; } controlUnderMouse?.OnShowSecondaryContextMenu(menu, controlUnderMouse.PointFromParent(location)); OnShowSecondaryContextMenu(menu, controlUnderMouse); // Show secondary context menu _cmStartPos = location; menu.Tag = selection; menu.MaximumItemsInViewCount = 24; menu.Show(this, location); }
/// <summary> /// Pastes the copied items. /// </summary> public void Paste() { if (!CanEdit) { return; } var data = Clipboard.Text; if (data == null || data.Length < 2) { return; } try { // Load Mr Json var model = FlaxEngine.Json.JsonSerializer.Deserialize <DataModel>(data); if (model.Nodes == null) { model.Nodes = new DataModelNode[0]; } // Build the nodes IDs mapping (need to generate new IDs for the pasted nodes and preserve the internal connections) var idsMapping = new Dictionary <uint, uint>(); for (int i = 0; i < model.Nodes.Length; i++) { uint result = 1; while (true) { bool valid = true; if (idsMapping.ContainsValue(result)) { result++; valid = false; } else { for (int j = 0; j < Nodes.Count; j++) { if (Nodes[j].ID == result) { result++; valid = false; break; } } } if (valid) { break; } } idsMapping.Add(model.Nodes[i].ID, result); } // Find controls upper left location Vector2 upperLeft = new Vector2(model.Nodes[0].X, model.Nodes[0].Y); for (int i = 1; i < model.Nodes.Length; i++) { upperLeft.X = Mathf.Min(upperLeft.X, model.Nodes[i].X); upperLeft.Y = Mathf.Min(upperLeft.Y, model.Nodes[i].Y); } // Create nodes var nodes = new Dictionary <uint, SurfaceNode>(); var nodesData = new Dictionary <uint, DataModelNode>(); for (int i = 0; i < model.Nodes.Length; i++) { var nodeData = model.Nodes[i]; // Peek type if (!NodeFactory.GetArchetype(NodeArchetypes, nodeData.GroupID, nodeData.TypeID, out var groupArchetype, out var nodeArchetype)) { throw new InvalidOperationException("Unknown node type."); } // Validate given node type if (!CanUseNodeType(nodeArchetype)) { continue; } // Create var node = NodeFactory.CreateNode(idsMapping[nodeData.ID], Context, groupArchetype, nodeArchetype); if (node == null) { throw new InvalidOperationException("Failed to create node."); } Nodes.Add(node); nodes.Add(nodeData.ID, node); nodesData.Add(nodeData.ID, nodeData); // Initialize if (nodeData.Values != null && node.Values.Length > 0) { if (node.Values != null && node.Values.Length == nodeData.Values.Length) { // Copy and fix values (Json deserializes may output them in a different format) for (int l = 0; l < node.Values.Length; l++) { var src = nodeData.Values[l].Value; var dst = node.Values[l]; if (nodeData.Values[l].EnumTypeName != null) { var enumType = TypeUtils.GetManagedType(nodeData.Values[l].EnumTypeName); if (enumType != null) { src = Enum.ToObject(enumType, src); } } else if (src is JToken token) { if (dst is Vector2) { src = new Vector2(token["X"].Value <float>(), token["Y"].Value <float>()); } else if (dst is Vector3) { src = new Vector3(token["X"].Value <float>(), token["Y"].Value <float>(), token["Z"].Value <float>()); } else if (dst is Vector4) { src = new Vector4(token["X"].Value <float>(), token["Y"].Value <float>(), token["Z"].Value <float>(), token["W"].Value <float>()); } else if (dst is Color) { src = new Color(token["R"].Value <float>(), token["G"].Value <float>(), token["B"].Value <float>(), token["A"].Value <float>()); } else { Editor.LogWarning("Unknown pasted node value token: " + token); src = dst; } } else if (src is double asDouble) { src = (float)asDouble; } else if (dst is Guid) { src = Guid.Parse((string)src); } else if (dst is int) { src = Convert.ToInt32(src); } else if (dst is float) { src = Convert.ToSingle(src); } else if (dst is byte[] && src is string s) { src = Convert.FromBase64String(s); } node.Values[l] = src; } } else { Editor.LogWarning("Invalid node custom values."); } } Context.OnControlLoaded(node); } // Setup connections foreach (var e in nodes) { var node = e.Value; var nodeData = nodesData[e.Key]; if (nodeData.Boxes != null) { foreach (var boxData in nodeData.Boxes) { var box = node.GetBox(boxData.ID); if (box == null || boxData.BoxIDs == null || boxData.NodeIDs == null || boxData.BoxIDs.Length != boxData.NodeIDs.Length) { continue; } for (int i = 0; i < boxData.NodeIDs.Length; i++) { if (nodes.TryGetValue(boxData.NodeIDs[i], out var targetNode) && targetNode.TryGetBox(boxData.BoxIDs[i], out var targetBox)) { box.Connections.Add(targetBox); } } } } } // Arrange controls foreach (var e in nodes) { var node = e.Value; var nodeData = nodesData[e.Key]; var pos = new Vector2(nodeData.X, nodeData.Y) - upperLeft; node.Location = ViewPosition + pos + _mousePos / ViewScale; } // Post load foreach (var node in nodes) { node.Value.OnSurfaceLoaded(); } // Add undo action if (Undo != null && nodes.Count > 0) { var actions = new List <IUndoAction>(); foreach (var node in nodes) { var action = new AddRemoveNodeAction(node.Value, true); actions.Add(action); } foreach (var node in nodes) { var action = new EditNodeConnections(Context, node.Value); action.End(); actions.Add(action); } Undo.AddAction(new MultiUndoAction(actions, nodes.Count == 1 ? "Paste node" : "Paste nodes")); } // Select those nodes Select(nodes.Values); MarkAsEdited(); } catch (Exception ex) { Editor.LogWarning("Failed to paste Visject Surface nodes"); Editor.LogWarning(ex); MessageBox.Show("Failed to paste Visject Surface nodes. " + ex.Message, "Paste failed", MessageBoxButtons.OK, MessageBoxIcon.Error); } }
/// <summary> /// Shows the secondary context menu. /// </summary> /// <param name="location">The location in the Surface Space.</param> /// <param name="controlUnderMouse">The Surface Control that is under the cursor. Used to customize the menu.</param> public virtual void ShowSecondaryCM(Vector2 location, SurfaceControl controlUnderMouse) { var selection = SelectedNodes; if (selection.Count == 0) { return; } // Create secondary context menu var menu = new FlaxEditor.GUI.ContextMenu.ContextMenu(); menu.AddButton("Save", _onSave); menu.AddSeparator(); _cmCopyButton = menu.AddButton("Copy", Copy); menu.AddButton("Paste", Paste).Enabled = CanPaste(); _cmDuplicateButton = menu.AddButton("Duplicate", Duplicate); var canRemove = selection.All(node => (node.Archetype.Flags & NodeFlags.NoRemove) == 0); menu.AddButton("Cut", Cut).Enabled = canRemove; menu.AddButton("Delete", Delete).Enabled = canRemove; menu.AddSeparator(); _cmRemoveNodeConnectionsButton = menu.AddButton("Remove all connections to that node(s)", () => { var nodes = ((List <SurfaceNode>)menu.Tag); if (Undo != null) { var actions = new List <IUndoAction>(nodes.Count); foreach (var node in nodes) { var action = new EditNodeConnections(Context, node); node.RemoveConnections(); action.End(); actions.Add(action); } Undo.AddAction(new MultiUndoAction(actions, actions[0].ActionString)); } else { foreach (var node in nodes) { node.RemoveConnections(); } } MarkAsEdited(); }); _cmRemoveBoxConnectionsButton = menu.AddButton("Remove all connections to that box", () => { var boxUnderMouse = (Box)_cmRemoveBoxConnectionsButton.Tag; if (Undo != null) { var action = new EditNodeConnections(Context, boxUnderMouse.ParentNode); boxUnderMouse.RemoveConnections(); action.End(); Undo.AddAction(action); } else { boxUnderMouse.RemoveConnections(); } MarkAsEdited(); }); { var boxUnderMouse = GetChildAtRecursive(location) as Box; _cmRemoveBoxConnectionsButton.Enabled = boxUnderMouse != null && boxUnderMouse.HasAnyConnection; _cmRemoveBoxConnectionsButton.Tag = boxUnderMouse; } controlUnderMouse?.OnShowSecondaryContextMenu(menu, controlUnderMouse.PointFromParent(location)); OnShowSecondaryContextMenu(menu, controlUnderMouse); // Show secondary context menu _cmStartPos = location; menu.Tag = selection; menu.Show(this, location); }