|
| 1 | +// Licensed to the .NET Foundation under one or more agreements. |
| 2 | +// The .NET Foundation licenses this file to you under the MIT license. |
| 3 | +// See the LICENSE file in the project root for more information. |
| 4 | + |
| 5 | +using CommunityToolkit.WinUI.Converters; |
| 6 | + |
| 7 | +namespace CommunityToolkit.WinUI.Controls; |
| 8 | + |
| 9 | +/// <summary> |
| 10 | +/// Internal helpers for use between <see cref="SwitchPresenter"/> and <see cref="SwitchConverter"/>. |
| 11 | +/// The logic here is the main code which looks across a <see cref="CaseCollection"/> to match a specific <see cref="Case"/> with a given value while converting types based on the <see cref="SwitchPresenter.TargetType"/> property. This will handle <see cref="Enum"/> values as well as values compatible with the <see cref="XamlBindingHelper.ConvertValue(Type, object)"/> method. |
| 12 | +/// </summary> |
| 13 | +internal static partial class SwitchHelpers |
| 14 | +{ |
| 15 | + /// <summary> |
| 16 | + /// Extension method for a set of cases to find the matching case given its value and type. |
| 17 | + /// </summary> |
| 18 | + /// <param name="switchCases">The collection of <see cref="Case"/>s in a <see cref="CaseCollection"/></param> |
| 19 | + /// <param name="value">The value of the <see cref="Case"/> to find</param> |
| 20 | + /// <param name="targetType">The desired type of the result for automatic conversion</param> |
| 21 | + /// <returns>The discovered value, the default value, or <c>null</c></returns> |
| 22 | + internal static Case? EvaluateCases(this CaseCollection switchCases, object value, Type targetType) |
| 23 | + { |
| 24 | + if (switchCases == null || |
| 25 | + switchCases.Count == 0) |
| 26 | + { |
| 27 | + // If we have no cases, then we can't match anything. |
| 28 | + return null; |
| 29 | + } |
| 30 | + |
| 31 | + Case? xdefault = null; |
| 32 | + Case? newcase = null; |
| 33 | + |
| 34 | + foreach (Case xcase in switchCases) |
| 35 | + { |
| 36 | + if (xcase.IsDefault) |
| 37 | + { |
| 38 | + // If there are multiple default cases provided, this will override and just grab the last one, the developer will have to fix this in their XAML. We call this out in the case comments. |
| 39 | + xdefault = xcase; |
| 40 | + continue; |
| 41 | + } |
| 42 | + |
| 43 | + if (CompareValues(value, xcase.Value, targetType)) |
| 44 | + { |
| 45 | + newcase = xcase; |
| 46 | + break; |
| 47 | + } |
| 48 | + } |
| 49 | + |
| 50 | + if (newcase == null && xdefault != null) |
| 51 | + { |
| 52 | + // Inject default if we found one without matching anything |
| 53 | + newcase = xdefault; |
| 54 | + } |
| 55 | + |
| 56 | + return newcase; |
| 57 | + } |
| 58 | + |
| 59 | + /// <summary> |
| 60 | + /// Compares two values using the TargetType. |
| 61 | + /// </summary> |
| 62 | + /// <param name="compare">Our main value in our SwitchPresenter.</param> |
| 63 | + /// <param name="value">The value from the case to compare to.</param> |
| 64 | + /// <returns>true if the two values are equal</returns> |
| 65 | + internal static bool CompareValues(object compare, object value, Type targetType) |
| 66 | + { |
| 67 | + if (compare == null || value == null) |
| 68 | + { |
| 69 | + return compare == value; |
| 70 | + } |
| 71 | + |
| 72 | + if (targetType == null || |
| 73 | + (targetType == compare.GetType() && |
| 74 | + targetType == value.GetType())) |
| 75 | + { |
| 76 | + // Default direct object comparison or we're all the proper type |
| 77 | + return compare.Equals(value); |
| 78 | + } |
| 79 | + else if (compare.GetType() == targetType) |
| 80 | + { |
| 81 | + // If we have a TargetType and the first value is the right type |
| 82 | + // Then our 2nd value isn't, so convert to string and coerce. |
| 83 | + var valueBase2 = ConvertValue(targetType, value); |
| 84 | + |
| 85 | + return compare.Equals(valueBase2); |
| 86 | + } |
| 87 | + |
| 88 | + // Neither of our two values matches the type so |
| 89 | + // we'll convert both to a String and try and coerce it to the proper type. |
| 90 | + var compareBase = ConvertValue(targetType, compare); |
| 91 | + |
| 92 | + var valueBase = ConvertValue(targetType, value); |
| 93 | + |
| 94 | + return compareBase.Equals(valueBase); |
| 95 | + } |
| 96 | + |
| 97 | + /// <summary> |
| 98 | + /// Helper method to convert a value from a source type to a target type. |
| 99 | + /// </summary> |
| 100 | + /// <param name="targetType">The target type</param> |
| 101 | + /// <param name="value">The value to convert</param> |
| 102 | + /// <returns>The converted value</returns> |
| 103 | + internal static object ConvertValue(Type targetType, object value) |
| 104 | + { |
| 105 | + if (targetType.IsInstanceOfType(value)) |
| 106 | + { |
| 107 | + return value; |
| 108 | + } |
| 109 | + else if (targetType.IsEnum && value is string str) |
| 110 | + { |
| 111 | +#if HAS_UNO |
| 112 | + if (Enum.IsDefined(targetType, str)) |
| 113 | + { |
| 114 | + return Enum.Parse(targetType, str); |
| 115 | + } |
| 116 | +#else |
| 117 | + if (Enum.TryParse(targetType, str, out object? result)) |
| 118 | + { |
| 119 | + return result!; |
| 120 | + } |
| 121 | +#endif |
| 122 | + |
| 123 | + static object ThrowExceptionForKeyNotFound() |
| 124 | + { |
| 125 | + throw new InvalidOperationException("The requested enum value was not present in the provided type."); |
| 126 | + } |
| 127 | + |
| 128 | + return ThrowExceptionForKeyNotFound(); |
| 129 | + } |
| 130 | + else |
| 131 | + { |
| 132 | + return XamlBindingHelper.ConvertValue(targetType, value); |
| 133 | + } |
| 134 | + } |
| 135 | +} |
0 commit comments