DialogueEditor Zooming out

Has anyone acomplished making the mouse wheel zoom in/out the DialogueEditor to reveal all the connected nodes?

I had a go, and got it some what working, but there is an issue where the visible area of the viewport does not seem to expand with the zoom, so you cant actually see all the nodes they are sort of cropped off (see screen shot)

Attached is my script so far if anyone can easily spot what is going wrong, that would be awesome. Bonus points would be having he background grid image scale up and down also like it does in the animator tool…

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;

namespace RPG.Dialogue.Editor
{
    public static class EditorZoomArea
    {
        private static Matrix4x4 prevGuiMatrix;
        public static void Begin(float zoomScale, Rect screenCoordsArea)
        {
            prevGuiMatrix = GUI.matrix;
            Matrix4x4 translation = Matrix4x4.TRS(screenCoordsArea.center, Quaternion.identity, Vector3.one);
            Matrix4x4 scale = Matrix4x4.Scale(new Vector3(zoomScale, zoomScale, 1.0f));
            GUI.matrix = translation * scale * translation.inverse * GUI.matrix;
            GUI.BeginGroup(new Rect(0, 0, screenCoordsArea.width, screenCoordsArea.height));
        }

        public static void End()
        {
            GUI.EndGroup();
            GUI.matrix = prevGuiMatrix;
        }

    }

    public class DialogueEditor : EditorWindow
    {
        Dialogue selectedDialogue = null;
        [NonSerialized] GUIStyle nodeStyle;
        [NonSerialized] GUIStyle playerNodeStyle;
        [NonSerialized] DialogueNode draggingNode = null;
        [NonSerialized] Vector2 draggingOffset;
        [NonSerialized] DialogueNode creatingNode = null;
        [NonSerialized] DialogueNode deletingNode = null;
        [NonSerialized] DialogueNode linkingParentNode = null;

        Vector2 scrollPosition;
        [NonSerialized] bool draggingCanvas = false;
        [NonSerialized] Vector2 draggingCanvasOffset;
        float zoom = 1.0f; // Zoom factor
        const float zoomMin = 0.5f;
        const float zoomMax = 2.0f;
        const float canvasSize = 8000;
        const float backgroundSize = 50;

        [MenuItem("Window/Dialogue Editor")]
        public static void ShowEditorWindow()
        {
            GetWindow(typeof(DialogueEditor), false, "Dialogue Editor");
        }

        [OnOpenAsset(1)]
        public static bool OnOpenAsset(int instanceID, int line)
        {
            Dialogue dialogue = EditorUtility.InstanceIDToObject(instanceID) as Dialogue;
            if(dialogue != null)
            {
                ShowEditorWindow();
                return true;
            }
            return false;
        }
        private void OnEnable()
        {
            Selection.selectionChanged += OnSelectionChange;

            nodeStyle = new GUIStyle();
            nodeStyle.normal.background = EditorGUIUtility.Load("node0") as Texture2D;
            nodeStyle.normal.textColor = Color.white;
            nodeStyle.padding = new RectOffset(20, 20, 20, 20);
            nodeStyle.border = new RectOffset(12, 12, 12, 12);

            playerNodeStyle = new GUIStyle();
            playerNodeStyle.normal.background = EditorGUIUtility.Load("node1") as Texture2D;
            playerNodeStyle.normal.textColor = Color.white;
            playerNodeStyle.padding = new RectOffset(20, 20, 20, 20);
            playerNodeStyle.border = new RectOffset(12, 12, 12, 12);
        }
        private void OnSelectionChange()
        {
            Dialogue newDialogue = Selection.activeObject as Dialogue;
            if(newDialogue != null)
            {
                selectedDialogue = newDialogue;
                Repaint();
            }
        }

