diff --git a/ParallelRoadTool/Extensions/LocaleManagerExtensions.cs b/ParallelRoadTool/Extensions/LocaleManagerExtensions.cs
new file mode 100644
index 0000000..c7d0eaa
--- /dev/null
+++ b/ParallelRoadTool/Extensions/LocaleManagerExtensions.cs
@@ -0,0 +1,101 @@
+using ColossalFramework.Globalization;
+using ParallelRoadTool;
+using ParallelRoadTool.Extensions.LocaleModels;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace ParallelRoadTool.Extensions
+{
+ ///
+ ///
+ ///
+ static class LocaleManagerExtensions
+ {
+ const string localeFieldName = "m_Locale";
+ const string localizedStringsFieldName = "m_LocalizedStrings";
+ const string localizedStringsCountFieldName = "m_LocalizedStringsCount";
+
+ static FieldInfo localeField = typeof(LocaleManager).GetField(localeFieldName, BindingFlags.Instance | BindingFlags.NonPublic);
+ static FieldInfo localizedStringsField = typeof(Locale).GetField(localizedStringsFieldName, BindingFlags.Instance | BindingFlags.NonPublic);
+ static FieldInfo localizedStringsCountField = typeof(Locale).GetField(localizedStringsCountFieldName, BindingFlags.Instance | BindingFlags.NonPublic);
+
+ public static Locale GetLocale(this LocaleManager localeManager)
+ {
+ return (Locale)localeField.GetValue(localeManager);
+ }
+
+ public static Dictionary GetLocalizedStrings(this Locale locale)
+ {
+ return (Dictionary)localizedStringsField.GetValue(locale);
+ }
+
+ public static Dictionary GetLocalizedStringsCount(this Locale locale)
+ {
+ return (Dictionary)localizedStringsCountField.GetValue(locale);
+ }
+
+ public static void RemoveRange(this LocaleManager localeManager, Locale.Key id)
+ {
+ Locale locale = localeManager.GetLocale();
+
+ // Set index to 0 so we can check for the string count
+ id.m_Index = 0;
+
+ if (!locale.Exists(id))
+ {
+ DebugUtils.Log($"Could not remove locale range {id}; localized string {id} does not exist!");
+ return;
+ }
+
+ Dictionary localizedStrings = locale.GetLocalizedStrings();
+ Dictionary localizedStringsCount = locale.GetLocalizedStringsCount();
+
+ for (int index = 0, lastIndex = locale.CountUnchecked(id); index <= lastIndex; index++, id.m_Index = index)
+ {
+ localizedStrings.Remove(id);
+ localizedStringsCount.Remove(id);
+ }
+
+ DebugUtils.Log($"Removed locale range {id.m_Identifier}[{id.m_Key}].");
+ }
+
+ public static void AddString(this LocaleManager localeManager, LocalizedString localizedString)
+ {
+ Locale locale = localeManager.GetLocale();
+
+ // Construct 0-index id for the localized string from argument
+ Locale.Key id;
+ id.m_Identifier = localizedString.Identifier;
+ id.m_Key = localizedString.Key;
+ id.m_Index = 0;
+
+ // Check if the id already exists; if so find next index
+ if (locale.Exists(id))
+ {
+ // Log message lags game on large namelists
+ // Log($"Localized string {localizedString.Identifier}[{localizedString.Key}] already exists, adding it with next available index.");
+ id.m_Index = locale.CountUnchecked(id);
+ }
+
+ // Add the localized string
+ locale.AddLocalizedString(id, localizedString.Value);
+
+ // Set the string counts accordingly
+ Dictionary localizedStringCounts = locale.GetLocalizedStringsCount();
+
+ // The count at the exact index appears to always be 0
+ localizedStringCounts[id] = 0;
+
+ // index = 0 appears to be a special case and indicates the count of localized strings with the same identifier and key
+ Locale.Key zeroIndexID = id;
+ zeroIndexID.m_Index = 0;
+ localizedStringCounts[zeroIndexID] = id.m_Index + 1;
+
+ // Log message lags game on large namelists
+ // Log($"Added localized string {id} = '{localizedString.Value}', count = {localizedStringCounts[zeroIndexID]}.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/ParallelRoadTool/Extensions/LocaleModels/LocalizedString.cs b/ParallelRoadTool/Extensions/LocaleModels/LocalizedString.cs
new file mode 100644
index 0000000..38a1409
--- /dev/null
+++ b/ParallelRoadTool/Extensions/LocaleModels/LocalizedString.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Xml.Serialization;
+
+namespace ParallelRoadTool.Extensions.LocaleModels
+{
+ ///
+ ///
+ ///
+ public struct LocalizedString
+ {
+ [XmlAttribute(AttributeName = "identifier")]
+ public string Identifier;
+ [XmlAttribute(AttributeName = "key")]
+ public string Key;
+ [XmlText]
+ public string Value;
+ }
+}
diff --git a/ParallelRoadTool/Extensions/LocaleModels/LocalizedStringKey.cs b/ParallelRoadTool/Extensions/LocaleModels/LocalizedStringKey.cs
new file mode 100644
index 0000000..f2acd78
--- /dev/null
+++ b/ParallelRoadTool/Extensions/LocaleModels/LocalizedStringKey.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Xml.Serialization;
+
+namespace ParallelRoadTool.Extensions.LocaleModels
+{
+ ///
+ ///
+ ///
+ public struct LocalizedStringKey
+ {
+ [XmlAttribute(AttributeName = "identifier")]
+ public string Identifier;
+ [XmlAttribute(AttributeName = "key")]
+ public string Key;
+ }
+}
diff --git a/ParallelRoadTool/Extensions/LocaleModels/NameList.cs b/ParallelRoadTool/Extensions/LocaleModels/NameList.cs
new file mode 100644
index 0000000..c88830f
--- /dev/null
+++ b/ParallelRoadTool/Extensions/LocaleModels/NameList.cs
@@ -0,0 +1,32 @@
+using ColossalFramework;
+using ColossalFramework.Globalization;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Xml.Serialization;
+
+namespace ParallelRoadTool.Extensions.LocaleModels
+{
+ ///
+ ///
+ ///
+ public class NameList
+ {
+ [XmlAttribute(AttributeName = "name")]
+ public string Name;
+ [XmlArray(ElementName = "strings")]
+ public LocalizedString[] LocalizedStrings;
+
+ public void Apply()
+ {
+ LocaleManager localeManager = SingletonLite.instance;
+ foreach (LocalizedString localizedString in LocalizedStrings)
+ {
+ localeManager.AddString(localizedString);
+ }
+
+ DebugUtils.Log($"Namelist {Name} applied.");
+ }
+ }
+}
diff --git a/ParallelRoadTool/Localization/en.xml b/ParallelRoadTool/Localization/en.xml
new file mode 100644
index 0000000..79ea929
--- /dev/null
+++ b/ParallelRoadTool/Localization/en.xml
@@ -0,0 +1,19 @@
+
+
+
+ Toggle snapping for existing nodes
+ Toggle Parallel Road Tool
+ Toggle reverse direction for this road
+ Remove network
+ Add network
+
+ Same as selected
+ Decrease horizontal offset of parallel networks
+ Increase horizontal offset of parallel networks
+ Decrease vertical offset of parallel networks
+ Increase vertical offset of parallel networks
+
+
\ No newline at end of file
diff --git a/ParallelRoadTool/OptionsKeymapping.cs b/ParallelRoadTool/OptionsKeymapping.cs
index adbb9e4..1d25b84 100644
--- a/ParallelRoadTool/OptionsKeymapping.cs
+++ b/ParallelRoadTool/OptionsKeymapping.cs
@@ -27,9 +27,9 @@ public class OptionsKeymapping : UICustomControl
private void Awake()
{
- AddKeymapping("Toggle Parallel Roads", toggleParallelRoads);
- AddKeymapping("Decrease HorizontalOffset of Parallel Roads", decreaseOffset);
- AddKeymapping("Increase HorizontalOffset of Parallel Roads", increaseOffset);
+ AddKeymapping(Locale.Get("PRT_TOOLTIPS", "ToolToggleButton"), toggleParallelRoads);
+ AddKeymapping(Locale.Get("PRT_TEXTS", "DecreaseHorizontalOffsetOption"), decreaseOffset);
+ AddKeymapping(Locale.Get("PRT_TEXTS", "IncreaseHorizontalOffsetOption"), increaseOffset);
}
private void AddKeymapping(string label, SavedInputKey savedInputKey)
diff --git a/ParallelRoadTool/ParallelRoadTool.cs b/ParallelRoadTool/ParallelRoadTool.cs
index 4780307..34a087e 100644
--- a/ParallelRoadTool/ParallelRoadTool.cs
+++ b/ParallelRoadTool/ParallelRoadTool.cs
@@ -1,9 +1,17 @@
using System;
using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Xml;
+using System.Xml.Serialization;
using ColossalFramework;
+using ColossalFramework.Globalization;
+using ColossalFramework.Plugins;
using ColossalFramework.UI;
using ICities;
using ParallelRoadTool.Detours;
+using ParallelRoadTool.Extensions.LocaleModels;
using ParallelRoadTool.UI;
using UnityEngine;
@@ -221,6 +229,54 @@ public override void OnCreated(ILoading loading)
{
ParallelRoadTool.Instance = new GameObject("ParallelRoadTool").AddComponent();
}*/
+
+ // Add post locale change event handlers
+ LocaleManager.eventLocaleChanged += OnLocaleChanged;
+
+ DebugUtils.Log("Added locale change event handlers.");
+
+ // Reload the current locale once to effect changes
+ LocaleManager.ForceReload();
+ }
+
+ public override void OnReleased()
+ {
+ // Remove post locale change event handlers
+ LocaleManager.eventLocaleChanged -= OnLocaleChanged;
+
+ DebugUtils.Log("Removed locale change event handlers.");
+
+ // Reload the current locale once to effect changes
+ LocaleManager.ForceReload();
+ }
+
+ private void OnLocaleChanged()
+ {
+ DebugUtils.Log("Locale changed callback started.");
+
+ XmlSerializer serializer = new XmlSerializer(typeof(NameList));
+
+ var assembly = Assembly.GetExecutingAssembly();
+ var resourceName = $"ParallelRoadTool.Localization.{LocaleManager.cultureInfo.TwoLetterISOLanguageName}.xml";
+
+ DebugUtils.Log($"Trying to read {resourceName} localization file...");
+
+ using (Stream stream = assembly.GetManifestResourceStream(resourceName))
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ using (XmlReader xmlStream = XmlReader.Create(reader))
+ {
+ if (serializer.CanDeserialize(xmlStream))
+ {
+ NameList nameList = (NameList)serializer.Deserialize(xmlStream);
+ nameList.Apply();
+ }
+ }
+ }
+
+ DebugUtils.Log($"Namelists {resourceName} applied.");
+
+ DebugUtils.Log("Locale changed callback finished.");
}
public override void OnLevelLoaded(LoadMode mode)
diff --git a/ParallelRoadTool/ParallelRoadTool.csproj b/ParallelRoadTool/ParallelRoadTool.csproj
index 2fd8062..0a24449 100644
--- a/ParallelRoadTool/ParallelRoadTool.csproj
+++ b/ParallelRoadTool/ParallelRoadTool.csproj
@@ -54,6 +54,10 @@
+
+
+
+
@@ -69,7 +73,8 @@
-
+
+
@@ -93,6 +98,7 @@
+
@@ -115,6 +121,7 @@
+
mkdir "%25LOCALAPPDATA%25\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)"
diff --git a/ParallelRoadTool/UI/UIMainWindow.cs b/ParallelRoadTool/UI/UIMainWindow.cs
index 9ec14c4..9d0c311 100644
--- a/ParallelRoadTool/UI/UIMainWindow.cs
+++ b/ParallelRoadTool/UI/UIMainWindow.cs
@@ -1,5 +1,6 @@
using System;
using ColossalFramework;
+using ColossalFramework.Globalization;
using ColossalFramework.UI;
using ParallelRoadTool.UI.Base;
using UnityEngine;
@@ -22,10 +23,11 @@ public class UIMainWindow : UIPanel
private UIOptionsPanel _mainPanel;
private UINetList _netList;
- private NetInfo _netToolSelection;
+ private NetInfo _netToolSelection;
+ private UIRightDragHandle _buttonDragHandle;
+
private UICheckBox _toolToggleButton;
private UICheckBox _snappingToggleButton;
- private UIRightDragHandle _buttonDragHandle;
// We use this to prevent clicks while user is dragging the button
private bool _isDragging;
@@ -39,7 +41,6 @@ public class UIMainWindow : UIPanel
private void UnsubscribeToUIEvents()
{
_toolToggleButton.eventCheckChanged -= ToolToggleButtonOnEventCheckChanged;
- _mainPanel.OnToolToggled -= ToolToggleButtonOnEventCheckChanged;
_snappingToggleButton.eventCheckChanged -= SnappingToggleButtonOnEventCheckChanged;
_buttonDragHandle.eventDragStart -= ButtonDragHandleOnEventDragStart;
_buttonDragHandle.eventDragEnd -= ButtonDragHandleOnEventDragEnd;
@@ -48,7 +49,6 @@ private void UnsubscribeToUIEvents()
private void SubscribeToUIEvents()
{
_toolToggleButton.eventCheckChanged += ToolToggleButtonOnEventCheckChanged;
- _mainPanel.OnToolToggled += ToolToggleButtonOnEventCheckChanged;
_snappingToggleButton.eventCheckChanged += SnappingToggleButtonOnEventCheckChanged;
_buttonDragHandle.eventDragStart += ButtonDragHandleOnEventDragStart;
_buttonDragHandle.eventDragEnd += ButtonDragHandleOnEventDragEnd;
@@ -151,7 +151,7 @@ public override void Start()
space.size = new Vector2(1, 1);
// Add options
- _snappingToggleButton = UIUtil.CreateCheckBox(_mainPanel, "Snapping", "Toggle snapping for all nodes", false);
+ _snappingToggleButton = UIUtil.CreateCheckBox(_mainPanel, "Snapping", Locale.Get("PRT_TOOLTIPS", "SnappingToggleButton"), false);
_snappingToggleButton.relativePosition = new Vector3(166, 38);
_snappingToggleButton.BringToFront();
@@ -168,7 +168,7 @@ public override void Start()
if (button != null)
Destroy(button);
- _toolToggleButton = UIUtil.CreateCheckBox(tsBar, "Parallel", "Parallel Road Tool", false);
+ _toolToggleButton = UIUtil.CreateCheckBox(tsBar, "Parallel", Locale.Get("PRT_TOOLTIPS", "ToolToggleButton"), false);
if (SavedToggleX.value != -1000 && SavedToggleY.value != -1000)
{
_toolToggleButton.absolutePosition = new Vector3(SavedToggleX.value, SavedToggleY.value);
diff --git a/ParallelRoadTool/UI/UINetTypeItem.cs b/ParallelRoadTool/UI/UINetTypeItem.cs
index 391a217..1259234 100644
--- a/ParallelRoadTool/UI/UINetTypeItem.cs
+++ b/ParallelRoadTool/UI/UINetTypeItem.cs
@@ -1,6 +1,7 @@
using System;
using System.Globalization;
using System.Linq;
+using ColossalFramework.Globalization;
using ColossalFramework.UI;
using ParallelRoadTool.UI.Base;
using UnityEngine;
@@ -52,7 +53,7 @@ public override void Start()
DropDown.relativePosition = Vector2.zero;
DropDown.eventSelectedIndexChanged += DropDown_eventSelectedIndexChanged;
- ReverseCheckbox = UIUtil.CreateCheckBox(this, "Reverse", "Toggle reverse road", false);
+ ReverseCheckbox = UIUtil.CreateCheckBox(this, "Reverse", Locale.Get("PRT_TOOLTIPS", "ReverseToggleButton"), false);
ReverseCheckbox.relativePosition = new Vector3(LabelWidth + ColumnPadding, 2);
ReverseCheckbox.eventCheckChanged += ReverseCheckboxOnEventCheckChanged;
@@ -76,7 +77,7 @@ public override void Start()
Label.relativePosition = new Vector3(10, 12);
Label.isVisible = false;
- DeleteButton = UIUtil.CreateUiButton(this, string.Empty, "Remove network", new Vector2(36, 36), "Remove");
+ DeleteButton = UIUtil.CreateUiButton(this, string.Empty, Locale.Get("PRT_TOOLTIPS", "RemoveNetworkButton"), new Vector2(36, 36), "Remove");
DeleteButton.zOrder = 0;
DeleteButton.textScale = 0.8f;
DeleteButton.relativePosition =
@@ -84,7 +85,7 @@ public override void Start()
DeleteButton.eventClicked += DeleteButton_eventClicked;
- AddButton = UIUtil.CreateUiButton(this, string.Empty, "Add network", new Vector2(36, 36), "Add");
+ AddButton = UIUtil.CreateUiButton(this, string.Empty, Locale.Get("PRT_TOOLTIPS", "AddNetworkButton"), new Vector2(36, 36), "Add");
AddButton.zOrder = 1;
AddButton.isVisible = false;
AddButton.textScale = 0.8f;
@@ -132,7 +133,7 @@ public void RenderItem()
DropDown.isVisible = false;
Label.isVisible = true;
AddButton.isVisible = true;
- Label.text = $"Same as selected";
+ Label.text = Locale.Get("PRT_TEXTS", "SameAsSelectedLabel");
}
private void DropDown_eventSelectedIndexChanged(UIComponent component, int index)
diff --git a/ParallelRoadTool/UI/UIOptionsPanel.cs b/ParallelRoadTool/UI/UIOptionsPanel.cs
index 5f8b707..c0d918f 100644
--- a/ParallelRoadTool/UI/UIOptionsPanel.cs
+++ b/ParallelRoadTool/UI/UIOptionsPanel.cs
@@ -5,12 +5,6 @@ namespace ParallelRoadTool.UI
{
public class UIOptionsPanel : UIPanel
{
- #region Events
-
- public event PropertyChangedEventHandler OnToolToggled;
-
- #endregion
-
public override void Start()
{
name = "PRT_OptionsPanel";
@@ -25,7 +19,6 @@ public override void Start()
autoLayout = true;
autoSize = false;
-
DebugUtils.Log($"UIOptionsPanel created {size} | {position}");
}
}
diff --git a/ParallelRoadTool/UI/UITipsWindow.cs b/ParallelRoadTool/UI/UITipsWindow.cs
new file mode 100644
index 0000000..c6c2a61
--- /dev/null
+++ b/ParallelRoadTool/UI/UITipsWindow.cs
@@ -0,0 +1,117 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+
+using ColossalFramework.UI;
+using ParallelRoadTool.UI.Base;
+
+namespace ParallelRoadTool.UI
+{
+ public class UITipsWindow : UILabel
+ {
+ public static UITipsWindow instance;
+
+ private string[] m_tips =
+ {
+ "New in 2.1.0: It is now possible to Export a selection into a file and Import it later in game or in the editor",
+ "Tip: Hold Alt to deselect objects using the marquee selection",
+ "Tip: A building with an orange highlight will despawn when the simulation is running",
+ "Tip: While cloning, Right Click to rotate 45° clockwise",
+ "Tip: Hold Ctrl while rotating to snap to 45° angles",
+ "Tip: Hold Shift to select multiple objects to move at once",
+ "Tip: Use Left Click to drag objects around",
+ "Tip: Hold Alt while dragging objects to reverse the Snapping option",
+ "Tip: While holding Right Click, move the mouse left and right to rotate objects",
+ "Tip: Use Alt for finer movements with the keyboard",
+ "Tip: Use Shift for faster movements with the keyboard",
+ "Tip: When Follow Terrain is disabled, objects will keep their height when moved",
+ "Tip: Right Click to clear the selection",
+ "Tip: Buildings, Trees, Props and Nodes can all be moved",
+ "Tip: Movable objects are highlighted when hovered",
+ "Tip: Hover various things to discover what can be moved",
+ "Tip: Look for the tiny green circle\nThat's the center of rotation",
+ "Tip: Disable tips in the mod options\nEsc > Options > Move It! > Hide tips"
+ };
+ private int m_currentTip = -1;
+
+ public override void Start()
+ {
+ atlas = UIUtil.GetAtlas("Ingame");
+ backgroundSprite = "GenericPanelWhite";
+
+ size = new Vector2(300, 100);
+ padding = new RectOffset(10, 10, 10, 10);
+ textColor = new Color32(109, 109, 109, 255);
+ textScale = 0.9f;
+
+ wordWrap = true;
+ autoHeight = true;
+
+ instance = this;
+
+ NextTip();
+ }
+
+ protected override void OnMouseEnter(UIMouseEventParameter p)
+ {
+ textColor = new Color32(0, 0, 0, 255);
+ }
+
+ protected override void OnMouseLeave(UIMouseEventParameter p)
+ {
+ textColor = new Color32(109, 109, 109, 255);
+ }
+
+ public void NextTip()
+ {
+ m_currentTip = (m_currentTip + 1) % m_tips.Length;
+ text = m_tips[m_currentTip];
+ }
+
+ protected override void OnClick(UIMouseEventParameter p)
+ {
+ NextTip();
+ }
+
+ protected override void OnVisibilityChanged()
+ {
+ if (isVisible)
+ {
+ RefreshPosition();
+ }
+ base.OnVisibilityChanged();
+ }
+
+ protected override void OnSizeChanged()
+ {
+ RefreshPosition();
+ }
+
+ public void RefreshPosition()
+ {
+ float x = GetUIView().GetScreenResolution().x - width - 10f;
+ float y;
+
+ //if (UIToolOptionPanel.instance != null && MoveItTool.marqueeSelection)
+ //{
+ // y = UIToolOptionPanel.instance.filtersPanel.absolutePosition.y - height - 10f;
+ //}
+ //else
+ //{
+ UIComponent thumbnailBar = GetUIView().FindUIComponent("ThumbnailBar");
+ if (thumbnailBar != null)
+ {
+ y = thumbnailBar.absolutePosition.y - height - 30f;
+ }
+ else
+ {
+ y = Screen.height - height - 200;
+ }
+ //}
+
+ absolutePosition = new Vector3(x, y);
+ }
+ }
+}