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 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + These signs are using DIN 1451 font for German Road Signs, namely the Mittelschrift variant, and for narrow 100+ speeds using Engschrift1. Use Inkscape editor and open the file2. Select road sign3. Menu "File" → Export (panel can be conveniently docked)4. Ensure you are exporting "Selection" of square size5. Set Image Size to 200x200 pxP.S. Font letter spacing might need to be adjusted (like 1 being off to the left and 5 being off-center too) Image from Wikipedia for Minimal Speedlimit + + + + 140 + + + + + 20 + + + + + 15 + + + + + 10 + + + + + 5 + + + + + 45 + + + + + 85 + + + + + 25 + + + + + 30 + + + + + 35 + + + + + 40 + + + + + 60 + + + + + 55 + + + + + 50 + + + + + 65 + + + + + 70 + + + + + 75 + + + + + 80 + + + + + 100 + + + + + 95 + + + + + 90 + + + + + 105 + + + + + 110 + + + + + 115 + + + + + 120 + + + + + 135 + + + + + 130 + + + + + 125 + + + + + + + + + + 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 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + 5 + These signs are using DIN 1451 font for German Road Signs, namely the Mittelschrift variant, and for narrow 100+ speeds using Engschrift1. Use Inkscape editor and open the file2. Select road sign3. Menu "File" → Export (panel can be conveniently docked)4. Ensure you are exporting "Selection" of square size5. Set Image Size to 200x200 px Image from Wikipedia for Minimal Speedlimit + + + 10 + + + + 15 + + + + 20 + + + + 30 + + + + 40 + + + + 50 + + + + 60 + + + + 70 + + + + 80 + + + + 90 + + + + 100 + + + + 110 + + + + 120 + + + + 130 + + + + 140 + + + + + + + + + 35 + + + + 55 + + + + 65 + + + + 75 + + + + 95 + + + + 105 + + + + 115 + + + + 135 + + + + 45 + + + + 85 + + + + 125 + + + + 25 + + 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; } }