        private void OnGUI()
        {
            if (selectedDialogue == null)
            {
                EditorGUILayout.LabelField("No Dialogue Selected.");
                return;
            }

            ProcessEvents();

            // Dynamically adjust the canvas size based on node positions
            Rect canvas = GetCanvasBounds();

            // Start Scroll View with zoom adjustment
            Rect scaledCanvas = new Rect(canvas.x, canvas.y, canvas.width * zoom, canvas.height * zoom);
            scrollPosition = GUI.BeginScrollView(new Rect(0, 0, position.width, position.height), scrollPosition, scaledCanvas);


            // Draw Background Texture
            Texture2D backgroundTex = UnityEngine.Resources.Load("background") as Texture2D;
            Rect texCoords = new Rect(0, 0, canvas.width / backgroundSize, canvas.height / backgroundSize);
            GUI.DrawTextureWithTexCoords(canvas, backgroundTex, texCoords);

            // Apply Zooming
            Matrix4x4 matrixBackup = GUI.matrix;
            GUI.matrix = Matrix4x4.TRS(new Vector3(-scrollPosition.x, -scrollPosition.y, 0), Quaternion.identity, new Vector3(zoom, zoom, 1));


            foreach (DialogueNode node in selectedDialogue.GetAllNodes())
            {
                DrawConnections(node);
            }
            foreach (DialogueNode node in selectedDialogue.GetAllNodes())
            {
                DrawNode(node);
            }

            // Restore Matrix
            GUI.matrix = matrixBackup;

            GUI.EndScrollView();


            if (creatingNode != null)
            {
                selectedDialogue.CreateNode(creatingNode);
                creatingNode = null;
            }
            if (deletingNode != null)
            {
                selectedDialogue.DeleteNode(deletingNode);
                deletingNode = null;
            }
        }

        private Rect GetCanvasBounds()
        {
            if (selectedDialogue == null || !selectedDialogue.GetAllNodes().Any())
{
                return new Rect(0, 0, canvasSize, canvasSize);
            }

            float minX = float.MaxValue;
            float minY = float.MaxValue;
            float maxX = float.MinValue;
            float maxY = float.MinValue;

            foreach (DialogueNode node in selectedDialogue.GetAllNodes())
            {
                Rect nodeRect = node.GetRect();
                if (nodeRect.x < minX) minX = nodeRect.x;
                if (nodeRect.y < minY) minY = nodeRect.y;
                if (nodeRect.xMax > maxX) maxX = nodeRect.xMax;
                if (nodeRect.yMax > maxY) maxY = nodeRect.yMax;
            }

            float width = (maxX - minX) + 200; // Add padding
            float height = (maxY - minY) + 200;

            return new Rect(minX, minY, Mathf.Max(width, canvasSize), Mathf.Max(height, canvasSize));
        }


        private void ProcessEvents()
        {
            Event e = Event.current;

            // Mouse Wheel Zoom
            if (e.type == EventType.ScrollWheel)
            {
                float zoomDelta = -e.delta.y * 0.01f;
                float oldZoom = zoom;
                zoom = Mathf.Clamp(zoom + zoomDelta, zoomMin, zoomMax);
                Repaint();

                Vector2 mouseWorldPos = (Event.current.mousePosition + scrollPosition) / oldZoom;
                scrollPosition = (mouseWorldPos * zoom) - Event.current.mousePosition;



                e.Use();
            }

            // Drag Nodes or Canvas
            if (e.type == EventType.MouseDown && draggingNode == null)
            {
                draggingNode = GetNodeAtPoint(e.mousePosition / zoom + scrollPosition);
                if (draggingNode != null)
                {
                    draggingOffset = draggingNode.GetRect().position - e.mousePosition / zoom;
                    Selection.activeObject = draggingNode;
                }
                else
                {
                    draggingCanvas = true;
                    draggingCanvasOffset = scrollPosition - e.mousePosition / zoom; // Adjust for zoom
                    Selection.activeObject = selectedDialogue;
                }

            }

            else if (e.type == EventType.MouseDrag)
            {
                if (draggingNode != null)
                {
                    Undo.RecordObject(selectedDialogue, "Move Dialogue Node");
                    draggingNode.SetPosition(e.mousePosition / zoom + draggingOffset);
                    GUI.changed = true;
                }
                else if (draggingCanvas)
                {
                    // Smooth panning using e.delta
                    scrollPosition -= e.delta / zoom;
                    GUI.changed = true;
                }
            }
            else if (e.type == EventType.MouseUp)
            {
                draggingNode = null;
                draggingCanvas = false;
            }

            else if (e.type == EventType.MouseDrag && draggingCanvas)
            {
                scrollPosition += e.delta / zoom;  // Adjust panning based on zoom
                GUI.changed = true;
                e.Use();
            }
        }

