DCDC/Assets/Samples/Meta Avatars SDK/40.0.1/Sample Scenes/Editor/Scripts/OvrAvatarMaterialExtensionConfigDrawer.cs
2026-02-14 19:35:16 +01:00

379 lines
16 KiB
C#

/*
* 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.
*/
#nullable enable
using UnityEditor;
using UnityEngine;
namespace Oculus.Avatar2
{
[CustomPropertyDrawer(typeof(OvrAvatarMaterialExtensionConfig))]
public class OvrAvatarMaterialExtensionConfigDrawer : PropertyDrawer
{
private const float PIXEL_HEIGHT_BETWEEN_FIELDS = 2.0f;
private const string ADD_BUTTON_TEXT = "+";
private const float ADD_BUTTON_PIXEL_WIDTH = 30.0f;
private const string REMOVE_BUTTON_TEXT = "-";
private const float REMOVE_BUTTON_PIXEL_WIDTH = 30.0f;
private const string DEFAULT_EXTENSION_NAME = "Material Extension";
private const string DEFAULT_ENTRY_NAME = "Extension Entry";
private const string DEFAULT_REPLACEMENT_NAME = "Replacement Name";
private const string ENTRIES_LABEL_TEXT = "Entries:";
private static readonly float ENTRY_NAME_WIDTH = EditorGUIUtility.labelWidth;
private static readonly GUIContent ADD_EXTENSION_BUTTON_TOOLTIP = new GUIContent(ADD_BUTTON_TEXT, "Add a new extension");
private static readonly GUIContent ADD_ENTRY_BUTTON_TOOLTIP = new GUIContent(ADD_BUTTON_TEXT, "Add a new entry mapping for the extension");
private static readonly GUIContent REMOVE_ENTRY_BUTTON_TOOLTIP = new GUIContent(REMOVE_BUTTON_TEXT, "Removes the last entry mapping for the extension");
private static readonly GUIContent EXTENSION_ENTRY_FOLDOUT_CONTENT =
new GUIContent("Extension Name:", "Edit the extension name here.");
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
float totalHeight = EditorGUIUtility.singleLineHeight; // Always at least one single line for the property label
if (property.isExpanded)
{
// Add height needed for the "add new extension" button and the padding between then
totalHeight += EditorGUIUtility.singleLineHeight + PIXEL_HEIGHT_BETWEEN_FIELDS;
// Add in height for each extension
var extNamesProp = property.FindPropertyRelative(OvrAvatarMaterialExtensionConfig.ExtensionNamesPropertyName);
var numEntriesPerExtension =
property.FindPropertyRelative(OvrAvatarMaterialExtensionConfig.EntryNamesPropertyName);
if (extNamesProp != null && numEntriesPerExtension != null)
{
Debug.Assert(extNamesProp.arraySize == numEntriesPerExtension.arraySize);
var numExtensions = extNamesProp.arraySize;
for (int i = 0; i < numExtensions; i++)
{
// Add in padding between the property label and the extension
totalHeight += GetSingleExtensionPixelHeight(extNamesProp, numEntriesPerExtension, i) + PIXEL_HEIGHT_BETWEEN_FIELDS;
}
}
else
{
Debug.LogError("Failed to get all serialized properties for GetPropertyHeight");
}
}
return totalHeight;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
label = EditorGUI.BeginProperty(position, label, property);
var foldoutRect = new Rect(position)
{
height = EditorGUIUtility.singleLineHeight,
};
property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, label);
if (property.isExpanded)
{
DrawExpanded(foldoutRect, property, label);
}
EditorGUI.EndProperty();
}
private void DrawExpanded(in Rect position, SerializedProperty property, GUIContent label)
{
var extensionNamesProp =
property.FindPropertyRelative(OvrAvatarMaterialExtensionConfig.ExtensionNamesPropertyName);
var entryNamesProp = property.FindPropertyRelative(OvrAvatarMaterialExtensionConfig.EntryNamesPropertyName);
var replacementNamesProp =
property.FindPropertyRelative(OvrAvatarMaterialExtensionConfig.ReplacementNamesPropertyName);
if (extensionNamesProp != null && entryNamesProp != null && replacementNamesProp != null)
{
Debug.Assert(extensionNamesProp.arraySize == entryNamesProp.arraySize);
Debug.Assert(entryNamesProp.arraySize == replacementNamesProp.arraySize);
int lvl = EditorGUI.indentLevel;
EditorGUI.indentLevel = lvl + 1;
Rect r = position;
for (int i = 0; i < extensionNamesProp.arraySize; i++)
{
// Draw the extension name at one indent level in, it has its own foldout
r = DrawSingleExtension(
r,
extensionNamesProp,
entryNamesProp,
replacementNamesProp,
i);
}
EditorGUI.indentLevel = lvl;
// Draw a + and - button for adding new extension
DrawAddExtensionButton(r, extensionNamesProp, entryNamesProp, replacementNamesProp);
}
else
{
Debug.LogError("Failed to get all serialized properties for DrawExpanded");
}
}
private static Rect DrawAddExtensionButton(
in Rect position,
SerializedProperty extensionNamesProp,
SerializedProperty entryNamesProp,
SerializedProperty replacementNamesProp)
{
var r = GetNextRect(position);
var pRect = new Rect(r.xMin, r.yMin, ADD_BUTTON_PIXEL_WIDTH, EditorGUIUtility.singleLineHeight);
if (GUI.Button(pRect, ADD_EXTENSION_BUTTON_TOOLTIP))
{
AddNewExtension(extensionNamesProp, entryNamesProp, replacementNamesProp);
}
return r;
}
private static Rect DrawAddAndRemoveEntryButtons(
in Rect position,
SerializedProperty extensionNamesProp,
SerializedProperty entryNamesPerExtensionProp,
SerializedProperty replacementNamesPerExtensionProp,
SerializedProperty entryNamesForThisExtension,
SerializedProperty replacementNamesForThisExtension,
int extensionIndex)
{
// Calculate placement rectangles (right aligned)
var r = GetNextRect(position);
var pRect = new Rect(r.xMax - ADD_BUTTON_PIXEL_WIDTH - REMOVE_BUTTON_PIXEL_WIDTH, r.yMin, ADD_BUTTON_PIXEL_WIDTH, EditorGUIUtility.singleLineHeight);
var mRect = new Rect(r.xMax - REMOVE_BUTTON_PIXEL_WIDTH, r.yMin, REMOVE_BUTTON_PIXEL_WIDTH, EditorGUIUtility.singleLineHeight);
if (GUI.Button(pRect, ADD_ENTRY_BUTTON_TOOLTIP))
{
// If clicked, add new entry
AddNewEntryForExtension(entryNamesForThisExtension, replacementNamesForThisExtension);
}
if (GUI.Button(mRect, REMOVE_ENTRY_BUTTON_TOOLTIP))
{
RemoveLastEntryForExtension(entryNamesForThisExtension, replacementNamesForThisExtension);
if (entryNamesForThisExtension.arraySize == 0)
{
// Empty extension, remove it
RemoveExtension(extensionNamesProp, entryNamesPerExtensionProp, replacementNamesPerExtensionProp, extensionIndex);
}
}
return r;
}
// Returns last used rect
private static Rect DrawSingleExtension(
in Rect position,
SerializedProperty extensionNamesProp,
SerializedProperty entryNamesPerExtensionProp,
SerializedProperty replacementNamesPerExtensionProp,
int extensionIndex)
{
// Draw the extension name at one indent level in, it has it's own foldout
// Draw foldout to left of property label
var r = GetNextRect(position);
var prop = extensionNamesProp.GetArrayElementAtIndex(extensionIndex);
DrawExtensionFoldout(r, prop);
if (prop.isExpanded)
{
// Add all entries for this extension
var keysProp = GetNestedSerializedArrayProperty(entryNamesPerExtensionProp, extensionIndex);
var valuesProp = GetNestedSerializedArrayProperty(replacementNamesPerExtensionProp, extensionIndex);
// Make a label that states that the following are entries
r = GetNextRect(r);
EditorGUI.LabelField(r, ENTRIES_LABEL_TEXT);
EditorGUI.indentLevel++;
for (int i = 0; i < keysProp.arraySize; i++)
{
r = GetNextRect(r);
var valueWidth = r.width - ENTRY_NAME_WIDTH;
var keyRect = new Rect(r.xMin, r.yMin, ENTRY_NAME_WIDTH, r.height);
var valueRect = new Rect(keyRect.xMax, r.yMin, valueWidth, r.height);
var keyProp = keysProp.GetArrayElementAtIndex(i);
var valueProp = valuesProp.GetArrayElementAtIndex(i);
EditorGUI.PropertyField(keyRect, keyProp, GUIContent.none, false);
EditorGUI.PropertyField(valueRect, valueProp, GUIContent.none, false);
}
EditorGUI.indentLevel--;
// Draw +- buttons for adding and removing entries
r = DrawAddAndRemoveEntryButtons(
r,
extensionNamesProp,
entryNamesPerExtensionProp,
replacementNamesPerExtensionProp,
keysProp,
valuesProp,
extensionIndex);
}
return r;
}
private static float GetSingleExtensionPixelHeight(
SerializedProperty extensionNamesProp,
SerializedProperty entriesProp,
int extensionIndex)
{
// Add in height for each extension
// Extension has single line at least extension name label
float extensionHeight = EditorGUIUtility.singleLineHeight;
var prop = extensionNamesProp.GetArrayElementAtIndex(extensionIndex);
if (prop.isExpanded)
{
// Add a line (and vertical padding) for the "Entries" label
// Add a line (and vertical padding) for the +- buttons
extensionHeight += 2.0f * (EditorGUIUtility.singleLineHeight + PIXEL_HEIGHT_BETWEEN_FIELDS);
// Pull num entries out
prop = GetNestedSerializedArrayProperty(entriesProp, extensionIndex);
var numEntries = prop.arraySize;
if (numEntries > 0)
{
extensionHeight += (EditorGUIUtility.singleLineHeight * numEntries) +
(numEntries * PIXEL_HEIGHT_BETWEEN_FIELDS);
}
}
return extensionHeight;
}
private static Rect GetNextRect(in Rect position)
{
var heightBetweenRects = EditorGUIUtility.singleLineHeight + PIXEL_HEIGHT_BETWEEN_FIELDS;
var heightOfRect = EditorGUIUtility.singleLineHeight;
return new Rect(position.xMin, position.yMin + heightBetweenRects, position.width, heightOfRect);
}
private static void AddNewExtension(
SerializedProperty extNameProp,
SerializedProperty entryNamesProp,
SerializedProperty replacementNamesProp)
{
// Insert empty string as new extension name
var extensionIndex = extNameProp.arraySize;
extNameProp.InsertArrayElementAtIndex(extensionIndex);
var prop = extNameProp.GetArrayElementAtIndex(extensionIndex);
prop.stringValue = DEFAULT_EXTENSION_NAME;
// TODO*: Check for key uniqueness
// Insert new list/array of entries
entryNamesProp.InsertArrayElementAtIndex(extensionIndex); // new list of entries
prop = GetNestedSerializedArrayProperty(entryNamesProp, extensionIndex);
prop.arraySize = 0; // Unity docs say Insert inserts an undefined value, so, explicitly set here
var entryIndex = 0; // Should be new list of entries, so can assume index 0
prop.InsertArrayElementAtIndex(entryIndex);
prop = prop.GetArrayElementAtIndex(entryIndex);
prop.stringValue = DEFAULT_ENTRY_NAME;
// Insert a new list/array of replacement names
replacementNamesProp.InsertArrayElementAtIndex(extensionIndex);
prop = GetNestedSerializedArrayProperty(replacementNamesProp, extensionIndex);
prop.arraySize = 0; // Unity docs say Insert inserts an undefined value, so, explicitly set here
// Add new replacement name
prop.InsertArrayElementAtIndex(entryIndex);
prop = prop.GetArrayElementAtIndex(entryIndex);
prop.stringValue = DEFAULT_REPLACEMENT_NAME;
}
private static void AddNewEntryForExtension(
SerializedProperty entryNamesForThisExtension,
SerializedProperty replacementNamesProp)
{
int entryIndex = entryNamesForThisExtension.arraySize;
// Insert a new default entry
entryNamesForThisExtension.InsertArrayElementAtIndex(entryIndex);
var prop = entryNamesForThisExtension.GetArrayElementAtIndex(entryIndex);
prop.stringValue = DEFAULT_ENTRY_NAME;
// TODO* Enforce name uniqueness?
// Insert a new default mapping (empty string)
replacementNamesProp.InsertArrayElementAtIndex(entryIndex);
prop = replacementNamesProp.GetArrayElementAtIndex(entryIndex);
prop.stringValue = DEFAULT_REPLACEMENT_NAME;
}
private static void RemoveLastEntryForExtension(
SerializedProperty entryNamesForThisExtension,
SerializedProperty replacementNamesForThisExtension)
{
// Seemingly have to set data to be null first, then delete....
// Unity docs don't say this but forums do
int lastIndex = entryNamesForThisExtension.arraySize - 1;
if (lastIndex >= 0)
{
entryNamesForThisExtension.DeleteArrayElementAtIndex(lastIndex);
replacementNamesForThisExtension.DeleteArrayElementAtIndex(lastIndex);
}
}
private static void RemoveExtension(
SerializedProperty extensionNames,
SerializedProperty entryNameLists,
SerializedProperty replacementNameLists,
int extensionIndex)
{
extensionNames.DeleteArrayElementAtIndex(extensionIndex);
entryNameLists.DeleteArrayElementAtIndex(extensionIndex);
replacementNameLists.DeleteArrayElementAtIndex(extensionIndex);
}
private static SerializedProperty GetNestedSerializedArrayProperty(SerializedProperty property, int index)
{
var prop = property.GetArrayElementAtIndex(index);
return prop.FindPropertyRelative(OvrAvatarMaterialExtensionConfig.InnerListProperyName);
}
private static void DrawExtensionFoldout(in Rect position, SerializedProperty prop)
{
EditorGUI.PropertyField(position, prop, EXTENSION_ENTRY_FOLDOUT_CONTENT, false);
prop.isExpanded = EditorGUI.Foldout(position, prop.isExpanded, GUIContent.none, false);
}
}
}