Просмотр исходного кода

More work on the Project Window
Context menus no longer register global shortcuts with the shortcut manager
Scroll area optimal size is now properly calculated
Add global "onFocusChanged" event for GUI elements

Marko Pintera 10 лет назад
Родитель
Сommit
adee486455

+ 4 - 0
BansheeEditor/Include/BsGUIMenuBar.h

@@ -40,6 +40,7 @@ namespace BansheeEngine
 		GUIButton* mCloseBtn;
 		GUIButton* mCloseBtn;
 
 
 		Vector<GUIMenuBarData> mChildMenus;
 		Vector<GUIMenuBarData> mChildMenus;
+		UnorderedMap<WString, ShortcutKey> mEntryShortcuts;
 
 
 		GUIButton* mSubMenuButton;
 		GUIButton* mSubMenuButton;
 		bool mSubMenuOpen;
 		bool mSubMenuOpen;
@@ -57,6 +58,9 @@ namespace BansheeEngine
 		 */
 		 */
 		bool stripPath(WString& path, WString& pathRoot) const;
 		bool stripPath(WString& path, WString& pathRoot) const;
 
 
+		void registerShortcut(const WString& path, const ShortcutKey& shortcut, std::function<void()> callback);
+		void unregisterShortcut(const WString& path);
+
 		void openSubMenu(const WString& name);
 		void openSubMenu(const WString& name);
 		void closeSubMenu();
 		void closeSubMenu();
 
 

+ 27 - 0
BansheeEditor/Source/BsGUIMenuBar.cpp

@@ -12,6 +12,7 @@
 #include "BsSceneObject.h"
 #include "BsSceneObject.h"
 #include "BsPlatform.h"
 #include "BsPlatform.h"
 #include "BsCoreThread.h"
 #include "BsCoreThread.h"
+#include <BsShortcutManager.h>
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
@@ -106,6 +107,9 @@ namespace BansheeEngine
 			refreshNonClientAreas();
 			refreshNonClientAreas();
 		}
 		}
 
 
+		if (shortcut.isValid() && callback != nullptr)
+			registerShortcut(path, shortcut, callback);
+
 		return subMenu->menu->addMenuItem(strippedPath, callback, priority, shortcut);
 		return subMenu->menu->addMenuItem(strippedPath, callback, priority, shortcut);
 	}
 	}
 
 
@@ -219,6 +223,29 @@ namespace BansheeEngine
 		return nullptr;
 		return nullptr;
 	}
 	}
 
 
+	void GUIMenuBar::registerShortcut(const WString& path, const ShortcutKey& shortcut, std::function<void()> callback)
+	{
+		ShortcutManager::instance().addShortcut(shortcut, callback);
+
+		WString trimmedPath = path;
+		StringUtil::trim(trimmedPath, L"/\\", false, true);
+
+		mEntryShortcuts[trimmedPath] = shortcut;
+	}
+
+	void GUIMenuBar::unregisterShortcut(const WString& path)
+	{
+		WString trimmedPath = path;
+		StringUtil::trim(trimmedPath, L"/\\", false, true);
+
+		auto findIter = mEntryShortcuts.find(trimmedPath);
+		if (findIter != mEntryShortcuts.end())
+		{
+			ShortcutManager::instance().removeShortcut(findIter->second);
+			mEntryShortcuts.erase(findIter);
+		}
+	}
+
 	bool GUIMenuBar::stripPath(WString& path, WString& pathRoot) const
 	bool GUIMenuBar::stripPath(WString& path, WString& pathRoot) const
 	{
 	{
 		Vector<WString> pathElements = StringUtil::split(path, L"/");
 		Vector<WString> pathElements = StringUtil::split(path, L"/");

+ 1 - 1
BansheeEditor/Source/BsGUITreeView.cpp

@@ -428,7 +428,7 @@ namespace BansheeEngine
 			}
 			}
 		}
 		}
 
 
-		return false;
+		return GUIElementContainer::_commandEvent(ev);
 	}
 	}
 
 
 	bool GUITreeView::_virtualButtonEvent(const GUIVirtualButtonEvent& ev)
 	bool GUITreeView::_virtualButtonEvent(const GUIVirtualButtonEvent& ev)

+ 5 - 0
BansheeEngine/Include/BsGUIElement.h

@@ -326,6 +326,11 @@ namespace BansheeEngine
 		 */
 		 */
 		virtual Rect2I _getTextInputRect() const { return Rect2I(); }
 		virtual Rect2I _getTextInputRect() const { return Rect2I(); }
 
 