        private void DrawNode(DialogueNode node)
        {
            GUIStyle style = nodeStyle;
            if (node.IsPlayerSpeaking())
            {
                style = playerNodeStyle;
            }
            GUILayout.BeginArea(node.GetRect(), style);

            if (selectedDialogue.GetIsDebug())
            {
                //Node Name Lable
                GUIStyle titleStyle = new GUIStyle(EditorStyles.miniLabel);
                titleStyle.alignment= TextAnchor.UpperLeft;
                titleStyle.fontSize = 8;
                GUILayout.Box(node.GetName(),titleStyle, GUILayout.ExpandWidth(false), GUILayout.Height(15));

            }

            node.SetText(EditorGUILayout.TextField(node.GetText()));

            if (GUILayout.Button("Delete"))
            {
                deletingNode = node;
            }

            DrawLinkButtons(node);

            if (GUILayout.Button("Add Child Node"))
            {
                creatingNode = node;
            }

            GUILayout.EndArea();
        }

        private void DrawLinkButtons(DialogueNode node)
        {
            if (linkingParentNode == null)
            {
                if (GUILayout.Button("Link"))
                {
                    linkingParentNode = node;
                }
            }
            else if(linkingParentNode == node)
            {
                if (GUILayout.Button("Cancel"))
                {
                    linkingParentNode = null;
                }
            }
            else if (linkingParentNode.GetChildren().Contains(node.name))
            {
                if (GUILayout.Button("UnLink"))
                {
                    Undo.RecordObject(selectedDialogue, "Remove Dialogue Link");
                    linkingParentNode.RemoveChild(node.name);
                    linkingParentNode = null;
                }
            }
            else
            {
                if (GUILayout.Button("Child"))
                {
                    Undo.RecordObject(selectedDialogue, "Add Dialogue Link");
                    linkingParentNode.AddChild(node.name);
                    linkingParentNode = null;
                }
            }
        }

        private void DrawConnections(DialogueNode node)
        {
            Vector3 startPosition = new Vector2(node.GetRect().xMax, node.GetRect().center.y);
            foreach (DialogueNode childNode in selectedDialogue.GetAllChildren(node))
            {
                Vector3 endPosition = new Vector2(childNode.GetRect().xMin, childNode.GetRect().center.y);
                Vector3 controlPointOffset = endPosition - startPosition;
                controlPointOffset.y = 0;
                controlPointOffset.x *= 0.8f;
                Handles.DrawBezier(
                    startPosition, endPosition, 
                    startPosition + controlPointOffset, 
                    endPosition - controlPointOffset, 
                    Color.white, null, 4f);
            }
        }

        private DialogueNode GetNodeAtPoint(Vector2 point)
        {
            DialogueNode foundNode = null;
            foreach (DialogueNode node in selectedDialogue.GetAllNodes())
            {
                if (node.GetRect().Contains(point))
                {
                    foundNode = node;
                }

            }

            return foundNode;
        }
    }
}


LuDiChRiS, here is a link to another post where this issue was discussed. Doesn’t look like there is an easy way to do this.

https://community.gamedev.tv/t/anyone-able-to-add-zooming-to-the-dialogue-editor/210219

1 Like

Here is my updated code for anyone else that might be struggling with this topic. I was able to get zoom working and still be able to select and move the nodes around. There is just one thing still TODO and that is account for the viewport position when trying to detect if the mouse is over a node. (currently you must remain in the top left corner of the view port for selecting nodes to work correctly.)

For now this will do as I really just wanted to be able to zoom out and see the whole network better. If anyone is able to figure out how to add an offset to account for horizontal and vertical scroll bar position, that would be ace. It’ll be on line 59

using System;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEngine;

namespace RPG.Dialogue.Editor
{
    public class DialogueEditor : EditorWindow
    {
        Dialogue selectedDialogue = null;
        [NonSerialized] GUIStyle nodeStyle;
        [NonSerialized] GUIStyle playerNodeStyle;
        [NonSerialized] DialogueNode draggingNode = null;
        [NonSerialized] Vector2 draggingOffset;
        [NonSerialized] DialogueNode creatingNode = null;
        [NonSerialized] DialogueNode deletingNode = null;
        [NonSerialized] DialogueNode linkingParentNode = null;

