Browse Source

Localized strings no longer use a callback to notify owners about changes
Started work on localized strings in multiple string tables

Marko Pintera 10 years ago
parent
commit
cd3174bc2a
33 changed files with 458 additions and 443 deletions
  1. 3 1
      BansheeCore/Source/BsResourceHandle.cpp
  2. 0 1
      BansheeEditor/Include/BsEditorPrerequisites.h
  3. 2 0
      BansheeEngine/BansheeEngine.vcxproj
  4. 6 0
      BansheeEngine/BansheeEngine.vcxproj.filters
  5. 0 2
      BansheeEngine/Include/BsGUIButtonBase.h
  6. 2 2
      BansheeEngine/Include/BsGUIContent.h
  7. 9 10
      BansheeEngine/Include/BsGUILabel.h
  8. 48 0
      BansheeEngine/Include/BsHEString.h
  9. 1 0
      BansheeEngine/Include/BsPrerequisites.h
  10. 20 3
      BansheeEngine/Include/BsProfilerOverlay.h
  11. 0 7
      BansheeEngine/Source/BsGUIButtonBase.cpp
  12. 1 1
      BansheeEngine/Source/BsGUIHelper.cpp
  13. 2 8
      BansheeEngine/Source/BsGUILabel.cpp
  14. 1 11
      BansheeEngine/Source/BsGUIManager.cpp
  15. 30 0
      BansheeEngine/Source/BsHEString.cpp
  16. 108 130
      BansheeEngine/Source/BsProfilerOverlay.cpp
  17. 27 35
      BansheeUtility/Include/BsHString.h
  18. 3 15
      BansheeUtility/Include/BsStringTable.h
  19. 54 69
      BansheeUtility/Source/BsHString.cpp
  20. 9 80
      BansheeUtility/Source/BsStringTable.cpp
  21. 13 13
      MBansheeEditor/ColorPicker.cs
  22. 16 16
      MBansheeEditor/DialogBox.cs
  23. 1 1
      MBansheeEditor/HierarchyWindow.cs
  24. 20 20
      MBansheeEditor/Inspector/InspectorWindow.cs
  25. 37 0
      MBansheeEditor/LocEdString.cs
  26. 1 0
      MBansheeEditor/MBansheeEditor.csproj
  27. 15 8
      MBansheeEditor/ProjectWindow.cs
  28. 1 1
      MBansheeEditor/Scene/SceneWindow.cs
  29. 8 3
      MBansheeEngine/LocString.cs
  30. 0 1
      SBansheeEditor/Include/BsScriptGizmos.h
  31. 1 1
      SBansheeEngine/Include/BsScriptHString.h
  32. 2 2
      SBansheeEngine/Source/BsScriptHString.cpp
  33. 17 2
      TODO.txt

+ 3 - 1
BansheeCore/Source/BsResourceHandle.cpp

@@ -91,10 +91,12 @@ namespace BansheeEngine
 
 	void ResourceHandleBase::throwIfNotLoaded() const
 	{
-		if(!isLoaded()) 
+#if BS_DEBUG_MODE
+		if (!isLoaded())
 		{
 			BS_EXCEPT(InternalErrorException, "Trying to access a resource that hasn't been loaded yet.");
 		}
+#endif
 	}
 
 	RTTITypeBase* ResourceHandleBase::getRTTIStatic()

+ 0 - 1
BansheeEditor/Include/BsEditorPrerequisites.h

@@ -20,7 +20,6 @@
 
 namespace BansheeEngine
 {
-	// GUI
 	class EditorWindowBase;
 	class EditorWindow;
 	class EditorWidgetBase;

+ 2 - 0
BansheeEngine/BansheeEngine.vcxproj

@@ -244,6 +244,7 @@
     <ClCompile Include="Source\BsGUISkinRTTI.cpp" />
     <ClCompile Include="Source\BsGUISlider.cpp" />
     <ClCompile Include="Source\BsGUISpace.cpp" />
+    <ClCompile Include="Source\BsHEString.cpp" />
     <ClCompile Include="Source\BsInputConfiguration.cpp" />
     <ClCompile Include="Source\BsLight.cpp" />
     <ClCompile Include="Source\BsPlainText.cpp" />
@@ -273,6 +274,7 @@
     <ClInclude Include="Include\BsGUISkinRTTI.h" />
     <ClInclude Include="Include\BsGUISlider.h" />
     <ClInclude Include="Include\BsGUIWidgetRTTI.h" />
+    <ClInclude Include="Include\BsHEString.h" />
     <ClInclude Include="Include\BsLight.h" />
     <ClInclude Include="Include\BsLightInternal.h" />
     <ClInclude Include="Include\BsLightInternalRTTI.h" />

+ 6 - 0
BansheeEngine/BansheeEngine.vcxproj.filters

@@ -353,6 +353,9 @@
     <ClInclude Include="Include\BsProfilerOverlayRTTI.h">
       <Filter>Header Files\RTTI</Filter>
     </ClInclude>
+    <ClInclude Include="Include\BsHEString.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsGUIElement.cpp">
@@ -604,5 +607,8 @@
     <ClCompile Include="Source\BsLight.cpp">
       <Filter>Source Files\Components</Filter>
     </ClCompile>
+    <ClCompile Include="Source\BsHEString.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 0 - 2
BansheeEngine/Include/BsGUIButtonBase.h

@@ -161,7 +161,5 @@ namespace BansheeEngine
 
 		IMAGE_SPRITE_DESC mImageDesc;
 		GUIContent mContent;
-
-		HEvent mLocStringUpdatedConn;
 	};
 }

+ 2 - 2
BansheeEngine/Include/BsGUIContent.h

@@ -18,7 +18,7 @@ namespace BansheeEngine
 		/**
 		 * @brief	Constructs content with just a string.
 		 */
-		explicit GUIContent(const HString& text);
+		GUIContent(const HString& text);
 
 		/**
 		 * @brief	Constructs content with a string and a tooltip.
@@ -28,7 +28,7 @@ namespace BansheeEngine
 		/**
 		 * @brief	Constructs content with just an image.
 		 */
-		explicit GUIContent(const HSpriteTexture& image);
+		GUIContent(const HSpriteTexture& image);
 
 		/**
 		 * @brief	Constructs content with an image and a tooltip.

+ 9 - 10
BansheeEngine/Include/BsGUILabel.h

@@ -70,56 +70,55 @@ namespace BansheeEngine
 		/**
 		 * @copydoc	GUIElement::setTint
 		 */
-		virtual void setTint(const Color& color);
+		virtual void setTint(const Color& color) override;
 
 		/**
 		 * @copydoc	GUIElement::_getOptimalSize
 		 */
-		virtual Vector2I _getOptimalSize() const;
+		virtual Vector2I _getOptimalSize() const override;
 
 		/**
 		 * @copydoc	GUIElement::getElementType
 		 */
-		virtual ElementType _getElementType() const { return ElementType::Label; }
+		virtual ElementType _getElementType() const override { return ElementType::Label; }
 	protected:
 		~GUILabel();
 
 		/**
 		 * @copydoc GUIElement::getNumRenderElements
 		 */
-		virtual UINT32 _getNumRenderElements() const;
+		virtual UINT32 _getNumRenderElements() const override;
 
 		/**
 		 * @copydoc GUIElement::getMaterial
 		 */
-		virtual const GUIMaterialInfo& _getMaterial(UINT32 renderElementIdx) const;
+		virtual const GUIMaterialInfo& _getMaterial(UINT32 renderElementIdx) const override;
 
 		/**
 		 * @copydoc GUIElement::getNumQuads
 		 */
-		virtual UINT32 _getNumQuads(UINT32 renderElementIdx) const;
+		virtual UINT32 _getNumQuads(UINT32 renderElementIdx) const override;
 
 		/**
 		 * @copydoc GUIElement::fillBuffer
 		 */
 		virtual void _fillBuffer(UINT8* vertices, UINT8* uv, UINT32* indices, UINT32 startingQuad, 
-			UINT32 maxNumQuads, UINT32 vertexStride, UINT32 indexStride, UINT32 renderElementIdx) const;
+			UINT32 maxNumQuads, UINT32 vertexStride, UINT32 indexStride, UINT32 renderElementIdx) const override;
 
 		/**
 		 * @copydoc GUIElement::updateRenderElementsInternal
 		 */
-		virtual void updateRenderElementsInternal();
+		virtual void updateRenderElementsInternal() override;
 
 		/**
 		 * @copydoc GUIElement::updateBounds
 		 */
-		virtual void updateClippedBounds();
+		virtual void updateClippedBounds() override;
 	private:
 		GUILabel(const String& styleName, const GUIContent& content, const GUIDimensions& dimensions);
 
 		TextSprite* mTextSprite;
 		GUIContent mContent;
-		HEvent mLocStringUpdatedConn;
 		Color mColor;
 
 		TEXT_SPRITE_DESC mDesc;

+ 48 - 0
BansheeEngine/Include/BsHEString.h

@@ -0,0 +1,48 @@
+#pragma once
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	Helper class used for constructing HString%s that references the engine string table.
+	 *			Engine string table is just a separate string table so it doesn't conflict with
+	 *			the game string table.
+	 */
+	class BS_EXPORT HEString
+	{
+	public:
+		/**
+		 * @brief	Creates a new localized string with the specified identifier in the engine string table. 
+		 *			If the identifier doesn't previously exist in the string table, identifier value will also 
+		 *			be used for initializing the default language version of the string.
+		 *
+		 * @param	identifier		String you can use for later referencing the localized string.
+		 */
+		HEString(const WString& identifier);
+
+		/**
+		 * @brief	Creates a new localized string with the specified identifier in the engine string table and sets the 
+		 *			default language version of the string. If a string with that identifier already exists default language 
+		 *			string will be updated.
+		 *
+		 * @param	identifier		String you can use for later referencing the localized string.
+		 * @param	default			Default string to assign to the specified identifier. Language to which it
+		 *							will be assigned depends on the StringTable::DEFAULT_LANGUAGE value.
+		 */
+		HEString(const WString& identifier, const WString& default);
+
+		/**
+		 * @brief	Creates a new empty localized string in the engine string table.
+		 */
+		HEString();
+
+		/**
+		 * @brief	Implicitly casts the editor string type to a generic string type.
+		 */
+		operator HString() const;
+
+	private:
+		static const UINT32 ENGINE_STRING_TABLE_ID;
+
+		HString mInternal;
+	};
+}

+ 1 - 0
BansheeEngine/Include/BsPrerequisites.h

@@ -20,6 +20,7 @@
 
 #include "BsGameObject.h"
 #include "BsEnums.h"
