SonyWWS / ATF

Authoring Tools Framework (ATF) is a set of C#/.NET components for making tools on Windows. ATF has been in continuous development in Sony Computer Entertainment's (SCE) Worldwide Studios central tools group since early 2005. ATF has been used by most SCE first party studios to make many custom tools such as Naughty Dog’s level editor and shader editor for The Last of Us, Guerrilla Games’ sequence editor for Killzone games (including the Killzone: Shadow Fall PS4 launch title), an animation blending tool at Santa Monica Studio, a level editor at Bend Studio, a visual state machine editor for Quantic Dream, sound editing tools, and many others.
Apache License 2.0
1.89k stars 262 forks source link

TreeControl user re-ordering support #46

Closed Ron2 closed 8 years ago

Ron2 commented 8 years ago

If a TreeControl’s ShowDragBetweenCue is set to true, and if the context can be adapted to the new interface, IOrderedInsertionContext, then the user will be allowed to re-order nodes by dragging. Also, objects can be dragged into a specific location within a tree, like when dragging from the palette.

Ron2 commented 8 years ago

Here's the implementation of IOrderedInsertionContext that works in one version of LevelEditor:

#region IOrderedInsertionContext Members

/// <summary>
/// Returns true if 'item' can be inserted.</summary>
/// <param name="parent">The object that will become the parent of the inserted object.
/// GameContext requires this to not be null.</param>
/// <param name="before">The object that is immediately before the inserted object.
/// Can be null to indicate that the inserted item should become the first child.</param>
/// <param name="item">The item to be inserted</param>
/// <returns>True iff 'item' can be successfully inserted</returns>
bool IOrderedInsertionContext.CanInsert(object parent, object before, object itemDataObject)
{
    // We can't let the root be replaced, so we will require 'parent' to be valid.
    var parentNode = parent.As<DomNode>();
    if (parentNode == null)
        return false;
    var beforeNode = before.As<DomNode>();

    // Convert from the DragEventArgs data to DomNodes.
    IEnumerable<object> items = Util.ConvertData(itemDataObject, false);
    var insertNodes = new List<DomNode>();
    foreach (object item in items)
    {
        DomNode insertNode = item as DomNode;
        if (insertNode != null)
            insertNodes.Add(insertNode);
    }
    if (insertNodes.Count == 0)
        return false;

    // If the user has selected multiple nodes and some of those nodes are children of other
    //  selected nodes, remove the children from our list. This preserves the hierarchy. This
    //  behavior is like the Mac OS X Finder, for example.
    insertNodes = new List<DomNode>(DomNode.GetRoots(insertNodes));

    // Find a compatible ChildInfo (from the parent) for each inserted DomNode.
    // The order of the ChildInfos determines the order that the Project Lister displays items.
    // This code assumes that TreeControl gives us the nodes to insert in the same order that
    //  they're being displayed (and not in the order that the user happened to do the multi-
    //  select.)
    // So, we can step through the ChildInfos and 'insertNodes', from beginning to end.
    var childInfos = new List<ChildInfo>(insertNodes.Count);
    {
        bool foundFirst = false; // Find the first ChildInfo that matches 'beforeNode'.
        int i = 0;
        foreach (ChildInfo info in parentNode.Type.Children)
        {
            if (!foundFirst)
            {
                if (beforeNode == null)
                    foundFirst = true;
                else if (info.Type.IsAssignableFrom(beforeNode.Type))
                    foundFirst = true;
                else
                    continue;
            }
            while (info.Type.IsAssignableFrom(insertNodes[i].Type))
            {
                childInfos.Add(info);
                i++;
                if (i == insertNodes.Count)
                    break;
            }
            if (i == insertNodes.Count)
                break;
        }
        if (childInfos.Count != insertNodes.Count)
            return false;
    }

    // Do a sanity check on the DomNode hierarchy.
    for(int i = 0; i < insertNodes.Count; i++)
    {
        DomNode insertNode = insertNodes[i];
        ChildInfo childInfo = childInfos[i];

        // We can only currently insert into a list.
        if (!childInfo.IsList)
            return false;

        // Avoid useless inserts.
        if (parentNode == insertNode || beforeNode == insertNode)
            return false;

        // We can't insert a node into itself or a descendant of itself.
        if (parentNode.IsDescendantOf(insertNode))
            return false;

        // Make sure that itemNode is compatible with the parent's ChildInfo.
        if (!childInfo.Type.IsAssignableFrom(insertNode.Type))
            return false;
    }
    return true;
}

/// <summary>
/// Inserts 'item' into the set of objects at the desired position. Will only be called
/// if CanInsert() returns true.</summary>
/// <param name="parent">The object that will become the parent of the inserted object.</param>
/// <param name="before">The object that is immediately before the inserted object.
/// Can be null to indicate that the inserted item should become the first child.</param>
/// <param name="item">The item to be inserted.</param>
void IOrderedInsertionContext.Insert(object parent, object before, object itemDataObject)
{
    var parentNode = parent.Cast<DomNode>();
    var beforeNode = before.As<DomNode>();

    // Convert to DomNodes.
    IEnumerable<object> items = Util.ConvertData(itemDataObject, false);
    var insertNodes = new List<DomNode>();
    foreach (object item in items)
    {
        DomNode insertNode = item as DomNode;
        if (insertNode != null)
            insertNodes.Add(insertNode);
    }

    // If the user has selected multiple nodes and some of those nodes are children of other
    //  selected nodes, remove the children from our list. This preserves the hierarchy. This
    //  behavior is like the Mac OS X Finder, for example.
    insertNodes = new List<DomNode>(DomNode.GetRoots(insertNodes));

    // Remove the DomNodes from their parents before calculating the insertion index.
    foreach (DomNode insertNode in insertNodes)
        insertNode.RemoveFromParent();

    // Find a compatible ChildInfo (from the parent) for each inserted DomNode.
    // The order of the ChildInfos determines the order that the Project Lister displays items,
    //  so we need to step through the ChildInfos and match inserted DomNodes at the same time.
    var childInfos = new List<ChildInfo>(insertNodes.Count);
    {
        bool foundFirst = false;
        int i = 0;
        foreach (ChildInfo info in parentNode.Type.Children)
        {
            if (!foundFirst)
            {
                if (beforeNode == null)
                    foundFirst = true;
                else if (info.Type.IsAssignableFrom(beforeNode.Type))
                    foundFirst = true;
                else
                    continue;
            }
            while (info.Type.IsAssignableFrom(insertNodes[i].Type))
            {
                childInfos.Add(info);
                i++;
                if (i == insertNodes.Count)
                    break;
            }
            if (i == insertNodes.Count)
                break;
        }
    }

    // Insert the previously removed DomNodes, using the correct ChildInfo.
    int insertionIndex;
    if (beforeNode == null)
        insertionIndex = 0;
    else
    {
        IList<DomNode> childList = parentNode.GetChildList(childInfos[0]);
        insertionIndex = childList.IndexOf(beforeNode) + 1;
    }
    ChildInfo priorInfo = childInfos[0];
    for (int i = 0; i < insertNodes.Count; i++)
    {
        DomNode insertNode = insertNodes[i];
        ChildInfo info = childInfos[i];
        if (info != priorInfo)
        {
            priorInfo = info;
            insertionIndex = 0;
        }
        IList<DomNode> childList = parentNode.GetChildList(info);
        childList.Insert(insertionIndex, insertNode);
        insertionIndex++;
    }
}

#endregion
abeckus commented 8 years ago

Thank you Ron, I merged all the changes to trunk. Alan