Browse Source

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 years ago
parent
commit
adee486455

+ 4 - 0
BansheeEditor/Include/BsGUIMenuBar.h

@@ -40,6 +40,7 @@ namespace BansheeEngine
 		GUIButton* mCloseBtn;
 
 		Vector<GUIMenuBarData> mChildMenus;
+		UnorderedMap<WString, ShortcutKey> mEntryShortcuts;
 
 		GUIButton* mSubMenuButton;
 		bool mSubMenuOpen;
@@ -57,6 +58,9 @@ namespace BansheeEngine
 		 */
 		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 closeSubMenu();
 

+ 27 - 0
BansheeEditor/Source/BsGUIMenuBar.cpp

@@ -12,6 +12,7 @@
 #include "BsSceneObject.h"
 #include "BsPlatform.h"
 #include "BsCoreThread.h"
+#include <BsShortcutManager.h>
 
 namespace BansheeEngine
 {
@@ -106,6 +107,9 @@ namespace BansheeEngine
 			refreshNonClientAreas();
 		}
 
+		if (shortcut.isValid() && callback != nullptr)
+			registerShortcut(path, shortcut, callback);
+
 		return subMenu->menu->addMenuItem(strippedPath, callback, priority, shortcut);
 	}
 
@@ -219,6 +223,29 @@ namespace BansheeEngine
 		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
 	{
 		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)

+ 5 - 0
BansheeEngine/Include/BsGUIElement.h

@@ -326,6 +326,11 @@ namespace BansheeEngine
 		 */
 		virtual Rect2I _getTextInputRect() const { return Rect2I(); }
 
+		/**
+		 * @brief	Triggered when the element loses or gains focus.
+		 */
+		Event<void(bool)> onFocusChanged;
+
 	protected:
 		/**
 		 * @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.
 		 */
-		void _updateOptimalLayoutSizes();
+		void _updateOptimalLayoutSizes() override;
 
 		/**
 		 * @copydoc	GUIElementBase::_calculateLayoutSizeRange
 		 */
-		virtual LayoutSizeRange _calculateLayoutSizeRange() const;
+		virtual LayoutSizeRange _calculateLayoutSizeRange() const override;
 
 		/**
 		 * @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
 		 */
-		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.

+ 10 - 0
BansheeEngine/Include/BsGUIScrollArea.h

@@ -161,6 +161,16 @@ namespace BansheeEngine
 		 * @copydoc GUIElementContainer::updateBounds
 		 */
 		virtual void updateClippedBounds();
+
+		/**
+		 * @copydoc	GUIElementBase::_calculateLayoutSizeRange
+		 */
+		LayoutSizeRange _calculateLayoutSizeRange() const override;
+
+		/**
+		 * @copydoc GUIElementContainer::_getOptimalSize
+		 */
+		Vector2I _getOptimalSize() const override;
 	private:
 		GUIScrollArea(ScrollBarType vertBarType, ScrollBarType horzBarType, 
 			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 baseReturn = GUIElementContainer::_commandEvent(ev);
+
 		if (!mKeyboardFocus)
-			return false;
+			return baseReturn;
 
 		UINT32 maxElemIdx = (UINT32)mDropDownData.entries.size();
 
@@ -211,7 +213,7 @@ namespace BansheeEngine
 			return true;
 		}
 
-		return false;
+		return baseReturn;
 	}
 
 	void GUIDropDownContent::setSelected(UINT32 idx)

+ 5 - 0
BansheeEngine/Source/BsGUIElement.cpp

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

+ 3 - 1
BansheeEngine/Source/BsGUIInputBox.cpp

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

+ 1 - 8
BansheeEngine/Source/BsGUIMenu.cpp

@@ -1,6 +1,5 @@
 #include "BsGUIMenu.h"
 #include "BsGUIDropDownMenu.h"
-#include "BsShortcutManager.h"
 
 namespace BansheeEngine
 {
@@ -12,8 +11,7 @@ namespace BansheeEngine
 	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)
 	{
-		if (mCallback != nullptr && mShortcut.isValid())
-			ShortcutManager::instance().addShortcut(mShortcut, mCallback);
+
 	}
 
 	GUIMenuItem::GUIMenuItem(GUIMenuItem* parent, INT32 priority)
@@ -24,9 +22,6 @@ namespace BansheeEngine
 
 	GUIMenuItem::~GUIMenuItem()
 	{
-		if (mCallback != nullptr && mShortcut.isValid())
-			ShortcutManager::instance().removeShortcut(mShortcut);
-
 		for(auto& child : mChildren)
 			bs_delete<PoolAlloc>(child);
 	}
@@ -105,8 +100,6 @@ namespace BansheeEngine
 					existingItem = bs_new<GUIMenuItem, PoolAlloc>(curSubMenu, pathElem, callback, priority, key);
 				else
 				{
-					const WString& nextPathElem = *(pathElements.begin() + i);
-
 					existingItem = bs_alloc<GUIMenuItem, PoolAlloc>();
 					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);
 	}
 
+	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,
 		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 BansheeEngine;
 
 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
         {
             public EntryGUI(GUITexture icon, GUILabel label)
@@ -32,7 +33,7 @@ namespace BansheeEditor
         private bool hasContentFocus = false;
         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 List<string> selectionPaths = new List<string>();
@@ -41,12 +42,22 @@ namespace BansheeEditor
         private GUIScrollArea contentScrollArea;
         private GUIPanel scrollAreaPanel;
 
+        private GUIButton optionsButton;
+
+        private ContextMenu entryContextMenu;
+
         private Dictionary<string, EntryGUI> pathToGUIEntry = new Dictionary<string, EntryGUI>();
 
         // Cut/Copy/Paste
         private List<string> copyPaths = 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)]
         private static void OpenProjectWindow()
         {
@@ -58,11 +69,32 @@ namespace BansheeEditor
             ProjectLibrary.OnEntryAdded += 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 directory bar + home button
 
             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();
         }
