using System; using System.Collections.Generic; using UnityEngine; namespace UnityEditor.XR.Interaction.Toolkit.Utilities { /// /// Class with methods to draw editor gui controls. /// static class XRInteractionEditorGUI { static class Contents { public static readonly GUIContent nothing = EditorGUIUtility.TrTextContent("Nothing"); public static readonly GUIContent everything = EditorGUIUtility.TrTextContent("Everything"); public static readonly GUIContent mixed = EditorGUIUtility.TrTextContent("Mixed..."); public static readonly GUIContent addLayer = EditorGUIUtility.TrTextContent("Add layer..."); } class SetPropertyMaskParameter { public readonly int MaskValue; public readonly SerializedProperty SerializedProperty; public SetPropertyMaskParameter(int maskValue, SerializedProperty serializedProperty) { MaskValue = maskValue; SerializedProperty = serializedProperty; } } static readonly int k_PropertyMaskField = nameof(k_PropertyMaskField).GetHashCode(); static void SetPropertyMask(System.Object parameter) { if (!(parameter is SetPropertyMaskParameter setPropertyMaskParameter)) return; var serializedProperty = setPropertyMaskParameter.SerializedProperty; serializedProperty.longValue = (uint)setPropertyMaskParameter.MaskValue; serializedProperty.serializedObject.ApplyModifiedProperties(); } static GUIContent GetMaskContent(int mask, IList displayedOptions, IList valueOptions) { switch (mask) { case 0: return Contents.nothing; case -1: return Contents.everything; } var count = 0; var displayedMaskContent = Contents.mixed; var size = Mathf.Min(displayedOptions.Count, valueOptions.Count); for (var i = 0; i < size; i++) { if ((mask & 1 << valueOptions[i]) != 0) { if (count == 0) displayedMaskContent = EditorGUIUtility.TrTempContent(displayedOptions[i]); ++count; if (count >= 2) return Contents.mixed; } } return displayedMaskContent; } /// /// Returns true if the event is a main keyboard action for the supplied control id. /// /// The target gui event. /// The target control id. /// Returns whether the supplied event is a main keyboard action for the supplied control id. static bool IsMainActionKeyForControl(Event evt, int controlId) { if (GUIUtility.keyboardControl != controlId) return false; var modifier = evt.alt || evt.shift || evt.command || evt.control; return evt.type == EventType.KeyDown && (evt.keyCode == KeyCode.Space || evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter) && !modifier; } /// /// Makes a property field for masks. /// Inspired the internal Unity method EditorGUI.DoPopup. /// /// Rectangle on the screen to use for this control. /// Label for the field. /// The current SerializedProperty mask to display. /// A string array containing the labels for each flag. /// An integer list containing the value (or bit index) for each flag. /// Optional callback called when users select the add layer option. internal static void PropertyMaskField(Rect position, GUIContent label, SerializedProperty property, IList displayOptions, IList valueOptions, GenericMenu.MenuFunction onAddLayerCallback = null) { // draw the property label var controlId = GUIUtility.GetControlID(k_PropertyMaskField, FocusType.Keyboard, position); position = EditorGUI.PrefixLabel(position, controlId, label); var mask = property.intValue; var currentEvent = Event.current; if (currentEvent.type == EventType.Repaint) { // draw the drop down button var content = GetMaskContent(mask, displayOptions, valueOptions); EditorStyles.popup.Draw(position, content, controlId, false, position.Contains(currentEvent.mousePosition)); } else if (currentEvent.type == EventType.MouseDown && position.Contains(currentEvent.mousePosition) || IsMainActionKeyForControl(currentEvent, controlId)) { currentEvent.Use(); for (var i = 0; i < valueOptions.Count; i++) { valueOptions[i] = 1 << valueOptions[i]; } CalculateMaskValues(mask, valueOptions, out var optionMaskValues); // show the interaction layer options menu var menu = new GenericMenu(); menu.AddItem(Contents.nothing, mask == 0, SetPropertyMask, new SetPropertyMaskParameter(0, property)); menu.AddItem(Contents.everything, mask == -1, SetPropertyMask, new SetPropertyMaskParameter(-1, property)); var size = Mathf.Min(displayOptions.Count, valueOptions.Count); for (var i = 0; i < size; i++) { var displayedOption = displayOptions[i]; var optionMaskValue = valueOptions[i]; menu.AddItem(new GUIContent(displayedOption), (mask & optionMaskValue) != 0, SetPropertyMask, new SetPropertyMaskParameter(optionMaskValues[i + 2], property)); } if (onAddLayerCallback != null) { menu.AddSeparator(""); menu.AddItem(Contents.addLayer, false, onAddLayerCallback); } menu.DropDown(position); GUIUtility.keyboardControl = controlId; } } // Logic pulled from MaskFieldGUI.GetMenuOptions static void CalculateMaskValues(int mask, IList valueOptions, out int[] layerMaskValues) { // Account for "Nothing" and "Everything" options var bufferLength = valueOptions.Count + 2; var numberOfUserLayers = valueOptions.Count; var totalMaskValue = 0; var selectedOptionsMaskValue = 0; // Calculate mask for selected options for (var index = 0; index < numberOfUserLayers; ++index) { var flagValue = valueOptions[index]; totalMaskValue |= flagValue; if ((mask & flagValue) == flagValue) selectedOptionsMaskValue |= flagValue; } layerMaskValues = new int[bufferLength]; layerMaskValues[0] = 0; // Default mask value for "Nothing" layerMaskValues[1] = -1; // Default mask value for "Everything" for (var valueOptionIndex = 0; valueOptionIndex < numberOfUserLayers; ++valueOptionIndex) { var layerMaskValueIndex = valueOptionIndex + 2; var flagValue = valueOptions[valueOptionIndex]; var optionMaskValue = (selectedOptionsMaskValue & flagValue) == flagValue ? selectedOptionsMaskValue & ~flagValue : selectedOptionsMaskValue | flagValue; if (optionMaskValue == totalMaskValue) optionMaskValue = -1; layerMaskValues[layerMaskValueIndex] = optionMaskValue; } } internal static void EnumPropertyField(SerializedProperty property, GUIContent label, GUIContent[] displayedOptions, params GUILayoutOption[] options) { // This is similar to EditorGUILayout.PropertyField but allows the displayed options of the popup to be specified var rect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight, EditorStyles.popup, options); using (var scope = new EditorGUI.PropertyScope(rect, label, property)) using (var change = new EditorGUI.ChangeCheckScope()) { var enumValueIndex = property.hasMultipleDifferentValues ? -1 : property.enumValueIndex; enumValueIndex = EditorGUI.Popup(rect, scope.content, enumValueIndex, displayedOptions); if (change.changed) { property.enumValueIndex = enumValueIndex; } } } internal static void EnumPropertyField(SerializedProperty property, GUIContent label, Func checkEnabled, params GUILayoutOption[] options) where T : Enum { // This is similar to EditorGUILayout.PropertyField but allows the displayed options of the popup to be disabled (grayed out) var rect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight, EditorStyles.popup, options); using (var scope = new EditorGUI.PropertyScope(rect, label, property)) using (var change = new EditorGUI.ChangeCheckScope()) { EditorGUI.showMixedValue = property.hasMultipleDifferentValues; var intValue = (T)EditorGUI.EnumPopup(rect, scope.content, (T)(object)property.intValue, checkEnabled); if (change.changed) { property.intValue = Convert.ToInt32(intValue); } EditorGUI.showMixedValue = false; } } } }