+#include "BsHEString.h"
 
 namespace BansheeEngine
 {

+ 20 - 3
BansheeEngine/Include/BsProfilerOverlay.h

@@ -82,7 +82,15 @@ namespace BansheeEngine
 			GUILayout* contentLayout;
 			GUIFixedSpace* labelSpace;
 
-			Vector<GUIElement*> elements;
+			GUILabel* guiName;
+			GUILabel* guiPctOfParent;
+			GUILabel* guiNumCalls;
+			GUILabel* guiNumAllocs;
+			GUILabel* guiNumFrees;
+			GUILabel* guiAvgTime;
+			GUILabel* guiTotalTime;
+			GUILabel* guiAvgTimeSelf;
+			GUILabel* guiTotalTimeSelf;
 
 			HString name;
 			HString pctOfParent;
@@ -106,7 +114,15 @@ namespace BansheeEngine
 			GUILayout* contentLayout;
 			GUIFixedSpace* labelSpace;
 
-			Vector<GUIElement*> elements;
+			GUILabel* guiName;
+			GUILabel* guiPctOfParent;
+			GUILabel* guiNumCalls;
+			GUILabel* guiNumAllocs;
+			GUILabel* guiNumFrees;
+			GUILabel* guiAvgCycles;
+			GUILabel* guiTotalCycles;
+			GUILabel* guiAvgCyclesSelf;
+			GUILabel* guiTotalCyclesSelf;
 
 			HString name;
 			HString pctOfParent;
@@ -128,7 +144,8 @@ namespace BansheeEngine
 		{
 			GUILayout* layout;
 
-			Vector<GUIElement*> elements;
+			GUILabel* guiName;
+			GUILabel* guiTime;
 
 			HString name;
 			HString time;

+ 0 - 7
BansheeEngine/Source/BsGUIButtonBase.cpp

@@ -20,14 +20,10 @@ namespace BansheeEngine
 		HSpriteTexture contentTex = content.getImage();
 		if(SpriteTexture::checkIsLoaded(contentTex))
 			mContentImageSprite = bs_new<ImageSprite>();
-
-		mLocStringUpdatedConn = mContent.getText().addOnStringModifiedCallback(std::bind(&GUIButtonBase::_markLayoutAsDirty, this));
 	}
 
 	GUIButtonBase::~GUIButtonBase()
 	{
-		mLocStringUpdatedConn.disconnect();
-
 		bs_delete(mTextSprite);
 		bs_delete(mImageSprite);
 
@@ -37,9 +33,6 @@ namespace BansheeEngine
 
 	void GUIButtonBase::setContent(const GUIContent& content)
 	{
-		mLocStringUpdatedConn.disconnect();
-		mLocStringUpdatedConn = content.getText().addOnStringModifiedCallback(std::bind(&GUIButtonBase::_markLayoutAsDirty, this));
-
 		Vector2I origSize = mDimensions.calculateSizeRange(_getOptimalSize()).optimal;
 		mContent = content;
 

+ 1 - 1
BansheeEngine/Source/BsGUIHelper.cpp

@@ -16,7 +16,7 @@ namespace BansheeEngine
 
 	Vector2I GUIHelper::calcOptimalContentsSize(const GUIContent& content, const GUIElementStyle& style, const GUIDimensions& dimensions)
 	{
-		Vector2I textContentBounds = calcOptimalContentsSize(content.getText(), style, dimensions);
+		Vector2I textContentBounds = calcOptimalContentsSize((const WString&)content.getText(), style, dimensions);
 
 		UINT32 contentWidth = style.margins.left + style.margins.right + style.contentOffset.left + style.contentOffset.right;
 		UINT32 contentHeight = style.margins.top + style.margins.bottom + style.contentOffset.top + style.contentOffset.bottom;

+ 2 - 8
BansheeEngine/Source/BsGUILabel.cpp

@@ -12,15 +12,12 @@ namespace BansheeEngine
 	GUILabel::GUILabel(const String& styleName, const GUIContent& content, const GUIDimensions& dimensions)
 		:GUIElement(styleName, dimensions), mContent(content)
 	{
-		mTextSprite = bs_new<TextSprite, PoolAlloc>();
-
-		mLocStringUpdatedConn = mContent.getText().addOnStringModifiedCallback(std::bind(&GUILabel::_markLayoutAsDirty, this));
+		mTextSprite = bs_new<TextSprite>();
 	}
 
 	GUILabel::~GUILabel()
 	{
-		mLocStringUpdatedConn.disconnect();
-		bs_delete<PoolAlloc>(mTextSprite);
+		bs_delete(mTextSprite);
 	}
 
 	UINT32 GUILabel::_getNumRenderElements() const
@@ -77,9 +74,6 @@ namespace BansheeEngine
 
 	void GUILabel::setContent(const GUIContent& content)
 	{
-		mLocStringUpdatedConn.disconnect();
-		mLocStringUpdatedConn = content.getText().addOnStringModifiedCallback(std::bind(&GUILabel::_markLayoutAsDirty, this));
-
 		Vector2I origSize = mDimensions.calculateSizeRange(_getOptimalSize()).optimal;
 		mContent = content;
 		Vector2I newSize = mDimensions.calculateSizeRange(_getOptimalSize()).optimal;

+ 1 - 11
BansheeEngine/Source/BsGUIManager.cpp

@@ -357,8 +357,6 @@ namespace BansheeEngine
 			bool isDirty = renderData.isDirty;
 			renderData.isDirty = false;
 
-			gProfilerCPU().beginSample("updateRenderElements");
-
 			for(auto& widget : renderData.widgets)
 			{
 				if (widget->isDirty(true))
@@ -367,8 +365,6 @@ namespace BansheeEngine
 				}
 			}
 
-			gProfilerCPU().endSample("updateRenderElements");
-
 			if(!isDirty)
 				continue;
 
@@ -387,8 +383,6 @@ namespace BansheeEngine
 						(aDepth == bDepth && a.element == b.element && a.renderElement > b.renderElement); 
 				};
 
-				gProfilerCPU().beginSample("generateMatGroups");
-
 				FrameSet<GUIGroupElement, std::function<bool(const GUIGroupElement&, const GUIGroupElement&)>> allElements(elemComp);
 
 				for (auto& widget : renderData.widgets)
@@ -436,6 +430,7 @@ namespace BansheeEngine
 					//    overlap the current elements bounds.
 					FrameVector<GUIMaterialGroup>& allGroups = materialGroups[materialId];
 					GUIMaterialGroup* foundGroup = nullptr;
+
 					for (auto groupIter = allGroups.rbegin(); groupIter != allGroups.rend(); ++groupIter)
 					{
 						// If we separate meshes by widget, ignore any groups with widget parents other than mine
@@ -528,9 +523,6 @@ namespace BansheeEngine
 					}
 				}
 
-				gProfilerCPU().endSample("generateMatGroups");
-				gProfilerCPU().beginSample("updateMeshes");
-
 				UINT32 numMeshes = (UINT32)sortedGroups.size();
 				UINT32 oldNumMeshes = (UINT32)renderData.cachedMeshes.size();
 
@@ -600,8 +592,6 @@ namespace BansheeEngine
 
 					groupIdx++;
 				}
-
-				gProfilerCPU().endSample("updateMeshes");
 			}
 
 			bs_frame_clear();			

+ 30 - 0
BansheeEngine/Source/BsHEString.cpp

@@ -0,0 +1,30 @@
+#include "BsPrerequisites.h"
+#include "BsHEString.h"
+
+namespace BansheeEngine
+{
+	const UINT32 HEString::ENGINE_STRING_TABLE_ID = 30000; // Arbitrary
+
+	HEString::HEString(const WString& identifier)
+		:mInternal(identifier, ENGINE_STRING_TABLE_ID)
+	{
+		
+	}
+
+	HEString::HEString(const WString& identifier, const WString& default)
+		: mInternal(identifier, default, ENGINE_STRING_TABLE_ID)
+	{
+		
+	}
+
+	HEString::HEString()
+		: mInternal(ENGINE_STRING_TABLE_ID)
+	{
+		
+	}
+
+	HEString::operator HString() const
+	{
+		return mInternal;
+	}
+}

+ 108 - 130
BansheeEngine/Source/BsProfilerOverlay.cpp

@@ -14,6 +14,7 @@
 #include "BsProfilingManager.h"
 #include "BsRenderTarget.h"
 #include "BsProfilerOverlayRTTI.h"
