Ryle Radio 1.0.0
An open-source "radio" system for Unity, allowing tracks, tuning, broadcasters, and more!
Loading...
Searching...
No Matches
MultiselectAttribute.cs
1using UnityEngine;
2using System.Linq;
3
4namespace RyleRadio
5{
6
7#if UNITY_EDITOR
8 using NaughtyAttributes.Editor;
9 using UnityEditor;
10
11 using System.Reflection;
12 using System.Collections;
13 using System;
14#endif
15
16 /// <summary>
17 /// A custom attribute that allows ints to display as a multiselect dropdown for a given collection, like a custom LayerMask. Due to int limitations you can only have up to 32 options.
18 ///
19 /// Has editor code in \ref MultiselectDrawer
20 /// </summary>
21 public class MultiselectAttribute : PropertyAttribute
22 {
23 /// <summary>
24 /// Name of the variable this attribute uses for the options list
25 /// </summary>
26 public string OptionsName { get; private set; }
27
28 /// <summary>
29 /// A filler array with numbers 0 - 31, used when converting from a flag int to a list subset
30 /// </summary>
31 public static int[] ZeroTo31 => new int[32]
32 {
33 0,
34 1, 2, 3, 4, 5, 6, 7, 8,
35 9, 10, 11, 12, 13, 14, 15, 16,
36 17, 18, 19, 20, 21, 22, 23, 24,
37 25, 26, 27, 28, 29, 30, 31
38 };
39
40 /// <summary>
41 /// A filler array with numbers 1 - 32. Not yet used, but theoretically helpful for indexing from the end of a list (list[^i] instead of list[i])
42 /// </summary>
43 public static int[] OneTo32 => new int[32]
44 {
45 1, 2, 3, 4, 5, 6, 7, 8,
46 9, 10, 11, 12, 13, 14, 15, 16,
47 17, 18, 19, 20, 21, 22, 23, 24,
48 25, 26, 27, 28, 29, 30, 31,
49 32
50 };
51
52 /// <summary>
53 /// Initialises the attribute
54 /// </summary>
55 public MultiselectAttribute(string _optionsName)
56 {
57 OptionsName = _optionsName;
58 }
59
60
61 /// <summary>
62 /// Converts an int with the Multiselect attribute to a subset of a given list according to the int flags- converts the flag int to usable data
63 /// </summary>
64 /// <typeparam name="T">The eventType content of the list we're converting the flags to</typeparam>
65 /// <param name="_flags">The int with the Multiselect attribute- a flag int</param>
66 /// <param name="_options">The list we're getting a subset of according to the flags</param>
67 /// <returns>A subset of `_options` that matches the `_flags` int</returns>
68 /// <example><code>
69 /// string[] options = new string[4] { "awesome", "attribute", "thanks", "ryle-e" };
70 ///
71 /// [Multiselect("options")]
72 /// public int flags1; // in the inspector, we set it to ["awesome", "thanks"]- the first and third options in the inspector. this int then becomes 0x0101
73 ///
74 /// public void Convert()
75 /// {
76 /// int flags2 = (1 << 1) | (1 << 3); // equivalent to selecting the second and fourth options in the inspector
77 ///
78 /// string[] converted1 = MultiselectAttribute.To<string>(flags1, options); // sets to ["awesome", "thanks"]
79 /// string[] converted2 = MultiselectAttribute.To<string>(flags2, options); // sets to ["attribute", "ryle-e"]
80 /// }
81 /// </code></example>
82 public static T[] To<T>(int _flags, T[] _options)
83 {
84 // if the flag int is invalid, alert the user and return nothing
85 if (_flags < 0)
86 {
87 Debug.LogWarning("A value less than 0 is being used as the flag variable in a MultiselectAttribute.To<T>() call! The value is " + _flags);
88 return new T[0];
89 }
90
91 int[] outIndexes = new int[32];
92
93 // for each number 0 - 32,
94 for (int i = 0; i < 32; i++)
95 {
96 // check if the int has the bit flag for this index
97 if ((_flags & (1 << i)) != 0)
98 outIndexes[i] = i; // if it does, add the index to the output list
99 else
100 outIndexes[i] = -1; // otherwise make the index invalid
101 }
102
103 // get the items out of the options list based on the valid indexes in the output list
104 T[] o = outIndexes
105 .Where(b => b >= 0)
106 .Select(i => _options[i])
107 .ToArray();
108
109 // give back the output list as T
110 return o;
111 }
112
113 /// <summary>
114 /// Shorthand for `MultiselectAttribute.To<int>(_flags, _options)`. Useful for converting a multiselect to indexes in a list
115 /// </summary>
116 /// <param name="_flags">The int with the Multiselect attribute</param>
117 /// <returns></returns>
118 public static int[] ToInt(int _flags)
119 {
120 return To<int>(_flags, ZeroTo31);
121 }
122 }
123
124
125#if UNITY_EDITOR
126 /// <summary>
127 /// Draws the MultiselectAttribute in the inspector
128 /// </summary>
129 [CustomPropertyDrawer(typeof(MultiselectAttribute))]
130 public class MultiselectDrawer : PropertyDrawer
131 {
132#if !SKIP_IN_DOXYGEN
133 // the inspector seems to think this is larger than it actually is, so we give it a small adjustment in height
134 public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
135 {
136 return -2;
137 }
138#endif
139
140 /// <summary>
141 /// Displays the multiselect dropdown
142 /// </summary>
143 public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
144 {
145 // make the selection a bit mask
146 int mask = property.intValue;
147 string dataName = ((MultiselectAttribute)attribute).OptionsName; // save the id of the variable in the attribute
148
149 object options = GetValues(property, dataName); // get the options from that variable
150 string[] optionNames;
151
152 EditorGUILayout.BeginHorizontal();
153 EditorGUILayout.PrefixLabel(label);
154
155 if (options != null) // if there are options, display them
156 {
157 // we have to use the individual collection types here as there's no way (that i know of) to access IEnumerable without
158 // specifying a generic eventType- and since we only have the variable id, we can't do that
159
160 // if anyone knows of an alternate way to do this please do share :)
161
162 if (options is IList list) // if the options are stored in a list, get them
163 {
164 if (list.Count > 0)
165 {
166 optionNames = new string[list.Count];
167
168 // convert the options to strings for displaying
169 for (int i = 0; i < list.Count; i++)
170 optionNames[i] = list[i].ToString();
171
172 // draw the field
173 mask = EditorGUILayout.MaskField(mask, optionNames);
174 }
175 else
176 {
177 // options is empty- tell the user
178 EditorGUILayout.LabelField($"{dataName} has size 0!");
179 }
180 }
181 else if (options is Array array) // if the options are stored in an array, get them
182 {
183 if (array.Length > 0)
184 {
185 optionNames = new string[array.Length];
186
187 // convert the options to strings for displaying
188 for (int i = 0; i < array.Length; i++)
189 optionNames[i] = array.GetValue(i).ToString();
190
191 // draw the field
192 mask = EditorGUILayout.MaskField(mask, optionNames);
193 }
194 else
195 {
196 // options is empty- tell the user
197 EditorGUILayout.LabelField($"{dataName} has size 0!");
198 }
199 }
200 else
201 {
202 // options is an unsupported collection- tell the user
203 EditorGUILayout.LabelField($"{dataName} is not a List or an Array!");
204 }
205 }
206 else // the attribute has been given an invalid options variable id- tell the user
207 EditorGUILayout.LabelField($"Invalid collection at {dataName}! Cannot display multiselect!");
208
209 EditorGUILayout.EndHorizontal();
210
211 // assign the mask clampedValue
212 property.intValue = mask;
213 }
214
215#if !SKIP_IN_DOXYGEN
216 // taken from DropdownPropertyDrawer.cs in NaughtyAttributes.Editor
217 // ======
218 private object GetValues(SerializedProperty property, string valuesName)
219 {
220 object target = PropertyUtility.GetTargetObjectWithProperty(property);
221
222 FieldInfo valuesFieldInfo = ReflectionUtility.GetField(target, valuesName);
223 if (valuesFieldInfo != null)
224 {
225 return valuesFieldInfo.GetValue(target);
226 }
227
228 PropertyInfo valuesPropertyInfo = ReflectionUtility.GetProperty(target, valuesName);
229 if (valuesPropertyInfo != null)
230 {
231 return valuesPropertyInfo.GetValue(target);
232 }
233
234 MethodInfo methodValuesInfo = ReflectionUtility.GetMethod(target, valuesName);
235 if (methodValuesInfo != null &&
236 methodValuesInfo.ReturnType != typeof(void) &&
237 methodValuesInfo.GetParameters().Length == 0)
238 {
239 return methodValuesInfo.Invoke(target, null);
240 }
241
242 return null;
243 }
244 // ======
245#endif
246 }
247#endif
248
249}
static int[] ZeroTo31
A filler array with numbers 0 - 31, used when converting from a flag int to a list subset.
static int[] ToInt(int _flags)
Shorthand for MultiselectAttribute.To<int>(_flags, _options). Useful for converting a multiselect to ...
string OptionsName
Name of the variable this attribute uses for the options list.
MultiselectAttribute(string _optionsName)
Initialises the attribute.
static T[] To< T >(int _flags, T[] _options)
Converts an int with the Multiselect attribute to a subset of a given list according to the int flags...
static int[] OneTo32
A filler array with numbers 1 - 32. Not yet used, but theoretically helpful for indexing from the end...
The entire package.