예제 #1
0
        /// <summary>
        ///		Internal implementation of EnumMaskPopup.
        /// </summary>
        /// <param name="values"></param>
        /// <param name="names"></param>
        /// <param name="position"></param>
        /// <param name="property"></param>
        /// <param name="entries">An optional bitmask with selectable entries.</param>
        /// <param name="isSkippingZero">If true, enum entries with a value of zero are not displayed (useful if the entry already maps to 'None').</param>
        /// <param name="isDisplayingEverything">If true, if all possible elements are selected (all elements that entries allows), 'Everything' is displayed instead of comma-separated values.</param>
        /// <param name="isSkippingNonFlags">If true, elements which don't map to a flag value aren't included.</param>
        private static void EnumMaskPopup(List <int> values, List <string> names, Rect position, SerializedProperty property, int entries = ~0, bool isSkippingZero = false, bool isDisplayingEverything = true, bool isSkippingNonFlags = true)
        {
            List <int>    valuesCopy;
            List <string> namesCopy;
            int           omitted = 0;

            //	Popup only supports 32 entries.  If exceeded, only use the first 32 and flag the field as red with an error tooltip
            if (values.Count <= 32)
            {
                valuesCopy = new List <int>(values);
                namesCopy  = new List <string>(names);
            }
            else
            {
                valuesCopy = values.GetRange(0, 32);
                namesCopy  = names.GetRange(0, 32);
                omitted    = values.Count - valuesCopy.Count;
            }

            //	Get displayable values list, so that we don't have to work with negative values
            List <int> entriesValues = valuesCopy.Where(en => entries.Contains(en)).ToList();

            if (isSkippingZero)
            {
                entriesValues.RemoveAll(val => val == 0);
            }

            if (isSkippingNonFlags)
            {
                entriesValues.RemoveAll(val => !BitmaskUtility.isPowerOfTwo(val));
            }

            List <string> displayNames = namesCopy.Where((en, index) => entriesValues.Contains(valuesCopy[index])).ToList();

            //	Key: entry value; Value: entriesValues index
            Dictionary <int, int> existing = new Dictionary <int, int>();
            int    match;
            string enumName;

            //	Combine any elements that have a duplicate value
            for (int i = 0; i < entriesValues.Count; i++)
            {
                if (existing.TryGetValue(entriesValues[i], out match))
                {
                    enumName = displayNames[i];
                    displayNames.RemoveAt(i);
                    entriesValues.RemoveAt(i--);

                    //	Append the description.  (thin space + full-width ampersand + enumName)
                    displayNames[match] += FormatUtility.MARK_AMPERSAND_SAFE + enumName;
                    continue;
                }
                else
                {
                    //	Check if we need to use a custom display name for combined-entry value, using format "entry (entry 1 & entry 2 ...)"
                    if (!isSkippingNonFlags && !BitmaskUtility.isPowerOfTwo(entriesValues[i]))
                    {
                        ///	First check to make sure all its values are allowed (in entries).  Otherwise, remove it
                        List <int> bitmaskValues = BitmaskUtility.GetBitmaskValues(entriesValues[i], valuesCopy.Count);

                        displayNames[i] +=
                            " ("
                            + String.Join(
                                FormatUtility.MARK_AMPERSAND_SAFE,
                                //	Get all entries in the combined-entry.  Then get their display name and append
                                bitmaskValues.Select <int, string>(val => displayNames[entriesValues.IndexOf(val)]).ToArray())
                            + ")";
                    }

                    existing.Add(entriesValues[i], i);
                }
            }

            int    mask        = 0;
            string displayName = String.Empty;

            if (property.hasMultipleDifferentValues)
            {
                displayName = "—";
            }
            else
            {
                //	Get selected list, removing any selected that aren't currently allowed
                List <int> selectedValues = entriesValues.Where(en => property.intValue.Contains(en)).ToList();

                //	We need to convert the entriesList (which contains potentially skipped values) into an ordered mask (with no skipped values) so that EditorGUI.MaskField can use it
                //	The reason that we iterate over entriesValues instead of selectedValues is so that we can add in duplicated entries (an enum with two entries with the same value)
                for (int i = 0; i < entriesValues.Count; i++)
                {
                    if (selectedValues.Contains(entriesValues[i]))
                    {
                        mask |= 1 << i;

                        //	Don't display combined-entry values in the label (it clutters things up)
                        if (!isSkippingNonFlags && !BitmaskUtility.isPowerOfTwo(entriesValues[i]))
                        {
                            continue;
                        }

                        //	Add a name to display
                        if (String.IsNullOrEmpty(displayName))
                        {
                            displayName = displayNames[i];
                        }
                        else
                        {
                            displayName += ", " + displayNames[i];
                        }
                    }
                }

                if (String.IsNullOrEmpty(displayName))
                {
                    displayName = "Nothing";
                }
                else if (selectedValues.Count == entriesValues.Count && isDisplayingEverything)
                {
                    displayName = "Everything";
                }
            }

            //	Make sure the displayName fits in the content field.  If not, reduce it.  (Subtracts pixels so that it doesn't overlap the dropdown arrow on the right)
            displayName = FitContent(displayName, position.width - 15, EditorStyles.popup, CutoffOption.ToEntry);

            int valueNew = 0;
            //	long is used for valueDiff (instead of int) because int will cause an OverflowException when attempting absolute value check for 1 << 31 (Math.Abs(int32.MinValue)).
            long valueDiff = 0;
            bool isAdded;

            //	Verify that no values outside of entries are assigned (on any of the selections)
            foreach (SerializedProperty prop in property.Multiple())
            {
                foreach (int val in BitmaskUtility.GetBitmaskValues(property.intValue))
                {
                    if (!entries.Contains(val))
                    {
                        prop.intValue &= ~val;
                    }
                }
            }

            //	Append dashes to the display names for any skipped elements (due to exceeding popup display count of 32)
            displayNames.AddRange(Enumerable.Repeat <string>(null, omitted));

            //	Draw popup (without selection display)
            MultiField(
                property,
                (propIter) => valueNew = EditorGUI.MaskField(position, mask, displayNames.ToArray(), PopupWithoutLabelStyle),
                (propIter) => {
                //	'Nothing' selected
                if (valueNew == 0)
                {
                    property.intValue = 0;
                    return;
                }
                //	'Everything' selected
                if (valueNew == -1)
                {
                    property.intValue = entries;
                    return;
                }

                //	Determine what was clicked by getting the difference (bitwise difference)
                //valueDiff = valueNew ^ mask;
                valueDiff = (long)valueNew - (long)mask;
                if (valueDiff == 0)
                {
                    return;
                }

                //	Determine if value was added or removed.
                //	(1 << 31 is a negative int value, so its isAdded will need to be flipped.  Check against the min value and its two's-complement.)
                isAdded = valueDiff > 0;
                if (valueDiff == Int32.MinValue || valueDiff == ~((long)Int32.MinValue - 1))
                {
                    isAdded = !isAdded;
                }

                //	Convert ordered mask to real mask
                valueDiff = entriesValues[BitmaskUtility.GetMaskIndex(Math.Abs(valueDiff))];

                //	Standard, add or remove selection
                if (BitmaskUtility.isPowerOfTwo(valueDiff))
                {
                    if (isAdded)
                    {
                        property.intValue |= (int)valueDiff;
                    }
                    else
                    {
                        property.intValue &= (int)~valueDiff;
                    }
                }
                //	If not a power of two, selection is a combined-entry.  Add or remove all its parts
                else
                {
                    if (isAdded)
                    {
                        foreach (int val in BitmaskUtility.GetBitmaskValues(valueDiff, valuesCopy.Count))
                        {
                            property.intValue |= val;
                        }
                    }
                    else
                    {
                        foreach (int val in BitmaskUtility.GetBitmaskValues(valueDiff, valuesCopy.Count))
                        {
                            property.intValue &= ~val;
                        }
                    }
                }
            });

            GUIContent label      = new GUIContent(displayName);
            Color      colorPrior = GUI.color;

            if (omitted > 0)
            {
                label.tooltip = omitted + " elements were omitted, due to the length of the enum's entries (" + values.Count + ") exceeding Unity's limitation of 32 for popups.";
                GUI.color     = Color.red;
            }

            //	Display label with active selection
            EditorGUI.LabelField(position, label, EditorStyles.popup);

            if (omitted > 0)
            {
                GUI.color = colorPrior;
            }
        }
예제 #2
0
 /// <summary>
 ///		Does the provided enum mask have multiple flagged values (is not a power of two)?
 /// </summary>
 /// <param name="bitmask"></param>
 /// <returns></returns>
 public static bool DoesMaskHaveMultiple(Enum bitmask)
 {
     return(!BitmaskUtility.isPowerOfTwo((int)(object)bitmask));
 }
예제 #3
0
 /// <summary>
 ///		Does the provided enum mask have multiple flagged values (is not a power of two)?
 /// </summary>
 /// <param name="bitmask"></param>
 /// <returns></returns>
 public static bool DoesMaskHaveMultiple(int bitmask)
 {
     return(!BitmaskUtility.isPowerOfTwo(bitmask));
 }
예제 #4
0
 /// <summary>
 ///		Gets the index that corresponds to a non-combined bitmask value.
 /// </summary>
 /// <exception cref="ArgumentException">Passed enumValue cannot be a combined enum.</exception>
 /// <param name="enumValue">A non-combined enum mask value.</param>
 /// <returns></returns>
 public static int GetMaskIndex(Enum enumValue)
 {
     return(BitmaskUtility.GetMaskIndex((int)(object)enumValue));
 }