@@ -153,12 +185,6 @@ namespace BansheeEditor
             }
         }
 
-        private void SetView(ViewType type)
-        {
-            viewType = type;
-            Refresh();
-        }
-
         private void EditorUpdate()
         {
             if (HasContentFocus)
@@ -167,24 +193,20 @@ namespace BansheeEditor
                 {
                     if (Input.IsButtonUp(ButtonCode.C))
                     {
-                        if(selectionPaths.Count > 0)
-                            Copy(selectionPaths);
+                        CopySelection();
                     }
                     else if (Input.IsButtonUp(ButtonCode.X))
                     {
-                        if (selectionPaths.Count > 0)
-                            Cut(selectionPaths);
+                        CutSelection();
                     }
                     else if (Input.IsButtonUp(ButtonCode.D))
                     {
-                        if (selectionPaths.Count > 0)
-                            Duplicate(selectionPaths);
+                        DuplicateSelection();
                     }
                     else if (Input.IsButtonUp(ButtonCode.V))
                     {
-                        Paste(currentDirectory);
+                        PasteToSelection();
                     }
-                    
                 }
             }
 
@@ -275,7 +297,7 @@ namespace BansheeEditor
             if (childEntries.Length == 0)
                 return;
 
-            if (viewType == ViewType.List16)
+            if (viewType == ProjectViewType.List16)
             {
                 int tileSize = 16;
 
@@ -296,9 +318,9 @@ namespace BansheeEditor
                 int tileSize = 64;
                 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();
@@ -338,6 +360,7 @@ namespace BansheeEditor
             GUIButton catchAll = new GUIButton("", EditorStyles.Blank);
             catchAll.Bounds = contentBounds;
             catchAll.OnClick += OnCatchAllClicked;
+            catchAll.SetContextMenu(entryContextMenu);
 
             contentUnderlayPanel.AddElement(catchAll);
 
@@ -384,6 +407,7 @@ namespace BansheeEditor
             overlayBtn.Bounds = entryButtonBounds;
             overlayBtn.OnClick += () => OnEntryClicked(entry.Path);
             overlayBtn.OnDoubleClick += () => OnEntryDoubleClicked(entry.Path);
+            overlayBtn.SetContextMenu(entryContextMenu);
 
             overlayPanel.AddElement(overlayBtn);
 
@@ -435,6 +459,62 @@ namespace BansheeEditor
             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()
         {
             currentDirectory = ProjectLibrary.Root.Path;
@@ -448,4 +528,77 @@ namespace BansheeEditor
             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;
         private bool isDestroyed;
 
+        public Action<bool> OnFocusChanged;
+
         public Rect2I Bounds
         {
             get { return Internal_GetBounds(mCachedPtr); }
@@ -20,6 +22,12 @@ namespace BansheeEngine
             get { return Internal_GetVisualBounds(mCachedPtr); }
         }
 
+        private void InternalOnFocusChanged(bool focus)
+        {
+            if (OnFocusChanged != null)
+                OnFocusChanged(focus);
+        }
+
         internal virtual void SetParent(GUILayout layout)
         {
             if (parent != null)

+ 6 - 0
SBansheeEngine/Include/BsScriptGUIElement.h

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

+ 4 - 4
SBansheeEngine/Source/BsScriptContextMenu.cpp

@@ -22,10 +22,10 @@ namespace BansheeEngine
 
 	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();
 	}

+ 21 - 1
SBansheeEngine/Source/BsScriptGUIElement.cpp

@@ -8,7 +8,9 @@
 #include "BsGUIElement.h"
 #include "BsScriptGUILayout.h"
 #include "BsScriptContextMenu.h"
-#include "BsGUILayout.h"
+#include "BsGUIElement.h"
+
+using namespace std::placeholders;
 
 namespace BansheeEngine
 {
@@ -21,6 +23,20 @@ namespace BansheeEngine
 	void ScriptGUIElementBaseTBase::initialize(GUIElementBase* 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)
@@ -43,6 +59,8 @@ namespace BansheeEngine
 		}
 	}
 
+	ScriptGUIElement::OnFocusChangedThunkDef ScriptGUIElement::onFocusChangedThunk;
+
 	ScriptGUIElement::ScriptGUIElement(MonoObject* instance)
 		:ScriptObject(instance)
 	{
@@ -63,6 +81,8 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_SetFlexibleHeight", &ScriptGUIElement::internal_SetFlexibleHeight);
 		metaData.scriptClass->addInternalCall("Internal_ResetDimensions", &ScriptGUIElement::internal_ResetDimensions);
 		metaData.scriptClass->addInternalCall("Internal_SetContextMenu", &ScriptGUIElement::internal_SetContextMenu);
+
+		onFocusChangedThunk = (OnFocusChangedThunkDef)metaData.scriptClass->getMethod("InternalOnFocusChanged", 1)->getThunk();
 	}
 
 	void ScriptGUIElement::internal_destroy(ScriptGUIElementBaseTBase* nativeInstance)

+ 6 - 4
TODO.txt

@@ -29,18 +29,20 @@ FBXImporter:
  - It should use DefaultMeshData
  - 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:
- - Add C# context menu support for GUI elements
  - 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
  - Add directory bar
- - Add DropDownWindow type that contains size options
 
 Test:
  - Basic look & navigation, selection/deselection