Seneral / Node_Editor_Framework

A flexible and modular Node Editor Framework for creating node based displays and editors in Unity
https://nodeeditor.seneral.dev
MIT License
2.01k stars 414 forks source link

Dynamic Node Rect Size #39

Closed Seneral closed 7 years ago

Seneral commented 8 years ago

Description: Node Rect size adapting to the content

State: None. Either achievable using reflection, checking the GUIStack size after the node has been drawn for the next time, or by requesting an override function. Way easier if the actualy content is moved from the node body to the side window and only the Inputs/Outputs are drawn (very easy to adjust node body)

snarlynarwhal commented 8 years ago

Hello - this refers to automatically resizing the nodes, right?

Is there currently a way to add a resize handle on the bottom right? Someone claimed to have added this feature on the forums: http://forum.unity3d.com/threads/simple-node-editor.189230/

However, I can't seem to locate this feature.

Seneral commented 8 years ago

This post you're refering to is from pre-node editor times (sounds weird). Basically, I posted my first version of this project after this has been posted. It is not in the current version.

Initially this issue was thought for automatic resizing so you don't even need a handle, just like groups resize in GUILayout normally. But a manual resize handle is a start, indeed. You could add it relatively easily but it would require understanding the dynamic input controls system. Basically, if you want it for your node only, overwrite DrawNode in you node, else lookup the Node.cs source code. There, you can add this functionality by adding a handle and saving it's rect. Then you would create input control functions to handle dragging in that rect to change the node size. It is possible but I don't expect you to be able to implement this when you're new to the project;)

snarlynarwhal commented 8 years ago

I'll look into it later this week if I have time. :)

Keerpich commented 8 years ago

It should resize according to the number of input knobs and elements inside, right ?

Also, should elements like OptionSlider resize automatically too i accordance to the number of options ? Because if we don't take that into account at some point the density of points on the slider will be too large to make accurate selections.

Seneral commented 8 years ago

Yes, in principle. That sizing is all handled by the layouting system already, luckily:) But the real problem is getting to that information:/ For that we'd need to use alot of reflection. I've had a quick glance at it 3 months ago and it probably will not be easy. There are functions to retrieve the layouting elements in the last/current group if I remember correctly, which we could iterate over and read the sizing calculation done by the layouting System;) If you're familar with reflection and really want this, there are good chances to implementiert this, but it will be tricky. If you want I can check again and send you the unity classes I've previously considered helpful:)

JoshAshby commented 7 years ago

Chiming in because I'd be interested in that list of helpful classes or any pointers on starting points with getting all the elements in the previous group.

For an easy way to pull this off, if you stick to using only GUILayout methods while drawing the node, is like:

    Vector2 BodyOffset = new Vector2(0, 10);
    Vector2 LastPosition = Vector2.zero;

    [ ... later when drawing the node ... ]
    Rect nodeRect = this.PositionRect;

    GUI.BeginGroup(nodeRect, GUI.skin.box);
    GUILayout.BeginArea(nodeRect, GUI.skin.box);

    OnGUI();
    LastPosition = GUILayoutUtility.GetLastRect().max + BodyOffset;

    GUILayout.EndArea();
    GUI.EndGroup();

    if (Event.current.type != EventType.Repaint)
        return;

    Vector2 maxSize = LastPosition + BodyOffset;
    maxSize.x = nodeRect.width; // If I want to add custom width adjustment in the future, it should replace this

    if (maxSize != nodeRect.size)
        nodeRect.size = maxSize;

    if (this.PositionRect.size != nodeRect.size)
    {
        this.PositionRect = nodeRect;
        Canvas.OnRepaint();
    }

Basically, keep track of the position of the last GUI element to be drawn, in the local coordinates of inside the node, and say that that position is the same as the height/width of the content, and adjust the nodes window size to fit. This could also be used for manually resizing by setting the LastPosition manually in a drag handler on a corner or something.