+		/**
+		 * @brief	Triggered when the element loses or gains focus.
+		 */
+		Event<void(bool)> onFocusChanged;
+
 	protected:
 	protected:
 		/**
 		/**
 		 * @brief	Called whenever render elements are dirty and need to be rebuilt.
 		 * @brief	Called whenever render elements are dirty and need to be rebuilt.

+ 5 - 5
BansheeEngine/Include/BsGUILayoutY.h

@@ -19,23 +19,23 @@ namespace BansheeEngine
 		/**
 		/**
 		 * @brief	Calculate optimal sizes of all child layout elements.
 		 * @brief	Calculate optimal sizes of all child layout elements.
 		 */
 		 */
-		void _updateOptimalLayoutSizes();
+		void _updateOptimalLayoutSizes() override;
 
 
 		/**
 		/**
 		 * @copydoc	GUIElementBase::_calculateLayoutSizeRange
 		 * @copydoc	GUIElementBase::_calculateLayoutSizeRange
 		 */
 		 */
-		virtual LayoutSizeRange _calculateLayoutSizeRange() const;
+		virtual LayoutSizeRange _calculateLayoutSizeRange() const override;
 
 
 		/**
 		/**
 		 * @copydoc	GUILayout::_getElementAreas
 		 * @copydoc	GUILayout::_getElementAreas
 		 */
 		 */
-		void _getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements, 
-			const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const;
+		void _getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
+			const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const override;
 
 
 		/**
 		/**
 		 * @copydoc	GUILayout::_calcActualSize
 		 * @copydoc	GUILayout::_calcActualSize
 		 */
 		 */
-		virtual Vector2I _calcActualSize(INT32 x, INT32 y, Rect2I* elementAreas, UINT32 numElements) const;
+		 virtual Vector2I _calcActualSize(INT32 x, INT32 y, Rect2I* elementAreas, UINT32 numElements) const override;
 
 
 		/**
 		/**
 		 * @brief	Creates a new vertical layout.
 		 * @brief	Creates a new vertical layout.

+ 10 - 0
BansheeEngine/Include/BsGUIScrollArea.h

@@ -161,6 +161,16 @@ namespace BansheeEngine
 		 * @copydoc GUIElementContainer::updateBounds
 		 * @copydoc GUIElementContainer::updateBounds
 		 */
 		 */
 		virtual void updateClippedBounds();
 		virtual void updateClippedBounds();
+
+		/**
+		 * @copydoc	GUIElementBase::_calculateLayoutSizeRange
+		 */
+		LayoutSizeRange _calculateLayoutSizeRange() const override;
+
+		/**
+		 * @copydoc GUIElementContainer::_getOptimalSize
+		 */
+		Vector2I _getOptimalSize() const override;
 	private:
 	private:
 		GUIScrollArea(ScrollBarType vertBarType, ScrollBarType horzBarType, 
 		GUIScrollArea(ScrollBarType vertBarType, ScrollBarType horzBarType, 
 			const String& scrollBarStyle, const String& scrollAreaStyle, const GUIDimensions& dimensions);
 			const String& scrollBarStyle, const String& scrollAreaStyle, const GUIDimensions& dimensions);

+ 4 - 2
BansheeEngine/Source/BsGUIDropDownContent.cpp

@@ -168,8 +168,10 @@ namespace BansheeEngine
 
 
 	bool GUIDropDownContent::_commandEvent(const GUICommandEvent& ev)
 	bool GUIDropDownContent::_commandEvent(const GUICommandEvent& ev)
 	{
 	{
+		bool baseReturn = GUIElementContainer::_commandEvent(ev);
+
 		if (!mKeyboardFocus)
 		if (!mKeyboardFocus)
-			return false;
+			return baseReturn;
 
 
 		UINT32 maxElemIdx = (UINT32)mDropDownData.entries.size();
 		UINT32 maxElemIdx = (UINT32)mDropDownData.entries.size();
 
 
@@ -211,7 +213,7 @@ namespace BansheeEngine
 			return true;
 			return true;
 		}
 		}
 
 
-		return false;
+		return baseReturn;
 	}
 	}
 
 
 	void GUIDropDownContent::setSelected(UINT32 idx)
 	void GUIDropDownContent::setSelected(UINT32 idx)

+ 5 - 0
BansheeEngine/Source/BsGUIElement.cpp

@@ -47,6 +47,11 @@ namespace BansheeEngine
 
 
 	bool GUIElement::_commandEvent(const GUICommandEvent& ev)
 	bool GUIElement::_commandEvent(const GUICommandEvent& ev)
 	{
 	{
+		if (ev.getType() == GUICommandEventType::FocusGained)
+			onFocusChanged(true);
+		else if (ev.getType() == GUICommandEventType::FocusLost)
+			onFocusChanged(false);
+
 		return false;
 		return false;
 	}
 	}
 
 

