ソースを参照

WIP - Way of adding menu items from C#, ShortcutManager and extended GUIMenu so it displays shortcut keys

Marko Pintera 10 年 前
コミット
eedeb41ab4
36 ファイル変更613 行追加88 行削除
  1. 15 0
      BansheeCore/Include/BsInputFwd.h
  2. 9 0
      BansheeCore/Include/BsPlatform.h
  3. 17 0
      BansheeCore/Source/Win32/BsWin32Platform.cpp
  4. 4 3
      BansheeEditor/Include/BsGUIMenuBar.h
  5. 1 0
      BansheeEditor/Include/BsMainEditorWindow.h
  6. 5 5
      BansheeEditor/Source/BsEditorApplication.cpp
  7. 5 4
      BansheeEditor/Source/BsGUIMenuBar.cpp
  8. 4 0
      BansheeEngine/BansheeEngine.vcxproj
  9. 12 0
      BansheeEngine/BansheeEngine.vcxproj.filters
  10. 17 2
      BansheeEngine/Include/BsGUIDropDownBox.h
  11. 39 9
      BansheeEngine/Include/BsGUIMenu.h
  12. 3 18
      BansheeEngine/Include/BsInputConfiguration.h
  13. 1 0
      BansheeEngine/Include/BsPrerequisites.h
  14. 42 0
      BansheeEngine/Include/BsShortcutKey.h
  15. 39 0
      BansheeEngine/Include/BsShortcutManager.h
  16. 3 0
      BansheeEngine/Source/BsApplication.cpp
  17. 55 10
      BansheeEngine/Source/BsGUIDropDownBox.cpp
  18. 3 3
      BansheeEngine/Source/BsGUIInputBox.cpp
  19. 25 14
      BansheeEngine/Source/BsGUIMenu.cpp
  20. 3 3
      BansheeEngine/Source/BsInputConfiguration.cpp
  21. 47 0
      BansheeEngine/Source/BsShortcutKey.cpp
  22. 47 0
      BansheeEngine/Source/BsShortcutManager.cpp
  23. 7 7
      BansheeEngine/Source/BsVirtualInput.cpp
  24. 2 0
      MBansheeEditor/MBansheeEditor.csproj
  25. 24 0
      MBansheeEditor/MenuItem.cs
  26. 20 0
      MBansheeEditor/ShortcutKey.cs
  27. 3 3
      MBansheeEngine/InputConfiguration.cs
  28. 31 0
      SBansheeEditor/Include/BsMenuItemManager.h
  29. 2 0
      SBansheeEditor/SBansheeEditor.vcxproj
  30. 6 0
      SBansheeEditor/SBansheeEditor.vcxproj.filters
  31. 3 0
      SBansheeEditor/Source/BsEditorScriptManager.cpp
  32. 112 0
      SBansheeEditor/Source/BsMenuItemManager.cpp
  33. 1 1
      SBansheeEngine/Include/BsScriptInputConfiguration.h
  34. 1 1
      SBansheeEngine/Source/BsManagedSerializableObject.cpp
  35. 1 1
      SBansheeEngine/Source/BsScriptInputConfiguration.cpp
  36. 4 4
      TODO.txt

+ 15 - 0
BansheeCore/Include/BsInputFwd.h

@@ -406,4 +406,19 @@ namespace BansheeEngine
 		RightTrigger, /**< Gamepad right trigger */
 		RightTrigger, /**< Gamepad right trigger */
 		Count // Keep at end
 		Count // Keep at end
 	};
 	};
+
+	/**
+	 * @brief	Modifiers used with along with keyboard buttons.
+	 */
+	enum class ButtonModifier
+	{
+		None = 0x00,
+		Shift = 0x01,
+		Ctrl = 0x02,
+		Alt = 0x04,
+		ShiftCtrl = 0x03,
+		CtrlAlt = 0x06,
+		ShiftAlt = 0x05,
+		ShiftCtrlAlt = 0x07
+	};
 }
 }

+ 9 - 0
BansheeCore/Include/BsPlatform.h

@@ -358,6 +358,15 @@ namespace BansheeEngine
 		 */
 		 */
 		static WString copyFromClipboard();
 		static WString copyFromClipboard();
 
 
