/// <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);
        }