        Vector2 scrollPosition;
        [NonSerialized] bool draggingCanvas = false;
        [NonSerialized] Vector2 draggingCanvasOffset;
        const float canavasSize = 4000;
        const float backgroundSize = 50;

        [MenuItem("Window/Dialogue Editor")]
        private static void Init()
        {
            DialogueEditor window = DialogueEditor.GetWindow<DialogueEditor>(false, "Dialogue Editor");
            window.minSize = new Vector2(600.0f, 300.0f);
            window.wantsMouseMove = true;
            window.Show();

            EditorWindow.FocusWindowIfItsOpen<DialogueEditor>();

        }

        private const float kZoomMin = 0.2f;
        private const float kZoomMax = 1.0f;

        private float _zoom = 1.0f;
        private Vector2 _zoomCoordsOrigin = Vector2.zero;

        private Rect ZoomArea => new Rect(0.0f, 75.0f, position.width, position.height - 100.0f);

        private Vector2 ConvertScreenCoordsToZoomCoords(Vector2 screenCoords)
        {
            float verticalOffset = 60f;
            screenCoords = new Vector2(screenCoords.x, screenCoords.y-verticalOffset);

            // Correctly apply zoom to scrollPosition
            Vector2 zoomAdjustedScroll = scrollPosition / _zoom;

            // Apply zoomed scrolling offset
            Vector2 zoomedCoords = (screenCoords + zoomAdjustedScroll) * (1 / _zoom);

            Debug.Log($"scrollPosition: {scrollPosition}, position.width: {position.width}, zoom: {_zoom}, result: {zoomedCoords}");

            // TODO: Add offset for scrolling when user is not in the TopLeft corner of the window
            return (screenCoords + scrollPosition) * (1 / _zoom);
        }

        [OnOpenAsset(1)]
        public static bool OnOpenAsset(int instanceID, int line)
        {
            Dialogue dialogue = EditorUtility.InstanceIDToObject(instanceID) as Dialogue;
            if (dialogue != null)
            {
                Init();
                return true;
            }
            return false;
        }

        private void OnEnable()
        {
            Selection.selectionChanged += OnSelectionChange;

            nodeStyle = new GUIStyle();
            nodeStyle.normal.background = EditorGUIUtility.Load("node0") as Texture2D;
            nodeStyle.normal.textColor = Color.white;
            nodeStyle.padding = new RectOffset(20, 20, 20, 20);
            nodeStyle.border = new RectOffset(12, 12, 12, 12);

            playerNodeStyle = new GUIStyle();
            playerNodeStyle.normal.background = EditorGUIUtility.Load("node1") as Texture2D;
            playerNodeStyle.normal.textColor = Color.white;
            playerNodeStyle.padding = new RectOffset(20, 20, 20, 20);
            playerNodeStyle.border = new RectOffset(12, 12, 12, 12);
        }

        private void OnSelectionChange()
        {
            Dialogue newDialogue = Selection.activeObject as Dialogue;
            if (newDialogue != null)
            {
                selectedDialogue = newDialogue;
                Repaint();
            }
        }

        private void OnGUI()
        {
            if (selectedDialogue == null)
            {
                EditorGUILayout.LabelField("No Dialogue Selected.");
            }
            else
            {
                ProcessEvents();
                DrawZoomArea();
                DrawNonZoomArea();
            }
        }

        private void CreateNodeCanvas()
        {
            scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);

            Rect canvas = GUILayoutUtility.GetRect(canavasSize, canavasSize);
            Texture2D backgroundTex = UnityEngine.Resources.Load("background") as Texture2D;
            Rect texCoords = new Rect(0, 0, canavasSize / backgroundSize, canavasSize / backgroundSize);
            GUI.DrawTextureWithTexCoords(canvas, backgroundTex, texCoords);

            foreach (DialogueNode node in selectedDialogue.GetAllNodes())
            {
                DrawConnections(node);
            }
            foreach (DialogueNode node in selectedDialogue.GetAllNodes())
            {
                DrawNode(node);
            }

            EditorGUILayout.EndScrollView();

            if (creatingNode != null)
            {
                selectedDialogue.CreateNode(creatingNode);
                creatingNode = null;
            }
            if (deletingNode != null)
            {
                selectedDialogue.DeleteNode(deletingNode);
                deletingNode = null;
            }
        }

