2
0
Эх сурвалжийг харах

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

Marko Pintera 10 жил өмнө
parent
commit
cd3174bc2a
33 өөрчлөгдсөн 458 нэмэгдсэн , 443 устгасан
  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)