first commit
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Nitrox.Launcher.Models.Utils;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Converters;
|
||||
|
||||
public class BitmapAssetValueConverter : Converter<BitmapAssetValueConverter>
|
||||
{
|
||||
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
value switch
|
||||
{
|
||||
null => null,
|
||||
Bitmap when targetType.IsAssignableFrom(typeof(Bitmap)) => value,
|
||||
string s when targetType.IsAssignableFrom(typeof(Bitmap)) => AssetHelper.GetAssetFromStream(s, static stream => new Bitmap(stream)),
|
||||
_ => throw new NotSupportedException()
|
||||
};
|
||||
}
|
45
Nitrox.Launcher/Models/Converters/BoolToIconConverter.cs
Normal file
45
Nitrox.Launcher/Models/Converters/BoolToIconConverter.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Nitrox.Launcher.Models.Utils;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Converters;
|
||||
|
||||
public sealed class BoolToIconConverter : MarkupExtension, IValueConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// String that will be outputted if the input boolean value is <c>true</c>
|
||||
/// </summary>
|
||||
public string True { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// String that will be outputted if the input boolean value is <c>false</c>
|
||||
/// </summary>
|
||||
public string False { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Decides if the converter will inverse the input boolean value before computing the output
|
||||
/// </summary>
|
||||
public bool Invert { get; set; }
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not bool @bool)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Invert)
|
||||
{
|
||||
@bool = !@bool;
|
||||
}
|
||||
|
||||
return AssetHelper.GetAssetFromStream(@bool ? True : False, static stream => new Bitmap(stream));
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
|
||||
|
||||
public override object ProvideValue(IServiceProvider serviceProvider) => this;
|
||||
}
|
21
Nitrox.Launcher/Models/Converters/Converter.cs
Normal file
21
Nitrox.Launcher/Models/Converters/Converter.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// A converter base class that provides itself as value to the XAML compiler.
|
||||
/// </summary>
|
||||
public abstract class Converter<TSelf> : MarkupExtension, IValueConverter
|
||||
where TSelf : Converter<TSelf>, new()
|
||||
{
|
||||
private static TSelf Instance { get; } = new();
|
||||
|
||||
public sealed override object ProvideValue(IServiceProvider serviceProvider) => Instance;
|
||||
|
||||
public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture);
|
||||
|
||||
public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotSupportedException();
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Formats the bound value as a relative date string from a DateTime value.
|
||||
/// </summary>
|
||||
public class DateToRelativeDateConverter : Converter<DateToRelativeDateConverter>
|
||||
{
|
||||
private const float DAYS_IN_YEAR = 365.2425f;
|
||||
private const float MEAN_DAYS_IN_MONTH = DAYS_IN_YEAR / 12f;
|
||||
|
||||
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
DateTimeOffset date = value switch
|
||||
{
|
||||
DateTime dateTime => dateTime,
|
||||
DateTimeOffset dateTimeOffset => dateTimeOffset,
|
||||
DateOnly dateOnly => dateOnly.ToDateTime(TimeOnly.MinValue),
|
||||
string text when DateTimeOffset.TryParse(text, out DateTimeOffset offset) => offset,
|
||||
_ => throw new ArgumentException($"Value must be a {nameof(DateTime)} or {nameof(DateTimeOffset)}", nameof(value))
|
||||
};
|
||||
|
||||
TimeSpan delta = DateTimeOffset.UtcNow - date.UtcDateTime;
|
||||
|
||||
return delta switch
|
||||
{
|
||||
{ TotalSeconds: < 1 } => "just now",
|
||||
{ TotalSeconds: < 2 } => "a second ago",
|
||||
{ TotalMinutes: < 1 } => $"{(int)delta.TotalSeconds} seconds ago",
|
||||
{ TotalMinutes: < 2 } => "a minute ago",
|
||||
{ TotalMinutes: < 45 } => $"{(int)delta.TotalMinutes} minutes ago",
|
||||
{ TotalHours: < 1.5 } => "an hour ago",
|
||||
{ TotalDays: < 1 } => $"{(int)delta.TotalHours} hours ago",
|
||||
{ TotalDays: < 2 } => "yesterday",
|
||||
{ TotalDays: < MEAN_DAYS_IN_MONTH } => $"{(int)delta.TotalDays} days ago",
|
||||
{ TotalDays: < MEAN_DAYS_IN_MONTH * 2 } => "a month ago",
|
||||
{ TotalDays: < DAYS_IN_YEAR } => $"{(int)(delta.TotalDays / MEAN_DAYS_IN_MONTH)} months ago",
|
||||
{ TotalDays: < DAYS_IN_YEAR * 2 } => "a year ago",
|
||||
_ => $"{(int)(delta.TotalDays / DAYS_IN_YEAR)} years ago"
|
||||
};
|
||||
}
|
||||
}
|
22
Nitrox.Launcher/Models/Converters/DeduplicateConverter.cs
Normal file
22
Nitrox.Launcher/Models/Converters/DeduplicateConverter.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Removes duplicates by non-unique ToString values of the given list.
|
||||
/// </summary>
|
||||
public class DeduplicateConverter : Converter<DeduplicateConverter>
|
||||
{
|
||||
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not IEnumerable<object> list)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return list.DistinctBy(i => i.ToString());
|
||||
}
|
||||
}
|
36
Nitrox.Launcher/Models/Converters/EqualityConverter.cs
Normal file
36
Nitrox.Launcher/Models/Converters/EqualityConverter.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if values are equal to each other.
|
||||
/// Or if value is singular, if parameter is equal to the value.
|
||||
/// </summary>
|
||||
public class EqualityConverter : Converter<EqualityConverter>, IMultiValueConverter
|
||||
{
|
||||
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) => Equals(value, parameter);
|
||||
|
||||
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotSupportedException();
|
||||
|
||||
public object Convert(IList<object> values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
foreach (object val1 in values)
|
||||
{
|
||||
foreach (object val2 in values)
|
||||
{
|
||||
if (ReferenceEquals(val1, val2))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!Equals(val1, val2))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
43
Nitrox.Launcher/Models/Converters/IntToStringConverter.cs
Normal file
43
Nitrox.Launcher/Models/Converters/IntToStringConverter.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Formats the bound value as a string from an integer.
|
||||
/// </summary>
|
||||
public partial class IntToStringConverter : Converter<IntToStringConverter>
|
||||
{
|
||||
[GeneratedRegex("[^0-9]")]
|
||||
private static partial Regex DigitReplaceRegex { get; }
|
||||
|
||||
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return value?.ToString() ?? "";
|
||||
}
|
||||
|
||||
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (value is not string str)
|
||||
{
|
||||
str = value.ToString();
|
||||
if (str is null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
str = DigitReplaceRegex.Replace(str, "");
|
||||
if (int.TryParse(str, out int result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
34
Nitrox.Launcher/Models/Converters/IsTypeConverter.cs
Normal file
34
Nitrox.Launcher/Models/Converters/IsTypeConverter.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if value is of the type as given by parameter (or any if parameter is a collection of types).
|
||||
/// </summary>
|
||||
public class IsTypeConverter : Converter<IsTypeConverter>
|
||||
{
|
||||
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
switch (parameter)
|
||||
{
|
||||
case Type typeParameter:
|
||||
return typeParameter.IsInstanceOfType(value);
|
||||
case IEnumerable<Type> typeParameters:
|
||||
{
|
||||
foreach (Type type in typeParameters)
|
||||
{
|
||||
if (type.IsInstanceOfType(value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
return new BindingNotification(new ArgumentException($"Expected {nameof(parameter)} to be a {typeof(Type).FullName}"), BindingErrorType.Error);
|
||||
}
|
||||
}
|
||||
}
|
24
Nitrox.Launcher/Models/Converters/PlatformToIconConverter.cs
Normal file
24
Nitrox.Launcher/Models/Converters/PlatformToIconConverter.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Nitrox.Launcher.Models.Utils;
|
||||
using NitroxModel.Discovery.Models;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Converters;
|
||||
|
||||
public class PlatformToIconConverter : Converter<PlatformToIconConverter>
|
||||
{
|
||||
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return AssetHelper.GetAssetFromStream(GetIconPathForPlatform(value as Platform?), static stream => new Bitmap(stream));
|
||||
}
|
||||
|
||||
private static string GetIconPathForPlatform(Platform? platform) => platform switch
|
||||
{
|
||||
Platform.EPIC => "/Assets/Images/store-icons/epic.png",
|
||||
Platform.STEAM => "/Assets/Images/store-icons/steam.png",
|
||||
Platform.MICROSOFT => "/Assets/Images/store-icons/xbox.png",
|
||||
Platform.DISCORD => "/Assets/Images/store-icons/discord.png",
|
||||
_ => "/Assets/Images/store-icons/missing.png",
|
||||
};
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Formats the bound value as "0 BoundValue 0 BoundValue" Margin from a Padding, used for TextBox styling.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This converter is used to solve a niche issue with the styling of TextBoxes.
|
||||
/// </remarks>
|
||||
public class TextBoxPaddingToMarginConverter : Converter<TextBoxPaddingToMarginConverter>
|
||||
{
|
||||
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not Thickness padding)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
bool isNegative = parameter != null && bool.TryParse(parameter.ToString(), out bool result) && result;
|
||||
double top = isNegative ? -padding.Top : padding.Top;
|
||||
double bottom = isNegative ? -padding.Bottom : padding.Bottom;
|
||||
return new Thickness(0, top, 0, bottom);
|
||||
}
|
||||
|
||||
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
30
Nitrox.Launcher/Models/Converters/ToIntConverter.cs
Normal file
30
Nitrox.Launcher/Models/Converters/ToIntConverter.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Converters;
|
||||
|
||||
public class ToIntConverter : Converter<ToIntConverter>
|
||||
{
|
||||
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
try
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
int i => i,
|
||||
string valueStr when int.TryParse(valueStr, out int result) => result,
|
||||
ICollection list => list.Count,
|
||||
IEnumerable enumerable => enumerable.Cast<object>().Count(),
|
||||
_ => System.Convert.ToInt32(value)
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => value;
|
||||
}
|
45
Nitrox.Launcher/Models/Converters/ToStringConverter.cs
Normal file
45
Nitrox.Launcher/Models/Converters/ToStringConverter.cs
Normal file
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data;
|
||||
using NitroxModel.Helper;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Formats the bound value as a string using a specific formatting style.
|
||||
/// </summary>
|
||||
public class ToStringConverter : Converter<ToStringConverter>
|
||||
{
|
||||
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value.GetType().IsEnum)
|
||||
{
|
||||
value = (value as Enum)?.GetAttribute<DescriptionAttribute>()?.Description ?? value.ToString();
|
||||
}
|
||||
|
||||
if (value is not string sourceText)
|
||||
{
|
||||
sourceText = value?.ToString();
|
||||
}
|
||||
|
||||
if (!targetType.IsAssignableTo(typeof(string)) || sourceText == null)
|
||||
{
|
||||
return new BindingNotification(new InvalidCastException(), BindingErrorType.Error);
|
||||
}
|
||||
|
||||
return parameter switch
|
||||
{
|
||||
"upper" => sourceText.ToUpperInvariant(),
|
||||
"lower" => sourceText.ToLowerInvariant(),
|
||||
_ => CultureManager.CultureInfo.TextInfo.ToTitleCase(sourceText.ToLower().Replace("_", " ")),
|
||||
};
|
||||
}
|
||||
|
||||
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => value;
|
||||
}
|
67
Nitrox.Launcher/Models/Converters/TrimConverter.cs
Normal file
67
Nitrox.Launcher/Models/Converters/TrimConverter.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
|
||||
namespace Nitrox.Launcher.Models.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Trims the value when retrieved by code but keeps the spaces in the input field intact for improved UX.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This converter is unconventional (inverted converter) in that the value is converted for the backend.
|
||||
/// The user wants to be able to input spaces while they're typing, but we don't want to save those spaces.
|
||||
/// </remarks>
|
||||
public class TrimConverter : Converter<TrimConverter>
|
||||
{
|
||||
private readonly Lock inOutCacheLock = new();
|
||||
/// <summary>
|
||||
/// Cache to remember the last known untrimmed value (here, the value) for trimmed values (here, the key).
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, string> inOutCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// Converts trimmed value back to last known untrimmed value.
|
||||
/// </summary>
|
||||
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not string strValue)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
lock (inOutCacheLock)
|
||||
{
|
||||
if (inOutCache.TryGetValue(strValue.Trim(), out string untrimmedValue))
|
||||
{
|
||||
strValue = untrimmedValue;
|
||||
}
|
||||
}
|
||||
return strValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts untrimmed value back to trimmed value.
|
||||
/// </summary>
|
||||
public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is not string strValue)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
if (!strValue.StartsWith(' ') && !strValue.EndsWith(' '))
|
||||
{
|
||||
// It's safe to reset cache now.
|
||||
lock (inOutCacheLock)
|
||||
{
|
||||
inOutCache.Clear();
|
||||
}
|
||||
return strValue;
|
||||
}
|
||||
string trim = strValue.Trim();
|
||||
lock (inOutCacheLock)
|
||||
{
|
||||
inOutCache[trim] = strValue;
|
||||
}
|
||||
return trim;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user