+		/**
+		 * @brief	Converts a keyboard key-code to a Unicode character.
+		 *
+		 * @note	Normally this will output a single character, but it can happen it outputs multiple 
+		 *			in case a accent/diacritic character could not be combined with the virtual key into 
+		 *			a single character.
+		 */
+		static WString keyCodeToUnicode(UINT32 keyCode);
+
 		/**
 		/**
 		 * @brief	Populates the provided buffer with a MAC address of the first available
 		 * @brief	Populates the provided buffer with a MAC address of the first available
 		 *			adapter, if one exists. If no adapters exist, returns false.
 		 *			adapter, if one exists. If no adapters exist, returns false.

+ 17 - 0
BansheeCore/Source/Win32/BsWin32Platform.cpp

@@ -292,6 +292,23 @@ namespace BansheeEngine
 		return L"";
 		return L"";
 	}
 	}
 
 
+	WString Platform::keyCodeToUnicode(UINT32 keyCode)
+	{
+		static HKL keyboardLayout = GetKeyboardLayout(0);
+		static UINT8 keyboarState[256];
+
+		if (GetKeyboardState(keyboarState) == FALSE)
+			return 0;
+
+		UINT virtualKey = MapVirtualKeyExW(keyCode, 1, keyboardLayout);
+
+		wchar_t output[2];
+		if (ToUnicodeEx(virtualKey, keyCode, keyboarState, output, 2, 0, keyboardLayout))
+			return WString(output, 2);
+
+		return StringUtil::WBLANK;
+	}
+
 	bool Platform::getMACAddress(MACAddress& address)
 	bool Platform::getMACAddress(MACAddress& address)
 	{
 	{
 		std::memset(&address, 0, sizeof(address));
 		std::memset(&address, 0, sizeof(address));

+ 4 - 3
BansheeEditor/Include/BsGUIMenuBar.h

@@ -1,10 +1,11 @@
 #pragma once
 #pragma once
 
 
 #include "BsEditorPrerequisites.h"
 #include "BsEditorPrerequisites.h"
+#include "BsShortcutKey.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
-	class GUIMenuBar
+	class BS_ED_EXPORT GUIMenuBar
 	{
 	{
 		struct GUIMenuBarData
 		struct GUIMenuBarData
 		{
 		{
@@ -19,8 +20,8 @@ namespace BansheeEngine
 
 
 		void setArea(INT32 x, INT32 y, UINT32 width, UINT32 height);
 		void setArea(INT32 x, INT32 y, UINT32 width, UINT32 height);
 
 
-		const GUIMenuItem* addMenuItem(const WString& path, std::function<void()> callback);
-		const GUIMenuItem* addSeparator(const WString& path);
+		const GUIMenuItem* addMenuItem(const WString& path, std::function<void()> callback, INT32 priority = 0, const ShortcutKey& shortcut = ShortcutKey::NONE);
+		const GUIMenuItem* addSeparator(const WString& path, INT32 priority = 0);
 		const GUIMenuItem* getMenuItem(const WString& path) const;
 		const GUIMenuItem* getMenuItem(const WString& path) const;
 		void removeMenuItem(const WString& path);
 		void removeMenuItem(const WString& path);
 	private:
 	private:

+ 1 - 0
BansheeEditor/Include/BsMainEditorWindow.h

@@ -18,6 +18,7 @@ namespace BansheeEngine
 		virtual bool isMain() const { return true; }
 		virtual bool isMain() const { return true; }
 
 
 		DockManager& getDockManager() const { return *mDockManager; }
 		DockManager& getDockManager() const { return *mDockManager; }
+		GUIMenuBar& getMenuBar() const { return *mMenuBar; }
 
 
 		static MainEditorWindow* create(const RenderWindowPtr& renderWindow);
 		static MainEditorWindow* create(const RenderWindowPtr& renderWindow);
 	protected:
 	protected:

+ 5 - 5
BansheeEditor/Source/BsEditorApplication.cpp

@@ -74,11 +74,11 @@ namespace BansheeEngine
 			auto inputConfig = VirtualInput::instance().getConfiguration();
 			auto inputConfig = VirtualInput::instance().getConfiguration();
 
 
 			inputConfig->registerButton("Rename", BC_F2);
 			inputConfig->registerButton("Rename", BC_F2);
-			inputConfig->registerButton("Undo", BC_Z, VButtonModifier::Ctrl);
-			inputConfig->registerButton("Redo", BC_Y, VButtonModifier::Ctrl);
-			inputConfig->registerButton("Copy", BC_C, VButtonModifier::Ctrl);
-			inputConfig->registerButton("Cut", BC_X, VButtonModifier::Ctrl);
-			inputConfig->registerButton("Paste", BC_V, VButtonModifier::Ctrl);
+			inputConfig->registerButton("Undo", BC_Z, ButtonModifier::Ctrl);
+			inputConfig->registerButton("Redo", BC_Y, ButtonModifier::Ctrl);
+			inputConfig->registerButton("Copy", BC_C, ButtonModifier::Ctrl);
+			inputConfig->registerButton("Cut", BC_X, ButtonModifier::Ctrl);
+			inputConfig->registerButton("Paste", BC_V, ButtonModifier::Ctrl);
 			inputConfig->registerButton("Delete", BC_DELETE);
 			inputConfig->registerButton("Delete", BC_DELETE);
 		}
 		}
 
 

+ 5 - 4
BansheeEditor/Source/BsGUIMenuBar.cpp

@@ -85,7 +85,8 @@ namespace BansheeEngine
 		refreshNonClientAreas();
 		refreshNonClientAreas();
 	}
 	}
 
 
-	const GUIMenuItem* GUIMenuBar::addMenuItem(const WString& path, std::function<void()> callback)
+	const GUIMenuItem* GUIMenuBar::addMenuItem(const WString& path, std::function<void()> callback, 
+		INT32 priority, const ShortcutKey& shortcut)
 	{
 	{
 		WString strippedPath = path;
 		WString strippedPath = path;
 		WString rootName;
 		WString rootName;
@@ -101,10 +102,10 @@ namespace BansheeEngine
 			refreshNonClientAreas();
 			refreshNonClientAreas();
 		}
 		}
 
 
-		return subMenu->menu->addMenuItem(strippedPath, callback);
+		return subMenu->menu->addMenuItem(strippedPath, callback, priority, shortcut);
 	}
 	}
 
 
-	const GUIMenuItem* GUIMenuBar::addSeparator(const WString& path)
+	const GUIMenuItem* GUIMenuBar::addSeparator(const WString& path, INT32 priority)
 	{
 	{
 		WString strippedPath = path;
 		WString strippedPath = path;
 		WString rootName;
 		WString rootName;
@@ -120,7 +121,7 @@ namespace BansheeEngine
 			refreshNonClientAreas();
 			refreshNonClientAreas();
 		}
 		}
 
 
-		return subMenu->menu->addSeparator(strippedPath);
+		return subMenu->menu->addSeparator(strippedPath, priority);
 	}
 	}
 
 
 	GUIMenuBar::GUIMenuBarData* GUIMenuBar::addNewButton(const WString& name)
 	GUIMenuBar::GUIMenuBarData* GUIMenuBar::addNewButton(const WString& name)

+ 4 - 0
BansheeEngine/BansheeEngine.vcxproj

@@ -250,6 +250,8 @@
     <ClCompile Include="Source\BsScriptCode.cpp" />
     <ClCompile Include="Source\BsScriptCode.cpp" />
     <ClCompile Include="Source\BsScriptCodeImporter.cpp" />
     <ClCompile Include="Source\BsScriptCodeImporter.cpp" />
     <ClCompile Include="Source\BsScriptCodeImportOptions.cpp" />
     <ClCompile Include="Source\BsScriptCodeImportOptions.cpp" />
+    <ClCompile Include="Source\BsShortcutKey.cpp" />
+    <ClCompile Include="Source\BsShortcutManager.cpp" />
     <ClCompile Include="Source\BsVirtualInput.cpp" />
     <ClCompile Include="Source\BsVirtualInput.cpp" />
     <ClInclude Include="Include\BsApplication.h" />
     <ClInclude Include="Include\BsApplication.h" />
     <ClInclude Include="Include\BsCameraHandler.h" />
     <ClInclude Include="Include\BsCameraHandler.h" />
@@ -332,6 +334,8 @@
     <ClInclude Include="Include\BsSceneManager.h" />
     <ClInclude Include="Include\BsSceneManager.h" />
     <ClInclude Include="Include\BsGUIScrollArea.h" />
     <ClInclude Include="Include\BsGUIScrollArea.h" />
     <ClInclude Include="Include\BsScriptManager.h" />
     <ClInclude Include="Include\BsScriptManager.h" />
+    <ClInclude Include="Include\BsShortcutKey.h" />
+    <ClInclude Include="Include\BsShortcutManager.h" />
     <ClInclude Include="Include\BsSpriteTextureRTTI.h" />
     <ClInclude Include="Include\BsSpriteTextureRTTI.h" />
     <ClInclude Include="Include\BsSprite.h" />
     <ClInclude Include="Include\BsSprite.h" />
     <ClInclude Include="Include\BsSpriteTexture.h" />
     <ClInclude Include="Include\BsSpriteTexture.h" />

+ 12 - 0
BansheeEngine/BansheeEngine.vcxproj.filters

@@ -317,6 +317,12 @@
     <ClInclude Include="Include\BsScriptCodeImportOptionsRTTI.h">
     <ClInclude Include="Include\BsScriptCodeImportOptionsRTTI.h">
       <Filter>Header Files\RTTI</Filter>
       <Filter>Header Files\RTTI</Filter>
     </ClInclude>
     </ClInclude>
+    <ClInclude Include="Include\BsShortcutKey.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="Include\BsShortcutManager.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsGUIElement.cpp">
     <ClCompile Include="Source\BsGUIElement.cpp">
@@ -547,5 +553,11 @@
     <ClCompile Include="Source\BsScriptCodeImportOptions.cpp">
     <ClCompile Include="Source\BsScriptCodeImportOptions.cpp">
       <Filter>Source Files</Filter>
       <Filter>Source Files</Filter>
     </ClCompile>
     </ClCompile>
+    <ClCompile Include="Source\BsShortcutManager.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Source\BsShortcutKey.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   </ItemGroup>
 </Project>
 </Project>

+ 17 - 2
BansheeEngine/Include/BsGUIDropDownBox.h

@@ -38,7 +38,7 @@ namespace BansheeEngine
 		 * @brief	Creates a new button entry with the specified callback that is triggered
 		 * @brief	Creates a new button entry with the specified callback that is triggered
 		 *			when button is selected.
 		 *			when button is selected.
 		 */
 		 */
-		static GUIDropDownDataEntry button(const WString& label, std::function<void()> callback);
+		static GUIDropDownDataEntry button(const WString& label, std::function<void()> callback, const WString& shortcutTag = StringUtil::WBLANK);
 
 
 		/**
 		/**
 		 * @brief	Creates a new sub-menu entry that will open the provided drop down data
 		 * @brief	Creates a new sub-menu entry that will open the provided drop down data
@@ -61,6 +61,11 @@ namespace BansheeEngine
 		 */
 		 */
 		const WString& getLabel() const { return mLabel; }
 		const WString& getLabel() const { return mLabel; }
 
 
+		/**
+		 * @brief	Returns the shortcut key combination string that is to be displayed along the entry label.
+		 */
+		const WString& getShortcutTag() const { return mShortcutTag; }
+
 		/**
 		/**
 		 * @brief	Returns a button callback if the entry (if an entry is a button).
 		 * @brief	Returns a button callback if the entry (if an entry is a button).
 		 */
 		 */
@@ -76,6 +81,7 @@ namespace BansheeEngine
 		std::function<void()> mCallback;
 		std::function<void()> mCallback;
 		GUIDropDownData mChildData;
 		GUIDropDownData mChildData;
 		WString mLabel;
 		WString mLabel;
+		WString mShortcutTag;
 		Type mType; 
 		Type mType; 
 	};
 	};
 
 