+#include <BsHEString.h>
 
 namespace BansheeEngine
 {
@@ -58,50 +59,30 @@ namespace BansheeEngine
 				ProfilerOverlayInternal::BasicRow& newRow = rows.back();
 
 				newRow.disabled = false;
-				newRow.name = HString(L"{0}");
-				newRow.pctOfParent = HString(L"{0} %");
-				newRow.numCalls = HString(L"{0}");
-				newRow.numAllocs = HString(L"{0}");
-				newRow.numFrees = HString(L"{0}");
-				newRow.avgTime = HString(L"{0}");
-				newRow.totalTime = HString(L"{0}");
-				newRow.avgTimeSelf = HString(L"{0}");
-				newRow.totalTimeSelf = HString(L"{0}");
+				newRow.name = HEString(L"{0}");
+				newRow.pctOfParent = HEString(L"{0} %");
+				newRow.numCalls = HEString(L"{0}");
+				newRow.numAllocs = HEString(L"{0}");
+				newRow.numFrees = HEString(L"{0}");
+				newRow.avgTime = HEString(L"{0}");
+				newRow.totalTime = HEString(L"{0}");
+				newRow.avgTimeSelf = HEString(L"{0}");
+				newRow.totalTimeSelf = HEString(L"{0}");
 
 				newRow.labelLayout = labelLayout.insertNewElement<GUILayoutX>(labelLayout.getNumChildren() - 1); // Insert before flexible space
 				newRow.contentLayout = contentLayout.insertNewElement<GUILayoutX>(contentLayout.getNumChildren() - 1); // Insert before flexible space
 
-				GUILabel* name = GUILabel::create(newRow.name, GUIOptions(GUIOption::fixedWidth(200)));
-				GUILabel* pctOfParent = GUILabel::create(newRow.pctOfParent, GUIOptions(GUIOption::fixedWidth(50)));
-				GUILabel* numCalls = GUILabel::create(newRow.numCalls, GUIOptions(GUIOption::fixedWidth(50)));
-				GUILabel* numAllocs = GUILabel::create(newRow.numAllocs, GUIOptions(GUIOption::fixedWidth(50)));
-				GUILabel* numFrees = GUILabel::create(newRow.numFrees, GUIOptions(GUIOption::fixedWidth(50)));
-				GUILabel* avgTime = GUILabel::create(newRow.avgTime, GUIOptions(GUIOption::fixedWidth(60)));
-				GUILabel* totalTime = GUILabel::create(newRow.totalTime, GUIOptions(GUIOption::fixedWidth(60)));
-				GUILabel* avgTimeSelf = GUILabel::create(newRow.avgTimeSelf, GUIOptions(GUIOption::fixedWidth(100)));
-				GUILabel* totalTimeSelf = GUILabel::create(newRow.totalTimeSelf, GUIOptions(GUIOption::fixedWidth(100)));
-
 				newRow.labelSpace = newRow.labelLayout->addNewElement<GUIFixedSpace>(0);
-				newRow.labelLayout->addElement(name);
-
-				newRow.contentLayout->addElement(pctOfParent);
-				newRow.contentLayout->addElement(numCalls);
-				newRow.contentLayout->addElement(numAllocs);
-				newRow.contentLayout->addElement(numFrees);
-				newRow.contentLayout->addElement(avgTime);
-				newRow.contentLayout->addElement(totalTime);
-				newRow.contentLayout->addElement(avgTimeSelf);
-				newRow.contentLayout->addElement(totalTimeSelf);
-
-				newRow.elements.push_back(name);
-				newRow.elements.push_back(pctOfParent);
-				newRow.elements.push_back(numCalls);
-				newRow.elements.push_back(numAllocs);
-				newRow.elements.push_back(numFrees);
-				newRow.elements.push_back(avgTime);
-				newRow.elements.push_back(totalTime);
-				newRow.elements.push_back(avgTimeSelf);
-				newRow.elements.push_back(totalTimeSelf);
+				newRow.guiName = newRow.labelLayout->addNewElement<GUILabel>(newRow.name, GUIOptions(GUIOption::fixedWidth(200)));
+
+				newRow.guiPctOfParent = newRow.contentLayout->addNewElement<GUILabel>(newRow.pctOfParent, GUIOptions(GUIOption::fixedWidth(50)));
+				newRow.guiNumCalls = newRow.contentLayout->addNewElement<GUILabel>(newRow.numCalls, GUIOptions(GUIOption::fixedWidth(50)));
+				newRow.guiNumAllocs = newRow.contentLayout->addNewElement<GUILabel>(newRow.numAllocs, GUIOptions(GUIOption::fixedWidth(50)));
+				newRow.guiNumFrees = newRow.contentLayout->addNewElement<GUILabel>(newRow.numFrees, GUIOptions(GUIOption::fixedWidth(50)));
+				newRow.guiAvgTime = newRow.contentLayout->addNewElement<GUILabel>(newRow.avgTime, GUIOptions(GUIOption::fixedWidth(60)));
+				newRow.guiTotalTime = newRow.contentLayout->addNewElement<GUILabel>(newRow.totalTime, GUIOptions(GUIOption::fixedWidth(60)));
+				newRow.guiAvgTimeSelf = newRow.contentLayout->addNewElement<GUILabel>(newRow.avgTimeSelf, GUIOptions(GUIOption::fixedWidth(100)));
+				newRow.guiTotalTimeSelf = newRow.contentLayout->addNewElement<GUILabel>(newRow.totalTimeSelf, GUIOptions(GUIOption::fixedWidth(100)));
 			}
 			
 			ProfilerOverlayInternal::BasicRow& row = rows[curIdx];
@@ -117,6 +98,16 @@ namespace BansheeEngine
 			row.avgTimeSelf.setParameter(0, toWString(avgSelfTime, 2, 0, ' ', std::ios::fixed));
 			row.totalTimeSelf.setParameter(0, toWString(totalSelfTime, 2, 0, ' ', std::ios::fixed));
 
+			row.guiName->setContent(row.name);
+			row.guiPctOfParent->setContent(row.pctOfParent);
+			row.guiNumCalls->setContent(row.numCalls);
+			row.guiNumAllocs->setContent(row.numAllocs);
+			row.guiNumFrees->setContent(row.numFrees);
+			row.guiAvgTime->setContent(row.avgTime);
+			row.guiTotalTime->setContent(row.totalTime);
+			row.guiAvgTimeSelf->setContent(row.avgTimeSelf);
+			row.guiTotalTimeSelf->setContent(row.totalTimeSelf);
+
 			if (row.disabled)
 			{
 				row.labelLayout->enableRecursively();
@@ -169,50 +160,30 @@ namespace BansheeEngine
 				ProfilerOverlayInternal::PreciseRow& newRow = rows.back();
 
 				newRow.disabled = false;
-				newRow.name = HString(L"{0}");
-				newRow.pctOfParent = HString(L"{0}");
-				newRow.numCalls = HString(L"{0}");
-				newRow.numAllocs = HString(L"{0}");
-				newRow.numFrees = HString(L"{0}");
-				newRow.avgCycles = HString(L"{0}");
-				newRow.totalCycles = HString(L"{0}");
-				newRow.avgCyclesSelf = HString(L"{0}");
-				newRow.totalCyclesSelf = HString(L"{0}");
+				newRow.name = HEString(L"{0}");
+				newRow.pctOfParent = HEString(L"{0}");
+				newRow.numCalls = HEString(L"{0}");
+				newRow.numAllocs = HEString(L"{0}");
+				newRow.numFrees = HEString(L"{0}");
+				newRow.avgCycles = HEString(L"{0}");
+				newRow.totalCycles = HEString(L"{0}");
+				newRow.avgCyclesSelf = HEString(L"{0}");
+				newRow.totalCyclesSelf = HEString(L"{0}");
 
 				newRow.labelLayout = labelLayout.insertNewElement<GUILayoutX>(labelLayout.getNumChildren() - 1); // Insert before flexible space
 				newRow.contentLayout = contentLayout.insertNewElement<GUILayoutX>(contentLayout.getNumChildren() - 1); // Insert before flexible space
 
-				GUILabel* name = GUILabel::create(newRow.name, GUIOptions(GUIOption::fixedWidth(200)));
-				GUILabel* pctOfParent = GUILabel::create(newRow.pctOfParent, GUIOptions(GUIOption::fixedWidth(50)));
-				GUILabel* numCalls = GUILabel::create(newRow.numCalls, GUIOptions(GUIOption::fixedWidth(50)));
-				GUILabel* numAllocs = GUILabel::create(newRow.numAllocs, GUIOptions(GUIOption::fixedWidth(50)));
-				GUILabel* numFrees = GUILabel::create(newRow.numFrees, GUIOptions(GUIOption::fixedWidth(50)));
-				GUILabel* avgCycles = GUILabel::create(newRow.avgCycles,GUIOptions(GUIOption::fixedWidth(60)));
-				GUILabel* totalCycles = GUILabel::create(newRow.totalCycles, GUIOptions(GUIOption::fixedWidth(60)));
-				GUILabel* avgCyclesSelf = GUILabel::create(newRow.avgCyclesSelf, GUIOptions(GUIOption::fixedWidth(100)));
-				GUILabel* totalCyclesSelf = GUILabel::create(newRow.totalCyclesSelf, GUIOptions(GUIOption::fixedWidth(100)));
-
 				newRow.labelSpace = newRow.labelLayout->addNewElement<GUIFixedSpace>(0);
-				newRow.labelLayout->addElement(name);
-
-				newRow.contentLayout->addElement(pctOfParent);
-				newRow.contentLayout->addElement(numCalls);
-				newRow.contentLayout->addElement(numAllocs);
-				newRow.contentLayout->addElement(numFrees);
-				newRow.contentLayout->addElement(avgCycles);
-				newRow.contentLayout->addElement(totalCycles);
-				newRow.contentLayout->addElement(avgCyclesSelf);
-				newRow.contentLayout->addElement(totalCyclesSelf);
-
-				newRow.elements.push_back(name);
-				newRow.elements.push_back(pctOfParent);
-				newRow.elements.push_back(numCalls);
-				newRow.elements.push_back(numAllocs);
-				newRow.elements.push_back(numFrees);
-				newRow.elements.push_back(avgCycles);
-				newRow.elements.push_back(totalCycles);
-				newRow.elements.push_back(avgCyclesSelf);
-				newRow.elements.push_back(totalCyclesSelf);
+				newRow.guiName = newRow.labelLayout->addNewElement<GUILabel>(newRow.name, GUIOptions(GUIOption::fixedWidth(200)));
+
+				newRow.guiPctOfParent = newRow.contentLayout->addNewElement<GUILabel>(newRow.pctOfParent, GUIOptions(GUIOption::fixedWidth(50)));
+				newRow.guiNumCalls = newRow.contentLayout->addNewElement<GUILabel>(newRow.numCalls, GUIOptions(GUIOption::fixedWidth(50)));
+				newRow.guiNumAllocs = newRow.contentLayout->addNewElement<GUILabel>(newRow.numAllocs, GUIOptions(GUIOption::fixedWidth(50)));
+				newRow.guiNumFrees = newRow.contentLayout->addNewElement<GUILabel>(newRow.numFrees, GUIOptions(GUIOption::fixedWidth(50)));
+				newRow.guiAvgCycles = newRow.contentLayout->addNewElement<GUILabel>(newRow.avgCycles, GUIOptions(GUIOption::fixedWidth(60)));
+				newRow.guiTotalCycles = newRow.contentLayout->addNewElement<GUILabel>(newRow.totalCycles, GUIOptions(GUIOption::fixedWidth(60)));
+				newRow.guiAvgCyclesSelf = newRow.contentLayout->addNewElement<GUILabel>(newRow.avgCyclesSelf, GUIOptions(GUIOption::fixedWidth(100)));
+				newRow.guiTotalCyclesSelf = newRow.contentLayout->addNewElement<GUILabel>(newRow.totalCyclesSelf, GUIOptions(GUIOption::fixedWidth(100)));
 			}
 
 			ProfilerOverlayInternal::PreciseRow& row = rows[curIdx];
@@ -228,6 +199,16 @@ namespace BansheeEngine
 			row.avgCyclesSelf.setParameter(0, toWString(avgSelfCycles));
 			row.totalCyclesSelf.setParameter(0, toWString(totalSelfCycles));
 
+			row.guiName->setContent(row.name);
+			row.guiPctOfParent->setContent(row.pctOfParent);
+			row.guiNumCalls->setContent(row.numCalls);
+			row.guiNumAllocs->setContent(row.numAllocs);
+			row.guiNumFrees->setContent(row.numFrees);
+			row.guiAvgCycles->setContent(row.avgCycles);
+			row.guiTotalCycles->setContent(row.totalCycles);
+			row.guiAvgCyclesSelf->setContent(row.avgCyclesSelf);
+			row.guiTotalCyclesSelf->setContent(row.totalCyclesSelf);
+
 			if (row.disabled)
 			{
 				row.labelLayout->enableRecursively();
@@ -277,25 +258,22 @@ namespace BansheeEngine
 				ProfilerOverlayInternal::GPUSampleRow& newRow = rows.back();
 
 				newRow.disabled = false;
-				newRow.name = HString(L"{1}");
-				newRow.time = HString(L"{0}");
+				newRow.name = HEString(L"{1}");
+				newRow.time = HEString(L"{0}");
 
 				newRow.layout = layout.insertNewElement<GUILayoutX>(layout.getNumChildren());
 
-				GUILabel* nameLabel = GUILabel::create(newRow.name, GUIOptions(GUIOption::fixedWidth(100)));
-				GUILabel* timeLabel = GUILabel::create(newRow.time, GUIOptions(GUIOption::fixedWidth(100)));
-
-				newRow.layout->addElement(nameLabel);
-				newRow.layout->addElement(timeLabel);
-
-				newRow.elements.push_back(nameLabel);
-				newRow.elements.push_back(timeLabel);
+				newRow.guiName = newRow.layout->addNewElement<GUILabel>(newRow.name, GUIOptions(GUIOption::fixedWidth(100)));
+				newRow.guiTime = newRow.layout->addNewElement<GUILabel>(newRow.time, GUIOptions(GUIOption::fixedWidth(100)));
 			}
 
 			ProfilerOverlayInternal::GPUSampleRow& row = rows[curIdx];
 			row.name.setParameter(0, toWString(name));
 			row.time.setParameter(0, toWString(timeMs));
 