        private void ProcessEvents()
        {
            if (Event.current.type == EventType.ScrollWheel)
            {
                Vector2 zoomCoordsMousePos = ConvertScreenCoordsToZoomCoords(Event.current.mousePosition);
                float zoomDelta = -Event.current.delta.y / 150.0f;
                float oldZoom = _zoom;

                _zoom = Mathf.Clamp(_zoom + zoomDelta, kZoomMin, kZoomMax);

                // Ensure zooming remains centered
                //_zoomCoordsOrigin += (zoomCoordsMousePos - _zoomCoordsOrigin) - (oldZoom / _zoom) * (zoomCoordsMousePos - _zoomCoordsOrigin);
                //_zoomCoordsOrigin = zoomCoordsMousePos - (zoomCoordsMousePos - _zoomCoordsOrigin) * (oldZoom / _zoom);
                //_zoomCoordsOrigin = (zoomCoordsMousePos - _zoomCoordsOrigin) * (1 - oldZoom / _zoom) + _zoomCoordsOrigin;
                _zoomCoordsOrigin += (zoomCoordsMousePos - _zoomCoordsOrigin) * (1 - oldZoom / _zoom);

                Debug.Log($"Zoom Origin: {_zoomCoordsOrigin}");

                Event.current.Use();
            }

            // Allow moving the zoom area's origin by dragging with the middle mouse button or dragging
            // with the left mouse button with Alt pressed.
            if (Event.current.type == EventType.MouseDrag && ((Event.current.button == 0 && Event.current.modifiers == EventModifiers.Alt) || Event.current.button == 2))
            {
                scrollPosition = draggingCanvasOffset - Event.current.mousePosition;
                GUI.changed = true;
            }

            // Mouse down and not dragging
            else if (Event.current.type == EventType.MouseDown && draggingNode == null)
            {
                Vector2 zoomAdjustedMousePos = ConvertScreenCoordsToZoomCoords(Event.current.mousePosition);

                draggingNode = GetNodeAtPoint(zoomAdjustedMousePos);
                if (draggingNode != null)
                {
                    draggingOffset = draggingNode.GetRect().position - zoomAdjustedMousePos;
                    Selection.activeObject = draggingNode;
                }
                else
                {
                    draggingCanvas = true;
                    draggingCanvasOffset = Event.current.mousePosition + scrollPosition;
                    Selection.activeObject = selectedDialogue;
                }
            }

            else if (Event.current.type == EventType.MouseDrag && draggingNode != null)
            {
                Undo.RecordObject(selectedDialogue, "Move Dialogue Node");
                Vector2 zoomAdjustedMousePos = ConvertScreenCoordsToZoomCoords(Event.current.mousePosition);
                draggingNode.SetPosition(zoomAdjustedMousePos + draggingOffset);

                GUI.changed = true;
            }
            // The Below code has been commented to out to prevent left click and panning
            //else if (Event.current.type == EventType.MouseDrag && draggingCanvas)
            //{
            //    // Left click editor dragging
            //    //scrollPosition = draggingCanvasOffset - Event.current.mousePosition;
            //    //GUI.changed = true;
            //}
            else if (Event.current.type == EventType.MouseUp && draggingNode != null)
            {
                draggingNode = null;
            }
            else if (Event.current.type == EventType.MouseUp && draggingCanvas)
            {
                draggingCanvas = false;
            }
        }
        private void DrawZoomArea()
        {
            // Compute dynamically the full area for zooming
            Rect fullZoomArea = new Rect(0, 75, position.width, position.height - 100);

            // Adjusting zoom area dynamically
            Vector2 zoomOrigin = Event.current.mousePosition; // Track mouse as zoom origin
            EditorZoomArea.Begin(_zoom, fullZoomArea, zoomOrigin);


            // Use the full area width and height for stretching
            GUILayout.BeginArea(new Rect(0f, 0f, fullZoomArea.width / _zoom, fullZoomArea.height / _zoom));

            CreateNodeCanvas(); // The nodes will now stretch with the zoom level

            GUILayout.EndArea();

            EditorZoomArea.End();
        }