@@ -183,6 +189,15 @@ namespace BansheeEngine
 		 */
 		 */
 		struct DropDownSubMenu
 		struct DropDownSubMenu
 		{
 		{
+			/**
+			 * @brief	Contains various GUI elements used for displaying a single menu entry.
+			 */
+			struct EntryElementGUI
+			{
+				GUIButton* button;
+				GUILabel* shortcutLabel;
+			};
+
 		public:
 		public:
 			/**
 			/**
 			 * @brief	Creates a new drop down box sub-menu
 			 * @brief	Creates a new drop down box sub-menu
@@ -259,7 +274,7 @@ namespace BansheeEngine
 			bool mOpenedUpward;
 			bool mOpenedUpward;
 
 
 			Vector<GUITexture*> mCachedSeparators;
 			Vector<GUITexture*> mCachedSeparators;
-			Vector<GUIButton*> mCachedEntryBtns;
+			Vector<EntryElementGUI> mCachedEntryBtns;
 			Vector<GUIButton*> mCachedExpEntryBtns;
 			Vector<GUIButton*> mCachedExpEntryBtns;
 			GUIButton* mScrollUpBtn;
 			GUIButton* mScrollUpBtn;
 			GUIButton* mScrollDownBtn;
 			GUIButton* mScrollDownBtn;

+ 39 - 9
BansheeEngine/Include/BsGUIMenu.h

@@ -2,9 +2,21 @@
 
 
 #include "BsPrerequisites.h"
 #include "BsPrerequisites.h"
 #include "BsGUIDropDownBox.h"
 #include "BsGUIDropDownBox.h"
+#include "BsShortcutKey.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
+	class GUIMenuItem;
+
+	/**
+	 * @brief	Used for comparing GUI menu items in order to determine the order
+	 *			in which they are presented.
+	 */
+	struct GUIMenuItemComparer
+	{
+		bool operator() (const GUIMenuItem* const& a, const GUIMenuItem* const& b);
+	};
+
 	/**
 	/**
 	 * Holds information about a single element in a GUI menu.
 	 * Holds information about a single element in a GUI menu.
 	 */
 	 */
@@ -17,21 +29,25 @@ namespace BansheeEngine
 		 * @param	parent		Parent item, if any.
 		 * @param	parent		Parent item, if any.
 		 * @param	name		Name of the item to be displayed.
 		 * @param	name		Name of the item to be displayed.
 		 * @param	callback	Callback to be triggered when menu items is selected.
 		 * @param	callback	Callback to be triggered when menu items is selected.
+		 * @param	priority	Priority that determines the order of this element compared to its siblings.
+		 * @param	key			Keyboard shortcut that can be used for triggering the menu item.
 		 */
 		 */
-		GUIMenuItem(GUIMenuItem* parent, const WString& name, std::function<void()> callback);
+		GUIMenuItem(GUIMenuItem* parent, const WString& name, std::function<void()> callback, 
+			INT32 priority, const ShortcutKey& key);
 
 
 		/**
 		/**
 		 * @brief	Constructs a new separator menu item.
 		 * @brief	Constructs a new separator menu item.
 		 *
 		 *
 		 * @param	parent		Parent item, if any.
 		 * @param	parent		Parent item, if any.
+		 * @param	priority	Priority that determines the order of this element compared to its siblings.
 		 */
 		 */
-		GUIMenuItem(GUIMenuItem* parent);
+		GUIMenuItem(GUIMenuItem* parent, INT32 priority);
 		~GUIMenuItem();
 		~GUIMenuItem();
 
 
 		/**
 		/**
 		 * @brief	Registers a new child with the item.
 		 * @brief	Registers a new child with the item.
 		 */
 		 */
-		void addChild(GUIMenuItem* child) { mChildren.push_back(child); }
+		void addChild(GUIMenuItem* child) { mChildren.insert(child); }
 
 
 		/**
 		/**
 		 * @brief	Returns number of child menu items.
 		 * @brief	Returns number of child menu items.
@@ -53,6 +69,11 @@ namespace BansheeEngine
 		 */
 		 */
 		std::function<void()> getCallback() const { return mCallback; }
 		std::function<void()> getCallback() const { return mCallback; }
 
 
+		/**
+		 * @brief	Returns a keyboard shortcut that may be used for triggering the menu item callback.
+		 */
+		const ShortcutKey& getShortcut() const { return mShortcut; }
+
 		/**
 		/**
 		 * @brief	Checks is the menu item a separator or a normal named menu item.
 		 * @brief	Checks is the menu item a separator or a normal named menu item.
 		 */
 		 */
@@ -71,6 +92,7 @@ namespace BansheeEngine
 
 
 	private:
 	private:
 		friend class GUIMenu;
 		friend class GUIMenu;
+		friend struct GUIMenuItemComparer;
 
 
 		/**
 		/**
 		 * @copydoc	GUIMenuitem::findChild(const WString& name) const
 		 * @copydoc	GUIMenuitem::findChild(const WString& name) const
@@ -81,7 +103,9 @@ namespace BansheeEngine
 		bool mIsSeparator;
 		bool mIsSeparator;
 		WString mName;
 		WString mName;
 		std::function<void()> mCallback;
 		std::function<void()> mCallback;
-		Vector<GUIMenuItem*> mChildren;
+		INT32 mPriority;
+		ShortcutKey mShortcut;
+		Set<GUIMenuItem*, GUIMenuItemComparer> mChildren;
 	};
 	};
 
 
 	/**
 	/**
@@ -103,25 +127,30 @@ namespace BansheeEngine
 		virtual ~GUIMenu();
 		virtual ~GUIMenu();
 
 
 		/**
 		/**
-		 * @brief	Adds a new menu item with the specified callback. Element items are added in order.
+		 * @brief	Adds a new menu item with the specified callback. 
 		 *			
 		 *			
 		 * @param	path		Path that determines where to add the element. See class information on how to specify paths.
 		 * @param	path		Path that determines where to add the element. See class information on how to specify paths.
 		 *						All sub-elements of a path will be added automatically.
 		 *						All sub-elements of a path will be added automatically.
 		 * @param	callback	Callback that triggers when the path element is selected.
 		 * @param	callback	Callback that triggers when the path element is selected.
+		 * @param	priority	Priority determines the position of the menu item relative to its siblings.
+		 *						Higher priority means it will be placed higher up in the menu.
+		 * @param	key			Keyboard shortcut that can be used for triggering the menu item.
 		 *
 		 *
 		 * @returns	A menu item object that you may use for removing the menu item later. Its lifetime is managed internally.
 		 * @returns	A menu item object that you may use for removing the menu item later. Its lifetime is managed internally.
 		 */
 		 */
-		const GUIMenuItem* addMenuItem(const WString& path, std::function<void()> callback);
+		const GUIMenuItem* addMenuItem(const WString& path, std::function<void()> callback, INT32 priority, const ShortcutKey& key = ShortcutKey::NONE);
 
 
 		/**
 		/**
-		 * @brief	Adds a new separator menu item with the specified callback. Element items are added in order.
+		 * @brief	Adds a new separator menu item with the specified callback.
 		 *			
 		 *			
 		 * @param	path		Path that determines where to add the element. See class information on how to specify paths.
 		 * @param	path		Path that determines where to add the element. See class information on how to specify paths.
 		 *						All sub-elements of a path will be added automatically.
 		 *						All sub-elements of a path will be added automatically.
+		 * @param	priority	Priority determines the position of the menu item relative to its siblings.
+		 *						Higher priority means it will be placed higher up in the menu.
 		 *
 		 *
 		 * @returns	A menu item object that you may use for removing the menu item later. Its lifetime is managed internally.
 		 * @returns	A menu item object that you may use for removing the menu item later. Its lifetime is managed internally.
 		 */
 		 */
-		const GUIMenuItem* addSeparator(const WString& path);
+		const GUIMenuItem* addSeparator(const WString& path, INT32 priority);
 
 
 		/**
 		/**
 		 * @brief	Returns a menu item at the specified path, or null if one is not found.
 		 * @brief	Returns a menu item at the specified path, or null if one is not found.
@@ -152,7 +181,8 @@ namespace BansheeEngine
 		/**
 		/**
 		 * @brief	Adds a menu item at the specified path, as a normal button or as a separator.
 		 * @brief	Adds a menu item at the specified path, as a normal button or as a separator.
 		 */
 		 */
-		const GUIMenuItem* addMenuItemInternal(const WString& path, std::function<void()> callback, bool isSeparator);
+		const GUIMenuItem* addMenuItemInternal(const WString& path, std::function<void()> callback, bool isSeparator, 
+			INT32 priority, const ShortcutKey& key);
 
 
 		/**
 		/**
 		 * @brief	Return drop down data for the specified menu.
 		 * @brief	Return drop down data for the specified menu.

+ 3 - 18
BansheeEngine/Include/BsInputConfiguration.h

@@ -5,21 +5,6 @@
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
-	/**
-	 * @brief	Modifiers used with virtual buttons.
-	 */
-	enum class VButtonModifier
-	{
-		None = 0x00,
-		Shift = 0x01,
-		Ctrl = 0x02,
-		Alt = 0x04,
-		ShiftCtrl = 0x03,
-		CtrlAlt = 0x06,
-		ShiftAlt = 0x05,
-		ShiftCtrlAlt = 0x07
-	};
-
 	/**
 	/**
 	 * @brief	Describes a virtual button. Virtual buttons allow you to
 	 * @brief	Describes a virtual button. Virtual buttons allow you to
 	 *			map custom actions without needing to know about what 
 	 *			map custom actions without needing to know about what 
@@ -36,10 +21,10 @@ namespace BansheeEngine
 		 * @param	modifiers	Modifiers required to be pressed with the physical button to trigger the virtual button.
 		 * @param	modifiers	Modifiers required to be pressed with the physical button to trigger the virtual button.
 		 * @param	repeatable	If true, the virtual button events will be sent continually while the physical button is being held.
 		 * @param	repeatable	If true, the virtual button events will be sent continually while the physical button is being held.
 		 */
 		 */
-		VIRTUAL_BUTTON_DESC(ButtonCode buttonCode, VButtonModifier modifiers = VButtonModifier::None, bool repeatable = false);
+		VIRTUAL_BUTTON_DESC(ButtonCode buttonCode, ButtonModifier modifiers = ButtonModifier::None, bool repeatable = false);
 
 
 		ButtonCode buttonCode;
 		ButtonCode buttonCode;
-		VButtonModifier modifiers;
+		ButtonModifier modifiers;
 		bool repeatable;
 		bool repeatable;
 	};
 	};
 
 
@@ -172,7 +157,7 @@ namespace BansheeEngine
 		 * @param	modifiers	Modifiers required to be pressed with the physical button to trigger the virtual button.
 		 * @param	modifiers	Modifiers required to be pressed with the physical button to trigger the virtual button.
 		 * @param	repeatable	If true, the virtual button events will be sent continually while the physical button is being held.
 		 * @param	repeatable	If true, the virtual button events will be sent continually while the physical button is being held.
 		 */
 		 */