+			row.guiName->setContent(row.name);
+			row.guiTime->setContent(row.time);
+
 			if (row.disabled)
 			{
 				row.layout->enableRecursively();
@@ -388,25 +366,25 @@ namespace BansheeEngine
 		mPreciseLayoutContents = mWidget->getPanel()->addNewElement<GUILayoutY>();
 
 		// Set up CPU sample title bars
-		mTitleBasicName = GUILabel::create(HString(L"Name"), GUIOptions(GUIOption::fixedWidth(200)));
-		mTitleBasicPctOfParent = GUILabel::create(HString(L"% parent"), GUIOptions(GUIOption::fixedWidth(50)));
-		mTitleBasicNumCalls = GUILabel::create(HString(L"# calls"), GUIOptions(GUIOption::fixedWidth(50)));
-		mTitleBasicNumAllocs = GUILabel::create(HString(L"# allocs"), GUIOptions(GUIOption::fixedWidth(50)));
-		mTitleBasicNumFrees = GUILabel::create(HString(L"# frees"), GUIOptions(GUIOption::fixedWidth(50)));
-		mTitleBasicAvgTime = GUILabel::create(HString(L"Avg. time"), GUIOptions(GUIOption::fixedWidth(60)));
-		mTitleBasicTotalTime = GUILabel::create(HString(L"Total time"), GUIOptions(GUIOption::fixedWidth(60)));
-		mTitleBasicAvgTitleSelf = GUILabel::create(HString(L"Avg. self time"), GUIOptions(GUIOption::fixedWidth(100)));
-		mTitleBasicTotalTimeSelf = GUILabel::create(HString(L"Total self time"), GUIOptions(GUIOption::fixedWidth(100)));
-
-		mTitlePreciseName = GUILabel::create(HString(L"Name"), GUIOptions(GUIOption::fixedWidth(200)));
-		mTitlePrecisePctOfParent = GUILabel::create(HString(L"% parent"), GUIOptions(GUIOption::fixedWidth(50)));
-		mTitlePreciseNumCalls = GUILabel::create(HString(L"# calls"), GUIOptions(GUIOption::fixedWidth(50)));
-		mTitlePreciseNumAllocs = GUILabel::create(HString(L"# allocs"), GUIOptions(GUIOption::fixedWidth(50)));
-		mTitlePreciseNumFrees = GUILabel::create(HString(L"# frees"), GUIOptions(GUIOption::fixedWidth(50)));
-		mTitlePreciseAvgCycles = GUILabel::create(HString(L"Avg. cycles"), GUIOptions(GUIOption::fixedWidth(60)));
-		mTitlePreciseTotalCycles = GUILabel::create(HString(L"Total cycles"), GUIOptions(GUIOption::fixedWidth(60)));
-		mTitlePreciseAvgCyclesSelf = GUILabel::create(HString(L"Avg. self cycles"), GUIOptions(GUIOption::fixedWidth(100)));
-		mTitlePreciseTotalCyclesSelf = GUILabel::create(HString(L"Total self cycles"), GUIOptions(GUIOption::fixedWidth(100)));
+		mTitleBasicName = GUILabel::create(HEString(L"Name"), GUIOptions(GUIOption::fixedWidth(200)));
+		mTitleBasicPctOfParent = GUILabel::create(HEString(L"% parent"), GUIOptions(GUIOption::fixedWidth(50)));
+		mTitleBasicNumCalls = GUILabel::create(HEString(L"# calls"), GUIOptions(GUIOption::fixedWidth(50)));
+		mTitleBasicNumAllocs = GUILabel::create(HEString(L"# allocs"), GUIOptions(GUIOption::fixedWidth(50)));
+		mTitleBasicNumFrees = GUILabel::create(HEString(L"# frees"), GUIOptions(GUIOption::fixedWidth(50)));
+		mTitleBasicAvgTime = GUILabel::create(HEString(L"Avg. time"), GUIOptions(GUIOption::fixedWidth(60)));
+		mTitleBasicTotalTime = GUILabel::create(HEString(L"Total time"), GUIOptions(GUIOption::fixedWidth(60)));
+		mTitleBasicAvgTitleSelf = GUILabel::create(HEString(L"Avg. self time"), GUIOptions(GUIOption::fixedWidth(100)));
+		mTitleBasicTotalTimeSelf = GUILabel::create(HEString(L"Total self time"), GUIOptions(GUIOption::fixedWidth(100)));
+
+		mTitlePreciseName = GUILabel::create(HEString(L"Name"), GUIOptions(GUIOption::fixedWidth(200)));
+		mTitlePrecisePctOfParent = GUILabel::create(HEString(L"% parent"), GUIOptions(GUIOption::fixedWidth(50)));
+		mTitlePreciseNumCalls = GUILabel::create(HEString(L"# calls"), GUIOptions(GUIOption::fixedWidth(50)));
+		mTitlePreciseNumAllocs = GUILabel::create(HEString(L"# allocs"), GUIOptions(GUIOption::fixedWidth(50)));
+		mTitlePreciseNumFrees = GUILabel::create(HEString(L"# frees"), GUIOptions(GUIOption::fixedWidth(50)));
+		mTitlePreciseAvgCycles = GUILabel::create(HEString(L"Avg. cycles"), GUIOptions(GUIOption::fixedWidth(60)));
+		mTitlePreciseTotalCycles = GUILabel::create(HEString(L"Total cycles"), GUIOptions(GUIOption::fixedWidth(60)));
+		mTitlePreciseAvgCyclesSelf = GUILabel::create(HEString(L"Avg. self cycles"), GUIOptions(GUIOption::fixedWidth(100)));
+		mTitlePreciseTotalCyclesSelf = GUILabel::create(HEString(L"Total self cycles"), GUIOptions(GUIOption::fixedWidth(100)));
 
 		GUILayout* basicTitleLabelLayout = mBasicLayoutLabels->addNewElement<GUILayoutX>();
 		GUILayout* preciseTitleLabelLayout = mPreciseLayoutLabels->addNewElement<GUILayoutX>();
@@ -460,29 +438,29 @@ namespace BansheeEngine
 		gpuSampleTitleRow->addElement(GUILabel::create(gpuSamplesNameStr, GUIOptions(GUIOption::fixedWidth(100))));
 		gpuSampleTitleRow->addElement(GUILabel::create(gpuSamplesTimeStr, GUIOptions(GUIOption::fixedWidth(100))));
 
-		mGPUFrameNumStr = HString(L"__ProfOvFrame", L"Frame #{0}");
-		mGPUTimeStr = HString(L"__ProfOvTime", L"Time: {0}ms");
-		mGPUDrawCallsStr = HString(L"__ProfOvDrawCalls", L"Draw calls: {0}");
-		mGPURenTargetChangesStr = HString(L"__ProfOvRTChanges", L"Render target changes: {0}");
-		mGPUPresentsStr = HString(L"__ProfOvPresents", L"Presents: {0}");
-		mGPUClearsStr = HString(L"__ProfOvClears", L"Clears: {0}");
-		mGPUVerticesStr = HString(L"__ProfOvVertices", L"Num. vertices: {0}");
-		mGPUPrimitivesStr = HString(L"__ProfOvPrimitives", L"Num. primitives: {0}");
-		mGPUSamplesStr = HString(L"__ProfOvSamples", L"Samples drawn: {0}");
-		mGPUBlendStateChangesStr = HString(L"__ProfOvBSChanges", L"Blend state changes: {0}");
-		mGPURasterStateChangesStr = HString(L"__ProfOvRSChanges", L"Rasterizer state changes: {0}");
-		mGPUDepthStencilStateChangesStr = HString(L"__ProfOvDSSChanges", L"Depth/stencil state changes: {0}");
-
-		mGPUObjectsCreatedStr = HString(L"__ProfOvObjsCreated", L"Objects created: {0}");
-		mGPUObjectsDestroyedStr = HString(L"__ProfOvObjsDestroyed", L"Objects destroyed: {0}");
-		mGPUResourceWritesStr = HString(L"__ProfOvResWrites", L"Resource writes: {0}");
-		mGPUResourceReadsStr = HString(L"__ProfOvResReads", L"Resource reads: {0}");
-		mGPUTextureBindsStr = HString(L"__ProfOvTexBinds", L"Texture binds: {0}");
-		mGPUSamplerBindsStr = HString(L"__ProfOvSampBinds", L"Sampler binds: {0}");
-		mGPUVertexBufferBindsStr = HString(L"__ProfOvVBBinds", L"VB binds: {0}");
-		mGPUIndexBufferBindsStr = HString(L"__ProfOvIBBinds", L"IB binds: {0}");
-		mGPUGPUProgramBufferBindsStr = HString(L"__ProfOvProgBuffBinds", L"GPU program buffer binds: {0}");
-		mGPUGPUProgramBindsStr = HString(L"__ProfOvProgBinds", L"GPU program binds: {0}");
+		mGPUFrameNumStr = HEString(L"__ProfOvFrame", L"Frame #{0}");
+		mGPUTimeStr = HEString(L"__ProfOvTime", L"Time: {0}ms");
+		mGPUDrawCallsStr = HEString(L"__ProfOvDrawCalls", L"Draw calls: {0}");
+		mGPURenTargetChangesStr = HEString(L"__ProfOvRTChanges", L"Render target changes: {0}");
+		mGPUPresentsStr = HEString(L"__ProfOvPresents", L"Presents: {0}");
+		mGPUClearsStr = HEString(L"__ProfOvClears", L"Clears: {0}");
+		mGPUVerticesStr = HEString(L"__ProfOvVertices", L"Num. vertices: {0}");
+		mGPUPrimitivesStr = HEString(L"__ProfOvPrimitives", L"Num. primitives: {0}");
+		mGPUSamplesStr = HEString(L"__ProfOvSamples", L"Samples drawn: {0}");
+		mGPUBlendStateChangesStr = HEString(L"__ProfOvBSChanges", L"Blend state changes: {0}");
+		mGPURasterStateChangesStr = HEString(L"__ProfOvRSChanges", L"Rasterizer state changes: {0}");
+		mGPUDepthStencilStateChangesStr = HEString(L"__ProfOvDSSChanges", L"Depth/stencil state changes: {0}");
+
+		mGPUObjectsCreatedStr = HEString(L"__ProfOvObjsCreated", L"Objects created: {0}");
+		mGPUObjectsDestroyedStr = HEString(L"__ProfOvObjsDestroyed", L"Objects destroyed: {0}");
+		mGPUResourceWritesStr = HEString(L"__ProfOvResWrites", L"Resource writes: {0}");
+		mGPUResourceReadsStr = HEString(L"__ProfOvResReads", L"Resource reads: {0}");
+		mGPUTextureBindsStr = HEString(L"__ProfOvTexBinds", L"Texture binds: {0}");
+		mGPUSamplerBindsStr = HEString(L"__ProfOvSampBinds", L"Sampler binds: {0}");
+		mGPUVertexBufferBindsStr = HEString(L"__ProfOvVBBinds", L"VB binds: {0}");
+		mGPUIndexBufferBindsStr = HEString(L"__ProfOvIBBinds", L"IB binds: {0}");
+		mGPUGPUProgramBufferBindsStr = HEString(L"__ProfOvProgBuffBinds", L"GPU program buffer binds: {0}");
+		mGPUGPUProgramBindsStr = HEString(L"__ProfOvProgBinds", L"GPU program binds: {0}");
 
 		mGPULayoutFrameContentsLeft->addElement(GUILabel::create(mGPUFrameNumStr, GUIOptions(GUIOption::fixedWidth(200))));
 		mGPULayoutFrameContentsLeft->addElement(GUILabel::create(mGPUTimeStr, GUIOptions(GUIOption::fixedWidth(200))));

+ 27 - 35
BansheeUtility/Include/BsHString.h

@@ -17,45 +17,38 @@ namespace BansheeEngine
 	class BS_UTILITY_EXPORT HString
 	{
 	public:
-		class BS_UTILITY_EXPORT StringData
-		{
-		public:
-			StringData();
-			~StringData();
-
-			mutable Event<void()> onStringModified;
-
-		private:
-			friend class HString;
-
-			LocalizedStringData* mStringData;
-			WString* mParameters;
-			HEvent mUpdateConn;
-
-			mutable bool mIsDirty;
-			mutable WString mCachedString;
-			mutable WString* mStringPtr;
-
-			void updateString();
-		};
-
 		/**
 		 * @brief	Creates a new localized string with the specified identifier. If the identifier
 		 * 			doesn't previously exist in the string table, identifier value will also be used 
 		 * 			for initializing the default language version of the string.
+		 *
+		 * @param	identifier		String you can use for later referencing the localized string.
+		 * @param	stringTableId	Unique identifier of the string table to retrieve the string from.
 		 */
-		explicit HString(const WString& identifierString);
+		explicit HString(const WString& identifier, UINT32 stringTableId = 0);
 
 		/**
-		* @brief	Creates a new localized string with the specified identifier and sets the default language version
-		*			of the string. If a string with that identifier already exists default language string will be updated.
-		*/
-		explicit HString(const WString& identifierString, const WString& englishString);
+		 * @brief	Creates a new localized string with the specified identifier and sets the default language version
+		 *			of the string. If a string with that identifier already exists default language string will be updated.
+		 *
+		 * @param	identifier		String you can use for later referencing the localized string.
+		 * @param	default			Default string to assign to the specified identifier. Language to which it
+		 *							will be assigned depends on the StringTable::DEFAULT_LANGUAGE value.
+		 * @param	stringTableId	Unique identifier of the string table to retrieve the string from.
+		 */
+		explicit HString(const WString& identifier, const WString& default, UINT32 stringTableId = 0);
 
-		HString();
+		/**
+		 * @brief	Creates a new empty localized string.
+		 *
+		 * @param	stringTableId	Unique identifier of the string table to retrieve the string from.
+		 */
+		HString(UINT32 stringTableId = 0);
 		HString(const HString& copy);
 		~HString();
 
+		HString& operator=(const HString& rhs);
+
 		operator const WString& () const;
 		const WString& getValue() const;
 
@@ -67,17 +60,16 @@ namespace BansheeEngine
 		 */
 		void setParameter(UINT32 idx, const WString& value);
 		
-		/**
-		 * @brief	Registers a callback that gets triggered whenever string value changes. This may happen
-		 * 			when the string table is modified, or when the active language is changed.
-		 */
-		HEvent addOnStringModifiedCallback(std::function<void()> callback) const;
-
 		/**
 		 * @brief	Returns an empty string.
 		 */
 		static const HString& dummy();
 	private:
-		std::shared_ptr<StringData> mData;
+		SPtr<LocalizedStringData> mStringData;
+		WString* mParameters;
+
+		mutable bool mIsDirty;
+		mutable WString mCachedString;
+		mutable WString* mStringPtr;
 	};
 }

