Selaa lähdekoodia

Bulk of the work for Project Window drop down (WIP)

Marko Pintera 10 vuotta sitten
vanhempi
sitoutus
0eb7d2b417

+ 3 - 0
BansheeEditor/Source/BsSceneViewHandler.cpp

@@ -120,6 +120,9 @@ namespace BansheeEngine
 
 	Vector2I SceneViewHandler::wrapCursorToWindow()
 	{
+		if (mParentWidget == nullptr)
+			return Vector2I();
+
 		RenderWindowPtr parentWindow = mParentWidget->getParentWindow()->getRenderWindow();
 
 		Vector2I windowPos = parentWindow->screenToWindowPos(Cursor::instance().getScreenPosition());

+ 2 - 1
MBansheeEditor/MBansheeEditor.csproj

@@ -55,7 +55,8 @@
     <Compile Include="DialogBox.cs" />
     <Compile Include="DragDrop.cs" />
     <Compile Include="DropDownWindow.cs" />
-    <Compile Include="DropTarget.cs" />
+    <Compile Include="ProjectDropTarget.cs" />
+    <Compile Include="OSDropTarget.cs" />
     <Compile Include="EditorApplication.cs" />
     <Compile Include="EditorBuiltin.cs" />
     <Compile Include="EditorSettings.cs" />

+ 3 - 3
MBansheeEditor/DropTarget.cs → MBansheeEditor/OSDropTarget.cs

@@ -4,14 +4,14 @@ using BansheeEngine;
 
 namespace BansheeEditor
 {
-    public class DropTarget : ScriptObject
+    public class OSDropTarget : ScriptObject
     {
         public Action<int, int> OnDrop;
         public Action<int, int> OnEnter;
         public Action OnLeave;
         public Action<int, int> OnDrag;
 
-        public DropTarget(EditorWindow window)
+        public OSDropTarget(EditorWindow window)
         {
             IntPtr nativeWindow = window.GetCachedPtr();;
             Internal_CreateInstance(this, nativeWindow);
@@ -57,7 +57,7 @@ namespace BansheeEditor
         }
 
         [MethodImpl(MethodImplOptions.InternalCall)]
-        private static extern void Internal_CreateInstance(DropTarget instance, IntPtr editorWindow);
+        private static extern void Internal_CreateInstance(OSDropTarget instance, IntPtr editorWindow);
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_Destroy(IntPtr nativeInstance);

+ 184 - 0
MBansheeEditor/ProjectDropTarget.cs

@@ -0,0 +1,184 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+    public class ProjectDropTarget
+    {
+        private const int DragStartDistancePx = 3;
+
+        public Action<Vector2I, string[]> OnDrop;
+        public Action<Vector2I> OnStart;
+        public Action<Vector2I> OnEnter;
+        public Action OnLeave;
+        public Action<Vector2I> OnDrag;
+
+        private readonly EditorWindow parentWindow;
+
+        private OSDropTarget dropTargetOS;
+        private Rect2I bounds;
+
+        private bool isMouseDown;
+        private bool isDragInProgress;
+        private bool triggerStartDrag;
+        private bool isDragInBounds;
+        private bool isOSDragActive;
+        private Vector2I mouseDownScreenPos;
+        private Vector2I lastDragWindowPos;
+
+        public Rect2I Bounds
+        {
+            get { return bounds; }
+            set
+            {
+                bounds = value;
+                dropTargetOS.Bounds = bounds;
+            }
+        }
+
+        public ProjectDropTarget(EditorWindow window)
+        {
+            parentWindow = window;
+
+            dropTargetOS = new OSDropTarget(window);
+            dropTargetOS.OnDrag += DoOnOSDrag;
+            dropTargetOS.OnDrop += DoOnOSDrop;
+            dropTargetOS.OnEnter += DoOnOSDragEnter;
+            dropTargetOS.OnLeave += DoOnOSDragLeave;
+
+            Input.OnPointerPressed += Input_OnPointerPressed;
+            Input.OnPointerReleased += Input_OnPointerReleased;
+            Input.OnPointerMoved += Input_OnPointerMoved;
+        }
+
+        void Input_OnPointerMoved(PointerEvent ev)
+        {
+            Vector2I currentWindowPos = parentWindow.ScreenToWindowPos(ev.screenPos);
+
+            if (isMouseDown && !isDragInProgress)
+            {
+                Debug.Log("MOUSE DOWN");
+
+                Vector2I startWindowPos = parentWindow.ScreenToWindowPos(mouseDownScreenPos);
+                if (!Bounds.Contains(startWindowPos))
+                    return;
+
+                Debug.Log("IN BOUNDS");
+
+                int distance = Vector2I.Distance(startWindowPos, currentWindowPos);
+                if (distance >= DragStartDistancePx)
+                    triggerStartDrag = true;
+            }
+        }
+
+        void Input_OnPointerReleased(PointerEvent ev)
+        {
+            isDragInProgress = false;
+            isMouseDown = false;
+            isDragInBounds = false;
+        }
+
+        void Input_OnPointerPressed(PointerEvent ev)
+        {
+            isMouseDown = true;
+            mouseDownScreenPos = ev.screenPos;
+        }
+
+        public void Update()
+        {
+            Vector2I currentWindowPos = parentWindow.ScreenToWindowPos(Input.PointerPosition);
+
+            if (triggerStartDrag)
+            {
+                Debug.Log("TRIGGER START");
+
+                isDragInProgress = true;
+                triggerStartDrag = false;
+
+                if (OnStart != null)
+                    OnStart(currentWindowPos);
+            }
+
+            if (isOSDragActive)
+                return;
+
+            if (DragDrop.DragInProgress && DragDrop.Type == DragDropType.Resource)
+            {
+                if (lastDragWindowPos != currentWindowPos)
+                {
+                    if (!isDragInBounds)
+                    {
+                        if (Bounds.Contains(currentWindowPos))
+                        {
+                            isDragInBounds = true;
+                            if (OnEnter != null)
+                                OnEnter(currentWindowPos);
+                        }
+                    }
+
+                    if (OnDrag != null)
+                        OnDrag(currentWindowPos);
+
+                    if (isDragInBounds)
+                    {
+                        if (!Bounds.Contains(currentWindowPos))
+                        {
+                            isDragInBounds = false;
+                            if (OnLeave != null)
+                                OnLeave();
+                        }
+                    }
+
+                    lastDragWindowPos = currentWindowPos;
+                }
+
+                if (DragDrop.DropInProgress)
+                {
+                    if (OnDrop != null)
+                    {
+                        ResourceDragDropData resourceDragDrop = (ResourceDragDropData)DragDrop.Data;
+                        OnDrop(currentWindowPos, resourceDragDrop.Paths);
+                    }
+                }
+            }
+        }
+
+        public void Destroy()
+        {
+            dropTargetOS.Destroy();
+            dropTargetOS = null;
+        }
+
+        private void DoOnOSDragEnter(int x, int y)
+        {
+            isOSDragActive = true;
+
+            if (OnEnter != null)
+                OnEnter(new Vector2I(x, y));
+        }
+
+        private void DoOnOSDragLeave()
+        {
+            isOSDragActive = false;
+
+            if (OnLeave != null)
+                OnLeave();
+        }
+
+        private void DoOnOSDrag(int x, int y)
+        {
+            if (OnDrag != null)
+                OnDrag(new Vector2I(x, y));
+        }
+
+        private void DoOnOSDrop(int x, int y)
+        {
+            isOSDragActive = false;
+
+            if (OnDrop != null)
+                OnDrop(new Vector2I(x, y), dropTargetOS.FilePaths);
+        }
+    }
+}

+ 466 - 161
MBansheeEditor/ProjectWindow.cs

@@ -12,23 +12,255 @@ namespace BansheeEditor
 
     internal sealed class ProjectWindow : EditorWindow
     {
-        private struct EntryGUI
+        private class ContentInfo
         {
-            public EntryGUI(GUITexture icon, GUILabel label)
+            public ContentInfo(ProjectWindow window, ProjectViewType viewType)
             {
-                this.icon = icon;
-                this.label = label;
+                GUIPanel parentPanel = window.scrollAreaPanel;
+
+                GUIPanel contentPanel = parentPanel.AddPanel(1);
+                overlay = parentPanel.AddPanel(0);
+                underlay = parentPanel.AddPanel(2);
+
+                main = contentPanel.AddLayoutY();
+
+                if (viewType == ProjectViewType.List16)
+                {
+                    tileSize = 16;
+                    gridLayout = false;
+                }
+                else
+                {
+                    switch (viewType)
+                    {
+                        case ProjectViewType.Grid64:
+                            tileSize = 64;
+                            break;
+                        case ProjectViewType.Grid48:
+                            tileSize = 48;
+                            break;
+                        case ProjectViewType.Grid32:
+                            tileSize = 32;
+                            break;
+                    }
+
+                    gridLayout = true;
+                }
+
+                this.window = window;
             }
 
+            public GUILayout main;
+            public GUIPanel overlay;
+            public GUIPanel underlay;
+
+            public ProjectWindow window;
+            public int tileSize;
+            public bool gridLayout;
+        }
+
+        private class ElementEntry
+        {
+            // Note: Order of these is relevant
+            enum UnderlayState
+            {
+                None, Hovered, Selected, Pinged
+            }
+
+            public string path;
             public GUITexture icon;
             public GUILabel label;
+            public Rect2I bounds;
+
+            private GUITexture underlay;
+            private ContentInfo info;
+            private UnderlayState underlayState;
+
+            public ElementEntry(ContentInfo info, GUILayout parent, LibraryEntry entry)
+            {
+                GUILayout entryLayout;
+
+                if (info.gridLayout)
+                    entryLayout = parent.AddLayoutY();
+                else
+                    entryLayout = parent.AddLayoutX();
+
+                SpriteTexture iconTexture = GetIcon(entry);
+
+                icon = new GUITexture(iconTexture, GUIImageScaleMode.ScaleToFit,
+                    true, GUIOption.FixedHeight(info.tileSize), GUIOption.FixedWidth(info.tileSize));
+
+                label = null;
+
+                if (info.gridLayout)
+                {
+                    label = new GUILabel(entry.Name, EditorStyles.MultiLineLabel,
+                        GUIOption.FixedWidth(info.tileSize), GUIOption.FlexibleHeight(0, MAX_LABEL_HEIGHT));
+                }
+                else
+                {
+                    label = new GUILabel(entry.Name);
+                }
+
+                entryLayout.AddElement(icon);
+                entryLayout.AddElement(label);
+
+                this.info = info;
+                this.path = entry.Path;
+                this.bounds = new Rect2I();
+                this.underlay = null;
+            }
+
+            public void Initialize()
+            {
+                bounds = icon.Bounds;
+                Rect2I labelBounds = label.Bounds;
+
+                bounds.x = MathEx.Min(bounds.x, labelBounds.x);
+                bounds.y = MathEx.Min(bounds.y, labelBounds.y);
+                bounds.width = MathEx.Max(bounds.x + bounds.width,
+                    labelBounds.x + labelBounds.width) - bounds.x;
+                bounds.height = MathEx.Max(bounds.y + bounds.height,
+                    labelBounds.y + labelBounds.height) - bounds.y;
+
+                ProjectWindow hoistedWindow = info.window;
+                string hoistedPath = path;
+
+                GUIButton overlayBtn = new GUIButton("", EditorStyles.Blank);
+                overlayBtn.Bounds = bounds;
+                overlayBtn.OnClick += () => hoistedWindow.OnEntryClicked(hoistedPath);
+                overlayBtn.OnDoubleClick += () => hoistedWindow.OnEntryDoubleClicked(hoistedPath);
+                overlayBtn.SetContextMenu(info.window.entryContextMenu);
+
+                info.overlay.AddElement(overlayBtn);
+            }
+
+            public Rect2I Bounds
+            {
+                get { return bounds; }
+            }
+
+            public void MarkAsCut(bool enable)
+            {
+                if (enable)
+                    icon.SetTint(CUT_COLOR);
+                else
+                    icon.SetTint(Color.White);
+            }
+
+            public void MarkAsSelected(bool enable)
+            {
+                if ((int)underlayState > (int) UnderlayState.Selected)
+                    return;
+
+                if (enable)
+                {
+                    CreateUnderlay();
+                    underlay.SetTint(SELECTION_COLOR);
+                }
+                else
+                    ClearUnderlay();
+
+                underlayState = UnderlayState.Selected;
+            }
+
+            public void MarkAsPinged(bool enable)
+            {
+                if ((int)underlayState > (int)UnderlayState.Pinged)
+                    return;
+
+                if (enable)
+                {
+                    CreateUnderlay();
+                    underlay.SetTint(PING_COLOR);
+                }
+                else
+                    ClearUnderlay();
+
+                underlayState = UnderlayState.Pinged;
+            }
+
+            public void MarkAsHovered(bool enable)
+            {
+                if ((int)underlayState > (int)UnderlayState.Hovered)
+                    return;
+
+                if (enable)
+                {
+                    CreateUnderlay();
+                    underlay.SetTint(HOVER_COLOR);
+                }
+                else
+                    ClearUnderlay();
+
+                underlayState = UnderlayState.Hovered;
+            }
+
+            private void ClearUnderlay()
+            {
+                if (underlay != null)
+                {
+                    underlay.Destroy();
+                    underlay = null;
+                }
+
+                underlayState = UnderlayState.None;
+            }
+
+            private void CreateUnderlay()
+            {
+                if (underlay == null)
+                {
+                    underlay = new GUITexture(Builtin.WhiteTexture);
+                    underlay.Bounds = Bounds;
+
+                    info.underlay.AddElement(underlay);
+                }
+            }
+
+            private static SpriteTexture GetIcon(LibraryEntry entry)
+            {
+                if (entry.Type == LibraryEntryType.Directory)
+                {
+                    return EditorBuiltin.FolderIcon;
+                }
+                else
+                {
+                    FileEntry fileEntry = (FileEntry)entry;
+                    switch (fileEntry.ResType)
+                    {
+                        case ResourceType.Font:
+                            return EditorBuiltin.FontIcon;
+                        case ResourceType.Mesh:
+                            return EditorBuiltin.MeshIcon;
+                        case ResourceType.Texture:
+                            return EditorBuiltin.TextureIcon;
+                        case ResourceType.PlainText:
+                            return null; // TODO
+                        case ResourceType.ScriptCode:
+                            return null; // TODO
+                        case ResourceType.SpriteTexture:
+                            return null; // TODO
+                        case ResourceType.Shader:
+                            return null; // TODO
+                        case ResourceType.Material:
+                            return null; // TODO
+                    }
+                }
+
+                return null;
+            }
         }
 
         private const int GRID_ENTRY_SPACING = 15;
         private const int LIST_ENTRY_SPACING = 7;
         private const int MAX_LABEL_HEIGHT = 50;
+        private const int DRAG_SCROLL_HEIGHT = 50;
+        private const int DRAG_SCROLL_AMOUNT_PER_SECOND = 200;
         private static readonly Color PING_COLOR = Color.BansheeOrange;
         private static readonly Color SELECTION_COLOR = Color.DarkCyan;
+        private static readonly Color HOVER_COLOR = new Color(Color.DarkCyan.r, Color.DarkCyan.g, Color.DarkCyan.b, 0.5f);
+        private static readonly Color CUT_COLOR = new Color(1.0f, 1.0f, 1.0f, 0.5f);
 
         private bool hasContentFocus = false;
         private bool HasContentFocus { get { return HasFocus && hasContentFocus; } } // TODO - This is dummy and never set
@@ -38,15 +270,20 @@ namespace BansheeEditor
         private string currentDirectory = "";
         private List<string> selectionPaths = new List<string>();
         private string pingPath = "";
+        private string hoverHighlightPath = "";
 
         private GUIScrollArea contentScrollArea;
         private GUIPanel scrollAreaPanel;
-
         private GUIButton optionsButton;
 
         private ContextMenu entryContextMenu;
+        private ProjectDropTarget dropTarget;
+
+        private List<ElementEntry> entries = new List<ElementEntry>();
+        private Dictionary<string, ElementEntry> entryLookup = new Dictionary<string, ElementEntry>(); 
+        private GUITexture pingUnderlay;
 
-        private Dictionary<string, EntryGUI> pathToGUIEntry = new Dictionary<string, EntryGUI>();
+        private int autoScrollAmount;
 
         // Cut/Copy/Paste
         private List<string> copyPaths = new List<string>();
@@ -96,50 +333,220 @@ namespace BansheeEditor
             entryContextMenu.AddItem("Duplicate", DuplicateSelection, new ShortcutKey(ButtonModifier.Ctrl, ButtonCode.D));
             entryContextMenu.AddItem("Paste", PasteToSelection, new ShortcutKey(ButtonModifier.Ctrl, ButtonCode.V));
 
+            dropTarget = new ProjectDropTarget(this);
+            dropTarget.Bounds = contentScrollArea.Bounds;
+            dropTarget.OnStart += DoOnDragStart; 
+            dropTarget.OnDrag += DoOnDragMove;
+            dropTarget.OnLeave += DoOnDragLeave;
+            dropTarget.OnDrop += DoOnDragDropped;
+
             Reset();
         }
 
+        private ElementEntry FindElementAt(Vector2I windowPos)
+        {
+            Rect2I scrollBounds = contentScrollArea.Bounds;
+            Vector2I scrollPos = windowPos;
+            scrollPos.x -= scrollBounds.x;
+            scrollPos.y -= scrollBounds.y;
+
+            foreach (var element in entries)
+            {
+                if (element.bounds.Contains(scrollPos))
+                    return element;
+            }
+
+            return null;
+        }
+
+        private void DoOnDragStart(Vector2I windowPos)
+        {
+            ElementEntry underCursorElem = FindElementAt(windowPos);
+            if (underCursorElem == null)
+                return;
+
+            if (!selectionPaths.Contains(underCursorElem.path))
+                Select(new List<string> { underCursorElem.path });
+
+            ResourceDragDropData dragDropData = new ResourceDragDropData(selectionPaths.ToArray());
+            DragDrop.StartDrag(dragDropData);
+        }
+
+        private void DoOnDragMove(Vector2I windowPos)
+        {
+            ElementEntry underCursorElem = FindElementAt(windowPos);
+            if (underCursorElem == null)
+            {
+                if (!string.IsNullOrEmpty(hoverHighlightPath))
+                {
+                    ElementEntry previousUnderCursorElem;
+                    if (entryLookup.TryGetValue(hoverHighlightPath, out previousUnderCursorElem))
+                        previousUnderCursorElem.MarkAsHovered(false);
+                }
+
+                hoverHighlightPath = "";
+            }
+            else
+            {
+                if (underCursorElem.path != hoverHighlightPath)
+                {
+                    if (!string.IsNullOrEmpty(hoverHighlightPath))
+                    {
+                        ElementEntry previousUnderCursorElem;
+                        if (entryLookup.TryGetValue(hoverHighlightPath, out previousUnderCursorElem))
+                            previousUnderCursorElem.MarkAsHovered(false);
+                    }
+
+                    hoverHighlightPath = underCursorElem.path;
+                    underCursorElem.MarkAsHovered(true);
+                }
+            }
+
+            Rect2I scrollAreaBounds = contentScrollArea.Bounds;
+            int scrollAreaTop = scrollAreaBounds.y;
+            int scrollAreaBottom = scrollAreaBounds.y + scrollAreaBounds.height;
+
+            if (windowPos.y > scrollAreaTop && windowPos.y <= (scrollAreaTop + DRAG_SCROLL_HEIGHT))
+                autoScrollAmount = -DRAG_SCROLL_AMOUNT_PER_SECOND;
+            else if (windowPos.y >= (scrollAreaBottom - DRAG_SCROLL_HEIGHT) && windowPos.y < scrollAreaBottom)
+                autoScrollAmount = DRAG_SCROLL_AMOUNT_PER_SECOND;
+            else
+                autoScrollAmount = 0;
+        }
+
+        private void DoOnDragLeave()
+        {
+            if (!string.IsNullOrEmpty(hoverHighlightPath))
+            {
+                ElementEntry previousUnderCursorElem;
+                if (entryLookup.TryGetValue(hoverHighlightPath, out previousUnderCursorElem))
+                    previousUnderCursorElem.MarkAsHovered(false);
+            }
+
+            hoverHighlightPath = "";
+        }
+
+        private void DoOnDragDropped(Vector2I windowPos, string[] paths)
+        {
+            string destinationFolder = currentDirectory;
+
+            ElementEntry underCursorElement = FindElementAt(windowPos);
+            if (underCursorElement != null)
+            {
+                LibraryEntry entry = ProjectLibrary.GetEntry(underCursorElement.path);
+                if (entry != null && entry.Type == LibraryEntryType.Directory)
+                    destinationFolder = entry.Path;
+            }
+
+            if (paths != null)
+            {
+                foreach (var path in paths)
+                {
+                    if (PathEx.IsPartOf(destinationFolder, path) || PathEx.Compare(path, destinationFolder))
+                        continue;
+
+                    string destination = Path.Combine(destinationFolder, Path.GetFileName(path));
+
+                    ProjectLibrary.Move(path, destination, true);
+                }
+            }
+        }
+
         public void Ping(Resource resource)
         {
-            pingPath = ProjectLibrary.GetPath(resource);
+            if (!string.IsNullOrEmpty(pingPath))
+            {
+                ElementEntry entry;
+                if (entryLookup.TryGetValue(pingPath, out entry))
+                    entry.MarkAsPinged(false);
+            }
 
-            Refresh();
-            ScrollToEntry(pingPath);
+            if (resource != null)
+                pingPath = ProjectLibrary.GetPath(resource);
+            else
+                pingPath = "";
+
+            if (!string.IsNullOrEmpty(pingPath))
+            {
+                ElementEntry entry;
+                if (entryLookup.TryGetValue(pingPath, out entry))
+                    entry.MarkAsPinged(true);
+
+                ScrollToEntry(pingPath);
+            }
         }
 
         private void Select(List<string> paths)
         {
+            if (selectionPaths != null)
+            {
+                foreach (var path in selectionPaths)
+                {
+                    ElementEntry entry;
+                    if (entryLookup.TryGetValue(path, out entry))
+                        entry.MarkAsSelected(false);
+                }
+            }
+
             selectionPaths = paths;
-            pingPath = "";
 
-            Refresh();
+            if (selectionPaths != null)
+            {
+                foreach (var path in selectionPaths)
+                {
+                    ElementEntry entry;
+                    if (entryLookup.TryGetValue(path, out entry))
+                        entry.MarkAsSelected(true);
+                }
+            }
+
+            Ping(null);
         }
 
         private void EnterDirectory(string directory)
         {
             currentDirectory = directory;
-            pingPath = "";
-            selectionPaths.Clear();
+            Ping(null);
+            Select(new List<string>());
 
             Refresh();
         }
 
         private void Cut(IEnumerable<string> sourcePaths)
         {
+            foreach (var path in cutPaths)
+            {
+                ElementEntry entry;
+                if (entryLookup.TryGetValue(path, out entry))
+                    entry.MarkAsCut(false);
+            }
+
             cutPaths.Clear();
             cutPaths.AddRange(sourcePaths);
-            copyPaths.Clear();
 
-            Refresh();
+            foreach (var path in cutPaths)
+            {
+                ElementEntry entry;
+                if (entryLookup.TryGetValue(path, out entry))
+                    entry.MarkAsCut(true);
+            }
+
+            copyPaths.Clear();
         }
 
         private void Copy(IEnumerable<string> sourcePaths)
         {
             copyPaths.Clear();
             copyPaths.AddRange(sourcePaths);
-            cutPaths.Clear();
 
-            Refresh();
+            foreach (var path in cutPaths)
+            {
+                ElementEntry entry;
+                if (entryLookup.TryGetValue(path, out entry))
+                    entry.MarkAsCut(false);
+            }
+
+            cutPaths.Clear();
         }
 
         private void Duplicate(IEnumerable<string> sourcePaths)
@@ -210,11 +617,15 @@ namespace BansheeEditor
                 }
             }
 
-            // TODO - Handle input, drag and drop and whatever else might be needed
-            // TODO - Animate ping?
-            // TODO - Automatically scroll window when dragging near border?
-            // TODO - Drag and drop from Explorer should work to import an asset (i.e. DragAndDropArea)
-            // - This should be something that should be enabled per editor window perhaps?
+            if (autoScrollAmount != 0)
+            {
+                Rect2I contentBounds = contentScrollArea.ContentBounds;
+                float scrollPct = DRAG_SCROLL_AMOUNT_PER_SECOND / (float)contentBounds.height;
+
+                contentScrollArea.VerticalScroll += scrollPct;
+            }
+
+            dropTarget.Update();
         }
 
         private void OnEntryChanged(string entry)
@@ -227,8 +638,8 @@ namespace BansheeEditor
             Rect2I contentBounds = scrollAreaPanel.Bounds;
             Rect2I scrollAreaBounds = contentScrollArea.ContentBounds;
 
-            EntryGUI entryGUI;
-            if (!pathToGUIEntry.TryGetValue(path, out entryGUI))
+            ElementEntry entryGUI;
+            if (!entryLookup.TryGetValue(path, out entryGUI))
                 return;
 
             Rect2I entryBounds = entryGUI.icon.Bounds;
@@ -237,39 +648,6 @@ namespace BansheeEditor
             contentScrollArea.VerticalScroll = percent;
         }
 
-        private SpriteTexture GetIcon(LibraryEntry entry)
-        {
-            if (entry.Type == LibraryEntryType.Directory)
-            {
-                return EditorBuiltin.FolderIcon;
-            }
-            else
-            {
-                FileEntry fileEntry = (FileEntry)entry;
-                switch (fileEntry.ResType)
-                {
-                    case ResourceType.Font:
-                        return EditorBuiltin.FontIcon;
-                    case ResourceType.Mesh:
-                        return EditorBuiltin.MeshIcon;
-                    case ResourceType.Texture:
-                        return EditorBuiltin.TextureIcon;
-                    case ResourceType.PlainText:
-                        return null; // TODO
-                    case ResourceType.ScriptCode:
-                        return null; // TODO
-                    case ResourceType.SpriteTexture:
-                        return null; // TODO
-                    case ResourceType.Shader:
-                        return null; // TODO
-                    case ResourceType.Material:
-                        return null; // TODO
-                }
-            }
-
-            return null;
-        }
-
         private void Refresh()
         {
             DirectoryEntry entry = ProjectLibrary.GetEntry(currentDirectory) as DirectoryEntry;
@@ -282,14 +660,11 @@ namespace BansheeEditor
             if (scrollAreaPanel != null)
                 scrollAreaPanel.Destroy();
 
-            pathToGUIEntry.Clear();
+            entries.Clear();
+            entryLookup.Clear();
             scrollAreaPanel = contentScrollArea.Layout.AddPanel();
 
-            GUIPanel contentPanel = scrollAreaPanel.AddPanel(1);
-            GUIPanel contentOverlayPanel = scrollAreaPanel.AddPanel(0);
-            GUIPanel contentUnderlayPanel = scrollAreaPanel.AddPanel(2);
-
-            GUILayout contentLayout = contentPanel.AddLayoutY();
+            ContentInfo contentInfo = new ContentInfo(this, viewType);
 
             Rect2I scrollBounds = contentScrollArea.Bounds;
             LibraryEntry[] childEntries = entry.Children;
@@ -303,15 +678,15 @@ namespace BansheeEditor
 
                 for (int i = 0; i < childEntries.Length; i++)
                 {
-                    LibraryEntry currentEntry = childEntries[i];
-
-                    CreateEntryGUI(contentLayout, tileSize, false, currentEntry);
+                    ElementEntry guiEntry = new ElementEntry(contentInfo, contentInfo.main, childEntries[i]);
+                    entries.Add(guiEntry);
+                    entryLookup[guiEntry.path] = guiEntry;
 
                     if (i != childEntries.Length - 1)
-                        contentLayout.AddSpace(LIST_ENTRY_SPACING);
+                        contentInfo.main.AddSpace(LIST_ENTRY_SPACING);
                 }
 
-                contentLayout.AddFlexibleSpace();
+                contentInfo.main.AddFlexibleSpace();
             }
             else
             {
@@ -323,7 +698,7 @@ namespace BansheeEditor
                     case ProjectViewType.Grid32: tileSize = 32; break;
                 }
 
-                GUILayoutX rowLayout = contentLayout.AddLayoutX();
+                GUILayoutX rowLayout = contentInfo.main.AddLayoutX();
                 rowLayout.AddFlexibleSpace();
 
                 int elemSize = tileSize + GRID_ENTRY_SPACING;
@@ -335,15 +710,17 @@ namespace BansheeEditor
                 {
                     if (elemsInRow == elemsPerRow && elemsInRow > 0)
                     {
-                        rowLayout = contentLayout.AddLayoutX();
-                        contentLayout.AddSpace(GRID_ENTRY_SPACING);
+                        rowLayout = contentInfo.main.AddLayoutX();
+                        contentInfo.main.AddSpace(GRID_ENTRY_SPACING);
 
                         rowLayout.AddFlexibleSpace();
                         elemsInRow = 0;
                     }
 
-                    LibraryEntry currentEntry = childEntries[i];
-                    CreateEntryGUI(rowLayout, tileSize, true, currentEntry);
+                    ElementEntry guiEntry = new ElementEntry(contentInfo, rowLayout, childEntries[i]);
+                    entries.Add(guiEntry);
+                    entryLookup[guiEntry.path] = guiEntry;
+
                     rowLayout.AddFlexibleSpace();
 
                     elemsInRow++;
@@ -356,100 +733,30 @@ namespace BansheeEditor
                     rowLayout.AddFlexibleSpace();
                 }
 
-                contentLayout.AddFlexibleSpace();
+                contentInfo.main.AddFlexibleSpace();
             }
 
-            for (int i = 0; i < childEntries.Length; i++)
+            for (int i = 0; i < entries.Count; i++)
             {
-                LibraryEntry currentEntry = childEntries[i];
-                CreateEntryOverlayGUI(contentOverlayPanel, contentUnderlayPanel, pathToGUIEntry[currentEntry.Path], currentEntry);
+                ElementEntry guiEntry = entries[i];
+                guiEntry.Initialize();
+
+                if (cutPaths.Contains(guiEntry.path))
+                    guiEntry.MarkAsCut(true);
+
+                if (selectionPaths.Contains(guiEntry.path))
+                    guiEntry.MarkAsSelected(true);
+                else if (pingPath == guiEntry.path)
+                    guiEntry.MarkAsPinged(true);
             }
 
-            Rect2I contentBounds = contentLayout.Bounds;
+            Rect2I contentBounds = contentInfo.main.Bounds;
             GUIButton catchAll = new GUIButton("", EditorStyles.Blank);
             catchAll.Bounds = contentBounds;
             catchAll.OnClick += OnCatchAllClicked;
             catchAll.SetContextMenu(entryContextMenu);
 
-            contentUnderlayPanel.AddElement(catchAll);
-
-            Debug.Log("REFRESHED " + Time.FrameNumber);
-        }
-
-        private void CreateEntryGUI(GUILayout parentLayout, int tileSize, bool grid, LibraryEntry entry)
-        {
-            GUILayout entryLayout;
-            
-            if(grid)
-                entryLayout = parentLayout.AddLayoutY();
-            else
-                entryLayout = parentLayout.AddLayoutX();
-
-            SpriteTexture iconTexture = GetIcon(entry);
-
-            GUITexture icon = new GUITexture(iconTexture, GUIImageScaleMode.ScaleToFit,
-                true, GUIOption.FixedHeight(tileSize), GUIOption.FixedWidth(tileSize));
-
-            GUILabel label = null;
-
-            if (grid)
-            {
-                label = new GUILabel(entry.Name, EditorStyles.MultiLineLabel,
-                    GUIOption.FixedWidth(tileSize), GUIOption.FlexibleHeight(0, MAX_LABEL_HEIGHT));
-            }
-            else
-            {
-                label = new GUILabel(entry.Name);   
-            }
-
-            entryLayout.AddElement(icon);
-            entryLayout.AddElement(label);
-
-            pathToGUIEntry[entry.Path] = new EntryGUI(icon, label);
-        }
-
-        private void CreateEntryOverlayGUI(GUIPanel overlayPanel, GUIPanel underlayPanel, EntryGUI gui, LibraryEntry entry)
-        {
-            // Add overlay button
-            Rect2I entryButtonBounds = gui.icon.Bounds;
-            Rect2I labelBounds = gui.label.Bounds;
-
-            entryButtonBounds.x = MathEx.Min(entryButtonBounds.x, labelBounds.x);
-            entryButtonBounds.y = MathEx.Min(entryButtonBounds.y, labelBounds.y);
-            entryButtonBounds.width = MathEx.Max(entryButtonBounds.x + entryButtonBounds.width,
-                labelBounds.x + labelBounds.width) - entryButtonBounds.x;
-            entryButtonBounds.height = MathEx.Max(entryButtonBounds.y + entryButtonBounds.height,
-                labelBounds.y + labelBounds.height) - entryButtonBounds.y;
-
-            GUIButton overlayBtn = new GUIButton("", EditorStyles.Blank);
-            overlayBtn.Bounds = entryButtonBounds;
-            overlayBtn.OnClick += () => OnEntryClicked(entry.Path);
-            overlayBtn.OnDoubleClick += () => OnEntryDoubleClicked(entry.Path);
-            overlayBtn.SetContextMenu(entryContextMenu);
-
-            overlayPanel.AddElement(overlayBtn);
-
-            if (cutPaths.Contains(entry.Path))
-            {
-                gui.icon.SetTint(new Color(1.0f, 1.0f, 1.0f, 0.5f));
-            }
-
-            if (selectionPaths.Contains(entry.Path))
-            {
-                GUITexture underlay = new GUITexture(Builtin.WhiteTexture);
-                underlay.Bounds = entryButtonBounds;
-                underlay.SetTint(SELECTION_COLOR);
-
-                underlayPanel.AddElement(underlay);
-            }
-            else if (pingPath == entry.Path)
-            {
-                GUITexture underlay = new GUITexture(Builtin.WhiteTexture);
-                underlay.Bounds = entryButtonBounds;
-                underlay.SetTint(PING_COLOR);
-
-                underlayPanel.AddElement(underlay);
-            }
+            contentInfo.underlay.AddElement(catchAll);
         }
 
         private void OnEntryClicked(string path)
@@ -457,14 +764,10 @@ namespace BansheeEditor
             Select(new List<string> { path });
 
             Selection.resourcePaths = new string[] {path};
-
-            Debug.Log("CLICKED " + Time.FrameNumber);
         }
 
         private void OnEntryDoubleClicked(string path)
         {
-            Debug.Log("DOUBLE CLICK " + path);
-
             LibraryEntry entry = ProjectLibrary.GetEntry(path);
             if (entry != null && entry.Type == LibraryEntryType.Directory)
             {
@@ -546,6 +849,8 @@ namespace BansheeEditor
             base.WindowResized(width, height);
 
             Refresh();
+
+            dropTarget.Bounds = contentScrollArea.Bounds;
         }
     }
 

+ 1 - 0
MBansheeEngine/MBansheeEngine.csproj

@@ -102,6 +102,7 @@
     <Compile Include="Mesh.cs" />
     <Compile Include="MeshData.cs" />
     <Compile Include="MissingComponent.cs" />
+    <Compile Include="PathEx.cs" />
     <Compile Include="PixelData.cs" />
     <Compile Include="PixelUtility.cs" />
     <Compile Include="PlainText.cs" />

+ 6 - 0
MBansheeEngine/Math/Vector2I.cs

@@ -62,6 +62,12 @@ namespace BansheeEngine
             }
         }
 
+        // Manhattan distance
+        public static int Distance(Vector2I a, Vector2I b)
+		{
+			return Math.Abs(b.x - a.x) + Math.Abs(b.y - a.y);
+		}
+
         public static Vector2I operator +(Vector2I a, Vector2I b)
         {
             return new Vector2I(a.x + b.x, a.y + b.y);

+ 17 - 0
MBansheeEngine/PathEx.cs

@@ -0,0 +1,17 @@
+using System.IO;
+
+namespace BansheeEngine
+{
+    public static class PathEx
+    {
+        public static bool Compare(string a, string b)
+        {
+            return Path.GetFullPath(a) == Path.GetFullPath(b);
+        }
+
+        public static bool IsPartOf(string path, string parent)
+        {
+            return Path.GetFullPath(path).StartsWith(Path.GetFullPath(parent));
+        }
+    }
+}

+ 4 - 1
SBansheeEditor/Include/BsScriptEditorWindow.h

@@ -22,6 +22,7 @@ namespace BansheeEngine
 		~ScriptEditorWindow();
 
 		EditorWidgetBase* getEditorWidget() const;
+		bool isDestroyed() const { return mIsDestroyed; }
 
 		static void registerManagedEditorWindows();
 		static void clearRegisteredEditorWindow();
@@ -54,6 +55,7 @@ namespace BansheeEngine
 		HEvent mOnFocusChangedConn;
 		HEvent mOnAssemblyRefreshStartedConn;
 		bool mRefreshInProgress;
+		bool mIsDestroyed;
 
 		static MonoMethod* onResizedMethod;
 		static MonoMethod* onFocusChangedMethod;
@@ -81,6 +83,7 @@ namespace BansheeEngine
 		void triggerOnInitialize();
 		void triggerOnDestroy();
 
+		void setScriptOwner(ScriptEditorWindow* owner) { mScriptOwner = owner; }
 		MonoObject* getManagedInstance() const { return mManagedInstance; }
 
 	private:
@@ -96,7 +99,7 @@ namespace BansheeEngine
 		UpdateThunkDef mUpdateThunk;
 		MonoObject* mManagedInstance;
 
-		ScriptEditorWindow* mScriptParent;
+		ScriptEditorWindow* mScriptOwner;
 		ScriptGUILayout* mContentsPanel;
 	};
 }

+ 16 - 13
SBansheeEditor/Include/BsScriptDropTarget.h → SBansheeEditor/Include/BsScriptOSDropTarget.h

@@ -2,17 +2,17 @@
 
 #include "BsScriptEditorPrerequisites.h"
 #include "BsScriptObject.h"
-#include "BsScriptDropTarget.h"
 #include "BsRect2I.h"
 
 namespace BansheeEngine
 {
-	class BS_SCR_BED_EXPORT ScriptDropTarget : public ScriptObject <ScriptDropTarget>
+	class BS_SCR_BED_EXPORT ScriptOSDropTarget : public ScriptObject <ScriptOSDropTarget>
 	{
 	public:
-		SCRIPT_OBJ(EDITOR_ASSEMBLY, "BansheeEditor", "DropTarget")
+		SCRIPT_OBJ(EDITOR_ASSEMBLY, "BansheeEditor", "OSDropTarget")
 
 	private:
+		ScriptEditorWindow* mParent;
 		OSDropTarget* mDropTarget;
 		Rect2I mParentArea;
 		Rect2I mArea;
@@ -25,8 +25,8 @@ namespace BansheeEngine
 		HEvent mWidgetParentChangedConn;
 		HEvent mWidgetResizedConn;
 
-		ScriptDropTarget(MonoObject* instance, EditorWidgetBase* parent);
-		~ScriptDropTarget();
+		ScriptOSDropTarget(MonoObject* instance, ScriptEditorWindow* parent);
+		~ScriptOSDropTarget();
 
 		void destroy();
 		void setDropTarget(const RenderWindowPtr& parentWindow, INT32 x, INT32 y, UINT32 width, UINT32 height);
@@ -35,20 +35,23 @@ namespace BansheeEngine
 		void widgetParentChanged(EditorWidgetContainer* parent);
 		void widgetResized(UINT32 width, UINT32 height);
 
+		EditorWidgetBase* getParentWidget() const;
+		Rect2I getDropTargetArea() const;
+
 		typedef void(__stdcall *OnEnterThunkDef) (MonoObject*, INT32, INT32, MonoException**);
 		typedef void(__stdcall *OnMoveDef) (MonoObject*, INT32, INT32, MonoException**);
 		typedef void(__stdcall *OnLeaveDef) (MonoObject*, MonoException**);
 		typedef void(__stdcall *OnDropThunkDef) (MonoObject*, INT32, INT32, MonoException**);
 
 		static void internal_CreateInstance(MonoObject* instance, ScriptEditorWindow* editorWindow);
-		static void internal_Destroy(ScriptDropTarget* nativeInstance);
-		static void internal_SetBounds(ScriptDropTarget* nativeInstance, Rect2I bounds);
-		static MonoArray* internal_GetFilePaths(ScriptDropTarget* nativeInstance);
-
-		static void dropTargetDragEnter(MonoObject* instance, INT32 x, INT32 y);
-		static void dropTargetDragMove(MonoObject* instance, INT32 x, INT32 y);
-		static void dropTargetDragLeave(MonoObject* instance);
-		static void dropTargetDragDropped(MonoObject* instance, INT32 x, INT32 y);
+		static void internal_Destroy(ScriptOSDropTarget* nativeInstance);
+		static void internal_SetBounds(ScriptOSDropTarget* nativeInstance, Rect2I bounds);
+		static MonoArray* internal_GetFilePaths(ScriptOSDropTarget* nativeInstance);
+
+		static void dropTargetDragEnter(ScriptOSDropTarget* thisPtr, INT32 x, INT32 y);
+		static void dropTargetDragMove(ScriptOSDropTarget* thisPtr, INT32 x, INT32 y);
+		static void dropTargetDragLeave(ScriptOSDropTarget* thisPtr);
+		static void dropTargetDragDropped(ScriptOSDropTarget* thisPtr, INT32 x, INT32 y);
 
 		static OnEnterThunkDef onEnterThunk;
 		static OnMoveDef onMoveThunk;

+ 2 - 2
SBansheeEditor/SBansheeEditor.vcxproj

@@ -241,7 +241,7 @@
     <ClInclude Include="Include\BsScriptCodeEditor.h" />
     <ClInclude Include="Include\BsScriptDragDropManager.h" />
     <ClInclude Include="Include\BsScriptDropDownWindow.h" />
-    <ClInclude Include="Include\BsScriptDropTarget.h" />
+    <ClInclude Include="Include\BsScriptOSDropTarget.h" />
     <ClInclude Include="Include\BsScriptEditorApplication.h" />
     <ClInclude Include="Include\BsScriptEditorBuiltin.h" />
     <ClInclude Include="Include\BsScriptEditorPrerequisites.h" />
@@ -286,7 +286,7 @@
     <ClCompile Include="Source\BsGUIResourceField.cpp" />
     <ClCompile Include="Source\BsScriptBrowseDialog.cpp" />
     <ClCompile Include="Source\BsScriptDropDownWindow.cpp" />
-    <ClCompile Include="Source\BsScriptDropTarget.cpp" />
+    <ClCompile Include="Source\BsScriptOSDropTarget.cpp" />
     <ClCompile Include="Source\BsScriptEditorApplication.cpp" />
     <ClCompile Include="Source\BsScriptEditorBuiltin.cpp" />
     <ClCompile Include="Source\BsScriptEditorPlugin.cpp" />

+ 4 - 4
SBansheeEditor/SBansheeEditor.vcxproj.filters

@@ -138,10 +138,10 @@
     <ClInclude Include="Include\BsScriptEditorBuiltin.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="Include\BsScriptDropTarget.h">
+    <ClInclude Include="Include\BsScriptDropDownWindow.h">
       <Filter>Header Files</Filter>
     </ClInclude>
-    <ClInclude Include="Include\BsScriptDropDownWindow.h">
+    <ClInclude Include="Include\BsScriptOSDropTarget.h">
       <Filter>Header Files</Filter>
     </ClInclude>
   </ItemGroup>
@@ -269,10 +269,10 @@
     <ClCompile Include="Source\BsScriptEditorBuiltin.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="Source\BsScriptDropTarget.cpp">
+    <ClCompile Include="Source\BsScriptDropDownWindow.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
-    <ClCompile Include="Source\BsScriptDropDownWindow.cpp">
+    <ClCompile Include="Source\BsScriptOSDropTarget.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
   </ItemGroup>

+ 1 - 1
SBansheeEditor/Source/BsScriptDropDownWindow.cpp

@@ -52,7 +52,7 @@ namespace BansheeEngine
 		Vector2I position, int width, int height)
 	{
 		ManagedDropDownWindow* dropDownWindow = nullptr;
-		if (parentWindow != nullptr)
+		if (parentWindow != nullptr && !parentWindow->isDestroyed())
 		{
 			EditorWidgetBase* editorWidget = parentWindow->getEditorWidget();
 			EditorWidgetContainer* parentContainer = editorWidget->_getParent();

+ 0 - 205
SBansheeEditor/Source/BsScriptDropTarget.cpp

@@ -1,205 +0,0 @@
-#include "BsScriptDropTarget.h"
-#include "BsScriptMeta.h"
-#include "BsMonoField.h"
-#include "BsMonoClass.h"
-#include "BsMonoManager.h"
-#include "BsMonoMethod.h"
-#include "BsMonoUtil.h"
-#include "BsMonoArray.h"
-#include "BsRTTIType.h"
-#include "BsPlatform.h"
-#include "BsEditorWidget.h"
-#include "BsEditorWindowBase.h"
-#include "BsEditorWidgetContainer.h"
-#include "BsScriptEditorWindow.h"
-
-using namespace std::placeholders;
-
-namespace BansheeEngine
-{
-	ScriptDropTarget::OnEnterThunkDef ScriptDropTarget::onEnterThunk;
-	ScriptDropTarget::OnMoveDef ScriptDropTarget::onMoveThunk;
-	ScriptDropTarget::OnLeaveDef ScriptDropTarget::onLeaveThunk;
-	ScriptDropTarget::OnDropThunkDef ScriptDropTarget::onDropThunk;
-
-	ScriptDropTarget::ScriptDropTarget(MonoObject* instance, EditorWidgetBase* parent)
-		:ScriptObject(instance), mDropTarget(nullptr), mIsDestroyed(false)
-	{
-		mWidgetParentChangedConn = parent->onParentChanged.connect(std::bind(&ScriptDropTarget::widgetParentChanged, this, _1));
-		mWidgetResizedConn = parent->onResized.connect(std::bind(&ScriptDropTarget::widgetResized, this, _1, _2));
-
-		if (parent != nullptr)
-		{
-			EditorWindowBase* parentWindow = parent->getParentWindow();
-
-			if (parentWindow != nullptr)
-				setDropTarget(parentWindow->getRenderWindow(), 0, 0, 0, 0);
-		}
-	}
-
-	ScriptDropTarget::~ScriptDropTarget()
-	{
-		if (!mIsDestroyed)
-			destroy();
-	}
-
-	void ScriptDropTarget::initRuntimeData()
-	{
-		metaData.scriptClass->addInternalCall("Internal_CreateInstance", &ScriptDropTarget::internal_CreateInstance);
-		metaData.scriptClass->addInternalCall("Internal_Destroy", &ScriptDropTarget::internal_Destroy);
-		metaData.scriptClass->addInternalCall("Internal_SetBounds", &ScriptDropTarget::internal_SetBounds);
-		metaData.scriptClass->addInternalCall("Internal_GetFilePaths", &ScriptDropTarget::internal_GetFilePaths);
-
-		onEnterThunk = (OnEnterThunkDef)metaData.scriptClass->getMethod("InternalDoOnEnter", 2)->getThunk();
-		onMoveThunk = (OnMoveDef)metaData.scriptClass->getMethod("InternalDoOnDrag", 2)->getThunk();
-		onLeaveThunk = (OnLeaveDef)metaData.scriptClass->getMethod("InternalDoOnLeave")->getThunk();
-		onDropThunk = (OnDropThunkDef)metaData.scriptClass->getMethod("InternalDoOnDrop", 2)->getThunk();
-	}
-
-	void ScriptDropTarget::internal_CreateInstance(MonoObject* instance, ScriptEditorWindow* editorWindow)
-	{
-		ScriptDropTarget* nativeInstance = new (bs_alloc<ScriptDropTarget>()) ScriptDropTarget(instance, editorWindow->getEditorWidget());
-	}
-
-	void ScriptDropTarget::internal_Destroy(ScriptDropTarget* nativeInstance)
-	{
-		if (nativeInstance->mIsDestroyed)
-			return;
-
-		nativeInstance->destroy();
-	}
-
-	void ScriptDropTarget::internal_SetBounds(ScriptDropTarget* nativeInstance, Rect2I bounds)
-	{
-		if (nativeInstance->mIsDestroyed)
-			return;
-
-		nativeInstance->setBounds(bounds);
-	}
-
-	MonoArray* ScriptDropTarget::internal_GetFilePaths(ScriptDropTarget* nativeInstance)
-	{
-		OSDropTarget* dropTarget = nativeInstance->mDropTarget;
-
-		if (nativeInstance->mIsDestroyed || dropTarget == nullptr || dropTarget->getDropType() != OSDropType::FileList)
-			return ScriptArray::create<String>(0).getInternal();
-
-		Vector<WString> fileList = dropTarget->getFileList();
-		ScriptArray output = ScriptArray::create<WString>((UINT32)fileList.size());
-
-		UINT32 idx = 0;
-		for (auto& path : fileList)
-		{
-			output.set(idx, path);
-			idx++;
-		}
-
-		return output.getInternal();
-	}
-
-	void ScriptDropTarget::destroy()
-	{
-		mIsDestroyed = true;
-
-		mWidgetParentChangedConn.disconnect();
-		mWidgetResizedConn.disconnect();
-
-		setDropTarget(nullptr, 0, 0, 0, 0);
-	}
-
-	void ScriptDropTarget::setDropTarget(const RenderWindowPtr& parentWindow, INT32 x, INT32 y, UINT32 width, UINT32 height)
-	{
-		if (mDropTarget != nullptr)
-		{
-			Platform::destroyDropTarget(*mDropTarget);
-
-			mDropTargetEnterConn.disconnect();
-			mDropTargetLeaveConn.disconnect();
-			mDropTargetMoveConn.disconnect();
-			mDropTargetDroppedConn.disconnect();
-		}
-
-		if (parentWindow != nullptr)
-		{
-			mDropTarget = &Platform::createDropTarget(parentWindow.get(), x, y, width, height);
-
-			mDropTargetEnterConn = mDropTarget->onEnter.connect(std::bind(&ScriptDropTarget::dropTargetDragMove, getManagedInstance(), _1, _2));
-			mDropTargetMoveConn = mDropTarget->onDragOver.connect(std::bind(&ScriptDropTarget::dropTargetDragMove, getManagedInstance(), _1, _2));
-			mDropTargetLeaveConn = mDropTarget->onLeave.connect(std::bind(&ScriptDropTarget::dropTargetDragLeave, getManagedInstance()));
-			mDropTargetDroppedConn = mDropTarget->onDrop.connect(std::bind(&ScriptDropTarget::dropTargetDragDropped, getManagedInstance(), _1, _2));
-		}
-		else
-			mDropTarget = nullptr;
-	}
-
-	void ScriptDropTarget::setBounds(const Rect2I& bounds)
-	{
-		mArea = bounds;
-
-		Rect2I clippedArea = mArea;
-		clippedArea.clip(mParentArea);
-
-		if (mDropTarget != nullptr)
-			mDropTarget->setArea(mParentArea.x + clippedArea.x, mParentArea.y + clippedArea.y, clippedArea.width, clippedArea.height);
-	}
-
-	void ScriptDropTarget::dropTargetDragEnter(MonoObject* instance, INT32 x, INT32 y)
-	{
-		MonoException* exception = nullptr;
-		onEnterThunk(instance, x, y, &exception);
-
-		MonoUtil::throwIfException(exception);
-	}
-
-	void ScriptDropTarget::dropTargetDragMove(MonoObject* instance, INT32 x, INT32 y)
-	{
-		MonoException* exception = nullptr;
-		onMoveThunk(instance, x, y, &exception);
-
-		MonoUtil::throwIfException(exception);
-	}
-
-	void ScriptDropTarget::dropTargetDragLeave(MonoObject* instance)
-	{
-		MonoException* exception = nullptr;
-		onLeaveThunk(instance, &exception);
-
-		MonoUtil::throwIfException(exception);
-	}
-
-	void ScriptDropTarget::dropTargetDragDropped(MonoObject* instance, INT32 x, INT32 y)
-	{
-		MonoException* exception = nullptr;
-		onDropThunk(instance, x, y, &exception);
-
-		MonoUtil::throwIfException(exception);
-	}
-
-	void ScriptDropTarget::widgetParentChanged(EditorWidgetContainer* parent)
-	{
-		RenderWindowPtr parentRenderWindow;
-		if (parent != nullptr)
-		{
-			EditorWindowBase* parentWindow = parent->getParentWindow();
-
-			if (parentWindow != nullptr)
-				parentRenderWindow = parentWindow->getRenderWindow();
-		}
-
-		Rect2I clippedArea = mArea;
-		if (parentRenderWindow == nullptr)
-			mParentArea = Rect2I();
-		
-		clippedArea.clip(mParentArea);
-
-		setDropTarget(parentRenderWindow, mParentArea.x + clippedArea.x, mParentArea.y + clippedArea.y, clippedArea.width, clippedArea.height);
-	}
-
-	void ScriptDropTarget::widgetResized(UINT32 width, UINT32 height)
-	{
-		mParentArea.width = width;
-		mParentArea.height = height;
-
-		setBounds(mArea);
-	}
-}

+ 48 - 11
SBansheeEditor/Source/BsScriptEditorWindow.cpp

@@ -23,7 +23,8 @@ namespace BansheeEngine
 	MonoField* ScriptEditorWindow::guiPanelField = nullptr;
 
 	ScriptEditorWindow::ScriptEditorWindow(ScriptEditorWidget* editorWidget)
-		:ScriptObject(editorWidget->getManagedInstance()), mName(editorWidget->getName()), mEditorWidget(editorWidget), mRefreshInProgress(false)
+		:ScriptObject(editorWidget->getManagedInstance()), mName(editorWidget->getName()), mEditorWidget(editorWidget), 
+		mRefreshInProgress(false), mIsDestroyed(false)
 	{
 		mOnWidgetResizedConn = editorWidget->onResized.connect(std::bind(&ScriptEditorWindow::onWidgetResized, this, _1, _2));
 		mOnFocusChangedConn = editorWidget->onFocusChanged.connect(std::bind(&ScriptEditorWindow::onFocusChanged, this, _1));
@@ -111,7 +112,11 @@ namespace BansheeEngine
 	{
 		mRefreshInProgress = false;
 
-		mManagedInstance = mEditorWidget->getManagedInstance();
+		if (!isDestroyed())
+			mManagedInstance = mEditorWidget->getManagedInstance();
+		else
+			mManagedInstance = nullptr;
+
 		if (mManagedInstance != nullptr)
 		{
 			auto iterFind = OpenScriptEditorWindows.find(mName);
@@ -133,49 +138,76 @@ namespace BansheeEngine
 
 	void ScriptEditorWindow::onAssemblyRefreshStarted()
 	{
-		mEditorWidget->triggerOnDestroy();
+		if (!isDestroyed())
+			mEditorWidget->triggerOnDestroy();
 	}
 
 	MonoObject* ScriptEditorWindow::_createManagedInstance(bool construct)
 	{
-		mEditorWidget->createManagedInstance();
+		if (!isDestroyed())
+		{
+			mEditorWidget->createManagedInstance();
 
-		return mEditorWidget->getManagedInstance();
+			return mEditorWidget->getManagedInstance();
+		}
+		else
+			return nullptr;
 	}
 
 	bool ScriptEditorWindow::internal_hasFocus(ScriptEditorWindow* thisPtr)
 	{
-		return thisPtr->getEditorWidget()->hasFocus();
+		if (!thisPtr->isDestroyed())
+			return thisPtr->getEditorWidget()->hasFocus();
+		else
+			return false;
 	}
 
 	void ScriptEditorWindow::internal_screenToWindowPos(ScriptEditorWindow* thisPtr, Vector2I screenPos, Vector2I* windowPos)
 	{
-		*windowPos = thisPtr->getEditorWidget()->screenToWidgetPos(screenPos);
+		if (!thisPtr->isDestroyed())
+			*windowPos = thisPtr->getEditorWidget()->screenToWidgetPos(screenPos);
+		else
+			*windowPos = screenPos;
 	}
 
 	void ScriptEditorWindow::internal_windowToScreenPos(ScriptEditorWindow* thisPtr, Vector2I windowPos, Vector2I* screenPos)
 	{
-		*screenPos = thisPtr->getEditorWidget()->widgetToScreenPos(windowPos);
+		if (!thisPtr->isDestroyed())
+			*screenPos = thisPtr->getEditorWidget()->widgetToScreenPos(windowPos);
+		else
+			*screenPos = windowPos;
 	}
 
 	UINT32 ScriptEditorWindow::internal_getWidth(ScriptEditorWindow* thisPtr)
 	{
-		return thisPtr->mEditorWidget->getWidth();
+		if (!thisPtr->isDestroyed())
+			return thisPtr->mEditorWidget->getWidth();
+		else
+			return 0;
 	}
 
 	UINT32 ScriptEditorWindow::internal_getHeight(ScriptEditorWindow* thisPtr)
 	{
-		return thisPtr->mEditorWidget->getHeight();
+		if (!thisPtr->isDestroyed())
+			return thisPtr->mEditorWidget->getHeight();
+		else
+			return 0;
 	}
 
 	void ScriptEditorWindow::onWidgetResized(UINT32 width, UINT32 height)
 	{
+		if (isDestroyed() || mManagedInstance == nullptr)
+			return;
+
 		void* params[2] = { &width, &height };
 		onResizedMethod->invokeVirtual(mManagedInstance, params);
 	}
 
 	void ScriptEditorWindow::onFocusChanged(bool inFocus)
 	{
+		if (isDestroyed() || mManagedInstance == nullptr)
+			return;
+
 		void* params[1] = { &inFocus};
 		onFocusChangedMethod->invokeVirtual(mManagedInstance, params);
 	}
@@ -220,6 +252,7 @@ namespace BansheeEngine
 		ScriptEditorWindow::registerScriptEditorWindow(nativeInstance);
 
 		mono_runtime_object_init(editorWidget->getManagedInstance()); // Construct it
+		editorWidget->setScriptOwner(nativeInstance);
 		editorWidget->triggerOnInitialize();
 
 		return editorWidget;
@@ -255,13 +288,17 @@ namespace BansheeEngine
 
 	ScriptEditorWidget::ScriptEditorWidget(const String& ns, const String& type, EditorWidgetContainer& parentContainer)
 		:EditorWidgetBase(HString(toWString(type)), ns + "." + type, parentContainer), mNamespace(ns), mTypename(type),
-		mUpdateThunk(nullptr), mManagedInstance(nullptr), mOnInitializeThunk(nullptr), mOnDestroyThunk(nullptr), mContentsPanel(nullptr)
+		mUpdateThunk(nullptr), mManagedInstance(nullptr), mOnInitializeThunk(nullptr), mOnDestroyThunk(nullptr), 
+		mContentsPanel(nullptr), mScriptOwner(nullptr)
 	{
 		createManagedInstance();
 	}
 
 	ScriptEditorWidget::~ScriptEditorWidget()
 	{
+		mScriptOwner->mIsDestroyed = true;
+		mScriptOwner->mEditorWidget = nullptr;
+
 		mContentsPanel->destroy();
 		mContentsPanel = nullptr;
 

+ 252 - 0
SBansheeEditor/Source/BsScriptOSDropTarget.cpp

@@ -0,0 +1,252 @@
+#include "BsScriptOSDropTarget.h"
+#include "BsScriptMeta.h"
+#include "BsMonoField.h"
+#include "BsMonoClass.h"
+#include "BsMonoManager.h"
+#include "BsMonoMethod.h"
+#include "BsMonoUtil.h"
+#include "BsMonoArray.h"
+#include "BsRTTIType.h"
+#include "BsPlatform.h"
+#include "BsEditorWidget.h"
+#include "BsEditorWindowBase.h"
+#include "BsEditorWidgetContainer.h"
+#include "BsScriptEditorWindow.h"
+
+using namespace std::placeholders;
+
+namespace BansheeEngine
+{
+	ScriptOSDropTarget::OnEnterThunkDef ScriptOSDropTarget::onEnterThunk;
+	ScriptOSDropTarget::OnMoveDef ScriptOSDropTarget::onMoveThunk;
+	ScriptOSDropTarget::OnLeaveDef ScriptOSDropTarget::onLeaveThunk;
+	ScriptOSDropTarget::OnDropThunkDef ScriptOSDropTarget::onDropThunk;
+
+	ScriptOSDropTarget::ScriptOSDropTarget(MonoObject* instance, ScriptEditorWindow* parent)
+		:ScriptObject(instance), mDropTarget(nullptr), mIsDestroyed(false), mParent(parent)
+	{
+		EditorWidgetBase* parentWidget = getParentWidget();
+
+		if (parentWidget != nullptr)
+		{
+			mWidgetParentChangedConn = parentWidget->onParentChanged.connect(std::bind(&ScriptOSDropTarget::widgetParentChanged, this, _1));
+			mWidgetResizedConn = parentWidget->onResized.connect(std::bind(&ScriptOSDropTarget::widgetResized, this, _1, _2));
+
+			EditorWindowBase* parentWindow = parentWidget->getParentWindow();
+
+			if (parentWindow != nullptr)
+				setDropTarget(parentWindow->getRenderWindow(), 0, 0, 0, 0);
+		}
+	}
+
+	ScriptOSDropTarget::~ScriptOSDropTarget()
+	{
+		if (!mIsDestroyed)
+			destroy();
+	}
+
+	void ScriptOSDropTarget::initRuntimeData()
+	{
+		metaData.scriptClass->addInternalCall("Internal_CreateInstance", &ScriptOSDropTarget::internal_CreateInstance);
+		metaData.scriptClass->addInternalCall("Internal_Destroy", &ScriptOSDropTarget::internal_Destroy);
+		metaData.scriptClass->addInternalCall("Internal_SetBounds", &ScriptOSDropTarget::internal_SetBounds);
+		metaData.scriptClass->addInternalCall("Internal_GetFilePaths", &ScriptOSDropTarget::internal_GetFilePaths);
+
+		onEnterThunk = (OnEnterThunkDef)metaData.scriptClass->getMethod("InternalDoOnEnter", 2)->getThunk();
+		onMoveThunk = (OnMoveDef)metaData.scriptClass->getMethod("InternalDoOnDrag", 2)->getThunk();
+		onLeaveThunk = (OnLeaveDef)metaData.scriptClass->getMethod("InternalDoOnLeave")->getThunk();
+		onDropThunk = (OnDropThunkDef)metaData.scriptClass->getMethod("InternalDoOnDrop", 2)->getThunk();
+	}
+
+	void ScriptOSDropTarget::internal_CreateInstance(MonoObject* instance, ScriptEditorWindow* editorWindow)
+	{
+		ScriptOSDropTarget* nativeInstance = new (bs_alloc<ScriptOSDropTarget>()) ScriptOSDropTarget(instance, editorWindow);
+	}
+
+	void ScriptOSDropTarget::internal_Destroy(ScriptOSDropTarget* nativeInstance)
+	{
+		if (nativeInstance->mIsDestroyed)
+			return;
+
+		nativeInstance->destroy();
+	}
+
+	void ScriptOSDropTarget::internal_SetBounds(ScriptOSDropTarget* nativeInstance, Rect2I bounds)
+	{
+		if (nativeInstance->mIsDestroyed)
+			return;
+
+		nativeInstance->setBounds(bounds);
+	}
+
+	MonoArray* ScriptOSDropTarget::internal_GetFilePaths(ScriptOSDropTarget* nativeInstance)
+	{
+		OSDropTarget* dropTarget = nativeInstance->mDropTarget;
+
+		if (nativeInstance->mIsDestroyed || dropTarget == nullptr || dropTarget->getDropType() != OSDropType::FileList)
+			return ScriptArray::create<String>(0).getInternal();
+
+		Vector<WString> fileList = dropTarget->getFileList();
+		ScriptArray output = ScriptArray::create<WString>((UINT32)fileList.size());
+
+		UINT32 idx = 0;
+		for (auto& path : fileList)
+		{
+			output.set(idx, path);
+			idx++;
+		}
+
+		return output.getInternal();
+	}
+
+	void ScriptOSDropTarget::destroy()
+	{
+		mIsDestroyed = true;
+
+		mWidgetParentChangedConn.disconnect();
+		mWidgetResizedConn.disconnect();
+
+		setDropTarget(nullptr, 0, 0, 0, 0);
+	}
+
+	EditorWidgetBase* ScriptOSDropTarget::getParentWidget() const
+	{
+		EditorWidgetBase* parentWidget = nullptr;
+
+		if (mParent != nullptr && mParent->isDestroyed())
+			parentWidget = mParent->getEditorWidget();
+
+		return parentWidget;
+	}
+
+	Rect2I ScriptOSDropTarget::getDropTargetArea() const
+	{
+		Rect2I dropTargetArea = mArea;
+		dropTargetArea.clip(mParentArea);
+
+		EditorWidgetBase* parentWidget = getParentWidget();
+		if (parentWidget != nullptr)
+		{
+			dropTargetArea.x += parentWidget->getX();
+			dropTargetArea.y += parentWidget->getY();
+		}
+
+		return dropTargetArea;
+	}
+
+	void ScriptOSDropTarget::setDropTarget(const RenderWindowPtr& parentWindow, INT32 x, INT32 y, UINT32 width, UINT32 height)
+	{
+		if (mDropTarget != nullptr)
+		{
+			Platform::destroyDropTarget(*mDropTarget);
+
+			mDropTargetEnterConn.disconnect();
+			mDropTargetLeaveConn.disconnect();
+			mDropTargetMoveConn.disconnect();
+			mDropTargetDroppedConn.disconnect();
+		}
+
+		if (parentWindow != nullptr)
+		{
+			mDropTarget = &Platform::createDropTarget(parentWindow.get(), x, y, width, height);
+
+			mDropTargetEnterConn = mDropTarget->onEnter.connect(std::bind(&ScriptOSDropTarget::dropTargetDragMove, this, _1, _2));
+			mDropTargetMoveConn = mDropTarget->onDragOver.connect(std::bind(&ScriptOSDropTarget::dropTargetDragMove, this, _1, _2));
+			mDropTargetLeaveConn = mDropTarget->onLeave.connect(std::bind(&ScriptOSDropTarget::dropTargetDragLeave, this));
+			mDropTargetDroppedConn = mDropTarget->onDrop.connect(std::bind(&ScriptOSDropTarget::dropTargetDragDropped, this, _1, _2));
+		}
+		else
+			mDropTarget = nullptr;
+	}
+
+	void ScriptOSDropTarget::setBounds(const Rect2I& bounds)
+	{
+		mArea = bounds;
+		Rect2I dropTargetArea = getDropTargetArea();
+
+		if (mDropTarget != nullptr)
+			mDropTarget->setArea(dropTargetArea.x, dropTargetArea.y, dropTargetArea.width, dropTargetArea.height);
+	}
+
+	void ScriptOSDropTarget::dropTargetDragEnter(ScriptOSDropTarget* thisPtr, INT32 x, INT32 y)
+	{
+		if (thisPtr->mIsDestroyed)
+			return;
+
+		EditorWidgetBase* parentWidget = thisPtr->getParentWidget();
+		if (parentWidget == nullptr)
+			return;
+
+		MonoException* exception = nullptr;
+		onEnterThunk(thisPtr->getManagedInstance(), x - parentWidget->getX(), y - parentWidget->getY(), &exception);
+
+		MonoUtil::throwIfException(exception);
+	}
+
+	void ScriptOSDropTarget::dropTargetDragMove(ScriptOSDropTarget* thisPtr, INT32 x, INT32 y)
+	{
+		if (thisPtr->mIsDestroyed)
+			return;
+
+		EditorWidgetBase* parentWidget = thisPtr->getParentWidget();
+		if (parentWidget == nullptr)
+			return;
+
+		MonoException* exception = nullptr;
+		onMoveThunk(thisPtr->getManagedInstance(), x - parentWidget->getX(), y - parentWidget->getY(), &exception);
+
+		MonoUtil::throwIfException(exception);
+	}
+
+	void ScriptOSDropTarget::dropTargetDragLeave(ScriptOSDropTarget* thisPtr)
+	{
+		if (thisPtr->mIsDestroyed)
+			return;
+
+		MonoException* exception = nullptr;
+		onLeaveThunk(thisPtr->getManagedInstance(), &exception);
+
+		MonoUtil::throwIfException(exception);
+	}
+
+	void ScriptOSDropTarget::dropTargetDragDropped(ScriptOSDropTarget* thisPtr, INT32 x, INT32 y)
+	{
+		if (thisPtr->mIsDestroyed)
+			return;
+
+		EditorWidgetBase* parentWidget = thisPtr->getParentWidget();
+		if (parentWidget == nullptr)
+			return;
+
+		MonoException* exception = nullptr;
+		onDropThunk(thisPtr->getManagedInstance(), x - parentWidget->getX(), y - parentWidget->getY(), &exception);
+
+		MonoUtil::throwIfException(exception);
+	}
+
+	void ScriptOSDropTarget::widgetParentChanged(EditorWidgetContainer* parent)
+	{
+		RenderWindowPtr parentRenderWindow;
+		if (parent != nullptr)
+		{
+			EditorWindowBase* parentWindow = parent->getParentWindow();
+
+			if (parentWindow != nullptr)
+				parentRenderWindow = parentWindow->getRenderWindow();
+		}
+
+		if (parentRenderWindow == nullptr)
+			mParentArea = Rect2I();
+		
+		Rect2I dropTargetArea = getDropTargetArea();
+		setDropTarget(parentRenderWindow, dropTargetArea.x, dropTargetArea.y, dropTargetArea.width, dropTargetArea.height);
+	}
+
+	void ScriptOSDropTarget::widgetResized(UINT32 width, UINT32 height)
+	{
+		mParentArea.width = width;
+		mParentArea.height = height;
+
+		setBounds(mArea);
+	}
+}

+ 4 - 1
SBansheeEditor/Source/BsScriptSceneViewHandler.cpp

@@ -34,7 +34,10 @@ namespace BansheeEngine
 
 	void ScriptSceneViewHandler::internal_Create(MonoObject* managedInstance, ScriptEditorWindow* parentWindow, ScriptCameraHandler* camera)
 	{
-		EditorWidgetBase* widget = parentWindow->getEditorWidget();
+		EditorWidgetBase* widget = nullptr;
+		
+		if (parentWindow != nullptr && !parentWindow->isDestroyed())
+			widget = parentWindow->getEditorWidget();
 
 		new (bs_alloc<ScriptSceneViewHandler>()) ScriptSceneViewHandler(managedInstance, widget, camera->getInternal());
 	}

+ 15 - 7
TODO.txt

@@ -25,15 +25,16 @@ GUIResourceField doesn't distinguish between tex2d, tex3d and texcube.
 ----------------------------------------------------------------------
 Project window
 
-Right-clicking on an element to open a context menu doesn't seem to properly select the element first
- - This might only happen if the right click happens while another context menu is already open
+OSDropTarget works in window coordinates but EditorWindow uses editor window coordiantes for intializing it, and assumes it also returns editor window coordinates
+
+Test:
+ - Drag and drop internal
+ - Drag and drop from outside
+ - Scroll when dragging
 
 Simple tasks:
- - Hook up windows drag and drop support
- - Hook up drag and drop internal to project window
- - Hook up drag and drop that interacts with scene and hierarchy windows
- - Ensure dragging near scroll area border scrolls the area
- - Add search bar
+ - Hook up scene view drag and drop instantiation
+ - Hook up search
  - Add directory bar
 
 Test:
@@ -44,6 +45,13 @@ Test:
 Later:
  - Shift and ctrl-click to select multiple entries
  - Drag to select multiple entries?
+ - Improved navigation:
+   - Enter to go into folder, backspace to go up
+   - Arrow keys to move, shift to select while moving
+    - While moving like this I also need to automatically scroll to the selected element
+ - Hook up ping effect so it triggers when I select a resource or sceneobject
+  - Add ping to SceneTreeView
+ - Check if drag and drop cursor changes properly when over valid/invalid objects (might not be needed)
 
 ----------------------------------------------------------------------
 Resources