Code/General/FloatRange.cs
using Sandbox;
using System;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ExtendedBox.General;
[JsonConverter(typeof(JsonConverter))]
public struct FloatRange : IEquatable<FloatRange>, IParsable<FloatRange> //, IFormattable // TODO: implement IFormattable when https://github.com/Facepunch/sbox-public/issues/10191 get resolved
{
private float _min = 0;
private float _max = 0;
public float Min
{
readonly get => _min;
set => _min = Math.Min(_max, value);
}
public float Max
{
readonly get => _max;
set => _max = Math.Max(_min, value);
}
[JsonIgnore, Hide]
public readonly float Delta => Max - Min;
public FloatRange()
{
}
[JsonConstructor]
public FloatRange(float min, float max)
{
_min = Math.Min(min, max);
_max = Math.Max(min, max);
}
public static FloatRange Parse(string? str, IFormatProvider? provider)
{
if(TryParse(str, provider, out var result))
return result;
return default;
}
public static FloatRange Parse(string? str)
{
return Parse(str, CultureInfo.InvariantCulture);
}
public static bool TryParse(string str, out FloatRange result)
{
return TryParse(str, CultureInfo.InvariantCulture, out result);
}
public static bool TryParse(string? str, IFormatProvider? provider, out FloatRange result)
{
result = default;
if(string.IsNullOrWhiteSpace(str))
{
return false;
}
str = str.Trim('[', ']', ' ', '\n', '\r', '\t', '"');
string[] array = str.Split([' ', ',', ';', '\n', '\r'], StringSplitOptions.RemoveEmptyEntries);
if(array.Length != 2)
{
return false;
}
if(!float.TryParse(array[0], NumberStyles.Float, provider, out var result2) ||
!float.TryParse(array[1], NumberStyles.Float, provider, out var result3))
{
return false;
}
result = new(result2, result3);
return true;
}
public readonly bool Equals(FloatRange other) => Min == other.Min && Max == other.Max;
public override readonly bool Equals(object? obj) => obj is FloatRange other && Equals(other);
public static bool operator ==(FloatRange left, FloatRange right) => left.Equals(right);
public static bool operator !=(FloatRange left, FloatRange right) => !(left == right);
public override readonly int GetHashCode() => HashCode.Combine(Min, Max);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly string ToString(IFormatProvider? formatProvider) => ToString(null, formatProvider);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly string ToString(string? format) => ToString(format, null);
public readonly string ToString(string? format, IFormatProvider? formatProvider) => $"{Min.ToString(format, formatProvider)},{Max.ToString(format, formatProvider)}";
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override readonly string ToString() => ToString(null, null);
public class JsonConverter : JsonConverter<FloatRange>
{
public override FloatRange Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
if(reader.TokenType == JsonTokenType.Null)
return default;
if(reader.TokenType == JsonTokenType.String)
return Parse(reader.GetString());
if(reader.TokenType == JsonTokenType.StartArray)
{
reader.Read();
float min = 0;
float max = 0;
if(reader.TokenType == JsonTokenType.Number)
{
max = min = reader.GetSingle();
reader.Read();
}
if(reader.TokenType == JsonTokenType.Number)
{
max = reader.GetSingle();
reader.Read();
}
while(reader.TokenType != JsonTokenType.EndArray)
{
reader.Read();
}
return new(min, max);
}
Log.Warning($"{typeof(JsonConverter).FullName} - unable to read from {reader.TokenType}");
return default;
}
public override void Write(Utf8JsonWriter writer, FloatRange value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString());
}
public override FloatRange ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return Parse(reader.GetString());
}
public override void WriteAsPropertyName(Utf8JsonWriter writer, FloatRange value, JsonSerializerOptions options)
{
writer.WritePropertyName(value.ToString()!);
}
public override bool CanConvert(Type typeToConvert)
{
return typeToConvert == typeof(FloatRange) || typeToConvert == typeof(string);
}
}
}