+ 3 - 15
BansheeUtility/Include/BsStringTable.h

@@ -205,12 +205,6 @@ namespace BansheeEngine
 	 */
 	struct LocalizedStringData
 	{
-		struct Common
-		{
-			WString identifier;
-			Event<void()> onStringDataModified;
-		};
-
 		struct ParamOffset
 		{
 			ParamOffset()
@@ -232,8 +226,6 @@ namespace BansheeEngine
 		UINT32 numParameters;
 		ParamOffset* parameterOffsets; 
 
-		Common* commonData;
-
 		void concatenateString(WString& outputString, WString* parameters, UINT32 numParameterValues) const;
 		void updateString(const WString& string);
 	};
@@ -248,7 +240,7 @@ namespace BansheeEngine
 
 		struct LanguageData
 		{
-			UnorderedMap<WString, LocalizedStringData*> strings;
+			UnorderedMap<WString, SPtr<LocalizedStringData>> strings;
 		};
 	public:
 		StringTable();
@@ -285,7 +277,7 @@ namespace BansheeEngine
 		 *
 		 * @return	The string data. Don't store reference to this data as it may get deleted.
 		 */
-		LocalizedStringData& getStringData(const WString& identifier, bool insertIfNonExisting = true);
+		SPtr<LocalizedStringData> getStringData(const WString& identifier, bool insertIfNonExisting = true);
 
 		/**
 		 * @brief	Gets a string data for the specified string identifier and language.
@@ -298,7 +290,7 @@ namespace BansheeEngine
 		 *
 		 * @return	The string data. Don't store reference to this data as it may get deleted.
 		 */
-		LocalizedStringData& getStringData(const WString& identifier, Language language, bool insertIfNonExisting = true);
+		SPtr<LocalizedStringData> getStringData(const WString& identifier, Language language, bool insertIfNonExisting = true);
 
 	private:
 		friend class HString;
@@ -310,9 +302,5 @@ namespace BansheeEngine
 		LanguageData* mDefaultLanguageData;
 
 		LanguageData* mAllLanguages;
-
-		UnorderedMap<WString, LocalizedStringData::Common*> mCommonData;
-
-		void notifyAllStringsChanged();
 	};
 }

+ 54 - 69
BansheeUtility/Source/BsHString.cpp

@@ -4,81 +4,44 @@
 
 namespace BansheeEngine
 {
-	HString::StringData::StringData()
+	HString::HString(UINT32 stringTableId)
 		:mParameters(nullptr), mIsDirty(true), mStringPtr(nullptr)
-	{ }
-
-	HString::StringData::~StringData()
-	{
-		mUpdateConn.disconnect();
-
-		if(mParameters != nullptr)
-			bs_deleteN(mParameters, mStringData->numParameters);
-	}
-
-	void HString::StringData::updateString()
 	{
-		LocalizedStringData* stringData = &StringTable::instance().getStringData(mStringData->commonData->identifier);
-
-		// If common data changed re-apply the connections
-		if(stringData->commonData != mStringData->commonData)
-		{
-			mUpdateConn.disconnect();
-			mUpdateConn = stringData->commonData->onStringDataModified.connect(std::bind(&HString::StringData::updateString, this));
-		}
-
-		mStringData = stringData;
-		mIsDirty = true;
+		mStringData = StringTable::instance().getStringData(L"");
 
-		onStringModified();
+		if(mStringData->numParameters > 0)
+			mParameters = bs_newN<WString>(mStringData->numParameters);
 	}
 
-	HString::HString()
-	{
-		mData = bs_shared_ptr<StringData>();
-
-		mData->mStringData = &StringTable::instance().getStringData(L"");
-
-		if(mData->mStringData->numParameters > 0)
-			mData->mParameters = bs_newN<WString>(mData->mStringData->numParameters);
-
-		mData->mUpdateConn = mData->mStringData->commonData->onStringDataModified.connect(std::bind(&HString::StringData::updateString, mData.get()));
-	}
-
-	HString::HString(const WString& identifierString)
+	HString::HString(const WString& identifierString, UINT32 stringTableId)
+		:mParameters(nullptr), mIsDirty(true), mStringPtr(nullptr)
 	{
-		mData = bs_shared_ptr<StringData>();
-
-		mData->mStringData = &StringTable::instance().getStringData(identifierString);
+		mStringData = StringTable::instance().getStringData(identifierString);
 
-		if(mData->mStringData->numParameters > 0)
-			mData->mParameters = bs_newN<WString>(mData->mStringData->numParameters);
-
-		mData->mUpdateConn = mData->mStringData->commonData->onStringDataModified.connect(std::bind(&HString::StringData::updateString, mData.get()));
+		if(mStringData->numParameters > 0)
+			mParameters = bs_newN<WString>(mStringData->numParameters);
 	}
 
-	HString::HString(const WString& identifierString, const WString& defaultString)
+	HString::HString(const WString& identifierString, const WString& defaultString, UINT32 stringTableId)
+		:mParameters(nullptr), mIsDirty(true), mStringPtr(nullptr)
 	{
-		mData = bs_shared_ptr<StringData>();
-
 		StringTable::instance().setString(identifierString, StringTable::DEFAULT_LANGUAGE, defaultString);
 
-		mData->mStringData = &StringTable::instance().getStringData(identifierString);
-
-		if (mData->mStringData->numParameters > 0)
-			mData->mParameters = bs_newN<WString>(mData->mStringData->numParameters);
+		mStringData = StringTable::instance().getStringData(identifierString);
 
-		mData->mUpdateConn = mData->mStringData->commonData->onStringDataModified.connect(std::bind(&HString::StringData::updateString, mData.get()));
+		if (mStringData->numParameters > 0)
+			mParameters = bs_newN<WString>(mStringData->numParameters);
 	}
 
 	HString::HString(const HString& copy)
 	{
-		mData = copy.mData;
+		*this = copy;
 	}
 
 	HString::~HString()
 	{
-
+		if (mParameters != nullptr)
+			bs_deleteN(mParameters, mStringData->numParameters);
 	}
 
 	HString::operator const WString& () const 
@@ -86,37 +49,59 @@ namespace BansheeEngine
 		return getValue(); 
 	}
 
