diff --git a/TLM/.editorconfig b/TLM/.editorconfig
index a480af62f..c971b6413 100644
--- a/TLM/.editorconfig
+++ b/TLM/.editorconfig
@@ -35,11 +35,11 @@ resharper_add_imports_to_deepest_scope=true
resharper_align_linq_query=true
resharper_align_multiline_argument=true
resharper_align_multiline_calls_chain=true
-resharper_align_multiline_expression=true
+resharper_align_multiline_expression=false
resharper_align_multiline_extends_list=true
resharper_align_multiline_for_stmt=true
resharper_align_multiline_parameter=true
-resharper_align_multiple_declaration=true
+resharper_align_multiple_declaration=false
resharper_align_multline_type_parameter_constrains=true
resharper_align_multline_type_parameter_list=true
resharper_autodetect_indent_settings=true
diff --git a/TLM/CSUtil.CameraControl b/TLM/CSUtil.CameraControl
index a908f9cbe..48a551209 160000
--- a/TLM/CSUtil.CameraControl
+++ b/TLM/CSUtil.CameraControl
@@ -1 +1 @@
-Subproject commit a908f9cbe1d3bcfa632ca6557ad6fc028ddbcf76
+Subproject commit 48a5512090b28d98310583ae461ed0d32f867647
diff --git a/TLM/OptionsFramework b/TLM/OptionsFramework
index 9da22408e..378ce2163 160000
--- a/TLM/OptionsFramework
+++ b/TLM/OptionsFramework
@@ -1 +1 @@
-Subproject commit 9da22408e84909f01bd6cba91d06dc94ee7afdce
+Subproject commit 378ce2163c9328218a7d661f82207f495998b5e0
diff --git a/TLM/TLM/Constants.cs b/TLM/TLM/Constants.cs
index ab713ea0d..7c1bf3d4c 100644
--- a/TLM/TLM/Constants.cs
+++ b/TLM/TLM/Constants.cs
@@ -15,17 +15,6 @@ public static class Constants {
///
public static readonly bool[] ALL_BOOL = { false, true };
- ///
- /// Conversion rate from km/h to game speed (also exists in TrafficManager.API.Constants)
- ///
- public const float SPEED_TO_KMPH = 50.0f; // 1.0f equals 50 km/h
-
- ///
- /// Conversion rate from MPH to game speed (also exists in TrafficManager.API.Constants)
- ///
- [UsedImplicitly]
- public const float SPEED_TO_MPH = 32.06f; // 50 km/h converted to mph
-
public static float ByteToFloat(byte b) {
return b * BYTE_TO_FLOAT_SCALE;
}
@@ -41,5 +30,11 @@ public static IServiceFactory ServiceFactory {
}
public static IManagerFactory ManagerFactory => Manager.Impl.ManagerFactory.Instance;
+
+ /// Size for clickable signs used in overlays. Larger than readonly signs.
+ public const float OVERLAY_INTERACTIVE_SIGN_SIZE = 6.0f;
+
+ /// Size for readonly signs used in overlays.
+ public const float OVERLAY_READONLY_SIGN_SIZE = 3.8f;
}
}
\ No newline at end of file
diff --git a/TLM/TLM/Custom/PathFinding/CustomPathFind.cs b/TLM/TLM/Custom/PathFinding/CustomPathFind.cs
index e0ae7ac7b..0cde2b31c 100644
--- a/TLM/TLM/Custom/PathFinding/CustomPathFind.cs
+++ b/TLM/TLM/Custom/PathFinding/CustomPathFind.cs
@@ -147,7 +147,7 @@ private bool Terminated {
}
// stock fields
- public ThreadProfiler m_pathfindProfiler;
+ // public ThreadProfiler m_pathfindProfiler;
private object bufferLock_;
private int bufferMinPos_;
private int bufferMaxPos_;
diff --git a/TLM/TLM/Manager/Impl/OptionsManager.cs b/TLM/TLM/Manager/Impl/OptionsManager.cs
index 6126829b0..5a3379193 100644
--- a/TLM/TLM/Manager/Impl/OptionsManager.cs
+++ b/TLM/TLM/Manager/Impl/OptionsManager.cs
@@ -232,7 +232,7 @@ int LoadBool(int idx, ILegacySerializableOption opt) {
index = LoadBool(index, OptionsMassEditTab.PriorityRoad_CrossMainR);
index = LoadBool(index, OptionsMassEditTab.PriorityRoad_AllowLeftTurns);
- index = LoadBool(index, OptionsMassEditTab.PriorityRoad_EnterBlockedYeild);
+ index = LoadBool(index, OptionsMassEditTab.PriorityRoad_EnterBlockedYield);
index = LoadBool(index, OptionsMassEditTab.PriorityRoad_StopAtEntry);
index = LoadBool(index, OptionsMassEditTab.RoundAboutQuickFix_KeepClearYieldR);
@@ -297,7 +297,7 @@ public byte[] SaveData(ref bool success) {
(byte)(OptionsMassEditTab.PriorityRoad_CrossMainR.Save()),
(byte)(OptionsMassEditTab.PriorityRoad_AllowLeftTurns.Save()),
- (byte)(OptionsMassEditTab.PriorityRoad_EnterBlockedYeild.Save()),
+ (byte)(OptionsMassEditTab.PriorityRoad_EnterBlockedYield.Save()),
(byte)(OptionsMassEditTab.PriorityRoad_StopAtEntry.Save()),
(byte)(OptionsMassEditTab.RoundAboutQuickFix_KeepClearYieldR.Save()),
diff --git a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs
index 35b12b96e..dc847e776 100644
--- a/TLM/TLM/Manager/Impl/SpeedLimitManager.cs
+++ b/TLM/TLM/Manager/Impl/SpeedLimitManager.cs
@@ -3,7 +3,6 @@ namespace TrafficManager.Manager.Impl {
using CSUtil.Commons;
using JetBrains.Annotations;
using System.Collections.Generic;
- using System.Diagnostics;
using System;
using TrafficManager.API.Manager;
using TrafficManager.API.Traffic.Data;
@@ -11,151 +10,101 @@ namespace TrafficManager.Manager.Impl {
#if DEBUG
using TrafficManager.State.ConfigData;
#endif
- using TrafficManager.UI.SubTools.SpeedLimits;
using TrafficManager.Util;
using UnityEngine;
using System.Text;
+ using TrafficManager.API.Traffic;
+ ///
+ /// Handles mod-global storage for speed limit overrides/default speed overrides.
+ ///
public class SpeedLimitManager
: AbstractGeometryObservingManager,
ICustomDataManager>,
ICustomDataManager>,
ISpeedLimitManager {
+ /// Lane types which can have speed limits override.
public const NetInfo.LaneType LANE_TYPES =
NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle;
+ ///
+ /// Vehicle types to filter the roads/lanes which can have speed limit override.
+ ///
public const VehicleInfo.VehicleType VEHICLE_TYPES =
VehicleInfo.VehicleType.Car | VehicleInfo.VehicleType.Tram |
VehicleInfo.VehicleType.Metro | VehicleInfo.VehicleType.Train |
VehicleInfo.VehicleType.Monorail | VehicleInfo.VehicleType.Trolleybus;
- /// Ingame speed units, max possible speed
+ /// Ingame speed units, max possible speed.
public const float MAX_SPEED = 10f * 2f; // 1000 km/h
+ /// Singleton for this class.
public static readonly SpeedLimitManager Instance = new SpeedLimitManager();
- // For each NetInfo (by name) and lane index: custom speed limit
- internal Dictionary CustomLaneSpeedLimitByNetInfoName;
+ /// For each NetInfo (by name) and lane index: custom speed limit.
+ internal readonly Dictionary CustomLaneSpeedLimitByNetInfoName;
- // For each name: NetInfo
- internal Dictionary NetInfoByName;
+ /// For each name: NetInfo.
+ private readonly Dictionary NetInfoByName;
- /// Ingame speed units, minimal speed
+ /// Ingame speed units, minimal speed.
private const float MIN_SPEED = 0.1f; // 5 km/h
- // For each NetInfo (by name) and lane index: game default speed limit
- private Dictionary vanillaLaneSpeedLimitsByNetInfoName;
+ /// For each NetInfo (by name) and lane index: game default speed limit.
+ private readonly Dictionary vanillaLaneSpeedLimitsByNetInfoName_;
- // For each NetInfo (by name): Parent NetInfo (name)
- private Dictionary> childNetInfoNamesByCustomizableNetInfoName;
+ /// For each NetInfo (by name): Parent NetInfo (name).
+ private readonly Dictionary> childNetInfoNamesByCustomizableNetInfoName_;
- private List customizableNetInfos;
+ private List customizableNetInfos_;
private SpeedLimitManager() {
- vanillaLaneSpeedLimitsByNetInfoName = new Dictionary();
+ vanillaLaneSpeedLimitsByNetInfoName_ = new Dictionary();
CustomLaneSpeedLimitByNetInfoName = new Dictionary();
- customizableNetInfos = new List();
- childNetInfoNamesByCustomizableNetInfoName = new Dictionary>();
+ customizableNetInfos_ = new List();
+ childNetInfoNamesByCustomizableNetInfoName_ = new Dictionary>();
NetInfoByName = new Dictionary();
}
- ///
- /// Determines if custom speed limits may be assigned to the given segment.
- ///
- /// Affected segment id
- /// Reference to affected segment
- /// Success
- public bool MayHaveCustomSpeedLimits(ushort segmentId, ref NetSegment segment) {
- if ((segment.m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None)
+ /// Determines if custom speed limits may be assigned to the given segment.
+ /// Reference to affected segment.
+ /// Success.
+ public bool MayHaveCustomSpeedLimits(ref NetSegment segment) {
+ if ((segment.m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None) {
return false;
+ }
+
ItemClass connectionClass = segment.Info.GetConnectionClass();
- return connectionClass.m_service == ItemClass.Service.Road
- || (connectionClass.m_service == ItemClass.Service.PublicTransport
- && (connectionClass.m_subService == ItemClass.SubService.PublicTransportTrain
- || connectionClass.m_subService ==
- ItemClass.SubService.PublicTransportTram
- || connectionClass.m_subService ==
- ItemClass.SubService.PublicTransportMetro
- || connectionClass.m_subService ==
- ItemClass.SubService.PublicTransportMonorail));
+ ItemClass.SubService subService = connectionClass.m_subService;
+ ItemClass.Service service = connectionClass.m_service;
+
+ return service == ItemClass.Service.Road
+ || (service == ItemClass.Service.PublicTransport
+ && (subService == ItemClass.SubService.PublicTransportTrain
+ || subService == ItemClass.SubService.PublicTransportTram
+ || subService == ItemClass.SubService.PublicTransportMetro
+ || subService == ItemClass.SubService.PublicTransportMonorail));
}
///
/// Determines if custom speed limits may be assigned to the given lane info.
///
/// The that you wish to check.
- ///
+ /// Whether lane can have custom speed limits.
public bool MayHaveCustomSpeedLimits(NetInfo.Lane laneInfo) {
return (laneInfo.m_laneType & LANE_TYPES) != NetInfo.LaneType.None
&& (laneInfo.m_vehicleType & VEHICLE_TYPES) != VehicleInfo.VehicleType.None;
}
- ///
- /// Determines the currently set speed limit for the given segment and lane direction in
- /// terms of discrete speed limit levels. An in-game speed limit of 2.0 (e.g. on highway) is
- /// hereby translated into a discrete speed limit value of 100 (km/h).
- ///
- /// Interested in this segment
- /// Direction
- /// Mean speed limit, average for custom and default lane speeds
- public float GetCustomSpeedLimit(ushort segmentId, NetInfo.Direction finalDir) {
- // calculate the currently set mean speed limit
- if (segmentId == 0 ||
- (Singleton.instance.m_segments.m_buffer[segmentId].m_flags &
- NetSegment.Flags.Created) == NetSegment.Flags.None) {
- return 0f;
- }
-
- NetInfo segmentInfo =
- Singleton.instance.m_segments.m_buffer[segmentId].Info;
- uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes;
- var laneIndex = 0;
- var meanSpeedLimit = 0f;
- uint validLanes = 0;
-
- while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) {
- NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex];
- NetInfo.Direction d = laneInfo.m_finalDirection;
-
- if (d != finalDir) {
- goto nextIter;
- }
-
- if (!MayHaveCustomSpeedLimits(laneInfo)) {
- goto nextIter;
- }
-
- float? setSpeedLimit = Flags.GetLaneSpeedLimit(curLaneId);
-
- if (setSpeedLimit != null) {
- meanSpeedLimit += ToGameSpeedLimit(setSpeedLimit.Value); // custom speed limit
- } else {
- meanSpeedLimit += laneInfo.m_speedLimit; // game default
- }
-
- ++validLanes;
-
- nextIter:
- curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane;
- laneIndex++;
- }
-
- if (validLanes > 0) {
- meanSpeedLimit /= validLanes;
- }
-
- return meanSpeedLimit;
- }
-
///
/// Determines the average default speed limit for a given NetInfo object in terms of
/// discrete speed limit levels. An in-game speed limit of 2.0 (e.g. on highway) is hereby
/// translated into a discrete speed limit value of 100 (km/h).
///
- /// Interested in this segment
- /// Direction
- /// Result
+ /// Interested in this segment.
+ /// Direction.
+ /// Result.
[UsedImplicitly]
public float GetAverageDefaultCustomSpeedLimit(NetInfo segmentInfo,
NetInfo.Direction? finalDir = null) {
@@ -188,9 +137,9 @@ public float GetAverageDefaultCustomSpeedLimit(NetInfo segmentInfo,
/// discrete speed limit levels. An in-game speed limit of 2.0 (e.g. on highway) is hereby
/// translated into a discrete speed limit value of 100 (km/h).
///
- /// Interested in this segment
- /// Directoin
- /// Result
+ /// Interested in this segment.
+ /// Direction.
+ /// Result.
[UsedImplicitly]
public ushort GetAverageCustomSpeedLimit(ushort segmentId,
ref NetSegment segment,
@@ -230,58 +179,122 @@ public ushort GetAverageCustomSpeedLimit(ushort segmentId,
return (ushort)Mathf.Round(meanSpeedLimit);
}
+ ///
+ /// Determines the currently set speed limit for the given segment and lane direction in
+ /// terms of discrete speed limit levels. An in-game speed limit of 2.0 (e.g. on highway) is
+ /// hereby translated into a discrete speed limit value of 100 (km/h).
+ ///
+ /// Interested in this segment.
+ /// Direction.
+ ///
+ /// Mean speed limit, average for custom and default lane speeds or null
+ /// if cannot be determined.
+ ///
+ public SpeedValue? GetCustomSpeedLimit(ushort segmentId, NetInfo.Direction finalDir) {
+ // calculate the currently set mean speed limit
+ NetSegment[] segmentsBuffer = Singleton.instance.m_segments.m_buffer;
+ if (segmentId == 0 ||
+ (segmentsBuffer[segmentId].m_flags & NetSegment.Flags.Created) == NetSegment.Flags.None) {
+ return null;
+ }
+
+ NetInfo segmentInfo = segmentsBuffer[segmentId].Info;
+ uint curLaneId = segmentsBuffer[segmentId].m_lanes;
+ var laneIndex = 0;
+ uint validLanes = 0;
+ SpeedValue meanSpeedLimit = new SpeedValue(0f);
+
+ while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) {
+ NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex];
+ NetInfo.Direction d = laneInfo.m_finalDirection;
+
+ if (d != finalDir) {
+ goto nextIter;
+ }
+
+ if (!MayHaveCustomSpeedLimits(laneInfo)) {
+ goto nextIter;
+ }
+
+ SpeedValue? setSpeedLimit = Flags.GetLaneSpeedLimit(curLaneId);
+
+ if (setSpeedLimit.HasValue) {
+ // custom speed limit
+ meanSpeedLimit += setSpeedLimit.Value;
+ } else {
+ // game default (in game units where 1.0f = 50kmph)
+ meanSpeedLimit += new SpeedValue(laneInfo.m_speedLimit);
+ }
+
+ ++validLanes;
+
+ nextIter:
+ curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane;
+ laneIndex++;
+ }
+
+ if (validLanes > 0) {
+ meanSpeedLimit = meanSpeedLimit.Scale(1.0f / validLanes);
+ }
+
+ return meanSpeedLimit;
+ }
+
///
/// Determines the currently set speed limit for the given lane in terms of discrete speed
/// limit levels. An in-game speed limit of 2.0 (e.g. on highway) is hereby translated into
/// a discrete speed limit value of 100 (km/h).
///
- /// Interested in this lane
- /// Speed limit if set for lane, otherwise 0
- public float GetCustomSpeedLimit(uint laneId) {
- // check custom speed limit
- float? setSpeedLimit = Flags.GetLaneSpeedLimit(laneId);
- if (setSpeedLimit != null) {
- return setSpeedLimit.Value;
- }
-
+ /// Interested in this lane.
+ /// Speed limit if set for lane, otherwise 0.
+ public GetSpeedLimitResult GetCustomSpeedLimit(uint laneId) {
+ //----------------------------------------
+ // check custom speed limit for the lane
+ //----------------------------------------
+ SpeedValue? overrideValue = Flags.GetLaneSpeedLimit(laneId);
+
+ //----------------------------
// check default speed limit
- ushort segmentId = Singleton.instance.m_lanes.m_buffer[laneId].m_segment;
- if (!MayHaveCustomSpeedLimits(
- segmentId,
- ref Singleton.instance.m_segments.m_buffer[segmentId])) {
- return 0;
- }
+ //----------------------------
+ NetLane[] laneBuffer = Singleton.instance.m_lanes.m_buffer;
+ NetSegment[] segmentsBuffer = Singleton.instance.m_segments.m_buffer;
+ ushort segmentId = laneBuffer[laneId].m_segment;
- NetInfo segmentInfo =
- Singleton.instance.m_segments.m_buffer[segmentId].Info;
+ if (!MayHaveCustomSpeedLimits(ref segmentsBuffer[segmentId])) {
+ return new GetSpeedLimitResult(GetSpeedLimitResult.ResultType.NotAvailable);
+ }
- uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes;
+ NetInfo segmentInfo = segmentsBuffer[segmentId].Info;
+ uint curLaneId = segmentsBuffer[segmentId].m_lanes;
int laneIndex = 0;
while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) {
if (curLaneId == laneId) {
NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex];
- return !MayHaveCustomSpeedLimits(laneInfo) ? 0 : laneInfo.m_speedLimit;
+ var defaultValue = new SpeedValue(laneInfo.m_speedLimit);
+ return MayHaveCustomSpeedLimits(laneInfo)
+ ? new GetSpeedLimitResult(overrideValue, defaultValue)
+ : new GetSpeedLimitResult(GetSpeedLimitResult.ResultType.NotAvailable);
}
laneIndex++;
- curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane;
+ curLaneId = laneBuffer[curLaneId].m_nextLane;
}
Log.Warning($"Speed limit for lane {laneId} could not be determined.");
- return 0; // no speed limit found
+ return new GetSpeedLimitResult(GetSpeedLimitResult.ResultType.NotAvailable);
}
- ///
- /// Determines the currently set speed limit for the given lane in terms of game (floating
- /// point) speed limit levels.
- ///
- /// Interested in this lane
- /// Km/h in lane converted to game speed float
- [UsedImplicitly]
- public float GetGameSpeedLimit(uint laneId) {
- return ToGameSpeedLimit(GetCustomSpeedLimit(laneId));
- }
+ // ///
+ // /// Determines the currently set speed limit for the given lane in terms of game (floating
+ // /// point) speed limit levels.
+ // ///
+ // /// Interested in this lane.
+ // /// Km/h in lane converted to game speed float.
+ // [UsedImplicitly]
+ // public float GetGameSpeedLimit(uint laneId) {
+ // return ToGameSpeedLimit(GetCustomSpeedLimit(laneId));
+ // }
public float GetLockFreeGameSpeedLimit(ushort segmentId,
byte laneIndex,
@@ -316,10 +329,12 @@ public float ToGameSpeedLimit(float customSpeedLimit) {
: customSpeedLimit;
}
+#if FGSFDS_DISABLED_CODE
///
/// Explicitly stores currently set speed limits for all segments of the specified NetInfo
///
/// The for which speed limits should be stored.
+ [Obsolete("Not used, delete this")]
public void FixCurrentSpeedLimits(NetInfo info) {
if (info == null) {
#if DEBUG
@@ -334,7 +349,7 @@ public void FixCurrentSpeedLimits(NetInfo info) {
// return;
// }
- if (!customizableNetInfos.Contains(info)) {
+ if (!customizableNetInfos_.Contains(info)) {
return;
}
@@ -343,21 +358,26 @@ public void FixCurrentSpeedLimits(NetInfo info) {
continue;
}
- ushort segmentId =
- Singleton.instance.m_lanes.m_buffer[laneId].m_segment;
- NetInfo laneInfo =
- Singleton.instance.m_segments.m_buffer[segmentId].Info;
+ NetLane[] lanesBuffer = Singleton.instance.m_lanes.m_buffer;
+ NetSegment[] segmentsBuffer = Singleton.instance.m_segments.m_buffer;
+ ushort segmentId = lanesBuffer[laneId].m_segment;
+ NetInfo laneInfo = segmentsBuffer[segmentId].Info;
if (laneInfo.name != info.name
- && (!childNetInfoNamesByCustomizableNetInfoName.ContainsKey(info.name)
- || !childNetInfoNamesByCustomizableNetInfoName[info.name]
- .Contains(laneInfo.name))) {
+ && (!childNetInfoNamesByCustomizableNetInfoName_.ContainsKey(info.name)
+ || !childNetInfoNamesByCustomizableNetInfoName_[info.name].Contains(laneInfo.name))) {
continue;
}
- Flags.SetLaneSpeedLimit(laneId, GetCustomSpeedLimit(laneId));
+ GetSpeedLimitResult laneCustomSpeedLimit = GetCustomSpeedLimit(laneId);
+ if (laneCustomSpeedLimit.OverrideValue.HasValue) {
+ Flags.SetLaneSpeedLimit(
+ laneId: laneId,
+ action: SetSpeedLimitAction.SetSpeed(laneCustomSpeedLimit.OverrideValue.Value));
+ }
}
}
+#endif // disabled code
///
/// Explicitly clear currently set speed limits for all segments of the specified NetInfo
@@ -375,7 +395,7 @@ public void ClearCurrentSpeedLimits(NetInfo info) {
// return;
// }
- if (!customizableNetInfos.Contains(info)) {
+ if (!customizableNetInfos_.Contains(info)) {
return;
}
@@ -391,8 +411,8 @@ public void ClearCurrentSpeedLimits(NetInfo info) {
.Info;
if (laneInfo.name != info.name &&
- (!childNetInfoNamesByCustomizableNetInfoName.ContainsKey(info.name) ||
- !childNetInfoNamesByCustomizableNetInfoName[info.name]
+ (!childNetInfoNamesByCustomizableNetInfoName_.ContainsKey(info.name) ||
+ !childNetInfoNamesByCustomizableNetInfoName_[info.name]
.Contains(laneInfo.name))) {
continue;
}
@@ -426,7 +446,7 @@ public float GetVanillaNetInfoSpeedLimit(NetInfo info, bool roundToSignLimits =
// }
string infoName = info.name;
- if (!vanillaLaneSpeedLimitsByNetInfoName.TryGetValue(
+ if (!vanillaLaneSpeedLimitsByNetInfoName_.TryGetValue(
infoName,
out float[] vanillaSpeedLimits)) {
return 0;
@@ -447,24 +467,17 @@ public float GetVanillaNetInfoSpeedLimit(NetInfo info, bool roundToSignLimits =
/// Determines the custom speed limit of the given NetInfo.
///
/// the NetInfo of which the custom speed limit should be determined
- /// -1 if no custom speed limit was set
+ /// -1 if no custom speed limit was set.
public float GetCustomNetInfoSpeedLimit(NetInfo info) {
if (info == null) {
- Log._DebugOnlyWarning(
- $"SpeedLimitManager.SetCustomNetInfoSpeedLimitIndex: info is null!");
+ Log._DebugOnlyWarning("SpeedLimitManager.GetCustomNetInfoSpeedLimit: info is null!");
return -1;
}
- // Resharper warning: condition always false
- // if (info.name == null) {
- // Log._DebugOnlyWarning($"SpeedLimitManager.SetCustomNetInfoSpeedLimitIndex: info.name is null!");
- // return -1;
- // }
-
string infoName = info.name;
return !CustomLaneSpeedLimitByNetInfoName.TryGetValue(infoName, out float speedLimit)
- ? GetVanillaNetInfoSpeedLimit(info, true)
- : speedLimit;
+ ? GetVanillaNetInfoSpeedLimit(info: info, roundToSignLimits: true)
+ : speedLimit;
}
///
@@ -472,6 +485,7 @@ public float GetCustomNetInfoSpeedLimit(NetInfo info) {
///
/// the NetInfo for which the custom speed limit should be set
/// The speed value to set in game speed units
+ // TODO: The speed limit manager only supports default speed limit overrides per road type, not per lane
public void SetCustomNetInfoSpeedLimit(NetInfo info, float customSpeedLimit) {
if (info == null) {
Log._DebugOnlyWarning($"SetCustomNetInfoSpeedLimitIndex: info is null!");
@@ -494,7 +508,7 @@ public void SetCustomNetInfoSpeedLimit(NetInfo info, float customSpeedLimit) {
#endif
UpdateNetInfoGameSpeedLimit(info, gameSpeedLimit);
- if (childNetInfoNamesByCustomizableNetInfoName.TryGetValue(
+ if (childNetInfoNamesByCustomizableNetInfoName_.TryGetValue(
infoName,
out List childNetInfoNames)) {
foreach (string childNetInfoName in childNetInfoNames) {
@@ -544,22 +558,19 @@ private void UpdateNetInfoGameSpeedLimit(NetInfo info, float gameSpeedLimit) {
}
/// Sets the speed limit of a given lane.
- ///
- ///
- ///
- ///
- /// Game speed units, 0=unlimited, null=default
- /// Returns true if successful, otherwise false.
- public bool SetSpeedLimit(ushort segmentId,
- uint laneIndex,
- NetInfo.Lane laneInfo,
- uint laneId,
- float ?speedLimit) {
+ /// Game speed units, unlimited, or default.
+ /// Success.
+ public bool SetLaneSpeedLimit(ushort segmentId,
+ uint laneIndex,
+ NetInfo.Lane laneInfo,
+ uint laneId,
+ SetSpeedLimitAction action) {
if (!MayHaveCustomSpeedLimits(laneInfo)) {
return false;
}
- if (speedLimit != null && !IsValidRange((float)speedLimit)) {
+ if (action.Type != SetSpeedLimitAction.ValueType.Default
+ && !IsValidRange(action.Value.GameUnits)) {
return false;
}
@@ -567,90 +578,88 @@ public bool SetSpeedLimit(ushort segmentId,
return false;
}
- if (speedLimit != null) {
- Flags.SetLaneSpeedLimit(segmentId, laneIndex, laneId, speedLimit);
+ if (action.Type != SetSpeedLimitAction.ValueType.Default) {
+ Flags.SetLaneSpeedLimit(segmentId, laneIndex, laneId, action);
} else {
Flags.RemoveLaneSpeedLimit(laneId);
}
return true;
}
- ///
- /// Sets speed limit for all configurable lanes.
- ///
- /// Speed limit in game units, or null to restore defaults
- /// falseif there are no configurable lanes. true if any speed limits were applied.
- public bool SetSpeedLimit(ushort segmentId, float? speedLimit) {
+ /// Sets speed limit for all configurable lanes.
+ /// Speed limit in game units, or null to restore defaults.
+ ///
+ /// falseif there are no configurable lanes.
+ /// true if any speed limits were applied.
+ ///
+ public bool SetSegmentSpeedLimit(ushort segmentId, SetSpeedLimitAction action) {
bool ret = false;
+
foreach(NetInfo.Direction finaldir in Enum.GetValues(typeof(NetInfo.Direction))) {
- ret |= SetSpeedLimit(segmentId, finaldir, speedLimit);
+ ret |= SetSegmentSpeedLimit(segmentId, finaldir, action);
}
+
return ret;
}
- ///
- /// Sets the speed limit of a given segment and lane direction.
- ///
- ///
- ///
- /// Game speed units, 0=unlimited, null=default
- ///
- public bool SetSpeedLimit(ushort segmentId, NetInfo.Direction finalDir, float ?speedLimit) {
- if (!MayHaveCustomSpeedLimits(
- segmentId,
- ref Singleton.instance.m_segments.m_buffer[segmentId])) {
+ /// Sets the speed limit of a given segment and lane direction.
+ /// Segment id.
+ /// Direction.
+ /// Game speed units, unlimited, or reset to default.
+ /// Success.
+ public bool SetSegmentSpeedLimit(ushort segmentId,
+ NetInfo.Direction finalDir,
+ SetSpeedLimitAction action) {
+ NetSegment[] segmentsBuffer = Singleton.instance.m_segments.m_buffer;
+
+ if (!MayHaveCustomSpeedLimits(ref segmentsBuffer[segmentId])) {
return false;
}
- if (speedLimit != null && !IsValidRange((float)speedLimit)) {
+ if (action.Type == SetSpeedLimitAction.ValueType.SetSpeed
+ && !IsValidRange(action.Value.GameUnits)) {
return false;
}
- NetInfo segmentInfo =
- Singleton.instance.m_segments.m_buffer[segmentId].Info;
+ NetInfo segmentInfo = segmentsBuffer[segmentId].Info;
if (segmentInfo == null) {
-#if DEBUG
- Log.Warning($"SpeedLimitManager.SetSpeedLimit: info is null!");
-#endif
+ Log._DebugOnlyWarning($"SpeedLimitManager.SetSpeedLimit: info is null!");
return false;
}
if (segmentInfo.m_lanes == null) {
-#if DEBUG
- Log.Warning($"SpeedLimitManager.SetSpeedLimit: info.m_lanes is null!");
-#endif
+ Log._DebugOnlyWarning($"SpeedLimitManager.SetSpeedLimit: info.m_lanes is null!");
return false;
}
- uint curLaneId = Singleton.instance.m_segments.m_buffer[segmentId].m_lanes;
+ uint curLaneId = segmentsBuffer[segmentId].m_lanes;
int laneIndex = 0;
+ NetLane[] laneBuffer = Singleton.instance.m_lanes.m_buffer;
+ //-------------------------
+ // For each affected lane
+ //-------------------------
while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) {
NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex];
NetInfo.Direction d = laneInfo.m_finalDirection;
- if (d != finalDir) {
- goto nextIter;
- }
- if (!MayHaveCustomSpeedLimits(laneInfo)) {
- goto nextIter;
- }
-
- if (speedLimit != null) {
- Log._Debug(
- $"SpeedLimitManager: Setting speed limit of lane {curLaneId} " +
- $"to {speedLimit * Constants.SPEED_TO_KMPH}");
- Flags.SetLaneSpeedLimit(curLaneId, speedLimit);
- } else {
- Log._Debug(
- $"SpeedLimitManager: Setting speed limit of lane {curLaneId} " +
- $"to default");
- Flags.RemoveLaneSpeedLimit(curLaneId);
+ if (d == finalDir && MayHaveCustomSpeedLimits(laneInfo)) {
+ if (action.Type == SetSpeedLimitAction.ValueType.Default) {
+ // Setting to 'Default' will instead remove the override
+ Log._Debug(
+ $"SpeedLimitManager: Setting speed limit of lane {curLaneId} " +
+ $"to default");
+ Flags.RemoveLaneSpeedLimit(curLaneId);
+ } else {
+ Log._Debug(
+ $"SpeedLimitManager: Setting speed limit of lane {curLaneId} " +
+ $"to {action.Value}");
+ Flags.SetLaneSpeedLimit(curLaneId, action);
+ }
}
- nextIter:
- curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane;
+ curLaneId = laneBuffer[curLaneId].m_nextLane;
laneIndex++;
}
@@ -658,7 +667,7 @@ public bool SetSpeedLimit(ushort segmentId, NetInfo.Direction finalDir, float ?s
}
public List GetCustomizableNetInfos() {
- return customizableNetInfos;
+ return customizableNetInfos_;
}
public override void OnBeforeLoadData() {
@@ -674,10 +683,10 @@ public override void OnBeforeLoadData() {
int numLoaded = PrefabCollection.LoadedCount();
// todo: move this to a Reset() or Clear() method?
- vanillaLaneSpeedLimitsByNetInfoName.Clear();
- customizableNetInfos.Clear();
+ vanillaLaneSpeedLimitsByNetInfoName_.Clear();
+ customizableNetInfos_.Clear();
CustomLaneSpeedLimitByNetInfoName.Clear();
- childNetInfoNamesByCustomizableNetInfoName.Clear();
+ childNetInfoNamesByCustomizableNetInfoName_.Clear();
NetInfoByName.Clear();
List mainNetInfos = new List();
@@ -750,7 +759,7 @@ public override void OnBeforeLoadData() {
continue;
}
- if (!vanillaLaneSpeedLimitsByNetInfoName.ContainsKey(infoName)) {
+ if (!vanillaLaneSpeedLimitsByNetInfoName_.ContainsKey(infoName)) {
if (info.m_lanes == null) {
log.AppendFormat(
"- Skipped: NetInfo #{0} ({1}) - m_lanes is null!\n",
@@ -773,7 +782,7 @@ public override void OnBeforeLoadData() {
vanillaLaneSpeedLimits[k] = info.m_lanes[k].m_speedLimit;
}
- vanillaLaneSpeedLimitsByNetInfoName[infoName] = vanillaLaneSpeedLimits;
+ vanillaLaneSpeedLimitsByNetInfoName_[infoName] = vanillaLaneSpeedLimits;
}
}
@@ -848,10 +857,10 @@ public override void OnBeforeLoadData() {
Log._Trace(
$"Identified child NetInfo {infoName} of parent {parentInfo.name}");
- if (!childNetInfoNamesByCustomizableNetInfoName.TryGetValue(
+ if (!childNetInfoNamesByCustomizableNetInfoName_.TryGetValue(
parentInfo.name,
out List childNetInfoNames)) {
- childNetInfoNamesByCustomizableNetInfoName[parentInfo.name] =
+ childNetInfoNamesByCustomizableNetInfoName_[parentInfo.name] =
childNetInfoNames = new List();
}
@@ -869,7 +878,7 @@ public override void OnBeforeLoadData() {
}
}
- customizableNetInfos = mainNetInfos;
+ customizableNetInfos_ = mainNetInfos;
}
protected override void HandleInvalidSegment(ref ExtSegment seg) {
@@ -880,9 +889,7 @@ protected override void HandleInvalidSegment(ref ExtSegment seg) {
int laneIndex = 0;
while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) {
- // NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex];
- // float? setSpeedLimit = Flags.getLaneSpeedLimit(curLaneId);
- Flags.SetLaneSpeedLimit(curLaneId, null);
+ Flags.SetLaneSpeedLimit(curLaneId, SetSpeedLimitAction.Default());
curLaneId = Singleton.instance.m_lanes.m_buffer[curLaneId].m_nextLane;
laneIndex++;
@@ -928,10 +935,12 @@ public bool LoadData(List data) {
"SpeedLimitManager.LoadData: Loading lane speed limit: " +
$"lane {laneSpeedLimit.laneId} = {laneSpeedLimit.speedLimit} km/h");
#endif
- float kmph = laneSpeedLimit.speedLimit /
- Constants.SPEED_TO_KMPH; // convert to game units
+ // convert to game units
+ float units = laneSpeedLimit.speedLimit / ApiConstants.SPEED_TO_KMPH;
- Flags.SetLaneSpeedLimit(laneSpeedLimit.laneId, kmph);
+ Flags.SetLaneSpeedLimit(
+ laneSpeedLimit.laneId,
+ SetSpeedLimitAction.SetSpeed(new SpeedValue(units)));
} else {
#if DEBUG
Log._DebugIf(debugSpeedLimits, () =>
diff --git a/TLM/TLM/RedirectionFramework b/TLM/TLM/RedirectionFramework
index b7f0f51eb..68a47a187 160000
--- a/TLM/TLM/RedirectionFramework
+++ b/TLM/TLM/RedirectionFramework
@@ -1 +1 @@
-Subproject commit b7f0f51ebfb51fead71b4319e66c7d86bd142434
+Subproject commit 68a47a18757ea50269498d7db8a5f3e70318aa88
diff --git a/TLM/TLM/Resources/MainMenu/LegacyButtons.png b/TLM/TLM/Resources/MainMenu/LegacyButtons.png
deleted file mode 100644
index 7b50758b5..000000000
Binary files a/TLM/TLM/Resources/MainMenu/LegacyButtons.png and /dev/null differ
diff --git a/TLM/TLM/Resources/SpeedLimits/EditSegments-fg-active.png b/TLM/TLM/Resources/SpeedLimits/EditSegments-fg-active.png
new file mode 100644
index 000000000..047e916ab
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/EditSegments-fg-active.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/EditSegments-fg-normal.png b/TLM/TLM/Resources/SpeedLimits/EditSegments-fg-normal.png
new file mode 100644
index 000000000..60a90074b
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/EditSegments-fg-normal.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/0.png b/TLM/TLM/Resources/SpeedLimits/Kmh/0.png
index 075830e4e..1166524e8 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/0.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/0.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/10.png b/TLM/TLM/Resources/SpeedLimits/Kmh/10.png
index e9e76b026..d35738075 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/10.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/10.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/100.png b/TLM/TLM/Resources/SpeedLimits/Kmh/100.png
index b9f164a07..c6c99361a 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/100.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/100.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/105.png b/TLM/TLM/Resources/SpeedLimits/Kmh/105.png
index fb0ca66fa..32a36deca 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/105.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/105.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/110.png b/TLM/TLM/Resources/SpeedLimits/Kmh/110.png
index 28f2b0f93..91b9a9783 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/110.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/110.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/115.png b/TLM/TLM/Resources/SpeedLimits/Kmh/115.png
index f61ec322f..422ec4b4a 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/115.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/115.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/120.png b/TLM/TLM/Resources/SpeedLimits/Kmh/120.png
index f35423ce2..bbfe04c16 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/120.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/120.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/125.png b/TLM/TLM/Resources/SpeedLimits/Kmh/125.png
index 004225955..f64d197b9 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/125.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/125.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/130.png b/TLM/TLM/Resources/SpeedLimits/Kmh/130.png
index 0c305d250..420f2159b 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/130.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/130.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/135.png b/TLM/TLM/Resources/SpeedLimits/Kmh/135.png
index 253ad482f..87384a8a1 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/135.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/135.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/140.png b/TLM/TLM/Resources/SpeedLimits/Kmh/140.png
index 5cf5aabb7..e87444249 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/140.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/140.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/15.png b/TLM/TLM/Resources/SpeedLimits/Kmh/15.png
index 120fed7b7..b6700950f 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/15.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/15.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/20.png b/TLM/TLM/Resources/SpeedLimits/Kmh/20.png
index 8ea15abdc..48260446e 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/20.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/20.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/25.png b/TLM/TLM/Resources/SpeedLimits/Kmh/25.png
index 108f2ad9e..3169441fd 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/25.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/25.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/30.png b/TLM/TLM/Resources/SpeedLimits/Kmh/30.png
index 9df4b9065..cb4f41c9d 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/30.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/30.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/35.png b/TLM/TLM/Resources/SpeedLimits/Kmh/35.png
index 54b56e4f6..42b60f505 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/35.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/35.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/40.png b/TLM/TLM/Resources/SpeedLimits/Kmh/40.png
index e6811c3b0..c7f280fcc 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/40.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/40.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/45.png b/TLM/TLM/Resources/SpeedLimits/Kmh/45.png
index df1406954..d26aee3da 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/45.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/45.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/5.png b/TLM/TLM/Resources/SpeedLimits/Kmh/5.png
index c6ae24a07..1aafac061 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/5.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/5.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/50.png b/TLM/TLM/Resources/SpeedLimits/Kmh/50.png
index 898d38e61..7fcf68d01 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/50.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/50.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/55.png b/TLM/TLM/Resources/SpeedLimits/Kmh/55.png
index 656194e74..cfaab00df 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/55.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/55.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/60.png b/TLM/TLM/Resources/SpeedLimits/Kmh/60.png
index 967b1e5e9..c1deb4bef 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/60.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/60.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/65.png b/TLM/TLM/Resources/SpeedLimits/Kmh/65.png
index 99e967802..c3d093bea 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/65.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/65.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/70.png b/TLM/TLM/Resources/SpeedLimits/Kmh/70.png
index 4b32b6c9b..ab58467ef 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/70.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/70.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/75.png b/TLM/TLM/Resources/SpeedLimits/Kmh/75.png
index d85c6fff1..e66673c87 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/75.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/75.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/80.png b/TLM/TLM/Resources/SpeedLimits/Kmh/80.png
index 4da791dd5..9b5af21e1 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/80.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/80.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/85.png b/TLM/TLM/Resources/SpeedLimits/Kmh/85.png
index 63615a3ea..3cac29227 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/85.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/85.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/90.png b/TLM/TLM/Resources/SpeedLimits/Kmh/90.png
index 1187f77bf..33f4b602f 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/90.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/90.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/95.png b/TLM/TLM/Resources/SpeedLimits/Kmh/95.png
index 2c4022279..5f7aa0b6b 100644
Binary files a/TLM/TLM/Resources/SpeedLimits/Kmh/95.png and b/TLM/TLM/Resources/SpeedLimits/Kmh/95.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/Kmh/roadsigns-km-german.svg b/TLM/TLM/Resources/SpeedLimits/Kmh/roadsigns-km-german.svg
new file mode 100644
index 000000000..0a9a5568c
--- /dev/null
+++ b/TLM/TLM/Resources/SpeedLimits/Kmh/roadsigns-km-german.svg
@@ -0,0 +1,2923 @@
+
+
+
+
diff --git a/TLM/TLM/Resources/SpeedLimits/MphToggle-fg-active.png b/TLM/TLM/Resources/SpeedLimits/MphToggle-fg-active.png
new file mode 100644
index 000000000..70c911240
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/MphToggle-fg-active.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/MphToggle-fg-normal.png b/TLM/TLM/Resources/SpeedLimits/MphToggle-fg-normal.png
new file mode 100644
index 000000000..f3cdcf527
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/MphToggle-fg-normal.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/0.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/0.png
new file mode 100644
index 000000000..dd53b3c0e
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/0.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/10.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/10.png
new file mode 100644
index 000000000..430555b85
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/10.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/100.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/100.png
new file mode 100644
index 000000000..a697f2f6a
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/100.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/105.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/105.png
new file mode 100644
index 000000000..7efb9fb64
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/105.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/110.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/110.png
new file mode 100644
index 000000000..246431994
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/110.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/115.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/115.png
new file mode 100644
index 000000000..b6ad3f7d2
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/115.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/120.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/120.png
new file mode 100644
index 000000000..08bfaa53f
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/120.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/125.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/125.png
new file mode 100644
index 000000000..a21ced18f
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/125.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/130.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/130.png
new file mode 100644
index 000000000..ec402cec2
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/130.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/135.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/135.png
new file mode 100644
index 000000000..e974a7e27
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/135.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/140.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/140.png
new file mode 100644
index 000000000..63749e9f9
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/140.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/15.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/15.png
new file mode 100644
index 000000000..b2fd5c841
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/15.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/20.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/20.png
new file mode 100644
index 000000000..b6745bef3
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/20.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/25.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/25.png
new file mode 100644
index 000000000..d9774cf1c
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/25.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/30.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/30.png
new file mode 100644
index 000000000..d03a47ca3
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/30.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/35.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/35.png
new file mode 100644
index 000000000..a8f2c2291
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/35.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/40.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/40.png
new file mode 100644
index 000000000..12c97d925
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/40.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/45.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/45.png
new file mode 100644
index 000000000..93fed2af2
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/45.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/5.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/5.png
new file mode 100644
index 000000000..81f1f0d82
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/5.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/50.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/50.png
new file mode 100644
index 000000000..4caf35562
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/50.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/55.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/55.png
new file mode 100644
index 000000000..eaca600dd
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/55.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/60.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/60.png
new file mode 100644
index 000000000..4fffdfb6b
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/60.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/65.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/65.png
new file mode 100644
index 000000000..c8dce6a2b
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/65.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/70.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/70.png
new file mode 100644
index 000000000..4e6154ddd
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/70.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/75.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/75.png
new file mode 100644
index 000000000..3aa48ec41
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/75.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/80.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/80.png
new file mode 100644
index 000000000..90797ab70
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/80.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/85.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/85.png
new file mode 100644
index 000000000..a5d02b875
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/85.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/90.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/90.png
new file mode 100644
index 000000000..bde567edf
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/90.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/95.png b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/95.png
new file mode 100644
index 000000000..12b09cb3b
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/95.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/RoadDefaults/roadsigns-default.svg b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/roadsigns-default.svg
new file mode 100644
index 000000000..e89fa212d
--- /dev/null
+++ b/TLM/TLM/Resources/SpeedLimits/RoadDefaults/roadsigns-default.svg
@@ -0,0 +1,1181 @@
+
+
+
+
diff --git a/TLM/TLM/Resources/SpeedLimits/kmh_2.png b/TLM/TLM/Resources/SpeedLimits/kmh_2.png
new file mode 100644
index 000000000..e2176c8e3
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/kmh_2.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/mph_2.png b/TLM/TLM/Resources/SpeedLimits/mph_2.png
new file mode 100644
index 000000000..137639f4b
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/mph_2.png differ
diff --git a/TLM/TLM/Resources/SpeedLimits/standard.png b/TLM/TLM/Resources/SpeedLimits/standard.png
new file mode 100644
index 000000000..691c085a2
Binary files /dev/null and b/TLM/TLM/Resources/SpeedLimits/standard.png differ
diff --git a/TLM/TLM/State/ConfigData/Main.cs b/TLM/TLM/State/ConfigData/Main.cs
index 25227f0e7..647d20e9d 100644
--- a/TLM/TLM/State/ConfigData/Main.cs
+++ b/TLM/TLM/State/ConfigData/Main.cs
@@ -14,13 +14,15 @@ public class Main {
public int MainMenuButtonY = 10;
public bool MainMenuButtonPosLocked = false;
- ///
- /// Main menu position
- ///
+ /// Main menu position.
public int MainMenuX = MainMenuWindow.DEFAULT_MENU_X;
public int MainMenuY = MainMenuWindow.DEFAULT_MENU_Y;
public bool MainMenuPosLocked = false;
+ /// Speed Limits tool window position.
+ public int SpeedLimitsWindowX = 0;
+ public int SpeedLimitsWindowY = 0;
+
///
/// Already displayed tutorial messages
///
@@ -74,7 +76,7 @@ public class Main {
///
/// Selected theme for road signs when MPH is active.
///
- public MphSignStyle MphRoadSignStyle = MphSignStyle.SquareUS;
+ public SpeedLimitSignTheme MphRoadSignStyle = SpeedLimitSignTheme.RectangularUS;
public void AddDisplayedTutorialMessage(string messageKey) {
HashSet newMessages = DisplayedTutorialMessages != null
diff --git a/TLM/TLM/State/Flags.cs b/TLM/TLM/State/Flags.cs
index 58f69efa0..49d2050dd 100644
--- a/TLM/TLM/State/Flags.cs
+++ b/TLM/TLM/State/Flags.cs
@@ -7,6 +7,7 @@ namespace TrafficManager.State {
using System.Collections.Generic;
using System.Threading;
using System;
+ using TrafficManager.API.Traffic.Data;
using TrafficManager.API.Traffic.Enums;
using TrafficManager.Manager.Impl;
using TrafficManager.State.ConfigData;
@@ -485,7 +486,7 @@ internal static bool CheckLane(uint laneId) {
(NetSegment.Flags.Created | NetSegment.Flags.Deleted)) == NetSegment.Flags.Created;
}
- public static void SetLaneSpeedLimit(uint laneId, float? speedLimit) {
+ public static void SetLaneSpeedLimit(uint laneId, SetSpeedLimitAction action) {
if (!CheckLane(laneId)) {
return;
}
@@ -497,7 +498,7 @@ public static void SetLaneSpeedLimit(uint laneId, float? speedLimit) {
while (laneIndex < segmentInfo.m_lanes.Length && curLaneId != 0u) {
if (curLaneId == laneId) {
- SetLaneSpeedLimit(segmentId, laneIndex, laneId, speedLimit);
+ SetLaneSpeedLimit(segmentId, laneIndex, laneId, action);
return;
}
@@ -507,28 +508,32 @@ public static void SetLaneSpeedLimit(uint laneId, float? speedLimit) {
}
public static void RemoveLaneSpeedLimit(uint laneId) {
- SetLaneSpeedLimit(laneId, null);
+ SetLaneSpeedLimit(laneId, SetSpeedLimitAction.Default());
}
public static void SetLaneSpeedLimit(ushort segmentId,
uint laneIndex,
uint laneId,
- float? speedLimit) {
+ SetSpeedLimitAction action) {
if (segmentId <= 0 || laneId <= 0) {
return;
}
- if ((Singleton.instance.m_segments.m_buffer[segmentId].m_flags &
+ NetSegment[] segmentsBuffer = Singleton.instance.m_segments.m_buffer;
+
+ if ((segmentsBuffer[segmentId].m_flags &
(NetSegment.Flags.Created | NetSegment.Flags.Deleted)) != NetSegment.Flags.Created) {
return;
}
- if (((NetLane.Flags)Singleton.instance.m_lanes.m_buffer[laneId].m_flags &
+ NetLane[] lanesBuffer = Singleton.instance.m_lanes.m_buffer;
+
+ if (((NetLane.Flags)lanesBuffer[laneId].m_flags &
(NetLane.Flags.Created | NetLane.Flags.Deleted)) != NetLane.Flags.Created) {
return;
}
- NetInfo segmentInfo = Singleton.instance.m_segments.m_buffer[segmentId].Info;
+ NetInfo segmentInfo = segmentsBuffer[segmentId].Info;
if (laneIndex >= segmentInfo.m_lanes.Length) {
return;
@@ -541,33 +546,41 @@ public static void SetLaneSpeedLimit(ushort segmentId,
$"Flags.setLaneSpeedLimit: setting speed limit of lane index {laneIndex} @ seg. " +
$"{segmentId} to {speedLimit}");
#endif
- if (speedLimit == null) {
- laneSpeedLimit.Remove(laneId);
+ switch (action.Type) {
+ case SetSpeedLimitAction.ValueType.Default: {
+ laneSpeedLimit.Remove(laneId);
- if (laneSpeedLimitArray[segmentId] == null) {
- return;
- }
+ if (laneSpeedLimitArray[segmentId] == null) {
+ return;
+ }
- if (laneIndex >= laneSpeedLimitArray[segmentId].Length) {
- return;
- }
+ if (laneIndex >= laneSpeedLimitArray[segmentId].Length) {
+ return;
+ }
- laneSpeedLimitArray[segmentId][laneIndex] = null;
- } else {
- laneSpeedLimit[laneId] = speedLimit.Value;
-
- // save speed limit into the fast-access array.
- // (1) ensure that the array is defined and large enough
- if (laneSpeedLimitArray[segmentId] == null) {
- laneSpeedLimitArray[segmentId] = new float?[segmentInfo.m_lanes.Length];
- } else if (laneSpeedLimitArray[segmentId].Length < segmentInfo.m_lanes.Length) {
- float?[] oldArray = laneSpeedLimitArray[segmentId];
- laneSpeedLimitArray[segmentId] = new float?[segmentInfo.m_lanes.Length];
- Array.Copy(oldArray, laneSpeedLimitArray[segmentId], oldArray.Length);
+ laneSpeedLimitArray[segmentId][laneIndex] = null;
+ break;
+ }
+ case SetSpeedLimitAction.ValueType.Unlimited:
+ case SetSpeedLimitAction.ValueType.SetSpeed: {
+ laneSpeedLimit[laneId] = action.Value.GetKmph();
+
+ // save speed limit into the fast-access array.
+ // (1) ensure that the array is defined and large enough
+ if (laneSpeedLimitArray[segmentId] == null) {
+ laneSpeedLimitArray[segmentId] = new float?[segmentInfo.m_lanes.Length];
+ } else if (laneSpeedLimitArray[segmentId].Length < segmentInfo.m_lanes.Length) {
+ float?[] oldArray = laneSpeedLimitArray[segmentId];
+ laneSpeedLimitArray[segmentId] = new float?[segmentInfo.m_lanes.Length];
+ Array.Copy(sourceArray: oldArray,
+ destinationArray: laneSpeedLimitArray[segmentId],
+ length: oldArray.Length);
+ }
+
+ // (2) insert the custom speed limit
+ laneSpeedLimitArray[segmentId][laneIndex] = action.Value.GetKmph();
+ break;
}
-
- // (2) insert the custom speed limit
- laneSpeedLimitArray[segmentId][laneIndex] = speedLimit;
}
}
finally {
@@ -862,15 +875,16 @@ internal static bool CanHaveLaneArrows(uint laneId, bool? startNode = null) {
return false;
}
- public static float? GetLaneSpeedLimit(uint laneId) {
+ public static SpeedValue? GetLaneSpeedLimit(uint laneId) {
try {
Monitor.Enter(laneSpeedLimitLock);
- if (laneId <= 0 || !laneSpeedLimit.TryGetValue(laneId, out float speedLimit)) {
+ if (laneId <= 0 || !laneSpeedLimit.TryGetValue(laneId, out float kmphOverride)) {
return null;
}
- return speedLimit;
+ // assumption: speed limit is stored in km/h
+ return SpeedValue.FromKmph(kmphOverride);
}
finally {
Monitor.Exit(laneSpeedLimitLock);
diff --git a/TLM/TLM/State/GetSpeedLimitResult.cs b/TLM/TLM/State/GetSpeedLimitResult.cs
new file mode 100644
index 000000000..01abb41c8
--- /dev/null
+++ b/TLM/TLM/State/GetSpeedLimitResult.cs
@@ -0,0 +1,44 @@
+namespace TrafficManager {
+ using System.Runtime.CompilerServices;
+ using TrafficManager.API.Traffic.Data;
+
+ public struct GetSpeedLimitResult {
+ public enum ResultType {
+ /// The speed limit had an override.
+ OverrideExists,
+
+ /// There was no override possible.
+ NotAvailable,
+ }
+
+ public ResultType Type;
+
+ /// Valid only if Type=='Value'.
+ public SpeedValue? OverrideValue;
+
+ public SpeedValue DefaultValue;
+
+ ///
+ /// Initializes a new instance of the struct
+ /// from maybe an override value (nullable SpeedValue) and a
+ /// default speedlimit value for that lane type.
+ ///
+ /// Nullable override value.
+ /// Default speed value.
+ public GetSpeedLimitResult(SpeedValue? value, SpeedValue defaultValue) {
+ Type = ResultType.OverrideExists;
+ OverrideValue = value;
+ DefaultValue = defaultValue;
+ }
+
+ ///
+ /// Initializes a new instance of the struct
+ /// as error state (no override possible).
+ ///
+ public GetSpeedLimitResult(ResultType _ignored) {
+ Type = ResultType.NotAvailable;
+ OverrideValue = null;
+ DefaultValue = new SpeedValue(0f);
+ }
+ }
+}
\ No newline at end of file
diff --git a/TLM/TLM/State/OptionsTabs/OptionsGeneralTab.cs b/TLM/TLM/State/OptionsTabs/OptionsGeneralTab.cs
index e9c5329b8..27d9edee5 100644
--- a/TLM/TLM/State/OptionsTabs/OptionsGeneralTab.cs
+++ b/TLM/TLM/State/OptionsTabs/OptionsGeneralTab.cs
@@ -306,13 +306,13 @@ private static void OnRoadSignsMphThemeChanged(int newRoadSignStyle) {
}
// The UI order is: US, UK, German
- var newStyle = MphSignStyle.RoundGerman;
+ var newStyle = SpeedLimitSignTheme.RoundGerman;
switch (newRoadSignStyle) {
case 1:
- newStyle = MphSignStyle.RoundUK;
+ newStyle = SpeedLimitSignTheme.RoundUK;
break;
case 0:
- newStyle = MphSignStyle.SquareUS;
+ newStyle = SpeedLimitSignTheme.RectangularUS;
break;
}
diff --git a/TLM/TLM/State/OptionsTabs/OptionsMassEditTab.cs b/TLM/TLM/State/OptionsTabs/OptionsMassEditTab.cs
index 33d60cdb9..9542bdc5e 100644
--- a/TLM/TLM/State/OptionsTabs/OptionsMassEditTab.cs
+++ b/TLM/TLM/State/OptionsTabs/OptionsMassEditTab.cs
@@ -72,7 +72,7 @@ public static class OptionsMassEditTab {
Tooltip = "Priority roads.Tooltip:Allow far turns",
};
- public static CheckboxOption PriorityRoad_EnterBlockedYeild =
+ public static CheckboxOption PriorityRoad_EnterBlockedYield =
new CheckboxOption("PriorityRoad_EnterBlockedYeild") {
Label = "Priority roads.Option:Enter blocked yield road",
};
@@ -106,7 +106,7 @@ internal static void MakePanel_MassEdit(UIHelperBase panelHelper) {
UIHelperBase priorityRoadGroup = panelHelper.AddGroup(T("MassEdit.Group.Priority roads"));
PriorityRoad_CrossMainR.AddUI(priorityRoadGroup);
PriorityRoad_AllowLeftTurns.AddUI(priorityRoadGroup);
- PriorityRoad_EnterBlockedYeild.AddUI(priorityRoadGroup);
+ PriorityRoad_EnterBlockedYield.AddUI(priorityRoadGroup);
PriorityRoad_StopAtEntry.AddUI(priorityRoadGroup);
}
diff --git a/TLM/TLM/State/SetSpeedLimitAction.cs b/TLM/TLM/State/SetSpeedLimitAction.cs
new file mode 100644
index 000000000..9a0bfc974
--- /dev/null
+++ b/TLM/TLM/State/SetSpeedLimitAction.cs
@@ -0,0 +1,64 @@
+namespace TrafficManager {
+ using System;
+ using JetBrains.Annotations;
+ using TrafficManager.API.Traffic.Data;
+
+ ///
+ /// Used to give clear command to SetSpeedLimit.
+ /// Does not define where the speed limit goes (to override per segment, to override per lane,
+ /// to default per road type etc) for that is used.
+ ///
+ public readonly struct SetSpeedLimitAction {
+ /// Defines the action on set speedlimit call.
+ public enum ValueType {
+ /// The Value contains the speed to set.
+ SetSpeed,
+
+ /// The value is ignored. Speed is set to unlimited.
+ Unlimited,
+
+ /// The value is ignored. Speed override is reset.
+ Default,
+ }
+
+ /// Defines the action type.
+ public readonly ValueType Type;
+
+ /// If Type is GameSpeedUnits, contains the value.
+ public readonly SpeedValue Value;
+
+ private SetSpeedLimitAction(ValueType t, SpeedValue v) {
+ this.Type = t;
+ this.Value = v;
+ }
+
+ public static SetSpeedLimitAction Default() {
+ return new SetSpeedLimitAction(ValueType.Default, default);
+ }
+
+ [UsedImplicitly]
+ public static SetSpeedLimitAction Unlimited() {
+ return new SetSpeedLimitAction(ValueType.Unlimited, default);
+ }
+
+ public static SetSpeedLimitAction SetSpeed(SpeedValue v) {
+ return new SetSpeedLimitAction(ValueType.SetSpeed, v);
+ }
+
+ /// Reapply value from GetSpeedLimitResult.
+ /// Value to apply.
+ /// New action to pass to SetSpeedLimit.
+ public static SetSpeedLimitAction FromGetResult(GetSpeedLimitResult res) {
+ switch (res.Type) {
+ case GetSpeedLimitResult.ResultType.NotAvailable:
+ return Default();
+ case GetSpeedLimitResult.ResultType.OverrideExists:
+ return res.OverrideValue.HasValue
+ ? SetSpeed(res.OverrideValue.Value)
+ : Default();
+ }
+
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/TLM/TLM/State/SetSpeedLimitTarget.cs b/TLM/TLM/State/SetSpeedLimitTarget.cs
new file mode 100644
index 000000000..a10187409
--- /dev/null
+++ b/TLM/TLM/State/SetSpeedLimitTarget.cs
@@ -0,0 +1,18 @@
+namespace TrafficManager {
+ ///
+ /// Defines where is applied.
+ ///
+ public enum SetSpeedLimitTarget {
+ /// The speed limit will be set or cleared for override per segment.
+ SegmentOverride,
+
+ /// The speed limit will be set or cleared for override per lane.
+ LaneOverride,
+
+ /// The speed limit will be set or cleared for default per road type.
+ SegmentDefault,
+
+ /// The speed limit will be set or cleared for default per road type per lane.
+ LaneDefault,
+ }
+}
\ No newline at end of file
diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj
index 03050fab7..50b5d71df 100644
--- a/TLM/TLM/TLM.csproj
+++ b/TLM/TLM/TLM.csproj
@@ -13,6 +13,7 @@
512
latest
+ disable
true
@@ -170,8 +171,14 @@
+
+
+
+
+
+
@@ -261,6 +268,7 @@
+
@@ -303,8 +311,16 @@
-
+
+
+
+
+
+
+
+
+
@@ -371,11 +387,16 @@
+
+
+
+
+
@@ -407,7 +428,6 @@
-
@@ -588,7 +608,6 @@
-
@@ -678,6 +697,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/TLM/TLM/TLM.csproj.DotSettings b/TLM/TLM/TLM.csproj.DotSettings
new file mode 100644
index 000000000..27f93102b
--- /dev/null
+++ b/TLM/TLM/TLM.csproj.DotSettings
@@ -0,0 +1,13 @@
+
+ False
+ False
+ False
+ False
+ False
+ False
+ False
+ False
+ False
+ False
+ False
+ False
\ No newline at end of file
diff --git a/TLM/TLM/U/AtlasBuilder.cs b/TLM/TLM/U/AtlasBuilder.cs
new file mode 100644
index 000000000..94d78d56f
--- /dev/null
+++ b/TLM/TLM/U/AtlasBuilder.cs
@@ -0,0 +1,49 @@
+namespace TrafficManager.U {
+ using System.Collections.Generic;
+ using System.Linq;
+ using ColossalFramework.UI;
+ using TrafficManager.Util;
+
+ ///
+ /// Populates a set of spritedefs as your UI form is populated with controls. Allows to use
+ /// same atlas for all UI controls on a form.
+ ///
+ public class AtlasBuilder {
+ private HashSet spriteDefs_ = new HashSet();
+
+ public HashSet SpriteDefs => spriteDefs_;
+
+ ///
+ /// Add one more sprite to load.
+ /// Use via where required sprites are added.
+ ///
+ /// Sprite size, name, filename, optional path etc.
+ public void Add(AtlasSpriteDef spriteDef) {
+ this.spriteDefs_.Add(spriteDef);
+ }
+
+ /// Following the settings in the Skin fields, load sprites into an UI atlas.
+ /// Longer list of atlas keys can be loaded into one atlas.
+ /// The name to append to "TMPE_U_***".
+ /// Path inside Resources. directory (dot separated).
+ /// Square atlas of this size is created.
+ /// New UI atlas.
+ public UITextureAtlas CreateAtlas(string atlasName,
+ string loadingPath,
+ IntVector2 atlasSizeHint) {
+ string fullName = $"TMPE_U_{atlasName}";
+ UITextureAtlas foundAtlas = TextureUtil.FindAtlas(fullName);
+
+ // If is NOT the same as UI default, means the atlas is already loaded (return cached)
+ if (!System.Object.ReferenceEquals(foundAtlas, UIView.GetAView().defaultAtlas)) {
+ return foundAtlas;
+ }
+
+ return TextureUtil.CreateAtlas(
+ atlasName: fullName,
+ resourcePrefix: loadingPath,
+ spriteDefs: spriteDefs_.ToArray(),
+ atlasSizeHint: atlasSizeHint);
+ }
+ }
+}
\ No newline at end of file
diff --git a/TLM/TLM/U/AtlasSpriteDef.cs b/TLM/TLM/U/AtlasSpriteDef.cs
new file mode 100644
index 000000000..18f81a165
--- /dev/null
+++ b/TLM/TLM/U/AtlasSpriteDef.cs
@@ -0,0 +1,29 @@
+namespace TrafficManager.U {
+ using System;
+ using TrafficManager.Util;
+
+ ///
+ /// Defines a sprite to be loaded into a Sprite Atlas (usually for buttons and other UI elements).
+ ///
+ // TODO: Can also store loading path here
+ public class AtlasSpriteDef: IEquatable {
+ /// Set this to nonempty string to load sprites from different resource path.
+ public string ResourcePrefix;
+
+ /// Sprite name in the atlas, and also PNG texture name.
+ public string Name;
+
+ /// Texture size assumed by the developer.
+ public IntVector2 Size;
+
+ public AtlasSpriteDef(string name, IntVector2 size, string prefix = "") {
+ Name = name;
+ Size = size;
+ ResourcePrefix = prefix;
+ }
+
+ public bool Equals(AtlasSpriteDef other) {
+ return other != null && this.Name == other.Name;
+ }
+ }
+}
\ No newline at end of file
diff --git a/TLM/TLM/U/Autosize/UResizerConfig.cs b/TLM/TLM/U/Autosize/UResizerConfig.cs
index 3c482f7c6..040025cba 100644
--- a/TLM/TLM/U/Autosize/UResizerConfig.cs
+++ b/TLM/TLM/U/Autosize/UResizerConfig.cs
@@ -3,15 +3,10 @@ namespace TrafficManager.U.Autosize {
using ColossalFramework.UI;
using CSUtil.Commons;
using JetBrains.Annotations;
+ using UnityEngine;
/// Stores callback info for a resizable control.
public class UResizerConfig {
- // ///
- // /// Set this to true to once attempt to realign the layout of this control and all children.
- // /// This flag is respected by .
- // ///
- // public bool InvalidateLayoutFlag = false;
-
///
/// Handler is called at control creation and also when resize is required due to screen
/// resolution or UI scale change. Can be null, in that case the size is preserved.
@@ -24,6 +19,22 @@ public class UResizerConfig {
///
public float Padding;
+ public USizeChoice SizeChoice = USizeChoice.ResizeFunction;
+
+ ///
+ /// Applied on resize function call if the is set to Predefined.
+ ///
+ public Vector2 FixedSize;
+
+ public UStackingChoice StackingChoice = UStackingChoice.ResizeFunction;
+
+ ///
+ /// Applied on resize function call if the is set to Predefined.
+ ///
+ public UStackMode Stacking;
+
+ public float StackingSpacing;
+
public UResizerConfig() {
onResize_ = null;
ContributeToBoundingBox = true;
@@ -49,14 +60,28 @@ public static UResizerConfig From(UIComponent control) {
UBoundingBox childrenBox) {
if (control is ISmartSizableControl currentAsResizable) {
UResizerConfig resizerConfig = currentAsResizable.GetResizerConfig();
+ UResizer resizer = new UResizer(
+ control: control,
+ config: resizerConfig,
+ previousSibling,
+ childrenBox);
+
+ // Apply predefined decision: fixed size
+ if (resizerConfig.SizeChoice == USizeChoice.Predefined) {
+ resizer.Width(UValue.FixedSize(resizerConfig.FixedSize.x));
+ resizer.Height(UValue.FixedSize(resizerConfig.FixedSize.y));
+ }
+
+ // Apply predefined decision: stacking and spacing
+ if (resizerConfig.StackingChoice == UStackingChoice.Predefined) {
+ resizer.Stack(
+ mode: resizerConfig.Stacking,
+ spacing: resizerConfig.StackingSpacing);
+ }
+ // Call the resize function to apply user decisions on the size and position
if (resizerConfig.onResize_ != null) {
// Create helper UResizer and run it
- UResizer resizer = new UResizer(
- control: control,
- config: resizerConfig,
- previousSibling,
- childrenBox);
try {
resizerConfig.onResize_(resizer);
}
diff --git a/TLM/TLM/U/Autosize/USizeChoice.cs b/TLM/TLM/U/Autosize/USizeChoice.cs
new file mode 100644
index 000000000..2f1659f1a
--- /dev/null
+++ b/TLM/TLM/U/Autosize/USizeChoice.cs
@@ -0,0 +1,15 @@
+namespace TrafficManager.U.Autosize {
+ ///
+ /// Used in to command to automatically
+ /// fix size of the control, or trust the user-provided resize function to do it.
+ ///
+ public enum USizeChoice {
+ ///
+ /// Do not apply fixed size, trust the user code to reset the size in resize function.
+ ///
+ ResizeFunction,
+
+ /// Apply fixed size from the .
+ Predefined,
+ }
+}
\ No newline at end of file
diff --git a/TLM/TLM/U/Autosize/UStackingChoice.cs b/TLM/TLM/U/Autosize/UStackingChoice.cs
new file mode 100644
index 000000000..b302abc77
--- /dev/null
+++ b/TLM/TLM/U/Autosize/UStackingChoice.cs
@@ -0,0 +1,13 @@
+namespace TrafficManager.U.Autosize {
+ ///
+ /// Used in to command to automatically
+ /// stack/position the control, or trust the user-provided resize function to do it.
+ ///
+ public enum UStackingChoice {
+ /// Do not apply stacking, trust the user code to reset the size in resize function.
+ ResizeFunction,
+
+ /// Apply stacking from the .
+ Predefined,
+ }
+}
\ No newline at end of file
diff --git a/TLM/TLM/U/Button/BaseUButton.cs b/TLM/TLM/U/Button/BaseUButton.cs
index 882675328..396901544 100644
--- a/TLM/TLM/U/Button/BaseUButton.cs
+++ b/TLM/TLM/U/Button/BaseUButton.cs
@@ -1,8 +1,9 @@
-namespace TrafficManager.U.Button {
+namespace TrafficManager.U {
using System;
using ColossalFramework.UI;
using TrafficManager.State.Keybinds;
using TrafficManager.U.Autosize;
+ using UnityEngine;
///
/// A smart button which can change its foreground and background textures based on its state,
@@ -34,7 +35,7 @@ public virtual void OnBeforeResizerUpdate() { }
public virtual void OnAfterResizerUpdate() { }
/// Defines how the button looks, hovers and activates.
- public U.Button.ButtonSkin Skin;
+ public U.ButtonSkin Skin;
/// Checks whether a button can ever be "activated", i.e. stays highlighted.
/// Whether a button can toggle-activate.
@@ -53,9 +54,12 @@ public override void Start() {
///
protected abstract bool IsActive();
- /// Override this to return localized string for the tooltip.
+ ///
+ /// Override this to return localized string for the tooltip.
+ /// Return empty if no override is required and `this.tooltip` will be used.
+ ///
/// The tooltip for this button.
- protected abstract string GetTooltip();
+ protected virtual string U_OverrideTooltipText() => string.Empty;
/// Override this to define whether the button should be visible on tool panel.
/// Whether the button visible.
@@ -65,9 +69,10 @@ public virtual void HandleClick(UIMouseEventParameter p) {
}
///
- /// Override this to return non-null, and it will display a keybind tooltip
+ /// Override this to return non-null, and it will display a keybind tooltip.
+ /// Return null if no keybind setting is available.
///
- public virtual KeybindSetting GetShortcutKey() {
+ public virtual KeybindSetting U_OverrideTooltipShortcutKey() {
return null;
}
@@ -87,7 +92,7 @@ internal void UpdateButtonImageAndTooltip() {
internal void UpdateTooltip(bool refreshTooltip) {
// Update localized tooltip with shortcut key if available
- string overrideTooltip = GetTooltip();
+ string overrideTooltip = U_OverrideTooltipText();
if (!string.IsNullOrEmpty(overrideTooltip)) {
this.tooltip = overrideTooltip + GetShortcutTooltip();
}
@@ -148,9 +153,15 @@ internal void UpdateButtonImage() {
/// Tooltip to append to the main tooltip text, or an empty string
// TODO: Move this code to MainMenu buttons which have shortcuts, other buttons don't have them
private string GetShortcutTooltip() {
- return GetShortcutKey() != null
- ? GetShortcutKey().ToLocalizedString("\n")
+ return U_OverrideTooltipShortcutKey() != null
+ ? U_OverrideTooltipShortcutKey().ToLocalizedString("\n")
: string.Empty;
}
+
+ /// Colorizes button in all states.
+ /// Color hue.
+ public void ColorizeAllStates(Color32 c) {
+ this.color = this.hoveredColor = this.focusedColor = this.disabledColor = c;
+ }
}
}
\ No newline at end of file
diff --git a/TLM/TLM/U/Button/ButtonSkin.cs b/TLM/TLM/U/Button/ButtonSkin.cs
index 6de823a41..9006e8a01 100644
--- a/TLM/TLM/U/Button/ButtonSkin.cs
+++ b/TLM/TLM/U/Button/ButtonSkin.cs
@@ -1,8 +1,9 @@
-namespace TrafficManager.U.Button {
+namespace TrafficManager.U {
using System.Collections.Generic;
using System.Linq;
using ColossalFramework.UI;
using JetBrains.Annotations;
+ using TrafficManager.Util;
///
/// Struct defines button atlas keys for button states.
@@ -46,15 +47,75 @@ public class ButtonSkin {
public bool ForegroundHovered = false;
public bool ForegroundActive = false;
- public HashSet CreateAtlasKeyset() {
+ ///
+ /// Create Button Skin for a given button name, which can be hovered, active,
+ /// but not disabled.
+ ///
+ /// Prefix of the filenames to use for this button.
+ /// Button skin object.
+ public static ButtonSkin CreateDefault(string prefix, string backgroundPrefix) {
+ return new ButtonSkin {
+ Prefix = prefix,
+ BackgroundPrefix = backgroundPrefix,
+
+ BackgroundHovered = true,
+ BackgroundActive = true,
+ BackgroundDisabled = false,
+
+ ForegroundNormal = true,
+ ForegroundActive = true,
+ };
+ }
+
+ /// Create background only button skin.
+ /// Prefix.
+ /// New skin.
+ public static ButtonSkin CreateDefaultNoForeground(string buttonName) {
+ return new ButtonSkin {
+ Prefix = buttonName,
+ BackgroundPrefix = buttonName, // filename prefix
+
+ BackgroundHovered = true,
+ BackgroundActive = true,
+ BackgroundDisabled = false,
+
+ ForegroundNormal = false,
+ ForegroundActive = false,
+ };
+ }
+
+ /// Create foreground-only button skin.
+ /// Prefix.
+ /// New skin.
+ public static ButtonSkin CreateDefaultNoBackground(string buttonName) {
+ return new ButtonSkin {
+ Prefix = buttonName,
+ BackgroundPrefix = string.Empty, // no background
+
+ BackgroundHovered = false,
+ BackgroundActive = false,
+ BackgroundDisabled = false,
+
+ ForegroundNormal = true,
+ ForegroundActive = true,
+ };
+ }
+
+ ///
+ /// Create set of atlas spritedefs all of the same size.
+ /// SpriteDef sets can be merged together and fed into sprite atlas creation call.
+ ///
+ /// Will later load sprites and form a texture atlas.
+ /// The size to assume for all sprites.
+ public void UpdateAtlasBuilder(AtlasBuilder atlasBuilder, IntVector2 spriteSize) {
// Two normal textures (bg and fg) are always assumed to exist.
- var names = new HashSet();
+ List names = new List();
bool haveBackgroundPrefix = !string.IsNullOrEmpty(BackgroundPrefix);
if (haveBackgroundPrefix) {
names.Add($"{BackgroundPrefix}-bg-normal");
}
- if (ForegroundNormal) {
+ if (ForegroundNormal && !string.IsNullOrEmpty(Prefix)) {
names.Add($"{Prefix}-fg-normal");
}
if (BackgroundDisabled && haveBackgroundPrefix) {
@@ -66,40 +127,20 @@ public HashSet CreateAtlasKeyset() {
if (BackgroundActive && haveBackgroundPrefix) {
names.Add($"{BackgroundPrefix}-bg-active");
}
- if (ForegroundDisabled) {
+ if (ForegroundDisabled && !string.IsNullOrEmpty(Prefix)) {
names.Add($"{Prefix}-fg-disabled");
}
- if (ForegroundHovered) {
+ if (ForegroundHovered && !string.IsNullOrEmpty(Prefix)) {
names.Add($"{Prefix}-fg-hovered");
}
- if (ForegroundActive) {
+ if (ForegroundActive && !string.IsNullOrEmpty(Prefix)) {
names.Add($"{Prefix}-fg-active");
}
- return names;
- }
-
- /// Following the settings in the Skin fields, load sprites into an UI atlas.
- /// Longer list of atlas keys can be loaded into one atlas.
- /// Path inside Resources. directory (dot separated).
- /// When loading assume this width.
- /// When loading assume this height.
- /// Square atlas of this size is created.
- /// List of atlas keys to load under the loadingPath. Created by
- /// calling CreateAtlasKeysList() on a ButtonSkin.
- /// New UI atlas.
- public UITextureAtlas CreateAtlas(string loadingPath,
- int spriteWidth,
- int spriteHeight,
- int hintAtlasTextureSize,
- HashSet atlasKeyset) {
- return TextureUtil.CreateAtlas(
- atlasName: $"TMPE_U_{Prefix}_Atlas",
- resourcePrefix: loadingPath,
- spriteNames: atlasKeyset.ToArray(),
- spriteWidth: spriteWidth,
- spriteHeight: spriteHeight,
- hintAtlasTextureSize: hintAtlasTextureSize);
+ // Convert string hashset into spritedefs hashset
+ foreach (string n in names) {
+ atlasBuilder.Add(new U.AtlasSpriteDef(name: n, size: spriteSize));
+ }
}
///
diff --git a/TLM/TLM/U/Button/UButton.cs b/TLM/TLM/U/Button/UButton.cs
index 3d42e14db..dfb065ab9 100644
--- a/TLM/TLM/U/Button/UButton.cs
+++ b/TLM/TLM/U/Button/UButton.cs
@@ -1,7 +1,6 @@
-namespace TrafficManager.U.Button {
+namespace TrafficManager.U {
using System;
using ColossalFramework.UI;
- using TrafficManager.U.Autosize;
///
/// Basic button, cannot be activated, clickable, no tooltip.
@@ -10,6 +9,18 @@ public class UButton : BaseUButton {
[Obsolete("Remove this field and simplify tooltip handling in BaseUButton")]
public string uTooltip;
+ public override void Awake() {
+ base.Awake();
+ SetupDefaultSprites();
+ }
+
+ private void SetupDefaultSprites() {
+ this.atlas = U.TextureUtil.FindAtlas("Ingame");
+ this.normalBgSprite = "ButtonMenu";
+ this.hoveredBgSprite = "ButtonMenuHovered";
+ this.pressedBgSprite = "ButtonMenuPressed";
+ }
+
public override bool CanActivate() {
if (this.uCanActivate != null) {
return this.uCanActivate(this);
@@ -19,15 +30,22 @@ public override bool CanActivate() {
}
protected override bool IsActive() {
- if (this.uIsActive != null) {
- return this.uIsActive(this);
- }
-
- return false;
+ // use uIsActive if its defined, otherwise false. Override this in your buttons.
+ return this.uIsActive != null && this.uIsActive(this);
}
- protected override string GetTooltip() => this.uTooltip; // to override in subclass
+ protected override string U_OverrideTooltipText() => this.uTooltip; // to override in subclass
protected override bool IsVisible() => this.isVisible;
+
+ ///
+ /// Sets up a clickable button which can be active (to toggle textures on the button).
+ ///
+ public void SetupToggleButton(MouseEventHandler onClickFun,
+ Func isActiveFun) {
+ this.uOnClick = onClickFun;
+ this.uIsActive = isActiveFun;
+ this.uCanActivate = _ => true;
+ }
}
}
\ No newline at end of file
diff --git a/TLM/TLM/U/Label/ULabel.cs b/TLM/TLM/U/Label/ULabel.cs
index 6933216a6..d27c8f263 100644
--- a/TLM/TLM/U/Label/ULabel.cs
+++ b/TLM/TLM/U/Label/ULabel.cs
@@ -1,4 +1,4 @@
-namespace TrafficManager.U.Label {
+namespace TrafficManager.U {
using ColossalFramework.UI;
using TrafficManager.U.Autosize;
diff --git a/TLM/TLM/U/Panel/BaseUWindowPanel.cs b/TLM/TLM/U/Panel/BaseUWindowPanel.cs
index 77906afe1..84d35a62c 100644
--- a/TLM/TLM/U/Panel/BaseUWindowPanel.cs
+++ b/TLM/TLM/U/Panel/BaseUWindowPanel.cs
@@ -84,5 +84,27 @@ internal void SetOpacity(U.UOpacityValue opacity) {
modified.a = opacity.GetOpacityByte();
this.color = modified;
}
+
+ ///
+ /// Creates a drag handle gameobject child for this window, which can be enabled or disabled
+ /// by the caller.
+ ///
+ /// New UIDragHandle object.
+ public UIDragHandle CreateDragHandle() {
+ GameObject dragHandler = new GameObject("TMPE_DragHandler");
+ dragHandler.transform.parent = transform;
+ dragHandler.transform.localPosition = Vector3.zero;
+
+ return dragHandler.AddComponent();
+ }
+
+ /// Make this panel use dark gray generic background and opacity from the GUI Options.
+ internal void GenericBackgroundAndOpacity() {
+ // the GenericPanel sprite is silver, make it dark
+ this.backgroundSprite = "GenericPanel";
+ this.color = new Color32(64, 64, 64, 240);
+
+ SetOpacity(UOpacityValue.FromOpacity(0.01f * GlobalConfig.Instance.Main.GuiOpacity));
+ }
}
}
\ No newline at end of file
diff --git a/TLM/TLM/U/Panel/UPanel.cs b/TLM/TLM/U/Panel/UPanel.cs
index a26db07ac..245cb7d69 100644
--- a/TLM/TLM/U/Panel/UPanel.cs
+++ b/TLM/TLM/U/Panel/UPanel.cs
@@ -1,4 +1,4 @@
-namespace TrafficManager.U.Panel {
+namespace TrafficManager.U {
using ColossalFramework.UI;
using TrafficManager.U.Autosize;
diff --git a/TLM/TLM/U/TextureUtil.cs b/TLM/TLM/U/TextureUtil.cs
index 8c340cd1e..6c625c361 100644
--- a/TLM/TLM/U/TextureUtil.cs
+++ b/TLM/TLM/U/TextureUtil.cs
@@ -5,55 +5,66 @@ namespace TrafficManager.U {
using CSUtil.Commons;
using TrafficManager.UI.Textures;
using UnityEngine;
- using TrafficManager.State.ConfigData;
+ using TrafficManager.Util;
public static class TextureUtil {
///
- /// Initializes a new instance of the class from resource names list.
+ /// Initializes a new instance of the class from resource names list.
+ /// NOTE: For UI sprites loading use instead, do not call this.
///
/// Name for the new atlas.
/// Prefix to resource directory.
- /// Array of paths to load also they become atlas keys.
- /// Hint width for texture loader (todo: get rid of this).
- /// Hint height for texture loader (todo: get rid of this).
- /// Square texture with this side size is created.
+ /// Names of textures and sizes to load.
+ /// Texture with this side size is created.
public static UITextureAtlas CreateAtlas(string atlasName,
string resourcePrefix,
- string[] spriteNames,
- int spriteWidth,
- int spriteHeight,
- int hintAtlasTextureSize) {
- Texture2D texture2D = new Texture2D(
- width: hintAtlasTextureSize,
- height: hintAtlasTextureSize,
- format: TextureFormat.ARGB32,
- mipmap: false);
-
- var loadedTextures = new List(spriteNames.Length);
+ U.AtlasSpriteDef[] spriteDefs,
+ IntVector2 atlasSizeHint) {
+ var loadedTextures = new List(spriteDefs.Length);
var loadedSpriteNames = new List();
// Load separate sprites and then pack it in a texture together
- foreach (string spriteName in spriteNames) {
- string resourceName = $"{resourcePrefix}.{spriteName}.png";
- Log._Debug($"TextureUtil: Loading {resourceName} for sprite={spriteName}");
-
- Texture2D loadedSprite = TextureResources.LoadDllResource(
+ foreach (U.AtlasSpriteDef spriteDef in spriteDefs) {
+ // Allow spritedef resouce prefix to override prefix given to this func
+ string prefix = string.IsNullOrEmpty(spriteDef.ResourcePrefix)
+ ? resourcePrefix
+ : spriteDef.ResourcePrefix;
+
+ //--------------------------
+ // Try loading the texture
+ //--------------------------
+ string resourceName = $"{prefix}.{spriteDef.Name}.png";
+ Log._Debug($"TextureUtil: Loading {resourceName} for sprite={spriteDef.Name}");
+
+ Texture2D tex = TextureResources.LoadDllResource(
resourceName: resourceName,
- width: spriteWidth,
- height: spriteHeight);
-
- if (loadedSprite != null) {
- loadedTextures.Add(loadedSprite);
- loadedSpriteNames.Add(spriteName); // only take those which are loaded
- } else {
- Log.Error($"TextureUtil: Sprite load failed: {resourceName} for sprite={spriteName}");
+ size: spriteDef.Size);
+
+ if (tex != null) {
+ loadedTextures.Add(tex);
+ loadedSpriteNames.Add(spriteDef.Name); // only take those which are loaded
}
+ // No error reporting, it is done in LoadDllResource
}
- var regions = texture2D.PackTextures(
+ Log._Debug($"TextureUtil: Atlas textures loaded, in {spriteDefs.Length}, success {loadedTextures.Count}");
+ return PackTextures(atlasName, atlasSizeHint, loadedTextures, loadedSpriteNames);
+ }
+
+ private static UITextureAtlas PackTextures(string atlasName,
+ IntVector2 atlasSizeHint,
+ List loadedTextures,
+ List loadedSpriteNames) {
+ Texture2D texture2D = new Texture2D(
+ width: atlasSizeHint.x,
+ height: atlasSizeHint.y,
+ format: TextureFormat.ARGB32,
+ mipmap: false);
+
+ Rect[] regions = texture2D.PackTextures(
textures: loadedTextures.ToArray(),
padding: 2,
- maximumAtlasSize: hintAtlasTextureSize);
+ maximumAtlasSize: Math.Max(atlasSizeHint.x, atlasSizeHint.y));
// Now using loaded and packed textures, create the atlas with sprites
UITextureAtlas newAtlas = ScriptableObject.CreateInstance();
@@ -64,94 +75,18 @@ public static UITextureAtlas CreateAtlas(string atlasName,
newAtlas.material = material;
newAtlas.name = atlasName;
- for (int i = 0; i < spriteNames.Length; i++) {
+ for (int i = 0; i < loadedTextures.Count; i++) {
var item = new UITextureAtlas.SpriteInfo {
- name = loadedSpriteNames[i],
- texture = loadedTextures[i],
- region = regions[i],
- };
-
+ name = loadedSpriteNames[i],
+ texture = loadedTextures[i],
+ region = regions[i],
+ };
newAtlas.AddSprite(item);
}
return newAtlas;
}
- // [Obsolete("Replace with ModUI global atlas and Unity atlas function call")]
- // public static UITextureAtlas GenerateLinearAtlas(string name,
- // Texture2D texture,
- // int numSprites,
- // string[] spriteNames) {
- // return Generate2DAtlas(name, texture, numSprites, 1, spriteNames);
- // }
-
- [Obsolete("Remove and use U.ButtonTexture instead")]
- public static UITextureAtlas Generate2DAtlas(string name,
- Texture2D texture,
- int numX,
- int numY,
- string[] spriteNames) {
- if (spriteNames.Length != numX * numY) {
- throw new ArgumentException(
- "Number of sprite name does not match dimensions " +
- $"(expected {numX} x {numY}, was {spriteNames.Length})");
- }
-
-#if DEBUG
- Log._DebugIf(
- DebugSwitch.ResourceLoading.Get(),
- () => $"Loading atlas for {name} count:{numX}x{numY} " +
- $"texture:{texture.name} size:{texture.width}x{texture.height}");
-#endif
-
- UITextureAtlas atlas = ScriptableObject.CreateInstance();
- atlas.padding = 0;
- atlas.name = name;
-
- Shader shader = Shader.Find("UI/Default UI Shader");
-
- if (shader != null) {
- atlas.material = new Material(shader);
- }
-
- atlas.material.mainTexture = texture;
-
- int spriteWidth = Mathf.RoundToInt(texture.width / (float)numX);
- int spriteHeight = Mathf.RoundToInt(texture.height / (float)numY);
- int k = 0;
-
- for (int i = 0; i < numX; ++i) {
- float x = i / (float)numX;
- for (int j = 0; j < numY; ++j) {
- float y = j / (float)numY;
-
- var sprite = new UITextureAtlas.SpriteInfo {
- name = spriteNames[k],
- region = new Rect(
- x,
- y,
- spriteWidth / (float)texture.width,
- spriteHeight / (float)texture.height),
- };
-
- var spriteTexture = new Texture2D(spriteWidth, spriteHeight);
- spriteTexture.SetPixels(
- texture.GetPixels(
- (int)(texture.width * sprite.region.x),
- (int)(texture.height * sprite.region.y),
- spriteWidth,
- spriteHeight));
- sprite.texture = spriteTexture;
-
- atlas.AddSprite(sprite);
-
- ++k;
- }
- }
-
- return atlas;
- }
-
/// Creates new texture with changed alpha transparency for every pixel.
/// Copy from.
/// New alpha.
diff --git a/TLM/TLM/U/UIBuilder.cs b/TLM/TLM/U/UIBuilder.cs
index 40ff9e959..1ff30532c 100644
--- a/TLM/TLM/U/UIBuilder.cs
+++ b/TLM/TLM/U/UIBuilder.cs
@@ -5,6 +5,7 @@ namespace TrafficManager.U {
using TrafficManager.State.Keybinds;
using TrafficManager.U.Autosize;
using TrafficManager.UI;
+ using UnityEngine;
///
/// Create an UI builder to populate a panel with good things: buttons, sub-panels, create a
@@ -20,6 +21,33 @@ public UiBuilder(TControl curr) {
Control = curr;
}
+ ///
+ /// Generic window creation.
+ /// Creates a window with generic padding (4px). The size will be "fit to children".
+ /// Calls a custom function after the setup is done, there you can populate the window.
+ ///
+ /// The window root panel type.
+ /// Function called on to perform window post-setup.
+ /// The new window panel.
+ public static TWindow CreateWindow(Action> setupFn)
+ where TWindow : UIComponent, ISmartSizableControl
+ {
+ var parent = UIView.GetAView();
+ var window = (TWindow)parent.AddUIComponent(typeof(TWindow));
+
+ using (var builder = new U.UiBuilder(window)) {
+ builder.ResizeFunction(r => { r.FitToChildren(); });
+ builder.SetPadding(UConst.UIPADDING);
+
+ setupFn(builder);
+
+ // Resize everything correctly
+ builder.Done();
+ }
+
+ return window;
+ }
+
///
/// Create a button as a child of current UIBuilder. A new UIBuilder is returned.
///
@@ -45,6 +73,20 @@ public UiBuilder Button(Type t)
return new UiBuilder(newButton);
}
+ public UiBuilder FixedSizeButton(string text,
+ string tooltip,
+ Vector2 size,
+ UStackMode stack)
+ where TButton : UIButton, ISmartSizableControl
+ {
+ UiBuilder builder = this.Button();
+ builder.Control.text = text;
+ builder.Control.tooltip = tooltip;
+ builder.SetStacking(stack);
+ builder.SetFixedSize(size);
+ return builder;
+ }
+
public UiBuilder Label(string t)
where TLabel : UILabel, ISmartSizableControl {
var newLabel = Control.AddUIComponent(typeof(TLabel)) as TLabel;
@@ -52,6 +94,19 @@ public UiBuilder Label(string t)
return new UiBuilder(newLabel);
}
+ /// Quick create a label and stack it. Optionally set markup processing mode.
+ /// Localized text.
+ /// Stacking mode related to previous sibling.
+ /// Whether label text contains C:S color markup.
+ /// New label.
+ public U.ULabel Label(string t, UStackMode stack, bool processMarkup = false) {
+ using (UiBuilder labelB = this.Label(t)) {
+ labelB.ResizeFunction(r => r.Stack(mode: stack));
+ labelB.Control.processMarkup = processMarkup;
+ return labelB.Control;
+ }
+ }
+
public UiBuilder ChildPanel(Action setupFn)
where TPanel : UIPanel, ISmartSizableControl {
var newPanel = Control.AddUIComponent(typeof(TPanel)) as TPanel;
@@ -77,5 +132,23 @@ public void Dispose() { }
public void SetPadding(float f) {
Control.GetResizerConfig().Padding = f;
}
+
+ /// Instruct the to always use fixed size for the control.
+ /// The size in units of 1080p screen.
+ public void SetFixedSize(Vector2 size) {
+ UResizerConfig c = Control.GetResizerConfig();
+ c.SizeChoice = USizeChoice.Predefined;
+ c.FixedSize = size;
+ }
+
+ /// Instruct the to always use this stacking for the control.
+ /// The stacking mode to always use.
+ /// Spacing to use in the call to automatic predefined spacing.
+ public void SetStacking(UStackMode mode, float spacing = 0f) {
+ UResizerConfig c = Control.GetResizerConfig();
+ c.StackingChoice = UStackingChoice.Predefined;
+ c.Stacking = mode;
+ c.StackingSpacing = spacing;
+ }
}
}
\ No newline at end of file
diff --git a/TLM/TLM/U/UIUtil.cs b/TLM/TLM/U/UIUtil.cs
index 2058bb007..137a733c5 100644
--- a/TLM/TLM/U/UIUtil.cs
+++ b/TLM/TLM/U/UIUtil.cs
@@ -62,9 +62,9 @@ public static bool ClampToScreen(UIComponent window, UIComponent alwaysVisible)
pos += new Vector3(xMotion, yMotion, 0f);
window.absolutePosition = pos;
- Log._Debug(
- $"Clamping origRect={origRect} to new={clampedRect} "
- + $"moving by {xMotion};{yMotion} newpos={pos}");
+ // Log._Debug(
+ // $"Clamping origRect={origRect} to new={clampedRect} "
+ // + $"moving by {xMotion};{yMotion} newpos={pos}");
return true;
}
}
diff --git a/TLM/TLM/UI/DebugToolGUI.cs b/TLM/TLM/UI/DebugToolGUI.cs
new file mode 100644
index 000000000..7a1c7858c
--- /dev/null
+++ b/TLM/TLM/UI/DebugToolGUI.cs
@@ -0,0 +1,633 @@
+namespace TrafficManager.UI {
+ using System.Text;
+ using ColossalFramework;
+ using TrafficManager.API.Manager;
+ using TrafficManager.API.Traffic.Data;
+ using TrafficManager.Manager.Impl;
+ using TrafficManager.State;
+ using TrafficManager.Util;
+ using UnityEngine;
+#if DEBUG
+ using TrafficManager.State.ConfigData;
+ using TrafficManager.API.Traffic.Enums;
+#endif
+
+ ///
+ /// Renders extra debug time overlays.
+ ///
+ public static class DebugToolGUI {
+ private const float DEBUG_CLOSE_LOD = 300f;
+
+ /// Displays segment ids over segments.
+ internal static void DisplaySegments() {
+ TrafficMeasurementManager trafficMeasurementManager = TrafficMeasurementManager.Instance;
+ NetManager netManager = Singleton.instance;
+ GUIStyle counterStyle = new GUIStyle();
+ IExtSegmentEndManager endMan = Constants.ManagerFactory.ExtSegmentEndManager;
+ NetSegment[] segmentsBuffer = netManager.m_segments.m_buffer;
+
+ for (int i = 1; i < NetManager.MAX_SEGMENT_COUNT; ++i) {
+ if ((segmentsBuffer[i].m_flags & NetSegment.Flags.Created) ==
+ NetSegment.Flags.None) {
+ // segment is unused
+ continue;
+ }
+
+#if DEBUG
+ ItemClass.Service service = segmentsBuffer[i].Info.GetService();
+ ItemClass.SubService subService = segmentsBuffer[i].Info.GetSubService();
+#else
+ if ((netManager.m_segments.m_buffer[i].m_flags & NetSegment.Flags.Untouchable) !=
+ NetSegment.Flags.None) {
+ continue;
+ }
+#endif
+ NetInfo segmentInfo = segmentsBuffer[i].Info;
+
+ Vector3 centerPos = segmentsBuffer[i].m_bounds.center;
+ bool visible = GeometryUtil.WorldToScreenPoint(centerPos, out Vector3 screenPos);
+
+ if (!visible) {
+ continue;
+ }
+
+ Vector3 camPos = Singleton.instance.m_simulationView.m_position;
+ Vector3 diff = centerPos - camPos;
+
+ if (diff.magnitude > DEBUG_CLOSE_LOD) {
+ continue; // do not draw if too distant
+ }
+
+ float zoom = 1.0f / diff.magnitude * 150f;
+ counterStyle.fontSize = (int)(12f * zoom);
+ counterStyle.normal.textColor = new Color(1f, 0f, 0f);
+
+ var labelSb = new StringBuilder();
+ labelSb.AppendFormat("Segment {0}", i);
+#if DEBUG
+ labelSb.AppendFormat(", flags: {0}", segmentsBuffer[i].m_flags);
+ labelSb.AppendFormat("\nsvc: {0}, sub: {1}", service, subService);
+
+ uint startVehicles = endMan.GetRegisteredVehicleCount(
+ ref endMan.ExtSegmentEnds[endMan.GetIndex((ushort)i, true)]);
+
+ uint endVehicles = endMan.GetRegisteredVehicleCount(
+ ref endMan.ExtSegmentEnds[endMan.GetIndex((ushort)i, false)]);
+
+ labelSb.AppendFormat( "\nstart veh.: {0}, end veh.: {1}", startVehicles, endVehicles);
+#endif
+ labelSb.AppendFormat("\nTraffic: {0} %", segmentsBuffer[i].m_trafficDensity);
+
+#if DEBUG
+ int fwdSegIndex = trafficMeasurementManager.GetDirIndex(
+ (ushort)i,
+ NetInfo.Direction.Forward);
+ int backSegIndex = trafficMeasurementManager.GetDirIndex(
+ (ushort)i,
+ NetInfo.Direction.Backward);
+
+ labelSb.Append("\n");
+
+#if MEASURECONGESTION
+ float fwdCongestionRatio =
+ trafficMeasurementManager
+ .segmentDirTrafficData[fwdSegIndex].numCongestionMeasurements > 0
+ ? ((uint)trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongested * 100u) /
+ (uint)trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongestionMeasurements
+ : 0; // now in %
+ float backCongestionRatio =
+ trafficMeasurementManager
+ .segmentDirTrafficData[backSegIndex].numCongestionMeasurements > 0
+ ? ((uint)trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongested * 100u) /
+ (uint)trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongestionMeasurements
+ : 0; // now in %
+
+
+ labelSb.Append("min speeds: ");
+ labelSb.AppendFormat(
+ " {0}%/{1}%",
+ trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].minSpeed / 100,
+ trafficMeasurementManager.segmentDirTrafficData[backSegIndex].minSpeed /
+ 100);
+ labelSb.Append(", ");
+#endif
+ labelSb.Append("mean speeds: ");
+ labelSb.AppendFormat(
+ " {0}%/{1}%",
+ trafficMeasurementManager.SegmentDirTrafficData[fwdSegIndex].meanSpeed /
+ 100,
+ trafficMeasurementManager.SegmentDirTrafficData[backSegIndex].meanSpeed /
+ 100);
+#if PFTRAFFICSTATS || MEASURECONGESTION
+ labelSb.Append("\n");
+#endif
+#if PFTRAFFICSTATS
+ labelSb.Append("pf bufs: ");
+ labelSb.AppendFormat(
+ " {0}/{1}",
+ trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].totalPathFindTrafficBuffer,
+ trafficMeasurementManager.segmentDirTrafficData[backSegIndex].totalPathFindTrafficBuffer);
+#endif
+#if PFTRAFFICSTATS && MEASURECONGESTION
+ labelSb.Append(", ");
+#endif
+#if MEASURECONGESTION
+ labelSb.Append("cong: ");
+ labelSb.AppendFormat(
+ " {0}% ({1}/{2})/{3}% ({4}/{5})",
+ fwdCongestionRatio,
+ trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongested,
+ trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongestionMeasurements,
+ backCongestionRatio,
+ trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongested,
+ trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongestionMeasurements);
+#endif
+ labelSb.AppendFormat(
+ "\nstart: {0}, end: {1}",
+ segmentsBuffer[i].m_startNode,
+ segmentsBuffer[i].m_endNode);
+#endif
+
+ var labelStr = labelSb.ToString();
+ Vector2 dim = counterStyle.CalcSize(new GUIContent(labelStr));
+ Rect labelRect = new Rect(
+ x: screenPos.x - (dim.x / 2f),
+ y: screenPos.y,
+ width: dim.x,
+ height: dim.y);
+
+ GUI.Label(labelRect, labelStr, counterStyle);
+
+ if (Options.showLanes) {
+ DebugToolGUI.DisplayLanes(
+ segmentId: (ushort)i,
+ segment: ref segmentsBuffer[i],
+ segmentInfo: ref segmentInfo);
+ }
+ }
+ } // end DisplaySegments
+
+ /// Displays node ids over nodes.
+ internal static void DisplayNodes() {
+ var counterStyle = new GUIStyle();
+ NetManager netManager = Singleton.instance;
+
+ for (int i = 1; i < NetManager.MAX_NODE_COUNT; ++i) {
+ if ((netManager.m_nodes.m_buffer[i].m_flags & NetNode.Flags.Created) ==
+ NetNode.Flags.None) {
+ // node is unused
+ continue;
+ }
+
+ Vector3 pos = netManager.m_nodes.m_buffer[i].m_position;
+ bool visible = GeometryUtil.WorldToScreenPoint(pos, out Vector3 screenPos);
+
+ if (!visible) {
+ continue;
+ }
+
+ Vector3 camPos = Singleton.instance.m_simulationView.m_position;
+ Vector3 diff = pos - camPos;
+ if (diff.magnitude > DEBUG_CLOSE_LOD) {
+ continue; // do not draw if too distant
+ }
+
+ float zoom = 1.0f / diff.magnitude * 150f;
+
+ counterStyle.fontSize = (int)(15f * zoom);
+ counterStyle.normal.textColor = new Color(0f, 0f, 1f);
+
+ string labelStr = "Node " + i;
+#if DEBUG
+ labelStr += string.Format(
+ "\nflags: {0}\nlane: {1}",
+ netManager.m_nodes.m_buffer[i].m_flags,
+ netManager.m_nodes.m_buffer[i].m_lane);
+#endif
+ Vector2 dim = counterStyle.CalcSize(new GUIContent(labelStr));
+ Rect labelRect = new Rect(
+ x: screenPos.x - (dim.x / 2f),
+ y: screenPos.y,
+ width: dim.x,
+ height: dim.y);
+
+ GUI.Label(labelRect, labelStr, counterStyle);
+ }
+ } // end DisplayNodes
+
+ /// Displays vehicle ids over vehicles.
+ internal static void DisplayVehicles() {
+ GUIStyle _counterStyle = new GUIStyle();
+ SimulationManager simManager = Singleton.instance;
+ ExtVehicleManager vehStateManager = ExtVehicleManager.Instance;
+ VehicleManager vehicleManager = Singleton.instance;
+
+ int startVehicleId = 1;
+ int endVehicleId = Constants.ServiceFactory.VehicleService.MaxVehicleCount - 1;
+#if DEBUG
+ if (DebugSettings.VehicleId != 0) {
+ startVehicleId = endVehicleId = DebugSettings.VehicleId;
+ }
+#endif
+ Vehicle[] vehiclesBuffer = Singleton.instance.m_vehicles.m_buffer;
+
+ for (int i = startVehicleId; i <= endVehicleId; ++i) {
+ if (vehicleManager.m_vehicles.m_buffer[i].m_flags == 0) {
+ // node is unused
+ continue;
+ }
+
+ Vector3 vehPos = vehicleManager.m_vehicles.m_buffer[i].GetSmoothPosition((ushort)i);
+ bool visible = GeometryUtil.WorldToScreenPoint(vehPos, out Vector3 screenPos);
+
+ if (!visible) {
+ continue;
+ }
+
+ Vector3 camPos = simManager.m_simulationView.m_position;
+ Vector3 diff = vehPos - camPos;
+ if (diff.magnitude > DEBUG_CLOSE_LOD) {
+ continue; // do not draw if too distant
+ }
+
+ float zoom = 1.0f / diff.magnitude * 150f;
+
+ _counterStyle.fontSize = (int)(10f * zoom);
+ _counterStyle.normal.textColor = new Color(1f, 1f, 1f);
+ // _counterStyle.normal.background = MakeTex(1, 1, new Color(0f, 0f, 0f, 0.4f));
+
+ ExtVehicle vState = vehStateManager.ExtVehicles[(ushort)i];
+ ExtCitizenInstance driverInst =
+ ExtCitizenInstanceManager.Instance.ExtInstances[
+ Constants.ManagerFactory.ExtVehicleManager
+ .GetDriverInstanceId(
+ (ushort)i,
+ ref vehiclesBuffer[i])];
+ // bool startNode = vState.currentStartNode;
+ // ushort segmentId = vState.currentSegmentId;
+
+ // Converting magnitudes into game speed float, and then into km/h
+ SpeedValue vehSpeed = SpeedValue.FromVelocity(vehicleManager.m_vehicles.m_buffer[i].GetLastFrameVelocity().magnitude);
+#if DEBUG
+ if (GlobalConfig.Instance.Debug.ExtPathMode != ExtPathMode.None &&
+ driverInst.pathMode != GlobalConfig.Instance.Debug.ExtPathMode) {
+ continue;
+ }
+#endif
+ string labelStr = string.Format(
+ "V #{0} is a {1}{2} {3} @ ~{4} (len: {5:0.0}, {6} @ {7} ({8}), l. {9} " +
+ "-> {10}, l. {11}), w: {12}\n" +
+ "di: {13} dc: {14} m: {15} f: {16} l: {17} lid: {18} ltsu: {19} lpu: {20} " +
+ "als: {21} srnd: {22} trnd: {23}",
+ i,
+ vState.recklessDriver ? "reckless " : string.Empty,
+ vState.flags,
+ vState.vehicleType,
+ vehSpeed.ToKmphPrecise().ToString(),
+ vState.totalLength,
+ vState.junctionTransitState,
+ vState.currentSegmentId,
+ vState.currentStartNode,
+ vState.currentLaneIndex,
+ vState.nextSegmentId,
+ vState.nextLaneIndex,
+ vState.waitTime,
+ driverInst.instanceId,
+ ExtCitizenInstanceManager.Instance.GetCitizenId(driverInst.instanceId),
+ driverInst.pathMode,
+ driverInst.failedParkingAttempts,
+ driverInst.parkingSpaceLocation,
+ driverInst.parkingSpaceLocationId,
+ vState.lastTransitStateUpdate,
+ vState.lastPositionUpdate,
+ vState.lastAltLaneSelSegmentId,
+ Constants.ManagerFactory.ExtVehicleManager.GetStaticVehicleRand((ushort)i),
+ Constants.ManagerFactory.ExtVehicleManager.GetTimedVehicleRand((ushort)i));
+
+ Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr));
+ Rect labelRect = new Rect(
+ x: screenPos.x - (dim.x / 2f),
+ y: screenPos.y - dim.y - 50f,
+ width: dim.x,
+ height: dim.y);
+
+ GUI.Box(labelRect, labelStr, _counterStyle);
+ }
+ } // end DisplayVehicles
+
+ /// Displays debug data over citizens.
+ internal static void DisplayCitizens() {
+ GUIStyle counterStyle = new GUIStyle();
+ CitizenManager citManager = Singleton.instance;
+ Citizen[] citizensBuffer = Singleton.instance.m_citizens.m_buffer;
+ VehicleParked[] parkedVehiclesBuffer = Singleton.instance.m_parkedVehicles.m_buffer;
+ Vehicle[] vehiclesBuffer = Singleton.instance.m_vehicles.m_buffer;
+
+ for (int i = 1; i < CitizenManager.MAX_INSTANCE_COUNT; ++i) {
+ if ((citManager.m_instances.m_buffer[i].m_flags &
+ CitizenInstance.Flags.Character) == CitizenInstance.Flags.None) {
+ continue;
+ }
+#if DEBUG
+ if (DebugSwitch.NoValidPathCitizensOverlay.Get()) {
+#endif
+ if (citManager.m_instances.m_buffer[i].m_path != 0) {
+ continue;
+ }
+#if DEBUG
+ }
+#endif
+
+ Vector3 pos = citManager.m_instances.m_buffer[i].GetSmoothPosition((ushort)i);
+ bool visible = GeometryUtil.WorldToScreenPoint(pos, out Vector3 screenPos);
+
+ if (!visible) {
+ continue;
+ }
+
+ Vector3 camPos = Singleton.instance.m_simulationView.m_position;
+ Vector3 diff = pos - camPos;
+
+ if (diff.magnitude > DEBUG_CLOSE_LOD) {
+ continue; // do not draw if too distant
+ }
+
+ float zoom = 1.0f / diff.magnitude * 150f;
+
+ counterStyle.fontSize = (int)(10f * zoom);
+ counterStyle.normal.textColor = new Color(1f, 0f, 1f);
+ // _counterStyle.normal.background = MakeTex(1, 1, new Color(0f, 0f, 0f, 0.4f));
+
+#if DEBUG
+ if (GlobalConfig.Instance.Debug.ExtPathMode != ExtPathMode.None &&
+ ExtCitizenInstanceManager.Instance.ExtInstances[i].pathMode !=
+ GlobalConfig.Instance.Debug.ExtPathMode) {
+ continue;
+ }
+#endif
+
+ var labelSb = new StringBuilder();
+ ExtCitizen[] extCitizensBuf = ExtCitizenManager.Instance.ExtCitizens;
+ labelSb.AppendFormat(
+ "Inst. {0}, Cit. {1},\nm: {2}, tm: {3}, ltm: {4}, ll: {5}",
+ i,
+ citManager.m_instances.m_buffer[i].m_citizen,
+ ExtCitizenInstanceManager.Instance.ExtInstances[i].pathMode,
+ extCitizensBuf[citManager.m_instances.m_buffer[i].m_citizen].transportMode,
+ extCitizensBuf[citManager.m_instances.m_buffer[i].m_citizen].lastTransportMode,
+ extCitizensBuf[citManager.m_instances.m_buffer[i].m_citizen].lastLocation);
+
+ if (citManager.m_instances.m_buffer[i].m_citizen != 0) {
+ Citizen citizen = citizensBuffer[citManager.m_instances.m_buffer[i].m_citizen];
+ if (citizen.m_parkedVehicle != 0) {
+ labelSb.AppendFormat(
+ "\nparked: {0} dist: {1}",
+ citizen.m_parkedVehicle,
+ (parkedVehiclesBuffer[citizen.m_parkedVehicle].m_position - pos).magnitude);
+ }
+
+ if (citizen.m_vehicle != 0) {
+ labelSb.AppendFormat(
+ "\nveh: {0} dist: {1}",
+ citizen.m_vehicle,
+ (vehiclesBuffer[citizen.m_vehicle].GetLastFramePosition() - pos).magnitude);
+ }
+ }
+
+ string labelStr = labelSb.ToString();
+ Vector2 dim = counterStyle.CalcSize(new GUIContent(labelStr));
+ Rect labelRect = new Rect(
+ x: screenPos.x - (dim.x / 2f),
+ y: screenPos.y - dim.y - 50f,
+ width: dim.x,
+ height: dim.y);
+
+ GUI.Box(labelRect, labelStr, counterStyle);
+ }
+ } // end DisplayCitizens
+
+ internal static void DisplayBuildings() {
+ GUIStyle _counterStyle = new GUIStyle();
+ BuildingManager buildingManager = Singleton.instance;
+
+ for (int i = 1; i < BuildingManager.MAX_BUILDING_COUNT; ++i) {
+ if ((buildingManager.m_buildings.m_buffer[i].m_flags & Building.Flags.Created)
+ == Building.Flags.None) {
+ continue;
+ }
+
+ Vector3 pos = buildingManager.m_buildings.m_buffer[i].m_position;
+ bool visible = GeometryUtil.WorldToScreenPoint(pos, out Vector3 screenPos);
+
+ if (!visible) {
+ continue;
+ }
+
+ Vector3 camPos = Singleton.instance.m_simulationView.m_position;
+ Vector3 diff = pos - camPos;
+ if (diff.magnitude > DEBUG_CLOSE_LOD) {
+ continue; // do not draw if too distant
+ }
+
+ float zoom = 150f / diff.magnitude;
+
+ _counterStyle.fontSize = (int)(10f * zoom);
+ _counterStyle.normal.textColor = new Color(0f, 1f, 0f);
+ // _counterStyle.normal.background = MakeTex(1, 1, new Color(0f, 0f, 0f, 0.4f));
+
+ string labelStr = string.Format(
+ "Building {0}, PDemand: {1}, IncTDem: {2}, OutTDem: {3}",
+ i,
+ ExtBuildingManager.Instance.ExtBuildings[i].parkingSpaceDemand,
+ ExtBuildingManager.Instance.ExtBuildings[i].incomingPublicTransportDemand,
+ ExtBuildingManager.Instance.ExtBuildings[i].outgoingPublicTransportDemand);
+
+ Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr));
+ Rect labelRect = new Rect(
+ x: screenPos.x - (dim.x / 2f),
+ y: screenPos.y - dim.y - 50f,
+ width: dim.x,
+ height: dim.y);
+
+ GUI.Box(labelRect, labelStr, _counterStyle);
+ }
+ } // end DisplayBuildings
+
+ /// Displays lane ids over lanes.
+ private static void DisplayLanes(ushort segmentId,
+ ref NetSegment segment,
+ ref NetInfo segmentInfo)
+ {
+ var _counterStyle = new GUIStyle();
+ Vector3 centerPos = segment.m_bounds.center;
+ bool visible = GeometryUtil.WorldToScreenPoint(centerPos, out Vector3 screenPos);
+
+ if (!visible) {
+ return;
+ }
+
+ screenPos.y -= 200;
+
+ if (screenPos.z < 0) {
+ return;
+ }
+
+ Vector3 camPos = Singleton.instance.m_simulationView.m_position;
+ Vector3 diff = centerPos - camPos;
+
+ if (diff.magnitude > DEBUG_CLOSE_LOD) {
+ return; // do not draw if too distant
+ }
+
+ float zoom = 1.0f / diff.magnitude * 150f;
+
+ _counterStyle.fontSize = (int)(11f * zoom);
+ _counterStyle.normal.textColor = new Color(1f, 1f, 0f);
+
+ // uint totalDensity = 0u;
+ // for (int i = 0; i < segmentInfo.m_lanes.Length; ++i) {
+ // if (CustomRoadAI.currentLaneDensities[segmentId] != null &&
+ // i < CustomRoadAI.currentLaneDensities[segmentId].Length)
+ // totalDensity += CustomRoadAI.currentLaneDensities[segmentId][i];
+ // }
+
+ uint curLaneId = segment.m_lanes;
+ var labelSb = new StringBuilder();
+ NetLane[] lanesBuffer = Singleton.instance.m_lanes.m_buffer;
+
+ for (int i = 0; i < segmentInfo.m_lanes.Length; ++i) {
+ if (curLaneId == 0) {
+ break;
+ }
+
+ bool laneTrafficDataLoaded =
+ TrafficMeasurementManager.Instance.GetLaneTrafficData(
+ segmentId,
+ (byte)i,
+ out LaneTrafficData laneTrafficData);
+
+ NetInfo.Lane laneInfo = segmentInfo.m_lanes[i];
+
+#if PFTRAFFICSTATS
+ uint pfTrafficBuf =
+ TrafficMeasurementManager
+ .Instance.segmentDirTrafficData[
+ TrafficMeasurementManager.Instance.GetDirIndex(
+ segmentId,
+ laneInfo.m_finalDirection)]
+ .totalPathFindTrafficBuffer;
+#endif
+ // TrafficMeasurementManager.Instance.GetTrafficData(segmentId,
+ // laneInfo.m_finalDirection, out dirTrafficData);
+ // int dirIndex = laneInfo.m_finalDirection == NetInfo.Direction.Backward ? 1 : 0;
+
+ labelSb.AppendFormat("L idx {0}, id {1}", i, curLaneId);
+#if DEBUG
+ labelSb.AppendFormat(
+ ", in: {0}, out: {1}, f: {2}, l: {3} km/h, rst: {4}, dir: {5}, fnl: {6}, " +
+ "pos: {7:0.##}, sim: {8} for {9}/{10}",
+ RoutingManager.Instance.CalcInnerSimilarLaneIndex(segmentId, i),
+ RoutingManager.Instance.CalcOuterSimilarLaneIndex(segmentId, i),
+ (NetLane.Flags)lanesBuffer[curLaneId].m_flags,
+ SpeedLimitManager.Instance.GetCustomSpeedLimit(curLaneId),
+ VehicleRestrictionsManager.Instance.GetAllowedVehicleTypes(
+ segmentId,
+ segmentInfo,
+ (uint)i,
+ laneInfo,
+ VehicleRestrictionsMode.Configured),
+ laneInfo.m_direction,
+ laneInfo.m_finalDirection,
+ laneInfo.m_position,
+ laneInfo.m_similarLaneIndex,
+ laneInfo.m_vehicleType,
+ laneInfo.m_laneType);
+#endif
+ if (laneTrafficDataLoaded) {
+ labelSb.AppendFormat(
+ ", sp: {0}%",
+ TrafficMeasurementManager.Instance.CalcLaneRelativeMeanSpeed(
+ segmentId,
+ (byte)i,
+ curLaneId,
+ laneInfo) / 100);
+#if DEBUG
+ labelSb.AppendFormat(
+ ", buf: {0}, max: {1}, acc: {2}",
+ laneTrafficData.trafficBuffer,
+ laneTrafficData.maxTrafficBuffer,
+ laneTrafficData.accumulatedSpeeds);
+
+#if PFTRAFFICSTATS
+ labelSb.AppendFormat(
+ ", pfBuf: {0}/{1}, ({2} %)",
+ laneTrafficData.pathFindTrafficBuffer,
+ laneTrafficData.lastPathFindTrafficBuffer,
+ pfTrafficBuf > 0
+ ? "" + ((laneTrafficData.lastPathFindTrafficBuffer * 100u) /
+ pfTrafficBuf)
+ : "n/a");
+#endif
+#endif
+#if MEASUREDENSITY
+ if (dirTrafficDataLoaded) {
+ labelSb.AppendFormat(
+ ", rel. dens.: {0}%",
+ dirTrafficData.accumulatedDensities > 0
+ ? "" + Math.Min(
+ laneTrafficData[i].accumulatedDensities * 100 /
+ dirTrafficData.accumulatedDensities,
+ 100)
+ : "?");
+ }
+
+ labelSb.AppendFormat(
+ ", acc: {0}",
+ laneTrafficData[i].accumulatedDensities);
+#endif
+ }
+
+ labelSb.AppendFormat(", nd: {0}", lanesBuffer[curLaneId].m_nodes);
+#if DEBUG
+ // labelSb.AppendFormat(
+ // " ({0}/{1}/{2})",
+ // CustomRoadAI.currentLaneDensities[segmentId] != null &&
+ // i < CustomRoadAI.currentLaneDensities[segmentId].Length
+ // ? string.Empty + CustomRoadAI.currentLaneDensities[segmentId][i]
+ // : "?",
+ // CustomRoadAI.maxLaneDensities[segmentId] != null &&
+ // i < CustomRoadAI.maxLaneDensities[segmentId].Length
+ // ? string.Empty + CustomRoadAI.maxLaneDensities[segmentId][i]
+ // : "?",
+ // totalDensity);
+ // labelSb.AppendFormat(
+ // " ({0}/{1})",
+ // CustomRoadAI.currentLaneDensities[segmentId] != null &&
+ // i < CustomRoadAI.currentLaneDensities[segmentId].Length
+ // ? string.Empty + CustomRoadAI.currentLaneDensities[segmentId][i]
+ // : "?",
+ // totalDensity);
+#endif
+ // labelSb.AppendFormat(
+ // ", abs. dens.: {0} %",
+ // CustomRoadAI.laneMeanAbsDensities[segmentId] != null &&
+ // i < CustomRoadAI.laneMeanAbsDensities[segmentId].Length
+ // ? "" + CustomRoadAI.laneMeanAbsDensities[segmentId][i]
+ // : "?");
+ labelSb.Append("\n");
+
+ curLaneId = lanesBuffer[curLaneId].m_nextLane;
+ }
+
+ var labelStr = labelSb.ToString();
+ Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr));
+ Rect labelRect = new Rect(
+ x: screenPos.x - (dim.x / 2f),
+ y: screenPos.y,
+ width: dim.x,
+ height: dim.y);
+
+ GUI.Label(labelRect, labelStr, _counterStyle);
+ } // end DisplayLanes
+ } // end class
+}
\ No newline at end of file
diff --git a/TLM/TLM/UI/Helpers/Highlight.cs b/TLM/TLM/UI/Helpers/Highlight.cs
new file mode 100644
index 000000000..16238d81a
--- /dev/null
+++ b/TLM/TLM/UI/Helpers/Highlight.cs
@@ -0,0 +1,359 @@
+namespace TrafficManager.UI {
+ using ColossalFramework;
+ using ColossalFramework.Math;
+ using TrafficManager.Util;
+ using UnityEngine;
+
+ ///
+ /// Provides static functions for drawing overlay textures.
+ /// Must be called from GUI callbacks only, will not work from other code.
+ ///
+ public static class Highlight {
+ ///
+ /// Create this to describe a grid for rendering multiple icons.
+ /// Icons are positioned in the XZ plane in the world around the GridOrigin, but rendered
+ /// normally in screen space with their sides axis aligned.
+ ///
+ public class Grid {
+ /// Grid starts here.
+ public Vector3 GridOrigin;
+
+ /// Grid cell width.
+ public float CellWidth;
+
+ /// Grid cell height.
+ public float CellHeight;
+
+ /// Grid basis vector for X axis.
+ public Vector3 Xu;
+
+ /// Grid basis vector for Y axis.
+ public Vector3 Yu;
+
+ public Grid(Vector3 gridOrigin, float cellWidth, float cellHeight, Vector3 xu, Vector3 yu) {
+ GridOrigin = gridOrigin;
+ CellWidth = cellWidth;
+ CellHeight = cellHeight;
+ Xu = xu;
+ Yu = yu;
+ }
+
+ /// Grid position in game coordinates for row and column.
+ /// Column.
+ /// Row.
+ /// World position.
+ public Vector3 GetPositionForRowCol(float col, float row) {
+ return this.GridOrigin
+ + (this.CellWidth * col * this.Xu)
+ + (this.CellHeight * row * this.Yu);
+ }
+
+ ///
+ /// Position a texture rectangle in a "grid cell" of a regular grid with center in the
+ /// GridOrigin, and basis xu,yu. The draw box is not rotated together with the grid basis
+ /// and is aligned with screen axes.
+ ///
+ /// Draw this.
+ /// Visible from here.
+ /// Column in grid.
+ /// Row in grid.
+ /// Square draw size (axis aligned).
+ /// Output visible screen rect.
+ public void DrawStaticSquareOverlayGridTexture(Texture2D texture,
+ Vector3 camPos,
+ float x,
+ float y,
+ float size,
+ out Rect screenRect) {
+ DrawGenericOverlayGridTexture(
+ texture: texture,
+ camPos: camPos,
+ x: x,
+ y: y,
+ width: size,
+ height: size,
+ canHover: false,
+ screenRect: out screenRect);
+ }
+
+ ///
+ /// Position a texture rectangle in a "grid cell" of a regular grid with center in the
+ /// GridOrigin, and basis xu,yu. The draw box is not rotated together with the grid basis
+ /// and is aligned with screen axes.
+ ///
+ /// Draw this.
+ /// Visible from here.
+ /// X position in grid.
+ /// Y position in grid.
+ /// Draw box size x.
+ /// Draw box size y.
+ /// Whether the icon is interacting with the mouse.
+ /// Output visible screen rect.
+ /// Whether mouse hovers the icon.
+ public bool DrawGenericOverlayGridTexture(Texture2D texture,
+ Vector3 camPos,
+ float x,
+ float y,
+ float width,
+ float height,
+ bool canHover,
+ out Rect screenRect) {
+ Vector3 worldPos = this.GetPositionForRowCol(x, y);
+
+ return Highlight.DrawGenericOverlayTexture(
+ texture,
+ camPos,
+ worldPos,
+ width,
+ height,
+ canHover,
+ out screenRect);
+ }
+ }
+
+ public static void DrawNodeCircle(RenderManager.CameraInfo cameraInfo,
+ ushort nodeId,
+ bool warning = false,
+ bool alpha = false) {
+ DrawNodeCircle(
+ cameraInfo: cameraInfo,
+ nodeId: nodeId,
+ color: ModUI.GetTrafficManagerTool(createIfRequired: false)
+ .GetToolColor(warning: warning, error: false),
+ alpha: alpha);
+ // TODO: Potentially we do not need to refer to a TrafficManagerTool object
+ }
+
+ ///
+ /// Gets the coordinates of the given node.
+ ///
+ private static Vector3 GetNodePos(ushort nodeId) {
+ NetNode[] nodeBuffer = Singleton.instance.m_nodes.m_buffer;
+ Vector3 pos = nodeBuffer[nodeId].m_position;
+ float terrainY = Singleton.instance.SampleDetailHeightSmooth(pos);
+ if (terrainY > pos.y) {
+ pos.y = terrainY;
+ }
+
+ return pos;
+ }
+
+ /// the average half width of all connected segments
+ private static float CalculateNodeRadius(ushort nodeId) {
+ float sumHalfWidth = 0;
+ int count = 0;
+ Constants.ServiceFactory.NetService.IterateNodeSegments(
+ nodeId,
+ (ushort segmentId, ref NetSegment segment) => {
+ sumHalfWidth += segment.Info.m_halfWidth;
+ count++;
+ return true;
+ });
+ return sumHalfWidth / count;
+ }
+
+ public static void DrawNodeCircle(RenderManager.CameraInfo cameraInfo,
+ ushort nodeId,
+ Color color,
+ bool alpha = false) {
+ float r = CalculateNodeRadius(nodeId);
+ Vector3 pos = Singleton.instance.m_nodes.m_buffer[nodeId].m_position;
+ DrawOverlayCircle(cameraInfo, color, pos, r * 2, alpha);
+ }
+
+ ///
+ /// Draws a half sausage at segment end.
+ ///
+ ///
+ /// The lenght of the highlight [0~1]
+ /// Determines the direction of the half sausage.
+ // TODO: move to UI.Helpers (Highlight)
+ public static void DrawCutSegmentEnd(RenderManager.CameraInfo cameraInfo,
+ ushort segmentId,
+ float cut,
+ bool bStartNode,
+ Color color,
+ bool alpha = false) {
+ if (segmentId == 0) {
+ return;
+ }
+
+ ref NetSegment segment =
+ ref Singleton.instance.m_segments.m_buffer[segmentId];
+ float width = segment.Info.m_halfWidth;
+
+ NetNode[] nodeBuffer = Singleton.instance.m_nodes.m_buffer;
+
+ bool IsMiddle(ushort nodeId) =>
+ (nodeBuffer[nodeId].m_flags & NetNode.Flags.Middle) != 0;
+
+ Bezier3 bezier;
+ bezier.a = GetNodePos(segment.m_startNode);
+ bezier.d = GetNodePos(segment.m_endNode);
+
+ NetSegment.CalculateMiddlePoints(
+ startPos: bezier.a,
+ startDir: segment.m_startDirection,
+ endPos: bezier.d,
+ endDir: segment.m_endDirection,
+ smoothStart: IsMiddle(segment.m_startNode),
+ smoothEnd: IsMiddle(segment.m_endNode),
+ middlePos1: out bezier.b,
+ middlePos2: out bezier.c);
+
+ if (bStartNode) {
+ bezier = bezier.Cut(0, cut);
+ } else {
+ bezier = bezier.Cut(1 - cut, 1);
+ }
+
+ Singleton.instance.m_drawCallData.m_overlayCalls++;
+ Singleton.instance.OverlayEffect.DrawBezier(
+ cameraInfo,
+ color,
+ bezier,
+ size: width * 2f,
+ cutStart: bStartNode ? 0 : width,
+ cutEnd: bStartNode ? width : 0,
+ minY: -1f,
+ maxY: 1280f,
+ renderLimits: false,
+ alpha);
+ }
+
+ ///
+ /// similar to NetTool.RenderOverlay()
+ /// but with additional control over alphaBlend.
+ ///
+ internal static void DrawSegmentOverlay(
+ RenderManager.CameraInfo cameraInfo,
+ ushort segmentId,
+ Color color,
+ bool alphaBlend) {
+ if (segmentId == 0) {
+ return;
+ }
+
+ ref NetSegment segment =
+ ref Singleton.instance.m_segments.m_buffer[segmentId];
+ float width = segment.Info.m_halfWidth;
+
+ NetNode[] nodeBuffer = Singleton.instance.m_nodes.m_buffer;
+
+ bool IsMiddle(ushort nodeId) =>
+ (nodeBuffer[nodeId].m_flags & NetNode.Flags.Middle) != 0;
+
+ Bezier3 bezier;
+ bezier.a = GetNodePos(segment.m_startNode);
+ bezier.d = GetNodePos(segment.m_endNode);
+
+ NetSegment.CalculateMiddlePoints(
+ startPos: bezier.a,
+ startDir: segment.m_startDirection,
+ endPos: bezier.d,
+ endDir: segment.m_endDirection,
+ smoothStart: IsMiddle(segment.m_startNode),
+ smoothEnd: IsMiddle(segment.m_endNode),
+ middlePos1: out bezier.b,
+ middlePos2: out bezier.c);
+
+ Singleton.instance.m_drawCallData.m_overlayCalls++;
+ Singleton.instance.OverlayEffect.DrawBezier(
+ cameraInfo,
+ color,
+ bezier,
+ size: width * 2f,
+ cutStart: 0,
+ cutEnd: 0,
+ minY: -1f,
+ maxY: 1280f,
+ renderLimits: false,
+ alphaBlend);
+ }
+
+ private static void DrawOverlayCircle(RenderManager.CameraInfo cameraInfo,
+ Color color,
+ Vector3 position,
+ float width,
+ bool alpha) {
+ Singleton.instance.m_drawCallData.m_overlayCalls++;
+ Singleton.instance.OverlayEffect.DrawCircle(
+ cameraInfo,
+ color,
+ position,
+ size: width,
+ minY: position.y - 100f,
+ maxY: position.y + 100f,
+ renderLimits: false,
+ alpha);
+ }
+
+ public static bool DrawHoverableSquareOverlayTexture(Texture2D texture,
+ Vector3 camPos,
+ Vector3 worldPos,
+ float size) {
+ return DrawGenericOverlayTexture(
+ texture,
+ camPos,
+ worldPos,
+ width: size,
+ height: size,
+ canHover: true,
+ screenRect: out Rect _);
+ }
+
+ public static bool DrawGenericSquareOverlayTexture(Texture2D texture,
+ Vector3 camPos,
+ Vector3 worldPos,
+ float size,
+ bool canHover) {
+ return DrawGenericOverlayTexture(
+ texture,
+ camPos,
+ worldPos,
+ width: size,
+ height: size,
+ canHover,
+ screenRect: out Rect _);
+ }
+
+ public static bool DrawGenericOverlayTexture(Texture2D texture,
+ Vector3 camPos,
+ Vector3 worldPos,
+ float width,
+ float height,
+ bool canHover,
+ out Rect screenRect) {
+ // Is point in screen?
+ if (!GeometryUtil.WorldToScreenPoint(worldPos, out Vector3 screenPos)) {
+ screenRect = default;
+ return false;
+ }
+
+ // UI Scale should not affect the overlays (no multiplication by U.UIScaler.GetScale())
+ float visibleScale = 1.0f / (worldPos - camPos).magnitude * 100f;
+ width *= visibleScale;
+ height *= visibleScale;
+
+ screenRect = new Rect(
+ x: screenPos.x - (width / 2f),
+ y: screenPos.y - (height / 2f),
+ width: width,
+ height: height);
+
+ Color guiColor = GUI.color;
+ bool hovered = false;
+
+ if (canHover) {
+ hovered = TrafficManagerTool.IsMouseOver(screenRect);
+ }
+
+ guiColor.a = TrafficManagerTool.GetHandleAlpha(hovered);
+
+ GUI.color = guiColor;
+ GUI.DrawTexture(screenRect, texture);
+
+ return hovered;
+ }
+ }
+}
\ No newline at end of file
diff --git a/TLM/TLM/UI/Helpers/OverlayHandleColorController.cs b/TLM/TLM/UI/Helpers/OverlayHandleColorController.cs
new file mode 100644
index 000000000..f952f2c50
--- /dev/null
+++ b/TLM/TLM/UI/Helpers/OverlayHandleColorController.cs
@@ -0,0 +1,57 @@
+namespace TrafficManager.UI.Helpers {
+ using UnityEngine;
+
+ ///
+ /// Provides consistent coloring logic for graphic overlays rendered with GUI.DrawTexture.
+ /// Usage:
+ /// 1. Create color controller at the start of your overlay render.
+ /// 2. Call .SetGUIColor() with mouse hover true or false.
+ /// 3. In the end call .RestoreGUIColor()
+ ///
+ public struct OverlayHandleColorController {
+ private readonly bool isInteractable_;
+
+ ///
+ /// This color is used when drawing overlay clickable handles which are not disabled
+ /// and have mouse hovering over them (i.e. can be interacted with).
+ ///
+ private static Color MOUSE_HOVER_INTERACTABLE_COLOR = new Color(r: 1f, g: .7f, b: 0f);
+ private static Color NON_INTERACTABLE_COLOR = Color.gray;
+
+ private readonly Color originalColor_;
+
+ public OverlayHandleColorController(bool isInteractable) {
+ this.isInteractable_ = isInteractable;
+ this.originalColor_ = GUI.color;
+ }
+
+ ///
+ /// Magical logic, coloring interactable GUI overlay signs with orange and making them semi
+ /// or full opaque.
+ ///
+ /// Whether mouse is over the sign. Note: Interactive is set from
+ /// the constructor.
+ /// Multiply alpha with this. Can use to fade signs with distance.
+ public void SetGUIColor(bool hovered, float opacityMultiplier = 1f) {
+ var tmpColor = this.originalColor_;
+
+ if (this.isInteractable_) {
+ tmpColor.a = TrafficManagerTool.GetHandleAlpha(hovered) * opacityMultiplier;
+ if (hovered) {
+ tmpColor = Color.Lerp(tmpColor, MOUSE_HOVER_INTERACTABLE_COLOR, 0.5f);
+ }
+ } else {
+ // Gray-ish color and non-hover transparency
+ tmpColor.a = TrafficManagerTool.GetHandleAlpha(hovered: false) * opacityMultiplier;
+ tmpColor = Color.Lerp(tmpColor, NON_INTERACTABLE_COLOR, 0.5f);
+ }
+
+ GUI.color = tmpColor;
+ }
+
+ /// Set GUI color like it was before. Probably white and full opacity.
+ public void RestoreGUIColor() {
+ GUI.color = this.originalColor_;
+ }
+ }
+}
\ No newline at end of file
diff --git a/TLM/TLM/UI/Helpers/SegmentLaneMarker.cs b/TLM/TLM/UI/Helpers/SegmentLaneMarker.cs
index c9bd41b41..634d4ac21 100644
--- a/TLM/TLM/UI/Helpers/SegmentLaneMarker.cs
+++ b/TLM/TLM/UI/Helpers/SegmentLaneMarker.cs
@@ -103,16 +103,16 @@ internal void RenderOverlay(RenderManager.CameraInfo cameraInfo, Color color, bo
ColossalFramework.Singleton.instance.m_drawCallData.m_overlayCalls++;
RenderManager.instance.OverlayEffect.DrawBezier(
- cameraInfo,
- color,
- Bezier,
- enlarge ? Size * 1.41f : Size,
- 0,
- 0,
- minH - 100f,
- maxH + 100f,
- true,
- false);
+ cameraInfo: cameraInfo,
+ color: color,
+ bezier: Bezier,
+ size: enlarge ? Size * 1.41f : Size,
+ cutStart: 0,
+ cutEnd: 0,
+ minY: minH - 100f,
+ maxY: maxH + 100f,
+ renderLimits: true,
+ alphaBlend: false);
}
}
}
diff --git a/TLM/TLM/UI/Helpers/TrafficRulesOverlay.cs b/TLM/TLM/UI/Helpers/TrafficRulesOverlay.cs
index 7b6f6346e..2893fecf2 100644
--- a/TLM/TLM/UI/Helpers/TrafficRulesOverlay.cs
+++ b/TLM/TLM/UI/Helpers/TrafficRulesOverlay.cs
@@ -101,9 +101,10 @@ public SignsLayout(ushort segmentId,
this.startX_ = -lenX * 0.5f;
}
+ // TODO: Refactor this again, move mouse interaction logic out to the caller.
public bool DrawSign(bool small,
ref Vector3 camPos,
- Color guiColor,
+ OverlayHandleColorController colorController,
Texture2D signTexture)
{
int col = counter_ / signsPerRow_;
@@ -135,25 +136,9 @@ public bool DrawSign(bool small,
height: size);
bool hoveredHandle = !viewOnly_ && TrafficManagerTool.IsMouseOver(boundingBox);
- if (viewOnly_) {
- // Readonly signs look grey-ish
- guiColor = Color.Lerp(guiColor, Color.gray, 0.5f);
- guiColor.a = TrafficManagerTool.GetHandleAlpha(hovered: false);
- } else {
- // Handles in edit mode are always visible. Hovered handles are also highlighted.
- guiColor.a = 1f;
-
- if (hoveredHandle) {
- guiColor = Color.Lerp(
- a: guiColor,
- b: new Color(r: 1f, g: .7f, b: 0f),
- t: 0.5f);
- }
- }
- // guiColor.a = TrafficManagerTool.GetHandleAlpha(hoveredHandle);
-
- GUI.color = guiColor;
+ colorController.SetGUIColor(hoveredHandle);
GUI.DrawTexture(boundingBox, signTexture);
+
return hoveredHandle;
}
}
@@ -178,6 +163,7 @@ public bool DrawSignHandles(ushort nodeId,
// * and no permanent overlay enabled,
// * and is Junctions restrictions tool
// TODO generalize for all tools.
+ // NOTE to the above: Do not overly generalize, write everything twice (WET) as opposed to DRY
if (this.ViewOnly &&
!(Options.junctionRestrictionsOverlay ||
MassEditOverlay.IsActive) &&
@@ -189,6 +175,7 @@ public bool DrawSignHandles(ushort nodeId,
Color guiColor = GUI.color;
// Vector3 nodePos = Singleton.instance.m_nodes.m_buffer[nodeId].m_position;
IExtSegmentEndManager segEndMan = Constants.ManagerFactory.ExtSegmentEndManager;
+ var colorController = new OverlayHandleColorController(isInteractable: !this.ViewOnly);
for (int i = 0; i < 8; ++i) {
ushort segmentId = node.GetSegment(i);
@@ -221,7 +208,7 @@ public bool DrawSignHandles(ushort nodeId,
startNode: isStartNode,
signsPerRow: isIncoming ? 2 : 1,
viewOnly: this.ViewOnly,
- baseZoom: this.mainTool_.GetBaseZoom());
+ baseZoom: U.UIScaler.GetScale());
IJunctionRestrictionsManager junctionRManager = Constants.ManagerFactory.JunctionRestrictionsManager;
@@ -249,7 +236,7 @@ public bool DrawSignHandles(ushort nodeId,
bool signHovered = signsLayout.DrawSign(
small: !configurable,
camPos: ref camPos,
- guiColor: guiColor,
+ colorController: colorController,
signTexture: allowed
? JunctionRestrictions.LaneChangeAllowed
: JunctionRestrictions.LaneChangeForbidden);
@@ -283,7 +270,7 @@ public bool DrawSignHandles(ushort nodeId,
bool signHovered = signsLayout.DrawSign(
small: !configurable,
camPos: ref camPos,
- guiColor: guiColor,
+ colorController: colorController,
signTexture: allowed
? JunctionRestrictions.UturnAllowed
: JunctionRestrictions.UturnForbidden);
@@ -324,7 +311,7 @@ public bool DrawSignHandles(ushort nodeId,
bool signHovered = signsLayout.DrawSign(
small: !configurable,
camPos: ref camPos,
- guiColor: guiColor,
+ colorController: colorController,
signTexture: allowed
? JunctionRestrictions.EnterBlockedJunctionAllowed
: JunctionRestrictions.EnterBlockedJunctionForbidden);
@@ -356,7 +343,7 @@ public bool DrawSignHandles(ushort nodeId,
bool signHovered = signsLayout.DrawSign(
small: !configurable,
camPos: ref camPos,
- guiColor: guiColor,
+ colorController: colorController,
signTexture: allowed
? JunctionRestrictions.PedestrianCrossingAllowed
: JunctionRestrictions.PedestrianCrossingForbidden);
@@ -405,7 +392,7 @@ public bool DrawSignHandles(ushort nodeId,
bool signHovered = signsLayout.DrawSign(
small: !configurable,
camPos: ref camPos,
- guiColor: guiColor,
+ colorController: colorController,
signTexture: allowed
? JunctionRestrictions.LeftOnRedAllowed
: JunctionRestrictions.LeftOnRedForbidden);
@@ -446,7 +433,7 @@ public bool DrawSignHandles(ushort nodeId,
bool signHovered = signsLayout.DrawSign(
small: !configurable,
camPos: ref camPos,
- guiColor: guiColor,
+ colorController: colorController,
signTexture: allowed
? JunctionRestrictions.RightOnRedAllowed
: JunctionRestrictions.RightOnRedForbidden);
@@ -465,10 +452,8 @@ public bool DrawSignHandles(ushort nodeId,
}
}
- guiColor.a = 1f;
- GUI.color = guiColor;
-
+ colorController.RestoreGUIColor();
return isAnyHovered;
- }
+ } // end draw_sign_handles()
} // end class
}
\ No newline at end of file
diff --git a/TLM/TLM/UI/LegacySubTool.cs b/TLM/TLM/UI/LegacySubTool.cs
index 6816669f5..16e563a7b 100644
--- a/TLM/TLM/UI/LegacySubTool.cs
+++ b/TLM/TLM/UI/LegacySubTool.cs
@@ -156,6 +156,7 @@ public virtual void OnActivate() {
/// other tools.
///
/// The camera.
+ [Obsolete("Use new style TrafficManagerSubtool.RenderGenericInfoOverlay()")]
public virtual void RenderOverlayForOtherTools(RenderManager.CameraInfo cameraInfo) { }
public virtual void ShowGUIOverlay(ToolMode toolMode, bool viewOnly) { }
@@ -172,6 +173,7 @@ public virtual string GetTutorialKey() {
return this.GetType().Name;
}
+ [Obsolete("Implements old style IMGUI dragging. Do not use for new code.")]
protected void DragWindow(ref Rect window) {
Vector2 resolution = UIView.GetAView().GetScreenResolution();
window.x = Mathf.Clamp(window.x, 0, resolution.x - window.width);
diff --git a/TLM/TLM/UI/MainMenu/BaseMenuButton.cs b/TLM/TLM/UI/MainMenu/BaseMenuButton.cs
index 6ff613916..e1e200fd4 100644
--- a/TLM/TLM/UI/MainMenu/BaseMenuButton.cs
+++ b/TLM/TLM/UI/MainMenu/BaseMenuButton.cs
@@ -1,13 +1,12 @@
namespace TrafficManager.UI.MainMenu {
- using System;
using System.Collections.Generic;
using ColossalFramework.UI;
- using TrafficManager.U.Button;
+ using TrafficManager.U;
///
/// Base class for main menu panel buttons.
///
- public abstract class BaseMenuButton : BaseUButton {
+ public abstract class BaseMenuButton : U.BaseUButton {
/// Menu button gameobject name.
private const string MENU_BUTTON = "TMPE_MenuButton";
@@ -15,8 +14,8 @@ public abstract class BaseMenuButton : BaseUButton {
/// When creating the main panel, texture atlas is created for all buttons. Here
/// each button is given a chance to add their own required sprites to that atlas.
///
- /// List to modify.
- public abstract void SetupButtonSkin(HashSet atlasKeys);
+ /// This will be populated with required sprite names/paths.
+ public abstract void SetupButtonSkin(AtlasBuilder futureAtlas);
public override void HandleClick(UIMouseEventParameter p) { }
diff --git a/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs b/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs
index 34fe3a5df..3a39059f9 100644
--- a/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs
+++ b/TLM/TLM/UI/MainMenu/ClearTrafficButton.cs
@@ -3,25 +3,28 @@
using ColossalFramework.UI;
using TrafficManager.Manager.Impl;
using TrafficManager.RedirectionFramework;
- using TrafficManager.U.Button;
+ using TrafficManager.U;
+ using TrafficManager.Util;
public class ClearTrafficButton : BaseMenuButton {
protected override bool IsActive() => false;
- protected override string GetTooltip() => Translation.Menu.Get("Tooltip:Clear traffic");
+ protected override string U_OverrideTooltipText() => Translation.Menu.Get("Tooltip:Clear traffic");
protected override bool IsVisible() => true;
- public override void SetupButtonSkin(HashSet atlasKeys) {
+ public override void SetupButtonSkin(AtlasBuilder futureAtlas) {
// Button backround (from BackgroundPrefix) is provided by MainMenuPanel.Start
- this.Skin = new U.Button.ButtonSkin() {
- Prefix = "ClearTraffic",
- BackgroundPrefix = "RoundButton",
- BackgroundHovered = true,
- BackgroundActive = true,
- ForegroundActive = true,
- };
- atlasKeys.AddRange(this.Skin.CreateAtlasKeyset());
+ this.Skin = new U.ButtonSkin() {
+ Prefix = "ClearTraffic",
+ BackgroundPrefix = "RoundButton",
+ BackgroundHovered = true,
+ BackgroundActive = true,
+ ForegroundActive = true,
+ };
+ this.Skin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
}
protected override void OnClick(UIMouseEventParameter p) {
diff --git a/TLM/TLM/UI/MainMenu/DespawnButton.cs b/TLM/TLM/UI/MainMenu/DespawnButton.cs
index 06c948c4e..c2de01879 100644
--- a/TLM/TLM/UI/MainMenu/DespawnButton.cs
+++ b/TLM/TLM/UI/MainMenu/DespawnButton.cs
@@ -3,10 +3,11 @@ namespace TrafficManager.UI.MainMenu {
using ColossalFramework.UI;
using TrafficManager.RedirectionFramework;
using TrafficManager.State;
- using TrafficManager.U.Button;
+ using TrafficManager.U;
+ using TrafficManager.Util;
public class DespawnButton : BaseMenuButton {
- protected override string GetTooltip() =>
+ protected override string U_OverrideTooltipText() =>
Options.disableDespawning
? Translation.Menu.Get("Tooltip:Enable despawning")
: Translation.Menu.Get("Tooltip:Disable despawning");
@@ -19,16 +20,18 @@ protected override string GetTooltip() =>
///
protected override bool IsActive() => !Options.disableDespawning;
- public override void SetupButtonSkin(HashSet atlasKeys) {
+ public override void SetupButtonSkin(AtlasBuilder futureAtlas) {
// Button backround (from BackgroundPrefix) is provided by MainMenuPanel.Start
- this.Skin = new U.Button.ButtonSkin() {
- Prefix = "TrafficDespawning",
- BackgroundPrefix = "RoundButton",
- BackgroundHovered = true,
- BackgroundActive = true,
- ForegroundActive = true,
- };
- atlasKeys.AddRange(this.Skin.CreateAtlasKeyset());
+ this.Skin = new U.ButtonSkin() {
+ Prefix = "TrafficDespawning",
+ BackgroundPrefix = "RoundButton",
+ BackgroundHovered = true,
+ BackgroundActive = true,
+ ForegroundActive = true,
+ };
+ this.Skin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
}
protected override void OnClick(UIMouseEventParameter p) {
diff --git a/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs b/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs
index 25eb72290..56a6317b9 100644
--- a/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs
+++ b/TLM/TLM/UI/MainMenu/JunctionRestrictionsButton.cs
@@ -3,27 +3,30 @@
using TrafficManager.RedirectionFramework;
using TrafficManager.State;
using TrafficManager.State.Keybinds;
- using TrafficManager.U.Button;
+ using TrafficManager.U;
+ using TrafficManager.Util;
public class JunctionRestrictionsButton : BaseMenuToolModeButton {
protected override ToolMode ToolMode => ToolMode.JunctionRestrictions;
- public override void SetupButtonSkin(HashSet atlasKeys) {
+ public override void SetupButtonSkin(AtlasBuilder futureAtlas) {
// Button backround (from BackgroundPrefix) is provided by MainMenuPanel.Start
- this.Skin = new U.Button.ButtonSkin() {
- Prefix = "JunctionRestrictions",
- BackgroundPrefix = "RoundButton",
- BackgroundHovered = true,
- BackgroundActive = true,
- ForegroundActive = true,
- };
- atlasKeys.AddRange(this.Skin.CreateAtlasKeyset());
+ this.Skin = new U.ButtonSkin() {
+ Prefix = "JunctionRestrictions",
+ BackgroundPrefix = "RoundButton",
+ BackgroundHovered = true,
+ BackgroundActive = true,
+ ForegroundActive = true,
+ };
+ this.Skin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
}
- protected override string GetTooltip() =>
+ protected override string U_OverrideTooltipText() =>
Translation.Menu.Get("Tooltip:Junction restrictions");
- public override KeybindSetting GetShortcutKey() => KeybindSettingsBase.JunctionRestrictionsTool;
+ public override KeybindSetting U_OverrideTooltipShortcutKey() => KeybindSettingsBase.JunctionRestrictionsTool;
protected override bool IsVisible() => IsButtonEnabled();
diff --git a/TLM/TLM/UI/MainMenu/LaneArrowsMenuButton.cs b/TLM/TLM/UI/MainMenu/LaneArrowsMenuButton.cs
index d62369db6..88d5d12e7 100644
--- a/TLM/TLM/UI/MainMenu/LaneArrowsMenuButton.cs
+++ b/TLM/TLM/UI/MainMenu/LaneArrowsMenuButton.cs
@@ -2,27 +2,30 @@
using System.Collections.Generic;
using TrafficManager.RedirectionFramework;
using TrafficManager.State.Keybinds;
- using TrafficManager.U.Button;
+ using TrafficManager.U;
+ using TrafficManager.Util;
public class LaneArrowsMenuButton : BaseMenuToolModeButton {
protected override ToolMode ToolMode => ToolMode.LaneArrows;
- public override void SetupButtonSkin(HashSet atlasKeys) {
+ public override void SetupButtonSkin(AtlasBuilder futureAtlas) {
// Button backround (from BackgroundPrefix) is provided by MainMenuPanel.Start
- this.Skin = new U.Button.ButtonSkin() {
- Prefix = "LaneArrows",
- BackgroundPrefix = "RoundButton",
- BackgroundHovered = true,
- BackgroundActive = true,
- ForegroundActive = true,
- };
- atlasKeys.AddRange(this.Skin.CreateAtlasKeyset());
+ this.Skin = new U.ButtonSkin() {
+ Prefix = "LaneArrows",
+ BackgroundPrefix = "RoundButton",
+ BackgroundHovered = true,
+ BackgroundActive = true,
+ ForegroundActive = true,
+ };
+ this.Skin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
}
- protected override string GetTooltip() => Translation.Menu.Get("Tooltip:Change lane arrows");
+ protected override string U_OverrideTooltipText() => Translation.Menu.Get("Tooltip:Change lane arrows");
protected override bool IsVisible() => true;
- public override KeybindSetting GetShortcutKey() => KeybindSettingsBase.LaneArrowTool;
+ public override KeybindSetting U_OverrideTooltipShortcutKey() => KeybindSettingsBase.LaneArrowTool;
}
}
diff --git a/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs b/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs
index 597e94440..2bc3f676a 100644
--- a/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs
+++ b/TLM/TLM/UI/MainMenu/LaneConnectorButton.cs
@@ -3,26 +3,29 @@
using TrafficManager.RedirectionFramework;
using TrafficManager.State;
using TrafficManager.State.Keybinds;
- using TrafficManager.U.Button;
+ using TrafficManager.U;
+ using TrafficManager.Util;
public class LaneConnectorButton : BaseMenuToolModeButton {
protected override ToolMode ToolMode => ToolMode.LaneConnector;
- public override void SetupButtonSkin(HashSet atlasKeys) {
+ public override void SetupButtonSkin(AtlasBuilder futureAtlas) {
// Button backround (from BackgroundPrefix) is provided by MainMenuPanel.Start
- this.Skin = new U.Button.ButtonSkin() {
- Prefix = "LaneConnector",
- BackgroundPrefix = "RoundButton",
- BackgroundHovered = true,
- BackgroundActive = true,
- ForegroundActive = true,
- };
- atlasKeys.AddRange(this.Skin.CreateAtlasKeyset());
+ this.Skin = new U.ButtonSkin() {
+ Prefix = "LaneConnector",
+ BackgroundPrefix = "RoundButton",
+ BackgroundHovered = true,
+ BackgroundActive = true,
+ ForegroundActive = true,
+ };
+ this.Skin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
}
- protected override string GetTooltip() => Translation.Menu.Get("Tooltip:Lane connector");
+ protected override string U_OverrideTooltipText() => Translation.Menu.Get("Tooltip:Lane connector");
- public override KeybindSetting GetShortcutKey() => KeybindSettingsBase.LaneConnectionsTool;
+ public override KeybindSetting U_OverrideTooltipShortcutKey() => KeybindSettingsBase.LaneConnectionsTool;
protected override bool IsVisible() => IsButtonEnabled();
diff --git a/TLM/TLM/UI/MainMenu/MainMenuButton.cs b/TLM/TLM/UI/MainMenu/MainMenuButton.cs
index b4d0e2f14..bc84a27a8 100644
--- a/TLM/TLM/UI/MainMenu/MainMenuButton.cs
+++ b/TLM/TLM/UI/MainMenu/MainMenuButton.cs
@@ -6,7 +6,7 @@ namespace TrafficManager.UI.MainMenu {
using TrafficManager.State;
using TrafficManager.State.Keybinds;
using TrafficManager.U;
- using TrafficManager.U.Button;
+ using TrafficManager.Util;
using UnityEngine;
public class MainMenuButton
@@ -29,19 +29,22 @@ public override void Start() {
// Let the mainmenu atlas know we need this texture and assign it to self.atlas.
this.Skin = new ButtonSkin {
- BackgroundPrefix = "MainMenuButton",
- Prefix = "MainMenuButton",
- BackgroundHovered = true,
- BackgroundActive = true,
- ForegroundHovered = true,
- ForegroundActive = true,
- };
- this.atlas = this.Skin.CreateAtlas(
- "MainMenu",
- 50,
- 50,
- 256,
- this.Skin.CreateAtlasKeyset());
+ BackgroundPrefix = "MainMenuButton",
+ Prefix = "MainMenuButton",
+ BackgroundHovered = true,
+ BackgroundActive = true,
+ ForegroundHovered = true,
+ ForegroundActive = true,
+ };
+
+ var futureAtlas = new U.AtlasBuilder();
+ this.Skin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
+ this.atlas = futureAtlas.CreateAtlas(
+ atlasName: "MainTMPEButton_Atlas",
+ loadingPath: "MainMenu",
+ atlasSizeHint: new IntVector2(256));
UpdateButtonImageAndTooltip();
// Set the button dimensions to smallest of 2.6% of screen width or 4.6% of screen height
@@ -161,12 +164,12 @@ public void OnGUI() {
}
}
- protected override string GetTooltip() {
+ protected override string U_OverrideTooltipText() {
return Translation.Menu.Get("Tooltip:Toggle Main Menu");
// return KeybindSettingsBase.ToggleMainMenu.ToLocalizedString("\n");
}
- public override KeybindSetting GetShortcutKey() => KeybindSettingsBase.ToggleMainMenu;
+ public override KeybindSetting U_OverrideTooltipShortcutKey() => KeybindSettingsBase.ToggleMainMenu;
protected override bool IsVisible() => true;
diff --git a/TLM/TLM/UI/MainMenu/MainMenuWindow.cs b/TLM/TLM/UI/MainMenu/MainMenuWindow.cs
index 717b0d11d..973ffeaab 100644
--- a/TLM/TLM/UI/MainMenu/MainMenuWindow.cs
+++ b/TLM/TLM/UI/MainMenu/MainMenuWindow.cs
@@ -7,15 +7,16 @@ namespace TrafficManager.UI.MainMenu {
using System.Linq;
using System.Reflection;
using TrafficManager.API.Util;
- using TrafficManager.RedirectionFramework;
using TrafficManager.State.Keybinds;
using TrafficManager.State;
using TrafficManager.U;
using TrafficManager.U.Autosize;
- using TrafficManager.U.Button;
- using TrafficManager.U.Panel;
+ using TrafficManager.Util;
using UnityEngine;
+ ///
+ /// Implements the main TMPE window with tool palette, drag handle, hanging onscreen hints etc.
+ ///
public class MainMenuWindow
: U.Panel.BaseUWindowPanel,
IObserver
@@ -117,7 +118,7 @@ private static readonly MenuButtonDef[] EXTRA_BUTTON_DEFS
public UILabel StatsLabel { get; private set; }
- public UIDragHandle DragHandle { get; private set; }
+ private UIDragHandle dragHandle_;
IDisposable confDisposable;
@@ -158,7 +159,6 @@ internal static MainMenuWindow CreateMainMenuWindow() {
builder.SetPadding(UConst.UIPADDING);
window.SetupControls(builder);
- // window.SetTransparency(GlobalConfig.Instance.Main.GuiTransparency);
// Resize everything correctly
builder.Done();
@@ -171,16 +171,10 @@ internal static MainMenuWindow CreateMainMenuWindow() {
private void SetupWindow() {
this.name = WINDOW_CONTROL_NAME;
this.isVisible = false;
- this.backgroundSprite = "GenericPanel";
- this.color = new Color32(64, 64, 64, 240);
- this.SetOpacity(
- U.UOpacityValue.FromOpacity(0.01f * GlobalConfig.Instance.Main.GuiOpacity));
+ this.GenericBackgroundAndOpacity();
- var dragHandler = new GameObject("TMPE_Menu_DragHandler");
- dragHandler.transform.parent = transform;
- dragHandler.transform.localPosition = Vector3.zero;
- this.DragHandle = dragHandler.AddComponent();
- this.DragHandle.enabled = !GlobalConfig.Instance.Main.MainMenuPosLocked;
+ this.dragHandle_ = this.CreateDragHandle();
+ this.dragHandle_.enabled = !GlobalConfig.Instance.Main.MainMenuPosLocked;
this.eventVisibilityChanged += OnVisibilityChanged;
}
@@ -190,46 +184,47 @@ private void SetupWindow() {
public void SetupControls(UiBuilder builder) {
// Create and populate list of background atlas keys, used by all buttons
// And also each button will have a chance to add their own atlas keys for loading.
- var tmpSkin = new U.Button.ButtonSkin {
- Prefix = "MainMenuPanel",
- BackgroundPrefix = "RoundButton",
- ForegroundNormal = false,
- BackgroundHovered = true,
- BackgroundActive = true,
- };
+ var tmpSkin = new U.ButtonSkin {
+ Prefix = "MainMenuPanel",
+ BackgroundPrefix = "RoundButton",
+ ForegroundNormal = false,
+ BackgroundHovered = true,
+ BackgroundActive = true,
+ };
+
// By default the atlas will include backgrounds: DefaultRound-bg-normal
- HashSet atlasKeysSet = tmpSkin.CreateAtlasKeyset();
+ var futureAtlas = new U.AtlasBuilder();
+ tmpSkin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
// Create Version Label and Help button:
// [ TM:PE 11.x ] [?]
- UILabel versionLabel = SetupControls_TopRow(builder, atlasKeysSet);
+ SetupControls_TopRow(builder, futureAtlas);
// Main menu contains 2 panels side by side, one for tool buttons and another for
// despawn & clear buttons.
ButtonsDict = new Dictionary();
- U.Panel.UPanel leftPanel;
+ U.UPanel leftPanel;
- using (var innerPanelB = builder.ChildPanel(setupFn: p => {
+ using (var innerPanelB = builder.ChildPanel(setupFn: p => {
p.name = "TMPE_MainMenu_InnerPanel";
})) {
innerPanelB.ResizeFunction(r => {
- r.Stack(mode: UStackMode.Below, spacing: 0f, stackRef: versionLabel);
+ r.Stack(mode: UStackMode.Below, spacing: 0f, stackRef: this.VersionLabel);
r.FitToChildren();
});
- AddButtonsResult toolButtonsResult
- = SetupControls_ToolPanel(innerPanelB, atlasKeysSet);
+ AddButtonsResult toolButtonsResult = SetupControls_ToolPanel(innerPanelB, futureAtlas);
- SetupControls_ExtraPanel(innerPanelB, atlasKeysSet, toolButtonsResult);
+ SetupControls_ExtraPanel(innerPanelB, futureAtlas, toolButtonsResult);
}
// Create atlas and give it to all buttons
- allButtonsAtlas_ = tmpSkin.CreateAtlas(
+ allButtonsAtlas_ = futureAtlas.CreateAtlas(
+ atlasName: "MainMenu_Atlas",
loadingPath: "MainMenu.Tool",
- spriteWidth: 50,
- spriteHeight: 50,
- hintAtlasTextureSize: 512,
- atlasKeysSet);
+ atlasSizeHint: new IntVector2(512));
foreach (BaseMenuButton b in ToolButtonsList) {
b.atlas = allButtonsAtlas_;
@@ -249,7 +244,7 @@ AddButtonsResult toolButtonsResult
}
private void SetupControls_OnscreenDisplayPanel(UiBuilder builder) {
- using (var osdBuilder = builder.ChildPanel(
+ using (var osdBuilder = builder.ChildPanel(
p => {
p.name = "TMPE_MainMenu_KeybindsPanel";
// the GenericPanel sprite is Light Silver, make it dark
@@ -283,16 +278,13 @@ private void SetupControls_OnscreenDisplayPanel(UiBuilder builde
}
}
- private UILabel SetupControls_TopRow(UiBuilder builder,
- HashSet atlasKeySet) {
- UILabel versionLabel;
-
- using (var versionLabelB = builder.Label(TrafficManagerMod.ModName)) {
- versionLabelB.ResizeFunction(r => r.Stack(UStackMode.Below));
- this.VersionLabel = versionLabel = versionLabelB.Control;
- }
+ private void SetupControls_TopRow(UiBuilder builder,
+ U.AtlasBuilder futureAtlas) {
+ this.VersionLabel = builder.Label(
+ t: TrafficManagerMod.ModName,
+ stack: UStackMode.Below);
- using (var btnBuilder = builder.Button()) {
+ using (var btnBuilder = builder.Button()) {
UButton control = btnBuilder.Control;
this.toggleOsdButton_ = control;
control.atlas = this.allButtonsAtlas_;
@@ -300,13 +292,15 @@ private UILabel SetupControls_TopRow(UiBuilder builder,
// Texture for Help will be included in the `allButtonsAtlas_`
ButtonSkin skin = new ButtonSkin {
- BackgroundPrefix = "RoundButton",
- Prefix = "Help",
- BackgroundHovered = true,
- BackgroundActive = true,
- ForegroundActive = true,
- };
- atlasKeySet.AddRange(skin.CreateAtlasKeyset());
+ BackgroundPrefix = "RoundButton",
+ Prefix = "Help",
+ BackgroundHovered = true,
+ BackgroundActive = true,
+ ForegroundActive = true,
+ };
+ skin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
control.Skin = skin;
control.UpdateButtonImage();
@@ -314,14 +308,14 @@ private UILabel SetupControls_TopRow(UiBuilder builder,
// This has to be done later when form setup is done:
// helpB.Control.atlas = allButtonsAtlas_;
+ // assume Version label is 18 units high
+ btnBuilder.SetFixedSize(new Vector2(18f, 18f));
btnBuilder.ResizeFunction(
resizeFn: r => {
r.Control.isVisible = true; // not sure why its hidden on create? TODO
r.Stack(mode: UStackMode.ToTheRight,
spacing: UConst.UIPADDING * 3f,
- stackRef: versionLabel);
- r.Width(UValue.FixedSize(18f)); // assume Version label is 18pt high
- r.Height(UValue.FixedSize(18f));
+ stackRef: this.VersionLabel);
});
control.uCanActivate = c => true;
@@ -331,14 +325,12 @@ private UILabel SetupControls_TopRow(UiBuilder builder,
c => GlobalConfig.Instance.Main.KeybindsPanelVisible;
control.uOnClick += (component, eventParam) => {
- ModUI.Instance.MainMenu.OnToggleOsdButtonClicked(component as U.Button.UButton);
+ ModUI.Instance.MainMenu.OnToggleOsdButtonClicked(component as U.UButton);
};
}
-
- return versionLabel;
}
- private void OnToggleOsdButtonClicked(U.Button.UButton button) {
+ private void OnToggleOsdButtonClicked(U.UButton button) {
bool value = !GlobalConfig.Instance.Main.KeybindsPanelVisible;
GlobalConfig.Instance.Main.KeybindsPanelVisible = value;
GlobalConfig.WriteConfig();
@@ -374,7 +366,7 @@ private void SetupControls_DebugLabels(UiBuilder builder,
if (TrafficManagerMod.Instance.InGameHotReload) {
// Hot Reload version label (debug only)
string text = $"HOT RELOAD {Assembly.GetExecutingAssembly().GetName().Version}";
- using (var hotReloadB = builder.Label(text)) {
+ using (var hotReloadB = builder.Label(text)) {
// Allow the label to hang outside the parent box
UResizerConfig.From(hotReloadB.Control).ContributeToBoundingBox = false;
@@ -392,9 +384,9 @@ private void SetupControls_DebugLabels(UiBuilder builder,
}
private AddButtonsResult SetupControls_ToolPanel(UiBuilder innerPanelB,
- HashSet atlasKeysSet) {
+ U.AtlasBuilder futureAtlas) {
// This is tool buttons panel
- using (UiBuilder leftPanelB = innerPanelB.ChildPanel(
+ using (UiBuilder leftPanelB = innerPanelB.ChildPanel(
setupFn: panel => {
panel.name = "TMPE_MainMenu_ToolPanel";
}))
@@ -407,7 +399,7 @@ private AddButtonsResult SetupControls_ToolPanel(UiBuilder innerPanelB,
// Create 1 or 2 rows of button objects
var toolButtonsResult = AddButtonsFromButtonDefinitions(
builder: leftPanelB,
- atlasKeysSet: atlasKeysSet,
+ futureAtlas: futureAtlas,
buttonDefs: TOOL_BUTTON_DEFS,
minRowLength: 4);
ToolButtonsList = toolButtonsResult.Buttons;
@@ -417,10 +409,10 @@ private AddButtonsResult SetupControls_ToolPanel(UiBuilder innerPanelB,
}
private void SetupControls_ExtraPanel(UiBuilder innerPanelB,
- HashSet atlasKeysSet,
+ U.AtlasBuilder futureAtlas,
AddButtonsResult toolButtonsResult) {
// This is toggle despawn and clear traffic panel
- using (UiBuilder rightPanelB = innerPanelB.ChildPanel(
+ using (UiBuilder rightPanelB = innerPanelB.ChildPanel(
setupFn: p => {
p.name = "TMPE_MainMenu_ExtraPanel";
// Silver background panel
@@ -441,7 +433,7 @@ private void SetupControls_ExtraPanel(UiBuilder innerPanelB,
// Use as many rows as in the other panel.
var extraButtonsResult = AddButtonsFromButtonDefinitions(
builder: rightPanelB,
- atlasKeysSet: atlasKeysSet,
+ futureAtlas: futureAtlas,
buttonDefs: EXTRA_BUTTON_DEFS,
minRowLength: toolButtonsResult.Layout.Rows == 2 ? 1 : 2);
ExtraButtonsList = extraButtonsResult.Buttons;
@@ -449,17 +441,17 @@ private void SetupControls_ExtraPanel(UiBuilder innerPanelB,
}
public override void OnBeforeResizerUpdate() {
- if (this.DragHandle != null) {
- // Drag handle is manually resized to the form width, but when the form is large,
+ if (this.dragHandle_ != null) {
+ // Drag handle is manually resized to the label width, but when the form is large,
// the handle prevents it from shrinking. So shrink now, size properly after.
- this.DragHandle.size = Vector2.one;
+ this.dragHandle_.size = Vector2.one;
}
}
/// Called by UResizer for every control to be 'resized'.
public override void OnAfterResizerUpdate() {
- if (this.DragHandle != null) {
- this.DragHandle.size = this.VersionLabel.size;
+ if (this.dragHandle_ != null) {
+ this.dragHandle_.size = this.VersionLabel.size;
// Push the window back into screen if the label/draghandle are partially offscreen
U.UIUtil.ClampToScreen(window: this,
@@ -474,12 +466,12 @@ private struct AddButtonsResult {
/// Create buttons and add them to the given panel UIBuilder.
/// UI builder to use.
- /// Atlas keys to update for button images.
+ /// Contains sprite names, filenames, sizes, paths, etc.
/// Button defs collection to create from it.
/// Longest the row can be without breaking.
/// A list of created buttons.
private AddButtonsResult AddButtonsFromButtonDefinitions(UiBuilder builder,
- HashSet atlasKeysSet,
+ U.AtlasBuilder futureAtlas,
MenuButtonDef[] buttonDefs,
int minRowLength)
{
@@ -501,15 +493,11 @@ private AddButtonsResult AddButtonsFromButtonDefinitions(UiBuilder build
var buttonBuilder = builder.Button(buttonDef.ButtonType);
// Count buttons in a row and break the line
- bool doRowBreak = result.Layout.IsRowBreak(placedInARow, minRowLength);
-
- buttonBuilder.ResizeFunction(r => {
- r.Stack(doRowBreak ? UStackMode.NewRowBelow : UStackMode.ToTheRight);
- r.Width(UValue.FixedSize(40f));
- r.Height(UValue.FixedSize(40f));
- });
+ bool isRowBreak = result.Layout.IsRowBreak(placedInARow, minRowLength);
+ buttonBuilder.SetStacking(isRowBreak ? UStackMode.NewRowBelow : UStackMode.ToTheRight);
+ buttonBuilder.SetFixedSize(new Vector2(40f, 40f));
- if (doRowBreak) {
+ if (isRowBreak) {
placedInARow = 0;
result.Layout.Rows++;
} else {
@@ -517,7 +505,7 @@ private AddButtonsResult AddButtonsFromButtonDefinitions(UiBuilder build
}
// Also ask each button what sprites they need
- buttonBuilder.Control.SetupButtonSkin(atlasKeysSet);
+ buttonBuilder.Control.SetupButtonSkin(futureAtlas);
string buttonName = buttonDef.ButtonType.ToString().Split('.').Last();
buttonBuilder.Control.name = $"TMPE_MainMenuButton_{buttonName}";
@@ -545,7 +533,7 @@ public override void OnDestroy() {
}
internal void SetPosLock(bool lck) {
- DragHandle.enabled = !lck;
+ dragHandle_.enabled = !lck;
}
protected override void OnPositionChanged() {
diff --git a/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs b/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs
index b7bbb6b8a..472eaed12 100644
--- a/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs
+++ b/TLM/TLM/UI/MainMenu/ManualTrafficLightsButton.cs
@@ -2,24 +2,27 @@
using System.Collections.Generic;
using TrafficManager.RedirectionFramework;
using TrafficManager.State;
- using TrafficManager.U.Button;
+ using TrafficManager.U;
+ using TrafficManager.Util;
public class ManualTrafficLightsButton : BaseMenuToolModeButton {
protected override ToolMode ToolMode => ToolMode.ManualSwitch;
- public override void SetupButtonSkin(HashSet atlasKeys) {
+ public override void SetupButtonSkin(AtlasBuilder futureAtlas) {
// Button backround (from BackgroundPrefix) is provided by MainMenuPanel.Start
- this.Skin = new U.Button.ButtonSkin() {
- Prefix = "ManualTL",
- BackgroundPrefix = "RoundButton",
- BackgroundHovered = true,
- BackgroundActive = true,
- ForegroundActive = true,
- };
- atlasKeys.AddRange(this.Skin.CreateAtlasKeyset());
+ this.Skin = new U.ButtonSkin() {
+ Prefix = "ManualTL",
+ BackgroundPrefix = "RoundButton",
+ BackgroundHovered = true,
+ BackgroundActive = true,
+ ForegroundActive = true,
+ };
+ this.Skin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
}
- protected override string GetTooltip() =>
+ protected override string U_OverrideTooltipText() =>
Translation.Menu.Get("Tooltip:Manual traffic lights");
protected override bool IsVisible() => IsButtonEnabled();
diff --git a/TLM/TLM/UI/MainMenu/OSD/HardcodedMouseShortcut.cs b/TLM/TLM/UI/MainMenu/OSD/HardcodedMouseShortcut.cs
index 64ffea8fe..5d99a1f23 100644
--- a/TLM/TLM/UI/MainMenu/OSD/HardcodedMouseShortcut.cs
+++ b/TLM/TLM/UI/MainMenu/OSD/HardcodedMouseShortcut.cs
@@ -7,7 +7,6 @@ namespace TrafficManager.UI.MainMenu.OSD {
using TrafficManager.State.Keybinds;
using TrafficManager.U;
using TrafficManager.U.Autosize;
- using TrafficManager.U.Label;
///
/// Displays a mouse click shortcut in OSD panel.
@@ -23,10 +22,10 @@ public class HardcodedMouseShortcut : OsdItem {
private InputKey inputKey_;
public HardcodedMouseShortcut(UIMouseButton button,
- bool shift,
- bool ctrl,
- bool alt,
- string localizedText) {
+ string localizedText,
+ bool shift = false,
+ bool ctrl = false,
+ bool alt = false) {
button_ = button;
shift_ = shift;
ctrl_ = ctrl;
@@ -34,7 +33,7 @@ public HardcodedMouseShortcut(UIMouseButton button,
localizedText_ = localizedText;
}
- public override void Build(U.UiBuilder builder) {
+ public override void Build(U.UiBuilder builder) {
// Capacity 9 will fit all modifiers and separators and the text
StringBuilder text = new StringBuilder(capacity: 9);
@@ -44,25 +43,22 @@ public override void Build(U.UiBuilder builder) {
text.Append(Translation.Options.Get("Shortcut.Modifier:Shift"));
text.Append("+");
}
+
if (this.ctrl_) {
text.Append(Translation.Options.Get("Shortcut.Modifier:Ctrl"));
text.Append("+");
}
+
if (this.alt_) {
text.Append(Translation.Options.Get("Shortcut.Modifier:Alt"));
text.Append("+");
}
text.Append(TranslationForMouseButton(this.button_));
-
text.Append(" ");
text.Append(this.localizedText_);
- using (UiBuilder labelB = builder.Label(text.ToString())) {
- labelB.Control.processMarkup = true;
- labelB.ResizeFunction(
- r => { r.Stack(mode: UStackMode.NewRowBelow); });
- }
+ builder.Label(t: text.ToString(), stack: UStackMode.NewRowBelow, processMarkup: true);
}
private static string TranslationForMouseButton(UIMouseButton button) {
diff --git a/TLM/TLM/UI/MainMenu/OSD/HoldModifier.cs b/TLM/TLM/UI/MainMenu/OSD/HoldModifier.cs
new file mode 100644
index 000000000..165a6b0ef
--- /dev/null
+++ b/TLM/TLM/UI/MainMenu/OSD/HoldModifier.cs
@@ -0,0 +1,57 @@
+namespace TrafficManager.UI.MainMenu.OSD {
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+ using ColossalFramework;
+ using ColossalFramework.UI;
+ using HarmonyLib;
+ using TrafficManager.U;
+ using TrafficManager.U.Autosize;
+
+ ///
+ /// Displays Modifier key combination in OSD panel.
+ /// This is used to tell the user to hold some Alt, Ctrl, Shift key or combination of thereof.
+ ///
+ public class HoldModifier : OsdItem {
+ private readonly bool shift_;
+ private readonly bool ctrl_;
+ private readonly bool alt_;
+ private readonly string localizedText_;
+
+ public HoldModifier(string localizedText,
+ bool shift = false,
+ bool ctrl = false,
+ bool alt = false) {
+ shift_ = shift;
+ ctrl_ = ctrl;
+ alt_ = alt;
+ localizedText_ = localizedText;
+ }
+
+ public override void Build(U.UiBuilder builder) {
+ // Capacity 4 will fit color tags and modifier string and localised text
+ var text = new StringBuilder(capacity: 4);
+ var modifierStrings = new List(capacity: 3);
+
+ text.Append($"");
+
+ if (this.shift_) {
+ modifierStrings.Add(Translation.Options.Get("Shortcut.Modifier:Shift"));
+ }
+
+ if (this.ctrl_) {
+ modifierStrings.Add(Translation.Options.Get("Shortcut.Modifier:Ctrl"));
+ }
+
+ if (this.alt_) {
+ modifierStrings.Add(Translation.Options.Get("Shortcut.Modifier:Alt"));
+ }
+
+ text.Append(string.Join("+", modifierStrings.ToArray()));
+ text.Append(" ");
+ text.Append(this.localizedText_);
+
+ builder.Label(t: text.ToString(), stack: UStackMode.NewRowBelow, processMarkup: true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/TLM/TLM/UI/MainMenu/OSD/ModeDescription.cs b/TLM/TLM/UI/MainMenu/OSD/ModeDescription.cs
index babe84862..ec388416c 100644
--- a/TLM/TLM/UI/MainMenu/OSD/ModeDescription.cs
+++ b/TLM/TLM/UI/MainMenu/OSD/ModeDescription.cs
@@ -1,7 +1,6 @@
namespace TrafficManager.UI.MainMenu.OSD {
using TrafficManager.U;
using TrafficManager.U.Autosize;
- using TrafficManager.U.Label;
///
/// Displays a single text row with different background.
@@ -13,12 +12,11 @@ public ModeDescription(string localizedText) {
localizedText_ = localizedText;
}
- public override void Build(U.UiBuilder builder) {
- using (UiBuilder labelB = builder.Label(string.Empty)) {
- labelB.ResizeFunction(r => { r.Stack(mode: UStackMode.NewRowBelow); });
- labelB.Control.text = this.localizedText_;
- labelB.Control.opacity = 0.8f;
- }
+ public override void Build(U.UiBuilder builder) {
+ ULabel control = builder.Label(
+ t: this.localizedText_,
+ stack: UStackMode.NewRowBelow);
+ control.opacity = 0.8f;
}
}
}
\ No newline at end of file
diff --git a/TLM/TLM/UI/MainMenu/OSD/OnscreenDisplay.cs b/TLM/TLM/UI/MainMenu/OSD/OnscreenDisplay.cs
index 472351502..662bf91de 100644
--- a/TLM/TLM/UI/MainMenu/OSD/OnscreenDisplay.cs
+++ b/TLM/TLM/UI/MainMenu/OSD/OnscreenDisplay.cs
@@ -50,7 +50,7 @@ public static void Display(List items) {
// mainMenu.KeybindsPanel.transform.DetachChildren();
// Populate the panel with the items
- using (var builder = new UiBuilder(mainMenu.OnscreenDisplayPanel)) {
+ using (var builder = new UiBuilder(mainMenu.OnscreenDisplayPanel)) {
foreach (var item in items) {
item.Build(builder);
}
diff --git a/TLM/TLM/UI/MainMenu/OSD/OsdItem.cs b/TLM/TLM/UI/MainMenu/OSD/OsdItem.cs
index 465544c6e..63f4a7c24 100644
--- a/TLM/TLM/UI/MainMenu/OSD/OsdItem.cs
+++ b/TLM/TLM/UI/MainMenu/OSD/OsdItem.cs
@@ -2,6 +2,6 @@ namespace TrafficManager.UI.MainMenu.OSD {
public abstract class OsdItem {
/// Called by the OnscreenDisplay to add the contents of this item.
/// UI builder used to populate the panel.
- public abstract void Build(U.UiBuilder builder);
+ public abstract void Build(U.UiBuilder builder);
}
}
\ No newline at end of file
diff --git a/TLM/TLM/UI/MainMenu/OSD/Shortcut.cs b/TLM/TLM/UI/MainMenu/OSD/Shortcut.cs
index ef9b61d57..6e9482ca9 100644
--- a/TLM/TLM/UI/MainMenu/OSD/Shortcut.cs
+++ b/TLM/TLM/UI/MainMenu/OSD/Shortcut.cs
@@ -4,7 +4,6 @@ namespace TrafficManager.UI.MainMenu.OSD {
using TrafficManager.State.Keybinds;
using TrafficManager.U;
using TrafficManager.U.Autosize;
- using TrafficManager.U.Label;
///
/// Displays a keybind or dual keybind in the OSD panel.
@@ -20,12 +19,12 @@ public Shortcut(KeybindSetting keybindSetting, string localizedText) {
localizedText_ = localizedText;
}
- public override void Build(U.UiBuilder builder) {
+ public override void Build(U.UiBuilder builder) {
StringBuilder text = new StringBuilder();
List keybindStrings = this.keybindSetting_.ToLocalizedStringList();
bool firstShortcut = true; // tracking | separators between multiple keybinds
- using (UiBuilder labelB = builder.Label(string.Empty)) {
+ using (UiBuilder labelB = builder.Label(string.Empty)) {
labelB.Control.processMarkup = true;
labelB.ResizeFunction(
r => {
diff --git a/TLM/TLM/UI/MainMenu/ParkingRestrictionsButton.cs b/TLM/TLM/UI/MainMenu/ParkingRestrictionsButton.cs
index 0143562e2..f5487b23e 100644
--- a/TLM/TLM/UI/MainMenu/ParkingRestrictionsButton.cs
+++ b/TLM/TLM/UI/MainMenu/ParkingRestrictionsButton.cs
@@ -2,24 +2,27 @@ namespace TrafficManager.UI.MainMenu {
using System.Collections.Generic;
using TrafficManager.RedirectionFramework;
using TrafficManager.State;
- using TrafficManager.U.Button;
+ using TrafficManager.U;
+ using TrafficManager.Util;
public class ParkingRestrictionsButton : BaseMenuToolModeButton {
protected override ToolMode ToolMode => ToolMode.ParkingRestrictions;
- public override void SetupButtonSkin(HashSet atlasKeys) {
+ public override void SetupButtonSkin(AtlasBuilder futureAtlas) {
// Button backround (from BackgroundPrefix) is provided by MainMenuPanel.Start
- this.Skin = new U.Button.ButtonSkin {
- Prefix = "ParkingRestrictions",
- BackgroundPrefix = "RoundButton",
- BackgroundHovered = true,
- BackgroundActive = true,
- ForegroundActive = true,
- };
- atlasKeys.AddRange(this.Skin.CreateAtlasKeyset());
+ this.Skin = new U.ButtonSkin {
+ Prefix = "ParkingRestrictions",
+ BackgroundPrefix = "RoundButton",
+ BackgroundHovered = true,
+ BackgroundActive = true,
+ ForegroundActive = true,
+ };
+ this.Skin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
}
- protected override string GetTooltip() => Translation.Menu.Get("Tooltip:Parking restrictions");
+ protected override string U_OverrideTooltipText() => Translation.Menu.Get("Tooltip:Parking restrictions");
protected override bool IsVisible() => IsButtonEnabled();
diff --git a/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs b/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs
index 10578f538..cb80bcb7c 100644
--- a/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs
+++ b/TLM/TLM/UI/MainMenu/PrioritySignsButton.cs
@@ -3,26 +3,29 @@ namespace TrafficManager.UI.MainMenu {
using TrafficManager.RedirectionFramework;
using TrafficManager.State;
using TrafficManager.State.Keybinds;
- using TrafficManager.U.Button;
+ using TrafficManager.U;
+ using TrafficManager.Util;
public class PrioritySignsButton : BaseMenuToolModeButton {
protected override ToolMode ToolMode => ToolMode.AddPrioritySigns;
- public override void SetupButtonSkin(HashSet atlasKeys) {
+ public override void SetupButtonSkin(AtlasBuilder futureAtlas) {
// Button backround (from BackgroundPrefix) is provided by MainMenuPanel.Start
- this.Skin = new U.Button.ButtonSkin() {
- Prefix = "PrioritySigns",
- BackgroundPrefix = "RoundButton",
- BackgroundHovered = true,
- BackgroundActive = true,
- ForegroundActive = true,
- };
- atlasKeys.AddRange(this.Skin.CreateAtlasKeyset());
+ this.Skin = new U.ButtonSkin() {
+ Prefix = "PrioritySigns",
+ BackgroundPrefix = "RoundButton",
+ BackgroundHovered = true,
+ BackgroundActive = true,
+ ForegroundActive = true,
+ };
+ this.Skin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
}
- protected override string GetTooltip() => Translation.Menu.Get("Tooltip:Add priority signs");
+ protected override string U_OverrideTooltipText() => Translation.Menu.Get("Tooltip:Add priority signs");
- public override KeybindSetting GetShortcutKey() => KeybindSettingsBase.PrioritySignsTool;
+ public override KeybindSetting U_OverrideTooltipShortcutKey() => KeybindSettingsBase.PrioritySignsTool;
protected override bool IsVisible() => IsButtonEnabled();
diff --git a/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs b/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs
index b647a6672..3cee9bb47 100644
--- a/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs
+++ b/TLM/TLM/UI/MainMenu/SpeedLimitsButton.cs
@@ -3,25 +3,29 @@
using TrafficManager.RedirectionFramework;
using TrafficManager.State;
using TrafficManager.State.Keybinds;
+ using TrafficManager.U;
+ using TrafficManager.Util;
public class SpeedLimitsButton : BaseMenuToolModeButton {
protected override ToolMode ToolMode => ToolMode.SpeedLimits;
- public override void SetupButtonSkin(HashSet atlasKeys) {
+ public override void SetupButtonSkin(AtlasBuilder futureAtlas) {
// Button backround (from BackgroundPrefix) is provided by MainMenuPanel.Start
- this.Skin = new U.Button.ButtonSkin() {
- Prefix = "SpeedLimits",
- BackgroundPrefix = "RoundButton",
- BackgroundHovered = true,
- BackgroundActive = true,
- ForegroundActive = true,
- };
- atlasKeys.AddRange(this.Skin.CreateAtlasKeyset());
+ this.Skin = new U.ButtonSkin() {
+ Prefix = "SpeedLimits",
+ BackgroundPrefix = "RoundButton",
+ BackgroundHovered = true,
+ BackgroundActive = true,
+ ForegroundActive = true,
+ };
+ this.Skin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
}
- protected override string GetTooltip() => Translation.Menu.Get("Tooltip:Speed limits");
+ protected override string U_OverrideTooltipText() => Translation.Menu.Get("Tooltip:Speed limits");
- public override KeybindSetting GetShortcutKey() => KeybindSettingsBase.SpeedLimitsTool;
+ public override KeybindSetting U_OverrideTooltipShortcutKey() => KeybindSettingsBase.SpeedLimitsTool;
protected override bool IsVisible() => IsButtonEnabled();
diff --git a/TLM/TLM/UI/MainMenu/StatsLabel.cs b/TLM/TLM/UI/MainMenu/StatsLabel.cs
index 032b2b25c..ee0af3d6f 100644
--- a/TLM/TLM/UI/MainMenu/StatsLabel.cs
+++ b/TLM/TLM/UI/MainMenu/StatsLabel.cs
@@ -2,7 +2,7 @@ namespace TrafficManager.UI.MainMenu {
using TrafficManager.Custom.PathFinding;
using UnityEngine;
- public class StatsLabel : U.Label.ULabel {
+ public class StatsLabel : U.ULabel {
private uint _previousValue = 0;
public override void Start() {
diff --git a/TLM/TLM/UI/MainMenu/TimedTrafficLightsButton.cs b/TLM/TLM/UI/MainMenu/TimedTrafficLightsButton.cs
index 17cf348e4..d028ae1b9 100644
--- a/TLM/TLM/UI/MainMenu/TimedTrafficLightsButton.cs
+++ b/TLM/TLM/UI/MainMenu/TimedTrafficLightsButton.cs
@@ -2,24 +2,27 @@ namespace TrafficManager.UI.MainMenu {
using System.Collections.Generic;
using TrafficManager.RedirectionFramework;
using TrafficManager.State;
- using TrafficManager.U.Button;
+ using TrafficManager.U;
+ using TrafficManager.Util;
public class TimedTrafficLightsButton : BaseMenuToolModeButton {
protected override ToolMode ToolMode => ToolMode.TimedTrafficLights;
- public override void SetupButtonSkin(HashSet atlasKeys) {
+ public override void SetupButtonSkin(AtlasBuilder futureAtlas) {
// Button backround (from BackgroundPrefix) is provided by MainMenuPanel.Start
- this.Skin = new U.Button.ButtonSkin() {
- Prefix = "TimedTL",
- BackgroundPrefix = "RoundButton",
- BackgroundHovered = true,
- BackgroundActive = true,
- ForegroundActive = true,
- };
- atlasKeys.AddRange(this.Skin.CreateAtlasKeyset());
+ this.Skin = new U.ButtonSkin() {
+ Prefix = "TimedTL",
+ BackgroundPrefix = "RoundButton",
+ BackgroundHovered = true,
+ BackgroundActive = true,
+ ForegroundActive = true,
+ };
+ this.Skin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
}
- protected override string GetTooltip() => Translation.Menu.Get("Tooltip:Timed traffic lights");
+ protected override string U_OverrideTooltipText() => Translation.Menu.Get("Tooltip:Timed traffic lights");
protected override bool IsVisible() => IsButtonEnabled();
diff --git a/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs b/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs
index a504e354b..101af9f5e 100644
--- a/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs
+++ b/TLM/TLM/UI/MainMenu/ToggleTrafficLightsButton.cs
@@ -2,27 +2,30 @@
using System.Collections.Generic;
using TrafficManager.RedirectionFramework;
using TrafficManager.State.Keybinds;
- using TrafficManager.U.Button;
+ using TrafficManager.U;
+ using TrafficManager.Util;
public class ToggleTrafficLightsButton : BaseMenuToolModeButton {
protected override ToolMode ToolMode => ToolMode.ToggleTrafficLight;
- public override void SetupButtonSkin(HashSet atlasKeys) {
+ public override void SetupButtonSkin(AtlasBuilder futureAtlas) {
// Button backround (from BackgroundPrefix) is provided by MainMenuPanel.Start
- this.Skin = new U.Button.ButtonSkin() {
- Prefix = "ToggleTL",
- BackgroundPrefix = "RoundButton",
- BackgroundHovered = true,
- BackgroundActive = true,
- ForegroundActive = true,
- };
- atlasKeys.AddRange(this.Skin.CreateAtlasKeyset());
+ this.Skin = new U.ButtonSkin() {
+ Prefix = "ToggleTL",
+ BackgroundPrefix = "RoundButton",
+ BackgroundHovered = true,
+ BackgroundActive = true,
+ ForegroundActive = true,
+ };
+ this.Skin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
}
- protected override string GetTooltip() => Translation.Menu.Get("Tooltip:Switch traffic lights");
+ protected override string U_OverrideTooltipText() => Translation.Menu.Get("Tooltip:Switch traffic lights");
protected override bool IsVisible() => true;
- public override KeybindSetting GetShortcutKey() => KeybindSettingsBase.ToggleTrafficLightTool;
+ public override KeybindSetting U_OverrideTooltipShortcutKey() => KeybindSettingsBase.ToggleTrafficLightTool;
}
}
diff --git a/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs b/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs
index e9b3518fc..5a1471e62 100644
--- a/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs
+++ b/TLM/TLM/UI/MainMenu/VehicleRestrictionsButton.cs
@@ -2,24 +2,27 @@
using System.Collections.Generic;
using TrafficManager.RedirectionFramework;
using TrafficManager.State;
- using TrafficManager.U.Button;
+ using TrafficManager.U;
+ using TrafficManager.Util;
public class VehicleRestrictionsButton : BaseMenuToolModeButton {
protected override ToolMode ToolMode => ToolMode.VehicleRestrictions;
- public override void SetupButtonSkin(HashSet atlasKeys) {
+ public override void SetupButtonSkin(AtlasBuilder futureAtlas) {
// Button backround (from BackgroundPrefix) is provided by MainMenuPanel.Start
- this.Skin = new U.Button.ButtonSkin() {
- Prefix = "VehicleRestrictions",
- BackgroundPrefix = "RoundButton",
- BackgroundHovered = true,
- BackgroundActive = true,
- ForegroundActive = true,
- };
- atlasKeys.AddRange(this.Skin.CreateAtlasKeyset());
+ this.Skin = new U.ButtonSkin() {
+ Prefix = "VehicleRestrictions",
+ BackgroundPrefix = "RoundButton",
+ BackgroundHovered = true,
+ BackgroundActive = true,
+ ForegroundActive = true,
+ };
+ this.Skin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
}
- protected override string GetTooltip() => Translation.Menu.Get("Tooltip:Vehicle restrictions");
+ protected override string U_OverrideTooltipText() => Translation.Menu.Get("Tooltip:Vehicle restrictions");
protected override bool IsVisible() => IsButtonEnabled();
diff --git a/TLM/TLM/UI/RemoveCitizenInstanceButtonExtender.cs b/TLM/TLM/UI/RemoveCitizenInstanceButtonExtender.cs
index 6f7f87640..119c8a0cb 100644
--- a/TLM/TLM/UI/RemoveCitizenInstanceButtonExtender.cs
+++ b/TLM/TLM/UI/RemoveCitizenInstanceButtonExtender.cs
@@ -3,9 +3,7 @@ namespace TrafficManager.UI {
using CSUtil.Commons;
using System.Collections.Generic;
using TrafficManager.U;
- using TrafficManager.U.Button;
- using TrafficManager.UI.MainMenu;
- using TrafficManager.UI.Textures;
+ using TrafficManager.Util;
using UnityEngine;
public class RemoveCitizenInstanceButtonExtender : MonoBehaviour {
@@ -61,12 +59,17 @@ public override void Start() {
ForegroundHovered = true,
ForegroundActive = true,
};
- this.atlas = this.Skin.CreateAtlas(
- "Clear",
- 50,
- 50,
- 256,
- this.Skin.CreateAtlasKeyset());
+
+ // This creates an atlas for a single button
+ var futureAtlas = new U.AtlasBuilder();
+ this.Skin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
+ this.atlas = futureAtlas.CreateAtlas(
+ atlasName: "RemoveCitizenButton_Atlas",
+ loadingPath: "Clear",
+ atlasSizeHint: new IntVector2(256));
+
UpdateButtonImageAndTooltip();
width = height = 30;
}
@@ -105,7 +108,7 @@ public override void HandleClick(UIMouseEventParameter p) {
protected override bool IsActive() => false;
- protected override string GetTooltip() =>
+ protected override string U_OverrideTooltipText() =>
Translation.Menu.Get("Button:Remove this citizen");
protected override bool IsVisible() => true;
diff --git a/TLM/TLM/UI/RemoveVehicleButtonExtender.cs b/TLM/TLM/UI/RemoveVehicleButtonExtender.cs
index ae96b4180..268e93921 100644
--- a/TLM/TLM/UI/RemoveVehicleButtonExtender.cs
+++ b/TLM/TLM/UI/RemoveVehicleButtonExtender.cs
@@ -3,9 +3,7 @@ namespace TrafficManager.UI {
using CSUtil.Commons;
using System.Collections.Generic;
using TrafficManager.U;
- using TrafficManager.U.Button;
- using TrafficManager.UI.MainMenu;
- using TrafficManager.UI.Textures;
+ using TrafficManager.Util;
using UnityEngine;
public class RemoveVehicleButtonExtender : MonoBehaviour {
@@ -70,13 +68,17 @@ public override void Start() {
ForegroundHovered = true,
ForegroundActive = true,
};
- // TODO: This atlas is created multiple times, cache or find by name.
- this.atlas = this.Skin.CreateAtlas(
+
+ // This creates an atlas for a single button
+ var futureAtlas = new U.AtlasBuilder();
+ this.Skin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
+ this.atlas = futureAtlas.CreateAtlas(
+ atlasName: "RemoveVehButton_Atlas",
loadingPath: "Clear",
- spriteWidth: 50,
- spriteHeight: 50,
- hintAtlasTextureSize: 256,
- atlasKeyset: this.Skin.CreateAtlasKeyset());
+ atlasSizeHint: new IntVector2(256));
+
UpdateButtonImageAndTooltip();
width = height = 30f;
}
@@ -98,7 +100,7 @@ public override void HandleClick(UIMouseEventParameter p) {
// public override Texture2D AtlasTexture => Textures.MainMenu.RemoveButton;
- protected override string GetTooltip() =>
+ protected override string U_OverrideTooltipText() =>
Translation.Menu.Get("Button:Remove this vehicle");
protected override bool IsVisible() => true;
diff --git a/TLM/TLM/UI/RoadSelectionPanels.cs b/TLM/TLM/UI/RoadSelectionPanels.cs
index 251d740fd..240998085 100644
--- a/TLM/TLM/UI/RoadSelectionPanels.cs
+++ b/TLM/TLM/UI/RoadSelectionPanels.cs
@@ -5,7 +5,7 @@ namespace TrafficManager.UI {
using System.Collections.Generic;
using System.Linq;
using TrafficManager.RedirectionFramework;
- using TrafficManager.U.Button;
+ using TrafficManager.U;
using TrafficManager.UI.SubTools.PrioritySigns;
using TrafficManager.Util;
using TrafficManager.Util.Record;
@@ -304,7 +304,7 @@ public override void Start() {
private void SetupAtlas() {
// Create and populate list of background atlas keys, used by all buttons
// And also each button will have a chance to add their own atlas keys for loading.
- var tmpSkin = new ButtonSkin() {
+ var tmpSkin = new U.ButtonSkin() {
Prefix = "RoadSelection",
BackgroundPrefix = "RoundButton",
ForegroundNormal = false,
@@ -314,19 +314,22 @@ private void SetupAtlas() {
};
// By default the atlas will include backgrounds: DefaultRound-bg-normal
- HashSet atlasKeysSet = tmpSkin.CreateAtlasKeyset();
+ var futureAtlas = new AtlasBuilder();
+ tmpSkin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
foreach (var button in buttons_ ?? Enumerable.Empty()) {
- atlasKeysSet.AddRange(button.GetAtlasKeys);
+ button.Skin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
}
// Create atlas and give it to all buttons
- allButtonsAtlas_ = tmpSkin.CreateAtlas(
- "RoadSelectionPanel",
- 50,
- 50,
- 512,
- atlasKeysSet);
+ allButtonsAtlas_ = futureAtlas.CreateAtlas(
+ atlasName: "RoadSelectionPanelAtlas",
+ loadingPath: "RoadSelectionPanel",
+ atlasSizeHint: new IntVector2(512));
foreach (var button in buttons_ ?? Enumerable.Empty()) {
button.atlas = allButtonsAtlas_;
@@ -335,8 +338,9 @@ private void SetupAtlas() {
internal bool HasHoveringButton() {
foreach (var button in buttons_ ?? Enumerable.Empty()) {
- if (button.IsHovered && button.isEnabled)
+ if (button.IsHovered && button.isEnabled) {
return true;
+ }
}
return false;
}
@@ -348,30 +352,30 @@ public void Refresh() {
}
public class ClearButtton : ButtonExt {
- protected override string GetTooltip() => Translation.Menu.Get("RoadSelection.Tooltip:Clear");
public override string SkinPrefix => Function.ToString(); // remove _RHT/_LHT postFix.
+ protected override string U_OverrideTooltipText() => Translation.Menu.Get("RoadSelection.Tooltip:Clear");
internal override FunctionModes Function => FunctionModes.Clear;
public override IRecordable Do() => PriorityRoad.ClearRoad(Selection);
}
public class StopButtton : ButtonExt {
- protected override string GetTooltip() => Translation.Menu.Get("RoadSelection.Tooltip:Stop entry");
+ protected override string U_OverrideTooltipText() => Translation.Menu.Get("RoadSelection.Tooltip:Stop entry");
internal override FunctionModes Function => FunctionModes.Stop;
public override IRecordable Do() =>
PriorityRoad.FixPrioritySigns(PrioritySignsTool.PrioritySignsMassEditMode.MainStop, Selection);
}
public class YieldButton : ButtonExt {
- protected override string GetTooltip() => Translation.Menu.Get("RoadSelection.Tooltip:Yield entry");
+ protected override string U_OverrideTooltipText() => Translation.Menu.Get("RoadSelection.Tooltip:Yield entry");
internal override FunctionModes Function => FunctionModes.Yield;
public override IRecordable Do() =>
PriorityRoad.FixPrioritySigns(PrioritySignsTool.PrioritySignsMassEditMode.MainYield, Selection);
}
public class HighPriorityButtton : ButtonExt {
- protected override string GetTooltip() => Translation.Menu.Get("RoadSelection.Tooltip:High priority");
+ protected override string U_OverrideTooltipText() => Translation.Menu.Get("RoadSelection.Tooltip:High priority");
internal override FunctionModes Function => FunctionModes.HighPriority;
public override IRecordable Do() => PriorityRoad.FixRoad(Selection);
}
public class RoundaboutButtton : ButtonExt {
- protected override string GetTooltip() => Translation.Menu.Get("RoadSelection.Tooltip:Roundabout");
+ protected override string U_OverrideTooltipText() => Translation.Menu.Get("RoadSelection.Tooltip:Roundabout");
internal override FunctionModes Function => FunctionModes.Roundabout;
public override IRecordable Do() => RoundaboutMassEdit.Instance.FixRoundabout(Selection);
public override bool ShouldDisable() {
@@ -388,7 +392,7 @@ public override bool ShouldDisable() {
}
}
- public abstract class ButtonExt : BaseUButton {
+ public abstract class ButtonExt : U.BaseUButton {
const float REFERENCE_SIZE = 40f;
public RoadSelectionPanels Root => RoadSelectionPanels.Root;
@@ -399,8 +403,6 @@ public abstract class ButtonExt : BaseUButton {
public virtual string SkinPrefix => Function.ToString() + TrafficSidePostFix;
- public HashSet GetAtlasKeys => this.Skin.CreateAtlasKeyset();
-
public bool IsHovered => m_IsMouseHovering; // exposing the protected member
public List Selection => Root?.roadSelectionUtil_?.Selection;
@@ -425,7 +427,7 @@ public override void Start() {
public override void Awake() {
base.Awake();
name = ButtonName;
- Skin = new U.Button.ButtonSkin() {
+ Skin = new U.ButtonSkin() {
Prefix = SkinPrefix,
BackgroundPrefix = "RoundButton",
@@ -451,10 +453,10 @@ public void Refresh() {
public override void HandleClick(UIMouseEventParameter p) =>
throw new Exception("Unreachable code");
- /// Handles button click on activation. Apply traffic rules here.
+ /// Handles button click on activation. Apply traffic rules here.
public abstract IRecordable Do();
- /// Handles button click on de-activation. Reset/Undo traffic rules here.
+ /// Handles button click on de-activation. Reset/Undo traffic rules here.
public virtual void Undo() => Root.Record?.Restore();
protected override void OnClick(UIMouseEventParameter p) {
diff --git a/TLM/TLM/UI/SubTools/JunctionRestrictionsTool.cs b/TLM/TLM/UI/SubTools/JunctionRestrictionsTool.cs
index 2dc7e7f49..d622143f6 100644
--- a/TLM/TLM/UI/SubTools/JunctionRestrictionsTool.cs
+++ b/TLM/TLM/UI/SubTools/JunctionRestrictionsTool.cs
@@ -50,14 +50,14 @@ public override void RenderOverlayForOtherTools(RenderManager.CameraInfo cameraI
public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) {
if (SelectedNodeId != 0) {
// draw selected node
- MainTool.DrawNodeCircle(cameraInfo, SelectedNodeId, true);
+ Highlight.DrawNodeCircle(cameraInfo, SelectedNodeId, true);
}
if ((HoveredNodeId != 0) && (HoveredNodeId != SelectedNodeId) &&
((Singleton.instance.m_nodes.m_buffer[HoveredNodeId].m_flags &
(NetNode.Flags.Junction | NetNode.Flags.Bend)) != NetNode.Flags.None)) {
// draw hovered node
- MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, Input.GetMouseButton(0));
+ Highlight.DrawNodeCircle(cameraInfo, HoveredNodeId, Input.GetMouseButton(0));
}
}
diff --git a/TLM/TLM/UI/SubTools/LaneArrows/LaneArrowButton.cs b/TLM/TLM/UI/SubTools/LaneArrows/LaneArrowButton.cs
index 659d3aa83..03db76ccf 100644
--- a/TLM/TLM/UI/SubTools/LaneArrows/LaneArrowButton.cs
+++ b/TLM/TLM/UI/SubTools/LaneArrows/LaneArrowButton.cs
@@ -8,7 +8,7 @@ namespace TrafficManager.UI.SubTools.LaneArrows {
///
/// Button in Lane Arrows editor, containing a direction arrow.
///
- public class LaneArrowButton: U.Button.BaseUButton {
+ public class LaneArrowButton: U.BaseUButton {
public uint LaneId = 0;
public NetLane.Flags NetlaneFlagsMask = NetLane.Flags.None;
@@ -48,9 +48,5 @@ protected override bool IsActive() {
NetLane.Flags flags = (NetLane.Flags)lanesBuffer[LaneId].m_flags;
return (flags & NetlaneFlagsMask) == NetlaneFlagsMask;
}
-
- protected override string GetTooltip() {
- return string.Empty;
- }
}
}
\ No newline at end of file
diff --git a/TLM/TLM/UI/SubTools/LaneArrows/LaneArrowTool.cs b/TLM/TLM/UI/SubTools/LaneArrows/LaneArrowTool.cs
index 7033bcbf6..dd1d54e92 100644
--- a/TLM/TLM/UI/SubTools/LaneArrows/LaneArrowTool.cs
+++ b/TLM/TLM/UI/SubTools/LaneArrows/LaneArrowTool.cs
@@ -7,7 +7,6 @@ namespace TrafficManager.UI.SubTools.LaneArrows {
using TrafficManager.API.Traffic.Data;
using TrafficManager.API.Traffic.Enums;
using TrafficManager.Manager.Impl;
- using TrafficManager.State;
using TrafficManager.State.Keybinds;
using TrafficManager.U;
using TrafficManager.UI.MainMenu;
@@ -22,7 +21,7 @@ namespace TrafficManager.UI.SubTools.LaneArrows {
///
public class LaneArrowTool
: TrafficManagerSubTool,
- IOnscreenDisplayProvider
+ UI.MainMenu.IOnscreenDisplayProvider
{
const bool DEFAULT_ALT_MODE = true;
private bool alternativeMode_ = DEFAULT_ALT_MODE;
@@ -122,10 +121,10 @@ private void OnEnterSelectState() {
/// Called from GenericFsm when a segment is clicked to show lane arrows GUI.
private void OnEnterEditorState() {
int numLanes = GeometryUtil.GetSegmentNumVehicleLanes(
- SelectedSegmentId,
- SelectedNodeId,
- out int numDirections,
- LaneArrowManager.VEHICLE_TYPES);
+ segmentId: SelectedSegmentId,
+ nodeId: SelectedNodeId,
+ numDirections: out int _,
+ vehicleTypeFilter: LaneArrowManager.VEHICLE_TYPES);
if (numLanes <= 0) {
SelectedNodeId = 0;
@@ -133,23 +132,6 @@ private void OnEnterEditorState() {
return;
}
- // Vector3 nodePos = Singleton
- // .instance.m_nodes.m_buffer[SelectedNodeId].m_position;
- //
- // // Hide if node position is off-screen
- //
- // bool visible = GeometryUtil.WorldToScreenPoint(nodePos, out Vector3 screenPos);
- //
- // // if (!visible) {
- // // return;
- // // }
- //
- // Vector3 camPos = Singleton.instance.m_simulationView.m_position;
- // Vector3 diff = nodePos - camPos;
- //
- // if (diff.sqrMagnitude > TrafficManagerTool.MAX_OVERLAY_DISTANCE_SQR) {
- // return; // do not draw if too distant
- // }
// Calculate lanes and arrows
NetSegment[] segmentsBuffer = Singleton.instance.m_segments.m_buffer;
IList laneList = Constants.ServiceFactory.NetService.GetSortedLanes(
@@ -168,36 +150,17 @@ private void OnEnterEditorState() {
return;
}
- CreateLaneArrowsWindow(laneList.Count);
+ // Create a generic self-sizing window with padding of 4px.
+ ToolWindow = UiBuilder.CreateWindow(
+ setupFn: b => b.Control.SetupControls(b, numLanes));
+ RepositionWindowToNode();
+ // end create window
+
SetupLaneArrowsWindowButtons(laneList: laneList,
startNode: (bool)startNode);
MainTool.RequestOnscreenDisplayUpdate();
}
- ///
- /// Creates floating tool window for Lane Arrow controls and adds keyboard hints too.
- ///
- /// How many groups of buttons.
- private void CreateLaneArrowsWindow(int numLanes) {
- var parent = UIView.GetAView();
- ToolWindow = (LaneArrowToolWindow)parent.AddUIComponent(typeof(LaneArrowToolWindow));
- ToolWindow.SetOpacity(
- U.UOpacityValue.FromOpacity(0.01f * GlobalConfig.Instance.Main.GuiOpacity));
-
- RepositionWindowToNode(); // reposition 1st time to avoid visible window jump
-
- using (var builder = new U.UiBuilder(ToolWindow)) {
- builder.ResizeFunction(r => { r.FitToChildren(); });
- builder.SetPadding(UConst.UIPADDING);
-
- ToolWindow.SetupControls(builder, numLanes);
-
- // Resize everything correctly
- builder.Done();
- RepositionWindowToNode(); // reposition again 2nd time now that size is known
- }
- }
-
///
/// Given the tool window already created with its buttons set up,
/// go through them and assign click events, disable some, activate some etc.
@@ -308,6 +271,32 @@ internal void InformUserAboutPossibleFailure(SetLaneArrow_Result result) {
}
}
+ public override void RenderActiveToolOverlay(RenderManager.CameraInfo cameraInfo) {
+ // We will be calling GUI.DrawTexture in the overlay renderer, so we need *_GUI variant
+ switch (fsm_.State) {
+ case State.Select:
+ RenderOverlay_Select(cameraInfo);
+ break;
+ case State.EditLaneArrows:
+ RenderOverlay_Select(cameraInfo); // show potential half-segments to select
+ RenderOverlay_EditLaneArrows(cameraInfo);
+ break;
+ }
+ }
+
+ public override void RenderActiveToolOverlay_GUI() {
+ // No GUI-specific info overlay for lane arrows
+ }
+
+ /// No generic overlay for other tool modes is provided by this tool, render nothing.
+ public override void RenderGenericInfoOverlay(RenderManager.CameraInfo cameraInfo) {
+ // No info overlay for other tools
+ }
+
+ public override void RenderGenericInfoOverlay_GUI() {
+ // No GUI-specific info overlay to show while other tools active
+ }
+
/// Called from the Main Tool when left mouse button clicked.
public override void OnToolLeftClick() {
if (ToolWindow != null && MainTool.GetToolController().IsInsideUI) {
@@ -423,24 +412,18 @@ void IOnscreenDisplayProvider.UpdateOnscreenDisplayPanel() {
switch (fsm_.State) {
case State.Select: {
- var items = new List();
- items.Add(
+ var items = new List {
new MainMenu.OSD.ModeDescription(
- localizedText: T("LaneArrows.Mode:Select")));
- items.Add(
+ localizedText: T("LaneArrows.Mode:Select")),
new MainMenu.OSD.HardcodedMouseShortcut(
button: UIMouseButton.Left,
- shift: false,
ctrl: true,
- alt: false,
- localizedText: T("LaneArrows.Click:Separate lanes for entire junction")));
- items.Add(
+ localizedText: T("LaneArrows.Click:Separate lanes for entire junction")),
new MainMenu.OSD.HardcodedMouseShortcut(
button: UIMouseButton.Left,
- shift: false,
- ctrl: false,
alt: true,
- localizedText: T("LaneArrows.Click:Separate lanes for segment")));
+ localizedText: T("LaneArrows.Click:Separate lanes for segment")),
+ };
OnscreenDisplay.Display(items: items);
return;
}
@@ -549,19 +532,7 @@ private void DrawSegmentEnd(
HasSegmentEndLaneArrows(segmentId, segment.m_endNode);
float cut = con ? 1f : 0.5f;
- MainTool.DrawCutSegmentEnd(cameraInfo, segmentId, cut, bStartNode, color, alpha);
- }
-
- public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) {
- switch (fsm_.State) {
- case State.Select:
- RenderOverlay_Select(cameraInfo);
- break;
- case State.EditLaneArrows:
- RenderOverlay_Select(cameraInfo); // show potential half-segments to select
- RenderOverlay_EditLaneArrows(cameraInfo);
- break;
- }
+ Highlight.DrawCutSegmentEnd(cameraInfo, segmentId, cut, bStartNode, color, alpha);
}
/// Render info overlay for active tool, when UI is in Select state.
@@ -571,7 +542,7 @@ private void RenderOverlay_Select(RenderManager.CameraInfo cameraInfo) {
// If CTRL is held, and hovered something: Draw hovered node
if (SeparateNodeLanesModifierIsPressed && HoveredNodeId != 0) {
- MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, Input.GetMouseButton(0));
+ Highlight.DrawNodeCircle(cameraInfo, HoveredNodeId, Input.GetMouseButton(0));
return;
}
diff --git a/TLM/TLM/UI/SubTools/LaneArrows/LaneArrowToolWindow.cs b/TLM/TLM/UI/SubTools/LaneArrows/LaneArrowToolWindow.cs
index 43a4d93d9..c8ecf49b9 100644
--- a/TLM/TLM/UI/SubTools/LaneArrows/LaneArrowToolWindow.cs
+++ b/TLM/TLM/UI/SubTools/LaneArrows/LaneArrowToolWindow.cs
@@ -2,10 +2,9 @@ namespace TrafficManager.UI.SubTools.LaneArrows {
using System.Collections.Generic;
using ColossalFramework.UI;
using TrafficManager.RedirectionFramework;
- using TrafficManager.State;
using TrafficManager.U;
using TrafficManager.U.Autosize;
- using TrafficManager.U.Button;
+ using TrafficManager.Util;
using UnityEngine;
///
@@ -25,60 +24,41 @@ public class LaneArrowToolWindow : U.Panel.BaseUWindowPanel {
public override void Start() {
base.Start();
UIUtil.MakeUniqueAndSetName(gameObject, GAMEOBJECT_NAME);
-
- // the GenericPanel sprite is silver, make it dark
- this.backgroundSprite = "GenericPanel";
- this.color = new Color32(64, 64, 64, 240);
- this.SetOpacity(
- U.UOpacityValue.FromOpacity(0.01f * GlobalConfig.Instance.Main.GuiOpacity));
+ this.GenericBackgroundAndOpacity();
}
- private UITextureAtlas GetAtlas() {
+ private UITextureAtlas GetUiAtlas() {
if (laneArrowButtonAtlas_ != null) {
return laneArrowButtonAtlas_;
}
- var skin = CreateDefaultButtonSkin();
-
// Create base atlas with backgrounds and no foregrounds
- skin.ForegroundNormal = false;
- skin.ForegroundActive = false;
- HashSet atlasKeysSet = skin.CreateAtlasKeyset();
+ ButtonSkin backgroundOnlySkin = ButtonSkin.CreateDefaultNoForeground("LaneArrow");
+ var futureAtlas = new U.AtlasBuilder();
+ backgroundOnlySkin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(64));
// Merge names of all foreground sprites for 3 directions into atlasKeySet
- skin.ForegroundNormal = true;
- skin.ForegroundActive = true;
foreach (string prefix in new[]
- { "LaneArrowLeft", "LaneArrowRight", "LaneArrowForward" }) {
- skin.Prefix = prefix;
+ { "LaneArrowLeft", "LaneArrowRight", "LaneArrowForward" })
+ {
+ ButtonSkin skin = ButtonSkin.CreateDefaultNoBackground(prefix);
// Create keysets for lane arrow button icons and merge to the shared atlas
- atlasKeysSet.AddRange(skin.CreateAtlasKeyset());
+ skin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(64));
}
// Load actual graphics into an atlas
- laneArrowButtonAtlas_ = skin.CreateAtlas(
+ laneArrowButtonAtlas_ = futureAtlas.CreateAtlas(
+ atlasName: "LaneArrowsTool_Atlas",
loadingPath: "LaneArrows",
- spriteWidth: 64,
- spriteHeight: 64,
- hintAtlasTextureSize: 256, // 4x4 atlas
- atlasKeysSet);
+ atlasSizeHint: new IntVector2(256));
return laneArrowButtonAtlas_;
}
- private static ButtonSkin CreateDefaultButtonSkin() {
- return new ButtonSkin {
- BackgroundPrefix = "LaneArrow", // filename prefix
-
- BackgroundHovered = true,
- BackgroundActive = true,
- BackgroundDisabled = true,
-
- ForegroundNormal = true,
- ForegroundActive = true,
- };
- }
-
///
/// Create button triples for number of lanes.
/// Buttons are linked to lanes later by LaneArrowTool class.
@@ -88,7 +68,7 @@ private static ButtonSkin CreateDefaultButtonSkin() {
public void SetupControls(UiBuilder builder, int numLanes) {
Buttons = new List();
- using (var buttonRowBuilder = builder.ChildPanel(
+ using (var buttonRowBuilder = builder.ChildPanel(
setupFn: p => { p.name = "TMPE_ButtonRow"; })) {
buttonRowBuilder.ResizeFunction(
r => {
@@ -104,7 +84,7 @@ public void SetupControls(UiBuilder builder, int numLanes)
// -----------------------------------
for (var i = 0; i < numLanes; i++) {
string buttonName = $"TMPE_LaneArrow_ButtonGroup{i + 1}";
- using (var buttonGroupBuilder = buttonRowBuilder.ChildPanel(
+ using (var buttonGroupBuilder = buttonRowBuilder.ChildPanel(
setupFn: p => {
p.name = buttonName;
p.atlas = TextureUtil.FindAtlas("Ingame");
@@ -125,42 +105,36 @@ public void SetupControls(UiBuilder builder, int numLanes)
});
// Create a label with "Lane #" title
- string labelText = Translation.LaneRouting.Get("Format.Label:Lane") + " " + (i + 1);
- using (var laneLabel = buttonGroupBuilder.Label(labelText))
- {
- // The label will be repositioned to the top of the parent
- laneLabel.ResizeFunction(r => { r.Stack(UStackMode.Below); });
- }
+ // The label will be repositioned to the top of the parent
+ string localizedLane = Translation.LaneRouting.Get("Format.Label:Lane");
+ string labelText = $"{localizedLane} {i + 1}";
+ buttonGroupBuilder.Label(t: labelText, stack: UStackMode.Below);
// Create and populate the panel with buttons
// 3 buttons are created [←] [↑] [→],
// The click event is assigned outside in LaneArrowTool.cs
foreach (string prefix in new[] {
- "LaneArrowLeft",
- "LaneArrowForward",
- "LaneArrowRight",
- }) {
- using (UiBuilder buttonBuilder =
- buttonGroupBuilder.Button())
+ "LaneArrowLeft",
+ "LaneArrowForward",
+ "LaneArrowRight",
+ }) {
+ using (var b = buttonGroupBuilder.Button())
{
- buttonBuilder.Control.atlas = GetAtlas();
- buttonBuilder.Control.Skin = CreateDefaultButtonSkin();
- buttonBuilder.Control.Skin.Prefix = prefix;
- Buttons.Add(buttonBuilder.Control);
-
- buttonBuilder.ResizeFunction(
- r => {
- // First button in the group will be stacking vertical
- // under the "Lane #" label, while 2nd and 3rd will be
- // stacking horizontal
- r.Stack(
- mode: prefix == "LaneArrowLeft"
- ? UStackMode.Below
- : UStackMode.ToTheRight,
- spacing: UConst.UIPADDING);
- r.Width(UValue.FixedSize(40f));
- r.Height(UValue.FixedSize(40f));
- });
+ b.Control.atlas = GetUiAtlas();
+ b.Control.Skin = ButtonSkin.CreateDefault(
+ prefix: prefix,
+ backgroundPrefix: "LaneArrow");
+ Buttons.Add(b.Control);
+
+ // First button in the group will be stacking vertical
+ // under the "Lane #" label, while 2nd and 3rd will be
+ // stacking horizontal
+ b.SetStacking(
+ mode: prefix == "LaneArrowLeft"
+ ? UStackMode.Below
+ : UStackMode.ToTheRight,
+ spacing: UConst.UIPADDING);
+ b.SetFixedSize(new Vector2(40f, 40f));
}
} // for each button
} // end button group panel
diff --git a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs
index 74f920836..ab303863e 100644
--- a/TLM/TLM/UI/SubTools/LaneConnectorTool.cs
+++ b/TLM/TLM/UI/SubTools/LaneConnectorTool.cs
@@ -197,7 +197,7 @@ private void ShowOverlay(bool viewOnly, RenderManager.CameraInfo cameraInfo) {
bool hasMarkers = currentLaneEnds.TryGetValue((ushort)nodeId, out List laneEnds);
if (!viewOnly && (GetSelectionMode() == SelectionMode.None)) {
- MainTool.DrawNodeCircle(
+ Highlight.DrawNodeCircle(
cameraInfo: cameraInfo,
nodeId: (ushort)nodeId,
color: DefaultLaneEndColor,
@@ -398,7 +398,7 @@ public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) {
if ((GetSelectionMode() == SelectionMode.None) && (HoveredNodeId != 0)) {
// draw hovered node
- MainTool.DrawNodeCircle(
+ Highlight.DrawNodeCircle(
cameraInfo: cameraInfo,
nodeId: HoveredNodeId,
warning: Input.GetMouseButton(0),
diff --git a/TLM/TLM/UI/SubTools/ManualTrafficLightsTool.cs b/TLM/TLM/UI/SubTools/ManualTrafficLightsTool.cs
index 3e79a6938..ad1d4428a 100644
--- a/TLM/TLM/UI/SubTools/ManualTrafficLightsTool.cs
+++ b/TLM/TLM/UI/SubTools/ManualTrafficLightsTool.cs
@@ -990,7 +990,11 @@ private void RenderManualSelectionOverlay(RenderManager.CameraInfo cameraInfo) {
return;
}
- MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, false, false);
+ Highlight.DrawNodeCircle(
+ cameraInfo: cameraInfo,
+ nodeId: HoveredNodeId,
+ warning: false,
+ alpha: false);
}
private void RenderManualNodeOverlays(RenderManager.CameraInfo cameraInfo) {
@@ -998,7 +1002,7 @@ private void RenderManualNodeOverlays(RenderManager.CameraInfo cameraInfo) {
return;
}
- MainTool.DrawNodeCircle(
+ Highlight.DrawNodeCircle(
cameraInfo: cameraInfo,
nodeId: SelectedNodeId,
warning: true,
diff --git a/TLM/TLM/UI/SubTools/ParkingRestrictionsTool.cs b/TLM/TLM/UI/SubTools/ParkingRestrictionsTool.cs
index 9fc0797c0..55b504b6b 100644
--- a/TLM/TLM/UI/SubTools/ParkingRestrictionsTool.cs
+++ b/TLM/TLM/UI/SubTools/ParkingRestrictionsTool.cs
@@ -70,7 +70,7 @@ private void RenderSegmentParkings(RenderManager.CameraInfo cameraInfo) {
bool pressed = Input.GetMouseButton(0);
Color color;
if (pressed) {
- color = MainTool.GetToolColor(true, false);
+ color = MainTool.GetToolColor(warning: true, error: false);
} else if (allowed) {
color = Color.green;
} else {
@@ -79,19 +79,23 @@ private void RenderSegmentParkings(RenderManager.CameraInfo cameraInfo) {
Bezier3 bezier = default;
netService.IterateSegmentLanes(
- renderInfo_.SegmentId,
- (uint laneId,
- ref NetLane lane,
- NetInfo.Lane laneInfo,
- ushort _,
- ref NetSegment segment,
- byte laneIndex) => {
- bool isParking = laneInfo.m_laneType.IsFlagSet(NetInfo.LaneType.Parking);
+ segmentId: renderInfo_.SegmentId,
+ handler: (uint laneId,
+ ref NetLane lane,
+ NetInfo.Lane laneInfo,
+ ushort _,
+ ref NetSegment segment,
+ byte laneIndex) => {
+ bool isParking = laneInfo.m_laneType.IsFlagSet(flag: NetInfo.LaneType.Parking);
if (isParking && laneInfo.m_finalDirection == renderInfo_.FinalDirection) {
bezier = lane.m_bezier;
- laneMarker_ = new SegmentLaneMarker(bezier);
- laneMarker_.RenderOverlay(cameraInfo, color, enlarge: pressed);
+ laneMarker_ = new SegmentLaneMarker(bezier: bezier);
+ laneMarker_.RenderOverlay(
+ cameraInfo: cameraInfo,
+ color: color,
+ enlarge: pressed);
}
+
return true;
});
}
@@ -120,7 +124,7 @@ bool LaneVisitor(SegmentLaneVisitData data) {
}
if (finalDirection == renderInfo_.FinalDirection) {
bool pressed = Input.GetMouseButton(0);
- Color color = MainTool.GetToolColor(pressed, false);
+ Color color = MainTool.GetToolColor(warning: pressed, error: false);
uint otherLaneId = data.CurLanePos.laneId;
var laneMarker = new SegmentLaneMarker(laneBuffer[otherLaneId].m_bezier);
laneMarker.RenderOverlay(cameraInfo, color, enlarge: pressed);
@@ -171,6 +175,10 @@ public override void Cleanup() {
private Vector3? lastCamPos;
private void ShowSigns(bool viewOnly) {
+ if (viewOnly && !Options.parkingRestrictionsOverlay && !MassEditOverlay.IsActive) {
+ return;
+ }
+
NetManager netManager = Singleton.instance;
var currentCamera = new CameraTransformValue(Camera.main);
@@ -196,8 +204,8 @@ private void ShowSigns(bool viewOnly) {
}
bool visible = GeometryUtil.WorldToScreenPoint(
- netManager.m_segments.m_buffer[segmentId].m_bounds.center,
- out Vector3 _);
+ worldPos: netManager.m_segments.m_buffer[segmentId].m_bounds.center,
+ screenPos: out Vector3 _);
if (!visible) {
continue;
@@ -230,17 +238,18 @@ private void ShowSigns(bool viewOnly) {
// no parking restrictions overlay on selected segment when in vehicle restrictions mode
var dir = DrawParkingRestrictionHandles(
- segmentId,
- clicked,
- ref netManager.m_segments.m_buffer[segmentId],
- viewOnly,
- ref camPos);
+ segmentId: segmentId,
+ clicked: clicked,
+ viewOnly: viewOnly,
+ camPos: ref camPos);
+
if (dir != NetInfo.Direction.None) {
renderInfo_.SegmentId = segmentId;
renderInfo_.FinalDirection = dir;
hovered = true;
}
}
+
if (!hovered) {
renderInfo_.SegmentId = 0;
renderInfo_.FinalDirection = NetInfo.Direction.None;
@@ -248,14 +257,9 @@ private void ShowSigns(bool viewOnly) {
}
private NetInfo.Direction DrawParkingRestrictionHandles(ushort segmentId,
- bool clicked,
- ref NetSegment segment,
- bool viewOnly,
- ref Vector3 camPos) {
- if (viewOnly && !Options.parkingRestrictionsOverlay && !MassEditOverlay.IsActive) {
- return NetInfo.Direction.None;
- }
-
+ bool clicked,
+ bool viewOnly,
+ ref Vector3 camPos) {
NetManager netManager = Singleton.instance;
ParkingRestrictionsManager parkingManager = ParkingRestrictionsManager.Instance;
NetInfo.Direction hoveredDirection = NetInfo.Direction.None;
@@ -285,14 +289,14 @@ private NetInfo.Direction DrawParkingRestrictionHandles(ushort segmentId,
continue;
}
- float zoom = (1.0f / (e.Value - camPos).magnitude) * 100f * MainTool.GetBaseZoom();
+ float zoom = (1.0f / (e.Value - camPos).magnitude) * 100f * U.UIScaler.GetScale();
float size = (viewOnly ? 0.8f : 1f) * SIGN_SIZE * zoom;
Color guiColor = GUI.color;
Rect boundingBox = new Rect(
- screenPos.x - (size / 2),
- screenPos.y - (size / 2),
- size,
- size);
+ x: screenPos.x - (size / 2),
+ y: screenPos.y - (size / 2),
+ width: size,
+ height: size);
if (Options.speedLimitsOverlay || MassEditOverlay.IsActive) {
boundingBox.y -= size + 10f;
@@ -354,14 +358,14 @@ bool LaneVisitor(SegmentLaneVisitData data) {
}
SegmentLaneTraverser.Traverse(
- segmentId,
- SegmentTraverser.TraverseDirection.AnyDirection,
- SegmentTraverser.TraverseSide.AnySide,
- SegmentLaneTraverser.LaneStopCriterion.LaneCount,
- SegmentTraverser.SegmentStopCriterion.Junction,
- ParkingRestrictionsManager.LANE_TYPES,
- ParkingRestrictionsManager.VEHICLE_TYPES,
- LaneVisitor);
+ initialSegmentId: segmentId,
+ direction: SegmentTraverser.TraverseDirection.AnyDirection,
+ side: SegmentTraverser.TraverseSide.AnySide,
+ laneStopCrit: SegmentLaneTraverser.LaneStopCriterion.LaneCount,
+ segStopCrit: SegmentTraverser.SegmentStopCriterion.Junction,
+ laneTypeFilter: ParkingRestrictionsManager.LANE_TYPES,
+ vehicleTypeFilter: ParkingRestrictionsManager.VEHICLE_TYPES,
+ laneVisitor: LaneVisitor);
}
}
@@ -381,8 +385,6 @@ public void UpdateOnscreenDisplayPanel() {
new HardcodedMouseShortcut(
button: UIMouseButton.Left,
shift: true,
- ctrl: false,
- alt: false,
localizedText: T("Parking.ShiftClick:Apply to entire road")));
OnscreenDisplay.Display(items);
}
diff --git a/TLM/TLM/UI/SubTools/PrioritySigns/PrioritySignsTool.cs b/TLM/TLM/UI/SubTools/PrioritySigns/PrioritySignsTool.cs
index 356aca2fb..c3cd265eb 100644
--- a/TLM/TLM/UI/SubTools/PrioritySigns/PrioritySignsTool.cs
+++ b/TLM/TLM/UI/SubTools/PrioritySigns/PrioritySignsTool.cs
@@ -73,7 +73,7 @@ public override void OnPrimaryClickOverlay() {
if (!isRoundabout) {
record_ = PriorityRoad.FixRoad(HoveredSegmentId);
}
- // TODO: benchmark why bulk setup takes a long time.
+ // TODO: benchmark why bulk setup takes a long time.
Log.Info("After FixRoundabout/FixRoad. Before RefreshMassEditOverlay"); // log time for benchmarking.
RefreshMassEditOverlay();
Log.Info("After RefreshMassEditOverlay."); // log time for benchmarking.
@@ -122,12 +122,13 @@ public override void OnPrimaryClickOverlay() {
massEditMode = PrioritySignsMassEditMode.Min;
}
}
-
+
// refresh cache
- if(ControlIsPressed)
+ if(ControlIsPressed) {
RefreshMassEditOverlay();
- else
+ } else {
RefreshCurrentPriorityNodeIds();
+ }
}
public override void OnToolGUI(Event e) { }
@@ -174,10 +175,10 @@ public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) {
foreach (uint segmentId in segmentList) {
ref NetSegment seg = ref Singleton.instance.m_segments.m_buffer[segmentId];
NetTool.RenderOverlay(
- cameraInfo,
- ref seg,
- color,
- color);
+ cameraInfo: cameraInfo,
+ segment: ref seg,
+ importantColor: color,
+ nonImportantColor: color);
} // end foreach
} else {
SegmentTraverser.Traverse(
@@ -187,27 +188,32 @@ public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) {
SegmentStopCriterion.None,
data => {
NetTool.RenderOverlay(
- cameraInfo,
- ref Singleton.instance.m_segments.m_buffer[
+ cameraInfo: cameraInfo,
+ segment: ref Singleton.instance.m_segments.m_buffer[
data.CurSeg.segmentId],
- color,
- color);
+ importantColor: color,
+ nonImportantColor: color);
return true;
});
}
- if (!ControlIsPressed)
+ if (!ControlIsPressed) {
mode = ModifyMode.PriorityRoad;
- else if (!isRoundabout)
+ } else if (!isRoundabout) {
mode = ModifyMode.HighPriorityRoad;
- else
+ } else {
mode = ModifyMode.Roundabout;
+ }
if (mode != PrevHoveredState.Mode || HoveredSegmentId != PrevHoveredState.SegmentId) {
massEditMode = PrioritySignsMassEditMode.Min;
}
} else if (ControlIsPressed) {
- MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, Input.GetMouseButton(0));
+ Highlight.DrawNodeCircle(
+ cameraInfo: cameraInfo,
+ nodeId: HoveredNodeId,
+ warning: Input.GetMouseButton(0));
mode = ModifyMode.HighPriorityJunction;
+
if (mode != PrevHoveredState.Mode || HoveredNodeId != PrevHoveredState.NodeId) {
massEditMode = PrioritySignsMassEditMode.Min;
}
@@ -229,7 +235,10 @@ ref Singleton.instance.m_segments.m_buffer[
return;
}
- MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId, Input.GetMouseButton(0));
+ Highlight.DrawNodeCircle(
+ cameraInfo: cameraInfo,
+ nodeId: HoveredNodeId,
+ warning: Input.GetMouseButton(0));
}
PrevHoveredState.Mode = mode;
@@ -306,8 +315,8 @@ private void ShowGUI(bool viewOnly) {
Vector3 nodePos = default;
Constants.ServiceFactory.NetService.ProcessNode(
- nodeId,
- (ushort nId, ref NetNode node) => {
+ nodeId: nodeId,
+ handler: (ushort nId, ref NetNode node) => {
nodePos = node.m_position;
return true;
});
@@ -315,8 +324,8 @@ private void ShowGUI(bool viewOnly) {
for (int i = 0; i < 8; ++i) {
ushort segmentId = 0;
Constants.ServiceFactory.NetService.ProcessNode(
- nodeId,
- (ushort nId, ref NetNode node) => {
+ nodeId: nodeId,
+ handler: (ushort nId, ref NetNode node) => {
segmentId = node.GetSegment(i);
return true;
});
@@ -337,8 +346,8 @@ private void ShowGUI(bool viewOnly) {
Vector3 signPos = nodePos;
Constants.ServiceFactory.NetService.ProcessSegment(
- segmentId,
- (ushort sId, ref NetSegment segment) => {
+ segmentId: segmentId,
+ handler: (ushort sId, ref NetSegment segment) => {
signPos +=
10f * (startNode
? segment.m_startDirection
@@ -360,12 +369,12 @@ private void ShowGUI(bool viewOnly) {
showRemoveButton = true;
}
- if (MainTool.DrawGenericSquareOverlayTexture(
- RoadUI.PrioritySignTextures[sign],
- camPos,
- signPos,
- 90f,
- !viewOnly) && clicked)
+ if (Highlight.DrawGenericSquareOverlayTexture(
+ texture: RoadUI.PrioritySignTextures[sign],
+ camPos: camPos,
+ worldPos: signPos,
+ size: 90f,
+ canHover: !viewOnly) && clicked)
{
PriorityType? newSign;
switch (sign) {
@@ -386,11 +395,10 @@ private void ShowGUI(bool viewOnly) {
// also: case PriorityType.None:
default: {
- newSign = prioMan.CountPrioritySignsAtNode(
- nodeId,
- PriorityType.Main) >= 2
- ? PriorityType.Yield
- : PriorityType.Main;
+ byte count = prioMan.CountPrioritySignsAtNode(
+ nodeId: nodeId,
+ sign: PriorityType.Main);
+ newSign = count >= 2 ? PriorityType.Yield : PriorityType.Main;
break;
}
}
@@ -406,11 +414,12 @@ private void ShowGUI(bool viewOnly) {
// draw remove button and handle click
if (showRemoveButton
- && MainTool.DrawHoverableSquareOverlayTexture(
- RoadUI.SignClear,
+ &&
+ Highlight.DrawHoverableSquareOverlayTexture(
+ texture: RoadUI.SignClear,
camPos,
- nodePos,
- 90f)
+ worldPos: nodePos,
+ size: 90f)
&& clicked)
{
prioMan.RemovePrioritySignsFromNode(nodeId);
@@ -526,23 +535,18 @@ public void UpdateOnscreenDisplayPanel() {
items.Add(
new HardcodedMouseShortcut(
button: UIMouseButton.Left,
- shift: false,
ctrl: true,
- alt: false,
localizedText: T("Prio.Click:Quick setup prio junction")));
items.Add(
new HardcodedMouseShortcut(
button: UIMouseButton.Left,
shift: true,
- ctrl: false,
- alt: false,
localizedText: T("Prio.Click:Quick setup prio road/roundabout")));
items.Add(
new HardcodedMouseShortcut(
button: UIMouseButton.Left,
shift: true,
ctrl: true,
- alt: false,
localizedText: T("Prio.Click:Quick setup high prio road/roundabout")));
OnscreenDisplay.Display(items);
} else {
diff --git a/TLM/TLM/UI/SubTools/README.md b/TLM/TLM/UI/SubTools/README.md
index b17422123..e86e74a14 100644
--- a/TLM/TLM/UI/SubTools/README.md
+++ b/TLM/TLM/UI/SubTools/README.md
@@ -1,13 +1,18 @@
# TM:PE -- /UI/SubTool
-Tools that are selectable through the main menu.
-## Classes
-- **JunctionRestrictionsTool**: Junction restrictions
+Tools that are selectable through the main menu.
+
+## Subtools
+
- **LaneArrowTool**: Lane arrow changer
+- **SpeedLimitTool**: Speed limit overrides for roads and lanes, and default speed limits editor
+
+## Legacy Tools (refactor us!)
+
+- **JunctionRestrictionsTool**: Junction restrictions
- **LaneConnectorTool**: Lane connector
- **ManualTrafficLightsTool**: Manual traffic lights
- **ParkingRestrictionsTool**: Parking restrictions
- **PrioritySignsTool**: Priority signs
-- **SpeedLimitTool**: Speed limits
- **TimedTrafficLightTool**: Timed traffic lights
- **ToggleTrafficLightsTool**: Switch traffic lights
- **VehicleRestrictionsTool**: Vehicle restrictions
\ No newline at end of file
diff --git a/TLM/TLM/UI/SubTools/SpeedLimits/MphSignStyle.cs b/TLM/TLM/UI/SubTools/SpeedLimits/MphSignStyle.cs
deleted file mode 100644
index 9c4761e9f..000000000
--- a/TLM/TLM/UI/SubTools/SpeedLimits/MphSignStyle.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace TrafficManager.UI.SubTools.SpeedLimits {
- ///
- /// Defines styles available for road signs
- ///
- public enum MphSignStyle {
- SquareUS = 0,
- RoundUK = 1,
- RoundGerman = 2,
- }
-}
\ No newline at end of file
diff --git a/TLM/TLM/UI/SubTools/SpeedLimits/MphToggleButton.cs b/TLM/TLM/UI/SubTools/SpeedLimits/MphToggleButton.cs
new file mode 100644
index 000000000..903048f6e
--- /dev/null
+++ b/TLM/TLM/UI/SubTools/SpeedLimits/MphToggleButton.cs
@@ -0,0 +1,21 @@
+namespace TrafficManager.UI.SubTools.SpeedLimits {
+ using ColossalFramework.UI;
+ using TrafficManager.State;
+
+ /// Button to toggle MPH/Kmph display.
+ public class MphToggleButton : U.BaseUButton {
+ protected override bool IsVisible() => true;
+
+ public override void HandleClick(UIMouseEventParameter p) {
+ bool mph = !GlobalConfig.Instance.Main.DisplaySpeedLimitsMph;
+ OptionsGeneralTab.SetDisplayInMph(mph);
+ // this.UpdateMphToggleTexture();
+ }
+
+ /// Always clickable.
+ public override bool CanActivate() => true;
+
+ /// Active will mean MPH is activated.
+ protected override bool IsActive() => GlobalConfig.Instance.Main.DisplaySpeedLimitsMph;
+ }
+}
\ No newline at end of file
diff --git a/TLM/TLM/UI/SubTools/SpeedLimits/Overlay/OverlayLaneSpeedlimitHandle.cs b/TLM/TLM/UI/SubTools/SpeedLimits/Overlay/OverlayLaneSpeedlimitHandle.cs
new file mode 100644
index 000000000..acab89f37
--- /dev/null
+++ b/TLM/TLM/UI/SubTools/SpeedLimits/Overlay/OverlayLaneSpeedlimitHandle.cs
@@ -0,0 +1,201 @@
+namespace TrafficManager.UI.SubTools.SpeedLimits.Overlay {
+ using System;
+ using System.Collections.Generic;
+ using ColossalFramework;
+ using CSUtil.Commons;
+ using GenericGameBridge.Service;
+ using TrafficManager.Manager.Impl;
+ using TrafficManager.Util;
+
+ ///
+ /// Describes a recently rendered speed icon on the speed limits overlay for a LANE.
+ /// It is created while rendering, and if mouse is hovering over it, it is added to the list.
+ /// Click is handled separately away from the rendering code.
+ ///
+ public readonly struct OverlayLaneSpeedlimitHandle {
+ /// Segment id where the speedlimit sign was displayed.
+ public readonly ushort SegmentId;
+
+ public readonly uint LaneId;
+ public readonly byte LaneIndex;
+ public readonly NetInfo.Lane LaneInfo;
+ private readonly int SortedLaneIndex;
+
+ public OverlayLaneSpeedlimitHandle(ushort segmentId,
+ uint laneId,
+ byte laneIndex,
+ NetInfo.Lane laneInfo,
+ int sortedLaneIndex) {
+ this.SegmentId = segmentId;
+ this.LaneId = laneId;
+ this.LaneIndex = laneIndex;
+ this.LaneInfo = laneInfo;
+ this.SortedLaneIndex = sortedLaneIndex;
+ }
+
+ ///
+ /// Called when mouse is down, and when mouse is not in parent tool window area.
+ /// The show per lane mode is active.
+ ///
+ /// The speed limit to set or clear.
+ /// Destination (lanes or defaults).
+ /// Whether action affects entire street.
+ public void Click(in SetSpeedLimitAction action,
+ SetSpeedLimitTarget target,
+ bool multiSegmentMode) {
+ NetManager netManager = Singleton.instance;
+ NetSegment[] segmentsBuffer = netManager.m_segments.m_buffer;
+
+ Apply(
+ segmentId: this.SegmentId,
+ laneIndex: this.LaneIndex,
+ laneId: this.LaneId,
+ netInfo: segmentsBuffer[this.SegmentId].Info,
+ laneInfo: this.LaneInfo,
+ action: action,
+ target: target);
+
+ if (multiSegmentMode) {
+ ClickMultiSegment(action, target);
+ }
+ }
+
+ ///
+ /// Called if speed limit icon was clicked in segment display mode,
+ /// but also multisegment mode was enabled (like holding Shift).
+ ///
+ /// The active speed limit on the palette.
+ private void ClickMultiSegment(SetSpeedLimitAction action,
+ SetSpeedLimitTarget target) {
+ if (new RoundaboutMassEdit().TraverseLoop(this.SegmentId, out var segmentList)) {
+ IEnumerable lanes = FollowRoundaboutLane(
+ segmentList,
+ this.SegmentId,
+ this.SortedLaneIndex);
+
+ foreach (LanePos lane in lanes) {
+ // the speed limit for this lane has already been set.
+ if (lane.laneId == this.LaneId) {
+ continue;
+ }
+
+ SpeedLimitsTool.SetSpeedLimit(lane, action);
+ }
+ } else {
+ int slIndexCopy = this.SortedLaneIndex;
+
+ // Apply this to each lane
+ bool LaneVisitorFn(SegmentLaneTraverser.SegmentLaneVisitData data) {
+ if (data.SegVisitData.Initial) {
+ return true;
+ }
+
+ if (slIndexCopy != data.SortedLaneIndex) {
+ return true;
+ }
+
+ Constants.ServiceFactory.NetService.ProcessSegment(
+ segmentId: data.SegVisitData.CurSeg.segmentId,
+ handler: (ushort curSegmentId, ref NetSegment curSegment) => {
+ NetInfo.Lane curLaneInfo = curSegment.Info.m_lanes[data.CurLanePos.laneIndex];
+
+ Apply(
+ segmentId: curSegmentId,
+ laneIndex: data.CurLanePos.laneIndex,
+ laneId: data.CurLanePos.laneId,
+ netInfo: curSegment.Info,
+ laneInfo: curLaneInfo,
+ action: action,
+ target: target);
+ return true;
+ });
+
+ return true;
+ }
+
+ SegmentLaneTraverser.Traverse(
+ initialSegmentId: this.SegmentId,
+ direction: SegmentTraverser.TraverseDirection.AnyDirection,
+ side: SegmentTraverser.TraverseSide.AnySide,
+ laneStopCrit: SegmentLaneTraverser.LaneStopCriterion.LaneCount,
+ segStopCrit: SegmentTraverser.SegmentStopCriterion.Junction,
+ laneTypeFilter: SpeedLimitManager.LANE_TYPES,
+ vehicleTypeFilter: SpeedLimitManager.VEHICLE_TYPES,
+ laneVisitor: LaneVisitorFn);
+ }
+ } // end Click MultiSegment
+
+ ///
+ /// iterates through the given roundabout returning an enumeration
+ /// of all lanes with a matching based on
+ ///
+ /// input list of roundabout segments (must be oneway, and in the same direction).
+ /// The segment to match lane agaisnt
+ /// Index.
+ private IEnumerable FollowRoundaboutLane(
+ List segmentList,
+ ushort segmentId0,
+ int sortedLaneIndex) {
+ bool invert0 = segmentId0.ToSegment().m_flags.IsFlagSet(NetSegment.Flags.Invert);
+
+ int count0 = Shortcuts.netService.GetSortedLanes(
+ segmentId: segmentId0,
+ segment: ref segmentId0.ToSegment(),
+ startNode: null,
+ laneTypeFilter: SpeedLimitManager.LANE_TYPES,
+ vehicleTypeFilter: SpeedLimitManager.VEHICLE_TYPES,
+ sort: false).Count;
+
+ foreach (ushort segmentId in segmentList) {
+ bool invert = segmentId.ToSegment().m_flags.IsFlagSet(NetSegment.Flags.Invert);
+ IList lanes = Shortcuts.netService.GetSortedLanes(
+ segmentId: segmentId,
+ segment: ref segmentId.ToSegment(),
+ startNode: null,
+ laneTypeFilter: SpeedLimitManager.LANE_TYPES,
+ vehicleTypeFilter: SpeedLimitManager.VEHICLE_TYPES,
+ reverse: invert != invert0,
+ sort: true);
+ int index = sortedLaneIndex;
+
+ // if lane count does not match, assume segments are connected from outer side of the roundabout.
+ if (invert0) {
+ int diff = lanes.Count - count0;
+ index += diff;
+ }
+
+ if (index >= 0 && index < lanes.Count) {
+ yield return lanes[index];
+ }
+ } // foreach
+ }
+
+ ///
+ /// Based on target value, applies speed limit to a lane or default for that road type.
+ ///
+ /// Used for setting default speed limit for all roads if this type.
+ /// Used for setting override for one lane.
+ private static void Apply(ushort segmentId,
+ uint laneIndex,
+ uint laneId,
+ NetInfo netInfo,
+ NetInfo.Lane laneInfo,
+ SetSpeedLimitAction action,
+ SetSpeedLimitTarget target) {
+ switch (target) {
+ case SetSpeedLimitTarget.LaneOverride:
+ SpeedLimitManager.Instance.SetLaneSpeedLimit(segmentId, laneIndex, laneInfo, laneId, action);
+ break;
+ case SetSpeedLimitTarget.LaneDefault:
+ // SpeedLimitManager.Instance.FixCurrentSpeedLimits(netInfo);
+ SpeedLimitManager.Instance.SetCustomNetInfoSpeedLimit(netInfo, action.Value.GameUnits);
+ // TODO: The speed limit manager only supports default speed limit overrides per road type, not per lane
+ break;
+ default:
+ Log.Error(
+ $"Target for LANE speed handle click is not supported {nameof(target)}");
+ throw new NotSupportedException();
+ }
+ }
+ } // end struct
+} // end namespace
\ No newline at end of file
diff --git a/TLM/TLM/UI/SubTools/SpeedLimits/Overlay/OverlaySegmentSpeedlimitHandle.cs b/TLM/TLM/UI/SubTools/SpeedLimits/Overlay/OverlaySegmentSpeedlimitHandle.cs
new file mode 100644
index 000000000..320bb684c
--- /dev/null
+++ b/TLM/TLM/UI/SubTools/SpeedLimits/Overlay/OverlaySegmentSpeedlimitHandle.cs
@@ -0,0 +1,146 @@
+namespace TrafficManager.UI.SubTools.SpeedLimits.Overlay {
+ using System;
+ using ColossalFramework;
+ using CSUtil.Commons;
+ using TrafficManager.Manager.Impl;
+ using TrafficManager.Util;
+
+ ///
+ /// Describes a recently rendered speed icon on the speed limits overlay for SEGMENT.
+ /// It is created while rendering, and if mouse is hovering over it, it is added to the list.
+ /// Click is handled separately away from the rendering code.
+ ///
+ public readonly struct OverlaySegmentSpeedlimitHandle {
+ /// Segment id where the speedlimit sign was displayed.
+ public readonly ushort SegmentId;
+
+ /// Segment side, where the speedlimit sign was.
+ public readonly NetInfo.Direction FinalDirection;
+
+ public OverlaySegmentSpeedlimitHandle(ushort segmentId,
+ NetInfo.Direction finalDirection) {
+ SegmentId = segmentId;
+ FinalDirection = finalDirection;
+ }
+
+ ///
+ /// Called when mouse is down, and when mouse is not in parent tool window area.
+ /// The show per lane mode is disabled and editing per segment.
+ ///
+ /// What speed limit to set or clear.
+ ///
+ ///
+ public void Click(in SetSpeedLimitAction action,
+ SetSpeedLimitTarget target,
+ bool multiSegmentMode) {
+ NetManager netManager = Singleton.instance;
+ NetSegment[] segmentsBuffer = netManager.m_segments.m_buffer;
+
+ Apply(
+ segmentId: this.SegmentId,
+ finalDir: this.FinalDirection,
+ netInfo: segmentsBuffer[this.SegmentId].Info,
+ action: action,
+ target: target);
+
+ if (multiSegmentMode) {
+ ClickMultiSegment(action, target);
+ }
+ }
+
+ ///
+ /// Called if speed limit icon was clicked in segment display mode,
+ /// but also multisegment mode was enabled (like holding Shift).
+ ///
+ /// The active speed limit on the palette.
+ private void ClickMultiSegment(SetSpeedLimitAction action,
+ SetSpeedLimitTarget target) {
+ NetManager netManager = Singleton.instance;
+
+ if (new RoundaboutMassEdit().TraverseLoop(this.SegmentId, out var segmentList)) {
+ foreach (ushort segId in segmentList) {
+ SpeedLimitManager.Instance.SetSegmentSpeedLimit(segId, action);
+ }
+
+ return;
+ }
+
+ NetInfo.Direction normDir = this.FinalDirection;
+ NetSegment[] segmentsBuffer = netManager.m_segments.m_buffer;
+
+ if ((segmentsBuffer[this.SegmentId].m_flags &
+ NetSegment.Flags.Invert) != NetSegment.Flags.None) {
+ normDir = NetInfo.InvertDirection(normDir);
+ }
+
+ // Called for each lane in the traversed street
+ bool ForEachSegmentFun(SegmentLaneTraverser.SegmentLaneVisitData data) {
+ if (data.SegVisitData.Initial) {
+ return true;
+ }
+
+ bool reverse = data.SegVisitData.ViaStartNode ==
+ data.SegVisitData.ViaInitialStartNode;
+
+ ushort otherSegmentId = data.SegVisitData.CurSeg.segmentId;
+ NetInfo otherSegmentInfo = segmentsBuffer[otherSegmentId].Info;
+ byte laneIndex = data.CurLanePos.laneIndex;
+ NetInfo.Lane laneInfo = otherSegmentInfo.m_lanes[laneIndex];
+
+ NetInfo.Direction otherNormDir = laneInfo.m_finalDirection;
+
+ NetSegment.Flags invertFlag = segmentsBuffer[otherSegmentId].m_flags
+ & NetSegment.Flags.Invert;
+
+ if ((invertFlag != NetSegment.Flags.None) ^ reverse) {
+ otherNormDir = NetInfo.InvertDirection(otherNormDir);
+ }
+
+ if (otherNormDir == normDir) {
+ Apply(
+ segmentId: otherSegmentId,
+ finalDir: laneInfo.m_finalDirection,
+ netInfo: otherSegmentInfo,
+ action: action,
+ target: target);
+ }
+
+ return true;
+ }
+
+ SegmentLaneTraverser.Traverse(
+ initialSegmentId: this.SegmentId,
+ direction: SegmentTraverser.TraverseDirection.AnyDirection,
+ side: SegmentTraverser.TraverseSide.AnySide,
+ laneStopCrit: SegmentLaneTraverser.LaneStopCriterion.LaneCount,
+ segStopCrit: SegmentTraverser.SegmentStopCriterion.Junction,
+ laneTypeFilter: SpeedLimitManager.LANE_TYPES,
+ vehicleTypeFilter: SpeedLimitManager.VEHICLE_TYPES,
+ laneVisitor: ForEachSegmentFun);
+ } // end Click MultiSegment
+
+ ///
+ /// Based on target value, applies speed limit to a segmet or default for that road type.
+ ///
+ /// For defaults, will set default speed limit for that road type.
+ private static void Apply(ushort segmentId,
+ NetInfo.Direction finalDir,
+ NetInfo netInfo,
+ SetSpeedLimitAction action,
+ SetSpeedLimitTarget target) {
+ switch (target) {
+ case SetSpeedLimitTarget.SegmentOverride:
+ SpeedLimitManager.Instance.SetSegmentSpeedLimit(segmentId, finalDir, action);
+ break;
+ case SetSpeedLimitTarget.SegmentDefault:
+ // SpeedLimitManager.Instance.FixCurrentSpeedLimits(netInfo);
+ SpeedLimitManager.Instance.SetCustomNetInfoSpeedLimit(netInfo, action.Value.GameUnits);
+ break;
+ default:
+ Log.Error(
+ $"Target for SEGMENT speed handle click is not supported {nameof(target)}");
+ throw new NotSupportedException();
+ }
+ }
+ } // end struct
+} // end namespace
\ No newline at end of file
diff --git a/TLM/TLM/UI/SubTools/SpeedLimits/Overlay/SpeedLimitsOverlay.cs b/TLM/TLM/UI/SubTools/SpeedLimits/Overlay/SpeedLimitsOverlay.cs
new file mode 100644
index 000000000..b2fb04c04
--- /dev/null
+++ b/TLM/TLM/UI/SubTools/SpeedLimits/Overlay/SpeedLimitsOverlay.cs
@@ -0,0 +1,627 @@
+namespace TrafficManager.UI.SubTools.SpeedLimits.Overlay {
+ using System.Collections.Generic;
+ using ColossalFramework;
+ using GenericGameBridge.Service;
+ using TrafficManager.API.Traffic.Data;
+ using TrafficManager.Manager.Impl;
+ using TrafficManager.Traffic;
+ using TrafficManager.UI.Helpers;
+ using TrafficManager.UI.Textures;
+ using TrafficManager.Util;
+ using TrafficManager.Util.Caching;
+ using UnityEngine;
+
+ ///
+ /// Stores rendering state for Speed Limits overlay and provides rendering of speed limit signs
+ /// overlay for segments/lanes.
+ ///
+ public class SpeedLimitsOverlay {
+ const float SMALL_ICON_SCALE = 0.66f;
+
+ private ushort segmentId_;
+ private NetInfo.Direction finalDirection_ = NetInfo.Direction.None;
+
+ private TrafficManagerTool mainTool_;
+
+ /// Used to pass options to the overlay rendering.
+ public struct DrawArgs {
+ // /// Set to true to allow bigger and clickable road signs.
+ public bool InteractiveSigns;
+
+ /// Set to true when operating entire road between two junctions.
+ public bool MultiSegmentMode;
+
+ /// Set to true to show speed limits for each lane.
+ public bool ShowLimitsPerLane;
+
+ ///
+ /// Set this to true to additionally show the other PerLane/PerSegment mode as small
+ /// icons together with the large icons.
+ ///
+ public bool ShowOtherPerLaneModeTemporary;
+
+ ///
+ /// If false, overrides will be rendered as main icon, and defaults as small icon.
+ /// If true, defaults will be rendered large, and overrides small.
+ ///
+ public bool ShowDefaultsMode;
+
+ /// Hovered SEGMENT speed limit handles (output after rendering).
+ public List HoveredSegmentHandles;
+
+ /// Hovered LANE speed limit handles (output after rendering).
+ public List HoveredLaneHandles;
+
+ public static DrawArgs Create() {
+ return new DrawArgs {
+ InteractiveSigns = false,
+ MultiSegmentMode = false,
+ ShowLimitsPerLane = false,
+ HoveredSegmentHandles = new List(capacity: 10),
+ HoveredLaneHandles = new List(capacity: 10),
+ ShowDefaultsMode = false,
+ ShowOtherPerLaneModeTemporary = false,
+ };
+ }
+
+ public void ClearHovered() {
+ this.HoveredSegmentHandles.Clear();
+ this.HoveredLaneHandles.Clear();
+ }
+ }
+
+ ///
+ /// Stores potentially visible segment ids while the camera did not move.
+ ///
+ private GenericArrayCache cachedVisibleSegmentIds_;
+
+ ///
+ /// Stores last cached camera position in
+ ///
+ private CameraTransformValue lastCachedCamera_;
+
+ private const float SPEED_LIMIT_SIGN_SIZE = 70f;
+
+ private readonly Dictionary>
+ segmentCenterByDir = new Dictionary>();
+
+ public SpeedLimitsOverlay(TrafficManagerTool mainTool) {
+ this.mainTool_ = mainTool;
+ this.cachedVisibleSegmentIds_ = new GenericArrayCache(NetManager.MAX_SEGMENT_COUNT);
+ this.lastCachedCamera_ = new CameraTransformValue();
+ }
+
+ /// Displays non-sign overlays, like lane highlights.
+ /// The camera.
+ /// The state of the parent .
+ public void RenderHelperGraphics(RenderManager.CameraInfo cameraInfo,
+ DrawArgs args) {
+ if (args.ShowLimitsPerLane) {
+ RenderLanes(cameraInfo);
+ } else {
+ RenderSegments(cameraInfo, args);
+ }
+ }
+
+ private void RenderLanes(RenderManager.CameraInfo cameraInfo) {
+ }
+
+ private void RenderSegments(RenderManager.CameraInfo cameraInfo,
+ DrawArgs args) {
+ if (!args.MultiSegmentMode) {
+ //------------------------
+ // Single segment highlight
+ //------------------------
+ RenderSegmentSideOverlay(
+ cameraInfo: cameraInfo,
+ segmentId: this.segmentId_,
+ args: args,
+ finalDirection: this.finalDirection_);
+ } else {
+ //------------------------
+ // Entire street highlight
+ //------------------------
+ if (RoundaboutMassEdit.Instance.TraverseLoop(
+ segmentId: this.segmentId_,
+ segList: out var segmentList)) {
+ foreach (ushort segmentId in segmentList) {
+ RenderSegmentSideOverlay(
+ cameraInfo: cameraInfo,
+ segmentId: segmentId,
+ args: args);
+ }
+ } else {
+ SegmentTraverser.Traverse(
+ initialSegmentId: this.segmentId_,
+ direction: SegmentTraverser.TraverseDirection.AnyDirection,
+ side: SegmentTraverser.TraverseSide.AnySide,
+ stopCrit: SegmentTraverser.SegmentStopCriterion.Junction,
+ visitorFun: data => {
+ NetInfo.Direction finalDirection = this.finalDirection_;
+ if (data.IsReversed(this.segmentId_)) {
+ finalDirection = NetInfo.InvertDirection(finalDirection);
+ }
+
+ RenderSegmentSideOverlay(
+ cameraInfo: cameraInfo,
+ segmentId: data.CurSeg.segmentId,
+ args: args,
+ finalDirection: finalDirection);
+ return true;
+ });
+ }
+ }
+ }
+
+ ///
+ /// Renders all lanes with the given
+ /// if NetInfo.Direction.None, all lanes are rendered.
+ ///
+ private int RenderSegmentSideOverlay(
+ RenderManager.CameraInfo cameraInfo,
+ ushort segmentId,
+ DrawArgs args,
+ NetInfo.Direction finalDirection = NetInfo.Direction.None)
+ {
+ int count = 0;
+
+ // ------ visitor function
+ bool ForEachLane(uint laneId,
+ ref NetLane lane,
+ NetInfo.Lane laneInfo,
+ ushort _,
+ ref NetSegment segment,
+ byte laneIndex) {
+ bool render = (laneInfo.m_laneType & SpeedLimitManager.LANE_TYPES) != 0;
+ render &= (laneInfo.m_vehicleType & SpeedLimitManager.VEHICLE_TYPES) != 0;
+ render &= laneInfo.m_finalDirection == finalDirection
+ || finalDirection == NetInfo.Direction.None;
+
+ if (render) {
+ RenderLaneOverlay(cameraInfo: cameraInfo, laneId: laneId, args: args);
+ count++;
+ }
+
+ return true;
+ }
+ // end visitor function ------
+
+ Shortcuts.netService.IterateSegmentLanes(segmentId, handler: ForEachLane);
+ return count;
+ }
+
+ private void RenderLaneOverlay(RenderManager.CameraInfo cameraInfo,
+ uint laneId,
+ DrawArgs args) {
+ NetLane[] laneBuffer = NetManager.instance.m_lanes.m_buffer;
+ SegmentLaneMarker marker = new SegmentLaneMarker(laneBuffer[laneId].m_bezier);
+ bool pressed = Input.GetMouseButton(0);
+ Color color = this.mainTool_.GetToolColor(warning: pressed, error: false);
+
+ if (args.ShowLimitsPerLane) {
+ marker.Size = 3f; // lump the lanes together.
+ }
+
+ marker.RenderOverlay(cameraInfo, color, pressed);
+ }
+
+ ///
+ /// NOTE: This must be called from GUI mode, because of GUI.DrawTexture use.
+ /// Render the speed limit signs based on the current settings.
+ ///
+ /// Parameters how to draw exactly.
+ public void ShowSigns_GUI(DrawArgs args) {
+ Camera camera = Camera.main;
+ if (camera == null) {
+ return;
+ }
+
+ NetManager netManager = Singleton.instance;
+ SpeedLimitManager speedLimitManager = SpeedLimitManager.Instance;
+
+ var currentCamera = new CameraTransformValue(camera);
+ Transform currentCameraTransform = camera.transform;
+ Vector3 camPos = currentCameraTransform.position;
+
+ if (!lastCachedCamera_.Equals(currentCamera)) {
+ // cache visible segments
+ lastCachedCamera_ = currentCamera;
+ cachedVisibleSegmentIds_.Clear();
+
+ ShowSigns_CacheVisibleSegments(
+ netManager: netManager,
+ camPos: camPos,
+ speedLimitManager: speedLimitManager);
+ }
+
+ bool hover = false;
+ for (int segmentIdIndex = cachedVisibleSegmentIds_.Size - 1;
+ segmentIdIndex >= 0;
+ segmentIdIndex--) {
+ ushort segmentId = cachedVisibleSegmentIds_.Values[segmentIdIndex];
+
+ // If VehicleRestrictions tool is active, skip drawing the current selected segment
+ if ((mainTool_.GetToolMode() == ToolMode.VehicleRestrictions) &&
+ (segmentId == TrafficManagerTool.SelectedSegmentId)) {
+ continue;
+ }
+
+ // no speed limit overlay on selected segment when in vehicle restrictions mode
+ hover |= DrawSpeedLimitHandles(
+ segmentId: segmentId,
+ segment: ref netManager.m_segments.m_buffer[segmentId],
+ camPos: ref camPos,
+ args: args);
+ }
+
+ if (!hover) {
+ this.segmentId_ = 0;
+ }
+ }
+
+ ///
+ /// When camera position has changed and cached segments set is invalid, scan all segments
+ /// again and remember those visible in the camera frustum.
+ ///
+ /// Access to map data.
+ /// Camera position to consider.
+ /// Query if a segment is eligible for speed limits.
+ private void ShowSigns_CacheVisibleSegments(NetManager netManager,
+ Vector3 camPos,
+ SpeedLimitManager speedLimitManager) {
+ for (uint segmentId = 1; segmentId < NetManager.MAX_SEGMENT_COUNT; ++segmentId) {
+ if (!Constants.ServiceFactory.NetService.IsSegmentValid((ushort)segmentId)) {
+ continue;
+ }
+
+ // if ((netManager.m_segments.m_buffer[segmentId].m_flags &
+ // NetSegment.Flags.Untouchable) != NetSegment.Flags.None) continue;
+ Vector3 distToCamera = netManager.m_segments.m_buffer[segmentId].m_bounds.center - camPos;
+ if (distToCamera.sqrMagnitude > TrafficManagerTool.MAX_OVERLAY_DISTANCE_SQR) {
+ continue; // do not draw if too distant
+ }
+
+ bool visible = GeometryUtil.WorldToScreenPoint(
+ worldPos: netManager.m_segments.m_buffer[segmentId].m_bounds.center,
+ screenPos: out Vector3 _);
+
+ if (!visible) {
+ continue;
+ }
+
+ if (!speedLimitManager.MayHaveCustomSpeedLimits(
+ segment: ref netManager.m_segments.m_buffer[segmentId])) {
+ continue;
+ }
+
+ cachedVisibleSegmentIds_.Add((ushort)segmentId);
+ } // end for all segments
+ }
+
+ private bool DrawSpeedLimitHandles(ushort segmentId,
+ ref NetSegment segment,
+ ref Vector3 camPos,
+ DrawArgs args) {
+ if (args.ShowLimitsPerLane) {
+ return DrawSpeedLimitHandles_PerLane(
+ segmentId,
+ ref segment,
+ camPos,
+ args);
+ }
+
+ return DrawSpeedLimitHandles_PerSegment(
+ segmentId,
+ camPos,
+ args);
+ }
+
+ /// Render speed limit handles one per segment.
+ /// Seg id.
+ /// Camera.
+ /// Render args.
+ /// Whether sign is square or rectangular.
+ private bool DrawSpeedLimitHandles_PerSegment(ushort segmentId,
+ Vector3 camPos,
+ DrawArgs args) {
+ bool ret = false;
+
+ // draw speedlimits over mean middle points of lane beziers
+ if (!segmentCenterByDir.TryGetValue(
+ key: segmentId,
+ value: out Dictionary segCenter)) {
+ segCenter = new Dictionary();
+ segmentCenterByDir.Add(key: segmentId, value: segCenter);
+ GeometryUtil.CalculateSegmentCenterByDir(
+ segmentId: segmentId,
+ segmentCenterByDir: segCenter,
+ minDistance: SPEED_LIMIT_SIGN_SIZE * TrafficManagerTool.MAX_ZOOM);
+ }
+
+ // Sign renderer logic and chosen texture for signs
+ SpeedLimitsOverlaySign signRenderer = default;
+ IDictionary signsThemeTextures = SpeedLimitTextures.GetTextureSource();
+ IDictionary largeSignsTextureSource = args.ShowDefaultsMode
+ ? SpeedLimitTextures.RoadDefaults
+ : signsThemeTextures;
+
+ // Default signs are round, mph/kmph textures can be round or rectangular
+ Vector2 signsThemeAspectRatio = SpeedLimitTextures.GetTextureAspectRatio();
+ Vector2 largeRatio = args.ShowDefaultsMode ? Vector2.one : signsThemeAspectRatio;
+ var colorController = new OverlayHandleColorController(args.InteractiveSigns);
+
+ //--------------------------
+ // For all segments visible
+ //--------------------------
+ foreach (KeyValuePair e in segCenter) {
+ bool visible = GeometryUtil.WorldToScreenPoint(worldPos: e.Value, screenPos: out Vector3 screenPos);
+
+ if (!visible) {
+ continue;
+ }
+
+ float visibleScale = 100.0f / (e.Value - camPos).magnitude;
+ float size = (args.InteractiveSigns ? 1f : 0.8f) * SPEED_LIMIT_SIGN_SIZE * visibleScale;
+
+ // Recalculate visible rect for screen position and size
+ signRenderer.Reset(screenPos: screenPos, size: size * largeRatio);
+
+ bool isHoveredHandle = args.InteractiveSigns && signRenderer.ContainsMouse();
+
+ // Get speed limit override for segment
+ SpeedValue? overrideSpeedlimit =
+ SpeedLimitManager.Instance.GetCustomSpeedLimit(segmentId: segmentId, finalDir: e.Key);
+
+ // Get default or default-override speed limit for road type
+ NetInfo neti = GetSegmentNetinfo(segmentId: segmentId);
+ SpeedValue defaultSpeedlimit =
+ new SpeedValue(gameUnits: SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimit(info: neti));
+
+ //-----------
+ // Rendering
+ //-----------
+ // Sqrt(visibleScale) makes fade start later as distance grows
+ colorController.SetGUIColor(
+ hovered: isHoveredHandle,
+ opacityMultiplier: Mathf.Sqrt(visibleScale));
+
+ // Render override if interactive, or if readonly info layer and override exists
+ if (args.InteractiveSigns || overrideSpeedlimit.HasValue) {
+ signRenderer.DrawLargeTexture(
+ speedlimit: args.ShowDefaultsMode ? defaultSpeedlimit : overrideSpeedlimit,
+ textureSource: largeSignsTextureSource);
+ }
+
+ // If Alt is held, then also overlay the other (default limit in edit override mode,
+ // or override in edit defaults mode) as a small texture.
+ if (args.ShowOtherPerLaneModeTemporary) {
+ if (args.ShowDefaultsMode) {
+ signRenderer.DrawSmallTexture(
+ speedlimit: overrideSpeedlimit,
+ smallSize: size * SMALL_ICON_SCALE * signsThemeAspectRatio,
+ textureSource: signsThemeTextures);
+ } else {
+ signRenderer.DrawSmallTexture(
+ speedlimit: defaultSpeedlimit,
+ smallSize: size * SMALL_ICON_SCALE * Vector2.one,
+ textureSource: SpeedLimitTextures.RoadDefaults);
+ }
+ }
+
+ if (isHoveredHandle) {
+ // Clickable overlay (interactive signs also True):
+ // Register the position of a mouse-hovered speedlimit overlay icon
+ args.HoveredSegmentHandles.Add(
+ item: new OverlaySegmentSpeedlimitHandle(
+ segmentId: segmentId,
+ finalDirection: e.Key));
+
+ this.segmentId_ = segmentId;
+ this.finalDirection_ = e.Key;
+ ret = true;
+ }
+ }
+
+ colorController.RestoreGUIColor();
+ return ret;
+ }
+
+ ///
+ /// From segment ID find the Segment and from it retrieve the NetInfo. Should be fast.
+ ///
+ /// Segment.
+ /// Netinfo of that segment.
+ private NetInfo GetSegmentNetinfo(ushort segmentId) {
+ NetSegment[] segmentsBuffer = Singleton.instance.m_segments.m_buffer;
+ return segmentsBuffer[segmentId].Info;
+ }
+
+ /// Draw speed limit handles one per lane.
+ /// Seg id.
+ /// Segment reference from the game data.
+ /// Camera.
+ /// Render args.
+ /// Whether signs are square or rectangular.
+ private bool DrawSpeedLimitHandles_PerLane(ushort segmentId,
+ ref NetSegment segment,
+ Vector3 camPos,
+ DrawArgs args) {
+ bool ret = false;
+ Vector3 segmentCenterPos = segment.m_bounds.center;
+
+ // show individual speed limit handle per lane
+ int numLanes = GeometryUtil.GetSegmentNumVehicleLanes(
+ segmentId: segmentId,
+ nodeId: null,
+ numDirections: out int numDirections,
+ vehicleTypeFilter: SpeedLimitManager.VEHICLE_TYPES);
+
+ NetInfo segmentInfo = segment.Info;
+ Vector3 yu = (segment.m_endDirection - segment.m_startDirection).normalized;
+ Vector3 xu = Vector3.Cross(yu, new Vector3(0, 1f, 0)).normalized;
+
+ float signSize = args.InteractiveSigns
+ ? Constants.OVERLAY_INTERACTIVE_SIGN_SIZE
+ : Constants.OVERLAY_READONLY_SIGN_SIZE; // reserved sign size in game coordinates
+
+ Vector3 drawOriginPos = segmentCenterPos -
+ (0.5f * (((numLanes - 1) + numDirections) - 1) * signSize * xu);
+
+ IList sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes(
+ segmentId: segmentId,
+ segment: ref segment,
+ startNode: null,
+ laneTypeFilter: SpeedLimitManager.LANE_TYPES,
+ vehicleTypeFilter: SpeedLimitManager.VEHICLE_TYPES);
+
+ bool onlyMonorailLanes = sortedLanes.Count > 0;
+
+ // bool isMouseButtonDown =
+ // Input.GetMouseButtonDown(0) && !args.ParentTool.ContainsMouse();
+
+ if (args.InteractiveSigns) {
+ foreach (LanePos laneData in sortedLanes) {
+ byte laneIndex = laneData.laneIndex;
+ NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex];
+
+ if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Monorail) ==
+ VehicleInfo.VehicleType.None) {
+ onlyMonorailLanes = false;
+ break;
+ }
+ }
+ }
+
+ var directions = new HashSet();
+ int sortedLaneIndex = -1;
+
+ // Main grid for large icons
+ var grid = new Highlight.Grid(
+ gridOrigin: drawOriginPos,
+ cellWidth: signSize,
+ cellHeight: signSize,
+ xu: xu,
+ yu: yu);
+
+ // Sign renderer logic and chosen texture for signs
+ SpeedLimitsOverlaySign signRenderer = default;
+ IDictionary currentThemeTextures = SpeedLimitTextures.GetTextureSource();
+
+ Vector2 signsThemeAspectRatio = SpeedLimitTextures.GetTextureAspectRatio();
+ Vector2 largeRatio = args.ShowDefaultsMode ? Vector2.one : signsThemeAspectRatio;
+
+ IDictionary largeSignsTextureSource = args.ShowDefaultsMode
+ ? SpeedLimitTextures.RoadDefaults
+ : currentThemeTextures;
+
+ // Signs are rendered in a grid starting from col 0
+ float signColumn = 0f;
+ var colorController = new OverlayHandleColorController(args.InteractiveSigns);
+
+ //-----------------------
+ // For all lanes sorted
+ //-----------------------
+ foreach (LanePos laneData in sortedLanes) {
+ ++sortedLaneIndex;
+ uint laneId = laneData.laneId;
+ byte laneIndex = laneData.laneIndex;
+
+ NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex];
+ if (!directions.Contains(laneInfo.m_finalDirection)) {
+ if (directions.Count > 0) {
+ signColumn += 1f; // full space between opposite directions
+ }
+
+ directions.Add(laneInfo.m_finalDirection);
+ }
+
+ Vector3 worldPos = grid.GetPositionForRowCol(signColumn, 0);
+
+ bool visible = GeometryUtil.WorldToScreenPoint(worldPos, out Vector3 screenPos);
+
+ if (!visible) {
+ continue;
+ }
+
+ float visibleScale = 100.0f / (worldPos - camPos).magnitude;
+ float size = (args.InteractiveSigns ? 1f : 0.8f) * SPEED_LIMIT_SIGN_SIZE * visibleScale;
+ signRenderer.Reset(screenPos, largeRatio * size);
+
+ // Set render transparency based on mouse hover
+ bool isHoveredHandle = args.InteractiveSigns && signRenderer.ContainsMouse();
+
+ // Sqrt(visibleScale) makes fade start later as distance grows
+ colorController.SetGUIColor(
+ hovered: isHoveredHandle,
+ opacityMultiplier: Mathf.Sqrt(visibleScale));
+
+ // Get speed limit override for the lane
+ GetSpeedLimitResult overrideSpeedlimit =
+ SpeedLimitManager.Instance.GetCustomSpeedLimit(laneId);
+
+ SpeedValue? largeSpeedlimit =
+ (overrideSpeedlimit.Type == GetSpeedLimitResult.ResultType.OverrideExists
+ && overrideSpeedlimit.OverrideValue.HasValue)
+ ? overrideSpeedlimit.OverrideValue
+ : overrideSpeedlimit.DefaultValue;
+
+ signRenderer.DrawLargeTexture(largeSpeedlimit, largeSignsTextureSource);
+
+ // If Alt is held, then also overlay the other (default limit in edit override mode,
+ // or override in edit defaults mode) as a small texture.
+ if (args.ShowOtherPerLaneModeTemporary) {
+ if (args.ShowDefaultsMode) {
+ signRenderer.DrawSmallTexture(
+ speedlimit: overrideSpeedlimit.OverrideValue,
+ smallSize: size * SMALL_ICON_SCALE * signsThemeAspectRatio,
+ textureSource: currentThemeTextures);
+ } else {
+ signRenderer.DrawSmallTexture(
+ speedlimit: overrideSpeedlimit.DefaultValue,
+ smallSize: size * SMALL_ICON_SCALE * Vector2.one,
+ textureSource: SpeedLimitTextures.RoadDefaults);
+ }
+ }
+
+ if (args.InteractiveSigns
+ && !onlyMonorailLanes
+ && ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Monorail) != VehicleInfo.VehicleType.None))
+ {
+ Texture2D tex1 = RoadUI.VehicleInfoSignTextures[
+ LegacyExtVehicleType.ToNew(ExtVehicleType.PassengerTrain)];
+
+ // TODO: Replace with direct call to GUI.DrawTexture as in the func above
+ grid.DrawStaticSquareOverlayGridTexture(
+ texture: tex1,
+ camPos: camPos,
+ x: signColumn,
+ y: 1f,
+ size: SPEED_LIMIT_SIGN_SIZE,
+ screenRect: out Rect _);
+ }
+
+ if (isHoveredHandle) {
+ // Clickable overlay (interactive signs also True):
+ // Register the position of a mouse-hovered speedlimit overlay icon
+ args.HoveredLaneHandles.Add(
+ new OverlayLaneSpeedlimitHandle(
+ segmentId: segmentId,
+ laneId: laneId,
+ laneIndex: laneIndex,
+ laneInfo: laneInfo,
+ sortedLaneIndex: sortedLaneIndex));
+
+ this.segmentId_ = segmentId;
+ ret = true;
+ }
+
+ signColumn += 1f;
+ }
+
+ colorController.RestoreGUIColor();
+ return ret;
+ }
+ }
+
+ // end class
+}
\ No newline at end of file
diff --git a/TLM/TLM/UI/SubTools/SpeedLimits/Overlay/SpeedLimitsOverlaySign.cs b/TLM/TLM/UI/SubTools/SpeedLimits/Overlay/SpeedLimitsOverlaySign.cs
new file mode 100644
index 000000000..60f7912c2
--- /dev/null
+++ b/TLM/TLM/UI/SubTools/SpeedLimits/Overlay/SpeedLimitsOverlaySign.cs
@@ -0,0 +1,80 @@
+namespace TrafficManager.UI.SubTools.SpeedLimits.Overlay {
+ using System.Collections.Generic;
+ using TrafficManager.API.Traffic.Data;
+ using TrafficManager.UI.Textures;
+ using UnityEngine;
+
+ ///
+ /// Combo sign object rendered as GUI overlay.
+ /// Consists of a large speed limit icon using current theme, and a small optional icon
+ /// in the corner. Signs can have different aspect ratios (i.e. allows to combine US rectangular
+ /// and round signs).
+ ///
+ // +-----+
+ // | |
+ // | 9 0 |
+ // | 50|
+ // +-----+
+ public struct SpeedLimitsOverlaySign {
+ private Vector3 screenPos_;
+
+ /// The visible screen-space box of the large texture (for mouse interaction).
+ private Rect screenRect_;
+
+ /// For each new sign world position, recalculate new rect for rendering.
+ /// Sign position projected to screen.
+ /// Visible large sign size.
+ public void Reset(Vector3 screenPos, Vector2 size) {
+ this.screenPos_ = screenPos;
+
+ this.screenRect_ = new Rect(
+ x: screenPos.x - (size.x * 0.5f),
+ y: screenPos.y - (size.y * 0.5f),
+ width: size.x,
+ height: size.y);
+ }
+
+ public bool ContainsMouse() {
+ return TrafficManagerTool.IsMouseOver(this.screenRect_);
+ }
+
+ /// Draw large rect with the speed value or unlimited.
+ /// Show this speed.
+ public void DrawLargeTexture(SpeedValue? speedlimit,
+ IDictionary textureSource) {
+ Texture2D tex = speedlimit.HasValue
+ ? SpeedLimitTextures.GetSpeedLimitTexture(speedlimit.Value, textureSource)
+ : textureSource[0];
+
+ GUI.DrawTexture(
+ position: this.screenRect_,
+ image: tex);
+ }
+
+ ///
+ /// Draws the small texture in the corner. Size is passed here again, because we could be
+ /// drawing a combination of rectangular US sign and round default speed sign.
+ ///
+ /// Show this.
+ /// Size of small rect.
+ /// Texture collection to use.
+ public void DrawSmallTexture(SpeedValue? speedlimit,
+ Vector2 smallSize,
+ IDictionary textureSource) {
+ // Offset the drawing center to the bottom right quarter of the large rect
+ Rect smallRect = new Rect(
+ x: this.screenPos_.x,
+ y: this.screenPos_.y,
+ width: smallSize.x,
+ height: smallSize.y);
+
+ Texture2D tex = speedlimit.HasValue
+ ? SpeedLimitTextures.GetSpeedLimitTexture(speedlimit.Value, textureSource)
+ : textureSource[0];
+
+ GUI.DrawTexture(
+ position: smallRect,
+ image: tex);
+ }
+ }
+}
\ No newline at end of file
diff --git a/TLM/TLM/UI/SubTools/SpeedLimits/PaletteGenerator.cs b/TLM/TLM/UI/SubTools/SpeedLimits/PaletteGenerator.cs
new file mode 100644
index 000000000..3611a0d66
--- /dev/null
+++ b/TLM/TLM/UI/SubTools/SpeedLimits/PaletteGenerator.cs
@@ -0,0 +1,41 @@
+namespace TrafficManager.UI.SubTools.SpeedLimits {
+ using System.Collections.Generic;
+ using TrafficManager.API.Traffic.Data;
+ using TrafficManager.State;
+
+ /// Produces list of speed limits to offer user in the palette.
+ public class PaletteGenerator {
+ /// Produces list of speed limits to offer user in the palette.
+ /// What kind of speed limit list is required
+ /// List from smallest to largest speed with the given unit. Zero (no limit) is not added to the list.
+ /// The values are in-game speeds as float.
+ public static List AllSpeedLimits(SpeedUnit unit) {
+ var result = new List();
+ switch (unit) {
+ case SpeedUnit.Kmph:
+ for (var km = SpeedLimitsTool.LOWER_KMPH;
+ km <= SpeedLimitsTool.UPPER_KMPH;
+ km += SpeedLimitsTool.KMPH_STEP) {
+ result.Add(SpeedValue.FromKmph(km));
+ }
+
+ break;
+ case SpeedUnit.Mph:
+ for (var mi = SpeedLimitsTool.LOWER_MPH;
+ mi <= SpeedLimitsTool.UPPER_MPH;
+ mi += SpeedLimitsTool.MPH_STEP) {
+ result.Add(SpeedValue.FromMph(mi));
+ }
+
+ break;
+ case SpeedUnit.CurrentlyConfigured:
+ // Automatically choose from the config
+ return GlobalConfig.Instance.Main.DisplaySpeedLimitsMph
+ ? AllSpeedLimits(SpeedUnit.Mph)
+ : AllSpeedLimits(SpeedUnit.Kmph);
+ }
+
+ return result;
+ }
+ }
+}
\ No newline at end of file
diff --git a/TLM/TLM/UI/SubTools/SpeedLimits/SpeedLimitPaletteButton.cs b/TLM/TLM/UI/SubTools/SpeedLimits/SpeedLimitPaletteButton.cs
new file mode 100644
index 000000000..31e9bba7e
--- /dev/null
+++ b/TLM/TLM/UI/SubTools/SpeedLimits/SpeedLimitPaletteButton.cs
@@ -0,0 +1,65 @@
+namespace TrafficManager.UI.SubTools.SpeedLimits {
+ using ColossalFramework.UI;
+ using TrafficManager.API.Traffic.Data;
+ using TrafficManager.U;
+ using TrafficManager.Util;
+ using UnityEngine;
+
+ ///
+ /// Speed limits palette button, has speed assigned to it, and on click will update the selected
+ /// speed in the tool, and highlight self.
+ ///
+ internal class SpeedLimitPaletteButton : UButton {
+ /// Button width if it contains value less than 100 and is not selected in the palette.
+ public const float DEFAULT_WIDTH = 40f;
+ public const float DEFAULT_HEIGHT = 100f;
+
+ /// Button must know its speed value.
+ public SpeedValue AssignedValue;
+
+ public SpeedLimitsTool ParentTool;
+
+ /// Label below the speed limit button displaying alternate unit.
+ public ULabel AltUnitsLabel;
+
+ protected override void OnClick(UIMouseEventParameter p) {
+ base.OnClick(p);
+
+ // Tell the parent to update all buttons this will unmark all inactive buttons and
+ // mark one which is active. The call turns back here to this.UpdateSpeedlimitButton()
+ ParentTool.OnPaletteButtonClicked(this.AssignedValue);
+ }
+
+ ///
+ /// If button active state changes, update visual to highlight it.
+ /// Active button has large text and is blue.
+ ///
+ public void UpdateSpeedlimitButton() {
+ if (this.IsActive()) {
+ this.textScale = 2.0f;
+ this.ColorizeAllStates(new Color32(0, 128, 255, 255));
+
+ // Can't set width directly, but can via the resizer
+ this.GetResizerConfig().FixedSize.x = DEFAULT_WIDTH * 1.5f;
+
+ if (this.AltUnitsLabel) { this.AltUnitsLabel.Show(); }
+ } else {
+ this.textScale = 1.0f;
+ this.ColorizeAllStates(new Color32(255, 255, 255, 255));
+
+ // Can't set width directly, but can via the resizer
+ this.GetResizerConfig().FixedSize.x = DEFAULT_WIDTH;
+
+ if (this.AltUnitsLabel) { this.AltUnitsLabel.Hide(); }
+ }
+ }
+
+ public override bool CanActivate() => true;
+
+ protected override bool IsActive() {
+ return FloatUtil.NearlyEqual(
+ this.AssignedValue.GameUnits,
+ ParentTool.CurrentPaletteSpeedLimit.GameUnits);
+ }
+ }
+}
\ No newline at end of file
diff --git a/TLM/TLM/UI/SubTools/SpeedLimits/SpeedLimitSignTheme.cs b/TLM/TLM/UI/SubTools/SpeedLimits/SpeedLimitSignTheme.cs
new file mode 100644
index 000000000..57d640bd7
--- /dev/null
+++ b/TLM/TLM/UI/SubTools/SpeedLimits/SpeedLimitSignTheme.cs
@@ -0,0 +1,13 @@
+namespace TrafficManager.UI.SubTools.SpeedLimits {
+ /// Defines styles available for road signs.
+ public enum SpeedLimitSignTheme {
+ /// US style white rectangular road signs.
+ RectangularUS = 0,
+
+ /// Round British style signs with bolder font called "Transport".
+ RoundUK = 1,
+
+ /// German round road signs, using font "DIN 1451".
+ RoundGerman = 2,
+ }
+}
\ No newline at end of file
diff --git a/TLM/TLM/UI/SubTools/SpeedLimits/SpeedLimitsTool.cs b/TLM/TLM/UI/SubTools/SpeedLimits/SpeedLimitsTool.cs
index a5d02dfcb..cc11452bf 100644
--- a/TLM/TLM/UI/SubTools/SpeedLimits/SpeedLimitsTool.cs
+++ b/TLM/TLM/UI/SubTools/SpeedLimits/SpeedLimitsTool.cs
@@ -1,1195 +1,335 @@
namespace TrafficManager.UI.SubTools.SpeedLimits {
- using ColossalFramework;
+ using System.Collections.Generic;
using ColossalFramework.UI;
- using CSUtil.Commons;
using GenericGameBridge.Service;
- using System;
- using System.Collections.Generic;
using TrafficManager.API.Traffic.Data;
using TrafficManager.Manager.Impl;
using TrafficManager.State;
- using TrafficManager.Traffic;
- using TrafficManager.UI.Helpers;
+ using TrafficManager.U;
+ using TrafficManager.UI.MainMenu.OSD;
using TrafficManager.UI.SubTools.PrioritySigns;
- using TrafficManager.UI.Textures;
+ using TrafficManager.UI.SubTools.SpeedLimits.Overlay;
using TrafficManager.Util;
- using TrafficManager.Util.Caching;
using UnityEngine;
- using static TrafficManager.Util.Shortcuts;
-
- public class SpeedLimitsTool : LegacySubTool {
- public const int
- BREAK_PALETTE_COLUMN_KMPH = 8; // palette shows N in a row, then break and another row
- public const int
- BREAK_PALETTE_COLUMN_MPH = 10; // palette shows M in a row, then break and another row
-
- private const ushort LOWER_KMPH = 10;
+ ///
+ /// Implements new style Speed Limits palette and speed limits management UI.
+ ///
+ public class SpeedLimitsTool
+ : TrafficManagerSubTool,
+ UI.MainMenu.IOnscreenDisplayProvider
+ {
+ public const ushort LOWER_KMPH = 10;
public const ushort UPPER_KMPH = 140;
public const ushort KMPH_STEP = 10;
- private const ushort LOWER_MPH = 5;
+ public const ushort LOWER_MPH = 5;
public const ushort UPPER_MPH = 90;
public const ushort MPH_STEP = 5;
- /// Visible sign size, slightly reduced from 100 to accomodate another column for MPH
- private const int GUI_SPEED_SIGN_SIZE = 80;
- private readonly float speedLimitSignSize = 70f;
-
- private bool cursorInSecondaryPanel;
-
- /// Currently selected speed limit on the limits palette
- /// negative = invalid
- /// 0 = no limit
- /// null = default
- ///
- private SpeedValue? currentPaletteSpeedLimit = new SpeedValue(-1f);
-
- private readonly Dictionary> segmentCenterByDir =
- new Dictionary>();
-
- private Rect paletteWindowRect =
- TrafficManagerTool.GetDefaultScreenPositionForRect(new Rect(0, 0, 10 * (GUI_SPEED_SIGN_SIZE + 5), 150));
-
- private Rect defaultsWindowRect = TrafficManagerTool.GetDefaultScreenPositionForRect(new Rect(0, 80, 50, 50));
-
///
- /// Stores potentially visible segment ids while the camera did not move
+ /// Currently selected speed limit on the limits palette.
+ /// units less than 0: invalid (not selected)
+ /// units = 0: no limit.
///
- private GenericArrayCache CachedVisibleSegmentIds { get; }
+ public SpeedValue CurrentPaletteSpeedLimit = new SpeedValue(-1f);
///
- /// Stores last cached camera position in
+ /// Will show and edit speed limits for each lane.
+ /// This is toggled by the tool window button. Use to
+ /// retrieve the value affected by the Ctrl button state.
///
- private CameraTransformValue LastCachedCamera { get; set; }
-
- private bool defaultsWindowVisible;
- private int currentInfoIndex = -1;
- private SpeedValue currentSpeedLimit = new SpeedValue(-1f);
-
- private Texture2D RoadTexture {
- get {
- if (roadTexture == null) {
- roadTexture = new Texture2D(GUI_SPEED_SIGN_SIZE, GUI_SPEED_SIGN_SIZE);
- }
-
- return roadTexture;
- }
- }
-
- private Texture2D roadTexture;
private bool showLimitsPerLane_;
- private bool ShowLimitsPerLane => showLimitsPerLane_ ^ ControlIsPressed;
-
- private bool multiSegmentMode_;
- private bool MultiSegmentMode => multiSegmentMode_ ^ ShiftIsPressed;
+ /// Whether limits per lane are to be shown.
+ /// Gets but also holding Ctrl would invert it.
+ private bool GetShowLimitsPerLane() => showLimitsPerLane_ ^ Shortcuts.ControlIsPressed;
- private struct RenderData {
- internal ushort SegmentId;
- internal uint LaneId;
- internal byte LaneIndex;
- internal int SortedLaneIndex;
- internal NetInfo.Lane LaneInfo;
- internal NetInfo.Direction FinalDirection;
- }
- private RenderData renderData_;
+ ///
+ /// True if user is editing road defaults. False if user is editing speed limit overrides.
+ ///
+ private bool editDefaultsMode_;
+
+ /// Will edit entire road between two junctions by holding Shift.
+ private bool GetMultiSegmentMode() => Shortcuts.ShiftIsPressed;
+
+ // ///
+ // /// Finite State machine for the tool. Represents current UI state for Lane Arrows.
+ // ///
+ // private Util.GenericFsm fsm_;
+
+ private SpeedLimitsOverlay.DrawArgs overlayDrawArgs_ = SpeedLimitsOverlay.DrawArgs.Create();
+ private SpeedLimitsOverlay overlay_;
+
+ // /// Tool states.
+ // private enum State {
+ // /// Clicking a segment will override speed limit on all lanes.
+ // /// Holding Alt will temporarily show the Defaults.
+ // ///
+ // EditSegments,
+ //
+ // /// Clicking a road type will override default.
+ // EditDefaults,
+ //
+ // /// The user requested to leave the tool.
+ // ToolDisabled,
+ // }
+ //
+ // /// Events which trigger state transitions.
+ // private enum Trigger {
+ // /// Mode 1 - Segment Edit Mode - clicked.
+ // SegmentsButtonClick,
+ //
+ // /// Mode 2 - Edit Defaults - clicked.
+ // DefaultsButtonClick,
+ //
+ // /// Right mouse has been clicked.
+ // RightMouseClick,
+ // }
+
+ /// If exists, contains tool panel floating on the selected node.
+ private SpeedLimitsWindow Window { get; set; }
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Reference to the parent maintool.
public SpeedLimitsTool(TrafficManagerTool mainTool)
: base(mainTool) {
- CachedVisibleSegmentIds = new GenericArrayCache(NetManager.MAX_SEGMENT_COUNT);
- LastCachedCamera = new CameraTransformValue();
- }
-
- internal static void SetSpeedLimit(LanePos lane, SpeedValue? speed) {
- ushort segmentId = lane.laneId.ToLane().m_segment;
- SpeedLimitManager.Instance.SetSpeedLimit(
- segmentId: segmentId,
- laneIndex: lane.laneIndex,
- laneInfo: segmentId.ToSegment().Info.m_lanes[lane.laneIndex],
- laneId: lane.laneId,
- speedLimit: speed?.GameUnits);
- }
-
- public override bool IsCursorInPanel() {
- return base.IsCursorInPanel() || cursorInSecondaryPanel;
+ overlay_ = new SpeedLimitsOverlay(mainTool: this.MainTool);
}
- public override void OnActivate() {
- base.OnActivate();
- LastCachedCamera = new CameraTransformValue();
+ // ///
+ // /// Creates FSM ready to begin editing. Or recreates it when ESC is pressed
+ // /// and the tool is canceled.
+ // ///
+ // /// The new FSM in the initial state.
+ // private Util.GenericFsm InitFiniteStateMachine() {
+ // var fsm = new Util.GenericFsm(State.EditSegments);
+ //
+ // fsm.Configure(State.EditSegments)
+ // // .OnEntry(this.OnEnterSelectState)
+ // // .OnLeave(this.OnLeaveSelectState)
+ // .TransitionOnEvent(Trigger.DefaultsButtonClick, State.EditDefaults)
+ // .TransitionOnEvent(Trigger.RightMouseClick, State.ToolDisabled);
+ //
+ // fsm.Configure(State.EditDefaults)
+ // .TransitionOnEvent(Trigger.SegmentsButtonClick, State.EditSegments)
+ // .TransitionOnEvent(Trigger.RightMouseClick, State.ToolDisabled);
+ //
+ // fsm.Configure(State.ToolDisabled)
+ // .OnEntry(
+ // () => {
+ // // We are done here, leave the tool.
+ // // This will result in this.DeactivateTool being called.
+ // ModUI.Instance.MainMenu.ClickToolButton(ToolMode.LaneArrows);
+ // });
+ //
+ // return fsm;
+ // }
+
+ private static string T(string key) => Translation.SpeedLimits.Get(key);
+
+ public override void ActivateTool() {
+ RecreateToolWindow();
+
+ // this.fsm_ = InitFiniteStateMachine();
+ MainTool.RequestOnscreenDisplayUpdate();
}
- public override void OnPrimaryClickOverlay() { }
-
- public override void OnToolGUI(Event e) {
- base.OnToolGUI(e);
-
- string unitTitle = string.Format(
- " ({0})",
- GlobalConfig.Instance.Main.DisplaySpeedLimitsMph
- ? Translation.SpeedLimits.Get("Miles per hour")
- : Translation.SpeedLimits.Get("Kilometers per hour"));
-
- paletteWindowRect.width = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph
- ? 10 * (GUI_SPEED_SIGN_SIZE + 5)
- : 8 * (GUI_SPEED_SIGN_SIZE + 5);
+ /// Drop tool window if it existed, and create again.
+ internal void RecreateToolWindow() {
+ // Create a generic self-sizing window with padding of 4px.
+ void SetupFn(UiBuilder b) {
+ b.SetPadding(UConst.UIPADDING);
+ b.Control.SetupControls(builder: b, parentTool: this);
+ }
- paletteWindowRect = GUILayout.Window(
- id: 254,
- screenRect: paletteWindowRect,
- func: GuiSpeedLimitsWindow,
- text: Translation.Menu.Get("Tooltip:Speed limits") + unitTitle,
- style: WindowStyle);
+ if (this.Window) {
+ this.Window.Hide();
- if (defaultsWindowVisible) {
- defaultsWindowRect = GUILayout.Window(
- id: 258,
- screenRect: defaultsWindowRect,
- func: GuiDefaultsWindow,
- text: Translation.SpeedLimits.Get("Window.Title:Default speed limits"),
- style: WindowStyle);
+ // The constructor of new window will try to delete it by name, but we can help it
+ UnityEngine.Object.Destroy(this.Window);
}
- cursorInSecondaryPanel = paletteWindowRect.Contains(Event.current.mousePosition)
- || (defaultsWindowVisible
- && defaultsWindowRect.Contains(
- Event.current.mousePosition));
+ this.Window = UiBuilder.CreateWindow(setupFn: SetupFn);
- // overlayHandleHovered = false;
- // ShowSigns(false);
- }
+ //--------------------------------------------------
+ // Click handlers for the window are located here
+ // to have insight into SpeedLimits Tool internals
+ //--------------------------------------------------
+ this.Window.SegmentLaneModeToggleButton.SetupToggleButton(
+ onClickFun: OnClickSegmentLaneModeButton,
+ isActiveFun: b => this.showLimitsPerLane_);
+ this.Window.EditDefaultsModeButton.SetupToggleButton(
+ onClickFun: OnClickEditDefaultsButton,
+ isActiveFun: b => this.editDefaultsMode_);
- private static NetLane[] laneBuffer => NetManager.instance.m_lanes.m_buffer;
-
- private void RenderLaneOverlay(RenderManager.CameraInfo cameraInfo, uint laneId) {
- var marker = new SegmentLaneMarker(laneBuffer[laneId].m_bezier);
- bool pressed = Input.GetMouseButton(0);
- Color color = MainTool.GetToolColor(pressed, false);
- if (!ShowLimitsPerLane) {
- marker.Size = 3f; // lump the lanes together.
- }
- marker.RenderOverlay(cameraInfo, color, pressed);
+ this.Window.ToggleMphButton.uOnClick = OnClickToggleMphButton;
}
///
- /// Renders all lanes with the given
- /// if NetInfo.Direction.None, all lanes are rendered.
+ /// Additional action to toggling MPH/kmph: Also to refresh the window
+ /// The MPH toggling happens inside the custom button class
///
- private int RenderSegmentSideOverlay(
- RenderManager.CameraInfo cameraInfo,
- ushort segmentId,
- NetInfo.Direction finalDirection = NetInfo.Direction.None) {
- int count = 0;
- bool pressed = Input.GetMouseButton(0);
- Color color = MainTool.GetToolColor(pressed, false);
- netService.IterateSegmentLanes(
- segmentId,
- (uint laneId,
- ref NetLane lane,
- NetInfo.Lane laneInfo,
- ushort _,
- ref NetSegment segment,
- byte laneIndex) => {
- bool render = (laneInfo.m_laneType & SpeedLimitManager.LANE_TYPES) != 0;
- render &= (laneInfo.m_vehicleType & SpeedLimitManager.VEHICLE_TYPES) != 0;
- render &= laneInfo.m_finalDirection == finalDirection || finalDirection == NetInfo.Direction.None;
- if (render) {
- RenderLaneOverlay(cameraInfo, laneId);
- count++;
- }
- return true;
- });
- return count;
+ private void OnClickToggleMphButton(UIComponent component, UIMouseEventParameter param) {
+ this.RecreateToolWindow();
}
- private void RenderLanes(RenderManager.CameraInfo cameraInfo) {
- if (!MultiSegmentMode) {
- RenderLaneOverlay(cameraInfo, renderData_.LaneId);
- } else if (RoundaboutMassEdit.Instance.TraverseLoop(renderData_.SegmentId, out var segmentList)) {
- var lanes = FollowRoundaboutLane(segmentList, renderData_.SegmentId, renderData_.SortedLaneIndex);
- foreach (var lane in lanes)
- RenderLaneOverlay(cameraInfo, lane.laneId);
- } else {
- bool LaneVisitorFun(SegmentLaneTraverser.SegmentLaneVisitData data) {
- if (data.SortedLaneIndex == renderData_.SortedLaneIndex) {
- RenderLaneOverlay(cameraInfo, data.CurLanePos.laneId);
- }
- return true;
- }
- {
- SegmentLaneTraverser.Traverse(
- renderData_.SegmentId,
- SegmentTraverser.TraverseDirection.AnyDirection,
- SegmentTraverser.TraverseSide.AnySide,
- SegmentLaneTraverser.LaneStopCriterion.LaneCount,
- SegmentTraverser.SegmentStopCriterion.Junction,
- SpeedLimitManager.LANE_TYPES,
- SpeedLimitManager.VEHICLE_TYPES,
- LaneVisitorFun);
- }
- }
- }
-
- ///
- /// iterates through the given roundabout returning an enumeration
- /// of all lanes with a matching based on
- ///
- /// input list of roundabout segments (must be oneway, and in the same direction).
- /// The segment to match lane agaisnt
- ///
- private IEnumerable FollowRoundaboutLane(List segmentList, ushort segmentId0, int sortedLaneIndex) {
- bool invert0 = segmentId0.ToSegment().m_flags.IsFlagSet(NetSegment.Flags.Invert);
- int count0 = netService.GetSortedLanes(
- segmentId: segmentId0,
- segment: ref segmentId0.ToSegment(),
- startNode: null,
- laneTypeFilter: SpeedLimitManager.LANE_TYPES,
- vehicleTypeFilter: SpeedLimitManager.VEHICLE_TYPES,
- sort: false).Count;
- foreach (ushort segmentId in segmentList) {
- bool invert = segmentId.ToSegment().m_flags.IsFlagSet(NetSegment.Flags.Invert);
- var lanes = netService.GetSortedLanes(
- segmentId: segmentId,
- segment: ref segmentId.ToSegment(),
- startNode: null,
- laneTypeFilter: SpeedLimitManager.LANE_TYPES,
- vehicleTypeFilter: SpeedLimitManager.VEHICLE_TYPES,
- reverse: invert != invert0,
- sort: true);
- int index = sortedLaneIndex;
-
- // if lane count does not match, assume segments are connected from outer side of the roundabout.
- if (invert0) {
- int diff = lanes.Count - count0;
- index += diff;
- }
- if (0 <= index && index < lanes.Count) {
- yield return lanes[index];
- }
- } // foreach
- }
-
- private void RenderSegmentsSide(RenderManager.CameraInfo cameraInfo) {
- if (!MultiSegmentMode) {
- RenderSegmentSideOverlay(cameraInfo, renderData_.SegmentId, renderData_.FinalDirection);
- } else if (RoundaboutMassEdit.Instance.TraverseLoop(renderData_.SegmentId, out var segmentList)) {
- foreach (ushort segmentId in segmentList)
- RenderSegmentSideOverlay(cameraInfo, segmentId);
- } else {
- SegmentTraverser.Traverse(
- renderData_.SegmentId,
- SegmentTraverser.TraverseDirection.AnyDirection,
- SegmentTraverser.TraverseSide.AnySide,
- SegmentTraverser.SegmentStopCriterion.Junction,
- data => {
- NetInfo.Direction finalDirection = renderData_.FinalDirection;
- if (data.IsReversed(renderData_.SegmentId)) {
- finalDirection = NetInfo.InvertDirection(finalDirection);
- }
- RenderSegmentSideOverlay(cameraInfo, data.CurSeg.segmentId, finalDirection);
- return true;
- });
- }
- }
-
- public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) {
- if (renderData_.SegmentId == 0) {
- return;
- }
-
- if (ShowLimitsPerLane) {
- RenderLanes(cameraInfo);
- } else {
- RenderSegmentsSide(cameraInfo);
- }
- }
-
- public override void ShowGUIOverlay(ToolMode toolMode, bool viewOnly) {
- if (viewOnly && !Options.speedLimitsOverlay && !MassEditOverlay.IsActive) {
- return;
- }
-
- ShowSigns(viewOnly);
+ private void OnClickEditDefaultsButton(UIComponent component, UIMouseEventParameter evt) {
+ this.editDefaultsMode_ = !this.editDefaultsMode_;
+ MainTool.RequestOnscreenDisplayUpdate();
+ // TODO: update button active/texture
}
- public override void Cleanup() {
- segmentCenterByDir.Clear();
- CachedVisibleSegmentIds.Clear();
- currentInfoIndex = -1;
- currentSpeedLimit = new SpeedValue(-1f);
+ private void OnClickSegmentLaneModeButton(UIComponent component, UIMouseEventParameter evt) {
+ this.showLimitsPerLane_ = !this.showLimitsPerLane_;
+ MainTool.RequestOnscreenDisplayUpdate();
+ this.Window.SegmentLaneModeToggleButton.UpdateButtonImage();
}
- private void ShowSigns(bool viewOnly) {
- NetManager netManager = Singleton.instance;
- SpeedLimitManager speedLimitManager = SpeedLimitManager.Instance;
-
- var currentCamera = new CameraTransformValue(Camera.main);
- Transform currentCameraTransform = Camera.main.transform;
- Vector3 camPos = currentCameraTransform.position;
-
- if (!LastCachedCamera.Equals(currentCamera)) {
- // cache visible segments
- LastCachedCamera = currentCamera;
- CachedVisibleSegmentIds.Clear();
-
- for (uint segmentId = 1; segmentId < NetManager.MAX_SEGMENT_COUNT; ++segmentId) {
- if (!Constants.ServiceFactory.NetService.IsSegmentValid((ushort)segmentId)) {
- continue;
- }
-
- // if ((netManager.m_segments.m_buffer[segmentId].m_flags &
- // NetSegment.Flags.Untouchable) != NetSegment.Flags.None) continue;
- Vector3 distToCamera = netManager.m_segments.m_buffer[segmentId].m_bounds.center - camPos;
- if (distToCamera.sqrMagnitude > TrafficManagerTool.MAX_OVERLAY_DISTANCE_SQR) {
- continue; // do not draw if too distant
- }
-
- bool visible = GeometryUtil.WorldToScreenPoint(
- netManager.m_segments.m_buffer[segmentId].m_bounds.center,
- out Vector3 _);
-
- if (!visible) {
- continue;
- }
-
- if (!speedLimitManager.MayHaveCustomSpeedLimits(
- (ushort)segmentId,
- ref netManager.m_segments.m_buffer[segmentId])) {
- continue;
- }
-
- CachedVisibleSegmentIds.Add((ushort)segmentId);
- } // end for all segments
- }
-
- bool hover = false;
- for (int segmentIdIndex = CachedVisibleSegmentIds.Size - 1;
- segmentIdIndex >= 0;
- segmentIdIndex--) {
- ushort segmentId = CachedVisibleSegmentIds.Values[segmentIdIndex];
- // draw speed limits
- if ((MainTool.GetToolMode() == ToolMode.VehicleRestrictions) &&
- (segmentId == SelectedSegmentId)) {
- continue;
- }
-
- // no speed limit overlay on selected segment when in vehicle restrictions mode
- hover |= DrawSpeedLimitHandles(
- segmentId,
- ref netManager.m_segments.m_buffer[segmentId],
- viewOnly,
- ref camPos);
- }
- if (!hover) {
- renderData_.SegmentId = 0;
- }
+ public override void DeactivateTool() {
+ Object.Destroy(this.Window);
+ this.Window = null;
+ // this.fsm_ = null;
}
- ///
- /// The window for setting the defaullt speeds per road type
- ///
- ///
- private void GuiDefaultsWindow(int num) {
- List mainNetInfos = SpeedLimitManager.Instance.GetCustomizableNetInfos();
-
- if ((mainNetInfos == null) || (mainNetInfos.Count <= 0)) {
- Log._Debug($"mainNetInfos={mainNetInfos?.Count}");
- DragWindow(ref defaultsWindowRect);
- return;
- }
-
- bool updateRoadTex = false;
-
- if ((currentInfoIndex < 0) || (currentInfoIndex >= mainNetInfos.Count)) {
- currentInfoIndex = 0;
- updateRoadTex = true;
- Log._Debug($"set currentInfoIndex to 0");
- }
-
- NetInfo info = mainNetInfos[currentInfoIndex];
-
- if (updateRoadTex) {
- UpdateRoadTex(info);
- }
-
- if (currentSpeedLimit.GameUnits < 0f) {
- currentSpeedLimit = new SpeedValue(
- SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimit(info));
- Log._Debug($"set currentSpeedLimit to {currentSpeedLimit}");
- }
-
- // Log._Debug($"currentInfoIndex={currentInfoIndex} currentSpeedLimitIndex={currentSpeedLimitIndex}");
- // Road type label
- GUILayout.BeginVertical();
- GUILayout.Space(10);
- GUILayout.Label(Translation.SpeedLimits.Get("Defaults.Label:Road type") + ":");
- GUILayout.EndVertical();
-
- // switch between NetInfos
- GUILayout.BeginHorizontal();
-
- GUILayout.BeginVertical();
- GUILayout.FlexibleSpace();
-
- if (GUILayout.Button("←", GUILayout.Width(50))) {
- currentInfoIndex =
- ((currentInfoIndex + mainNetInfos.Count) - 1) % mainNetInfos.Count;
- info = mainNetInfos[currentInfoIndex];
- currentSpeedLimit = new SpeedValue(
- SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimit(info));
- UpdateRoadTex(info);
- }
-
- GUILayout.FlexibleSpace();
- GUILayout.EndVertical();
-
- GUILayout.FlexibleSpace();
- GUILayout.BeginVertical();
- GUILayout.FlexibleSpace();
-
- // NetInfo thumbnail
- GUILayout.Box(RoadTexture, GUILayout.Height(GUI_SPEED_SIGN_SIZE));
- GUILayout.FlexibleSpace();
-
- GUILayout.EndVertical();
- GUILayout.FlexibleSpace();
-
- GUILayout.BeginVertical();
- GUILayout.FlexibleSpace();
-
- if (GUILayout.Button("→", GUILayout.Width(50))) {
- currentInfoIndex = (currentInfoIndex + 1) % mainNetInfos.Count;
- info = mainNetInfos[currentInfoIndex];
- currentSpeedLimit = new SpeedValue(
- SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimit(info));
- UpdateRoadTex(info);
- }
-
- GUILayout.FlexibleSpace();
- GUILayout.EndVertical();
-
- GUILayout.EndHorizontal();
-
- var centeredTextStyle = new GUIStyle("label") { alignment = TextAnchor.MiddleCenter };
-
- // NetInfo name
- GUILayout.Label(info.name, centeredTextStyle);
-
- // Default speed limit label
- GUILayout.BeginVertical();
- GUILayout.Space(10);
- GUILayout.Label(Translation.SpeedLimits.Get("Label:Default speed limit") + ":");
- GUILayout.EndVertical();
-
- // switch between speed limits
- GUILayout.BeginHorizontal();
-
- GUILayout.BeginVertical();
- GUILayout.FlexibleSpace();
- if (GUILayout.Button("←", GUILayout.Width(50))) {
- // currentSpeedLimit = (currentSpeedLimitIndex +
- // SpeedLimitManager.Instance.AvailableSpeedLimits.Count - 1)
- // % SpeedLimitManager.Instance.AvailableSpeedLimits.Count;
- currentSpeedLimit = GetPrevious(currentSpeedLimit);
- }
-
- GUILayout.FlexibleSpace();
- GUILayout.EndVertical();
-
- GUILayout.FlexibleSpace();
-
- GUILayout.BeginVertical();
- GUILayout.FlexibleSpace();
-
- // speed limit sign
- GUILayout.Box(SpeedLimitTextures.GetSpeedLimitTexture(currentSpeedLimit),
- GUILayout.Width(GUI_SPEED_SIGN_SIZE),
- GUILayout.Height(GUI_SPEED_SIGN_SIZE));
- GUILayout.Label(GlobalConfig.Instance.Main.DisplaySpeedLimitsMph
- ? Translation.SpeedLimits.Get("Miles per hour")
- : Translation.SpeedLimits.Get("Kilometers per hour"));
-
- GUILayout.FlexibleSpace();
- GUILayout.EndVertical();
-
- GUILayout.FlexibleSpace();
-
- GUILayout.BeginVertical();
- GUILayout.FlexibleSpace();
-
- if (GUILayout.Button("→", GUILayout.Width(50))) {
- // currentSpeedLimitIndex = (currentSpeedLimitIndex + 1) %
- // SpeedLimitManager.Instance.AvailableSpeedLimits.Count;
- currentSpeedLimit = GetNext(currentSpeedLimit);
- }
-
- GUILayout.FlexibleSpace();
- GUILayout.EndVertical();
-
- GUILayout.EndHorizontal();
-
- // Save & Apply
- GUILayout.BeginVertical();
- GUILayout.Space(10);
-
- GUILayout.BeginHorizontal();
-
- // Close button. TODO: Make more visible or obey 'Esc' pressed or something
- GUILayout.FlexibleSpace();
-
- if (GUILayout.Button("X", GUILayout.Width(80))) {
- defaultsWindowVisible = false;
- }
-
- GUILayout.FlexibleSpace();
-
- if (GUILayout.Button(Translation.SpeedLimits.Get("Button:Save"),
- GUILayout.Width(70))) {
- SpeedLimitManager.Instance.FixCurrentSpeedLimits(info);
- SpeedLimitManager.Instance.SetCustomNetInfoSpeedLimit(info, currentSpeedLimit.GameUnits);
- }
-
- GUILayout.FlexibleSpace();
-
- if (GUILayout.Button(
- Translation.SpeedLimits.Get("Button:Save & Apply"),
- GUILayout.Width(160))) {
- SpeedLimitManager.Instance.SetCustomNetInfoSpeedLimit(info, currentSpeedLimit.GameUnits);
- SpeedLimitManager.Instance.ClearCurrentSpeedLimits(info);
- }
-
- GUILayout.FlexibleSpace();
- GUILayout.EndHorizontal();
+ /// Render overlay segments/lanes in non-GUI mode, as overlays.
+ public override void RenderActiveToolOverlay(RenderManager.CameraInfo cameraInfo) {
+ CreateOverlayDrawArgs(interactive: true);
- GUILayout.EndVertical();
-
- DragWindow(ref defaultsWindowRect);
+ // Draw hovered lanes or segments
+ overlay_.RenderHelperGraphics(cameraInfo: cameraInfo, args: this.overlayDrawArgs_);
}
- private void UpdateRoadTex(NetInfo info) {
- if (info != null) {
- if ((info.m_Atlas != null) && (info.m_Atlas.material != null) &&
- (info.m_Atlas.material.mainTexture != null) &&
- info.m_Atlas.material.mainTexture is Texture2D mainTex) {
- UITextureAtlas.SpriteInfo spriteInfo = info.m_Atlas[info.m_Thumbnail];
-
- if ((spriteInfo != null) && (spriteInfo.texture != null) &&
- (spriteInfo.texture.width > 0) && (spriteInfo.texture.height > 0)) {
- try {
- roadTexture = new Texture2D(
- spriteInfo.texture.width,
- spriteInfo.texture.height,
- TextureFormat.ARGB32,
- false);
+ /// Render overlay speed limit signs in GUI mode.
+ public override void RenderActiveToolOverlay_GUI() {
+ CreateOverlayDrawArgs(interactive: true);
- roadTexture.SetPixels(
- 0,
- 0,
- roadTexture.width,
- roadTexture.height,
- mainTex.GetPixels(
- (int)(spriteInfo.region.x * mainTex.width),
- (int)(spriteInfo.region.y * mainTex.height),
- (int)(spriteInfo.region.width * mainTex.width),
- (int)(spriteInfo.region.height * mainTex.height)));
-
- roadTexture.Apply();
- return;
- }
- catch (Exception e) {
- Log.Warning(
- $"Could not get texture from NetInfo {info.name}: {e.ToString()}");
- }
- }
- }
- }
-
- // fallback to "noimage" texture
- roadTexture = Textures.MainMenu.NoImage;
+ // Draw the clickable speed limit signs
+ overlay_.ShowSigns_GUI(args: this.overlayDrawArgs_);
}
- ///
- /// The window for selecting and applying a speed limit
- ///
- ///
- private void GuiSpeedLimitsWindow(int num) {
- GUILayout.BeginHorizontal();
- GUILayout.FlexibleSpace();
-
- Color oldColor = GUI.color;
- List allSpeedLimits = EnumerateSpeedLimits(SpeedUnit.CurrentlyConfigured);
- allSpeedLimits.Add(new SpeedValue(0)); // add last item: no limit
-
- bool showMph = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph;
- var column = 0u; // break palette to a new line at breakColumn
- int breakColumn = showMph ? BREAK_PALETTE_COLUMN_MPH : BREAK_PALETTE_COLUMN_KMPH;
-
- foreach (SpeedValue speedLimit in allSpeedLimits) {
- // Highlight palette item if it is very close to its float speed
- if (currentPaletteSpeedLimit != null &&
- FloatUtil.NearlyEqual(
- (float)currentPaletteSpeedLimit?.GameUnits,
- speedLimit.GameUnits)) {
- GUI.color = Color.gray;
- }
-
- GuiSpeedLimitsWindow_AddButton(showMph, speedLimit);
- GUI.color = oldColor;
-
- // TODO: This can be calculated from SpeedLimit MPH or KMPH limit constants
- column++;
- if (column % breakColumn == 0) {
- GUILayout.FlexibleSpace();
- GUILayout.EndHorizontal();
- GUILayout.BeginHorizontal();
- GUILayout.FlexibleSpace();
- }
- }
- {
- if (currentPaletteSpeedLimit == null) {
- GUI.color = Color.gray;
- }
- GuiSpeedLimitsWindow_AddClearButton();
- }
-
- GUILayout.FlexibleSpace();
- GUILayout.EndHorizontal();
-
- //---------------------
- // UI buttons row
- //---------------------
- GUILayout.BeginHorizontal();
- GUILayout.FlexibleSpace();
-
- if (GUILayout.Button(Translation.SpeedLimits.Get("Window.Title:Default speed limits"),
- GUILayout.Width(200))) {
- TrafficManagerTool.ShowAdvisor(this.GetType().Name + "_Defaults");
- defaultsWindowVisible = true;
- }
-
- GUILayout.FlexibleSpace();
-
- bool multiSegmentModeToggled = MultiSegmentMode != GUILayout.Toggle(
- MultiSegmentMode,
- Translation.SpeedLimits.Get("Checkbox:Apply to entire road") + " [shift]");
- if (multiSegmentModeToggled) {
- multiSegmentMode_ = !multiSegmentMode_;
- }
-
- GUILayout.FlexibleSpace();
- GUILayout.EndHorizontal();
-
- //---------------------
- // Checkboxes row
- //---------------------
- GUILayout.BeginHorizontal();
- GUILayout.FlexibleSpace();
-
- bool limitsPerLaneToggled = ShowLimitsPerLane != GUILayout.Toggle(
- ShowLimitsPerLane,
- Translation.SpeedLimits.Get("Checkbox:Show lane-wise speed limits") + " [ctrl]");
- if (limitsPerLaneToggled) {
- showLimitsPerLane_ = !showLimitsPerLane_;
- }
-
- GUILayout.FlexibleSpace();
-
- // Display MPH checkbox, if ticked will save global config
- bool displayMph = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph;
- displayMph = GUILayout.Toggle(
- displayMph,
- Translation.SpeedLimits.Get("Checkbox:Display speed limits mph"));
-
- if (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph != displayMph) {
- OptionsGeneralTab.SetDisplayInMph(displayMph);
- }
+ /// Copies important values for rendering the overlay into its args struct.
+ /// True if icons will be clickable.
+ private void CreateOverlayDrawArgs(bool interactive) {
+ overlayDrawArgs_.ClearHovered();
- GUILayout.FlexibleSpace();
- GUILayout.EndHorizontal();
-
- DragWindow(ref paletteWindowRect);
+ overlayDrawArgs_.InteractiveSigns = interactive;
+ overlayDrawArgs_.MultiSegmentMode = this.GetMultiSegmentMode();
+ overlayDrawArgs_.ShowLimitsPerLane = this.GetShowLimitsPerLane();
+ overlayDrawArgs_.ShowDefaultsMode = this.editDefaultsMode_;
+ overlayDrawArgs_.ShowOtherPerLaneModeTemporary = interactive && Shortcuts.AltIsPressed;
}
- /// Helper to create speed limit sign + label below converted to the opposite unit
- /// Config value from GlobalConfig.I.M.ShowMPH
- /// The float speed to show
- private void GuiSpeedLimitsWindow_AddButton(bool showMph, SpeedValue speedLimit) {
- // The button is wrapped in vertical sub-layout and a label for MPH/KMPH is added
- GUILayout.BeginVertical();
-
- GUILayout.BeginHorizontal();
- GUILayout.FlexibleSpace();
- float signSize = GUI_SPEED_SIGN_SIZE;
- if (GUILayout.Button(
- SpeedLimitTextures.GetSpeedLimitTexture(speedLimit),
- GUILayout.Width(signSize),
- GUILayout.Height(signSize * GetVerticalTextureScale()))) {
- currentPaletteSpeedLimit = speedLimit;
- }
-
- GUILayout.FlexibleSpace();
- GUILayout.EndHorizontal();
-
- // For MPH setting display KM/H below, for KM/H setting display MPH
- GUILayout.BeginHorizontal();
- GUILayout.FlexibleSpace();
- GUILayout.Label(
- showMph
- ? ToKmphPreciseString(speedLimit)
- : ToMphPreciseString(speedLimit));
- GUILayout.FlexibleSpace();
- GUILayout.EndHorizontal();
-
- GUILayout.EndVertical();
+ /// Render overlay for other tool modes, if speed limits overlay is on.
+ /// The camera.
+ public override void RenderGenericInfoOverlay(RenderManager.CameraInfo cameraInfo) {
+ // No non-GUI overlays for other tools, we draw signs in the *_GUI variant
}
- private void GuiSpeedLimitsWindow_AddClearButton() {
- GUILayout.BeginVertical();
-
- GUILayout.BeginHorizontal();
- GUILayout.FlexibleSpace();
- float signSize = TrafficManagerTool.AdaptWidth(GUI_SPEED_SIGN_SIZE);
- if (GUILayout.Button(
- SpeedLimitTextures.Clear,
- GUILayout.Width(signSize),
- GUILayout.Height(signSize * GetVerticalTextureScale()))) {
- currentPaletteSpeedLimit = null;
+ /// Called in the GUI mode for GUI.DrawTexture.
+ /// The camera.
+ public override void RenderGenericInfoOverlay_GUI() {
+ if (!Options.speedLimitsOverlay && !MassEditOverlay.IsActive) {
+ return;
}
- GUILayout.FlexibleSpace();
- GUILayout.EndHorizontal();
+ CreateOverlayDrawArgs(interactive: false);
- // For MPH setting display KM/H below, for KM/H setting display MPH
- GUILayout.BeginHorizontal();
- GUILayout.FlexibleSpace();
- GUILayout.Label(Translation.SpeedLimits.Get("Button:Default") + " [del]");
-
- GUILayout.FlexibleSpace();
- GUILayout.EndHorizontal();
-
- GUILayout.EndVertical();
+ // Draw the NON-clickable speed limit signs
+ overlay_.ShowSigns_GUI(args: this.overlayDrawArgs_);
}
- private bool DrawSpeedLimitHandles(ushort segmentId,
- ref NetSegment segment,
- bool viewOnly,
- ref Vector3 camPos) {
- if (viewOnly && !Options.speedLimitsOverlay && !MassEditOverlay.IsActive) {
- return false;
- }
- bool ret = false;
-
- Vector3 center = segment.m_bounds.center;
- NetManager netManager = Singleton.instance;
- SpeedValue? speedLimitToSet = viewOnly
- ? new SpeedValue(-1f)
- : currentPaletteSpeedLimit;
-
- // US signs are rectangular, all other are round
- float speedLimitSignVerticalScale = GetVerticalTextureScale();
-
- bool showPerLane = viewOnly ? showLimitsPerLane_ : ShowLimitsPerLane;
- if (showPerLane) {
- // show individual speed limit handle per lane
- int numLanes = GeometryUtil.GetSegmentNumVehicleLanes(
- segmentId,
- null,
- out int numDirections,
- SpeedLimitManager.VEHICLE_TYPES);
-
- NetInfo segmentInfo = segment.Info;
- Vector3 yu = (segment.m_endDirection - segment.m_startDirection).normalized;
- Vector3 xu = Vector3.Cross(yu, new Vector3(0, 1f, 0)).normalized;
-
- // if ((segment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None) {
- // xu = -xu; }
- float f = viewOnly ? 4f : 7f; // reserved sign size in game coordinates
- Vector3 zero = center - (0.5f * (((numLanes - 1) + numDirections) - 1) * f * xu);
-
- uint x = 0;
- IList sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes(
- segmentId,
- ref segment,
- null,
- SpeedLimitManager.LANE_TYPES,
- SpeedLimitManager.VEHICLE_TYPES);
- bool onlyMonorailLanes = sortedLanes.Count > 0;
-
- if (!viewOnly) {
- foreach (LanePos laneData in sortedLanes) {
- byte laneIndex = laneData.laneIndex;
- NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex];
-
- if ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Monorail) ==
- VehicleInfo.VehicleType.None) {
- onlyMonorailLanes = false;
- break;
- }
- }
- }
-
- var directions = new HashSet();
- int sortedLaneIndex = -1;
-
- foreach (LanePos laneData in sortedLanes) {
- ++sortedLaneIndex;
- uint laneId = laneData.laneId;
- byte laneIndex = laneData.laneIndex;
-
- NetInfo.Lane laneInfo = segmentInfo.m_lanes[laneIndex];
- if (!directions.Contains(laneInfo.m_finalDirection)) {
- if (directions.Count > 0) {
- ++x; // space between different directions
- }
-
- directions.Add(laneInfo.m_finalDirection);
- }
-
- SpeedValue laneSpeedLimit = new SpeedValue(
- SpeedLimitManager.Instance.GetCustomSpeedLimit(laneId));
-
- bool hoveredHandle = MainTool.DrawGenericOverlayGridTexture(
- SpeedLimitTextures.GetSpeedLimitTexture(laneSpeedLimit),
- camPos,
- zero,
- f,
- f,
- xu,
- yu,
- x,
- 0,
- speedLimitSignSize,
- speedLimitSignSize * speedLimitSignVerticalScale,
- !viewOnly);
-
- if (!viewOnly
- && !onlyMonorailLanes
- && ((laneInfo.m_vehicleType & VehicleInfo.VehicleType.Monorail) !=
- VehicleInfo.VehicleType.None)) {
- Texture2D tex1 = RoadUI.VehicleInfoSignTextures[
- LegacyExtVehicleType.ToNew(ExtVehicleType.PassengerTrain)];
- MainTool.DrawStaticSquareOverlayGridTexture(
- tex1,
- camPos,
- zero,
- f,
- xu,
- yu,
- x,
- 1,
- speedLimitSignSize);
- }
-
- if (hoveredHandle) {
- renderData_.SegmentId = segmentId;
- renderData_.LaneId = laneId;
- renderData_.LaneIndex = laneIndex;
- renderData_.LaneInfo = laneInfo;
- renderData_.SortedLaneIndex = sortedLaneIndex;
- ret = true;
- }
- if (hoveredHandle && Input.GetMouseButtonDown(0) && !IsCursorInPanel()) {
- SpeedLimitManager.Instance.SetSpeedLimit(
- segmentId,
- laneIndex,
- laneInfo,
- laneId,
- speedLimitToSet?.GameUnits);
-
- if (MultiSegmentMode) {
- if (new RoundaboutMassEdit().TraverseLoop(segmentId, out var segmentList)) {
- var lanes = FollowRoundaboutLane(segmentList, segmentId, sortedLaneIndex);
- foreach (var lane in lanes) {
- if (lane.laneId == laneId) // the speed limit for this lane has already been set.
- continue;
- SetSpeedLimit(lane, speedLimitToSet);
- }
- } else {
- int slIndexCopy = sortedLaneIndex;
- SegmentLaneTraverser.Traverse(
- segmentId,
- SegmentTraverser.TraverseDirection.AnyDirection,
- SegmentTraverser.TraverseSide.AnySide,
- SegmentLaneTraverser.LaneStopCriterion.LaneCount,
- SegmentTraverser.SegmentStopCriterion.Junction,
- SpeedLimitManager.LANE_TYPES,
- SpeedLimitManager.VEHICLE_TYPES,
- data => {
- if (data.SegVisitData.Initial) {
- return true;
- }
-
- if (slIndexCopy != data.SortedLaneIndex) {
- return true;
- }
-
- Constants.ServiceFactory.NetService.ProcessSegment(
- data.SegVisitData.CurSeg.segmentId,
- (ushort curSegmentId, ref NetSegment curSegment) => {
- NetInfo.Lane curLaneInfo = curSegment.Info.m_lanes[
- data.CurLanePos.laneIndex];
-
- SpeedLimitManager.Instance.SetSpeedLimit(
- curSegmentId,
- data.CurLanePos.laneIndex,
- curLaneInfo,
- data.CurLanePos.laneId,
- speedLimitToSet?.GameUnits);
- return true;
- });
-
- return true;
- });
- }
- }
- }
-
- ++x;
+ ///
+ public override void OnToolLeftClick() {
+ if (this.Window.containsMouse) {
+ return; // no click in the window
+ }
+
+ // Go through recently rendered overlay speedlimit handles, which had mouse over them
+ // Hovering multiple speed limits handles at once should set limits on multiple roads
+ if (this.GetShowLimitsPerLane()) {
+ SetSpeedLimitTarget target = this.editDefaultsMode_
+ ? SetSpeedLimitTarget.LaneDefault
+ : SetSpeedLimitTarget.LaneOverride;
+
+ foreach (var h in overlayDrawArgs_.HoveredLaneHandles) {
+ // per lane
+ h.Click(
+ action: SetSpeedLimitAction.SetSpeed(this.CurrentPaletteSpeedLimit),
+ multiSegmentMode: this.GetMultiSegmentMode(),
+ target: target);
}
} else {
- // draw speedlimits over mean middle points of lane beziers
-
- if (!segmentCenterByDir.TryGetValue(
- segmentId,
- out Dictionary segCenter)) {
- segCenter = new Dictionary();
- segmentCenterByDir.Add(segmentId, segCenter);
- GeometryUtil.CalculateSegmentCenterByDir(
- segmentId,
- segCenter,
- speedLimitSignSize * TrafficManagerTool.MAX_ZOOM);
- }
-
- foreach (KeyValuePair e in segCenter) {
- bool visible = GeometryUtil.WorldToScreenPoint(e.Value, out Vector3 screenPos);
-
- if (!visible) {
- continue;
- }
-
- float zoom = (1.0f / (e.Value - camPos).magnitude) * 100f * MainTool.GetBaseZoom();
- float size = (viewOnly ? 0.8f : 1f) * speedLimitSignSize * zoom;
- Color guiColor = GUI.color;
- var boundingBox = new Rect(screenPos.x - (size / 2),
- screenPos.y - (size / 2),
- size,
- size * speedLimitSignVerticalScale);
- bool hoveredHandle = !viewOnly && TrafficManagerTool.IsMouseOver(boundingBox);
-
- guiColor.a = TrafficManagerTool.GetHandleAlpha(hoveredHandle);
-
-
- // Draw something right here, the road sign texture
- GUI.color = guiColor;
- SpeedValue displayLimit = new SpeedValue(
- SpeedLimitManager.Instance.GetCustomSpeedLimit(segmentId, e.Key));
- Texture2D tex = SpeedLimitTextures.GetSpeedLimitTexture(displayLimit);
-
- GUI.DrawTexture(boundingBox, tex);
-
- if (hoveredHandle) {
- renderData_.SegmentId = segmentId;
- renderData_.FinalDirection = e.Key;
- ret = true;
- }
- if (hoveredHandle && Input.GetMouseButtonDown(0) && !IsCursorInPanel()) {
- // change the speed limit to the selected one
- // Log._Debug($"Setting speed limit of segment {segmentId}, dir {e.Key.ToString()}
- // to {speedLimitToSet}");
- SpeedLimitManager.Instance.SetSpeedLimit(segmentId,
- e.Key,
- currentPaletteSpeedLimit?.GameUnits);
-
- if (MultiSegmentMode) {
- if (new RoundaboutMassEdit().TraverseLoop(segmentId, out var segmentList)) {
- foreach (ushort segId in segmentList) {
- SpeedLimitManager.Instance.SetSpeedLimit(
- segId,
- currentPaletteSpeedLimit?.GameUnits);
- }
- } else {
- NetInfo.Direction normDir = e.Key;
- if ((netManager.m_segments.m_buffer[segmentId].m_flags & NetSegment.Flags.Invert) != NetSegment.Flags.None) {
- normDir = NetInfo.InvertDirection(normDir);
- }
-
- SegmentLaneTraverser.Traverse(
- segmentId,
- SegmentTraverser.TraverseDirection.AnyDirection,
- SegmentTraverser.TraverseSide.AnySide,
- SegmentLaneTraverser.LaneStopCriterion.LaneCount,
- SegmentTraverser.SegmentStopCriterion.Junction,
- SpeedLimitManager.LANE_TYPES,
- SpeedLimitManager.VEHICLE_TYPES,
- data => {
- if (data.SegVisitData.Initial) {
- return true;
- }
-
- bool reverse =
- data.SegVisitData.ViaStartNode
- == data.SegVisitData.ViaInitialStartNode;
-
- ushort otherSegmentId = data.SegVisitData.CurSeg.segmentId;
- NetInfo otherSegmentInfo =
- netManager.m_segments.m_buffer[otherSegmentId].Info;
- byte laneIndex = data.CurLanePos.laneIndex;
- NetInfo.Lane laneInfo = otherSegmentInfo.m_lanes[laneIndex];
-
- NetInfo.Direction otherNormDir = laneInfo.m_finalDirection;
-
- if (((netManager.m_segments.m_buffer[otherSegmentId].m_flags
- & NetSegment.Flags.Invert)
- != NetSegment.Flags.None) ^ reverse) {
- otherNormDir = NetInfo.InvertDirection(otherNormDir);
- }
-
- if (otherNormDir == normDir) {
- SpeedLimitManager.Instance.SetSpeedLimit(
- otherSegmentId,
- laneInfo.m_finalDirection,
- speedLimitToSet?.GameUnits);
- }
-
- return true;
- });
- }
- }
- }
-
- guiColor.a = 1f;
- GUI.color = guiColor;
+ // per segment
+ SetSpeedLimitTarget target = this.editDefaultsMode_
+ ? SetSpeedLimitTarget.SegmentDefault
+ : SetSpeedLimitTarget.SegmentOverride;
+
+ foreach (var h in overlayDrawArgs_.HoveredSegmentHandles) {
+ h.Click(
+ action: SetSpeedLimitAction.SetSpeed(this.CurrentPaletteSpeedLimit),
+ multiSegmentMode: this.GetMultiSegmentMode(),
+ target: target);
}
}
- return ret;
- }
- public static string ToMphPreciseString(SpeedValue speed) {
- return FloatUtil.IsZero(speed.GameUnits)
- ? Translation.SpeedLimits.Get("Unlimited")
- : speed.ToMphPrecise().ToString();
+ this.overlayDrawArgs_.ClearHovered();
}
- public static string ToKmphPreciseString(SpeedValue speed) {
- return FloatUtil.IsZero(speed.GameUnits)
- ? Translation.SpeedLimits.Get("Unlimited")
- : speed.ToKmphPrecise().ToString();
+ ///
+ public override void OnToolRightClick() {
}
- ///
- /// Produces list of speed limits to offer user in the palette
- ///
- /// What kind of speed limit list is required
- /// List from smallest to largest speed with the given unit. Zero (no limit) is not added to the list.
- /// The values are in-game speeds as float.
- public static List EnumerateSpeedLimits(SpeedUnit unit) {
- var result = new List();
- switch (unit) {
- case SpeedUnit.Kmph:
- for (var km = LOWER_KMPH; km <= UPPER_KMPH; km += KMPH_STEP) {
- result.Add(SpeedValue.FromKmph(km));
- }
-
- break;
- case SpeedUnit.Mph:
- for (var mi = LOWER_MPH; mi <= UPPER_MPH; mi += MPH_STEP) {
- result.Add(SpeedValue.FromMph(mi));
- }
-
- break;
- case SpeedUnit.CurrentlyConfigured:
- // Automatically choose from the config
- return GlobalConfig.Instance.Main.DisplaySpeedLimitsMph
- ? EnumerateSpeedLimits(SpeedUnit.Mph)
- : EnumerateSpeedLimits(SpeedUnit.Kmph);
- }
-
- return result;
+ ///
+ public override void UpdateEveryFrame() {
}
- ///
- /// Based on the MPH/KMPH settings round the current speed to the nearest STEP and
- /// then decrease by STEP.
- ///
- /// Ingame speed
- /// Ingame speed decreased by the increment for MPH or KMPH
- public static SpeedValue GetPrevious(SpeedValue speed) {
- if (speed.GameUnits < 0f) {
- return new SpeedValue(-1f);
- }
-
- if (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph) {
- MphValue rounded = speed.ToMphRounded(MPH_STEP);
- if (rounded.Mph == LOWER_MPH) {
- return new SpeedValue(0);
- }
-
- if (rounded.Mph == 0) {
- return SpeedValue.FromMph(UPPER_MPH);
- }
-
- return SpeedValue.FromMph(rounded.Mph > LOWER_MPH
- ? (ushort)(rounded.Mph - MPH_STEP)
- : LOWER_MPH);
- } else {
- KmphValue rounded = speed.ToKmphRounded(KMPH_STEP);
- if (rounded.Kmph == LOWER_KMPH) {
- return new SpeedValue(0);
- }
-
- if (rounded.Kmph == 0) {
- return SpeedValue.FromKmph(UPPER_KMPH);
- }
-
- return SpeedValue.FromKmph(rounded.Kmph > LOWER_KMPH
- ? (ushort)(rounded.Kmph - KMPH_STEP)
- : LOWER_KMPH);
- }
+ /// Called when the tool must update onscreen keyboard/mouse hints.
+ public void UpdateOnscreenDisplayPanel() {
+ // t: "Hold [Alt] to see default speed limits temporarily",
+ // t: "Hold [Ctrl] to see per lane limits temporarily",
+ // t: "Hold [Shift] to modify entire road between two junctions",
+ string toggleDefaultStr =
+ this.editDefaultsMode_
+ ? T("SpeedLimits.Alt:See speed limits overrides temporarily")
+ : T("SpeedLimits.Alt:See default speed limits temporarily");
+ string togglePerLaneStr =
+ this.GetShowLimitsPerLane()
+ ? T("SpeedLimits.Ctrl:See speed limits per segment temporarily")
+ : T("SpeedLimits.Ctrl:See speed limits per lane temporarily");
+ var items = new List {
+ new MainMenu.OSD.ModeDescription(localizedText: T("SpeedLimits.OSD:Select")),
+ new MainMenu.OSD.HoldModifier(
+ alt: true,
+ localizedText: toggleDefaultStr),
+ new MainMenu.OSD.HoldModifier(
+ ctrl: true,
+ localizedText: togglePerLaneStr),
+ new MainMenu.OSD.HoldModifier(
+ shift: true,
+ localizedText: T("SpeedLimits.Shift:Modify road between two junctions")),
+ };
+ OnscreenDisplay.Display(items: items);
}
- ///
- /// Based on the MPH/KMPH settings round the current speed to the nearest STEP and
- /// then increase by STEP.
- ///
- /// Ingame speed
- /// Ingame speed increased by the increment for MPH or KMPH
- public static SpeedValue GetNext(SpeedValue speed) {
- if (speed.GameUnits < 0f) {
- return new SpeedValue(-1f);
- }
-
- if (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph) {
- MphValue rounded = speed.ToMphRounded(MPH_STEP);
- rounded += MPH_STEP;
-
- if (rounded.Mph > UPPER_MPH) {
- rounded = new MphValue(0);
- }
-
- return SpeedValue.FromMph(rounded);
- } else {
- KmphValue rounded = speed.ToKmphRounded(KMPH_STEP);
- rounded += KMPH_STEP;
-
- if (rounded.Kmph > UPPER_KMPH) {
- rounded = new KmphValue(0);
- }
-
- return SpeedValue.FromKmph(rounded);
- }
+ internal static void SetSpeedLimit(LanePos lane, SetSpeedLimitAction action) {
+ ushort segmentId = lane.laneId.ToLane().m_segment;
+ SpeedLimitManager.Instance.SetLaneSpeedLimit(
+ segmentId: segmentId,
+ laneIndex: lane.laneIndex,
+ laneInfo: segmentId.ToSegment().Info.m_lanes[lane.laneIndex],
+ laneId: lane.laneId,
+ action: action);
}
- ///
- /// For US signs and MPH enabled, scale textures vertically by 1.25f.
- /// Other signs are round.
- ///
- /// Multiplier for horizontal sign size
- public static float GetVerticalTextureScale() {
- return (GlobalConfig.Instance.Main.DisplaySpeedLimitsMph &&
- (GlobalConfig.Instance.Main.MphRoadSignStyle == MphSignStyle.SquareUS))
- ? 1.25f
- : 1.0f;
+ /// When speed palette button clicked, touch all buttons forcing them to refresh.
+ public void OnPaletteButtonClicked(SpeedValue speed) {
+ this.CurrentPaletteSpeedLimit = speed;
+
+ // Deactivate all palette buttons and highlight one
+ Window.UpdatePaletteButtonsOnClick();
}
} // end class
}
diff --git a/TLM/TLM/UI/SubTools/SpeedLimits/SpeedLimitsWindow.cs b/TLM/TLM/UI/SubTools/SpeedLimits/SpeedLimitsWindow.cs
new file mode 100644
index 000000000..f74598766
--- /dev/null
+++ b/TLM/TLM/UI/SubTools/SpeedLimits/SpeedLimitsWindow.cs
@@ -0,0 +1,329 @@
+namespace TrafficManager.UI.SubTools.SpeedLimits {
+ using System.Collections.Generic;
+ using ColossalFramework.UI;
+ using CSUtil.Commons;
+ using TrafficManager.API.Traffic.Data;
+ using TrafficManager.State;
+ using TrafficManager.U;
+ using TrafficManager.U.Autosize;
+ using TrafficManager.Util;
+ using UnityEngine;
+
+ /// Implements U window for Speed Limits palette and speed defaults.
+ internal class SpeedLimitsWindow : U.Panel.BaseUWindowPanel {
+ private const string GAMEOBJECT_NAME = "TMPE_SpeedLimits";
+
+ /// Stores window title label also overlaid with the drag handle.
+ private ULabel titleLabel_;
+
+ /// Window drag handle.
+ private UIDragHandle dragHandle_;
+
+ /// UI button which toggles per-segment or per-lane speed limits.
+ public UButton SegmentLaneModeToggleButton { get; set; }
+
+ private UITextureAtlas guiAtlas_;
+ private List paletteButtons_ = new List();
+
+ public UButton EditDefaultsModeButton { get; set; }
+ public MphToggleButton ToggleMphButton { get; set; }
+
+ /// Called by Unity on instantiation once when the game is running.
+ public override void Start() {
+ base.Start();
+ UIUtil.MakeUniqueAndSetName(gameObject, GAMEOBJECT_NAME);
+ this.GenericBackgroundAndOpacity();
+ this.position = new Vector3(
+ GlobalConfig.Instance.Main.SpeedLimitsWindowX,
+ GlobalConfig.Instance.Main.SpeedLimitsWindowY);
+ this.ClampToScreen();
+ }
+
+ /// Populate the window using UIBuilder of the window panel.
+ /// The root builder of this window.
+ public void SetupControls(UiBuilder builder, SpeedLimitsTool parentTool) {
+ // design variant 1
+ //
+ // Speed Limits - Kilometers per Hour
+ // [ Lane/Segment ] [ 10 20 30 40 50 ... 120 130 140 ]
+ // [ Edit Default ] | | | | | | | | | |
+ // [_MPH/KM_______] [________________________________]
+ //
+ // design variant 2
+ //
+ // Speed Limits - Kilometers per Hour
+ // [Lane/Segment] [Edit Defaults] [MPH/KM]
+ // [ 10 20 30 40 50 ... 120 130 140 ]
+ // | | | | | | | | | |
+ // [________________________________]
+ //
+ // Goes first on top of the window
+ SetupControls_TitleBar(builder);
+
+ // Vertical panel goes under the titlebar
+ SetupControls_ModeButtons(builder);
+
+ // Goes right of the modebuttons panel
+ SetupControls_SpeedPalette(builder, parentTool);
+
+ // Text below for "Hold Alt, hold Shift, etc..."
+ // SetupControls_InfoRow(builder);
+
+ // Force buttons resize and show the current speed limit on the palette
+ this.UpdatePaletteButtonsOnClick();
+ }
+
+ /// Creates a draggable label with current unit (mph or km/h).
+ /// The UI builder to use.
+ private void SetupControls_TitleBar(UiBuilder builder) {
+ string unitTitle = string.Format(
+ format: "{0} - {1}",
+ Translation.SpeedLimits.Get("Window.Title:Speed Limits"),
+ GlobalConfig.Instance.Main.DisplaySpeedLimitsMph
+ ? Translation.SpeedLimits.Get("Miles per hour")
+ : Translation.SpeedLimits.Get("Kilometers per hour"));
+
+ // The label will be repositioned to the top of the parent
+ this.titleLabel_ = builder.Label(t: unitTitle, stack: UStackMode.Below);
+ this.dragHandle_ = this.CreateDragHandle();
+
+ // On window drag - clamp to screen and then save in the config
+ this.eventPositionChanged += (UIComponent component, Vector2 value) => {
+ this.ClampToScreen();
+ GlobalConfig.Instance.Main.SpeedLimitsWindowX = (int)value.x;
+ GlobalConfig.Instance.Main.SpeedLimitsWindowY = (int)value.y;
+ };
+ }
+
+ ///
+ public override void OnBeforeResizerUpdate() {
+ if (this.dragHandle_ != null) {
+ // Drag handle is manually resized to the label width in OnAfterResizerUpdate.
+ this.dragHandle_.size = Vector2.one;
+ }
+ }
+
+ /// Called by UResizer for every control to be 'resized'.
+ public override void OnAfterResizerUpdate() {
+ if (this.dragHandle_ != null) {
+ this.dragHandle_.size = this.titleLabel_.size;
+
+ // Push the window back into screen if the label/draghandle are partially offscreen
+ UIUtil.ClampToScreen(
+ window: this,
+ alwaysVisible: titleLabel_);
+ }
+ }
+
+ /// Create mode buttons panel on the left side.
+ /// The UI builder to use.
+ private void SetupControls_ModeButtons(UiBuilder builder) {
+ void ButtonpanelSetupFn(UPanel p) => p.name = GAMEOBJECT_NAME + "_ModesPanel";
+
+ using (var modePanelB = builder.ChildPanel(ButtonpanelSetupFn)) {
+ void ButtonpanelResizeFn(UResizer r) {
+ r.Stack(mode: UStackMode.Below, stackRef: this.titleLabel_);
+ r.FitToChildren();
+ }
+
+ modePanelB.ResizeFunction(ButtonpanelResizeFn);
+
+ Vector2 buttonSize = new Vector2(50f, 50f);
+
+ //----------------
+ // Edit Segments/Lanes mode button
+ //----------------
+ using (var b = modePanelB.FixedSizeButton(
+ text: string.Empty,
+ tooltip: "Edit segments. Click to edit lanes.",
+ size: buttonSize,
+ stack: UStackMode.Below))
+ {
+ this.SegmentLaneModeToggleButton = b.Control;
+ b.Control.atlas = GetUiAtlas();
+ b.Control.Skin = ButtonSkin.CreateDefaultNoBackground("EditSegments");
+ }
+
+ //----------------
+ // Edit Defaults mode button
+ //----------------
+ using (var defaultsB = modePanelB.FixedSizeButton(
+ text: string.Empty,
+ tooltip: "Default speed limits per road type",
+ size: buttonSize,
+ stack: UStackMode.Below)) {
+ this.EditDefaultsModeButton = defaultsB.Control;
+ }
+
+ //----------------
+ // MPH/Kmph switch
+ //----------------
+ bool displayMph = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph;
+ using (var b = modePanelB.FixedSizeButton(
+ text: string.Empty,
+ tooltip: displayMph
+ ? Translation.SpeedLimits.Get("Miles per hour")
+ : Translation.SpeedLimits.Get("Kilometers per hour"),
+ size: buttonSize,
+ stack: UStackMode.Below))
+ {
+ b.Control.atlas = GetUiAtlas();
+ b.Control.Skin = ButtonSkin.CreateDefaultNoBackground("MphToggle");
+ this.ToggleMphButton = b.Control;
+ }
+ }
+ }
+
+ /// Create speeds palette based on the current options choices.
+ /// The UI builder to use.
+ private void SetupControls_SpeedPalette(UiBuilder builder, SpeedLimitsTool parentTool) {
+ void PaletteSetupFn(UPanel p) => p.name = GAMEOBJECT_NAME + "_PalettePanel";
+ using (var palettePanelB = builder.ChildPanel(PaletteSetupFn)) {
+ palettePanelB.SetPadding(UConst.UIPADDING);
+
+ void PaletteResizeFn(UResizer r) {
+ r.Stack(mode: UStackMode.ToTheRight);
+ r.FitToChildren();
+ }
+
+ palettePanelB.ResizeFunction(PaletteResizeFn);
+ bool showMph = GlobalConfig.Instance.Main.DisplaySpeedLimitsMph;
+
+ // Fill with buttons
+ // [ 10 20 30 ... 120 130 140 0(no limit) ]
+ //-----------------------------------------
+ // the Current Selected Speed is highlighted
+ List values =
+ PaletteGenerator.AllSpeedLimits(SpeedUnit.CurrentlyConfigured);
+ values.Add(new SpeedValue(0)); // add last item: no limit
+
+ this.paletteButtons_.Clear();
+
+ foreach (var speedValue in values) {
+ SpeedLimitPaletteButton nextButton = SetupControls_SpeedPalette_Button(
+ parentTool: parentTool,
+ builder: palettePanelB,
+ showMph: showMph,
+ speedValue: speedValue);
+ this.paletteButtons_.Add(nextButton);
+ }
+ } // end palette panel
+ }
+
+ /// Format string to display under the speed limit button with miles per hour.
+ /// The speed.
+ /// The string formatted with miles: MM MPH.
+ private static string ToMphPreciseString(SpeedValue speed) {
+ return FloatUtil.IsZero(speed.GameUnits)
+ ? Translation.SpeedLimits.Get("Unlimited")
+ : speed.ToMphPrecise().ToString();
+ }
+
+ /// Format string to display under the speed limit button with km/hour.
+ /// The speed.
+ /// The string formatted formatted with km: NN km/h.
+ private static string ToKmphPreciseString(SpeedValue speed) {
+ return FloatUtil.IsZero(speed.GameUnits)
+ ? Translation.SpeedLimits.Get("Unlimited")
+ : speed.ToKmphPrecise().ToIntegerString();
+ }
+
+ private SpeedLimitPaletteButton
+ SetupControls_SpeedPalette_Button(UiBuilder builder,
+ bool showMph,
+ SpeedValue speedValue,
+ SpeedLimitsTool parentTool)
+ {
+ int speedInteger = showMph
+ ? speedValue.ToMphRounded(SpeedLimitsTool.MPH_STEP).Mph
+ : speedValue.ToKmphRounded(SpeedLimitsTool.KMPH_STEP).Kmph;
+ SpeedLimitPaletteButton control;
+
+ //--- uncomment below to create a label under each button ---
+ // Create vertical combo:
+ // [ 100 ]
+ // 65 mph
+ // Create a small panel which stacks together with other button panels horizontally
+ using (var buttonPanelB = builder.ChildPanel(
+ p => p.name = $"{GAMEOBJECT_NAME}_Button_{speedInteger}")) {
+ buttonPanelB.ResizeFunction(
+ r => {
+ r.Stack(UStackMode.ToTheRight);
+ r.FitToChildren();
+ });
+
+ using (var buttonB = buttonPanelB.Button()) {
+ control = buttonB.Control;
+ control.text = speedInteger == 0 ? "X" : speedInteger.ToString();
+ control.textHorizontalAlignment = UIHorizontalAlignment.Center;
+
+ control.AssignedValue = speedValue; // button must know its speed value
+ // The click events will be routed via the parent tool OnPaletteButtonClicked
+ control.ParentTool = parentTool;
+
+ buttonB.SetStacking(UStackMode.NewRowBelow);
+
+ // Width will be overwritten in SpeedLimitPaletteButton.UpdateSpeedLimitButton
+ buttonB.SetFixedSize(
+ new Vector2(SpeedLimitPaletteButton.DEFAULT_WIDTH,
+ SpeedLimitPaletteButton.DEFAULT_HEIGHT));
+ }
+
+ //--- uncomment below to create a label under each button ---
+ // Other speed unit info label
+ string otherUnit = showMph
+ ? ToKmphPreciseString(speedValue)
+ : ToMphPreciseString(speedValue);
+
+ ULabel label =
+ control.AltUnitsLabel =
+ buttonPanelB.Label(t: otherUnit, stack: UStackMode.Below);
+
+ label.width = SpeedLimitPaletteButton.DEFAULT_WIDTH * 1.5f;
+ label.textAlignment = UIHorizontalAlignment.Center;
+ label.GetResizerConfig().ContributeToBoundingBox = false; // parent ignore our width
+ } // end containing mini panel
+
+ return control;
+ }
+
+ private UITextureAtlas GetUiAtlas() {
+ if (guiAtlas_ != null) {
+ return guiAtlas_;
+ }
+
+ // Create base atlas with backgrounds and no foregrounds
+ var futureAtlas = new U.AtlasBuilder();
+
+ // Merge names of all foreground sprites for 3 directions into atlasKeySet
+ foreach (string prefix in new[] { "MphToggle", "EditSegments", }) {
+ ButtonSkin skin = ButtonSkin.CreateDefaultNoBackground(prefix);
+
+ // Create keysets for lane arrow button icons and merge to the shared atlas
+ skin.UpdateAtlasBuilder(
+ atlasBuilder: futureAtlas,
+ spriteSize: new IntVector2(50));
+ }
+
+ // Load actual graphics into an atlas
+ return futureAtlas.CreateAtlas(
+ atlasName: "SpeedLimits_Atlas",
+ loadingPath: "SpeedLimits",
+ atlasSizeHint: new IntVector2(512));
+ }
+
+ ///
+ /// Forces speedlimit palette buttons to be updated, and active button also is highlighted.
+ ///
+ public void UpdatePaletteButtonsOnClick() {
+ foreach (SpeedLimitPaletteButton b in this.paletteButtons_) {
+ b.UpdateButtonImage();
+ b.UpdateSpeedlimitButton();
+ }
+
+ UResizer.UpdateControl(this); // force window relayout
+ }
+ }
+
+ // end class
+}
\ No newline at end of file
diff --git a/TLM/TLM/UI/SubTools/TimedTrafficLights/TimedTrafficLightsTool.cs b/TLM/TLM/UI/SubTools/TimedTrafficLights/TimedTrafficLightsTool.cs
index ce53257a8..1a7046513 100644
--- a/TLM/TLM/UI/SubTools/TimedTrafficLights/TimedTrafficLightsTool.cs
+++ b/TLM/TLM/UI/SubTools/TimedTrafficLights/TimedTrafficLightsTool.cs
@@ -333,7 +333,7 @@ public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) {
&& Cursor.visible
&& Flags.MayHaveTrafficLight(HoveredNodeId))
{
- MainTool.DrawNodeCircle(cameraInfo, HoveredNodeId);
+ Highlight.DrawNodeCircle(cameraInfo, HoveredNodeId);
}
if (selectedNodeIds.Count <= 0) {
@@ -341,7 +341,7 @@ public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) {
}
foreach (ushort index in selectedNodeIds) {
- MainTool.DrawNodeCircle(cameraInfo, index, true);
+ Highlight.DrawNodeCircle(cameraInfo, index, true);
}
}
@@ -1407,7 +1407,12 @@ public override void ShowGUIOverlay(ToolMode toolMode, bool viewOnly) {
? TrafficLightTextures.ClockTest
: TrafficLightTextures.ClockPlay)
: TrafficLightTextures.ClockPause;
- MainTool.DrawGenericSquareOverlayTexture(tex, camPos, nodePos, 120f, false);
+ Highlight.DrawGenericSquareOverlayTexture(
+ texture: tex,
+ camPos: camPos,
+ worldPos: nodePos,
+ size: 120f,
+ canHover: false);
}
}
}
@@ -1437,7 +1442,7 @@ private void ShowGUI() {
}
Vector3 diff = nodePos - Camera.main.transform.position;
- float zoom = 1.0f / diff.magnitude * 100f * MainTool.GetBaseZoom();
+ float zoom = 1.0f / diff.magnitude * 100f * U.UIScaler.GetScale();
for (int i = 0; i < 8; ++i) {
ushort srcSegmentId = 0;
@@ -2524,9 +2529,7 @@ public void UpdateOnscreenDisplayPanel() {
items.Add(
new UI.MainMenu.OSD.HardcodedMouseShortcut(
button: UIMouseButton.Left,
- shift: false,
ctrl: true,
- alt: false,
localizedText: T("TimedTL.CtrlClick:Quick setup")));
OnscreenDisplay.Display(items: items);
}
diff --git a/TLM/TLM/UI/SubTools/ToggleTrafficLightsTool.cs b/TLM/TLM/UI/SubTools/ToggleTrafficLightsTool.cs
index a5e92d3ab..32d2444e4 100644
--- a/TLM/TLM/UI/SubTools/ToggleTrafficLightsTool.cs
+++ b/TLM/TLM/UI/SubTools/ToggleTrafficLightsTool.cs
@@ -112,13 +112,14 @@ public override void OnToolGUI(Event e) {
overlayTex = TrafficLightTextures.TrafficLightDisabled;
}
- MainTool.DrawGenericOverlayTexture(
- overlayTex,
- camPos,
- nodesBuffer[nodeId].m_position,
- SIGN_SIZE,
- SIGN_SIZE,
- false);
+ Highlight.DrawGenericOverlayTexture(
+ texture: overlayTex,
+ camPos: camPos,
+ worldPos: nodesBuffer[nodeId].m_position,
+ width: SIGN_SIZE,
+ height: SIGN_SIZE,
+ canHover: false,
+ screenRect: out Rect _);
}
}
@@ -144,11 +145,11 @@ public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) {
// Render the current hovered node as blue
if ((HoveredNodeId != 0) && Flags.MayHaveTrafficLight(HoveredNodeId)) {
- MainTool.DrawNodeCircle(
- cameraInfo,
- HoveredNodeId,
- Input.GetMouseButton(0),
- false);
+ Highlight.DrawNodeCircle(
+ cameraInfo: cameraInfo,
+ nodeId: HoveredNodeId,
+ warning: Input.GetMouseButton(0),
+ alpha: false);
}
}
diff --git a/TLM/TLM/UI/SubTools/VehicleRestrictionsTool.cs b/TLM/TLM/UI/SubTools/VehicleRestrictionsTool.cs
index fcbeb7447..571022409 100644
--- a/TLM/TLM/UI/SubTools/VehicleRestrictionsTool.cs
+++ b/TLM/TLM/UI/SubTools/VehicleRestrictionsTool.cs
@@ -171,16 +171,16 @@ private void RenderLaneOverlay(RenderManager.CameraInfo cameraInfo, uint laneId)
///
private void RenderRoadLane(RenderManager.CameraInfo cameraInfo) {
SegmentLaneTraverser.Traverse(
- renderData_.segmentId,
- SegmentTraverser.TraverseDirection.AnyDirection,
- SegmentTraverser.TraverseSide.AnySide,
- SegmentLaneTraverser.LaneStopCriterion.LaneCount,
- SegmentTraverser.SegmentStopCriterion.Junction,
- SpeedLimitManager.LANE_TYPES,
- SpeedLimitManager.VEHICLE_TYPES,
- data => {
+ initialSegmentId: renderData_.segmentId,
+ direction: SegmentTraverser.TraverseDirection.AnyDirection,
+ side: SegmentTraverser.TraverseSide.AnySide,
+ laneStopCrit: SegmentLaneTraverser.LaneStopCriterion.LaneCount,
+ segStopCrit: SegmentTraverser.SegmentStopCriterion.Junction,
+ laneTypeFilter: SpeedLimitManager.LANE_TYPES,
+ vehicleTypeFilter: SpeedLimitManager.VEHICLE_TYPES,
+ laneVisitor: data => {
if (renderData_.SortedLaneIndex == data.SortedLaneIndex) {
- RenderLaneOverlay(cameraInfo, data.CurLanePos.laneId);
+ RenderLaneOverlay(cameraInfo: cameraInfo, laneId: data.CurLanePos.laneId);
}
return true;
});
@@ -189,11 +189,16 @@ private void RenderRoadLane(RenderManager.CameraInfo cameraInfo) {
public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) {
// Log._Debug($"Restrictions overlay {_cursorInSecondaryPanel} {HoveredNodeId} {SelectedNodeId} {HoveredSegmentId} {SelectedSegmentId}");
if (SelectedSegmentId != 0) {
- Color color = MainTool.GetToolColor(true, false);
+ Color color = MainTool.GetToolColor(warning: true, error: false);
+
// continues lane highlight requires lane alphaBlend == false.
// for such lane highlight to be on the top of segment highlight,
// the alphaBlend of segment highlight needs to be true.
- TrafficManagerTool.DrawSegmentOverlay(cameraInfo, SelectedSegmentId, color, true);
+ Highlight.DrawSegmentOverlay(
+ cameraInfo: cameraInfo,
+ segmentId: SelectedSegmentId,
+ color: color,
+ alphaBlend: true);
if (overlayHandleHovered) {
if (RoadMode) {
@@ -211,10 +216,10 @@ public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) {
if (HoveredSegmentId != 0 && HoveredSegmentId != SelectedSegmentId &&
!overlayHandleHovered) {
NetTool.RenderOverlay(
- cameraInfo,
- ref Singleton.instance.m_segments.m_buffer[HoveredSegmentId],
- MainTool.GetToolColor(false, false),
- MainTool.GetToolColor(false, false));
+ cameraInfo: cameraInfo,
+ segment: ref Singleton.instance.m_segments.m_buffer[HoveredSegmentId],
+ importantColor: MainTool.GetToolColor(warning: false, error: false),
+ nonImportantColor: MainTool.GetToolColor(warning: false, error: false));
}
}
@@ -250,10 +255,10 @@ private void ShowSigns(bool viewOnly) {
// draw vehicle restrictions
if (DrawVehicleRestrictionHandles(
- segmentId,
- ref netManager.m_segments.m_buffer[segmentId],
- viewOnly || segmentId != SelectedSegmentId,
- out bool updated)) {
+ segmentId: segmentId,
+ segment: ref netManager.m_segments.m_buffer[segmentId],
+ viewOnly: viewOnly || segmentId != SelectedSegmentId,
+ stateUpdated: out bool updated)) {
handleHovered = true;
}
@@ -451,10 +456,10 @@ private bool DrawVehicleRestrictionHandles(ushort segmentId,
}
int numLanes = GeometryUtil.GetSegmentNumVehicleLanes(
- segmentId,
- null,
- out int numDirections,
- VehicleRestrictionsManager.VEHICLE_TYPES);
+ segmentId: segmentId,
+ nodeId: null,
+ numDirections: out int numDirections,
+ vehicleTypeFilter: VehicleRestrictionsManager.VEHICLE_TYPES);
// draw vehicle restrictions over each lane
NetInfo segmentInfo = segment.Info;
@@ -463,7 +468,9 @@ private bool DrawVehicleRestrictionHandles(ushort segmentId,
// if ((segment.m_flags & NetSegment.Flags.Invert) == NetSegment.Flags.None)
// yu = -yu;
Vector3 xu = Vector3.Cross(yu, new Vector3(0, 1f, 0)).normalized;
- float f = viewOnly ? 4f : 7f; // reserved sign size in game coordinates
+ float signSize = viewOnly
+ ? Constants.OVERLAY_READONLY_SIGN_SIZE
+ : Constants.OVERLAY_INTERACTIVE_SIGN_SIZE; // reserved sign size in game coordinates
int maxNumSigns = 0;
if (VehicleRestrictionsManager.Instance.IsRoadSegment(segmentInfo)) {
@@ -473,21 +480,18 @@ private bool DrawVehicleRestrictionHandles(ushort segmentId,
}
// Vector3 zero = center - 0.5f * (float)(numLanes + numDirections - 1) * f * (xu + yu); // "bottom left"
- Vector3 zero = center - (0.5f * (numLanes - 1 + numDirections - 1) * f * xu)
- - (0.5f * maxNumSigns * f * yu); // "bottom left"
-
- // if (!viewOnly)
- // Log._Debug($"xu: {xu.ToString()} yu: {yu.ToString()} center: {center.ToString()}
- // zero: {zero.ToString()} numLanes: {numLanes} numDirections: {numDirections}");*/
+ Vector3 drawOrigin = center
+ - (0.5f * (numLanes - 1 + numDirections - 1) * signSize * xu)
+ - (0.5f * maxNumSigns * signSize * yu); // "bottom left"
uint x = 0;
Color guiColor = GUI.color;
IList sortedLanes = Constants.ServiceFactory.NetService.GetSortedLanes(
- segmentId,
- ref segment,
- null,
- VehicleRestrictionsManager.LANE_TYPES,
- VehicleRestrictionsManager.VEHICLE_TYPES);
+ segmentId: segmentId,
+ segment: ref segment,
+ startNode: null,
+ laneTypeFilter: VehicleRestrictionsManager.LANE_TYPES,
+ vehicleTypeFilter: VehicleRestrictionsManager.VEHICLE_TYPES);
bool hovered = false;
HashSet directions = new HashSet();
int sortedLaneIndex = -1;
@@ -527,26 +531,14 @@ private bool DrawVehicleRestrictionHandles(ushort segmentId,
VehicleRestrictionsMode.Configured);
uint y = 0;
-#if DEBUG_disabled_xxx
- Vector3 labelCenter = zero + f * (float)x * xu + f * (float)y * yu; // in game coordinates
-
- Vector3 labelScreenPos;
- bool visible = GeometryUtil.WorldToScreenPoint(labelCenter, out labelScreenPos);
- // BUGBUG: Using screen.height might be wrong, consider U.UIScaler.ScreenHeight (from UIView.fixedHeight)
- labelScreenPos.y = Screen.height - labelScreenPos.y;
- diff = labelCenter - camPos;
-
- var labelZoom = 1.0f / diff.magnitude * 100f;
- _counterStyle.fontSize = (int)(11f * labelZoom);
- _counterStyle.normal.textColor = new Color(1f, 1f, 0f);
-
- string labelStr = $"Idx {laneIndex}";
- Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr));
- Rect labelRect = new Rect(labelScreenPos.x - dim.x / 2f, labelScreenPos.y, dim.x, dim.y);
- GUI.Label(labelRect, labelStr, _counterStyle);
-
- ++y;
-#endif
+
+ Highlight.Grid grid = new Highlight.Grid(
+ gridOrigin: drawOrigin,
+ cellWidth: signSize,
+ cellHeight: signSize,
+ xu: xu,
+ yu: yu);
+
foreach (ExtVehicleType vehicleType in possibleVehicleTypes) {
bool allowed = VehicleRestrictionsManager.Instance.IsAllowed(allowedTypes, vehicleType);
@@ -554,17 +546,15 @@ private bool DrawVehicleRestrictionHandles(ushort segmentId,
continue; // do not draw allowed vehicles in view-only mode
}
- bool hoveredHandle = MainTool.DrawGenericSquareOverlayGridTexture(
- RoadUI.VehicleRestrictionTextures[vehicleType][allowed],
- camPos,
- zero,
- f,
- xu,
- yu,
- x,
- y,
- vehicleRestrictionsSignSize,
- !viewOnly);
+ bool hoveredHandle = grid.DrawGenericOverlayGridTexture(
+ texture: RoadUI.VehicleRestrictionTextures[key: vehicleType][key: allowed],
+ camPos: camPos,
+ x: x,
+ y: y,
+ width: vehicleRestrictionsSignSize,
+ height: vehicleRestrictionsSignSize,
+ canHover: !viewOnly,
+ screenRect: out Rect _);
if (hoveredHandle) {
hovered = true;
@@ -575,10 +565,8 @@ private bool DrawVehicleRestrictionHandles(ushort segmentId,
renderData_.SortedLaneIndex = sortedLaneIndex;
}
- if (hoveredHandle && MainTool.CheckClicked() ) {
+ if (hoveredHandle && MainTool.CheckClicked()) {
// toggle vehicle restrictions
- // Log._Debug($"Setting vehicle restrictions of segment {segmentId}, lane
- // idx {laneIndex}, {vehicleType.ToString()} to {!allowed}");
VehicleRestrictionsManager.Instance.ToggleAllowedType(
segmentId,
segmentInfo,
@@ -587,8 +575,10 @@ private bool DrawVehicleRestrictionHandles(ushort segmentId,
laneInfo,
vehicleType,
!allowed);
+
stateUpdated = true;
RefreshCurrentRestrictedSegmentIds(segmentId);
+
if (RoadMode) {
ApplyRestrictionsToAllSegments(sortedLaneIndex, vehicleType);
}
diff --git a/TLM/TLM/UI/Textures/JunctionRestrictions.cs b/TLM/TLM/UI/Textures/JunctionRestrictions.cs
index 5d91917d7..28e9505a6 100644
--- a/TLM/TLM/UI/Textures/JunctionRestrictions.cs
+++ b/TLM/TLM/UI/Textures/JunctionRestrictions.cs
@@ -1,4 +1,5 @@
namespace TrafficManager.UI.Textures {
+ using TrafficManager.Util;
using UnityEngine;
using static TextureResources;
@@ -25,58 +26,24 @@ public static class JunctionRestrictions {
public static readonly Texture2D PedestrianCrossingForbidden;
static JunctionRestrictions() {
- LaneChangeAllowed = LoadDllResource(
- "JunctionRestrictions.lanechange_allowed.png",
- 200,
- 200);
- LaneChangeForbidden = LoadDllResource(
- "JunctionRestrictions.lanechange_forbidden.png",
- 200,
- 200);
+ IntVector2 size = new IntVector2(200);
- UturnAllowed = LoadDllResource(
- "JunctionRestrictions.uturn_allowed.png",
- 200,
- 200);
- UturnForbidden = LoadDllResource(
- "JunctionRestrictions.uturn_forbidden.png",
- 200,
- 200);
+ LaneChangeAllowed = LoadDllResource("JunctionRestrictions.lanechange_allowed.png", size);
+ LaneChangeForbidden = LoadDllResource("JunctionRestrictions.lanechange_forbidden.png", size);
- RightOnRedAllowed = LoadDllResource(
- "JunctionRestrictions.right_on_red_allowed.png",
- 200,
- 200);
- RightOnRedForbidden = LoadDllResource(
- "JunctionRestrictions.right_on_red_forbidden.png",
- 200,
- 200);
- LeftOnRedAllowed = LoadDllResource(
- "JunctionRestrictions.left_on_red_allowed.png",
- 200,
- 200);
- LeftOnRedForbidden = LoadDllResource(
- "JunctionRestrictions.left_on_red_forbidden.png",
- 200,
- 200);
+ UturnAllowed = LoadDllResource("JunctionRestrictions.uturn_allowed.png", size);
+ UturnForbidden = LoadDllResource("JunctionRestrictions.uturn_forbidden.png", size);
- EnterBlockedJunctionAllowed = LoadDllResource(
- "JunctionRestrictions.enterblocked_allowed.png",
- 200,
- 200);
- EnterBlockedJunctionForbidden = LoadDllResource(
- "JunctionRestrictions.enterblocked_forbidden.png",
- 200,
- 200);
+ RightOnRedAllowed = LoadDllResource("JunctionRestrictions.right_on_red_allowed.png", size);
+ RightOnRedForbidden = LoadDllResource("JunctionRestrictions.right_on_red_forbidden.png", size);
+ LeftOnRedAllowed = LoadDllResource("JunctionRestrictions.left_on_red_allowed.png", size);
+ LeftOnRedForbidden = LoadDllResource("JunctionRestrictions.left_on_red_forbidden.png", size);
- PedestrianCrossingAllowed = LoadDllResource(
- "JunctionRestrictions.crossing_allowed.png",
- 200,
- 200);
- PedestrianCrossingForbidden = LoadDllResource(
- "JunctionRestrictions.crossing_forbidden.png",
- 200,
- 200);
+ EnterBlockedJunctionAllowed = LoadDllResource("JunctionRestrictions.enterblocked_allowed.png", size);
+ EnterBlockedJunctionForbidden = LoadDllResource("JunctionRestrictions.enterblocked_forbidden.png", size);
+
+ PedestrianCrossingAllowed = LoadDllResource("JunctionRestrictions.crossing_allowed.png", size);
+ PedestrianCrossingForbidden = LoadDllResource("JunctionRestrictions.crossing_forbidden.png", size);
}
}
}
\ No newline at end of file
diff --git a/TLM/TLM/UI/Textures/MainMenu.cs b/TLM/TLM/UI/Textures/MainMenu.cs
index 237983347..bb498294a 100644
--- a/TLM/TLM/UI/Textures/MainMenu.cs
+++ b/TLM/TLM/UI/Textures/MainMenu.cs
@@ -1,30 +1,18 @@
namespace TrafficManager.UI.Textures {
+ using TrafficManager.Util;
using UnityEngine;
using static TrafficManager.UI.Textures.TextureResources;
/// Textures for main menu button and main menu panel.
public static class MainMenu {
// public static readonly Texture2D MainMenuButton;
- public static readonly Texture2D MainMenuButtons;
- public static readonly Texture2D NoImage;
- public static readonly Texture2D RemoveButton;
+ // public static readonly Texture2D MainMenuButtons;
+ // public static readonly Texture2D NoImage;
+ // public static readonly Texture2D RemoveButton;
public static readonly Texture2D WindowBackground;
static MainMenu() {
- // missing image
- NoImage = LoadDllResource("MainMenu.noimage.png", 64, 64);
-
- // main menu icon
- // MainMenuButton = LoadDllResource("MainMenu.MenuButton.png", 300, 50);
- // MainMenuButton.name = "TMPE_MainMenuButtonIcon";
-
- // Main menu backgrounds, buttons, and highlighted buttons
- MainMenuButtons = LoadDllResource("MainMenu.LegacyButtons.png", 960, 50);
- MainMenuButtons.name = "TMPE_MainMenuButtons";
-
- RemoveButton = LoadDllResource("MainMenu.remove-btn.png", 150, 30);
-
- WindowBackground = LoadDllResource("MainMenu.WindowBackground.png", 16, 60);
+ WindowBackground = LoadDllResource("MainMenu.WindowBackground.png", new IntVector2(16, 60));
}
}
}
\ No newline at end of file
diff --git a/TLM/TLM/UI/Textures/RoadUI.cs b/TLM/TLM/UI/Textures/RoadUI.cs
index 6ef014601..70da9db13 100644
--- a/TLM/TLM/UI/Textures/RoadUI.cs
+++ b/TLM/TLM/UI/Textures/RoadUI.cs
@@ -14,16 +14,18 @@ public static class RoadUI {
public static readonly IDictionary ParkingRestrictionTextures;
static RoadUI() {
+ IntVector2 size = new IntVector2(200);
+
// priority signs
PrioritySignTextures = new TinyDictionary {
- [PriorityType.None] = LoadDllResource("RoadUI.sign_none.png", 200, 200),
- [PriorityType.Main] = LoadDllResource("RoadUI.sign_priority.png", 200, 200),
- [PriorityType.Stop] = LoadDllResource("RoadUI.sign_stop.png", 200, 200),
- [PriorityType.Yield] = LoadDllResource("RoadUI.sign_yield.png", 200, 200),
+ [PriorityType.None] = LoadDllResource("RoadUI.sign_none.png", size),
+ [PriorityType.Main] = LoadDllResource("RoadUI.sign_priority.png", size),
+ [PriorityType.Stop] = LoadDllResource("RoadUI.sign_stop.png", size),
+ [PriorityType.Yield] = LoadDllResource("RoadUI.sign_yield.png", size),
};
// delete priority sign
- SignClear = LoadDllResource("clear.png", 256, 256);
+ SignClear = LoadDllResource("clear.png", new IntVector2(256));
VehicleRestrictionTextures =
new TinyDictionary> {
@@ -43,28 +45,29 @@ static RoadUI() {
string suffix = b ? "allowed" : "forbidden";
e.Value[b] = LoadDllResource(
e.Key.ToString().ToLower() + "_" + suffix + ".png",
- 200,
- 200);
+ size);
}
}
ParkingRestrictionTextures = new TinyDictionary();
- ParkingRestrictionTextures[true] = LoadDllResource("RoadUI.parking_allowed.png", 200, 200);
- ParkingRestrictionTextures[false] = LoadDllResource("RoadUI.parking_disallowed.png", 200, 200);
+ ParkingRestrictionTextures[true] = LoadDllResource("RoadUI.parking_allowed.png", size);
+ ParkingRestrictionTextures[false] = LoadDllResource("RoadUI.parking_disallowed.png", size);
+
+ IntVector2 signSize = new IntVector2(449, 411);
VehicleInfoSignTextures = new TinyDictionary {
- [ExtVehicleType.Bicycle] = LoadDllResource("RoadUI.bicycle_infosign.png", 449, 411),
- [ExtVehicleType.Bus] = LoadDllResource("RoadUI.bus_infosign.png", 449, 411),
- [ExtVehicleType.CargoTrain] = LoadDllResource("RoadUI.cargotrain_infosign.png", 449, 411),
- [ExtVehicleType.CargoTruck] = LoadDllResource("RoadUI.cargotruck_infosign.png", 449, 411),
- [ExtVehicleType.Emergency] = LoadDllResource("RoadUI.emergency_infosign.png", 449, 411),
- [ExtVehicleType.PassengerCar] = LoadDllResource("RoadUI.passengercar_infosign.png", 449, 411),
- [ExtVehicleType.PassengerTrain] = LoadDllResource("RoadUI.passengertrain_infosign.png", 449, 411),
+ [ExtVehicleType.Bicycle] = LoadDllResource("RoadUI.bicycle_infosign.png", signSize),
+ [ExtVehicleType.Bus] = LoadDllResource("RoadUI.bus_infosign.png", signSize),
+ [ExtVehicleType.CargoTrain] = LoadDllResource("RoadUI.cargotrain_infosign.png", signSize),
+ [ExtVehicleType.CargoTruck] = LoadDllResource("RoadUI.cargotruck_infosign.png", signSize),
+ [ExtVehicleType.Emergency] = LoadDllResource("RoadUI.emergency_infosign.png", signSize),
+ [ExtVehicleType.PassengerCar] = LoadDllResource("RoadUI.passengercar_infosign.png", signSize),
+ [ExtVehicleType.PassengerTrain] = LoadDllResource("RoadUI.passengertrain_infosign.png", signSize),
};
VehicleInfoSignTextures[ExtVehicleType.RailVehicle] = VehicleInfoSignTextures[ExtVehicleType.PassengerTrain];
- VehicleInfoSignTextures[ExtVehicleType.Service] = LoadDllResource("RoadUI.service_infosign.png", 449, 411);
- VehicleInfoSignTextures[ExtVehicleType.Taxi] = LoadDllResource("RoadUI.taxi_infosign.png", 449, 411);
- VehicleInfoSignTextures[ExtVehicleType.Tram] = LoadDllResource("RoadUI.tram_infosign.png", 449, 411);
+ VehicleInfoSignTextures[ExtVehicleType.Service] = LoadDllResource("RoadUI.service_infosign.png", signSize);
+ VehicleInfoSignTextures[ExtVehicleType.Taxi] = LoadDllResource("RoadUI.taxi_infosign.png", signSize);
+ VehicleInfoSignTextures[ExtVehicleType.Tram] = LoadDllResource("RoadUI.tram_infosign.png", signSize);
}
}
}
\ No newline at end of file
diff --git a/TLM/TLM/UI/Textures/SpeedLimitTextures.cs b/TLM/TLM/UI/Textures/SpeedLimitTextures.cs
index edbb2a345..9ae18dc86 100644
--- a/TLM/TLM/UI/Textures/SpeedLimitTextures.cs
+++ b/TLM/TLM/UI/Textures/SpeedLimitTextures.cs
@@ -4,63 +4,73 @@ namespace TrafficManager.UI.Textures {
using System;
using TrafficManager.API.Traffic.Data;
using TrafficManager.State;
+ using TrafficManager.State.ConfigData;
using TrafficManager.UI.SubTools.SpeedLimits;
using TrafficManager.Util;
using UnityEngine;
public static class SpeedLimitTextures {
+ /// Blue textures for road/lane default speed limits.
+ public static readonly IDictionary RoadDefaults;
+
+ /// German style textures for KM/hour also usable for MPH.
public static readonly IDictionary TexturesKmph;
+
+ /// White rectangular textures for MPH US style.
public static readonly IDictionary TexturesMphUS;
+
+ /// British style speed limit textures for MPH
public static readonly IDictionary TexturesMphUK;
+
public static readonly Texture2D Clear;
+
static SpeedLimitTextures() {
// TODO: Split loading here into dynamic sections, static enforces everything to stay in this ctor
+ RoadDefaults = new TinyDictionary();
TexturesKmph = new TinyDictionary();
TexturesMphUS = new TinyDictionary();
TexturesMphUK = new TinyDictionary();
+ IntVector2 size = new IntVector2(200);
+ IntVector2 sizeUS = new IntVector2(200, 250);
+
// Load shared speed limit signs for Kmph and Mph
// Assumes that signs from 0 to 140 with step 5 exist, 0 denotes no limit sign
for (var speedLimit = 0; speedLimit <= 140; speedLimit += 5) {
- var resource = LoadDllResource($"SpeedLimits.Kmh.{speedLimit}.png", 200, 200);
+ var resource = LoadDllResource($"SpeedLimits.Kmh.{speedLimit}.png", size, true);
TexturesKmph.Add(speedLimit, resource ?? TexturesKmph[5]);
}
+
+ for (var speedLimit = 0; speedLimit <= 140; speedLimit += 5) {
+ var resource = LoadDllResource($"SpeedLimits.RoadDefaults.{speedLimit}.png", size, true);
+ RoadDefaults.Add(speedLimit, resource ?? RoadDefaults[5]);
+ }
+
// Signs from 0 to 90 for MPH
for (var speedLimit = 0; speedLimit <= 90; speedLimit += 5) {
// Load US textures, they are rectangular
- var resourceUs = LoadDllResource($"SpeedLimits.Mph_US.{speedLimit}.png", 200, 250);
+ var resourceUs = LoadDllResource($"SpeedLimits.Mph_US.{speedLimit}.png", sizeUS, true);
TexturesMphUS.Add(speedLimit, resourceUs ?? TexturesMphUS[5]);
+
// Load UK textures, they are square
- var resourceUk = LoadDllResource($"SpeedLimits.Mph_UK.{speedLimit}.png", 200, 200);
+ var resourceUk = LoadDllResource($"SpeedLimits.Mph_UK.{speedLimit}.png", size, true);
TexturesMphUK.Add(speedLimit, resourceUk ?? TexturesMphUK[5]);
}
- Clear = LoadDllResource($"clear.png", 256, 256);
+
+ Clear = LoadDllResource("clear.png", new IntVector2(256));
}
///
/// Given the float speed, style and MPH option return a texture to render.
///
- /// float speed
- /// Signs theme
- /// Mph or km/h
- ///
- public static Texture2D GetSpeedLimitTexture(SpeedValue spd, MphSignStyle mphStyle, SpeedUnit unit) {
+ /// Speed to display.
+ /// Texture to display.
+ public static Texture2D GetSpeedLimitTexture(SpeedValue spd,
+ IDictionary textureSource) {
// Select the source for the textures based on unit and the theme
+ State.ConfigData.Main m = GlobalConfig.Instance.Main;
+ SpeedUnit unit = m.DisplaySpeedLimitsMph ? SpeedUnit.Mph : SpeedUnit.Kmph;
bool mph = unit == SpeedUnit.Mph;
- IDictionary textures = TexturesKmph;
- if (mph) {
- switch (mphStyle) {
- case MphSignStyle.SquareUS:
- textures = TexturesMphUS;
- break;
- case MphSignStyle.RoundUK:
- textures = TexturesMphUK;
- break;
- case MphSignStyle.RoundGerman:
- // Do nothing, this is the default above
- break;
- }
- }
// Round to nearest 5 MPH or nearest 10 km/h
ushort index = mph ? spd.ToMphRounded(SpeedLimitsTool.MPH_STEP).Mph
@@ -73,23 +83,68 @@ public static Texture2D GetSpeedLimitTexture(SpeedValue spd, MphSignStyle mphSty
// Show unlimited if the speed cannot be represented by the available sign textures
if (index == 0 || index > upper) {
// Log._Debug($"Trimming speed={speedLimit} index={index} to {upper}");
- return textures[0];
+ return textureSource[0];
}
// Trim from below to not go below index 5 (5 kmph or 5 mph)
ushort trimIndex = Math.Max((ushort)5, index);
- return textures[trimIndex];
+ return textureSource[trimIndex];
+ }
+
+ // ///
+ // /// Given speed limit, round it up to nearest Kmph or Mph and produce a texture
+ // ///
+ // /// Ingame speed
+ // /// The texture, hopefully it existed
+ // public static Texture2D GetSpeedLimitTexture(SpeedValue spd,
+ // IDictionary textureSource) {
+ // return GetSpeedLimitTexture(spd, textureSource);
+ // }
+
+ /// For current display settings get texture dictionary with the road signs.
+ public static IDictionary GetTextureSource() {
+ var m = GlobalConfig.Instance.Main;
+ var unit = m.DisplaySpeedLimitsMph ? SpeedUnit.Mph : SpeedUnit.Kmph;
+
+ // Select the source for the textures based on unit and the theme
+ bool mph = unit == SpeedUnit.Mph;
+
+ if (mph) {
+ switch (m.MphRoadSignStyle) {
+ case SpeedLimitSignTheme.RectangularUS:
+ return TexturesMphUS;
+ case SpeedLimitSignTheme.RoundUK:
+ return TexturesMphUK;
+ case SpeedLimitSignTheme.RoundGerman:
+ // Do nothing, this is the default above
+ break;
+ }
+ }
+ return TexturesKmph;
}
///
- /// Given speed limit, round it up to nearest Kmph or Mph and produce a texture
+ /// Returns vector of one for square/circle textures, or a proportionally scaled rect of
+ /// width one, for rectangular US signs.
///
- /// Ingame speed
- /// The texture, hopefully it existed
- public static Texture2D GetSpeedLimitTexture(SpeedValue spd) {
+ /// Scalable vector of texture aspect ratio.
+ public static Vector2 GetTextureAspectRatio() {
var m = GlobalConfig.Instance.Main;
var unit = m.DisplaySpeedLimitsMph ? SpeedUnit.Mph : SpeedUnit.Kmph;
- return GetSpeedLimitTexture(spd, m.MphRoadSignStyle, unit);
+
+ // Select the source for the textures based on unit and the theme
+ bool mph = unit == SpeedUnit.Mph;
+
+ if (mph) {
+ switch (m.MphRoadSignStyle) {
+ case SpeedLimitSignTheme.RectangularUS:
+ return new Vector2(1.0f / 1.25f, 1.0f);
+ case SpeedLimitSignTheme.RoundUK:
+ case SpeedLimitSignTheme.RoundGerman:
+ break;
+ }
+ }
+ return Vector2.one;
}
}
}
\ No newline at end of file
diff --git a/TLM/TLM/UI/Textures/TextureResources.cs b/TLM/TLM/UI/Textures/TextureResources.cs
index 2a9179595..2eda05b77 100644
--- a/TLM/TLM/UI/Textures/TextureResources.cs
+++ b/TLM/TLM/UI/Textures/TextureResources.cs
@@ -4,13 +4,16 @@ namespace TrafficManager.UI.Textures {
using System.Reflection;
using System;
using TrafficManager.State.ConfigData;
+ using TrafficManager.Util;
using UnityEngine;
public static class TextureResources {
static TextureResources() {
}
- internal static Texture2D LoadDllResource(string resourceName, int width, int height)
+ internal static Texture2D LoadDllResource(string resourceName,
+ IntVector2 size,
+ bool mip = false)
{
#if DEBUG
bool debug = DebugSwitch.ResourceLoading.Get();
@@ -23,16 +26,25 @@ internal static Texture2D LoadDllResource(string resourceName, int width, int he
#endif
var myAssembly = Assembly.GetExecutingAssembly();
var myStream = myAssembly.GetManifestResourceStream("TrafficManager.Resources." + resourceName);
- if (myStream == null)
- throw new Exception($"{resourceName} not found!");
+ if (myStream == null) {
+ throw new Exception($"Resource stream {resourceName} not found!");
+ }
- var texture = new Texture2D(width, height, TextureFormat.ARGB32, false);
+ var texture = new Texture2D(
+ width: size.x,
+ height: size.y,
+ format: TextureFormat.ARGB32,
+ mipmap: mip);
texture.LoadImage(ReadToEnd(myStream));
return texture;
} catch (Exception e) {
- Log.Error($"failed loading {resourceName} " + e);
+#if DEBUG
+ Log.Error("Failed to load texture " + e);
+#else
+ Log.Warning("Failed to load texture " + e);
+#endif
return null;
}
}
diff --git a/TLM/TLM/UI/Textures/TrafficLightTextures.cs b/TLM/TLM/UI/Textures/TrafficLightTextures.cs
index 382ead147..de6e047fd 100644
--- a/TLM/TLM/UI/Textures/TrafficLightTextures.cs
+++ b/TLM/TLM/UI/Textures/TrafficLightTextures.cs
@@ -1,4 +1,5 @@
namespace TrafficManager.UI.Textures {
+ using TrafficManager.Util;
using UnityEngine;
using static TextureResources;
@@ -49,83 +50,86 @@ public static class TrafficLightTextures {
public static readonly Texture2D TrafficLightDisabled;
static TrafficLightTextures() {
+ IntVector2 tlSize = new IntVector2(103, 243);
+
// simple
- RedLight = LoadDllResource("TrafficLights.light_1_1.png", 103, 243);
- YellowRedLight = LoadDllResource("TrafficLights.light_1_2.png", 103, 243);
- GreenLight = LoadDllResource("TrafficLights.light_1_3.png", 103, 243);
+ RedLight = LoadDllResource("TrafficLights.light_1_1.png", tlSize);
+ YellowRedLight = LoadDllResource("TrafficLights.light_1_2.png", tlSize);
+ GreenLight = LoadDllResource("TrafficLights.light_1_3.png", tlSize);
// forward
- RedLightStraight = LoadDllResource("TrafficLights.light_2_1.png", 103, 243);
- YellowLightStraight = LoadDllResource("TrafficLights.light_2_2.png", 103, 243);
- GreenLightStraight = LoadDllResource("TrafficLights.light_2_3.png", 103, 243);
+ RedLightStraight = LoadDllResource("TrafficLights.light_2_1.png", tlSize);
+ YellowLightStraight = LoadDllResource("TrafficLights.light_2_2.png", tlSize);
+ GreenLightStraight = LoadDllResource("TrafficLights.light_2_3.png", tlSize);
// right
- RedLightRight = LoadDllResource("TrafficLights.light_3_1.png", 103, 243);
- YellowLightRight = LoadDllResource("TrafficLights.light_3_2.png", 103, 243);
- GreenLightRight = LoadDllResource("TrafficLights.light_3_3.png", 103, 243);
+ RedLightRight = LoadDllResource("TrafficLights.light_3_1.png", tlSize);
+ YellowLightRight = LoadDllResource("TrafficLights.light_3_2.png", tlSize);
+ GreenLightRight = LoadDllResource("TrafficLights.light_3_3.png", tlSize);
// left
- RedLightLeft = LoadDllResource("TrafficLights.light_4_1.png", 103, 243);
- YellowLightLeft = LoadDllResource("TrafficLights.light_4_2.png", 103, 243);
- GreenLightLeft = LoadDllResource("TrafficLights.light_4_3.png", 103, 243);
+ RedLightLeft = LoadDllResource("TrafficLights.light_4_1.png", tlSize);
+ YellowLightLeft = LoadDllResource("TrafficLights.light_4_2.png", tlSize);
+ GreenLightLeft = LoadDllResource("TrafficLights.light_4_3.png", tlSize);
// forwardright
- RedLightForwardRight = LoadDllResource("TrafficLights.light_5_1.png", 103, 243);
- YellowLightForwardRight = LoadDllResource("TrafficLights.light_5_2.png", 103, 243);
- GreenLightForwardRight = LoadDllResource("TrafficLights.light_5_3.png", 103, 243);
+ RedLightForwardRight = LoadDllResource("TrafficLights.light_5_1.png", tlSize);
+ YellowLightForwardRight = LoadDllResource("TrafficLights.light_5_2.png", tlSize);
+ GreenLightForwardRight = LoadDllResource("TrafficLights.light_5_3.png", tlSize);
// forwardleft
- RedLightForwardLeft = LoadDllResource("TrafficLights.light_6_1.png", 103, 243);
- YellowLightForwardLeft = LoadDllResource("TrafficLights.light_6_2.png", 103, 243);
- GreenLightForwardLeft = LoadDllResource("TrafficLights.light_6_3.png", 103, 243);
+ RedLightForwardLeft = LoadDllResource("TrafficLights.light_6_1.png", tlSize);
+ YellowLightForwardLeft = LoadDllResource("TrafficLights.light_6_2.png", tlSize);
+ GreenLightForwardLeft = LoadDllResource("TrafficLights.light_6_3.png", tlSize);
// yellow
- YellowLight = LoadDllResource("TrafficLights.light_yellow.png", 103, 243);
+ YellowLight = LoadDllResource("TrafficLights.light_yellow.png", tlSize);
// pedestrian
- PedestrianRedLight = LoadDllResource("TrafficLights.pedestrian_light_1.png", 73, 123);
- PedestrianGreenLight = LoadDllResource("TrafficLights.pedestrian_light_2.png", 73, 123);
+ IntVector2 pedSize = new IntVector2(73, 123);
+ PedestrianRedLight = LoadDllResource("TrafficLights.pedestrian_light_1.png", pedSize);
+ PedestrianGreenLight = LoadDllResource("TrafficLights.pedestrian_light_2.png", pedSize);
//--------------------------
// Timed TL Editor
//--------------------------
// light mode
+ IntVector2 tlModeSize = new IntVector2(103, 95);
+
LightMode = LoadDllResource(
Translation.GetTranslatedFileName("TrafficLights.light_mode.png"),
- 103,
- 95);
+ tlModeSize);
LightCounter = LoadDllResource(
Translation.GetTranslatedFileName("TrafficLights.light_counter.png"),
- 103,
- 95);
+ tlModeSize);
// pedestrian mode
PedestrianModeAutomatic = LoadDllResource(
"TrafficLights.pedestrian_mode_1.png",
- 73,
- 70);
- PedestrianModeManual = LoadDllResource("TrafficLights.pedestrian_mode_2.png", 73, 73);
+ new IntVector2(73, 70));
+ PedestrianModeManual = LoadDllResource("TrafficLights.pedestrian_mode_2.png",
+ new IntVector2(73, 73));
// timer
- ClockPlay = LoadDllResource("TrafficLights.clock_play.png", 512, 512);
- ClockPause = LoadDllResource("TrafficLights.clock_pause.png", 512, 512);
- ClockTest = LoadDllResource("TrafficLights.clock_test.png", 512, 512);
+ IntVector2 timerSize = new IntVector2(512);
+
+ ClockPlay = LoadDllResource("TrafficLights.clock_play.png", timerSize);
+ ClockPause = LoadDllResource("TrafficLights.clock_pause.png", timerSize);
+ ClockTest = LoadDllResource("TrafficLights.clock_test.png", timerSize);
//--------------------------
// Toggle TL Tool
//--------------------------
+ IntVector2 toggleSize = new IntVector2(64);
TrafficLightEnabled = LoadDllResource(
"TrafficLights.IconJunctionTrafficLights.png",
- 64,
- 64);
+ toggleSize);
TrafficLightEnabledTimed = LoadDllResource(
"TrafficLights.IconJunctionTimedTL.png",
- 64,
- 64);
+ toggleSize);
TrafficLightDisabled = LoadDllResource(
"TrafficLights.IconJunctionNoTrafficLights.png",
- 64,
- 64);
+ toggleSize);
}
}
}
\ No newline at end of file
diff --git a/TLM/TLM/UI/TrafficManagerSubTool.cs b/TLM/TLM/UI/TrafficManagerSubTool.cs
index 743e49291..2184aa13b 100644
--- a/TLM/TLM/UI/TrafficManagerSubTool.cs
+++ b/TLM/TLM/UI/TrafficManagerSubTool.cs
@@ -41,9 +41,33 @@ protected ushort SelectedSegmentId {
/// Tool has been switched off by user selecting another tool or hitting Esc.
public abstract void DeactivateTool();
- /// Called every frame to display tool-specific overlay.
- /// Camera.
- public abstract void RenderOverlay(RenderManager.CameraInfo cameraInfo);
+ ///
+ /// NOTE: This is Non-GUI overlay which cannot call GUI.DrawTexture and similar GUI calls.
+ /// Called every frame to display edit assist overlay, when the tool is active.
+ ///
+ /// The camera.
+ public abstract void RenderActiveToolOverlay(RenderManager.CameraInfo cameraInfo);
+
+ ///
+ /// NOTE: This is GUI overlay CAN call GUI.DrawTexture and similar GUI calls.
+ /// Called every frame to display edit assist overlay, when the tool is active.
+ ///
+ public abstract void RenderActiveToolOverlay_GUI();
+
+ ///
+ /// NOTE: This is Non-GUI overlay which cannot call GUI.DrawTexture and similar GUI calls.
+ /// Called when settings want the tool to show some information overlay,
+ /// but the tool is not active.
+ ///
+ /// The camera.
+ public abstract void RenderGenericInfoOverlay(RenderManager.CameraInfo cameraInfo);
+
+ ///
+ /// NOTE: This is GUI overlay CAN call GUI.DrawTexture and similar GUI calls.
+ /// Called when settings want the tool to show some information overlay,
+ /// but the tool is not active.
+ ///
+ public abstract void RenderGenericInfoOverlay_GUI();
///
/// Called whenever the mouse left click happened on the world, while the tool was active.
diff --git a/TLM/TLM/UI/TrafficManagerTool.cs b/TLM/TLM/UI/TrafficManagerTool.cs
index 994ef8e6b..475ccd111 100644
--- a/TLM/TLM/UI/TrafficManagerTool.cs
+++ b/TLM/TLM/UI/TrafficManagerTool.cs
@@ -2,21 +2,13 @@ namespace TrafficManager.UI {
using System;
using System.Collections.Generic;
using System.Linq;
- using System.Text;
- using TrafficManager.API.Manager;
- using TrafficManager.API.Traffic.Data;
using TrafficManager.API.Traffic.Enums;
using TrafficManager.API.Util;
using ColossalFramework;
- using ColossalFramework.Math;
using ColossalFramework.UI;
using CSUtil.Commons;
using JetBrains.Annotations;
- using TrafficManager.Manager.Impl;
using TrafficManager.State;
-#if DEBUG
- using TrafficManager.State.ConfigData;
-#endif
using TrafficManager.UI.MainMenu;
using TrafficManager.UI.SubTools;
using TrafficManager.UI.SubTools.SpeedLimits;
@@ -40,9 +32,6 @@ public class TrafficManagerTool
// activate when we know the mechinism.
private bool ReadjustPathMode => false; //ShiftIsPressed;
- // /// Set this to true to once call .
- // public bool InvalidateOnscreenDisplayFlag { get; set; }
-
public GuideHandler Guide;
private ToolMode toolMode_;
@@ -63,11 +52,10 @@ public class TrafficManagerTool
private static bool _mouseClickProcessed;
- public const float DEBUG_CLOSE_LOD = 300f;
-
/// Square of the distance, where overlays are not rendered.
public const float MAX_OVERLAY_DISTANCE_SQR = 450f * 450f;
+ [Obsolete("Do not add to these tools. Refactor legacy tools into TrafficManagerSubTool")]
private IDictionary legacySubTools_;
private IDictionary subTools_;
@@ -143,11 +131,6 @@ internal static float AdaptWidth(float originalWidth) {
// return originalWidth * ((float)Screen.width / 1920f);
}
- [Obsolete("Use U.UIScaler and U size and position logic")]
- internal float GetBaseZoom() {
- return Screen.height / 1200f;
- }
-
internal const float MAX_ZOOM = 0.05f;
internal static float GetWindowAlpha() {
@@ -188,6 +171,7 @@ internal void Initialize() {
subTools_ = new TinyDictionary {
[ToolMode.LaneArrows] = new LaneArrowTool(this),
+ [ToolMode.SpeedLimits] = new SpeedLimitsTool(this),
};
legacySubTools_ = new TinyDictionary {
[ToolMode.ToggleTrafficLight] = new ToggleTrafficLightsTool(this),
@@ -195,7 +179,6 @@ internal void Initialize() {
[ToolMode.ManualSwitch] = new ManualTrafficLightsTool(this),
[ToolMode.TimedTrafficLights] = timedLightsTool,
[ToolMode.VehicleRestrictions] = new VehicleRestrictionsTool(this),
- [ToolMode.SpeedLimits] = new SpeedLimitsTool(this),
[ToolMode.LaneConnector] = new LaneConnectorTool(this),
[ToolMode.JunctionRestrictions] = new JunctionRestrictionsTool(this),
[ToolMode.ParkingRestrictions] = new ParkingRestrictionsTool(this),
@@ -211,7 +194,6 @@ internal void Initialize() {
Log.Info("TrafficManagerTool: Initialization completed.");
}
-
public void OnUpdate(GlobalConfig config) {
InitializeSubTools();
}
@@ -329,8 +311,10 @@ protected override void OnEnable() {
protected override void OnDisable() {
// If TMPE was disabled by switching to another tool, hide main menue panel.
- if (ModUI.Instance != null && ModUI.Instance.IsVisible())
+ if (ModUI.Instance != null && ModUI.Instance.IsVisible()) {
ModUI.Instance.CloseMainMenu();
+ }
+
// no call to base method to disable base class behavior
}
@@ -348,9 +332,11 @@ public override void RenderOverlay(RenderManager.CameraInfo cameraInfo) {
}
///
- /// renders presistent overlay.
- /// if any subtool is active it renders overlay for that subtool (e.g. node selection, segment selection, etc.)
- /// Must not call base.RenderOverlay() . Doing so may cause infinite recursion with Postfix of base.RenderOverlay()
+ /// Penders persistent overlay.
+ /// If any subtool is active it renders overlay for that subtool (e.g. node selection,
+ /// segment selection, etc.)
+ /// Must not call base.RenderOverlay() . Doing so may cause infinite recursion with
+ /// Postfix of base.RenderOverlay()
///
public void RenderOverlayImpl(RenderManager.CameraInfo cameraInfo) {
if (!(isActiveAndEnabled || SubTools.PrioritySigns.MassEditOverlay.IsActive)) {
@@ -358,7 +344,7 @@ public void RenderOverlayImpl(RenderManager.CameraInfo cameraInfo) {
}
activeLegacySubTool_?.RenderOverlay(cameraInfo);
- activeSubTool_?.RenderOverlay(cameraInfo);
+ activeSubTool_?.RenderActiveToolOverlay(cameraInfo);
ToolMode currentMode = GetToolMode();
@@ -368,7 +354,12 @@ public void RenderOverlayImpl(RenderManager.CameraInfo cameraInfo) {
continue;
}
- e.Value.RenderOverlayForOtherTools(cameraInfo);
+ e.Value?.RenderOverlayForOtherTools(cameraInfo);
+ }
+ foreach (var st in subTools_) {
+ if (st.Key != GetToolMode()) {
+ st.Value.RenderGenericInfoOverlay(cameraInfo);
+ }
}
}
@@ -383,15 +374,20 @@ void DefaultRenderOverlay(RenderManager.CameraInfo cameraInfo)
NetManager.instance.NetAdjust.PathVisible =
RoadSelectionPanels.Root.ShouldPathBeVisible();
+
if (NetManager.instance.NetAdjust.PathVisible) {
base.RenderOverlay(cameraInfo); // render path.
}
- if (HoveredSegmentId == 0)
+ if (HoveredSegmentId == 0) {
return;
+ }
+
var netAdjust = NetManager.instance?.NetAdjust;
- if (netAdjust == null)
+
+ if (netAdjust == null) {
return;
+ }
// use the same color as in NetAdjust
ref NetSegment segment = ref HoveredSegmentId.ToSegment();
@@ -405,22 +401,24 @@ void DefaultRenderOverlay(RenderManager.CameraInfo cameraInfo)
color = GetToolColor(Input.GetMouseButton(0), false);
}
bool isRoundabout = RoundaboutMassEdit.Instance.TraverseLoop(HoveredSegmentId, out var segmentList);
+
if (!isRoundabout) {
- var segments = SegmentTraverser.Traverse(
- HoveredSegmentId,
- TraverseDirection.AnyDirection,
- TraverseSide.Straight,
- SegmentStopCriterion.None,
- (_) => true);
+ IEnumerable segments = SegmentTraverser.Traverse(
+ initialSegmentId: HoveredSegmentId,
+ direction: TraverseDirection.AnyDirection,
+ side: TraverseSide.Straight,
+ stopCrit: SegmentStopCriterion.None,
+ visitorFun: (_) => true);
segmentList = new List(segmentList);
}
+
foreach (ushort segmentId in segmentList ?? Enumerable.Empty()) {
ref NetSegment seg = ref Singleton.instance.m_segments.m_buffer[segmentId];
NetTool.RenderOverlay(
- cameraInfo,
- ref seg,
- color,
- color);
+ cameraInfo: cameraInfo,
+ segment: ref seg,
+ importantColor: color,
+ nonImportantColor: color);
}
} else {
NetTool.RenderOverlay(cameraInfo, ref segment, color, color);
@@ -506,24 +504,38 @@ public void OnToolGUIImpl(Event e) {
}
if (Options.nodesOverlay) {
- DebugGuiDisplaySegments();
- DebugGuiDisplayNodes();
+ DebugToolGUI.DisplaySegments();
+ DebugToolGUI.DisplayNodes();
}
if (Options.vehicleOverlay) {
- DebugGuiDisplayVehicles();
+ DebugToolGUI.DisplayVehicles();
}
if (Options.citizenOverlay) {
- DebugGuiDisplayCitizens();
+ DebugToolGUI.DisplayCitizens();
}
if (Options.buildingOverlay) {
- DebugGuiDisplayBuildings();
+ DebugToolGUI.DisplayBuildings();
}
+ //----------------------
+ // Render legacy GUI overlay, and new style GUI mode overlays need to render too
+ ToolMode toolMode = GetToolMode();
+
foreach (KeyValuePair en in legacySubTools_) {
- en.Value.ShowGUIOverlay(en.Key, en.Key != GetToolMode());
+ en.Value.ShowGUIOverlay(
+ toolMode: en.Key,
+ viewOnly: en.Key != toolMode);
+ }
+
+ foreach (KeyValuePair st in subTools_) {
+ if (st.Key == toolMode) {
+ st.Value.RenderActiveToolOverlay_GUI();
+ } else {
+ st.Value.RenderGenericInfoOverlay_GUI();
+ }
}
Color guiColor = GUI.color;
@@ -532,8 +544,8 @@ public void OnToolGUIImpl(Event e) {
if (activeLegacySubTool_ != null) {
activeLegacySubTool_.OnToolGUI(e);
- } else if (activeSubTool_ != null) {
- activeSubTool_.UpdateEveryFrame();
+ } else {
+ activeSubTool_?.UpdateEveryFrame();
}
} catch (Exception ex) {
Log.Error("GUI Error: " + ex);
@@ -574,405 +586,6 @@ void DefaultOnToolGUI(Event e) {
}
}
- public void DrawNodeCircle(RenderManager.CameraInfo cameraInfo,
- ushort nodeId,
- bool warning = false,
- bool alpha = false) {
- DrawNodeCircle(
- cameraInfo: cameraInfo,
- nodeId: nodeId,
- color: GetToolColor(warning: warning, error: false),
- alpha: alpha);
- }
-
- ///
- /// Gets the coordinates of the given node.
- ///
- private static Vector3 GetNodePos(ushort nodeId) {
- NetNode[] nodeBuffer = Singleton.instance.m_nodes.m_buffer;
- Vector3 pos = nodeBuffer[nodeId].m_position;
- float terrainY = Singleton.instance.SampleDetailHeightSmooth(pos);
- if (terrainY > pos.y) {
- pos.y = terrainY;
- }
- return pos;
- }
-
- /// the average half width of all connected segments
- private static float CalculateNodeRadius(ushort nodeId) {
- float sumHalfWidth = 0;
- int count = 0;
- Constants.ServiceFactory.NetService.IterateNodeSegments(
- nodeId,
- (ushort segmentId, ref NetSegment segment) => {
- sumHalfWidth += segment.Info.m_halfWidth;
- count++;
- return true;
- });
- return sumHalfWidth / count;
- }
-
- // TODO: move to UI.Helpers (Highlight)
- public void DrawNodeCircle(RenderManager.CameraInfo cameraInfo,
- ushort nodeId,
- Color color,
- bool alpha = false) {
- float r = CalculateNodeRadius(nodeId);
- Vector3 pos = Singleton.instance.m_nodes.m_buffer[nodeId].m_position;
- DrawOverlayCircle(cameraInfo, color, pos, r * 2, alpha);
- }
-
- ///
- /// Draws a half sausage at segment end.
- ///
- ///
- /// The lenght of the highlight [0~1]
- /// Determines the direction of the half sausage.
- // TODO: move to UI.Helpers (Highlight)
- public void DrawCutSegmentEnd(RenderManager.CameraInfo cameraInfo,
- ushort segmentId,
- float cut,
- bool bStartNode,
- Color color,
- bool alpha = false) {
- if( segmentId == 0) {
- return;
- }
- ref NetSegment segment = ref Singleton.instance.m_segments.m_buffer[segmentId];
- float width = segment.Info.m_halfWidth;
-
- NetNode[] nodeBuffer = Singleton.instance.m_nodes.m_buffer;
- bool IsMiddle(ushort nodeId) => (nodeBuffer[nodeId].m_flags & NetNode.Flags.Middle) != 0;
-
- Bezier3 bezier;
- bezier.a = GetNodePos(segment.m_startNode);
- bezier.d = GetNodePos(segment.m_endNode);
-
- NetSegment.CalculateMiddlePoints(
- bezier.a,
- segment.m_startDirection,
- bezier.d,
- segment.m_endDirection,
- IsMiddle(segment.m_startNode),
- IsMiddle(segment.m_endNode),
- out bezier.b,
- out bezier.c);
-
- if (bStartNode) {
- bezier = bezier.Cut(0, cut);
- } else {
- bezier = bezier.Cut(1 - cut, 1);
- }
-
- Singleton.instance.m_drawCallData.m_overlayCalls++;
- Singleton.instance.OverlayEffect.DrawBezier(
- cameraInfo,
- color,
- bezier,
- width * 2f,
- bStartNode ? 0 : width,
- bStartNode ? width : 0,
- -1f,
- 1280f,
- false,
- alpha);
- }
-
- ///
- /// similar to NetTool.RenderOverlay()
- /// but with additional control over alphaBlend.
- ///
- // TODO: move to UI.Helpers (Highlight)
- internal static void DrawSegmentOverlay(
- RenderManager.CameraInfo cameraInfo,
- ushort segmentId,
- Color color,
- bool alphaBlend) {
- if (segmentId == 0) {
- return;
- }
-
- ref NetSegment segment = ref Singleton.instance.m_segments.m_buffer[segmentId];
- float width = segment.Info.m_halfWidth;
-
- NetNode[] nodeBuffer = Singleton.instance.m_nodes.m_buffer;
- bool IsMiddle(ushort nodeId) => (nodeBuffer[nodeId].m_flags & NetNode.Flags.Middle) != 0;
-
- Bezier3 bezier;
- bezier.a = GetNodePos(segment.m_startNode);
- bezier.d = GetNodePos(segment.m_endNode);
-
- NetSegment.CalculateMiddlePoints(
- bezier.a,
- segment.m_startDirection,
- bezier.d,
- segment.m_endDirection,
- IsMiddle(segment.m_startNode),
- IsMiddle(segment.m_endNode),
- out bezier.b,
- out bezier.c);
-
- Singleton.instance.m_drawCallData.m_overlayCalls++;
- Singleton.instance.OverlayEffect.DrawBezier(
- cameraInfo,
- color,
- bezier,
- width * 2f,
- 0,
- 0,
- -1f,
- 1280f,
- false,
- alphaBlend);
- }
-
- [UsedImplicitly]
- // TODO: move to UI.Helpers (Highlight)
- private static void DrawOverlayCircle(RenderManager.CameraInfo cameraInfo,
- Color color,
- Vector3 position,
- float width,
- bool alpha) {
- Singleton.instance.m_drawCallData.m_overlayCalls++;
- Singleton.instance.OverlayEffect.DrawCircle(
- cameraInfo,
- color,
- position,
- width,
- position.y - 100f,
- position.y + 100f,
- false,
- alpha);
- }
-
- // TODO: move to UI.Helpers (Highlight)
- public void DrawStaticSquareOverlayGridTexture(Texture2D texture,
- Vector3 camPos,
- Vector3 gridOrigin,
- float cellSize,
- Vector3 xu,
- Vector3 yu,
- uint x,
- uint y,
- float size) {
- DrawGenericSquareOverlayGridTexture(
- texture,
- camPos,
- gridOrigin,
- cellSize,
- xu,
- yu,
- x,
- y,
- size,
- false);
- }
-
- [UsedImplicitly]
- // TODO: move to UI.Helpers (Highlight)
- public bool DrawHoverableSquareOverlayGridTexture(Texture2D texture,
- Vector3 camPos,
- Vector3 gridOrigin,
- float cellSize,
- Vector3 xu,
- Vector3 yu,
- uint x,
- uint y,
- float size) {
- return DrawGenericSquareOverlayGridTexture(
- texture,
- camPos,
- gridOrigin,
- cellSize,
- xu,
- yu,
- x,
- y,
- size,
- true);
- }
-
- // TODO: move to UI.Helpers (Highlight)
- public bool DrawGenericSquareOverlayGridTexture(Texture2D texture,
- Vector3 camPos,
- Vector3 gridOrigin,
- float cellSize,
- Vector3 xu,
- Vector3 yu,
- uint x,
- uint y,
- float size,
- bool canHover) {
- return DrawGenericOverlayGridTexture(
- texture,
- camPos,
- gridOrigin,
- cellSize,
- cellSize,
- xu,
- yu,
- x,
- y,
- size,
- size,
- canHover);
- }
-
- // TODO: move to UI.Helpers (Highlight)
- public void DrawStaticOverlayGridTexture(Texture2D texture,
- Vector3 camPos,
- Vector3 gridOrigin,
- float cellWidth,
- float cellHeight,
- Vector3 xu,
- Vector3 yu,
- uint x,
- uint y,
- float width,
- float height) {
- DrawGenericOverlayGridTexture(
- texture,
- camPos,
- gridOrigin,
- cellWidth,
- cellHeight,
- xu,
- yu,
- x,
- y,
- width,
- height,
- false);
- }
-
- [UsedImplicitly]
- // TODO: move to UI.Helpers (Highlight)
- public bool DrawHoverableOverlayGridTexture(Texture2D texture,
- Vector3 camPos,
- Vector3 gridOrigin,
- float cellWidth,
- float cellHeight,
- Vector3 xu,
- Vector3 yu,
- uint x,
- uint y,
- float width,
- float height) {
- return DrawGenericOverlayGridTexture(
- texture,
- camPos,
- gridOrigin,
- cellWidth,
- cellHeight,
- xu,
- yu,
- x,
- y,
- width,
- height,
- true);
- }
-
- // TODO: move to UI.Helpers (Highlight)
- public bool DrawGenericOverlayGridTexture(Texture2D texture,
- Vector3 camPos,
- Vector3 gridOrigin,
- float cellWidth,
- float cellHeight,
- Vector3 xu,
- Vector3 yu,
- uint x,
- uint y,
- float width,
- float height,
- bool canHover) {
- Vector3 worldPos =
- gridOrigin + (cellWidth * x * xu) +
- (cellHeight * y * yu); // grid position in game coordinates
- return DrawGenericOverlayTexture(texture, camPos, worldPos, width, height, canHover);
- }
-
- // TODO: move to UI.Helpers (Highlight)
- public void DrawStaticSquareOverlayTexture(Texture2D texture,
- Vector3 camPos,
- Vector3 worldPos,
- float size) {
- DrawGenericOverlayTexture(texture, camPos, worldPos, size, size, false);
- }
-
- // TODO: move to UI.Helpers (Highlight)
- public bool DrawHoverableSquareOverlayTexture(Texture2D texture,
- Vector3 camPos,
- Vector3 worldPos,
- float size) {
- return DrawGenericOverlayTexture(texture, camPos, worldPos, size, size, true);
- }
-
- // TODO: move to UI.Helpers (Highlight)
- public bool DrawGenericSquareOverlayTexture(Texture2D texture,
- Vector3 camPos,
- Vector3 worldPos,
- float size,
- bool canHover) {
- return DrawGenericOverlayTexture(texture, camPos, worldPos, size, size, canHover);
- }
-
- // TODO: move to UI.Helpers (Highlight)
- public void DrawStaticOverlayTexture(Texture2D texture,
- Vector3 camPos,
- Vector3 worldPos,
- float width,
- float height) {
- DrawGenericOverlayTexture(texture, camPos, worldPos, width, height, false);
- }
-
- [UsedImplicitly]
- // TODO: move to UI.Helpers (Highlight)
- public bool DrawHoverableOverlayTexture(Texture2D texture,
- Vector3 camPos,
- Vector3 worldPos,
- float width,
- float height) {
- return DrawGenericOverlayTexture(texture, camPos, worldPos, width, height, true);
- }
-
- // TODO: move to UI.Helpers (Highlight)
- public bool DrawGenericOverlayTexture(Texture2D texture,
- Vector3 camPos,
- Vector3 worldPos,
- float width,
- float height,
- bool canHover) {
- // Is point in screen?
- if (!GeometryUtil.WorldToScreenPoint(worldPos, out Vector3 screenPos)) {
- return false;
- }
-
- float zoom = 1.0f / (worldPos - camPos).magnitude * 100f * GetBaseZoom();
- width *= zoom;
- height *= zoom;
-
- Rect boundingBox = new Rect(
- screenPos.x - (width / 2f),
- screenPos.y - (height / 2f),
- width,
- height);
-
- Color guiColor = GUI.color;
- bool hovered = false;
-
- if (canHover) {
- hovered = IsMouseOver(boundingBox);
- }
-
- guiColor.a = GetHandleAlpha(hovered);
-
- GUI.color = guiColor;
- GUI.DrawTexture(boundingBox, texture);
-
- return hovered;
- }
-
/// Shows a tutorial message. Must be called by a Unity thread.
/// Tutorial key.
public static void ShowAdvisor(string localeKey) {
@@ -998,30 +611,6 @@ public static void ShowAdvisor(string localeKey) {
}
}
- // Does nothing
- public override void SimulationStep() {
- base.SimulationStep();
-
- // currentFrame = Singleton.instance.m_currentFrameIndex >> 2;
- //
- // string displayToolTipText = tooltipText;
- // if (displayToolTipText != null) {
- // if (currentFrame <= tooltipStartFrame + 50) {
- // ShowToolInfo(true, displayToolTipText, (Vector3)tooltipWorldPos);
- // } else {
- // //ShowToolInfo(false, tooltipText, (Vector3)tooltipWorldPos);
- // //ShowToolInfo(false, null, Vector3.zero);
- // tooltipStartFrame = 0;
- // tooltipText = null;
- // tooltipWorldPos = null;
- // }
- // }
- }
-
- // public bool DoRayCast(RaycastInput input, out RaycastOutput output) {
- // return RayCast(input, out output);
- // }
-
private static Vector3 prevMousePosition;
private bool DetermineHoveredElements() {
@@ -1210,611 +799,6 @@ internal static float GetAccurateHitHeight() {
return prev_H_Fixed = HitPos.y + 0.5f;
}
- /// Displays lane ids over lanes.
- // TODO: Extract into a Debug Tool GUI class
- private void DebugGuiDisplayLanes(ushort segmentId,
- ref NetSegment segment,
- ref NetInfo segmentInfo)
- {
- var _counterStyle = new GUIStyle();
- Vector3 centerPos = segment.m_bounds.center;
- bool visible = GeometryUtil.WorldToScreenPoint(centerPos, out Vector3 screenPos);
-
- if (!visible) {
- return;
- }
-
- screenPos.y -= 200;
-
- if (screenPos.z < 0) {
- return;
- }
-
- Vector3 camPos = Singleton.instance.m_simulationView.m_position;
- Vector3 diff = centerPos - camPos;
-
- if (diff.magnitude > DEBUG_CLOSE_LOD) {
- return; // do not draw if too distant
- }
-
- float zoom = 1.0f / diff.magnitude * 150f;
-
- _counterStyle.fontSize = (int)(11f * zoom);
- _counterStyle.normal.textColor = new Color(1f, 1f, 0f);
-
- // uint totalDensity = 0u;
- // for (int i = 0; i < segmentInfo.m_lanes.Length; ++i) {
- // if (CustomRoadAI.currentLaneDensities[segmentId] != null &&
- // i < CustomRoadAI.currentLaneDensities[segmentId].Length)
- // totalDensity += CustomRoadAI.currentLaneDensities[segmentId][i];
- // }
-
- uint curLaneId = segment.m_lanes;
- var labelSb = new StringBuilder();
- NetLane[] lanesBuffer = Singleton.instance.m_lanes.m_buffer;
-
- for (int i = 0; i < segmentInfo.m_lanes.Length; ++i) {
- if (curLaneId == 0) {
- break;
- }
-
- bool laneTrafficDataLoaded =
- TrafficMeasurementManager.Instance.GetLaneTrafficData(
- segmentId,
- (byte)i,
- out LaneTrafficData laneTrafficData);
-
- NetInfo.Lane laneInfo = segmentInfo.m_lanes[i];
-
-#if PFTRAFFICSTATS
- uint pfTrafficBuf =
- TrafficMeasurementManager
- .Instance.segmentDirTrafficData[
- TrafficMeasurementManager.Instance.GetDirIndex(
- segmentId,
- laneInfo.m_finalDirection)]
- .totalPathFindTrafficBuffer;
-#endif
- // TrafficMeasurementManager.Instance.GetTrafficData(segmentId,
- // laneInfo.m_finalDirection, out dirTrafficData);
- // int dirIndex = laneInfo.m_finalDirection == NetInfo.Direction.Backward ? 1 : 0;
-
- labelSb.AppendFormat("L idx {0}, id {1}", i, curLaneId);
-#if DEBUG
- labelSb.AppendFormat(
- ", in: {0}, out: {1}, f: {2}, l: {3} km/h, rst: {4}, dir: {5}, fnl: {6}, " +
- "pos: {7:0.##}, sim: {8} for {9}/{10}",
- RoutingManager.Instance.CalcInnerSimilarLaneIndex(segmentId, i),
- RoutingManager.Instance.CalcOuterSimilarLaneIndex(segmentId, i),
- (NetLane.Flags)lanesBuffer[curLaneId].m_flags,
- SpeedLimitManager.Instance.GetCustomSpeedLimit(curLaneId),
- VehicleRestrictionsManager.Instance.GetAllowedVehicleTypes(
- segmentId,
- segmentInfo,
- (uint)i,
- laneInfo,
- VehicleRestrictionsMode.Configured),
- laneInfo.m_direction,
- laneInfo.m_finalDirection,
- laneInfo.m_position,
- laneInfo.m_similarLaneIndex,
- laneInfo.m_vehicleType,
- laneInfo.m_laneType);
-#endif
- if (laneTrafficDataLoaded) {
- labelSb.AppendFormat(
- ", sp: {0}%",
- TrafficMeasurementManager.Instance.CalcLaneRelativeMeanSpeed(
- segmentId,
- (byte)i,
- curLaneId,
- laneInfo) / 100);
-#if DEBUG
- labelSb.AppendFormat(
- ", buf: {0}, max: {1}, acc: {2}",
- laneTrafficData.trafficBuffer,
- laneTrafficData.maxTrafficBuffer,
- laneTrafficData.accumulatedSpeeds);
-
-#if PFTRAFFICSTATS
- labelSb.AppendFormat(
- ", pfBuf: {0}/{1}, ({2} %)",
- laneTrafficData.pathFindTrafficBuffer,
- laneTrafficData.lastPathFindTrafficBuffer,
- pfTrafficBuf > 0
- ? "" + ((laneTrafficData.lastPathFindTrafficBuffer * 100u) /
- pfTrafficBuf)
- : "n/a");
-#endif
-#endif
-#if MEASUREDENSITY
- if (dirTrafficDataLoaded) {
- labelSb.AppendFormat(
- ", rel. dens.: {0}%",
- dirTrafficData.accumulatedDensities > 0
- ? "" + Math.Min(
- laneTrafficData[i].accumulatedDensities * 100 /
- dirTrafficData.accumulatedDensities,
- 100)
- : "?");
- }
-
- labelSb.AppendFormat(
- ", acc: {0}",
- laneTrafficData[i].accumulatedDensities);
-#endif
- }
-
- labelSb.AppendFormat(", nd: {0}", lanesBuffer[curLaneId].m_nodes);
-#if DEBUG
- // labelSb.AppendFormat(
- // " ({0}/{1}/{2})",
- // CustomRoadAI.currentLaneDensities[segmentId] != null &&
- // i < CustomRoadAI.currentLaneDensities[segmentId].Length
- // ? string.Empty + CustomRoadAI.currentLaneDensities[segmentId][i]
- // : "?",
- // CustomRoadAI.maxLaneDensities[segmentId] != null &&
- // i < CustomRoadAI.maxLaneDensities[segmentId].Length
- // ? string.Empty + CustomRoadAI.maxLaneDensities[segmentId][i]
- // : "?",
- // totalDensity);
- // labelSb.AppendFormat(
- // " ({0}/{1})",
- // CustomRoadAI.currentLaneDensities[segmentId] != null &&
- // i < CustomRoadAI.currentLaneDensities[segmentId].Length
- // ? string.Empty + CustomRoadAI.currentLaneDensities[segmentId][i]
- // : "?",
- // totalDensity);
-#endif
- // labelSb.AppendFormat(
- // ", abs. dens.: {0} %",
- // CustomRoadAI.laneMeanAbsDensities[segmentId] != null &&
- // i < CustomRoadAI.laneMeanAbsDensities[segmentId].Length
- // ? "" + CustomRoadAI.laneMeanAbsDensities[segmentId][i]
- // : "?");
- labelSb.Append("\n");
-
- curLaneId = lanesBuffer[curLaneId].m_nextLane;
- }
-
- var labelStr = labelSb.ToString();
- Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr));
- Rect labelRect = new Rect(screenPos.x - (dim.x / 2f), screenPos.y, dim.x, dim.y);
-
- GUI.Label(labelRect, labelStr, _counterStyle);
- }
-
- /// Displays segment ids over segments.
- // TODO: Extract into a Debug Tool GUI class
- private void DebugGuiDisplaySegments() {
- TrafficMeasurementManager trafficMeasurementManager = TrafficMeasurementManager.Instance;
- NetManager netManager = Singleton.instance;
- GUIStyle counterStyle = new GUIStyle();
- IExtSegmentEndManager endMan = Constants.ManagerFactory.ExtSegmentEndManager;
- NetSegment[] segmentsBuffer = netManager.m_segments.m_buffer;
-
- for (int i = 1; i < NetManager.MAX_SEGMENT_COUNT; ++i) {
- if ((segmentsBuffer[i].m_flags & NetSegment.Flags.Created) ==
- NetSegment.Flags.None) {
- // segment is unused
- continue;
- }
-
- ItemClass.Service service = segmentsBuffer[i].Info.GetService();
- ItemClass.SubService subService = segmentsBuffer[i].Info.GetSubService();
-#if !DEBUG
- if ((netManager.m_segments.m_buffer[i].m_flags & NetSegment.Flags.Untouchable) !=
- NetSegment.Flags.None) {
- continue;
- }
-#endif
- NetInfo segmentInfo = segmentsBuffer[i].Info;
-
- Vector3 centerPos = segmentsBuffer[i].m_bounds.center;
- bool visible = GeometryUtil.WorldToScreenPoint(centerPos, out Vector3 screenPos);
-
- if (!visible) {
- continue;
- }
-
- Vector3 camPos = Singleton.instance.m_simulationView.m_position;
- Vector3 diff = centerPos - camPos;
-
- if (diff.magnitude > DEBUG_CLOSE_LOD) {
- continue; // do not draw if too distant
- }
-
- float zoom = 1.0f / diff.magnitude * 150f;
- counterStyle.fontSize = (int)(12f * zoom);
- counterStyle.normal.textColor = new Color(1f, 0f, 0f);
-
- var labelSb = new StringBuilder();
- labelSb.AppendFormat("Segment {0}", i);
-#if DEBUG
- labelSb.AppendFormat(", flags: {0}", segmentsBuffer[i].m_flags);
- labelSb.AppendFormat("\nsvc: {0}, sub: {1}", service, subService);
-
- uint startVehicles = endMan.GetRegisteredVehicleCount(
- ref endMan.ExtSegmentEnds[endMan.GetIndex((ushort)i, true)]);
-
- uint endVehicles = endMan.GetRegisteredVehicleCount(
- ref endMan.ExtSegmentEnds[endMan.GetIndex((ushort)i, false)]);
-
- labelSb.AppendFormat( "\nstart veh.: {0}, end veh.: {1}", startVehicles, endVehicles);
-#endif
- labelSb.AppendFormat("\nTraffic: {0} %", segmentsBuffer[i].m_trafficDensity);
-
-#if DEBUG
- int fwdSegIndex = trafficMeasurementManager.GetDirIndex(
- (ushort)i,
- NetInfo.Direction.Forward);
- int backSegIndex = trafficMeasurementManager.GetDirIndex(
- (ushort)i,
- NetInfo.Direction.Backward);
-
- labelSb.Append("\n");
-
-#if MEASURECONGESTION
- float fwdCongestionRatio =
- trafficMeasurementManager
- .segmentDirTrafficData[fwdSegIndex].numCongestionMeasurements > 0
- ? ((uint)trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongested * 100u) /
- (uint)trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongestionMeasurements
- : 0; // now in %
- float backCongestionRatio =
- trafficMeasurementManager
- .segmentDirTrafficData[backSegIndex].numCongestionMeasurements > 0
- ? ((uint)trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongested * 100u) /
- (uint)trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongestionMeasurements
- : 0; // now in %
-
-
- labelSb.Append("min speeds: ");
- labelSb.AppendFormat(
- " {0}%/{1}%",
- trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].minSpeed / 100,
- trafficMeasurementManager.segmentDirTrafficData[backSegIndex].minSpeed /
- 100);
- labelSb.Append(", ");
-#endif
- labelSb.Append("mean speeds: ");
- labelSb.AppendFormat(
- " {0}%/{1}%",
- trafficMeasurementManager.SegmentDirTrafficData[fwdSegIndex].meanSpeed /
- 100,
- trafficMeasurementManager.SegmentDirTrafficData[backSegIndex].meanSpeed /
- 100);
-#if PFTRAFFICSTATS || MEASURECONGESTION
- labelSb.Append("\n");
-#endif
-#if PFTRAFFICSTATS
- labelSb.Append("pf bufs: ");
- labelSb.AppendFormat(
- " {0}/{1}",
- trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].totalPathFindTrafficBuffer,
- trafficMeasurementManager.segmentDirTrafficData[backSegIndex].totalPathFindTrafficBuffer);
-#endif
-#if PFTRAFFICSTATS && MEASURECONGESTION
- labelSb.Append(", ");
-#endif
-#if MEASURECONGESTION
- labelSb.Append("cong: ");
- labelSb.AppendFormat(
- " {0}% ({1}/{2})/{3}% ({4}/{5})",
- fwdCongestionRatio,
- trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongested,
- trafficMeasurementManager.segmentDirTrafficData[fwdSegIndex].numCongestionMeasurements,
- backCongestionRatio,
- trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongested,
- trafficMeasurementManager.segmentDirTrafficData[backSegIndex].numCongestionMeasurements);
-#endif
- labelSb.AppendFormat(
- "\nstart: {0}, end: {1}",
- segmentsBuffer[i].m_startNode,
- segmentsBuffer[i].m_endNode);
-#endif
-
- var labelStr = labelSb.ToString();
- Vector2 dim = counterStyle.CalcSize(new GUIContent(labelStr));
- Rect labelRect = new Rect(screenPos.x - (dim.x / 2f), screenPos.y, dim.x, dim.y);
-
- GUI.Label(labelRect, labelStr, counterStyle);
-
- if (Options.showLanes) {
- DebugGuiDisplayLanes(
- (ushort)i,
- ref segmentsBuffer[i],
- ref segmentInfo);
- }
- }
- }
-
- /// Displays node ids over nodes.
- // TODO: Extract into a Debug Tool GUI class
- private void DebugGuiDisplayNodes() {
- var counterStyle = new GUIStyle();
- NetManager netManager = Singleton.instance;
-
- for (int i = 1; i < NetManager.MAX_NODE_COUNT; ++i) {
- if ((netManager.m_nodes.m_buffer[i].m_flags & NetNode.Flags.Created) ==
- NetNode.Flags.None) {
- // node is unused
- continue;
- }
-
- Vector3 pos = netManager.m_nodes.m_buffer[i].m_position;
- bool visible = GeometryUtil.WorldToScreenPoint(pos, out Vector3 screenPos);
-
- if (!visible) {
- continue;
- }
-
- Vector3 camPos = Singleton.instance.m_simulationView.m_position;
- Vector3 diff = pos - camPos;
- if (diff.magnitude > DEBUG_CLOSE_LOD) {
- continue; // do not draw if too distant
- }
-
- float zoom = 1.0f / diff.magnitude * 150f;
-
- counterStyle.fontSize = (int)(15f * zoom);
- counterStyle.normal.textColor = new Color(0f, 0f, 1f);
-
- string labelStr = "Node " + i;
-#if DEBUG
- labelStr += string.Format(
- "\nflags: {0}\nlane: {1}",
- netManager.m_nodes.m_buffer[i].m_flags,
- netManager.m_nodes.m_buffer[i].m_lane);
-#endif
- Vector2 dim = counterStyle.CalcSize(new GUIContent(labelStr));
- var labelRect = new Rect(screenPos.x - (dim.x / 2f), screenPos.y, dim.x, dim.y);
-
- GUI.Label(labelRect, labelStr, counterStyle);
- }
- }
-
- /// Displays vehicle ids over vehicles.
- // TODO: Extract into a Debug Tool GUI class
- private void DebugGuiDisplayVehicles() {
- GUIStyle _counterStyle = new GUIStyle();
- SimulationManager simManager = Singleton.instance;
- ExtVehicleManager vehStateManager = ExtVehicleManager.Instance;
- VehicleManager vehicleManager = Singleton.instance;
-
- int startVehicleId = 1;
- int endVehicleId = Constants.ServiceFactory.VehicleService.MaxVehicleCount - 1;
-#if DEBUG
- if (DebugSettings.VehicleId != 0) {
- startVehicleId = endVehicleId = DebugSettings.VehicleId;
- }
-#endif
- Vehicle[] vehiclesBuffer = Singleton.instance.m_vehicles.m_buffer;
-
- for (int i = startVehicleId; i <= endVehicleId; ++i) {
- if (vehicleManager.m_vehicles.m_buffer[i].m_flags == 0) {
- // node is unused
- continue;
- }
-
- Vector3 vehPos = vehicleManager.m_vehicles.m_buffer[i].GetSmoothPosition((ushort)i);
- bool visible = GeometryUtil.WorldToScreenPoint(vehPos, out Vector3 screenPos);
-
- if (!visible) {
- continue;
- }
-
- Vector3 camPos = simManager.m_simulationView.m_position;
- Vector3 diff = vehPos - camPos;
- if (diff.magnitude > DEBUG_CLOSE_LOD) {
- continue; // do not draw if too distant
- }
-
- float zoom = 1.0f / diff.magnitude * 150f;
-
- _counterStyle.fontSize = (int)(10f * zoom);
- _counterStyle.normal.textColor = new Color(1f, 1f, 1f);
- // _counterStyle.normal.background = MakeTex(1, 1, new Color(0f, 0f, 0f, 0.4f));
-
- ExtVehicle vState = vehStateManager.ExtVehicles[(ushort)i];
- ExtCitizenInstance driverInst =
- ExtCitizenInstanceManager.Instance.ExtInstances[
- Constants.ManagerFactory.ExtVehicleManager
- .GetDriverInstanceId(
- (ushort)i,
- ref vehiclesBuffer[i])];
- // bool startNode = vState.currentStartNode;
- // ushort segmentId = vState.currentSegmentId;
-
- // Converting magnitudes into game speed float, and then into km/h
- SpeedValue vehSpeed = SpeedValue.FromVelocity(vehicleManager.m_vehicles.m_buffer[i].GetLastFrameVelocity().magnitude);
-#if DEBUG
- if (GlobalConfig.Instance.Debug.ExtPathMode != ExtPathMode.None &&
- driverInst.pathMode != GlobalConfig.Instance.Debug.ExtPathMode) {
- continue;
- }
-#endif
- string labelStr = string.Format(
- "V #{0} is a {1}{2} {3} @ ~{4} (len: {5:0.0}, {6} @ {7} ({8}), l. {9} " +
- "-> {10}, l. {11}), w: {12}\n" +
- "di: {13} dc: {14} m: {15} f: {16} l: {17} lid: {18} ltsu: {19} lpu: {20} " +
- "als: {21} srnd: {22} trnd: {23}",
- i,
- vState.recklessDriver ? "reckless " : string.Empty,
- vState.flags,
- vState.vehicleType,
- vehSpeed.ToKmphPrecise().ToString(),
- vState.totalLength,
- vState.junctionTransitState,
- vState.currentSegmentId,
- vState.currentStartNode,
- vState.currentLaneIndex,
- vState.nextSegmentId,
- vState.nextLaneIndex,
- vState.waitTime,
- driverInst.instanceId,
- ExtCitizenInstanceManager.Instance.GetCitizenId(driverInst.instanceId),
- driverInst.pathMode,
- driverInst.failedParkingAttempts,
- driverInst.parkingSpaceLocation,
- driverInst.parkingSpaceLocationId,
- vState.lastTransitStateUpdate,
- vState.lastPositionUpdate,
- vState.lastAltLaneSelSegmentId,
- Constants.ManagerFactory.ExtVehicleManager.GetStaticVehicleRand((ushort)i),
- Constants.ManagerFactory.ExtVehicleManager.GetTimedVehicleRand((ushort)i));
-
- Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr));
- Rect labelRect = new Rect(
- screenPos.x - (dim.x / 2f),
- screenPos.y - dim.y - 50f,
- dim.x,
- dim.y);
-
- GUI.Box(labelRect, labelStr, _counterStyle);
- }
- }
-
- /// Displays debug data over citizens.
- // TODO: Extract into a Debug Tool GUI class
- private void DebugGuiDisplayCitizens() {
- GUIStyle counterStyle = new GUIStyle();
- CitizenManager citManager = Singleton.instance;
- Citizen[] citizensBuffer = Singleton.instance.m_citizens.m_buffer;
- VehicleParked[] parkedVehiclesBuffer = Singleton.instance.m_parkedVehicles.m_buffer;
- Vehicle[] vehiclesBuffer = Singleton.instance.m_vehicles.m_buffer;
-
- for (int i = 1; i < CitizenManager.MAX_INSTANCE_COUNT; ++i) {
- if ((citManager.m_instances.m_buffer[i].m_flags &
- CitizenInstance.Flags.Character) == CitizenInstance.Flags.None) {
- continue;
- }
-#if DEBUG
- if (DebugSwitch.NoValidPathCitizensOverlay.Get()) {
-#endif
- if (citManager.m_instances.m_buffer[i].m_path != 0) {
- continue;
- }
-#if DEBUG
- }
-#endif
-
- Vector3 pos = citManager.m_instances.m_buffer[i].GetSmoothPosition((ushort)i);
- bool visible = GeometryUtil.WorldToScreenPoint(pos, out Vector3 screenPos);
-
- if (!visible) {
- continue;
- }
-
- Vector3 camPos = Singleton.instance.m_simulationView.m_position;
- Vector3 diff = pos - camPos;
-
- if (diff.magnitude > DEBUG_CLOSE_LOD) {
- continue; // do not draw if too distant
- }
-
- float zoom = 1.0f / diff.magnitude * 150f;
-
- counterStyle.fontSize = (int)(10f * zoom);
- counterStyle.normal.textColor = new Color(1f, 0f, 1f);
- // _counterStyle.normal.background = MakeTex(1, 1, new Color(0f, 0f, 0f, 0.4f));
-
-#if DEBUG
- if (GlobalConfig.Instance.Debug.ExtPathMode != ExtPathMode.None &&
- ExtCitizenInstanceManager.Instance.ExtInstances[i].pathMode !=
- GlobalConfig.Instance.Debug.ExtPathMode) {
- continue;
- }
-#endif
-
- var labelSb = new StringBuilder();
- ExtCitizen[] extCitizensBuf = ExtCitizenManager.Instance.ExtCitizens;
- labelSb.AppendFormat(
- "Inst. {0}, Cit. {1},\nm: {2}, tm: {3}, ltm: {4}, ll: {5}",
- i,
- citManager.m_instances.m_buffer[i].m_citizen,
- ExtCitizenInstanceManager.Instance.ExtInstances[i].pathMode,
- extCitizensBuf[citManager.m_instances.m_buffer[i].m_citizen].transportMode,
- extCitizensBuf[citManager.m_instances.m_buffer[i].m_citizen].lastTransportMode,
- extCitizensBuf[citManager.m_instances.m_buffer[i].m_citizen].lastLocation);
-
- if (citManager.m_instances.m_buffer[i].m_citizen != 0) {
- Citizen citizen = citizensBuffer[citManager.m_instances.m_buffer[i].m_citizen];
- if (citizen.m_parkedVehicle != 0) {
- labelSb.AppendFormat(
- "\nparked: {0} dist: {1}",
- citizen.m_parkedVehicle,
- (parkedVehiclesBuffer[citizen.m_parkedVehicle].m_position - pos).magnitude);
- }
-
- if (citizen.m_vehicle != 0) {
- labelSb.AppendFormat(
- "\nveh: {0} dist: {1}",
- citizen.m_vehicle,
- (vehiclesBuffer[citizen.m_vehicle].GetLastFramePosition() - pos).magnitude);
- }
- }
-
- string labelStr = labelSb.ToString();
- Vector2 dim = counterStyle.CalcSize(new GUIContent(labelStr));
- Rect labelRect = new Rect(
- screenPos.x - (dim.x / 2f),
- screenPos.y - dim.y - 50f,
- dim.x,
- dim.y);
-
- GUI.Box(labelRect, labelStr, counterStyle);
- }
- }
-
- // TODO: Extract into a Debug Tool GUI class
- private void DebugGuiDisplayBuildings() {
- GUIStyle _counterStyle = new GUIStyle();
- BuildingManager buildingManager = Singleton.instance;
-
- for (int i = 1; i < BuildingManager.MAX_BUILDING_COUNT; ++i) {
- if ((buildingManager.m_buildings.m_buffer[i].m_flags & Building.Flags.Created)
- == Building.Flags.None) {
- continue;
- }
-
- Vector3 pos = buildingManager.m_buildings.m_buffer[i].m_position;
- bool visible = GeometryUtil.WorldToScreenPoint(pos, out Vector3 screenPos);
-
- if (!visible) {
- continue;
- }
-
- Vector3 camPos = Singleton.instance.m_simulationView.m_position;
- Vector3 diff = pos - camPos;
- if (diff.magnitude > DEBUG_CLOSE_LOD) {
- continue; // do not draw if too distant
- }
-
- float zoom = 150f / diff.magnitude;
-
- _counterStyle.fontSize = (int)(10f * zoom);
- _counterStyle.normal.textColor = new Color(0f, 1f, 0f);
- // _counterStyle.normal.background = MakeTex(1, 1, new Color(0f, 0f, 0f, 0.4f));
-
- string labelStr = string.Format(
- "Building {0}, PDemand: {1}, IncTDem: {2}, OutTDem: {3}",
- i,
- ExtBuildingManager.Instance.ExtBuildings[i].parkingSpaceDemand,
- ExtBuildingManager.Instance.ExtBuildings[i].incomingPublicTransportDemand,
- ExtBuildingManager.Instance.ExtBuildings[i].outgoingPublicTransportDemand);
-
- Vector2 dim = _counterStyle.CalcSize(new GUIContent(labelStr));
- Rect labelRect = new Rect(
- screenPos.x - (dim.x / 2f),
- screenPos.y - dim.y - 50f,
- dim.x,
- dim.y);
-
- GUI.Box(labelRect, labelStr, _counterStyle);
- }
- }
-
new internal Color GetToolColor(bool warning, bool error) {
return base.GetToolColor(warning, error);
}
diff --git a/TLM/TLM/Util/FloatUtil.cs b/TLM/TLM/Util/FloatUtil.cs
index ebbc2f3b1..43829bea2 100644
--- a/TLM/TLM/Util/FloatUtil.cs
+++ b/TLM/TLM/Util/FloatUtil.cs
@@ -6,14 +6,12 @@ namespace TrafficManager.Util {
/// Provides static functions for handling floating point values.
///
public static class FloatUtil {
- ///
- /// A very small value for float comparisons to zero
- ///
+ /// A very small value for float comparisons to zero.
public const float VERY_SMALL_FLOAT = 1e-12f;
///
/// Checks whether two floats are very close to each other.
- /// Similar to which uses 1e-6 precision.
+ /// Similar to which is far more tight.
///
/// One float.
/// Another float.
diff --git a/TLM/TLM/Util/GenericFsm.cs b/TLM/TLM/Util/GenericFsm.cs
index 939160f7b..ba1c787f3 100644
--- a/TLM/TLM/Util/GenericFsm.cs
+++ b/TLM/TLM/Util/GenericFsm.cs
@@ -32,11 +32,11 @@ public Configurator(GenericFsm fsm, TState state) {
}
/// Calling this sets up an allowed FSM transition to the new state.
- /// Trigger which will allow transition from the current to the new state.
+ /// Trigger which will allow transition from the current to the new state.
/// The new state.
/// This.
- public Configurator TransitionOnEvent(TTrigger t, TState toState) {
- fsm_.Permit(state_, t, toState);
+ public Configurator TransitionOnEvent(TTrigger trigger, TState toState) {
+ fsm_.Permit(state_, trigger, toState);
return this;
}
diff --git a/TLM/TLM/Util/IntVector2.cs b/TLM/TLM/Util/IntVector2.cs
new file mode 100644
index 000000000..011d930dd
--- /dev/null
+++ b/TLM/TLM/Util/IntVector2.cs
@@ -0,0 +1,23 @@
+namespace TrafficManager.Util {
+ /// Represents a pair of integers.
+ public struct IntVector2 {
+ public int x;
+ public int y;
+
+ /// Initializes a new instance of the struct.
+ /// First value.
+ /// Second value.
+ public IntVector2(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ ///
+ /// Initializes a new instance of the struct with same value.
+ ///
+ /// Both first and second value.
+ public IntVector2(int xy) {
+ this.x = this.y = xy;
+ }
+ }
+}
\ No newline at end of file
diff --git a/TLM/TLM/Util/PriorityRoad.cs b/TLM/TLM/Util/PriorityRoad.cs
index 5acdbae8b..b6adde968 100644
--- a/TLM/TLM/Util/PriorityRoad.cs
+++ b/TLM/TLM/Util/PriorityRoad.cs
@@ -410,7 +410,7 @@ private static void FixMajorSegmentRules(ushort segmentId, ushort nodeId) {
private static void FixMinorSegmentRules(ushort segmentId, ushort nodeId, List segmentList) {
Log._Debug($"FixMinorSegmentRules({segmentId}, {nodeId}, segmentList) was called");
bool startNode = (bool)netService.IsStartNode(segmentId, nodeId);
- if (OptionsMassEditTab.PriorityRoad_EnterBlockedYeild) {
+ if (OptionsMassEditTab.PriorityRoad_EnterBlockedYield) {
JunctionRestrictionsManager.Instance.SetEnteringBlockedJunctionAllowed(segmentId, startNode, true);
}
if (HasAccelerationLane(segmentList, segmentId, nodeId)) {
@@ -424,15 +424,25 @@ private static void FixMinorSegmentRules(ushort segmentId, ushort nodeId, List CountLanes(segmentId, nodeId, true);
- internal static int CountLanesAgainstJunction(ushort segmentId, ushort nodeId) => CountLanes(segmentId, nodeId, false);
+
+ internal static int CountLanesTowardJunction(ushort segmentId, ushort nodeId) =>
+ CountLanes(
+ segmentId: segmentId,
+ nodeId: nodeId,
+ toward: true);
+
+ internal static int CountLanesAgainstJunction(ushort segmentId, ushort nodeId) =>
+ CountLanes(
+ segmentId: segmentId,
+ nodeId: nodeId,
+ toward: false);
internal static bool HasAccelerationLane(List segmentList, ushort segmentId, ushort nodeId) {
@@ -445,22 +455,22 @@ bool IsMain(ushort segId) {
}
ref NetSegment seg = ref segmentId.ToSegment();
- ushort MainAgainst, MainToward;
+ ushort mainAgainst, mainToward;
if (lht) {
- MainAgainst = seg.GetLeftSegment(nodeId);
- MainToward = seg.GetRightSegment(nodeId);
+ mainAgainst = seg.GetLeftSegment(nodeId);
+ mainToward = seg.GetRightSegment(nodeId);
} else {
- MainAgainst = seg.GetRightSegment(nodeId);
- MainToward = seg.GetLeftSegment(nodeId);
+ mainAgainst = seg.GetRightSegment(nodeId);
+ mainToward = seg.GetLeftSegment(nodeId);
}
- Log._Debug($"HasAccelerationLane: segmentId:{segmentId} MainToward={MainToward} MainAgainst={MainAgainst} ");
- if (IsMain(MainToward) && IsMain(MainAgainst)) {
- int Yt = CountLanesTowardJunction(segmentId, nodeId); // Yeild Toward.
- int Mt = CountLanesTowardJunction(MainToward, nodeId); // Main Toward.
- int Ma = CountLanesAgainstJunction(MainAgainst, nodeId); // Main Against.
- bool ret = Yt > 0 && Yt + Mt <= Ma;
- Log._Debug($"HasAccelerationLane: Yt={Yt} Mt={Mt} Ma={Ma} ret={ret} : Yt + Mt <= Ma ");
+ Log._Debug($"HasAccelerationLane: segmentId:{segmentId} MainToward={mainToward} MainAgainst={mainAgainst} ");
+ if (IsMain(mainToward) && IsMain(mainAgainst)) {
+ int yieldToward1 = CountLanesTowardJunction(segmentId, nodeId);
+ int mainToward1 = CountLanesTowardJunction(mainToward, nodeId);
+ int mainAgainst1 = CountLanesAgainstJunction(mainAgainst, nodeId);
+ bool ret = yieldToward1 > 0 && yieldToward1 + mainToward1 <= mainAgainst1;
+ Log._Debug($"HasAccelerationLane: Yt={yieldToward1} Mt={mainToward1} Ma={mainAgainst1} ret={ret} : Yt + Mt <= Ma ");
return ret;
}
@@ -483,12 +493,12 @@ private static void FixMajorSegmentLanes(ushort segmentId, ushort nodeId) {
//list of outgoing lanes from current segment to current node.
IList laneList =
netService.GetSortedLanes(
- segmentId,
- ref seg,
- startNode,
- LaneArrowManager.LANE_TYPES,
- LaneArrowManager.VEHICLE_TYPES,
- !lht);
+ segmentId: segmentId,
+ segment: ref seg,
+ startNode: startNode,
+ laneTypeFilter: LaneArrowManager.LANE_TYPES,
+ vehicleTypeFilter: LaneArrowManager.VEHICLE_TYPES,
+ reverse: !lht);
int srcLaneCount = laneList.Count;
Log._Debug($"FixMajorSegmentLanes: segment:{segmentId} laneList:" + laneList.ToSTR());
@@ -532,17 +542,22 @@ private static void FixMinorSegmentLanes(ushort segmentId, ushort nodeId, List laneList =
netService.GetSortedLanes(
- segmentId,
- ref seg,
- startNode,
- LaneArrowManager.LANE_TYPES,
- LaneArrowManager.VEHICLE_TYPES,
- true);
+ segmentId: segmentId,
+ segment: ref seg,
+ startNode: startNode,
+ laneTypeFilter: LaneArrowManager.LANE_TYPES,
+ vehicleTypeFilter: LaneArrowManager.VEHICLE_TYPES,
+ reverse: true);
int srcLaneCount = laneList.Count;
- bool bLeft, bRight, bForward;
+ bool bForward;
ref ExtSegmentEnd segEnd = ref GetSegEnd(segmentId, nodeId);
- segEndMan.CalculateOutgoingLeftStraightRightSegments(ref segEnd, ref node, out bLeft, out bForward, out bRight);
+ segEndMan.CalculateOutgoingLeftStraightRightSegments(
+ segEnd: ref segEnd,
+ node: ref node,
+ left: out bool bLeft,
+ straight: out bForward,
+ right: out bool bRight);
// LHD vs RHD variables.
bool lht = LaneArrowManager.Instance.Services.SimulationService.TrafficDrivesOnLeft;
@@ -591,11 +606,11 @@ private static int CountRoadVehicleLanes(ushort segmentId) {
ref NetSegment segment = ref segmentId.ToSegment();
int forward = 0, backward = 0;
segment.CountLanes(
- segmentId,
- NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle,
- VehicleInfo.VehicleType.Car,
- ref forward,
- ref backward);
+ segmentID: segmentId,
+ laneTypes: NetInfo.LaneType.Vehicle | NetInfo.LaneType.TransportVehicle,
+ vehicleTypes: VehicleInfo.VehicleType.Car,
+ forward: ref forward,
+ backward: ref backward);
return forward + backward;
}
@@ -605,14 +620,17 @@ private static int CountRoadVehicleLanes(ushort segmentId) {
///
public static void ClearNode(ushort nodeId) {
LaneConnectionManager.Instance.RemoveLaneConnectionsFromNode(nodeId);
- netService.IterateNodeSegments(nodeId, (ushort segmentId, ref NetSegment seg) => {
+
+ bool ForEachSegment(ushort segmentId, ref NetSegment seg) {
ref NetNode node = ref GetNode(nodeId);
bool startNode = (bool)netService.IsStartNode(segmentId, nodeId);
TrafficPriorityManager.Instance.SetPrioritySign(segmentId, startNode, PriorityType.None);
JunctionRestrictionsManager.Instance.ClearSegmentEnd(segmentId, startNode);
LaneArrowManager.Instance.ResetLaneArrows(segmentId, startNode);
return true;
- });
+ }
+
+ netService.IterateNodeSegments(nodeId, ForEachSegment);
}
///
@@ -620,12 +638,19 @@ public static void ClearNode(ushort nodeId) {
/// Clears segment ends of connected branchs as well.
///
public static IRecordable ClearRoad(List segmentList) {
- if (segmentList == null || segmentList.Count == 0)
+ if (segmentList == null || segmentList.Count == 0) {
return null;
+ }
+
IRecordable record = RecordRoad(segmentList);
+
foreach (ushort segmentId in segmentList) {
ParkingRestrictionsManager.Instance.SetParkingAllowed(segmentId, true);
- SpeedLimitManager.Instance.SetSpeedLimit(segmentId, null);
+
+ SpeedLimitManager.Instance.SetSegmentSpeedLimit(
+ segmentId,
+ SetSpeedLimitAction.Default());
+
VehicleRestrictionsManager.Instance.ClearVehicleRestrictions(segmentId);
ClearNode(netService.GetSegmentNodeId(segmentId, true));
ClearNode(netService.GetSegmentNodeId(segmentId, false));
@@ -638,8 +663,11 @@ public static IRecordable ClearRoad(List segmentList) {
///
public static IRecordable RecordRoad(List segmentList) {
TrafficRulesRecord record = new TrafficRulesRecord();
- foreach (ushort segmetnId in segmentList)
+
+ foreach (ushort segmetnId in segmentList) {
record.AddCompleteSegment(segmetnId);
+ }
+
record.Record();
return record;
}
diff --git a/TLM/TLM/Util/Record/LaneConnectionRecord.cs b/TLM/TLM/Util/Record/LaneConnectionRecord.cs
index 3eaa2d5bf..2392e6531 100644
--- a/TLM/TLM/Util/Record/LaneConnectionRecord.cs
+++ b/TLM/TLM/Util/Record/LaneConnectionRecord.cs
@@ -78,24 +78,29 @@ uint MappedLaneId(uint originalLaneID) {
public static List GetLanes(ushort nodeId) {
var ret = new List();
ref NetNode node = ref nodeId.ToNode();
+
for (int i = 0; i < 8; ++i) {
ushort segmentId = node.GetSegment(i);
- if (segmentId == 0) continue;
+ if (segmentId == 0) {
+ continue;
+ }
+
bool Handler(
uint laneId,
ref NetLane lane,
NetInfo.Lane laneInfo,
- ushort segmentId,
+ ushort segmentId1,
ref NetSegment segment,
byte laneIndex) {
bool match = (laneInfo.m_laneType & LaneConnectionManager.LANE_TYPES) != 0 &&
(laneInfo.m_vehicleType & LaneConnectionManager.VEHICLE_TYPES) != 0;
- if (!match)
+ if (!match) {
return true;
+ }
var laneData = new LaneConnectionRecord {
LaneId = laneId,
LaneIndex = laneIndex,
- StartNode = (bool)netService.IsStartNode(segmentId, nodeId),
+ StartNode = (bool)netService.IsStartNode(segmentId1, nodeId),
};
ret.Add(laneData);
return true;
@@ -104,6 +109,7 @@ bool Handler(
segmentId,
Handler);
}
+
return ret;
}
diff --git a/TLM/TLM/Util/Record/SpeedLimitLaneRecord.cs b/TLM/TLM/Util/Record/SpeedLimitLaneRecord.cs
index 2a21644d1..a41846404 100644
--- a/TLM/TLM/Util/Record/SpeedLimitLaneRecord.cs
+++ b/TLM/TLM/Util/Record/SpeedLimitLaneRecord.cs
@@ -14,14 +14,12 @@ public class SpeedLimitLaneRecord : IRecordable {
public byte LaneIndex;
public uint LaneId;
- private float? speedLimit_; // game units
+ private GetSpeedLimitResult speedLimit_; // game units, unlimited or not set
InstanceID InstanceID => new InstanceID { NetLane = LaneId };
public void Record() {
speedLimit_ = SpeedLimitManager.Instance.GetCustomSpeedLimit(LaneId);
- if (speedLimit_ == 0)
- speedLimit_ = null;
}
public void Restore() => Transfer(LaneId);
@@ -32,12 +30,18 @@ public void Transfer(Dictionary map) =>
public void Transfer(uint laneId) {
ushort segmentId = laneId.ToLane().m_segment;
var laneInfo = GetLaneInfo(segmentId, LaneIndex);
- SpeedLimitManager.Instance.SetSpeedLimit(
+ // SpeedLimitManager.Instance.SetSpeedLimit(
+ // segmentId: segmentId,
+ // laneIndex: LaneIndex,
+ // laneInfo: laneInfo,
+ // laneId: laneId,
+ // speedLimit: speedLimit_);
+ SpeedLimitManager.Instance.SetLaneSpeedLimit(
segmentId: segmentId,
- laneIndex: LaneIndex,
+ LaneIndex,
laneInfo: laneInfo,
- laneId: laneId,
- speedLimit: speedLimit_);
+ LaneId,
+ SetSpeedLimitAction.FromGetResult(speedLimit_));
}
public static List GetLanes(ushort segmentId) {
diff --git a/TLM/TLM/Util/RoundaboutMassEdit.cs b/TLM/TLM/Util/RoundaboutMassEdit.cs
index 1a34943f0..3e1bcc372 100644
--- a/TLM/TLM/Util/RoundaboutMassEdit.cs
+++ b/TLM/TLM/Util/RoundaboutMassEdit.cs
@@ -26,14 +26,23 @@ private static void FixSegmentRoundabout(ushort segmentId, ushort nextSegmentId)
if (OptionsMassEditTab.RoundAboutQuickFix_ParkingBanMainR) {
ParkingRestrictionsManager.Instance.SetParkingAllowed(segmentId, false);
}
+
if (OptionsMassEditTab.RoundAboutQuickFix_RealisticSpeedLimits) {
- float? targetSpeed = CalculatePreferedSpeed(segmentId)?.GameUnits;
+ SpeedValue? targetSpeed = CalculatePreferedSpeed(segmentId);
float defaultSpeed = SpeedLimitManager.Instance.GetCustomNetInfoSpeedLimit(segmentId.ToSegment().Info);
- if (targetSpeed != null && targetSpeed < defaultSpeed) {
- SpeedLimitManager.Instance.SetSpeedLimit(segmentId, NetInfo.Direction.Forward, targetSpeed);
- SpeedLimitManager.Instance.SetSpeedLimit(segmentId, NetInfo.Direction.Backward, targetSpeed);
+
+ if (targetSpeed != null && targetSpeed.Value.GetKmph() < defaultSpeed) {
+ SpeedLimitManager.Instance.SetSegmentSpeedLimit(
+ segmentId: segmentId,
+ finalDir: NetInfo.Direction.Forward,
+ action: SetSpeedLimitAction.SetSpeed(targetSpeed.Value));
+ SpeedLimitManager.Instance.SetSegmentSpeedLimit(
+ segmentId: segmentId,
+ finalDir: NetInfo.Direction.Backward,
+ action: SetSpeedLimitAction.SetSpeed(targetSpeed.Value));
}
}
+
ushort nodeId = netService.GetHeadNode(segmentId);
if (OptionsMassEditTab.RoundAboutQuickFix_StayInLaneMainR && !HasJunctionFlag(nodeId)) {
diff --git a/TLM/TLM/Util/SegmentLaneTraverser.cs b/TLM/TLM/Util/SegmentLaneTraverser.cs
index 107716549..90f4dceb6 100644
--- a/TLM/TLM/Util/SegmentLaneTraverser.cs
+++ b/TLM/TLM/Util/SegmentLaneTraverser.cs
@@ -107,10 +107,10 @@ bool VisitorProcessFun(ushort segmentId, ref NetSegment segment) {
if (!laneVisitor(
new SegmentLaneVisitData(
- segData,
- i,
- sortedLanes[i],
- initialSortedLanes[i]))) {
+ segVisitData: segData,
+ sortedLaneIndex: i,
+ curLanePos: sortedLanes[i],
+ initLanePos: initialSortedLanes[i]))) {
return false;
}
}
diff --git a/TLM/TMPE.API/TMPE.API.csproj b/TLM/TMPE.API/TMPE.API.csproj
index 3b6ccadbf..039432715 100644
--- a/TLM/TMPE.API/TMPE.API.csproj
+++ b/TLM/TMPE.API/TMPE.API.csproj
@@ -121,7 +121,9 @@
+
+
diff --git a/TLM/TMPE.API/Traffic/ApiConstants.cs b/TLM/TMPE.API/Traffic/ApiConstants.cs
index c17febb26..90821916f 100644
--- a/TLM/TMPE.API/Traffic/ApiConstants.cs
+++ b/TLM/TMPE.API/Traffic/ApiConstants.cs
@@ -6,9 +6,9 @@ public static class ApiConstants {
public const float SPEED_TO_KMPH = 50.0f; // 1.0f equals 50 km/h
///
- /// Conversion rate from MPH to game speed (also exists in TrafficManager.Constants)
+ /// Conversion rate from MPH to game speed.
///
- public const float SPEED_TO_MPH = 32.06f; // 50 km/h converted to mph
+ public const float SPEED_TO_MPH = 31.06856f;
///
/// Multiplier used to convert between game speedlimits and velocities (directed speeds)
diff --git a/TLM/TMPE.API/Traffic/Data/KmphValue.cs b/TLM/TMPE.API/Traffic/Data/KmphValue.cs
new file mode 100644
index 000000000..a37f7980c
--- /dev/null
+++ b/TLM/TMPE.API/Traffic/Data/KmphValue.cs
@@ -0,0 +1,22 @@
+namespace TrafficManager.API.Traffic.Data {
+ using System;
+
+ ///
+ /// Represents a speed value expressed in km/hour.
+ ///
+ [Serializable]
+ public readonly struct KmphValue {
+ public KmphValue(ushort kmph) {
+ Kmph = kmph;
+ }
+
+ public override string ToString() => $"{Kmph:0.0} km/h";
+ public string ToIntegerString() => $"{Kmph} km/h";
+
+ public ushort Kmph { get; }
+
+ /// A new KmphValue increased by right
+ public static KmphValue operator +(KmphValue left, ushort right)
+ => new KmphValue((ushort)(left.Kmph + right));
+ }
+}
\ No newline at end of file
diff --git a/TLM/TMPE.API/Traffic/Data/MphValue.cs b/TLM/TMPE.API/Traffic/Data/MphValue.cs
new file mode 100644
index 000000000..9752e381e
--- /dev/null
+++ b/TLM/TMPE.API/Traffic/Data/MphValue.cs
@@ -0,0 +1,21 @@
+namespace TrafficManager.API.Traffic.Data {
+ using System;
+
+ ///
+ /// Represents a speed value expressed in miles/hour.
+ ///
+ [Serializable]
+ public readonly struct MphValue {
+ public MphValue(ushort mph) {
+ Mph = mph;
+ }
+
+ public override string ToString() => $"{Mph} MPH";
+
+ public ushort Mph { get; }
+
+ /// A new MphValue increased by right
+ public static MphValue operator +(MphValue left, ushort right)
+ => new MphValue((ushort)(left.Mph + right));
+ }
+}
\ No newline at end of file
diff --git a/TLM/TMPE.API/Traffic/Data/SpeedValue.cs b/TLM/TMPE.API/Traffic/Data/SpeedValue.cs
index 77beca7a5..eb9e211a1 100644
--- a/TLM/TMPE.API/Traffic/Data/SpeedValue.cs
+++ b/TLM/TMPE.API/Traffic/Data/SpeedValue.cs
@@ -41,7 +41,7 @@ public static SpeedValue FromVelocity(float vel)
///
/// A speed in kilometres/hour
/// A new speedvalue converted to the game units
- public static SpeedValue FromKmph(ushort kmph)
+ public static SpeedValue FromKmph(float kmph)
=> new SpeedValue(kmph / ApiConstants.SPEED_TO_KMPH);
///
@@ -57,7 +57,7 @@ public static SpeedValue FromKmph(KmphValue kmph)
///
/// A speed in miles/hour
/// A new speedvalue converted to the game units
- public static SpeedValue FromMph(ushort mph)
+ public static SpeedValue FromMph(float mph)
=> new SpeedValue(mph / ApiConstants.SPEED_TO_MPH);
///
@@ -110,41 +110,16 @@ public MphValue ToMphPrecise()
/// A typed km/h value with integer km/hour
public KmphValue ToKmphPrecise()
=> new KmphValue((ushort)Mathf.Round(GameUnits * ApiConstants.SPEED_TO_KMPH));
- }
-
- ///
- /// Represents a speed value expressed in km/hour.
- ///
- [Serializable]
- public readonly struct KmphValue {
- public KmphValue(ushort kmph) {
- Kmph = kmph;
- }
-
- public override string ToString() => $"{Kmph:0.0} km/h";
- public ushort Kmph { get; }
+ public static SpeedValue operator +(SpeedValue left, SpeedValue right)
+ => new SpeedValue(left.GameUnits + right.GameUnits);
- /// A new KmphValue increased by right
- public static KmphValue operator +(KmphValue left, ushort right)
- => new KmphValue((ushort)(left.Kmph + right));
- }
-
- ///
- /// Represents a speed value expressed in miles/hour.
- ///
- [Serializable]
- public readonly struct MphValue {
- public MphValue(ushort mph) {
- Mph = mph;
+ /// Scales the value by the multiplier.
+ /// Scale.
+ public SpeedValue Scale(float n) {
+ return new SpeedValue(this.GameUnits * n);
}
- public override string ToString() => $"{Mph} MPH";
-
- public ushort Mph { get; }
-
- /// A new MphValue increased by right
- public static MphValue operator +(MphValue left, ushort right)
- => new MphValue((ushort)(left.Mph + right));
+ public float GetKmph() => GameUnits * ApiConstants.SPEED_TO_KMPH;
}
}