-		void registerButton(const String& name, ButtonCode buttonCode, VButtonModifier modifiers = VButtonModifier::None, bool repeatable = false);
+		void registerButton(const String& name, ButtonCode buttonCode, ButtonModifier modifiers = ButtonModifier::None, bool repeatable = false);
 
 
 		/**
 		/**
 		 * @brief	Unregisters a virtual button with the specified name. Events will no longer be generated for that button.
 		 * @brief	Unregisters a virtual button with the specified name. Events will no longer be generated for that button.

+ 1 - 0
BansheeEngine/Include/BsPrerequisites.h

@@ -33,6 +33,7 @@ namespace BansheeEngine
 	class VirtualInput;
 	class VirtualInput;
 	class InputConfiguration;
 	class InputConfiguration;
 	struct DragCallbackInfo;
 	struct DragCallbackInfo;
+	struct ShortcutKey;
 
 
 	// GUI
 	// GUI
 	class GUIManager;
 	class GUIManager;

+ 42 - 0
BansheeEngine/Include/BsShortcutKey.h

@@ -0,0 +1,42 @@
+#pragma once
+
+#include "BsPrerequisites.h"
+#include "BsInputFwd.h"
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	A key combination that is used for triggering keyboard shortcuts.
+	 *			Contains a button code and an optional modifier.
+	 */
+	struct BS_EXPORT ShortcutKey
+	{
+		struct BS_EXPORT Hash
+		{
+			inline size_t operator()(const ShortcutKey& x) const;
+		};
+
+		struct BS_EXPORT Equals
+		{
+			inline bool operator()(const ShortcutKey& a, const ShortcutKey& b) const;
+		};
+
+		ShortcutKey();
+		ShortcutKey(ButtonModifier modifier, ButtonCode code);
+
+		/**
+		 * @brief	Checks is the shortcut button and modifier combination valid.
+		 */
+		bool isValid() const { return button != BC_UNASSIGNED; }
+
+		/**
+		 * @brief	Returns a readable name of the shortcut key (e.g. "Shift + F").
+		 */
+		WString getName() const;
+
+		ButtonModifier modifier;
+		ButtonCode button;
+
+		static const ShortcutKey NONE;
+	};
+}

+ 39 - 0
BansheeEngine/Include/BsShortcutManager.h

@@ -0,0 +1,39 @@
+#pragma once
+
+#include "BsPrerequisites.h"
+#include "BsModule.h"
+#include "BsShortcutKey.h"
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	Allows you to register global keyboard shortcuts that trigger
+	 *			callbacks when a certain key, or a key combination is pressed.
+	 */
+	class BS_EXPORT ShortcutManager : public Module<ShortcutManager>
+	{
+	public:
+		ShortcutManager();
+		~ShortcutManager();
+
+		/**
+		 * @brief	Registers a new shortcut key and a callback to be called when the shortcut key is triggered.
+		 */
+		void addShortcut(const ShortcutKey& key, std::function<void()> callback);
+
+		/**
+		 * @brief	Removes an existing shortcut key (it's callback will no longer be triggered when this
+		 *			combination is pressed).
+		 */
+		void removeShortcut(const ShortcutKey& key);
+
+	private:
+		/**
+		 * @brief	Triggered whenever a user presses a button.
+		 */
+		void onButtonDown(const ButtonEvent& event);
+
+		UnorderedMap<ShortcutKey, std::function<void()>, ShortcutKey::Hash, ShortcutKey::Equals> mShortcuts;
+		HEvent mOnButtonDownConn;
+	};
+}

+ 3 - 0
BansheeEngine/Source/BsApplication.cpp

@@ -15,6 +15,7 @@
 #include "BsFileSystem.h"
 #include "BsFileSystem.h"
 #include "BsPlainTextImporter.h"
 #include "BsPlainTextImporter.h"
 #include "BsImporter.h"
 #include "BsImporter.h"
+#include "BsShortcutManager.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
@@ -46,6 +47,7 @@ namespace BansheeEngine
 		GUIManager::startUp();
 		GUIManager::startUp();
 		GUIMaterialManager::startUp();
 		GUIMaterialManager::startUp();
 		OverlayManager::startUp();
 		OverlayManager::startUp();
+		ShortcutManager::startUp();
 
 
 		Cursor::startUp();
 		Cursor::startUp();
 	}
 	}
@@ -72,6 +74,7 @@ namespace BansheeEngine
 
 
 		GUIMaterialManager::instance().clearMaterials();
 		GUIMaterialManager::instance().clearMaterials();
 
 
+		ShortcutManager::shutDown();
 		OverlayManager::shutDown();
 		OverlayManager::shutDown();
 		GUIManager::shutDown();
 		GUIManager::shutDown();
 		GUIMaterialManager::shutDown();
 		GUIMaterialManager::shutDown();

+ 55 - 10
BansheeEngine/Source/BsGUIDropDownBox.cpp

@@ -1,8 +1,11 @@
 #include "BsGUIDropDownBox.h"
 #include "BsGUIDropDownBox.h"
 #include "BsGUIArea.h"
 #include "BsGUIArea.h"
 #include "BsGUILayout.h"
 #include "BsGUILayout.h"
+#include "BsGUILayoutX.h"
 #include "BsGUITexture.h"
 #include "BsGUITexture.h"
+#include "BsGUILabel.h"
 #include "BsGUIButton.h"
 #include "BsGUIButton.h"
+#include "BsGUISpace.h"
 #include "BsGUIContent.h"
 #include "BsGUIContent.h"
 #include "BsGUISkin.h"
 #include "BsGUISkin.h"
 #include "BsViewport.h"
 #include "BsViewport.h"
@@ -13,7 +16,7 @@
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
-	const UINT32 GUIDropDownBox::DROP_DOWN_BOX_WIDTH = 150;
+	const UINT32 GUIDropDownBox::DROP_DOWN_BOX_WIDTH = 250;
 
 
 	GUIDropDownDataEntry GUIDropDownDataEntry::separator()
 	GUIDropDownDataEntry GUIDropDownDataEntry::separator()
 	{
 	{
@@ -24,12 +27,13 @@ namespace BansheeEngine
 		return data;
 		return data;
 	}
 	}
 
 
-	GUIDropDownDataEntry GUIDropDownDataEntry::button(const WString& label, std::function<void()> callback)
+	GUIDropDownDataEntry GUIDropDownDataEntry::button(const WString& label, std::function<void()> callback, const WString& shortcutTag)
 	{
 	{
 		GUIDropDownDataEntry data;
 		GUIDropDownDataEntry data;
 		data.mLabel = label;
 		data.mLabel = label;
 		data.mType = Type::Entry;
 		data.mType = Type::Entry;
 		data.mCallback = callback;
 		data.mCallback = callback;
+		data.mShortcutTag = shortcutTag;
 
 
 		return data;
 		return data;
 	}
 	}
@@ -291,8 +295,13 @@ namespace BansheeEngine
 		for(auto& elem : mCachedSeparators)
 		for(auto& elem : mCachedSeparators)
 			GUIElement::destroy(elem);
 			GUIElement::destroy(elem);
 
 
-		for(auto& elem : mCachedEntryBtns)
-			GUIElement::destroy(elem);
+		for (auto& entryData : mCachedEntryBtns)
+		{
+			GUIElement::destroy(entryData.button);
+
+			if (entryData.shortcutLabel != nullptr)
+				GUIElement::destroy(entryData.shortcutLabel);
+		}
 
 
 		for(auto& elem : mCachedExpEntryBtns)
 		for(auto& elem : mCachedExpEntryBtns)
 			GUIElement::destroy(elem);
 			GUIElement::destroy(elem);
@@ -389,7 +398,7 @@ namespace BansheeEngine
 		}
 		}
 
 
 		Vector<GUITexture*> newSeparators;
 		Vector<GUITexture*> newSeparators;
-		Vector<GUIButton*> newEntryBtns;
+		Vector<EntryElementGUI> newEntryBtns;
 		Vector<GUIButton*> newExpEntryBtns;
 		Vector<GUIButton*> newExpEntryBtns;
 		for(UINT32 i = pageStart; i < pageEnd; i++)
 		for(UINT32 i = pageStart; i < pageEnd; i++)
 		{
 		{
@@ -431,9 +440,12 @@ namespace BansheeEngine
 			else
 			else
 			{
 			{
 				GUIButton* entryBtn = nullptr;
 				GUIButton* entryBtn = nullptr;
+				GUILabel* shortcutLabel = nullptr;
 				if(!mCachedEntryBtns.empty())
 				if(!mCachedEntryBtns.empty())
 				{
 				{
-					entryBtn = mCachedEntryBtns.back();
+					entryBtn = mCachedEntryBtns.back().button;
+					shortcutLabel = mCachedEntryBtns.back().shortcutLabel;
+
 					mCachedEntryBtns.erase(mCachedEntryBtns.end() - 1);
 					mCachedEntryBtns.erase(mCachedEntryBtns.end() - 1);
 				}
 				}
 				else
 				else
@@ -443,8 +455,36 @@ namespace BansheeEngine
 					entryBtn->onClick.connect(std::bind(&DropDownSubMenu::elementClicked, this,  i));
 					entryBtn->onClick.connect(std::bind(&DropDownSubMenu::elementClicked, this,  i));
 				}
 				}
 
 
-				mContentLayout->addElement(entryBtn);
-				newEntryBtns.push_back(entryBtn);
+				const WString& shortcutTag = element.getShortcutTag();
+				if (!shortcutTag.empty())
+				{
+					if (shortcutLabel == nullptr)
+						shortcutLabel = GUILabel::create(HString(shortcutTag));
+					else
+						shortcutLabel->setContent(GUIContent(HString(shortcutTag)));
+				}
+				else
+				{
+					if (shortcutLabel != nullptr)
+						GUIElement::destroy(shortcutLabel);
+
+					shortcutLabel = nullptr;
+				}
+				
+				EntryElementGUI entryElemGUI;
+
+				GUILayout& entryLayout = mContentLayout->addLayoutX();
+				entryLayout.addElement(entryBtn);
+				entryElemGUI.button = entryBtn;
+				entryElemGUI.shortcutLabel = shortcutLabel;
+
+				if (shortcutLabel != nullptr)
+				{
+					entryLayout.addFlexibleSpace();
+					entryLayout.addElement(shortcutLabel);
+				}
+
+				newEntryBtns.push_back(entryElemGUI);
 			}
 			}
 		}
 		}
 
 