+	HString& HString::operator=(const HString& rhs)
+	{
+		mStringData = rhs.mStringData;
+		mIsDirty = rhs.mIsDirty;
+		mCachedString = rhs.mCachedString;
+
+		if (rhs.mStringData->numParameters > 0)
+		{
+			mParameters = bs_newN<WString>(mStringData->numParameters);
+			if (rhs.mParameters != nullptr)
+			{
+				for (UINT32 i = 0; i < mStringData->numParameters; i++)
+					mParameters[i] = rhs.mParameters[i];
+			}
+
+			mStringPtr = &mCachedString;
+		}
+		else
+		{
+			mParameters = nullptr;
+			mStringPtr = &mStringData->string;
+		}
+
+		return *this;
+	}
+
 	const WString& HString::getValue() const
 	{
-		if(mData->mIsDirty)
+		if(mIsDirty)
 		{
-			if(mData->mParameters != nullptr)
+			if(mParameters != nullptr)
 			{
-				mData->mStringData->concatenateString(mData->mCachedString, mData->mParameters, mData->mStringData->numParameters);
-				mData->mStringPtr = &mData->mCachedString;
+				mStringData->concatenateString(mCachedString, mParameters, mStringData->numParameters);
+				mStringPtr = &mCachedString;
 			}
 			else
 			{
-				mData->mStringPtr = &mData->mStringData->string;
+				mStringPtr = &mStringData->string;
 			}
 
-			mData->mIsDirty = false;
+			mIsDirty = false;
 		}
 
-		return *mData->mStringPtr; 
+		return *mStringPtr; 
 	}
 
 	void HString::setParameter(UINT32 idx, const WString& value)
 	{
-		mData->mParameters[idx] = value;
-
-		mData->mIsDirty = true;
-		mData->onStringModified();
-	}
+		if (idx >= mStringData->numParameters)
+			return;
 
-	HEvent HString::addOnStringModifiedCallback(std::function<void()> callback) const
-	{
-		return mData->onStringModified.connect(callback);
+		mParameters[idx] = value;
+		mIsDirty = true;
 	}
 
 	const HString& HString::dummy()

+ 9 - 80
BansheeUtility/Source/BsStringTable.cpp

@@ -6,7 +6,7 @@ namespace BansheeEngine
 	const Language StringTable::DEFAULT_LANGUAGE = Language::EnglishUS;
 
 	LocalizedStringData::LocalizedStringData()
-		:commonData(nullptr), parameterOffsets(nullptr), numParameters(0)
+		:parameterOffsets(nullptr), numParameters(0)
 	{
 
 	}
@@ -171,16 +171,7 @@ namespace BansheeEngine
 	
 	StringTable::~StringTable()
 	{
-		for(UINT32 i = 0; i < (UINT32)Language::Count; i++)
-		{
-			for(auto& iter : mAllLanguages[i].strings)
-				bs_delete(iter.second);
-		}
-
 		bs_deleteN(mAllLanguages, (UINT32)Language::Count);
-
-		for(auto& common : mCommonData)
-			bs_delete(common.second);
 	}
 
 	void StringTable::setActiveLanguage(Language language)
@@ -190,8 +181,6 @@ namespace BansheeEngine
 
 		mActiveLanguageData = &(mAllLanguages[(UINT32)language]);
 		mActiveLanguage = language;
-
-		notifyAllStringsChanged();
 	}
 
 	void StringTable::setString(const WString& identifier, Language language, const WString& string)
@@ -200,24 +189,11 @@ namespace BansheeEngine
 
 		auto iterFind = curLanguage->strings.find(identifier);
 
-		LocalizedStringData* stringData;
+		SPtr<LocalizedStringData> stringData;
 		if(iterFind == curLanguage->strings.end())
 		{
-			auto iterFindCommon = mCommonData.find(identifier);
-
-			LocalizedStringData::Common* common = nullptr;
-			if(iterFindCommon == mCommonData.end())
-			{
-				common = bs_new<LocalizedStringData::Common>();
-				common->identifier = identifier;
-				mCommonData[identifier] = common;
-			}
-			else
-				common = iterFindCommon->second;
-
-			stringData = bs_new<LocalizedStringData>();
+			stringData = bs_shared_ptr<LocalizedStringData>();
 			curLanguage->strings[identifier] = stringData;
-			stringData->commonData = common;
 		}
 		else
 		{
@@ -225,70 +201,32 @@ namespace BansheeEngine
 		}
 
 		stringData->updateString(string);
-
-		if(mActiveLanguage == language)
-		{
-			if(!stringData->commonData->onStringDataModified.empty())
-				stringData->commonData->onStringDataModified();
-		}
 	}
 
 	void StringTable::removeString(const WString& identifier)
 	{
-		// Order of operations is very important here, in case a string that is in use
-		// is removed. In that case we want the string to be marked as modified and it should
-		// call getStringData which will generate a new entry for the string.
-		
-		LocalizedStringData* stringData = nullptr;
 		for(UINT32 i = 0; i < (UINT32)Language::Count; i++)
 		{
-			auto findIter = mAllLanguages[i].strings.find(identifier);
-			if(findIter != mAllLanguages[i].strings.end())
-			{
-				if(mActiveLanguage == (Language)i)
-					stringData = findIter->second;
-				else
-					bs_delete(findIter->second);
-
-				mAllLanguages[i].strings.erase(findIter);
-			}
-		}
-
-		auto findIterCommon = mCommonData.find(identifier);
-
-		LocalizedStringData::Common* common = nullptr;
-		if(findIterCommon != mCommonData.end())
-		{
-			common = findIterCommon->second;
-			mCommonData.erase(findIterCommon);
-
-			if(!common->onStringDataModified.empty())
-				common->onStringDataModified();
+			mAllLanguages[i].strings.erase(identifier);
 		}
-
-		if(stringData != nullptr)
-			bs_delete(stringData);
-
-		if(common != nullptr)
-			bs_delete(common);
 	}
 
-	LocalizedStringData& StringTable::getStringData(const WString& identifier, bool insertIfNonExisting)
+	SPtr<LocalizedStringData> StringTable::getStringData(const WString& identifier, bool insertIfNonExisting)
 	{
 		return getStringData(identifier, mActiveLanguage, insertIfNonExisting);
 	}
 
-	LocalizedStringData& StringTable::getStringData(const WString& identifier, Language language, bool insertIfNonExisting)
+	SPtr<LocalizedStringData> StringTable::getStringData(const WString& identifier, Language language, bool insertIfNonExisting)
 	{
 		LanguageData* curLanguage = &(mAllLanguages[(UINT32)language]);
 
 		auto iterFind = curLanguage->strings.find(identifier);
 		if(iterFind != curLanguage->strings.end())
-			return *iterFind->second;
+			return iterFind->second;
 
 		auto defaultIterFind = mDefaultLanguageData->strings.find(identifier);
 		if(defaultIterFind != mDefaultLanguageData->strings.end())
-			return *defaultIterFind->second;
+			return defaultIterFind->second;
 
 		if(insertIfNonExisting)
 		{
@@ -296,18 +234,9 @@ namespace BansheeEngine
 
 			auto defaultIterFind = mDefaultLanguageData->strings.find(identifier);
 			if(defaultIterFind != mDefaultLanguageData->strings.end())
-				return *defaultIterFind->second;
+				return defaultIterFind->second;
 		}
 
 		BS_EXCEPT(InvalidParametersException, "There is no string data for the provided identifier.");
 	}
-
-	void StringTable::notifyAllStringsChanged()
-	{
-		for(auto& iter : mCommonData)
-		{
-			if(!iter.second->onStringDataModified.empty())
-				iter.second->onStringDataModified();
-		}
-	}
 }

+ 13 - 13
MBansheeEditor/ColorPicker.cs