+ 3 - 1
BansheeEngine/Source/BsGUIInputBox.cpp

@@ -535,6 +535,8 @@ namespace BansheeEngine
 
 
 	bool GUIInputBox::_commandEvent(const GUICommandEvent& ev)
 	bool GUIInputBox::_commandEvent(const GUICommandEvent& ev)
 	{
 	{
+		bool baseReturn = GUIElement::_commandEvent(ev);
+
 		if(ev.getType() == GUICommandEventType::Redraw)
 		if(ev.getType() == GUICommandEventType::Redraw)
 		{
 		{
 			_markContentAsDirty();
 			_markContentAsDirty();
@@ -814,7 +816,7 @@ namespace BansheeEngine
 
 
 		}
 		}
 
 
-		return false;
+		return baseReturn;
 	}
 	}
 
 
 	bool GUIInputBox::_virtualButtonEvent(const GUIVirtualButtonEvent& ev)
 	bool GUIInputBox::_virtualButtonEvent(const GUIVirtualButtonEvent& ev)

+ 1 - 8
BansheeEngine/Source/BsGUIMenu.cpp

@@ -1,6 +1,5 @@
 #include "BsGUIMenu.h"
 #include "BsGUIMenu.h"
 #include "BsGUIDropDownMenu.h"
 #include "BsGUIDropDownMenu.h"
-#include "BsShortcutManager.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
@@ -12,8 +11,7 @@ namespace BansheeEngine
 	GUIMenuItem::GUIMenuItem(GUIMenuItem* parent, const WString& name, std::function<void()> callback, INT32 priority, const ShortcutKey& key)
 	GUIMenuItem::GUIMenuItem(GUIMenuItem* parent, const WString& name, std::function<void()> callback, INT32 priority, const ShortcutKey& key)
 		:mParent(parent), mName(name), mCallback(callback), mIsSeparator(false), mPriority(priority), mShortcut(key)
 		:mParent(parent), mName(name), mCallback(callback), mIsSeparator(false), mPriority(priority), mShortcut(key)
 	{
 	{
-		if (mCallback != nullptr && mShortcut.isValid())
-			ShortcutManager::instance().addShortcut(mShortcut, mCallback);
+
 	}
 	}
 
 
 	GUIMenuItem::GUIMenuItem(GUIMenuItem* parent, INT32 priority)
 	GUIMenuItem::GUIMenuItem(GUIMenuItem* parent, INT32 priority)
@@ -24,9 +22,6 @@ namespace BansheeEngine
 
 
 	GUIMenuItem::~GUIMenuItem()
 	GUIMenuItem::~GUIMenuItem()
 	{
 	{
-		if (mCallback != nullptr && mShortcut.isValid())
-			ShortcutManager::instance().removeShortcut(mShortcut);
-
 		for(auto& child : mChildren)
 		for(auto& child : mChildren)
 			bs_delete<PoolAlloc>(child);
 			bs_delete<PoolAlloc>(child);
 	}
 	}
@@ -105,8 +100,6 @@ namespace BansheeEngine
 					existingItem = bs_new<GUIMenuItem, PoolAlloc>(curSubMenu, pathElem, callback, priority, key);
 					existingItem = bs_new<GUIMenuItem, PoolAlloc>(curSubMenu, pathElem, callback, priority, key);
 				else
 				else
 				{
 				{
-					const WString& nextPathElem = *(pathElements.begin() + i);
-
 					existingItem = bs_alloc<GUIMenuItem, PoolAlloc>();
 					existingItem = bs_alloc<GUIMenuItem, PoolAlloc>();
 					existingItem = new (existingItem)GUIMenuItem(curSubMenu, pathElem, nullptr, 0, ShortcutKey::NONE);
 					existingItem = new (existingItem)GUIMenuItem(curSubMenu, pathElem, nullptr, 0, ShortcutKey::NONE);
 				}
 				}

+ 17 - 0
BansheeEngine/Source/BsGUIScrollArea.cpp

@@ -48,6 +48,23 @@ namespace BansheeEngine
 		mClippedBounds.clip(mLayoutData.clipRect);
 		mClippedBounds.clip(mLayoutData.clipRect);
 	}
 	}
 
 