        private void DrawNonZoomArea()
        {
            float width = position.width;
            float height = position.height;

            GUI.Box(new Rect(0.0f, 0.0f, width, 50.0f), "Adjust zoom with slider or mouse wheel.\nMove zoom area with middle mouse or Alt+left mouse.");

            ZoomAdjustments(width);

            GUI.Box(new Rect(0.0f, height - 25.0f, width, 25.0f), "Epic Adventure Quest - By Chris Burns");
        }

        private void ZoomAdjustments(float width)
        {
            Vector2 center = _zoomCoordsOrigin + (ZoomArea.size / 2f) / _zoom;
            float oldZoom = _zoom;
            _zoom = EditorGUI.Slider(new Rect(0.0f, 50.0f, width, 25.0f), _zoom, kZoomMin, kZoomMax);
            _zoom = Mathf.Clamp(_zoom, kZoomMin, kZoomMax);
            _zoomCoordsOrigin += (center - _zoomCoordsOrigin) - (oldZoom / _zoom) * (center - _zoomCoordsOrigin);

        }

        private void DrawNode(DialogueNode node)
        {
            GUIStyle style = nodeStyle;
            if (node.IsPlayerSpeaking())
            {
                style = playerNodeStyle;
            }
            GUILayout.BeginArea(node.GetRect(), style);

            if (selectedDialogue.GetIsDebug())
            {
                //Node Name Lable
                GUIStyle titleStyle = new GUIStyle(EditorStyles.miniLabel);
                titleStyle.alignment = TextAnchor.UpperLeft;
                titleStyle.fontSize = 8;
                GUILayout.Box(node.GetName(), titleStyle, GUILayout.ExpandWidth(false), GUILayout.Height(15));

            }

            node.SetText(EditorGUILayout.TextField(node.GetText()));

            if (GUILayout.Button("Delete"))
            {
                deletingNode = node;
            }

            DrawLinkButtons(node);

            if (GUILayout.Button("Add Child Node"))
            {
                creatingNode = node;
            }

            GUILayout.EndArea();
        }

        private void DrawLinkButtons(DialogueNode node)
        {
            if (linkingParentNode == null)
            {
                if (GUILayout.Button("Link"))
                {
                    linkingParentNode = node;
                }
            }
            else if (linkingParentNode == node)
            {
                if (GUILayout.Button("Cancel"))
                {
                    linkingParentNode = null;
                }
            }
            else if (linkingParentNode.GetChildren().Contains(node.name))
            {
                if (GUILayout.Button("UnLink"))
                {
                    Undo.RecordObject(selectedDialogue, "Remove Dialogue Link");
                    linkingParentNode.RemoveChild(node.name);
                    linkingParentNode = null;
                }
            }
            else
            {
                if (GUILayout.Button("Child"))
                {
                    Undo.RecordObject(selectedDialogue, "Add Dialogue Link");
                    linkingParentNode.AddChild(node.name);
                    linkingParentNode = null;
                }
            }
        }

        private void DrawConnections(DialogueNode node)
        {
            Vector3 startPosition = new Vector2(node.GetRect().xMax, node.GetRect().center.y);
            foreach (DialogueNode childNode in selectedDialogue.GetAllChildren(node))
            {
                Vector3 endPosition = new Vector2(childNode.GetRect().xMin, childNode.GetRect().center.y);
                Vector3 controlPointOffset = endPosition - startPosition;
                controlPointOffset.y = 0;
                controlPointOffset.x *= 0.8f;
                Handles.DrawBezier(
                    startPosition, endPosition,
                    startPosition + controlPointOffset,
                    endPosition - controlPointOffset,
                    Color.white, null, 4f);
            }
        }

        private DialogueNode GetNodeAtPoint(Vector2 point)
        {
            DialogueNode foundNode = null;
            Vector2 zoomAdjustedMousePos = ConvertScreenCoordsToZoomCoords(Event.current.mousePosition);
            Debug.Log($"Raw Mouse Pos: {Event.current.mousePosition}, Point: {point}, Adjusted Pos: {zoomAdjustedMousePos}");

            foreach (DialogueNode node in selectedDialogue.GetAllNodes())
            {
                if (node.GetRect().Contains(point))
                {
                    Debug.Log($"Selection over Node: {node.GetText()}");
                    foundNode = node;
                }

            }

            return foundNode;
        }
    }
}


1 Like

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms