/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * Licensed under the Oculus SDK License Agreement (the "License"); * you may not use the Oculus SDK except in compliance with the License, * which is provided at the time of installation or download, or which * otherwise accompanies this software in either electronic or hard copy form. * * You may obtain a copy of the License at * * https://developer.oculus.com/licenses/oculussdk/ * * Unless required by applicable law or agreed to in writing, the Oculus SDK * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using Oculus.Interaction.PoseDetection.Editor.Model; using System; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; using UnityEngine.Assertions; namespace Oculus.Interaction.PoseDetection.Editor { namespace Model { public class FeatureConfigList { private readonly SerializedProperty _root; private uint _flags; private readonly IReadOnlyDictionary _featureDescriptions; public FeatureConfigList(SerializedProperty root, IReadOnlyDictionary featureDescriptions) { Assert.IsNotNull(root); _root = root; _flags = GetFlags(_root); _featureDescriptions = featureDescriptions; } private uint GetFlags(SerializedProperty root) { if (!root.isArray) { return 0U; } uint flags = 0U; for (int i = 0; i < root.arraySize; i++) { var elemProp = root.GetArrayElementAtIndex(i); var featureProp = elemProp.FindPropertyRelative("_feature"); flags |= 1U << featureProp.enumValueIndex; } return flags; } public uint Flags { get => _flags; set { uint flagsToCreate = value; for (int i = 0; i < _root.arraySize;) { var elemProp = _root.GetArrayElementAtIndex(i); var featureProp = elemProp.FindPropertyRelative("_feature"); uint propFlags = (1U << featureProp.enumValueIndex); if ((flagsToCreate & propFlags) == 0U) { // Feature is in list, but not flags... delete list entry. _root.DeleteArrayElementAtIndex(i); } else { // Feature is in list, and in flags; remove from list of things we need to create. flagsToCreate &= ~propFlags; i++; } } // Create missing elements. foreach (var feature in _featureDescriptions.Keys) { uint flags = 1U << feature; if ((flagsToCreate & flags) == 0U) { continue; } var lastIndex = _root.arraySize; _root.InsertArrayElementAtIndex(lastIndex); var model = new FeatureConfig(_root.GetArrayElementAtIndex(lastIndex)); model.Feature = feature; model.FeatureState = _featureDescriptions[feature].FeatureStates[0].Id; } _flags = value; } } public IEnumerable ConfigModels { get { List models = new List(_root.arraySize); for (int i = 0; i < _root.arraySize; i++) { models.Add(new FeatureConfig(_root.GetArrayElementAtIndex(i))); } return models; } } } public class FeatureConfig { SerializedProperty _modeProp; SerializedProperty _featureProp; SerializedProperty _stateProp; public FeatureConfig(SerializedProperty root) { _modeProp = root.FindPropertyRelative("_mode"); _featureProp = root.FindPropertyRelative("_feature"); _stateProp = root.FindPropertyRelative("_state"); } public FeatureStateActiveMode Mode { get => (FeatureStateActiveMode)_modeProp.enumValueIndex; set { if (value != (FeatureStateActiveMode)_modeProp.enumValueIndex) { _modeProp.enumValueIndex = (int)value; } } } public int Feature { get => _featureProp.enumValueIndex; set { if (value != _featureProp.enumValueIndex) { _featureProp.enumValueIndex = value; } } } public string FeatureState { get => _stateProp.stringValue; set { if (value != _stateProp.stringValue) { _stateProp.stringValue = value; } } } } } public abstract class FeatureListPropertyDrawer : PropertyDrawer { public float ControlLineHeight => EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; // = 18 + 2 public float BottomMargin => EditorGUIUtility.standardVerticalSpacing * 2; private const float IndentSize = 16; public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { var height = base.GetPropertyHeight(property, label); // includes height of first line var model = CreateModel(property); if (property.isExpanded) { var controlCount = model.ConfigModels.Count(); height += controlCount * ControlLineHeight; height += BottomMargin; } return height; } public override bool CanCacheInspectorGUI(SerializedProperty property) { return true; } public override void OnGUI(Rect drawerPos, SerializedProperty property, GUIContent label) { EditorGUI.BeginProperty(drawerPos, label, property); var oldIndent = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; var labelPos = new Rect(drawerPos) { width = EditorGUIUtility.labelWidth - drawerPos.x, height = EditorGUIUtility.singleLineHeight }; var model = CreateModel(property); property.isExpanded = EditorGUI.Foldout(labelPos, property.isExpanded, label, true); if (property.isExpanded) { RenderExpanded(drawerPos, model); } else { RenderCollapsed(drawerPos, model); } EditorGUI.indentLevel = oldIndent; EditorGUI.EndProperty(); } private void RenderExpanded(Rect drawerPos, FeatureConfigList model) { Rect controlPos = new Rect(drawerPos.x, drawerPos.y, drawerPos.width, EditorGUIUtility.singleLineHeight); var flagsPos = Indent(controlPos, EditorGUIUtility.labelWidth); var newFlags = EnumToFlags(EditorGUI.EnumFlagsField(flagsPos, FlagsToEnum(model.Flags))); if (newFlags != model.Flags) { model.Flags = newFlags; } controlPos = Indent(controlPos, IndentSize); foreach (var configModel in model.ConfigModels) { controlPos.y += ControlLineHeight; // Render the label float indent = 0f; var labelPos = Indent(controlPos, indent); labelPos.width = EditorGUIUtility.labelWidth - IndentSize; var featureName = FeatureToString(configModel.Feature); featureName = ObjectNames.NicifyVariableName(featureName); EditorGUI.PrefixLabel(labelPos, new GUIContent(featureName)); // Render the mode dropdown indent += labelPos.width; var modePos = Indent(controlPos, indent); var allowedModes = GetAllowedModes(configModel); if (allowedModes.Length > 1) { modePos.width = 70; configModel.Mode = (FeatureStateActiveMode)EditorGUI.EnumPopup(modePos, configModel.Mode); } else if (allowedModes.Length == 1) { configModel.Mode = allowedModes[0]; modePos.width = 15; EditorGUI.SelectableLabel(modePos, ObjectNames.NicifyVariableName(allowedModes[0] + ": ")); } else { modePos.width = -2; } // Render the state dropdown indent += modePos.width + 2; var statePos = Indent(controlPos, indent); var featureStates = GetStatesForFeature(configModel.Feature); string[] options = featureStates.Select(fs => fs.Name).ToArray(); int selectedIndex = Array.FindIndex(featureStates, fs => fs.Id == configModel.FeatureState); int newSelectedIndex = EditorGUI.Popup(statePos, selectedIndex, options); if (newSelectedIndex != selectedIndex) { configModel.FeatureState = featureStates[newSelectedIndex].Id; } } } private void RenderCollapsed(Rect drawerPos, FeatureConfigList model) { Rect controlPos = drawerPos; controlPos.height = EditorGUIUtility.singleLineHeight; var valuePos = Indent(controlPos, EditorGUIUtility.labelWidth + 2); valuePos.width = drawerPos.width - valuePos.x; var flagsEnum = FlagsToEnum(model.Flags); var values = Enum.GetValues(flagsEnum.GetType()) .Cast() .Where(e => flagsEnum.HasFlag(e)) .Select(e => e.ToString()); EditorGUI.SelectableLabel(valuePos, String.Join(", ", values)); } private static Rect Indent(Rect position, float indentWidth) { return new Rect(position) { x = position.x + indentWidth, width = position.width - indentWidth }; } protected virtual FeatureStateActiveMode[] GetAllowedModes(FeatureConfig model) { var statesForFeature = GetStatesForFeature(model.Feature); if (statesForFeature.Length > 2) return (FeatureStateActiveMode[])Enum.GetValues(typeof(FeatureStateActiveMode)); else return new[] { FeatureStateActiveMode.Is }; } protected abstract Enum FlagsToEnum(uint flags); protected abstract uint EnumToFlags(Enum flags); protected abstract string FeatureToString(int featureIdx); protected abstract FeatureStateDescription[] GetStatesForFeature(int featureIdx); protected abstract Model.FeatureConfigList CreateModel(SerializedProperty property); } }