+	Vector2I GUIScrollArea::_getOptimalSize() const
+	{
+		return mContentLayout->_getOptimalSize();
+	}
+
+	LayoutSizeRange GUIScrollArea::_calculateLayoutSizeRange() const
+	{
+		if (mIsDisabled)
+			return LayoutSizeRange();
+
+		// I'm ignoring scroll bars here since if the content layout fits
+		// then they're not needed and the range is valid. And if it doesn't
+		// fit the area will get clipped anyway and including the scroll bars
+		// won't change the size much, but it would complicate this method significantly.
+		return mContentLayout->_calculateLayoutSizeRange();
+	}
+
 	void GUIScrollArea::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
 	void GUIScrollArea::_getElementAreas(const Rect2I& layoutArea, Rect2I* elementAreas, UINT32 numElements,
 		const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const
 		const Vector<LayoutSizeRange>& sizeRanges, const LayoutSizeRange& mySizeRange) const
 	{
 	{

+ 179 - 26
MBansheeEditor/ProjectWindow.cs

@@ -1,16 +1,17 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using BansheeEngine;
 using BansheeEngine;
 
 
 namespace BansheeEditor
 namespace BansheeEditor
 {
 {
-    internal sealed class ProjectWindow : EditorWindow
+    internal enum ProjectViewType
     {
     {
-        private enum ViewType
-        {
-            Grid64, Grid48, Grid32, List16
-        }
+        Grid64, Grid48, Grid32, List16
+    }
 
 
+    internal sealed class ProjectWindow : EditorWindow
+    {
         private struct EntryGUI
         private struct EntryGUI
         {
         {
             public EntryGUI(GUITexture icon, GUILabel label)
             public EntryGUI(GUITexture icon, GUILabel label)
@@ -32,7 +33,7 @@ namespace BansheeEditor
         private bool hasContentFocus = false;
         private bool hasContentFocus = false;
         private bool HasContentFocus { get { return HasFocus && hasContentFocus; } } // TODO - This is dummy and never set
         private bool HasContentFocus { get { return HasFocus && hasContentFocus; } } // TODO - This is dummy and never set
 
 
-        private ViewType viewType = ViewType.Grid32;
+        private ProjectViewType viewType = ProjectViewType.Grid32;
 
 
         private string currentDirectory = "";
         private string currentDirectory = "";
         private List<string> selectionPaths = new List<string>();
         private List<string> selectionPaths = new List<string>();
@@ -41,12 +42,22 @@ namespace BansheeEditor
         private GUIScrollArea contentScrollArea;
         private GUIScrollArea contentScrollArea;
         private GUIPanel scrollAreaPanel;
         private GUIPanel scrollAreaPanel;
 
 
+        private GUIButton optionsButton;
+
+        private ContextMenu entryContextMenu;
+
         private Dictionary<string, EntryGUI> pathToGUIEntry = new Dictionary<string, EntryGUI>();
         private Dictionary<string, EntryGUI> pathToGUIEntry = new Dictionary<string, EntryGUI>();
 
 
         // Cut/Copy/Paste
         // Cut/Copy/Paste
         private List<string> copyPaths = new List<string>();
         private List<string> copyPaths = new List<string>();
         private List<string> cutPaths = new List<string>();
         private List<string> cutPaths = new List<string>();
 
 
+        internal ProjectViewType ViewType
+        {
+            get { return viewType; }
+            set { viewType = value; Refresh(); }
+        }
+
         [MenuItem("Windows/Project", ButtonModifier.Ctrl, ButtonCode.P)]
         [MenuItem("Windows/Project", ButtonModifier.Ctrl, ButtonCode.P)]
         private static void OpenProjectWindow()
         private static void OpenProjectWindow()
         {
         {
@@ -58,11 +69,32 @@ namespace BansheeEditor
             ProjectLibrary.OnEntryAdded += OnEntryChanged;
             ProjectLibrary.OnEntryAdded += OnEntryChanged;
             ProjectLibrary.OnEntryRemoved += OnEntryChanged;
             ProjectLibrary.OnEntryRemoved += OnEntryChanged;
 
 
+            GUILayoutY contentLayout = GUI.AddLayoutY();
+
+            GUILayoutX searchBarLayout = contentLayout.AddLayoutX();
+            GUITextField searchField = new GUITextField();
+            searchField.OnChanged += OnSearchChanged;
+            GUIButton clearSearchBtn = new GUIButton("C");
+            clearSearchBtn.OnClick += ClearSearch;
+            clearSearchBtn.SetWidth(40);
+            optionsButton = new GUIButton("O");
+            optionsButton.OnClick += OpenOptionsWindow;
+            optionsButton.SetWidth(40);
+            searchBarLayout.AddElement(searchField);
+            searchBarLayout.AddElement(clearSearchBtn);
+            searchBarLayout.AddElement(optionsButton);
+
             // TODO - Add search bar + options button with drop-down
             // TODO - Add search bar + options button with drop-down
             // TODO - Add directory bar + home button
             // TODO - Add directory bar + home button
 
 
             contentScrollArea = new GUIScrollArea(GUIOption.FlexibleWidth(), GUIOption.FlexibleHeight());
             contentScrollArea = new GUIScrollArea(GUIOption.FlexibleWidth(), GUIOption.FlexibleHeight());
-            GUI.AddElement(contentScrollArea);
+            contentLayout.AddElement(contentScrollArea);
+
+            entryContextMenu = new ContextMenu();
+            entryContextMenu.AddItem("Cut", CutSelection, new ShortcutKey(ButtonModifier.Ctrl, ButtonCode.X));
+            entryContextMenu.AddItem("Copy", CopySelection, new ShortcutKey(ButtonModifier.Ctrl, ButtonCode.C));
+            entryContextMenu.AddItem("Duplicate", DuplicateSelection, new ShortcutKey(ButtonModifier.Ctrl, ButtonCode.D));
+            entryContextMenu.AddItem("Paste", PasteToSelection, new ShortcutKey(ButtonModifier.Ctrl, ButtonCode.V));
 
 
             Reset();
             Reset();
         }
         }
@@ -153,12 +185,6 @@ namespace BansheeEditor
             }
             }
         }
         }
 
 
-        private void SetView(ViewType type)
-        {
-            viewType = type;
-            Refresh();
-        }
-
         private void EditorUpdate()
         private void EditorUpdate()
         {
         {
             if (HasContentFocus)
             if (HasContentFocus)
@@ -167,24 +193,20 @@ namespace BansheeEditor
                 {
                 {
                     if (Input.IsButtonUp(ButtonCode.C))
                     if (Input.IsButtonUp(ButtonCode.C))
                     {
                     {
-                        if(selectionPaths.Count > 0)
-                            Copy(selectionPaths);
+                        CopySelection();
                     }
                     }
                     else if (Input.IsButtonUp(ButtonCode.X))
                     else if (Input.IsButtonUp(ButtonCode.X))
                     {
                     {
-                        if (selectionPaths.Count > 0)
-                            Cut(selectionPaths);
+                        CutSelection();
                     }
                     }
                     else if (Input.IsButtonUp(ButtonCode.D))
                     else if (Input.IsButtonUp(ButtonCode.D))
                     {
                     {
-                        if (selectionPaths.Count > 0)
-                            Duplicate(selectionPaths);
+                        DuplicateSelection();
                     }
                     }
                     else if (Input.IsButtonUp(ButtonCode.V))
                     else if (Input.IsButtonUp(ButtonCode.V))
                     {
                     {
-                        Paste(currentDirectory);
+                        PasteToSelection();
                     }
                     }
-                    
                 }
                 }
             }
             }
 
 
@@ -275,7 +297,7 @@ namespace BansheeEditor
             if (childEntries.Length == 0)
             if (childEntries.Length == 0)
                 return;
                 return;
 
 
-            if (viewType == ViewType.List16)
+            if (viewType == ProjectViewType.List16)
             {
             {
                 int tileSize = 16;
                 int tileSize = 16;
 
 
@@ -296,9 +318,9 @@ namespace BansheeEditor
                 int tileSize = 64;
                 int tileSize = 64;
                 switch (viewType)
                 switch (viewType)
                 {
                 {
-                    case ViewType.Grid64: tileSize = 64; break;
-                    case ViewType.Grid48: tileSize = 48; break;
-                    case ViewType.Grid32: tileSize = 32; break;
+                    case ProjectViewType.Grid64: tileSize = 64; break;
+                    case ProjectViewType.Grid48: tileSize = 48; break;
+                    case ProjectViewType.Grid32: tileSize = 32; break;
                 }
                 }
 
 
                 GUILayoutX rowLayout = contentLayout.AddLayoutX();
                 GUILayoutX rowLayout = contentLayout.AddLayoutX();
@@ -338,6 +360,7 @@ namespace BansheeEditor
             GUIButton catchAll = new GUIButton("", EditorStyles.Blank);
             GUIButton catchAll = new GUIButton("", EditorStyles.Blank);
             catchAll.Bounds = contentBounds;
             catchAll.Bounds = contentBounds;
             catchAll.OnClick += OnCatchAllClicked;
             catchAll.OnClick += OnCatchAllClicked;
+            catchAll.SetContextMenu(entryContextMenu);
 
 
             contentUnderlayPanel.AddElement(catchAll);
             contentUnderlayPanel.AddElement(catchAll);
 
 
@@ -384,6 +407,7 @@ namespace BansheeEditor
             overlayBtn.Bounds = entryButtonBounds;
             overlayBtn.Bounds = entryButtonBounds;
             overlayBtn.OnClick += () => OnEntryClicked(entry.Path);
             overlayBtn.OnClick += () => OnEntryClicked(entry.Path);
             overlayBtn.OnDoubleClick += () => OnEntryDoubleClicked(entry.Path);
             overlayBtn.OnDoubleClick += () => OnEntryDoubleClicked(entry.Path);
+            overlayBtn.SetContextMenu(entryContextMenu);
 
 
             overlayPanel.AddElement(overlayBtn);
             overlayPanel.AddElement(overlayBtn);
 
 
@@ -435,6 +459,62 @@ namespace BansheeEditor
             Selection.resourcePaths = new string[] { };
             Selection.resourcePaths = new string[] { };
         }
         }
 
 
+        private void CutSelection()
+        {
+            if (selectionPaths.Count > 0)
+                Cut(selectionPaths);
+        }
+
+        private void CopySelection()
+        {
+            if (selectionPaths.Count > 0)
+                Copy(selectionPaths);
+        }
+
+        private void DuplicateSelection()
+        {
+            if (selectionPaths.Count > 0)
+                Duplicate(selectionPaths);
+        }
+
+        private void PasteToSelection()
+        {
+            DirectoryEntry selectedDirectory = null;
+            if (selectionPaths.Count == 1)
+            {
+                LibraryEntry entry = ProjectLibrary.GetEntry(selectionPaths[0]);
+                if (entry != null && entry.Type == LibraryEntryType.Directory)
+                    selectedDirectory = (DirectoryEntry) entry;
+            }
+
+            if(selectedDirectory != null)
+                Paste(selectedDirectory.Path);
+            else
+                Paste(currentDirectory);
+        }
+
+        private void OnSearchChanged(string newValue)
+        {
+            // TODO
+        }
+
+        private void ClearSearch()
+        {
+            // TODO
+        }
+
+        private void OpenOptionsWindow()
+        {
+            Vector2I openPosition;
+            Rect2I buttonBounds = optionsButton.Bounds;
+
+            openPosition.x = buttonBounds.x + buttonBounds.width / 2;
+            openPosition.y = buttonBounds.y + buttonBounds.height / 2;
+
+            ProjectDropDown dropDown = DropDownWindow.Open<ProjectDropDown>(this, openPosition);
+            dropDown.SetParent(this);
+        }
+
         private void Reset()
         private void Reset()
         {
         {
             currentDirectory = ProjectLibrary.Root.Path;
             currentDirectory = ProjectLibrary.Root.Path;
@@ -448,4 +528,77 @@ namespace BansheeEditor
             Refresh();
             Refresh();
         }
         }
     }
     }
+
+    internal class ProjectDropDown : DropDownWindow
+    {
+        private ProjectWindow parent;
+
+        public ProjectDropDown()
+            :base(100, 50)
+        { }
+
+        internal void SetParent(ProjectWindow parent)
+        {
+            this.parent = parent;
+
+            GUIToggleGroup group = new GUIToggleGroup();
+
+            GUIToggle list16 = new GUIToggle("16", group);
+            GUIToggle grid32 = new GUIToggle("32", group);
+            GUIToggle grid48 = new GUIToggle("32", group);
+            GUIToggle grid64 = new GUIToggle("64", group);
+
+            ProjectViewType activeType = parent.ViewType;
+            switch (activeType)
+            {
+                case ProjectViewType.List16:
+                    list16.ToggleOn();
+                    break;
+                case ProjectViewType.Grid32:
+                    grid32.ToggleOn();
+                    break;
+                case ProjectViewType.Grid48:
+                    grid48.ToggleOn();
+                    break;
+                case ProjectViewType.Grid64:
+                    grid64.ToggleOn();
+                    break;
+            }
+
+            list16.OnToggled += (active) =>
+            {
+                if (active)
+                    ChangeViewType(ProjectViewType.List16);
+            };
+
+            grid32.OnToggled += (active) =>
+            {
+                if (active)
+                    ChangeViewType(ProjectViewType.Grid32);
+            };
+
+            grid48.OnToggled += (active) =>
+            {
+                if (active)
+                    ChangeViewType(ProjectViewType.Grid48);
+            };
+
+            grid64.OnToggled += (active) =>
+            {
+                if (active)
+                    ChangeViewType(ProjectViewType.Grid64);
+            };
+
+            GUILayoutX contentLayout = GUI.AddLayoutX();
+            contentLayout.AddElement(list16);
+            contentLayout.AddElement(grid32);
+            contentLayout.AddElement(grid48);
+            contentLayout.AddElement(grid64);
+        }
+
+        private void ChangeViewType(ProjectViewType viewType)
+        {
+            parent.ViewType = viewType;
+        }
+    }
 }
 }