@@ -96,7 +96,7 @@ namespace BansheeEditor
 
         private void OnInitialize()
         {
-            Title = "Color Picker";
+            Title = new LocEdString("Color Picker");
             Width = 270;
             Height = 400;
 
@@ -118,18 +118,18 @@ namespace BansheeEditor
             guiSliderAHorz = new GUISliderH(EditorStyles.ColorSliderHorz);
             guiSlider2DHandle = new GUITexture(null, EditorStyles.ColorSlider2DHandle);
 
-            guiLabelR = new GUILabel("R");
-            guiLabelG = new GUILabel("G");
-            guiLabelB = new GUILabel("B");
-            guiLabelA = new GUILabel("A");
+            guiLabelR = new GUILabel(new LocEdString("R"));
+            guiLabelG = new GUILabel(new LocEdString("G"));
+            guiLabelB = new GUILabel(new LocEdString("B"));
+            guiLabelA = new GUILabel(new LocEdString("A"));
 
             guiInputR = new GUIIntField();
             guiInputG = new GUIIntField();
             guiInputB = new GUIIntField();
             guiInputA = new GUIIntField();
 
-            guiOK = new GUIButton("OK");
-            guiCancel = new GUIButton("Cancel");
+            guiOK = new GUIButton(new LocEdString("OK"));
+            guiCancel = new GUIButton(new LocEdString("Cancel"));
 
             guiColorBoxBtn.OnClick += OnColorBoxModeChanged;
             guiColorModeBtn.OnClick += OnSliderModeChanged;
@@ -580,9 +580,9 @@ namespace BansheeEditor
         {
             if (sliderMode == SliderMode.RGB)
             {
-                guiLabelR.SetContent("R");
-                guiLabelG.SetContent("G");
-                guiLabelB.SetContent("B");
+                guiLabelR.SetContent(new LocEdString("R"));
+                guiLabelG.SetContent(new LocEdString("G"));
+                guiLabelB.SetContent(new LocEdString("B"));
 
                 guiInputR.SetRange(0, 255);
                 guiInputG.SetRange(0, 255);
@@ -590,9 +590,9 @@ namespace BansheeEditor
             }
             else
             {
-                guiLabelR.SetContent("H");
-                guiLabelG.SetContent("S");
-                guiLabelB.SetContent("V");
+                guiLabelR.SetContent(new LocEdString("H"));
+                guiLabelG.SetContent(new LocEdString("S"));
+                guiLabelB.SetContent(new LocEdString("V"));
 
                 guiInputR.SetRange(0, 359);
                 guiInputG.SetRange(0, 255);

+ 16 - 16
MBansheeEditor/DialogBox.cs

@@ -91,7 +91,7 @@ namespace BansheeEditor
             {
                 case Type.OK:
                     {
-                        GUIButton okBtn = new GUIButton("OK");
+                        GUIButton okBtn = new GUIButton(new LocEdString("OK"));
                         okBtn.OnClick += () => ButtonClicked(ResultType.OK);
 
                         btnLayout.AddElement(okBtn);
@@ -99,10 +99,10 @@ namespace BansheeEditor
                     break;
                 case Type.OKCancel:
                     {
-                        GUIButton okBtn = new GUIButton("OK");
+                        GUIButton okBtn = new GUIButton(new LocEdString("OK"));
                         okBtn.OnClick += () => ButtonClicked(ResultType.OK);
 
-                        GUIButton cancelBtn = new GUIButton("Cancel");
+                        GUIButton cancelBtn = new GUIButton(new LocEdString("Cancel"));
                         cancelBtn.OnClick += () => ButtonClicked(ResultType.Cancel);
 
                         btnLayout.AddElement(okBtn);
@@ -112,13 +112,13 @@ namespace BansheeEditor
                     break;
                 case Type.RetryAbortIgnore:
                     {
-                        GUIButton retryBtn = new GUIButton("Retry");
+                        GUIButton retryBtn = new GUIButton(new LocEdString("Retry"));
                         retryBtn.OnClick += () => ButtonClicked(ResultType.Retry);
 
-                        GUIButton abortBtn = new GUIButton("Abort");
+                        GUIButton abortBtn = new GUIButton(new LocEdString("Abort"));
                         abortBtn.OnClick += () => ButtonClicked(ResultType.Abort);
 
-                        GUIButton ignoreBtn = new GUIButton("Ignore");
+                        GUIButton ignoreBtn = new GUIButton(new LocEdString("Ignore"));
                         ignoreBtn.OnClick += () => ButtonClicked(ResultType.Ignore);
 
                         btnLayout.AddElement(retryBtn);
@@ -130,10 +130,10 @@ namespace BansheeEditor
                     break;
                 case Type.RetryCancel:
                     {
-                        GUIButton retryBtn = new GUIButton("Retry");
+                        GUIButton retryBtn = new GUIButton(new LocEdString("Retry"));
                         retryBtn.OnClick += () => ButtonClicked(ResultType.Retry);
 
-                        GUIButton cancelBtn = new GUIButton("Cancel");
+                        GUIButton cancelBtn = new GUIButton(new LocEdString("Cancel"));
                         cancelBtn.OnClick += () => ButtonClicked(ResultType.Cancel);
 
                         btnLayout.AddElement(retryBtn);
@@ -143,13 +143,13 @@ namespace BansheeEditor
                     break;
                 case Type.TryCancelContinue:
                     {
-                        GUIButton tryBtn = new GUIButton("Try");
+                        GUIButton tryBtn = new GUIButton(new LocEdString("Try"));
                         tryBtn.OnClick += () => ButtonClicked(ResultType.Try);
 
-                        GUIButton cancelBtn = new GUIButton("Cancel");
+                        GUIButton cancelBtn = new GUIButton(new LocEdString("Cancel"));
                         cancelBtn.OnClick += () => ButtonClicked(ResultType.Cancel);
 
-                        GUIButton continueBtn = new GUIButton("Continue");
+                        GUIButton continueBtn = new GUIButton(new LocEdString("Continue"));
                         continueBtn.OnClick += () => ButtonClicked(ResultType.Continue);
 
                         btnLayout.AddElement(tryBtn);
@@ -161,10 +161,10 @@ namespace BansheeEditor
                     break;
                 case Type.YesNo:
                     {
-                        GUIButton yesBtn = new GUIButton("Yes");
+                        GUIButton yesBtn = new GUIButton(new LocEdString("Yes"));
                         yesBtn.OnClick += () => ButtonClicked(ResultType.Yes);
 
-                        GUIButton noBtn = new GUIButton("No");
+                        GUIButton noBtn = new GUIButton(new LocEdString("No"));
                         noBtn.OnClick += () => ButtonClicked(ResultType.No);
 
                         btnLayout.AddElement(yesBtn);
@@ -174,13 +174,13 @@ namespace BansheeEditor
                     break;
                 case Type.YesNoCancel:
                     {
-                        GUIButton yesBtn = new GUIButton("Yes");
+                        GUIButton yesBtn = new GUIButton(new LocEdString("Yes"));
                         yesBtn.OnClick += () => ButtonClicked(ResultType.Yes);
 
-                        GUIButton noBtn = new GUIButton("No");
+                        GUIButton noBtn = new GUIButton(new LocEdString("No"));
                         noBtn.OnClick += () => ButtonClicked(ResultType.No);
 
-                        GUIButton cancelBtn = new GUIButton("Cancel");
+                        GUIButton cancelBtn = new GUIButton(new LocEdString("Cancel"));
                         cancelBtn.OnClick += () => ButtonClicked(ResultType.Cancel);
 
                         btnLayout.AddElement(yesBtn);

+ 1 - 1
MBansheeEditor/HierarchyWindow.cs

@@ -14,7 +14,7 @@ namespace BansheeEditor
 
         protected override LocString GetDisplayName()
         {
-            return "Hierarchy";
+            return new LocEdString("Hierarchy");
         }
 
         private void OnInitialize()

+ 20 - 20
MBansheeEditor/Inspector/InspectorWindow.cs

@@ -59,7 +59,7 @@ namespace BansheeEditor
 
         protected override LocString GetDisplayName()
         {
-            return "Inspector";
+            return new LocEdString("Inspector");
         }
 
         private void SetObjectToInspect(String resourcePath)
@@ -140,7 +140,7 @@ namespace BansheeEditor
             GUILayoutY sceneObjectLayout = sceneObjectPanel.AddLayoutY();
 
             GUILayoutX nameLayout = sceneObjectLayout.AddLayoutX();
-            GUILabel nameLbl = new GUILabel("Name", GUIOption.FixedWidth(70));
+            GUILabel nameLbl = new GUILabel(new LocEdString("Name"), GUIOption.FixedWidth(70));
             soNameInput = new GUITextBox(false, GUIOption.FlexibleWidth(200));
             soNameInput.Text = activeSO.Name;
             soNameInput.OnChanged += (x) => { if (activeSO != null) activeSO.Name = x; };
@@ -152,10 +152,10 @@ namespace BansheeEditor
             soPrefabLayout = sceneObjectLayout.AddLayoutX();
 
             GUILayoutX positionLayout = sceneObjectLayout.AddLayoutX();
-            GUILabel positionLbl = new GUILabel("Position", GUIOption.FixedWidth(70));
-            soPosX = new GUIFloatField(new GUIContent("X"), 10, "", GUIOption.FlexibleWidth(50));
-            soPosY = new GUIFloatField(new GUIContent("Y"), 10, "", GUIOption.FlexibleWidth(50));
-            soPosZ = new GUIFloatField(new GUIContent("Z"), 10, "", GUIOption.FlexibleWidth(50));
+            GUILabel positionLbl = new GUILabel(new LocEdString("Position"), GUIOption.FixedWidth(70));
+            soPosX = new GUIFloatField(new LocEdString("X"), 10, "", GUIOption.FlexibleWidth(50));
+            soPosY = new GUIFloatField(new LocEdString("Y"), 10, "", GUIOption.FlexibleWidth(50));
+            soPosZ = new GUIFloatField(new LocEdString("Z"), 10, "", GUIOption.FlexibleWidth(50));
 
             soPosX.OnChanged += (x) => OnPositionChanged(0, x);
             soPosY.OnChanged += (y) => OnPositionChanged(1, y);
@@ -170,10 +170,10 @@ namespace BansheeEditor
             positionLayout.AddFlexibleSpace();
 
             GUILayoutX rotationLayout = sceneObjectLayout.AddLayoutX();
-            GUILabel rotationLbl = new GUILabel("Rotation", GUIOption.FixedWidth(70));
-            soRotX = new GUIFloatField(new GUIContent("X"), 10, "", GUIOption.FlexibleWidth(50));
-            soRotY = new GUIFloatField(new GUIContent("Y"), 10, "", GUIOption.FlexibleWidth(50));
-            soRotZ = new GUIFloatField(new GUIContent("Z"), 10, "", GUIOption.FlexibleWidth(50));
+            GUILabel rotationLbl = new GUILabel(new LocEdString("Rotation"), GUIOption.FixedWidth(70));
+            soRotX = new GUIFloatField(new LocEdString("X"), 10, "", GUIOption.FlexibleWidth(50));
+            soRotY = new GUIFloatField(new LocEdString("Y"), 10, "", GUIOption.FlexibleWidth(50));
+            soRotZ = new GUIFloatField(new LocEdString("Z"), 10, "", GUIOption.FlexibleWidth(50));
 
             soRotX.OnChanged += (x) => OnRotationChanged(0, x);
             soRotY.OnChanged += (y) => OnRotationChanged(1, y);
@@ -188,10 +188,10 @@ namespace BansheeEditor
             rotationLayout.AddFlexibleSpace();
 
             GUILayoutX scaleLayout = sceneObjectLayout.AddLayoutX();
-            GUILabel scaleLbl = new GUILabel("Scale", GUIOption.FixedWidth(70));
-            soScaleX = new GUIFloatField(new GUIContent("X"), 10, "", GUIOption.FlexibleWidth(50));
-            soScaleY = new GUIFloatField(new GUIContent("Y"), 10, "", GUIOption.FlexibleWidth(50));
-            soScaleZ = new GUIFloatField(new GUIContent("Z"), 10, "", GUIOption.FlexibleWidth(50));
+            GUILabel scaleLbl = new GUILabel(new LocEdString("Scale"), GUIOption.FixedWidth(70));
+            soScaleX = new GUIFloatField(new LocEdString("X"), 10, "", GUIOption.FlexibleWidth(50));
+            soScaleY = new GUIFloatField(new LocEdString("Y"), 10, "", GUIOption.FlexibleWidth(50));
+            soScaleZ = new GUIFloatField(new LocEdString("Z"), 10, "", GUIOption.FlexibleWidth(50));
 
             soScaleX.OnChanged += (x) => OnScaleChanged(0, x);
             soScaleY.OnChanged += (y) => OnScaleChanged(1, y);
@@ -222,14 +222,14 @@ namespace BansheeEditor
                 for (int i = 0; i < numChildren; i++)
                     soPrefabLayout.GetChild(0).Destroy();
 
-                GUILabel prefabLabel =new GUILabel("Prefab", GUIOption.FixedWidth(70));
+                GUILabel prefabLabel =new GUILabel(new LocEdString("Prefab"), GUIOption.FixedWidth(70));
                 soPrefabLayout.AddElement(prefabLabel);
 
                 //if (hasPrefab) // TODO - Disabled check for preview purposes
                 {
-                    GUIButton btnApplyPrefab = new GUIButton("Apply");
-                    GUIButton btnRevertPrefab = new GUIButton("Revert");
-                    GUIButton btnBreakPrefab = new GUIButton("Break");
+                    GUIButton btnApplyPrefab = new GUIButton(new LocEdString("Apply"));
+                    GUIButton btnRevertPrefab = new GUIButton(new LocEdString("Revert"));
+                    GUIButton btnBreakPrefab = new GUIButton(new LocEdString("Break"));
 
                     btnApplyPrefab.OnClick += () => PrefabUtility.ApplyPrefab(activeSO);
                     btnRevertPrefab.OnClick += () => PrefabUtility.RevertPrefab(activeSO);
@@ -333,7 +333,7 @@ namespace BansheeEditor
                 inspectorScrollArea = new GUIScrollArea();
                 GUI.AddElement(inspectorScrollArea);
                 inspectorLayout = inspectorScrollArea.Layout;
-                inspectorLayout.AddElement(new GUILabel("No object selected"));
+                inspectorLayout.AddElement(new GUILabel(new LocEdString("No object selected")));
                 inspectorLayout.AddFlexibleSpace();
             }
             else if ((objects.Length + paths.Length) > 1)
@@ -342,7 +342,7 @@ namespace BansheeEditor
                 inspectorScrollArea = new GUIScrollArea();
                 GUI.AddElement(inspectorScrollArea);
                 inspectorLayout = inspectorScrollArea.Layout;
-                inspectorLayout.AddElement(new GUILabel("Multiple objects selected"));
+                inspectorLayout.AddElement(new GUILabel(new LocEdString("Multiple objects selected")));
                 inspectorLayout.AddFlexibleSpace();
             }
             else if (objects.Length == 1)

+ 37 - 0
MBansheeEditor/LocEdString.cs

@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using BansheeEngine;
+
+namespace BansheeEditor
+{
+    internal class LocEdString
+    {
+        // Note: This must match C++ HEString::ENGINE_STRING_TABLE_ID
+        private const int EDITOR_STRING_TABLE_ID = 30000;
+
+        private LocString internalString;
+
+        public LocEdString()
+        {
+            internalString = new LocString(EDITOR_STRING_TABLE_ID);
+        }
+
+        public LocEdString(string identifier)
+        {
+            internalString = new LocString(identifier, EDITOR_STRING_TABLE_ID);
+        }
+
+        public static implicit operator LocString(LocEdString edString)
+        {
+            return edString.internalString;
+        }
+
+        public static implicit operator GUIContent(LocEdString edString)
+        {
+            return new GUIContent(edString.internalString);
+        }
+    }
+}

+ 1 - 0
MBansheeEditor/MBansheeEditor.csproj

@@ -59,6 +59,7 @@
     <Compile Include="GUI\GUISceneTreeView.cs" />
     <Compile Include="GUI\TextureField.cs" />
     <Compile Include="HierarchyWindow.cs" />
+    <Compile Include="LocEdString.cs" />
     <Compile Include="PrefabUtility.cs" />
     <Compile Include="ProjectDropTarget.cs" />
     <Compile Include="OSDropTarget.cs" />

+ 15 - 8
MBansheeEditor/ProjectWindow.cs

@@ -83,7 +83,7 @@ namespace BansheeEditor
 
         protected override LocString GetDisplayName()
         {
-            return "Project";
+            return new LocEdString("Project");
         }
 
         private void OnInitialize()
@@ -130,6 +130,13 @@ namespace BansheeEditor
             entryContextMenu.AddSeparator("");
             entryContextMenu.AddItem("Delete", DeleteSelection, new ShortcutKey(ButtonModifier.None, ButtonCode.Delete));
 
+            entryContextMenu.SetLocalizedName("Rename", new LocEdString("Rename"));
+            entryContextMenu.SetLocalizedName("Cut", new LocEdString("Cut"));
+            entryContextMenu.SetLocalizedName("Copy", new LocEdString("Copy"));
+            entryContextMenu.SetLocalizedName("Duplicate", new LocEdString("Duplicate"));
+            entryContextMenu.SetLocalizedName("Paste", new LocEdString("Paste"));
+            entryContextMenu.SetLocalizedName("Delete", new LocEdString("Delete"));
+
             Reset();
 
             dropTarget = new ProjectDropTarget(this);
@@ -761,7 +768,7 @@ namespace BansheeEditor
                         bool renameOK = true;
                         if (!PathEx.IsValidFileName(newName))
                         {
-                            DialogBox.Open("Error", "The name you specified is not a valid file name. Try another.", DialogBox.Type.OK);
+                            DialogBox.Open(new LocEdString("Error"), new LocEdString("The name you specified is not a valid file name. Try another."), DialogBox.Type.OK);
                             renameOK = false;
                         }
 
@@ -773,7 +780,7 @@ namespace BansheeEditor
 
                             if (originalPath != trimmedNewPath && ProjectLibrary.Exists(trimmedNewPath))
                             {
-                                DialogBox.Open("Error", "File/folder with that name already exists in this folder.", DialogBox.Type.OK);
+                                DialogBox.Open(new LocEdString("Error"), new LocEdString("File/folder with that name already exists in this folder."), DialogBox.Type.OK);
                                 renameOK = false;
                             }
                         }
@@ -1296,7 +1303,7 @@ namespace BansheeEditor
             if (selectionPaths.Count == 0)
                 return;
 
-            DialogBox.Open("Confirm deletion", "Are you sure you want to delete the selected object(s)?",
+            DialogBox.Open(new LocEdString("Confirm deletion"), new LocEdString("Are you sure you want to delete the selected object(s)?"),
                 DialogBox.Type.YesNo,
                 type =>
                 {
@@ -1696,10 +1703,10 @@ namespace BansheeEditor
 
             GUIToggleGroup group = new GUIToggleGroup();
 
-            GUIToggle list16 = new GUIToggle("16", group, EditorStyles.Button, GUIOption.FixedWidth(30));
-            GUIToggle grid32 = new GUIToggle("32", group, EditorStyles.Button, GUIOption.FixedWidth(30));
-            GUIToggle grid48 = new GUIToggle("48", group, EditorStyles.Button, GUIOption.FixedWidth(30));
-            GUIToggle grid64 = new GUIToggle("64", group, EditorStyles.Button, GUIOption.FixedWidth(30));
+            GUIToggle list16 = new GUIToggle(new LocEdString("16"), group, EditorStyles.Button, GUIOption.FixedWidth(30));
+            GUIToggle grid32 = new GUIToggle(new LocEdString("32"), group, EditorStyles.Button, GUIOption.FixedWidth(30));
+            GUIToggle grid48 = new GUIToggle(new LocEdString("48"), group, EditorStyles.Button, GUIOption.FixedWidth(30));
+            GUIToggle grid64 = new GUIToggle(new LocEdString("64"), group, EditorStyles.Button, GUIOption.FixedWidth(30));
 
             ProjectViewType activeType = parent.ViewType;
             switch (activeType)

+ 1 - 1
MBansheeEditor/Scene/SceneWindow.cs

@@ -69,7 +69,7 @@ namespace BansheeEditor
 
         protected override LocString GetDisplayName()
         {
-            return "Scene";
+            return new LocEdString("Scene");
         }
 
         private void OnInitialize()

+ 8 - 3
MBansheeEngine/LocString.cs

@@ -5,9 +5,14 @@ namespace BansheeEngine
 {
     public sealed class LocString : ScriptObject
     {
-        public LocString(string identifier)
+        public LocString(int tableId = 0)
         {
-            Internal_CreateInstance(this, identifier);
+            Internal_CreateInstance(this, "", tableId);
+        }
+
+        public LocString(string identifier, int tableId = 0)
+        {
+            Internal_CreateInstance(this, identifier, tableId);
         }
 
         public static implicit operator LocString(string identifier)
@@ -28,7 +33,7 @@ namespace BansheeEngine
         }
 
         [MethodImpl(MethodImplOptions.InternalCall)]
-        private static extern void Internal_CreateInstance(LocString instance, string identifier);
+        private static extern void Internal_CreateInstance(LocString instance, string identifier, int tableId);
 
         [MethodImpl(MethodImplOptions.InternalCall)]
         private static extern void Internal_SetParameter(IntPtr nativeInstance, int idx, string identifier);

+ 0 - 1
SBansheeEditor/Include/BsScriptGizmos.h

@@ -2,7 +2,6 @@
 
 #include "BsScriptEditorPrerequisites.h"
 #include "BsScriptObject.h"
-#include "BsStringTable.h"
 #include "BsVector3.h"
 #include "BsMatrix4.h"
 #include "BsColor.h"

+ 1 - 1
SBansheeEngine/Include/BsScriptHString.h

@@ -14,7 +14,7 @@ namespace BansheeEngine
 		const HString& getInternalValue() const { return mString; }
 
 	private:
-		static void internal_createInstance(MonoObject* instance, MonoString* identifier);
+		static void internal_createInstance(MonoObject* instance, MonoString* identifier, UINT32 tableId);
 		static void internal_setParameter(HString* nativeInstance, UINT32 idx, MonoString* value);
 		static void internal_getValue(HString* nativeInstance, MonoString** value);
 

+ 2 - 2
SBansheeEngine/Source/BsScriptHString.cpp

@@ -18,9 +18,9 @@ namespace BansheeEngine
 		metaData.scriptClass->addInternalCall("Internal_GetValue", &ScriptHString::internal_getValue);
 	}
 
-	void ScriptHString::internal_createInstance(MonoObject* instance, MonoString* identifier)
+	void ScriptHString::internal_createInstance(MonoObject* instance, MonoString* identifier, UINT32 tableId)
 	{
-		HString string(MonoUtil::monoToWString(identifier));
+		HString string(MonoUtil::monoToWString(identifier), tableId);
 		
 		ScriptHString* nativeInstance = new (bs_alloc<ScriptHString>()) ScriptHString(instance, string);
 	}

+ 17 - 2
TODO.txt

@@ -57,9 +57,24 @@ Code quality improvements:
 ----------------------------------------------------------------------
 Polish stage 1
 
-GET RID OF CONTENT STRING CALLBACKS - They're messsing with my optimization as I cannot calculate orig bounds before and after their update
+Add StringTableManager
+ - Takes over the functionality of current StringTable module
+ - getTable(enum)
+ - register(enum, stResource) <- load/Save this in BsApplication or BsCoreApplication
+ - unregister(enum, stResource)
+
+Convert current StringTable to a Resource:
+ - Add RTTI
+ - Also I need methods to enumerate and modify all strings from all languages (so an editor can be built)
+  - getNumStrings() <- might require me to keep a global list of identifiers
+  - getIdentifier(idx)
+  - getValue(language, identifier)
+  - setValue(language, identifier)
+
+Reflect new changes in C#
+ - StringTableManager -> StringTables
+ - C# StringTable resource
 
-Get rid of all the profiling calls
 
 Test inspector selection, selecting a resource and adding/removing component updates
 Crash when showing the inspector (invalid index in Layout.InsertElement called from InspectableObject.Update)