Obviously this has the issue of not accounting for non GUILayout elements, such as when I do GUI.Box(new Rect(100, 100, 50, 50), "box"); inside of a node with a default height of 10px, but for me that was a price I'm willing to pay because it's not hard to set a minimum size for the node for when you need to draw non GUILayout item, and in what I need, I'm never doing that anyways :)

JoshAshby commented 7 years ago

For the curious, I ended up digging into this a little earlier this evening and ended up with this rough first pass at grabbing the layout systems calculated max height, however it still doesn't take into account non GUILayout elements still and it's returning the same height as using GUILayoutUtility.GetLastRect().max. Fair warning too, it's probably not that pretty but maybe someone can take it the last mile if its possible:

            GUI.BeginGroup(bodyRect, GUI.skin.box)
            GUILayout.BeginArea(bodyRect, GUI.skin.box);

            GUI.changed = false;
            OnGUI();
            lastPosition = GUILayoutUtility.GetLastRect().max + contentOffset;

            Type internalType =
                AppDomain.CurrentDomain
                    .GetAssemblies()
                    .Where(x => x.GetTypes().Select(y => y.Name).Contains("GUILayoutUtility"))
                    .First()
                    .GetTypes()
                    .First(x => x.Name == "GUILayoutUtility");

            object current = internalType
                    .GetField("current", BindingFlags.Static | BindingFlags.NonPublic)
                    .GetValue(internalType);

            object topLevel = current
                .GetType()
                .GetField("topLevel", BindingFlags.NonPublic | BindingFlags.Instance)
                .GetValue(current);

            topLevel.GetType()
                .GetMethod("CalcHeight", BindingFlags.Public | BindingFlags.Instance)
                .Invoke(topLevel, null);

            float maxHeight = (float)topLevel
                .GetType()
                .GetField("maxHeight", BindingFlags.Public | BindingFlags.Instance)
                .GetValue(topLevel);
            Debug.LogFormat("Calc'd Height: {0}", maxHeight + contentOffset.y);

            GUILayout.EndArea();
            GUI.EndGroup();

Here you can see its clipping the GUI.Box but calculated (Calc'd Height: 195) the same size as GetLastRect() was returning (Set Height: 195).

That said, this could probably also be taken care of with using GUILayout.Space() as suggested here which, if I ever need to draw non-auto laid out GUI items, is probably a fair enough work around for my needs.

Seneral commented 7 years ago

Looks great, definitely a fast and easy way to do that! I think this can be refined with optional minSize properties so it'll work with normal GUI, too, if desired. Also one problem is width, I don't think we can request the optimal width from the GUILayout, it will just take what it can get. But as most (dynamic) GUI is structured vertically that should not be too much of a problem...

I think something like your reflection approach was what I originally planned to do, but seems we got an easier solution;) Will take a look at that, you can make a PR if you want:)

JoshAshby commented 7 years ago

For width, I set up Vector2 MinSize and then check if there are any knobs on top or bottom, and if there are, I take the the max xMax and pick that or the minsize and set that as the width. Again, it works pretty well for my needs and allows the nodes window to grow and shrink in both directions as more items are added and removed.

I can see about throwing together a PR here in a few.

Seneral commented 7 years ago

Good approach! Yeah, PR would be great:) I like the idea of not having to specify the rect in Create anymore, it's always a hassle when you decide to change it later on...

Seneral commented 7 years ago

Any news on a PR? Would be really great:)

JoshAshby commented 7 years ago

Sorry, I've been pretty busy and Unity/C# world isn't a part of my normal day job, so time has been a bit constrained recently. I'll try to get to it by this weekend!

Seneral commented 7 years ago

Ya know that, don't worry:)

Seneral commented 7 years ago

Thanks, has been merged:) To conclude here: Automatic resizing is optional, you have to override Vector2 MinSize and bool Resizable in Node to enable it.