+ 8 - 0
MBansheeEngine/GUI/GUIElement.cs

@@ -9,6 +9,8 @@ namespace BansheeEngine
         protected GUILayout parent;
         protected GUILayout parent;
         private bool isDestroyed;
         private bool isDestroyed;
 
 
+        public Action<bool> OnFocusChanged;
+
         public Rect2I Bounds
         public Rect2I Bounds
         {
         {
             get { return Internal_GetBounds(mCachedPtr); }
             get { return Internal_GetBounds(mCachedPtr); }
@@ -20,6 +22,12 @@ namespace BansheeEngine
             get { return Internal_GetVisualBounds(mCachedPtr); }
             get { return Internal_GetVisualBounds(mCachedPtr); }
         }
         }
 
 
+        private void InternalOnFocusChanged(bool focus)
+        {
+            if (OnFocusChanged != null)
+                OnFocusChanged(focus);
+        }
+
         internal virtual void SetParent(GUILayout layout)
         internal virtual void SetParent(GUILayout layout)
         {
         {
             if (parent != null)
             if (parent != null)

+ 6 - 0
SBansheeEngine/Include/BsScriptGUIElement.h

@@ -20,6 +20,8 @@ namespace BansheeEngine
 	protected:
 	protected:
 		void initialize(GUIElementBase* element);
 		void initialize(GUIElementBase* element);
 
 
+		static void onFocusChanged(MonoObject* instance, bool focus);
+
 		bool mIsDestroyed;
 		bool mIsDestroyed;
 		GUIElementBase* mElement;
 		GUIElementBase* mElement;
 	};
 	};
@@ -86,6 +88,10 @@ namespace BansheeEngine
 	public:
 	public:
 		SCRIPT_OBJ(ENGINE_ASSEMBLY, "BansheeEngine", "GUIElement")
 		SCRIPT_OBJ(ENGINE_ASSEMBLY, "BansheeEngine", "GUIElement")
 
 
+		typedef void(__stdcall *OnFocusChangedThunkDef) (MonoObject*, bool, MonoException**);
+
+		static OnFocusChangedThunkDef onFocusChangedThunk;
+
 	private:
 	private:
 		static void internal_destroy(ScriptGUIElementBaseTBase* nativeInstance);
 		static void internal_destroy(ScriptGUIElementBaseTBase* nativeInstance);
 		static void internal_setVisible(ScriptGUIElementBaseTBase* nativeInstance, bool visible);
 		static void internal_setVisible(ScriptGUIElementBaseTBase* nativeInstance, bool visible);

+ 4 - 4
SBansheeEngine/Source/BsScriptContextMenu.cpp

@@ -22,10 +22,10 @@ namespace BansheeEngine
 
 
 	void ScriptContextMenu::initRuntimeData()
 	void ScriptContextMenu::initRuntimeData()
 	{
 	{
-		metaData.scriptClass->addInternalCall("internal_CreateInstance", &ScriptContextMenu::internal_CreateInstance);
-		metaData.scriptClass->addInternalCall("internal_AddItem", &ScriptContextMenu::internal_AddItem);
-		metaData.scriptClass->addInternalCall("internal_AddSeparator", &ScriptContextMenu::internal_AddSeparator);
-		metaData.scriptClass->addInternalCall("internal_SetLocalizedName", &ScriptContextMenu::internal_SetLocalizedName);
+		metaData.scriptClass->addInternalCall("Internal_CreateInstance", &ScriptContextMenu::internal_CreateInstance);
+		metaData.scriptClass->addInternalCall("Internal_AddItem", &ScriptContextMenu::internal_AddItem);
+		metaData.scriptClass->addInternalCall("Internal_AddSeparator", &ScriptContextMenu::internal_AddSeparator);
+		metaData.scriptClass->addInternalCall("Internal_SetLocalizedName", &ScriptContextMenu::internal_SetLocalizedName);
 
 
 		onEntryTriggered = (OnEntryTriggeredThunkDef)metaData.scriptClass->getMethod("InternalDoOnEntryTriggered", 1)->getThunk();
 		onEntryTriggered = (OnEntryTriggeredThunkDef)metaData.scriptClass->getMethod("InternalDoOnEntryTriggered", 1)->getThunk();
 	}
 	}