@@ -452,8 +492,13 @@ namespace BansheeEngine
 		for(auto& elem : mCachedSeparators)
 		for(auto& elem : mCachedSeparators)
 			GUIElement::destroy(elem);
 			GUIElement::destroy(elem);
 
 
-		for(auto& elem : mCachedEntryBtns)
-			GUIElement::destroy(elem);
+		for (auto& entryData : mCachedEntryBtns)
+		{
+			GUIElement::destroy(entryData.button);
+
+			if (entryData.shortcutLabel != nullptr)
+				GUIElement::destroy(entryData.shortcutLabel);
+		}
 
 
 		for(auto& elem : mCachedExpEntryBtns)
 		for(auto& elem : mCachedExpEntryBtns)
 			GUIElement::destroy(elem);
 			GUIElement::destroy(elem);

+ 3 - 3
BansheeEngine/Source/BsGUIInputBox.cpp

@@ -1102,9 +1102,9 @@ namespace BansheeEngine
 
 
 		if(!initialized)
 		if(!initialized)
 		{
 		{
-			mContextMenu.addMenuItem(L"Cut", std::bind(&GUIInputBox::cutText, const_cast<GUIInputBox*>(this)));
-			mContextMenu.addMenuItem(L"Copy", std::bind(&GUIInputBox::copyText, const_cast<GUIInputBox*>(this)));
-			mContextMenu.addMenuItem(L"Paste", std::bind(&GUIInputBox::pasteText, const_cast<GUIInputBox*>(this)));
+			mContextMenu.addMenuItem(L"Cut", std::bind(&GUIInputBox::cutText, const_cast<GUIInputBox*>(this)), 0);
+			mContextMenu.addMenuItem(L"Copy", std::bind(&GUIInputBox::copyText, const_cast<GUIInputBox*>(this)), 0);
+			mContextMenu.addMenuItem(L"Paste", std::bind(&GUIInputBox::pasteText, const_cast<GUIInputBox*>(this)), 0);
 
 
 			mContextMenu.setLocalizedName(L"Cut", HString(L"Cut"));
 			mContextMenu.setLocalizedName(L"Cut", HString(L"Cut"));
 			mContextMenu.setLocalizedName(L"Copy", HString(L"Copy"));
 			mContextMenu.setLocalizedName(L"Copy", HString(L"Copy"));

+ 25 - 14
BansheeEngine/Source/BsGUIMenu.cpp

@@ -1,22 +1,32 @@
 #include "BsGUIMenu.h"
 #include "BsGUIMenu.h"
 #include "BsGUIDropDownBox.h"
 #include "BsGUIDropDownBox.h"
+#include "BsShortcutManager.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
-	GUIMenuItem::GUIMenuItem(GUIMenuItem* parent, const WString& name, std::function<void()> callback)
-		:mParent(parent), mName(name), mCallback(callback), mIsSeparator(false)
+	bool GUIMenuItemComparer::operator() (const GUIMenuItem* const& a, const GUIMenuItem* const& b)
 	{
 	{
+		return a->mPriority > b->mPriority;
+	}
 
 
+	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)
-		:mParent(parent), mCallback(nullptr), mIsSeparator(true)
+	GUIMenuItem::GUIMenuItem(GUIMenuItem* parent, INT32 priority)
+		: mParent(parent), mCallback(nullptr), mIsSeparator(true), mPriority(priority)
 	{
 	{
 
 
 	}
 	}
 
 
 	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);
 	}
 	}
@@ -53,7 +63,7 @@ namespace BansheeEngine
 	}
 	}
 
 
 	GUIMenu::GUIMenu()
 	GUIMenu::GUIMenu()
-		:mRootElement(nullptr, L"", nullptr)
+		:mRootElement(nullptr, L"", nullptr, 0, ShortcutKey::NONE)
 	{
 	{
 
 
 	}
 	}
@@ -63,17 +73,18 @@ namespace BansheeEngine
 
 
 	}
 	}
 
 
-	const GUIMenuItem* GUIMenu::addMenuItem(const WString& path, std::function<void()> callback)
+	const GUIMenuItem* GUIMenu::addMenuItem(const WString& path, std::function<void()> callback, INT32 priority, const ShortcutKey& key)
 	{
 	{
-		return addMenuItemInternal(path, callback, false);
+		return addMenuItemInternal(path, callback, false, priority, key);
 	}
 	}
 
 
-	const GUIMenuItem* GUIMenu::addSeparator(const WString& path)
+	const GUIMenuItem* GUIMenu::addSeparator(const WString& path, INT32 priority)
 	{
 	{
-		return addMenuItemInternal(path, nullptr, true);
+		return addMenuItemInternal(path, nullptr, true, priority, ShortcutKey::NONE);
 	}
 	}
 
 
