VR4Medical/ICI/Library/PackageCache/com.unity.xr.interaction.toolkit@42ef3600567b/Editor/Utilities/XRInteractionEditorGUI.cs
2025-07-29 13:45:50 +03:00

219 lines
10 KiB
C#

using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEditor.XR.Interaction.Toolkit.Utilities
{
/// <summary>
/// Class with methods to draw editor gui controls.
/// </summary>
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<string> displayedOptions, IList<int> 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;
}
/// <summary>
/// Returns true if the event is a main keyboard action for the supplied control id.
/// </summary>
/// <param name="evt">The target gui event.</param>
/// <param name="controlId">The target control id.</param>
/// <returns>Returns whether the supplied event is a main keyboard action for the supplied control id.</returns>
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;
}
/// <summary>
/// Makes a property field for masks.
/// Inspired the internal Unity method <c>EditorGUI.DoPopup</c>.
/// </summary>
/// <param name="position">Rectangle on the screen to use for this control.</param>
/// <param name="label">Label for the field.</param>
/// <param name="property">The current SerializedProperty mask to display.</param>
/// <param name="displayOptions">A string array containing the labels for each flag.</param>
/// <param name="valueOptions">An integer list containing the value (or bit index) for each flag.</param>
/// <param name="onAddLayerCallback">Optional callback called when users select the add layer option.</param>
internal static void PropertyMaskField(Rect position, GUIContent label, SerializedProperty property,
IList<string> displayOptions, IList<int> 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<int> 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<T>(SerializedProperty property, GUIContent label, Func<Enum, bool> 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;
}
}
}
}