+ 21 - 1
SBansheeEngine/Source/BsScriptGUIElement.cpp

@@ -8,7 +8,9 @@
 #include "BsGUIElement.h"
 #include "BsGUIElement.h"
 #include "BsScriptGUILayout.h"
 #include "BsScriptGUILayout.h"
 #include "BsScriptContextMenu.h"
 #include "BsScriptContextMenu.h"
-#include "BsGUILayout.h"
+#include "BsGUIElement.h"
+
+using namespace std::placeholders;
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
@@ -21,6 +23,20 @@ namespace BansheeEngine
 	void ScriptGUIElementBaseTBase::initialize(GUIElementBase* element)
 	void ScriptGUIElementBaseTBase::initialize(GUIElementBase* element)
 	{
 	{
 		mElement = element;
 		mElement = element;
+
+		if (mElement != nullptr && mElement->_getType() == GUIElementBase::Type::Element)
+		{
+			GUIElement* guiElem = static_cast<GUIElement*>(element);
+			guiElem->onFocusChanged.connect(std::bind(&ScriptGUIElementBaseTBase::onFocusChanged, mManagedInstance, _1));
+		}
+	}
+
+	void ScriptGUIElementBaseTBase::onFocusChanged(MonoObject* instance, bool focus)
+	{
+		MonoException* exception = nullptr;
+		ScriptGUIElement::onFocusChangedThunk(instance, focus, &exception);
+
+		MonoUtil::throwIfException(exception);
 	}
 	}
 
 
 	ScriptGUIElementTBase::ScriptGUIElementTBase(MonoObject* instance)
 	ScriptGUIElementTBase::ScriptGUIElementTBase(MonoObject* instance)