-	const GUIMenuItem* GUIMenu::addMenuItemInternal(const WString& path, std::function<void()> callback, bool isSeparator)
+	const GUIMenuItem* GUIMenu::addMenuItemInternal(const WString& path, std::function<void()> callback, bool isSeparator, 
+		INT32 priority, const ShortcutKey& key)
 	{
 	{
 		Vector<WString> pathElements = StringUtil::split(path, L"/");
 		Vector<WString> pathElements = StringUtil::split(path, L"/");
 
 
@@ -91,13 +102,13 @@ namespace BansheeEngine
 				bool isLastElem = i == (UINT32)(pathElements.size() - 1);
 				bool isLastElem = i == (UINT32)(pathElements.size() - 1);
 
 
 				if(isLastElem)
 				if(isLastElem)
-					existingItem = bs_new<GUIMenuItem, PoolAlloc>(curSubMenu, pathElem, callback);
+					existingItem = bs_new<GUIMenuItem, PoolAlloc>(curSubMenu, pathElem, callback, priority, key);
 				else
 				else
 				{
 				{
 					const WString& nextPathElem = *(pathElements.begin() + i);
 					const WString& nextPathElem = *(pathElements.begin() + i);
 
 
 					existingItem = bs_alloc<GUIMenuItem, PoolAlloc>();
 					existingItem = bs_alloc<GUIMenuItem, PoolAlloc>();
-					existingItem = new (existingItem) GUIMenuItem(curSubMenu, pathElem, nullptr);
+					existingItem = new (existingItem)GUIMenuItem(curSubMenu, pathElem, nullptr, 0, ShortcutKey::NONE);
 				}
 				}
 
 
 				curSubMenu->addChild(existingItem);
 				curSubMenu->addChild(existingItem);
@@ -108,7 +119,7 @@ namespace BansheeEngine
 
 
 		if(isSeparator)
 		if(isSeparator)
 		{
 		{
-			GUIMenuItem* separatorItem = bs_new<GUIMenuItem, PoolAlloc>(curSubMenu);
+			GUIMenuItem* separatorItem = bs_new<GUIMenuItem, PoolAlloc>(curSubMenu, priority);
 			curSubMenu->addChild(separatorItem);
 			curSubMenu->addChild(separatorItem);
 
 
 			return separatorItem;
 			return separatorItem;
@@ -168,7 +179,7 @@ namespace BansheeEngine
 			{
 			{
 				if(menuItem->getNumChildren() == 0)
 				if(menuItem->getNumChildren() == 0)
 				{
 				{
-					dropDownData.entries.push_back(GUIDropDownDataEntry::button(menuItem->getName(), menuItem->getCallback()));
+					dropDownData.entries.push_back(GUIDropDownDataEntry::button(menuItem->getName(), menuItem->getCallback(), menuItem->getShortcut().getName()));
 				}
 				}
 				else
 				else
 				{
 				{

+ 3 - 3
BansheeEngine/Source/BsInputConfiguration.cpp

@@ -9,10 +9,10 @@ namespace BansheeEngine
 	UINT32 VirtualAxis::NextAxisId = 0;
 	UINT32 VirtualAxis::NextAxisId = 0;
 
 
 	VIRTUAL_BUTTON_DESC::VIRTUAL_BUTTON_DESC()
 	VIRTUAL_BUTTON_DESC::VIRTUAL_BUTTON_DESC()
-		:buttonCode(BC_0), modifiers(VButtonModifier::None), repeatable(false)
+		:buttonCode(BC_0), modifiers(ButtonModifier::None), repeatable(false)
 	{ }
 	{ }
 
 
-	VIRTUAL_BUTTON_DESC::VIRTUAL_BUTTON_DESC(ButtonCode buttonCode, VButtonModifier modifiers, bool repeatable)
+	VIRTUAL_BUTTON_DESC::VIRTUAL_BUTTON_DESC(ButtonCode buttonCode, ButtonModifier modifiers, bool repeatable)
 		:buttonCode(buttonCode), modifiers(modifiers), repeatable(repeatable)
 		:buttonCode(buttonCode), modifiers(modifiers), repeatable(repeatable)
 	{ }
 	{ }
 
 
@@ -62,7 +62,7 @@ namespace BansheeEngine
 		:mRepeatInterval(300)
 		:mRepeatInterval(300)
 	{ }
 	{ }
 
 
-	void InputConfiguration::registerButton(const String& name, ButtonCode buttonCode, VButtonModifier modifiers, bool repeatable)
+	void InputConfiguration::registerButton(const String& name, ButtonCode buttonCode, ButtonModifier modifiers, bool repeatable)
 	{
 	{
 		Vector<VirtualButtonData>& btnData = mButtons[buttonCode & 0x0000FFFF];
 		Vector<VirtualButtonData>& btnData = mButtons[buttonCode & 0x0000FFFF];
 
 

+ 47 - 0
BansheeEngine/Source/BsShortcutKey.cpp

@@ -0,0 +1,47 @@
+#include "BsShortcutKey.h"
+#include "BsPlatform.h"
+
+namespace BansheeEngine
+{
+	const ShortcutKey ShortcutKey::NONE = ShortcutKey();
+
+	inline size_t ShortcutKey::Hash::operator()(const ShortcutKey& x) const
+	{
+		size_t seed = 0;
+		hash_combine(seed, (UINT32)x.button);
+		hash_combine(seed, (UINT32)x.modifier);
+
+		return seed;
+	}
+
+	inline bool ShortcutKey::Equals::operator()(const ShortcutKey& a, const ShortcutKey& b) const
+	{
+		return a.button == b.button && a.modifier == b.modifier;
+	}
+
+	ShortcutKey::ShortcutKey()
+		:modifier(ButtonModifier::None), button(BC_UNASSIGNED)
+	{ }
+
+	ShortcutKey::ShortcutKey(ButtonModifier modifier, ButtonCode code)
+		: modifier(modifier), button(code)
+	{ }
+
+	WString ShortcutKey::getName() const
+	{
+		static const WString MODIFIER_TO_NAME[8] =
+		{
+			L"", L"Shift + ", L"Ctrl + ", L"Shift + Ctrl + ",
+			L"Alt + ", L"Shift + Alt + ", L"Ctrl + Alt + ",
+			L"Shift + Ctrl + Alt + "
+		};
+
+		if (button == BC_UNASSIGNED)
+			return L"";
+
+		WString charStr = Platform::keyCodeToUnicode((UINT32)button);
+		StringUtil::toUpperCase(charStr);
+
+		return MODIFIER_TO_NAME[(UINT32)modifier] + charStr;
+	}
+}

+ 47 - 0
BansheeEngine/Source/BsShortcutManager.cpp

@@ -0,0 +1,47 @@
+#include "BsShortcutManager.h"
+#include "BsInput.h"
+
+using namespace std::placeholders;
+
+namespace BansheeEngine
+{
+	ShortcutManager::ShortcutManager()
+	{
+		mOnButtonDownConn = Input::instance().onButtonDown.connect(std::bind(&ShortcutManager::onButtonDown, this, _1));
+	}
+
+	ShortcutManager::~ShortcutManager()
+	{
+		mOnButtonDownConn.disconnect();
+	}
+
+	void ShortcutManager::addShortcut(const ShortcutKey& key, std::function<void()> callback)
+	{
+		mShortcuts[key] = callback;
+	}
+
+	void ShortcutManager::removeShortcut(const ShortcutKey& key)
+	{
+		mShortcuts.erase(key);
+	}
+
+	void ShortcutManager::onButtonDown(const ButtonEvent& event)
+	{
+		UINT32 modifiers = 0;
+		if (Input::instance().isButtonHeld(BC_LSHIFT) || Input::instance().isButtonHeld(BC_RSHIFT))
+			modifiers |= (UINT32)ButtonModifier::Shift;
+		else if (Input::instance().isButtonHeld(BC_LCONTROL) || Input::instance().isButtonHeld(BC_RCONTROL))
+			modifiers |= (UINT32)ButtonModifier::Ctrl;
+		else if (Input::instance().isButtonHeld(BC_LMENU) || Input::instance().isButtonHeld(BC_RMENU))
+			modifiers |= (UINT32)ButtonModifier::Alt;
+
+		ShortcutKey searchKey((ButtonModifier)modifiers, event.buttonCode);
+
+		auto iterFind = mShortcuts.find(searchKey);
+		if (iterFind != mShortcuts.end())
+		{
+			if (iterFind->second != nullptr)
+				iterFind->second();
+		}
+	}
+}

+ 7 - 7
BansheeEngine/Source/BsVirtualInput.cpp

@@ -8,7 +8,7 @@ using namespace std::placeholders;
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
 	VirtualInput::VirtualInput()
 	VirtualInput::VirtualInput()
-		:mActiveModifiers((UINT32)VButtonModifier::None)
+		:mActiveModifiers((UINT32)ButtonModifier::None)
 	{
 	{
 		mInputConfiguration = createConfiguration();
 		mInputConfiguration = createConfiguration();
 
 
@@ -174,11 +174,11 @@ namespace BansheeEngine
 	void VirtualInput::buttonDown(const ButtonEvent& event)
 	void VirtualInput::buttonDown(const ButtonEvent& event)
 	{
 	{
 		if(event.buttonCode == BC_LSHIFT || event.buttonCode == BC_RSHIFT)
 		if(event.buttonCode == BC_LSHIFT || event.buttonCode == BC_RSHIFT)
-			mActiveModifiers |= (UINT32)VButtonModifier::Shift;
+			mActiveModifiers |= (UINT32)ButtonModifier::Shift;
 		else if(event.buttonCode == BC_LCONTROL || event.buttonCode == BC_RCONTROL)
 		else if(event.buttonCode == BC_LCONTROL || event.buttonCode == BC_RCONTROL)
-			mActiveModifiers |= (UINT32)VButtonModifier::Ctrl;
+			mActiveModifiers |= (UINT32)ButtonModifier::Ctrl;
 		else if(event.buttonCode == BC_LMENU || event.buttonCode == BC_RMENU)
 		else if(event.buttonCode == BC_LMENU || event.buttonCode == BC_RMENU)
-			mActiveModifiers |= (UINT32)VButtonModifier::Alt;
+			mActiveModifiers |= (UINT32)ButtonModifier::Alt;
 		else
 		else
 		{
 		{
 			VirtualButton btn;
 			VirtualButton btn;
@@ -209,11 +209,11 @@ namespace BansheeEngine
 	void VirtualInput::buttonUp(const ButtonEvent& event)
 	void VirtualInput::buttonUp(const ButtonEvent& event)
 	{
 	{
 		if(event.buttonCode == BC_LSHIFT || event.buttonCode == BC_RSHIFT)
 		if(event.buttonCode == BC_LSHIFT || event.buttonCode == BC_RSHIFT)
-			mActiveModifiers &= ~(UINT32)VButtonModifier::Shift;
+			mActiveModifiers &= ~(UINT32)ButtonModifier::Shift;
 		else if(event.buttonCode == BC_LCONTROL || event.buttonCode == BC_RCONTROL)
 		else if(event.buttonCode == BC_LCONTROL || event.buttonCode == BC_RCONTROL)
-			mActiveModifiers &= ~(UINT32)VButtonModifier::Ctrl;
+			mActiveModifiers &= ~(UINT32)ButtonModifier::Ctrl;
 		else if(event.buttonCode == BC_LMENU || event.buttonCode == BC_RMENU)
 		else if(event.buttonCode == BC_LMENU || event.buttonCode == BC_RMENU)
-			mActiveModifiers &= ~(UINT32)VButtonModifier::Alt;
+			mActiveModifiers &= ~(UINT32)ButtonModifier::Alt;
 		else
 		else
 		{
 		{
 			VirtualButton btn;
 			VirtualButton btn;

+ 2 - 0
MBansheeEditor/MBansheeEditor.csproj

@@ -92,6 +92,7 @@
     <Compile Include="Inspector\InspectableVector4.cs" />
     <Compile Include="Inspector\InspectableVector4.cs" />
     <Compile Include="Inspector\Inspector.cs" />
     <Compile Include="Inspector\Inspector.cs" />
     <Compile Include="Inspector\InspectorWindow.cs" />
     <Compile Include="Inspector\InspectorWindow.cs" />
+    <Compile Include="MenuItem.cs" />
     <Compile Include="ModalWindow.cs" />
     <Compile Include="ModalWindow.cs" />
     <Compile Include="ProgressBar.cs" />
     <Compile Include="ProgressBar.cs" />
     <Compile Include="Scene\SceneCamera.cs" />
     <Compile Include="Scene\SceneCamera.cs" />
@@ -117,6 +118,7 @@
     <Compile Include="Scene\ScaleHandle.cs" />
     <Compile Include="Scene\ScaleHandle.cs" />
     <Compile Include="ScriptCompiler.cs" />
     <Compile Include="ScriptCompiler.cs" />
     <Compile Include="Selection.cs" />
     <Compile Include="Selection.cs" />
+    <Compile Include="ShortcutKey.cs" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\MBansheeEngine\MBansheeEngine.csproj">
     <ProjectReference Include="..\MBansheeEngine\MBansheeEngine.csproj">

+ 24 - 0
MBansheeEditor/MenuItem.cs

@@ -0,0 +1,24 @@
+using System;
+namespace BansheeEditor
+{
+    [AttributeUsage(AttributeTargets.Method)]
+    public sealed class MenuItem : Attribute
+    {
+        public MenuItem(string path, ShortcutKey shortcut, int priority = 0)
+        {
+            this.path = path;
+            this.shortcut = shortcut;
+            this.priority = priority;
+        }
+
+        public MenuItem(string path, int priority = 0)
+        {
+            this.path = path;
+            this.priority = priority;
+        }
+
+        private string path;
+        private ShortcutKey shortcut;
+        private int priority;
+    }
+}

+ 20 - 0
MBansheeEditor/ShortcutKey.cs

@@ -0,0 +1,20 @@
+using System.Runtime.InteropServices;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+    [StructLayout(LayoutKind.Sequential)]
+    public struct ShortcutKey
+    {
+        public ButtonModifier modifier;
+        public ButtonCode key;
+
+        public ShortcutKey(ButtonModifier modifier, ButtonCode key)
+        {
+            this.modifier = modifier;
+            this.key = key;
+        }
+
+        public static ShortcutKey None = new ShortcutKey(ButtonModifier.None, ButtonCode.Unassigned);
+    }
+}

+ 3 - 3
MBansheeEngine/InputConfiguration.cs

@@ -28,7 +28,7 @@ namespace BansheeEngine
         }
         }
 
 
         public void RegisterButton(String name, ButtonCode buttonCode, 
         public void RegisterButton(String name, ButtonCode buttonCode, 
-            VButtonModifier modifiers = VButtonModifier.None, bool repeatable = false)
+            ButtonModifier modifiers = ButtonModifier.None, bool repeatable = false)
         {
         {
             Internal_RegisterButton(mCachedPtr, name, buttonCode, modifiers, repeatable);
             Internal_RegisterButton(mCachedPtr, name, buttonCode, modifiers, repeatable);
         }
         }
@@ -54,7 +54,7 @@ namespace BansheeEngine
 
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_RegisterButton(IntPtr thisPtr, String name, ButtonCode buttonCode,
         private static extern void Internal_RegisterButton(IntPtr thisPtr, String name, ButtonCode buttonCode,
-            VButtonModifier modifiers, bool repeatable);
+            ButtonModifier modifiers, bool repeatable);
 
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_UnregisterButton(IntPtr thisPtr, String name);
         private static extern void Internal_UnregisterButton(IntPtr thisPtr, String name);
@@ -74,7 +74,7 @@ namespace BansheeEngine
     }
     }
 
 
     // Do not modify IDs, they must match the C++ enum VButtonModifier
     // Do not modify IDs, they must match the C++ enum VButtonModifier
-    public enum VButtonModifier
+    public enum ButtonModifier
     {
     {
         None = 0x00,
         None = 0x00,
         Shift = 0x01,
         Shift = 0x01,

+ 31 - 0
SBansheeEditor/Include/BsMenuItemManager.h

@@ -0,0 +1,31 @@
+#pragma once
+
+#include "BsScriptEditorPrerequisites.h"
+#include "BsModule.h"
+
+namespace BansheeEngine
+{
+	class BS_SCR_BED_EXPORT MenuItemManager : public Module<MenuItemManager>
+	{
+	public:
+		MenuItemManager(ScriptAssemblyManager& scriptObjectManager);
+		~MenuItemManager();
+
+	private:
+		void clearMenuItems();
+		void reloadAssemblyData();
+		bool parseMenuItemMethod(MonoMethod* method, WString& path, ShortcutKey& shortcut, INT32& priority) const;
+
+		static void menuItemCallback(MonoMethod* method);
+
+		ScriptAssemblyManager& mScriptObjectManager;
+		HEvent mDomainLoadedConn;
+
+		MonoClass* mMenuItemAttribute;
+		MonoField* mPathField;
+		MonoField* mShortcutField;
+		MonoField* mPriorityField;
+
+		Vector<WString> mMenuItems;
+	};
+}

+ 2 - 0
SBansheeEditor/SBansheeEditor.vcxproj

@@ -236,6 +236,7 @@
     <ClInclude Include="Include\BsGUIGameObjectField.h" />
     <ClInclude Include="Include\BsGUIGameObjectField.h" />
     <ClInclude Include="Include\BsGUIPanelContainer.h" />
     <ClInclude Include="Include\BsGUIPanelContainer.h" />
     <ClInclude Include="Include\BsGUIResourceField.h" />
     <ClInclude Include="Include\BsGUIResourceField.h" />
+    <ClInclude Include="Include\BsMenuItemManager.h" />
     <ClInclude Include="Include\BsScriptBrowseDialog.h" />
     <ClInclude Include="Include\BsScriptBrowseDialog.h" />
     <ClInclude Include="Include\BsScriptBuildManager.h" />
     <ClInclude Include="Include\BsScriptBuildManager.h" />
     <ClInclude Include="Include\BsScriptCodeEditor.h" />
     <ClInclude Include="Include\BsScriptCodeEditor.h" />
@@ -275,6 +276,7 @@
     <ClInclude Include="Include\BsScriptSelection.h" />
     <ClInclude Include="Include\BsScriptSelection.h" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
+    <ClCompile Include="Source\BsMenuItemManager.cpp" />
     <ClCompile Include="Source\BsScriptBuildManager.cpp" />
     <ClCompile Include="Source\BsScriptBuildManager.cpp" />
     <ClCompile Include="Source\BsScriptCodeEditor.cpp" />
     <ClCompile Include="Source\BsScriptCodeEditor.cpp" />
     <ClCompile Include="Source\BsScriptDragDropManager.cpp" />
     <ClCompile Include="Source\BsScriptDragDropManager.cpp" />

+ 6 - 0
SBansheeEditor/SBansheeEditor.vcxproj.filters

@@ -138,6 +138,9 @@
     <ClInclude Include="Include\BsScriptBuildManager.h">
     <ClInclude Include="Include\BsScriptBuildManager.h">
       <Filter>Header Files</Filter>
       <Filter>Header Files</Filter>
     </ClInclude>
     </ClInclude>
+    <ClInclude Include="Include\BsMenuItemManager.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsScriptEditorPlugin.cpp">
     <ClCompile Include="Source\BsScriptEditorPlugin.cpp">
@@ -263,5 +266,8 @@
     <ClCompile Include="Source\BsScriptBuildManager.cpp">
     <ClCompile Include="Source\BsScriptBuildManager.cpp">
       <Filter>Source Files</Filter>
       <Filter>Source Files</Filter>
     </ClCompile>
     </ClCompile>
+    <ClCompile Include="Source\BsMenuItemManager.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   </ItemGroup>
 </Project>
 </Project>

+ 3 - 0
SBansheeEditor/Source/BsEditorScriptManager.cpp

@@ -10,6 +10,7 @@
 #include "BsScriptHandleSliderManager.h"
 #include "BsScriptHandleSliderManager.h"
 #include "BsScriptDragDropManager.h"
 #include "BsScriptDragDropManager.h"
 #include "BsScriptProjectLibrary.h"
 #include "BsScriptProjectLibrary.h"
+#include "BsMenuItemManager.h"
 #include "BsTime.h"
 #include "BsTime.h"
 #include "BsMath.h"
 #include "BsMath.h"
 #include "BsEditorApplication.h"
 #include "BsEditorApplication.h"
@@ -30,6 +31,7 @@ namespace BansheeEngine
 		HandleManager::startUp<ScriptHandleManager>(ScriptAssemblyManager::instance());
 		HandleManager::startUp<ScriptHandleManager>(ScriptAssemblyManager::instance());
 		ScriptDragDropManager::startUp();
 		ScriptDragDropManager::startUp();
 		ScriptProjectLibrary::startUp();
 		ScriptProjectLibrary::startUp();
+		MenuItemManager::startUp(ScriptAssemblyManager::instance());
 
 
 		mOnDomainLoadConn = ScriptObjectManager::instance().onRefreshDomainLoaded.connect(std::bind(&EditorScriptManager::loadMonoTypes, this));
 		mOnDomainLoadConn = ScriptObjectManager::instance().onRefreshDomainLoaded.connect(std::bind(&EditorScriptManager::loadMonoTypes, this));
 		mOnAssemblyRefreshDoneConn = ScriptObjectManager::instance().onRefreshComplete.connect(std::bind(&EditorScriptManager::onAssemblyRefreshDone, this));
 		mOnAssemblyRefreshDoneConn = ScriptObjectManager::instance().onRefreshComplete.connect(std::bind(&EditorScriptManager::onAssemblyRefreshDone, this));
@@ -45,6 +47,7 @@ namespace BansheeEngine
 		mOnDomainLoadConn.disconnect();
 		mOnDomainLoadConn.disconnect();
 		mOnAssemblyRefreshDoneConn.disconnect();
 		mOnAssemblyRefreshDoneConn.disconnect();
 
 
+		MenuItemManager::shutDown();
 		ScriptProjectLibrary::shutDown();
 		ScriptProjectLibrary::shutDown();
 		ScriptDragDropManager::shutDown();
 		ScriptDragDropManager::shutDown();
 		ScriptHandleSliderManager::shutDown();
 		ScriptHandleSliderManager::shutDown();

+ 112 - 0
SBansheeEditor/Source/BsMenuItemManager.cpp

@@ -0,0 +1,112 @@
+#include "BsMenuItemManager.h"
+#include "BsScriptAssemblyManager.h"
+#include "BsMonoAssembly.h"
+#include "BsMonoClass.h"
+#include "BsMonoMethod.h"
+#include "BsMonoField.h"
+#include "BsMonoManager.h"
+#include "BsMonoUtil.h"
+#include "BsScriptObjectManager.h"
+#include "BsShortcutKey.h"
+#include "BsEditorWindowManager.h"
+#include "BsMainEditorWindow.h"
+#include "BsGUIMenuBar.h"
+
+using namespace std::placeholders;
+
+namespace BansheeEngine
+{
+	MenuItemManager::MenuItemManager(ScriptAssemblyManager& scriptObjectManager)
+		:mScriptObjectManager(scriptObjectManager), mMenuItemAttribute(nullptr), mPathField(nullptr), 
+		mShortcutField(nullptr), mPriorityField(nullptr)
+	{
+		mDomainLoadedConn = ScriptObjectManager::instance().onRefreshDomainLoaded.connect(std::bind(&MenuItemManager::reloadAssemblyData, this));
+		reloadAssemblyData();
+	}
+
+	MenuItemManager::~MenuItemManager()
+	{
+		mDomainLoadedConn.disconnect();
+
+		clearMenuItems();
+	}
+
+	void MenuItemManager::clearMenuItems()
+	{
+		MainEditorWindow* mainWindow = EditorWindowManager::instance().getMainWindow();
+
+		for (auto& menuItem : mMenuItems)
+			mainWindow->getMenuBar().removeMenuItem(menuItem);
+	}
+
+	void MenuItemManager::reloadAssemblyData()
+	{
+		clearMenuItems();
+
+		// Reload MenuItem attribute from editor assembly
+		MonoAssembly* editorAssembly = MonoManager::instance().getAssembly(EDITOR_ASSEMBLY);
+		mMenuItemAttribute = editorAssembly->getClass("BansheeEditor", "MenuItem");
+		if (mMenuItemAttribute == nullptr)
+			BS_EXCEPT(InvalidStateException, "Cannot find MenuItem managed class.");
+
+		mPathField = mMenuItemAttribute->getField("path");
+		mShortcutField = mMenuItemAttribute->getField("shortcut");
+		mPriorityField = mMenuItemAttribute->getField("priority");
+
+		MainEditorWindow* mainWindow = EditorWindowManager::instance().getMainWindow();
+
+		Vector<String> scriptAssemblyNames = mScriptObjectManager.getScriptAssemblies();
+		for (auto& assemblyName : scriptAssemblyNames)
+		{
+			MonoAssembly* assembly = MonoManager::instance().getAssembly(assemblyName);
+
+			// Find new menu item methods
+			const Vector<MonoClass*>& allClasses = assembly->getAllClasses();
+			for (auto curClass : allClasses)
+			{
+				const Vector<MonoMethod*>& methods = curClass->getAllMethods();
+				for (auto& curMethod : methods)
+				{
+					WString path;
+					ShortcutKey shortcutKey = ShortcutKey::NONE;
+					INT32 priority = 0;
+					if (parseMenuItemMethod(curMethod, path, shortcutKey, priority))
+					{
+						std::function<void()> callback = std::bind(&MenuItemManager::menuItemCallback, curMethod);
+
+						mainWindow->getMenuBar().addMenuItem(path, callback, priority, shortcutKey);
+						mMenuItems.push_back(path);
+					}
+				}
+			}
+		}
+	}
+
+	bool MenuItemManager::parseMenuItemMethod(MonoMethod* method, WString& path, ShortcutKey& shortcut, INT32& priority) const
+	{
+		if (!method->hasAttribute(mMenuItemAttribute))
+			return false;
+
+		if (method->getNumParameters() != 0)
+			return false;
+
+		if (!method->isStatic())
+			return false;
+
+		MonoObject* menuItemAttrib = method->getAttribute(mMenuItemAttribute);
+
+		MonoString* monoPath;
+		mPathField->getValue(menuItemAttrib, &monoPath);
+
+		mShortcutField->getValue(menuItemAttrib, &shortcut);
+		path = MonoUtil::monoToWString(monoPath);
+		mPriorityField->getValue(menuItemAttrib, &priority);
+
+		return true;
+	}
+
+	void MenuItemManager::menuItemCallback(MonoMethod* method)
+	{
+		method->invoke(nullptr, nullptr);
+	}
+}

+ 1 - 1
SBansheeEngine/Include/BsScriptInputConfiguration.h

@@ -20,7 +20,7 @@ namespace BansheeEngine
 		static void internal_CreateInstance(MonoObject* object);
 		static void internal_CreateInstance(MonoObject* object);
 
 
 		static void internal_RegisterButton(ScriptInputConfiguration* thisPtr, MonoString* name, ButtonCode buttonCode,
 		static void internal_RegisterButton(ScriptInputConfiguration* thisPtr, MonoString* name, ButtonCode buttonCode,
-			VButtonModifier modifiers, bool repeatable);
+			ButtonModifier modifiers, bool repeatable);
 		static void internal_UnregisterButton(ScriptInputConfiguration* thisPtr, MonoString* name);
 		static void internal_UnregisterButton(ScriptInputConfiguration* thisPtr, MonoString* name);
 
 
 		static void internal_RegisterAxis(ScriptInputConfiguration* thisPtr, MonoString* name, InputAxis type, float deadZone,
 		static void internal_RegisterAxis(ScriptInputConfiguration* thisPtr, MonoString* name, InputAxis type, float deadZone,

+ 1 - 1
SBansheeEngine/Source/BsManagedSerializableObject.cpp

@@ -165,7 +165,7 @@ namespace BansheeEngine
 	{
 	{
 		MonoObject* fieldValue = fieldInfo->mMonoField->getValueBoxed(mManagedInstance);
 		MonoObject* fieldValue = fieldInfo->mMonoField->getValueBoxed(mManagedInstance);
 
 
-		return ManagedSerializableFieldData::create(fieldInfo->mTypeInfo, fieldValue);		
+		return ManagedSerializableFieldData::create(fieldInfo->mTypeInfo, fieldValue);
 	}
 	}
 
 
 	void ManagedSerializableObject::setObjectData(const ManagedSerializableObjectDataPtr& objectData, const ManagedSerializableObjectInfoPtr& originalEntriesType)
 	void ManagedSerializableObject::setObjectData(const ManagedSerializableObjectDataPtr& objectData, const ManagedSerializableObjectInfoPtr& originalEntriesType)

+ 1 - 1
SBansheeEngine/Source/BsScriptInputConfiguration.cpp

@@ -54,7 +54,7 @@ namespace BansheeEngine
 	}
 	}
 
 
 	void ScriptInputConfiguration::internal_RegisterButton(ScriptInputConfiguration* thisPtr, MonoString* name, ButtonCode buttonCode,
 	void ScriptInputConfiguration::internal_RegisterButton(ScriptInputConfiguration* thisPtr, MonoString* name, ButtonCode buttonCode,
-		VButtonModifier modifiers, bool repeatable)
+		ButtonModifier modifiers, bool repeatable)
 	{
 	{
 		String nameStr = MonoUtil::monoToString(name);
 		String nameStr = MonoUtil::monoToString(name);
 
 

+ 4 - 4
TODO.txt

@@ -21,11 +21,10 @@ the same and the only variable will be the 4byte random number, which can someti
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 MenuItem
 MenuItem
 
 
-MenuItem[] attribute in C#
- - Allowed on static methods in editor assembly
+TEST the new MenuItem (C#) and menu shortcut GUI, and menu shortcut functionality
 
 
-MenuItemManager registers then menu items with MainEditorWindow
- - upon assembly refresh all menu items are removed, and then re-added
+While I'm dealing with menus also add support for keyboard navigation (this also includes GUIList)
+ - arrows for navigation, enter to confirm, esc to close
 
 
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 VisualStudio integration
 VisualStudio integration
@@ -102,6 +101,7 @@ Other simple stuff:
  - Material/Shader/Technique/Pass don't have a C# interface
  - Material/Shader/Technique/Pass don't have a C# interface
  - Get rid of PoolAlloc and other unused allocators (plus fix bs_new and others which have weird overloads)
  - Get rid of PoolAlloc and other unused allocators (plus fix bs_new and others which have weird overloads)
  - Call stack from C# to use in Debug.Log calls
  - Call stack from C# to use in Debug.Log calls
+ - Get rid of event callback from HString and figure out a better way
 
 
 ----------------------------------------------------------------------
 ----------------------------------------------------------------------
 Handles
 Handles