Skip to content

Commit 664c5df

Browse files
authored
Merge pull request #1116 from PlayEveryWare/fix/product-version-validation
feat: User experience for entering invalid product version.
2 parents b1d716e + c5533e8 commit 664c5df

22 files changed

Lines changed: 391 additions & 53 deletions

Assets/Plugins/Source/Editor/Utility/GUIEditorUtility.cs

Lines changed: 63 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ namespace PlayEveryWare.EpicOnlineServices.Editor.Utility
3636
using System.Globalization;
3737
using System.Linq;
3838
using System.Reflection;
39+
using System.Text;
3940
using UnityEditor;
4041
using UnityEditorInternal;
4142
using UnityEngine;
@@ -245,7 +246,7 @@ public static void AssigningEnumField<T>(string label, ref T value, float labelW
245246
.OrderBy(group => group.Key);
246247
}
247248

248-
private static IOrderedEnumerable<IGrouping<int, (MemberInfo MemberInfo, ConfigFieldAttribute FieldDetails)>> GetMembersByGroup<T>()
249+
private static IOrderedEnumerable<IGrouping<int, (MemberInfo MemberInfo, ConfigFieldAttribute FieldDetails, IEnumerable<FieldValidatorAttribute> FieldValidators)>> GetMembersByGroup<T>()
249250
{
250251
var fields = typeof(T).GetFields(BindingFlags.Public | BindingFlags.Instance);
251252
var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
@@ -254,7 +255,8 @@ public static void AssigningEnumField<T>(string label, ref T value, float labelW
254255

255256
return members
256257
.Where(member => member.GetCustomAttribute<ConfigFieldAttribute>() != null)
257-
.Select(member => (MemberInfo: member, FieldDetails: member.GetCustomAttribute<ConfigFieldAttribute>()))
258+
.Select(member => (MemberInfo: member, FieldDetails: member.GetCustomAttribute<ConfigFieldAttribute>(),
259+
FieldValidators: member.GetCustomAttributes<FieldValidatorAttribute>()))
258260
.GroupBy(r => r.FieldDetails.Group)
259261
.OrderBy(group => group.Key);
260262
}
@@ -271,7 +273,6 @@ public static void AssigningEnumField<T>(string label, ref T value, float labelW
271273
{ typeof(float), (attr, val, width) => RenderInput(attr, (float)val, width) },
272274
{ typeof(double), (attr, val, width) => RenderInput(attr, (double)val, width) },
273275
{ typeof(bool), (attr, val, width) => RenderInput(attr, (bool)val, width) },
274-
{ typeof(Version), (attr, val, width) => RenderInput(attr, (Version)val, width) },
275276
{ typeof(Guid), (attr, val, width) => RenderInput(attr, (Guid)val, width)},
276277
{ typeof(List<string>), (attr, val, width) => RenderInput(attr, (List<string>)val, width)},
277278
#if !EOS_DISABLE
@@ -500,6 +501,9 @@ public static void RenderInputs<T>(ref T value)
500501
{
501502
continue; // Skip if MemberInfo is neither FieldInfo nor PropertyInfo
502503
}
504+
505+
// Assign the validators
506+
member.FieldDetails.Validators = member.FieldValidators;
503507

504508
// Use the handler from the dictionary
505509
if (FieldHandlers.TryGetValue(member.FieldDetails.FieldType, out var handler))
@@ -1126,7 +1130,7 @@ public static WrappedInitializeThreadAffinity RenderInput(ConfigFieldAttribute a
11261130
#endif
11271131
private static Guid RenderInput(ConfigFieldAttribute configFieldDetails, Guid value, float labelWidth)
11281132
{
1129-
return InputRendererWrapper(configFieldDetails.Label, configFieldDetails.ToolTip, labelWidth, value,
1133+
return InputRendererWrapper(configFieldDetails.Label, configFieldDetails.ToolTip, labelWidth, value, configFieldDetails.Validators,
11301134
GuidField);
11311135
}
11321136

@@ -1212,22 +1216,13 @@ public static Deployment RenderInput(ConfigFieldAttribute configFieldAttribute,
12121216
public static TEnum RenderEnumInput<TEnum>(ConfigFieldAttribute configFieldAttribute, TEnum value, float labelWidth) where TEnum : Enum
12131217
{
12141218
return InputRendererWrapper(configFieldAttribute.Label, configFieldAttribute.ToolTip, labelWidth, value,
1215-
EnumFlagsField, configFieldAttribute.HelpURL);
1219+
configFieldAttribute.Validators, EnumFlagsField, configFieldAttribute.HelpURL);
12161220
}
12171221

12181222
private static TEnum EnumFlagsField<TEnum>(GUIContent label, TEnum value, params GUILayoutOption[] options) where TEnum : Enum
12191223
{
12201224
return (TEnum)EditorGUILayout.EnumFlagsField(label, value, options);
12211225
}
1222-
private static Version RenderInput(ConfigFieldAttribute configFieldAttribute, Version value, float labelWidth)
1223-
{
1224-
return RenderInput(value, configFieldAttribute.Label, configFieldAttribute.ToolTip, labelWidth);
1225-
}
1226-
1227-
public static Version RenderInput(Version value, string label, string tooltip, float labelWidth)
1228-
{
1229-
return InputRendererWrapper(label, tooltip, labelWidth, value, VersionField);
1230-
}
12311226

12321227
public static ProductionEnvironments RenderInput(ConfigFieldAttribute configFieldAttribute,
12331228
ProductionEnvironments value, float labelWidth)
@@ -1306,7 +1301,7 @@ public static string RenderInput(DirectoryPathFieldAttribute configFieldAttribut
13061301
{
13071302
EditorGUILayout.BeginHorizontal();
13081303

1309-
string filePath = InputRendererWrapper(configFieldAttributeDetails.Label, value, labelWidth, tooltip, EditorGUILayout.TextField, configFieldAttributeDetails.HelpURL);
1304+
string filePath = InputRendererWrapper(configFieldAttributeDetails.Label, value, labelWidth, tooltip, configFieldAttributeDetails.Validators, EditorGUILayout.TextField, configFieldAttributeDetails.HelpURL);
13101305

13111306
if (GUILayout.Button("Select", GUILayout.MaxWidth(MAXIMUM_BUTTON_WIDTH)))
13121307
{
@@ -1327,7 +1322,7 @@ public static string RenderInput(FilePathFieldAttribute configFieldAttributeDeta
13271322
{
13281323
EditorGUILayout.BeginHorizontal();
13291324

1330-
string filePath = InputRendererWrapper(configFieldAttributeDetails.Label, value, labelWidth, tooltip, EditorGUILayout.TextField, configFieldAttributeDetails.HelpURL);
1325+
string filePath = InputRendererWrapper(configFieldAttributeDetails.Label, value, labelWidth, tooltip, configFieldAttributeDetails.Validators, EditorGUILayout.TextField, configFieldAttributeDetails.HelpURL);
13311326

13321327
if (GUILayout.Button("Select", GUILayout.MaxWidth(MAXIMUM_BUTTON_WIDTH)))
13331328
{
@@ -1347,35 +1342,25 @@ public static string RenderInput(FilePathFieldAttribute configFieldAttributeDeta
13471342

13481343
public static double RenderInput(ConfigFieldAttribute configFieldDetails, double value, float labelWidth)
13491344
{
1350-
return InputRendererWrapper(configFieldDetails.Label, configFieldDetails.ToolTip, labelWidth, value, EditorGUILayout.DoubleField, configFieldDetails.HelpURL);
1345+
return InputRendererWrapper(configFieldDetails.Label, configFieldDetails.ToolTip, labelWidth, value, configFieldDetails.Validators, EditorGUILayout.DoubleField, configFieldDetails.HelpURL);
13511346
}
13521347

13531348
public static float RenderInput(ConfigFieldAttribute configFieldDetails, float value, float labelWidth)
13541349
{
1355-
return InputRendererWrapper(configFieldDetails.Label, configFieldDetails.ToolTip, labelWidth, value, EditorGUILayout.FloatField, configFieldDetails.HelpURL);
1350+
return InputRendererWrapper(configFieldDetails.Label, configFieldDetails.ToolTip, labelWidth, value, configFieldDetails.Validators, EditorGUILayout.FloatField, configFieldDetails.HelpURL);
13561351
}
13571352

13581353
public static string RenderInput(ConfigFieldAttribute configFieldDetails, string value, float labelWidth)
13591354
{
1360-
return InputRendererWrapper(configFieldDetails.Label, configFieldDetails.ToolTip, labelWidth, value, EditorGUILayout.TextField, configFieldDetails.HelpURL);
1355+
return InputRendererWrapper(configFieldDetails.Label, configFieldDetails.ToolTip, labelWidth, value, configFieldDetails.Validators, EditorGUILayout.TextField, configFieldDetails.HelpURL);
13611356
}
13621357

13631358
public static ulong RenderInput(ConfigFieldAttribute configFieldDetails, ulong value, float labelWidth)
13641359
{
13651360
_ = SafeTranslatorUtility.TryConvert(value, out long temp);
13661361

13671362
long longValue = InputRendererWrapper(configFieldDetails.Label, configFieldDetails.ToolTip, labelWidth,
1368-
temp, EditorGUILayout.LongField);
1369-
1370-
return SafeTranslatorUtility.TryConvert(longValue, out ulong newValue) ? newValue : value;
1371-
}
1372-
1373-
private static ulong RenderInput(string label, string tooltip, ulong value, float labelWidth)
1374-
{
1375-
_ = SafeTranslatorUtility.TryConvert(value, out long temp);
1376-
1377-
long longValue = InputRendererWrapper(label, tooltip, labelWidth,
1378-
temp, EditorGUILayout.LongField);
1363+
temp, configFieldDetails.Validators, EditorGUILayout.LongField);
13791364

13801365
return SafeTranslatorUtility.TryConvert(longValue, out ulong newValue) ? newValue : value;
13811366
}
@@ -1384,7 +1369,7 @@ public static uint RenderInput(ConfigFieldAttribute configFieldDetails, uint val
13841369
{
13851370
_ = SafeTranslatorUtility.TryConvert(value, out int temp);
13861371

1387-
int intValue = InputRendererWrapper(configFieldDetails.Label, configFieldDetails.ToolTip, labelWidth, temp,
1372+
int intValue = InputRendererWrapper(configFieldDetails.Label, configFieldDetails.ToolTip, labelWidth, temp, configFieldDetails.Validators,
13881373
EditorGUILayout.IntField);
13891374

13901375
return SafeTranslatorUtility.TryConvert(intValue, out uint newValue) ? newValue : value;
@@ -1394,10 +1379,10 @@ public static bool RenderInput(ConfigFieldAttribute configFieldDetails, bool val
13941379
{
13951380
return InputRendererWrapper(
13961381
configFieldDetails.Label, configFieldDetails.ToolTip, labelWidth,
1397-
value, EditorGUILayout.Toggle);
1382+
value, configFieldDetails.Validators, EditorGUILayout.Toggle);
13981383
}
13991384

1400-
public delegate T TestDelegate<T>(GUIContent label, T value, params GUILayoutOption[] options);
1385+
public delegate T InputRendererDelegate<T>(GUIContent label, T value, params GUILayoutOption[] options);
14011386

14021387
private static T InputRendererWithAlignedLabel<T>(float labelWidth, Func<T> renderFn)
14031388
{
@@ -1412,10 +1397,50 @@ private static T InputRendererWithAlignedLabel<T>(float labelWidth, Func<T> rend
14121397
return newValue;
14131398
}
14141399

1415-
private static T InputRendererWrapper<T>(string label, string toolTip, float labelWidth, T value, TestDelegate<T> renderFn, string helpURL = null)
1400+
private static void RunValidators(IEnumerable<FieldValidatorAttribute> validators, object value, out bool isValid)
1401+
{
1402+
isValid = true;
1403+
StringBuilder errorMessageBuilder = new();
1404+
foreach (var validator in validators)
1405+
{
1406+
// If field is valid then go to the next validator.
1407+
if (validator.FieldValueIsValid(value, out string errorMessage))
1408+
{
1409+
continue;
1410+
}
1411+
1412+
// Otherwise append message from the validator.
1413+
errorMessageBuilder.AppendLine(errorMessage);
1414+
}
1415+
1416+
// If there are no error messages, then stop here
1417+
if (errorMessageBuilder.Length == 0)
1418+
{
1419+
return;
1420+
}
1421+
1422+
isValid = false;
1423+
EditorGUILayout.HelpBox(errorMessageBuilder.ToString(), MessageType.Warning);
1424+
}
1425+
1426+
private static T InputRendererWrapper<T>(string label, string toolTip, float labelWidth, T value, IEnumerable<FieldValidatorAttribute> validators, InputRendererDelegate<T> renderFn, string helpURL = null)
14161427
{
14171428
return InputRendererWithAlignedLabel(labelWidth, () =>
14181429
{
1430+
// Run validators for the config field.
1431+
RunValidators(validators, value, out bool isCurrentValueValid);
1432+
1433+
// Store the previous background color so that it can be
1434+
// restored if need be.
1435+
Color previousBackgroundColor = GUI.backgroundColor;
1436+
if (!isCurrentValueValid)
1437+
{
1438+
// This sets the background color for the input field that
1439+
// is about to be rendered to red - further highlighting the
1440+
// field that has invalid values.
1441+
GUI.backgroundColor = Color.red;
1442+
}
1443+
14191444
if (!string.IsNullOrEmpty(helpURL))
14201445
{
14211446
EditorGUILayout.BeginHorizontal();
@@ -1429,6 +1454,10 @@ private static T InputRendererWrapper<T>(string label, string toolTip, float lab
14291454
EditorGUILayout.EndHorizontal();
14301455
}
14311456

1457+
// Restore the background color that was set before the field
1458+
// was rendered.
1459+
GUI.backgroundColor = previousBackgroundColor;
1460+
14321461
return newValue;
14331462
});
14341463
}

com.playeveryware.eos/Runtime/Core/Config/Attributes/ConfigFieldAttribute.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
namespace PlayEveryWare.EpicOnlineServices
2626
{
2727
using System;
28+
using System.Collections.Generic;
2829

2930
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
3031
public class ConfigFieldAttribute : Attribute
@@ -58,31 +59,31 @@ public class ConfigFieldAttribute : Attribute
5859
/// </summary>
5960
public PlatformManager.Platform PlatformsEnabledOn { get; }
6061

62+
public IEnumerable<FieldValidatorAttribute> Validators { get; set; }
63+
6164
public ConfigFieldAttribute(
6265
PlatformManager.Platform enabledOn,
6366
string label,
6467
ConfigFieldType type,
6568
string tooltip = null,
6669
int group = -1,
67-
string helpUrl = null) : this(label, type, tooltip, group, helpUrl)
70+
string helpUrl = null)
6871
{
6972
PlatformsEnabledOn = enabledOn;
73+
Label = label;
74+
FieldType = type;
75+
ToolTip = tooltip;
76+
Group = group;
77+
HelpURL = helpUrl;
7078
}
7179

7280
public ConfigFieldAttribute(
7381
string label,
7482
ConfigFieldType type,
7583
string tooltip = null,
7684
int group = -1,
77-
string helpUrl = null)
78-
{
79-
PlatformsEnabledOn = PlatformManager.Platform.Any;
80-
HelpURL = helpUrl;
81-
Label = label;
82-
ToolTip = tooltip;
83-
Group = group;
84-
FieldType = type;
85-
}
85+
string helpUrl = null) : this(PlatformManager.Platform.Any, label, type, tooltip, group, helpUrl)
86+
{ }
8687
}
8788
}
8889

com.playeveryware.eos/Runtime/Core/Config/Attributes/Validators.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

com.playeveryware.eos/Runtime/Core/Config/Attributes/FieldValidator.cs renamed to com.playeveryware.eos/Runtime/Core/Config/Attributes/Validators/FieldValidator.cs

File renamed without changes.

com.playeveryware.eos/Runtime/Core/Config/Attributes/FieldValidator.cs.meta renamed to com.playeveryware.eos/Runtime/Core/Config/Attributes/Validators/FieldValidator.cs.meta

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

com.playeveryware.eos/Runtime/Core/Config/Attributes/FieldValidatorAttribute.cs renamed to com.playeveryware.eos/Runtime/Core/Config/Attributes/Validators/FieldValidatorAttribute.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,9 @@ namespace PlayEveryWare.EpicOnlineServices
2525
using System;
2626
using System.Reflection;
2727

28-
[AttributeUsage(AttributeTargets.Field)]
29-
28+
[AttributeUsage(AttributeTargets.Field, AllowMultiple = true)]
3029
public abstract class FieldValidatorAttribute : Attribute
3130
{
32-
public abstract bool FieldValueIsValid(object toValidate, out string configurationProblemMessage);
31+
public abstract bool FieldValueIsValid(object value, out string errorMessage);
3332
}
3433
}

com.playeveryware.eos/Runtime/Core/Config/Attributes/FieldValidatorAttribute.cs.meta renamed to com.playeveryware.eos/Runtime/Core/Config/Attributes/Validators/FieldValidatorAttribute.cs.meta

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

com.playeveryware.eos/Runtime/Core/Config/Attributes/GUIDFieldValidatorAttribute.cs renamed to com.playeveryware.eos/Runtime/Core/Config/Attributes/Validators/GUIDFieldValidatorAttribute.cs

File renamed without changes.

com.playeveryware.eos/Runtime/Core/Config/Attributes/GUIDFieldValidatorAttribute.cs.meta renamed to com.playeveryware.eos/Runtime/Core/Config/Attributes/Validators/GUIDFieldValidatorAttribute.cs.meta

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)