@@ -43,6 +59,8 @@ namespace BansheeEngine
 		}
 		}
 	}
 	}
 
 
+	ScriptGUIElement::OnFocusChangedThunkDef ScriptGUIElement::onFocusChangedThunk;
+
 	ScriptGUIElement::ScriptGUIElement(MonoObject* instance)
 	ScriptGUIElement::ScriptGUIElement(MonoObject* instance)
 		:ScriptObject(instance)
 		:ScriptObject(instance)
 	{
 	{
@@ -63,6 +81,8 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_SetFlexibleHeight", &ScriptGUIElement::internal_SetFlexibleHeight);
 		metaData.scriptClass->addInternalCall("Internal_SetFlexibleHeight", &ScriptGUIElement::internal_SetFlexibleHeight);
 		metaData.scriptClass->addInternalCall("Internal_ResetDimensions", &ScriptGUIElement::internal_ResetDimensions);
 		metaData.scriptClass->addInternalCall("Internal_ResetDimensions", &ScriptGUIElement::internal_ResetDimensions);
 		metaData.scriptClass->addInternalCall("Internal_SetContextMenu", &ScriptGUIElement::internal_SetContextMenu);
 		metaData.scriptClass->addInternalCall("Internal_SetContextMenu", &ScriptGUIElement::internal_SetContextMenu);
+
+		onFocusChangedThunk = (OnFocusChangedThunkDef)metaData.scriptClass->getMethod("InternalOnFocusChanged", 1)->getThunk();
 	}
 	}
 
 
 	void ScriptGUIElement::internal_destroy(ScriptGUIElementBaseTBase* nativeInstance)
 	void ScriptGUIElement::internal_destroy(ScriptGUIElementBaseTBase* nativeInstance)

+ 6 - 4
TODO.txt

@@ -29,18 +29,20 @@ FBXImporter:
  - It should use DefaultMeshData
  - It should use DefaultMeshData
  - It should use and apply MeshImportOptions
  - It should use and apply MeshImportOptions
 
 
-TODO:
- - Implement C# and Script mesh
+Test context menu
+Test drop down window
+
+ - Need to know when a focus was lost from the main area
+  - GUIElement::OnFocusChanged
+   - THis is only partially finished. I haven't hooked up the callback to GUIElement from ScriptGUIElement
 
 
 Simple tasks:
 Simple tasks:
- - Add C# context menu support for GUI elements
  - Hook up windows drag and drop support
  - Hook up windows drag and drop support
  - Hook up drag and drop internal to project window
  - Hook up drag and drop internal to project window
  - Hook up drag and drop that interacts with scene and hierarchy windows
  - Hook up drag and drop that interacts with scene and hierarchy windows
  - Ensure dragging near scroll area border scrolls the area
  - Ensure dragging near scroll area border scrolls the area
  - Add search bar
  - Add search bar
  - Add directory bar
  - Add directory bar
- - Add DropDownWindow type that contains size options
 
 
 Test:
 Test:
  - Basic look & navigation, selection/deselection
  - Basic look & navigation, selection/deselection