update
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
using Rive.Components;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Rive.EditorTools
|
||||
{
|
||||
[CustomEditor(typeof(AtlasRenderTargetStrategy), true)]
|
||||
internal class AtlasRenderTextureStrategyInspector : RiveBaseEditor
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0622d49537654134954a8e5d3817d74
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- m_styleSheet: {fileID: 7433441132597879392, guid: cc66ac87f8ecb4e3c91384370163ee7b,
|
||||
type: 3}
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/AtlasRenderTextureStrategyInspector.cs
|
||||
uploadId: 896810
|
||||
@@ -0,0 +1,25 @@
|
||||
using Rive.Components;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Rive.EditorTools
|
||||
{
|
||||
[CustomEditor(typeof(RiveCanvasRenderer), true)]
|
||||
internal class CanvasPanelRendererEditor : PanelRendererInspector
|
||||
{
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
|
||||
if (PanelRenderer.RivePanel == null)
|
||||
{
|
||||
RivePanel existingPanel = PanelRenderer.GetComponent<RivePanel>();
|
||||
|
||||
if (existingPanel != null)
|
||||
{
|
||||
PanelRenderer.RivePanel = existingPanel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6cf302e24face493783124060035fb4c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- m_styleSheet: {fileID: 7433441132597879392, guid: cc66ac87f8ecb4e3c91384370163ee7b,
|
||||
type: 3}
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/CanvasPanelRendererEditor.cs
|
||||
uploadId: 896810
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 43b94ffc302e04258ab481523df9abaa
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
#if !UNITY_2022_1_OR_NEWER
|
||||
using UnityEditor.UIElements; // Required for Unity 2021
|
||||
#endif
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace Rive.EditorTools
|
||||
{
|
||||
/// <summary>
|
||||
/// We don't directly use the property drawer for the Alignment class, but we keep it as a way to create the dropdown field in the inspector for RiveBaseEditor.
|
||||
/// We do this because the RiveBaseEditor class supports a bunch of the custom attributes we've created and having multiple drawers for the same class can cause conflicts so we do it all in the RiveBaseEditor class.
|
||||
/// </summary>
|
||||
//[CustomPropertyDrawer(typeof(Alignment))]
|
||||
internal class AlignmentPropertyDrawer : PropertyDrawer
|
||||
{
|
||||
private static readonly (string display, Alignment value)[] OPTIONS = new[]
|
||||
{
|
||||
("Top Left", Alignment.TopLeft),
|
||||
("Top Center", Alignment.TopCenter),
|
||||
("Top Right", Alignment.TopRight),
|
||||
("Center Left", Alignment.CenterLeft),
|
||||
("Center", Alignment.Center),
|
||||
("Center Right", Alignment.CenterRight),
|
||||
("Bottom Left", Alignment.BottomLeft),
|
||||
("Bottom Center", Alignment.BottomCenter),
|
||||
("Bottom Right", Alignment.BottomRight)
|
||||
};
|
||||
|
||||
|
||||
|
||||
public override VisualElement CreatePropertyGUI(SerializedProperty property)
|
||||
{
|
||||
var container = new VisualElement();
|
||||
|
||||
var xProp = property.FindPropertyRelative(Alignment.BindingPath_Xfield);
|
||||
var yProp = property.FindPropertyRelative(Alignment.BindingPath_Yfield);
|
||||
|
||||
// Default to Center if we can't get the values
|
||||
var centerIndex = Array.FindIndex(OPTIONS, o => o.value.Equals(Alignment.Center));
|
||||
var currentIndex = centerIndex;
|
||||
|
||||
if (xProp != null && yProp != null)
|
||||
{
|
||||
var currentAlignment = new Alignment(xProp.floatValue, yProp.floatValue);
|
||||
currentIndex = Array.FindIndex(OPTIONS, o => o.value.Equals(currentAlignment));
|
||||
if (currentIndex < 0) currentIndex = centerIndex;
|
||||
}
|
||||
|
||||
var choices = OPTIONS.Select(o => o.display).ToList();
|
||||
|
||||
var dropdown = new PopupField<string>(
|
||||
property.displayName,
|
||||
choices,
|
||||
currentIndex
|
||||
);
|
||||
|
||||
dropdown.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
var index = choices.IndexOf(evt.newValue);
|
||||
if (index >= 0 && xProp != null && yProp != null)
|
||||
{
|
||||
var selectedAlignment = OPTIONS[index].value;
|
||||
xProp.floatValue = selectedAlignment.X;
|
||||
yProp.floatValue = selectedAlignment.Y;
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
});
|
||||
|
||||
dropdown.AddToClassList(StyleHelper.CLASS_FIELD);
|
||||
|
||||
// This ensures that the dropdown is aligned with other fields in the inspector
|
||||
dropdown.AddToClassList(BaseField<UnityEditor.UIElements.PropertyField>.alignedFieldUssClassName);
|
||||
container.Add(dropdown);
|
||||
return container;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 114c8e3b6a625400a8497c76cf6af315
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/CustomElements/AlignmentPropertyDrawer.cs
|
||||
uploadId: 896810
|
||||
@@ -0,0 +1,144 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine.UIElements;
|
||||
using Rive.Utils;
|
||||
|
||||
namespace Rive.EditorTools
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(DropdownAttribute))]
|
||||
internal class DropdownDrawer : PropertyDrawer
|
||||
{
|
||||
private PopupOrTextField dropdown;
|
||||
private SerializedProperty property;
|
||||
private object target;
|
||||
private DropdownAttribute dropdownAttr;
|
||||
private MemberInfo optionsMember;
|
||||
|
||||
public override VisualElement CreatePropertyGUI(SerializedProperty property)
|
||||
{
|
||||
this.property = property;
|
||||
dropdownAttr = attribute as DropdownAttribute;
|
||||
target = property.serializedObject.targetObject;
|
||||
var targetType = target.GetType();
|
||||
|
||||
// Try to find member (field, property, or method)
|
||||
optionsMember = targetType.GetField(dropdownAttr.OptionsMemberName,
|
||||
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
|
||||
|
||||
if (optionsMember == null)
|
||||
{
|
||||
optionsMember = targetType.GetProperty(dropdownAttr.OptionsMemberName,
|
||||
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
|
||||
}
|
||||
|
||||
if (optionsMember == null)
|
||||
{
|
||||
optionsMember = targetType.GetMethod(dropdownAttr.OptionsMemberName,
|
||||
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
|
||||
}
|
||||
|
||||
if (optionsMember == null)
|
||||
{
|
||||
var errorContainer = new VisualElement();
|
||||
errorContainer.Add(new HelpBox($"Member {dropdownAttr.OptionsMemberName} not found", HelpBoxMessageType.Error));
|
||||
errorContainer.Add(new PropertyField(property));
|
||||
return errorContainer;
|
||||
}
|
||||
|
||||
dropdown = CreateDropdown();
|
||||
|
||||
// Only register for updates if TrackChanges is enabled
|
||||
if (dropdownAttr.TrackChanges)
|
||||
{
|
||||
EditorApplication.update += UpdateDropdownOptions;
|
||||
|
||||
dropdown.RegisterCallback<DetachFromPanelEvent>(evt =>
|
||||
{
|
||||
EditorApplication.update -= UpdateDropdownOptions;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// For non-tracked dropdowns, we still want to update when the panel is attached
|
||||
dropdown.RegisterCallback<AttachToPanelEvent>(evt =>
|
||||
{
|
||||
UpdateDropdownOptions();
|
||||
});
|
||||
}
|
||||
|
||||
return dropdown;
|
||||
}
|
||||
|
||||
private PopupOrTextField CreateDropdown()
|
||||
{
|
||||
var options = GetCurrentOptions();
|
||||
var currentValue = property.stringValue;
|
||||
|
||||
var label = ReflectionUtils.GetPropertyLabel(property);
|
||||
var dropdown = new PopupOrTextField(options, currentValue,
|
||||
label);
|
||||
|
||||
dropdown.BindProperty(property);
|
||||
|
||||
var inspectorFieldAttr = fieldInfo.GetCustomAttribute<InspectorFieldAttribute>();
|
||||
if (inspectorFieldAttr != null)
|
||||
{
|
||||
dropdown.AddToClassList(StyleHelper.CLASS_FIELD);
|
||||
}
|
||||
|
||||
return dropdown;
|
||||
}
|
||||
|
||||
private List<string> GetCurrentOptions()
|
||||
{
|
||||
object options = null;
|
||||
|
||||
switch (optionsMember)
|
||||
{
|
||||
case FieldInfo field:
|
||||
options = field.GetValue(target);
|
||||
break;
|
||||
case PropertyInfo prop:
|
||||
options = prop.GetValue(target);
|
||||
break;
|
||||
case MethodInfo method:
|
||||
options = method.Invoke(target, null);
|
||||
break;
|
||||
}
|
||||
|
||||
if (options is IEnumerable<string> enumerable)
|
||||
{
|
||||
return enumerable.ToList();
|
||||
}
|
||||
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
private void UpdateDropdownOptions()
|
||||
{
|
||||
if (dropdown?.panel == null) return;
|
||||
|
||||
var newOptions = GetCurrentOptions();
|
||||
if (!AreOptionsEqual(dropdown.Choices, newOptions))
|
||||
{
|
||||
dropdown.Choices = newOptions;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private bool AreOptionsEqual(List<string> a, List<string> b)
|
||||
{
|
||||
if (a.Count != b.Count) return false;
|
||||
for (int i = 0; i < a.Count; i++)
|
||||
{
|
||||
if (a[i] != b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 55ad3672c80b042f288de391c21e3bf9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/CustomElements/DropdownDrawer.cs
|
||||
uploadId: 896810
|
||||
@@ -0,0 +1,237 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UnityEditor.UIElements;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Rive.Utils;
|
||||
#if UNITY_6000_3_OR_NEWER
|
||||
using MaterialShaderPropertyType = UnityEngine.Rendering.ShaderPropertyType;
|
||||
#else
|
||||
using MaterialShaderPropertyType = UnityEditor.ShaderUtil.ShaderPropertyType;
|
||||
#endif
|
||||
|
||||
namespace Rive.EditorTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Draws a list of properties from a material on a component as a dropdown. This is useful if you want to display a list of properties from a material on a component in the inspector.
|
||||
/// </summary>
|
||||
[CustomPropertyDrawer(typeof(MaterialPropertiesAttribute))]
|
||||
internal class MaterialPropertiesDrawer : PropertyDrawer
|
||||
{
|
||||
private VisualElement m_root;
|
||||
private List<string> m_availablePropertyNames = new List<string>();
|
||||
private SerializedObject m_serializedObject;
|
||||
|
||||
private Material[] GetMaterialsFromSource(object target, string sourceName)
|
||||
{
|
||||
// Try to get materials directly
|
||||
if (ReflectionUtils.TryGetValue<Material[]>(target, sourceName, out var materials))
|
||||
{
|
||||
return materials;
|
||||
}
|
||||
|
||||
// If we got a renderer instead, get its materials
|
||||
if (ReflectionUtils.TryGetValue<UnityEngine.Renderer>(target, sourceName, out var renderer))
|
||||
{
|
||||
return renderer?.sharedMaterials;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public override VisualElement CreatePropertyGUI(SerializedProperty property)
|
||||
{
|
||||
var attr = attribute as MaterialPropertiesAttribute;
|
||||
m_root = new VisualElement();
|
||||
m_serializedObject = property.serializedObject;
|
||||
|
||||
var target = property.serializedObject.targetObject;
|
||||
var materials = GetMaterialsFromSource(target, attr.MaterialsSourceName);
|
||||
|
||||
if (materials == null)
|
||||
{
|
||||
m_root.Add(new HelpBox($"Could not find materials source: {attr.MaterialsSourceName}", HelpBoxMessageType.Error));
|
||||
return m_root;
|
||||
}
|
||||
|
||||
UpdateUI(property, materials, attr.PropertyType);
|
||||
return m_root;
|
||||
}
|
||||
|
||||
private void UpdateUI(SerializedProperty property, Material[] materials, MaterialShaderPropertyType propertyType)
|
||||
{
|
||||
m_root.Clear();
|
||||
UpdateAvailablePropertyNames(materials, propertyType);
|
||||
|
||||
var keysProperty = property.FindPropertyRelative(SerializedDictionary<int, Components.RiveTextureRenderer.PropertyNameListHolder>.BindingPath_Keys);
|
||||
var valuesProperty = property.FindPropertyRelative(SerializedDictionary<int, Components.RiveTextureRenderer.PropertyNameListHolder>.BindingPath_Values);
|
||||
|
||||
// Pre-create property holders for all materials
|
||||
EnsurePropertyHoldersExist(keysProperty, valuesProperty, materials.Length);
|
||||
|
||||
for (int i = 0; i < materials.Length; i++)
|
||||
{
|
||||
var material = materials[i];
|
||||
if (material == null) continue;
|
||||
|
||||
var materialFoldout = new Foldout { text = $"Material {i}: {material.name}" };
|
||||
m_root.Add(materialFoldout);
|
||||
|
||||
var propertyListHolder = FindPropertyListHolder(keysProperty, valuesProperty, i);
|
||||
if (propertyListHolder != null)
|
||||
{
|
||||
var propertyList = propertyListHolder.FindPropertyRelative(Components.RiveTextureRenderer.PropertyNameListHolder.BindingPath_PropertyNames);
|
||||
if (propertyList != null && propertyList.serializedObject != null)
|
||||
{
|
||||
var listView = CreateListView(propertyList);
|
||||
materialFoldout.Add(listView);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply any changes made during setup
|
||||
property.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private void EnsurePropertyHoldersExist(SerializedProperty keysProperty, SerializedProperty valuesProperty, int materialCount)
|
||||
{
|
||||
// First, create a list of existing material indices
|
||||
var existingIndices = new HashSet<int>();
|
||||
for (int i = 0; i < keysProperty.arraySize; i++)
|
||||
{
|
||||
existingIndices.Add(keysProperty.GetArrayElementAtIndex(i).intValue);
|
||||
}
|
||||
|
||||
// Create missing property holders
|
||||
for (int i = 0; i < materialCount; i++)
|
||||
{
|
||||
if (!existingIndices.Contains(i))
|
||||
{
|
||||
keysProperty.InsertArrayElementAtIndex(keysProperty.arraySize);
|
||||
keysProperty.GetArrayElementAtIndex(keysProperty.arraySize - 1).intValue = i;
|
||||
|
||||
valuesProperty.InsertArrayElementAtIndex(valuesProperty.arraySize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SerializedProperty FindPropertyListHolder(SerializedProperty keysProperty, SerializedProperty valuesProperty, int materialIndex)
|
||||
{
|
||||
for (int i = 0; i < keysProperty.arraySize; i++)
|
||||
{
|
||||
if (keysProperty.GetArrayElementAtIndex(i).intValue == materialIndex)
|
||||
{
|
||||
return valuesProperty.GetArrayElementAtIndex(i);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ListView CreateListView(SerializedProperty propertyList)
|
||||
{
|
||||
var listView = new ListView()
|
||||
{
|
||||
reorderable = true,
|
||||
showAddRemoveFooter = true,
|
||||
showBorder = true,
|
||||
showFoldoutHeader = false,
|
||||
showBoundCollectionSize = false,
|
||||
virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight
|
||||
};
|
||||
|
||||
// Delay binding until the next frame to ensure proper initialization
|
||||
m_root.schedule.Execute(() =>
|
||||
{
|
||||
listView.bindingPath = propertyList.propertyPath;
|
||||
listView.BindProperty(propertyList.serializedObject);
|
||||
});
|
||||
|
||||
listView.makeItem = () => new PopupOrTextField(m_availablePropertyNames, "");
|
||||
listView.bindItem = (element, index) =>
|
||||
{
|
||||
var popupOrTextField = element as PopupOrTextField;
|
||||
popupOrTextField.Choices = m_availablePropertyNames;
|
||||
|
||||
if (propertyList != null && propertyList.serializedObject != null)
|
||||
{
|
||||
var itemProperty = propertyList.GetArrayElementAtIndex(index);
|
||||
popupOrTextField.BindProperty(itemProperty);
|
||||
}
|
||||
};
|
||||
|
||||
listView.itemsAdded += (indexes) =>
|
||||
{
|
||||
if (propertyList != null && propertyList.serializedObject != null)
|
||||
{
|
||||
foreach (int index in indexes)
|
||||
{
|
||||
var itemProperty = propertyList.GetArrayElementAtIndex(index);
|
||||
if (string.IsNullOrEmpty(itemProperty.stringValue))
|
||||
{
|
||||
itemProperty.stringValue = m_availablePropertyNames.FirstOrDefault() ?? "";
|
||||
propertyList.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
listView.Rebuild();
|
||||
};
|
||||
|
||||
return listView;
|
||||
}
|
||||
|
||||
private static int GetPropertyCount(Shader shader)
|
||||
{
|
||||
#if UNITY_6000_3_OR_NEWER
|
||||
return shader.GetPropertyCount();
|
||||
#else
|
||||
return ShaderUtil.GetPropertyCount(shader);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static MaterialShaderPropertyType GetPropertyType(Shader shader, int index)
|
||||
{
|
||||
#if UNITY_6000_3_OR_NEWER
|
||||
return shader.GetPropertyType(index);
|
||||
#else
|
||||
return ShaderUtil.GetPropertyType(shader, index);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static string GetPropertyName(Shader shader, int index)
|
||||
{
|
||||
#if UNITY_6000_3_OR_NEWER
|
||||
return shader.GetPropertyName(index);
|
||||
#else
|
||||
return ShaderUtil.GetPropertyName(shader, index);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void UpdateAvailablePropertyNames(Material[] materials, MaterialShaderPropertyType propertyType)
|
||||
{
|
||||
m_availablePropertyNames.Clear();
|
||||
|
||||
foreach (var material in materials)
|
||||
{
|
||||
if (material != null)
|
||||
{
|
||||
var shader = material.shader;
|
||||
for (int i = 0; i < GetPropertyCount(shader); i++)
|
||||
{
|
||||
if (GetPropertyType(shader, i) == propertyType)
|
||||
{
|
||||
string propertyName = GetPropertyName(shader, i);
|
||||
if (!m_availablePropertyNames.Contains(propertyName))
|
||||
{
|
||||
m_availablePropertyNames.Add(propertyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13ea997b79c3b4e1b82a9adeabd70082
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/CustomElements/MaterialPropertiesDrawer.cs
|
||||
uploadId: 896810
|
||||
@@ -0,0 +1,445 @@
|
||||
using UnityEngine.UIElements;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditor.UIElements;
|
||||
using Rive.Utils;
|
||||
|
||||
namespace Rive.EditorTools
|
||||
{
|
||||
/// <summary>
|
||||
/// A visual element that allows the user to select from a list of choices or enter a custom value.
|
||||
/// </summary>
|
||||
internal class PopupOrTextField : VisualElement, INotifyValueChanged<string>
|
||||
{
|
||||
private PopupField<string> popupField;
|
||||
private TextField textField;
|
||||
private Button switchModeButton;
|
||||
private bool isCustomValue;
|
||||
private bool isUserEditing;
|
||||
private bool isProgrammaticChange;
|
||||
private SerializedProperty boundProperty;
|
||||
private SerializedObject serializedObject;
|
||||
private UnityEngine.Object targetObject;
|
||||
|
||||
private string m_Value;
|
||||
public string value
|
||||
{
|
||||
get => m_Value;
|
||||
set
|
||||
{
|
||||
if (m_Value != value)
|
||||
{
|
||||
using (var changeEvent = ChangeEvent<string>.GetPooled(m_Value, value))
|
||||
{
|
||||
changeEvent.target = this;
|
||||
SetValueWithoutNotify(value);
|
||||
SendEvent(changeEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> Choices
|
||||
{
|
||||
get => popupField.choices;
|
||||
set
|
||||
{
|
||||
popupField.choices = value;
|
||||
bool valueIsInChoices = value.Contains(m_Value);
|
||||
|
||||
// Handle transition from Popup to Custom
|
||||
if (!isCustomValue && !valueIsInChoices)
|
||||
{
|
||||
isCustomValue = true;
|
||||
isProgrammaticChange = true;
|
||||
SetValueWithoutNotify(m_Value);
|
||||
isProgrammaticChange = false;
|
||||
}
|
||||
// Handle transition from Custom to Popup
|
||||
else if (valueIsInChoices && isCustomValue)
|
||||
{
|
||||
isCustomValue = false;
|
||||
isProgrammaticChange = true;
|
||||
SetValueWithoutNotify(m_Value);
|
||||
isProgrammaticChange = false;
|
||||
}
|
||||
|
||||
UpdateVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
public string Label
|
||||
{
|
||||
get => popupField.label;
|
||||
set
|
||||
{
|
||||
popupField.label = value;
|
||||
textField.label = value;
|
||||
}
|
||||
}
|
||||
|
||||
public PopupOrTextField() : this(new List<string>(), "") { }
|
||||
|
||||
public PopupOrTextField(List<string> choices, string currentValue, string labelText = null)
|
||||
{
|
||||
popupField = new PopupField<string>(choices, 0);
|
||||
textField = new TextField();
|
||||
switchModeButton = new Button(ToggleMode)
|
||||
{
|
||||
text = "✎",
|
||||
tooltip = "Switch to text input"
|
||||
};
|
||||
|
||||
SetupUI();
|
||||
SetupCallbacks();
|
||||
SetInitialState(currentValue);
|
||||
|
||||
if (labelText != null)
|
||||
{
|
||||
Label = labelText;
|
||||
}
|
||||
|
||||
RegisterCallback<SerializedPropertyChangeEvent>(OnSerializedPropertyChange);
|
||||
}
|
||||
|
||||
private void SetupUI()
|
||||
{
|
||||
var container = new VisualElement();
|
||||
container.style.flexDirection = FlexDirection.Row;
|
||||
container.style.width = new StyleLength(Length.Percent(100));
|
||||
container.Add(popupField);
|
||||
container.Add(textField);
|
||||
container.Add(switchModeButton);
|
||||
|
||||
SetupFieldStyles(popupField);
|
||||
SetupFieldStyles(textField);
|
||||
SetupButtonStyles(switchModeButton);
|
||||
|
||||
textField.style.display = DisplayStyle.None;
|
||||
textField.visible = false;
|
||||
popupField.style.display = DisplayStyle.Flex;
|
||||
popupField.visible = true;
|
||||
|
||||
Add(container);
|
||||
}
|
||||
|
||||
private void SetupFieldStyles(VisualElement field)
|
||||
{
|
||||
// This keeps inspector positioned around the same point as other unity fields. Otherwise the popup fills the whole row, when it should stop in the middle.
|
||||
field.AddToClassList(BaseField<PropertyField>.alignedFieldUssClassName); // Same as using "unity-base-field__aligned" in UXML
|
||||
|
||||
|
||||
field.style.flexGrow = 1;
|
||||
field.style.marginRight = 20;
|
||||
field.style.paddingBottom = 0;
|
||||
field.style.paddingTop = 0;
|
||||
field.style.paddingLeft = 0;
|
||||
field.style.marginBottom = 0;
|
||||
field.style.marginTop = 0;
|
||||
field.style.marginLeft = 0;
|
||||
}
|
||||
|
||||
private void SetupButtonStyles(Button button)
|
||||
{
|
||||
button.style.position = Position.Absolute;
|
||||
button.style.right = 0;
|
||||
button.style.width = 20;
|
||||
button.style.height = popupField.style.height;
|
||||
button.style.marginRight = 0;
|
||||
button.style.marginLeft = 0;
|
||||
button.style.marginTop = 0;
|
||||
button.style.marginBottom = 0;
|
||||
button.style.paddingBottom = 0;
|
||||
button.style.paddingTop = 0;
|
||||
button.style.paddingLeft = 0;
|
||||
button.style.paddingRight = 0;
|
||||
}
|
||||
|
||||
|
||||
private void SetupCallbacks()
|
||||
{
|
||||
popupField.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
if (isProgrammaticChange)
|
||||
return; // Ignore programmatic changes to prevent recursive calls
|
||||
|
||||
value = evt.newValue;
|
||||
});
|
||||
|
||||
textField.RegisterCallback<FocusInEvent>(evt => isUserEditing = true);
|
||||
textField.RegisterCallback<FocusOutEvent>(evt =>
|
||||
{
|
||||
isUserEditing = false;
|
||||
UpdateVisualState();
|
||||
});
|
||||
|
||||
textField.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
if (isProgrammaticChange)
|
||||
return;
|
||||
|
||||
value = evt.newValue;
|
||||
});
|
||||
}
|
||||
|
||||
private void SetInitialState(string initialValue)
|
||||
{
|
||||
m_Value = initialValue;
|
||||
isCustomValue = !Choices.Contains(initialValue);
|
||||
|
||||
if (!isCustomValue)
|
||||
{
|
||||
popupField.SetValueWithoutNotify(initialValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
textField.SetValueWithoutNotify(initialValue);
|
||||
}
|
||||
|
||||
UpdateVisibility();
|
||||
}
|
||||
|
||||
private void ToggleMode()
|
||||
{
|
||||
if (targetObject != null)
|
||||
{
|
||||
Undo.RecordObject(targetObject, "Toggle PopupOrTextField Mode");
|
||||
}
|
||||
|
||||
var initialValue = m_Value;
|
||||
isCustomValue = !isCustomValue;
|
||||
|
||||
if (isCustomValue)
|
||||
{
|
||||
textField.SetValueWithoutNotify(m_Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Choices.Contains(m_Value))
|
||||
{
|
||||
popupField.SetValueWithoutNotify(m_Value);
|
||||
}
|
||||
else if (Choices.Count > 0)
|
||||
{
|
||||
SetValueWithoutNotify(Choices[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
popupField.SetValueWithoutNotify(string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateVisibility();
|
||||
|
||||
if (targetObject != null)
|
||||
{
|
||||
EditorUtility.SetDirty(targetObject);
|
||||
}
|
||||
|
||||
if (initialValue != m_Value)
|
||||
{
|
||||
using (var changeEvent = ChangeEvent<string>.GetPooled(initialValue, m_Value))
|
||||
{
|
||||
changeEvent.target = this;
|
||||
SendEvent(changeEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateVisualState()
|
||||
{
|
||||
bool valueInChoices = Choices.Contains(m_Value);
|
||||
|
||||
if (valueInChoices)
|
||||
{
|
||||
if (!isCustomValue)
|
||||
{
|
||||
// We make sure the popupField reflects the current value
|
||||
popupField.SetValueWithoutNotify(m_Value);
|
||||
}
|
||||
|
||||
if (isCustomValue && !isUserEditing)
|
||||
{
|
||||
isCustomValue = false;
|
||||
UpdateVisibility();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
textField.SetValueWithoutNotify(m_Value);
|
||||
if (!isCustomValue)
|
||||
{
|
||||
isCustomValue = true;
|
||||
UpdateVisibility();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateVisibility()
|
||||
{
|
||||
if (isCustomValue)
|
||||
{
|
||||
popupField.style.display = DisplayStyle.None;
|
||||
popupField.visible = false;
|
||||
|
||||
textField.style.display = DisplayStyle.Flex;
|
||||
textField.visible = true;
|
||||
|
||||
switchModeButton.text = "▼";
|
||||
switchModeButton.tooltip = "Switch to dropdown";
|
||||
}
|
||||
else
|
||||
{
|
||||
textField.style.display = DisplayStyle.None;
|
||||
textField.visible = false;
|
||||
|
||||
popupField.style.display = DisplayStyle.Flex;
|
||||
popupField.visible = true;
|
||||
|
||||
switchModeButton.text = "✎";
|
||||
switchModeButton.tooltip = "Switch to text input";
|
||||
}
|
||||
|
||||
this.MarkDirtyRepaint();
|
||||
}
|
||||
|
||||
public void SetValueWithoutNotify(string newValue)
|
||||
{
|
||||
if (serializedObject != null)
|
||||
{
|
||||
serializedObject.Update();
|
||||
}
|
||||
|
||||
m_Value = newValue;
|
||||
|
||||
// Force check if value is in choices and update mode accordingly
|
||||
bool valueInChoices = Choices.Contains(newValue);
|
||||
|
||||
if (valueInChoices && (!isUserEditing || isProgrammaticChange))
|
||||
{
|
||||
isCustomValue = false;
|
||||
isProgrammaticChange = true;
|
||||
popupField.SetValueWithoutNotify(newValue);
|
||||
isProgrammaticChange = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!valueInChoices)
|
||||
{
|
||||
isCustomValue = true;
|
||||
}
|
||||
isProgrammaticChange = true;
|
||||
textField.SetValueWithoutNotify(newValue);
|
||||
isProgrammaticChange = false;
|
||||
}
|
||||
|
||||
UpdateVisibility();
|
||||
|
||||
if (boundProperty != null)
|
||||
{
|
||||
boundProperty.stringValue = newValue;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSerializedPropertyChange(SerializedPropertyChangeEvent evt)
|
||||
{
|
||||
if (evt.changedProperty == boundProperty)
|
||||
{
|
||||
isProgrammaticChange = true;
|
||||
SetValueWithoutNotify(boundProperty.stringValue);
|
||||
isProgrammaticChange = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void BindProperty(SerializedProperty property)
|
||||
{
|
||||
UnbindProperty();
|
||||
|
||||
if (property != null && property.propertyType == SerializedPropertyType.String)
|
||||
{
|
||||
boundProperty = property;
|
||||
serializedObject = property.serializedObject;
|
||||
targetObject = serializedObject.targetObject;
|
||||
SetInitialState(property.stringValue);
|
||||
EditorApplication.update += UpdateFromSerializedProperty;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugLogger.Instance.LogError("PopupOrTextField: Attempted to bind to a null or non-string property.");
|
||||
}
|
||||
|
||||
this.RegisterCallback<AttachToPanelEvent>(OnAttachToPanel);
|
||||
this.RegisterCallback<DetachFromPanelEvent>(OnDetachFromPanel);
|
||||
}
|
||||
|
||||
private void OnAttachToPanel(AttachToPanelEvent evt)
|
||||
{
|
||||
if (serializedObject != null && boundProperty != null)
|
||||
{
|
||||
serializedObject.Update();
|
||||
SetInitialState(boundProperty.stringValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDetachFromPanel(DetachFromPanelEvent evt)
|
||||
{
|
||||
UnbindProperty();
|
||||
}
|
||||
|
||||
private void UpdateFromSerializedProperty()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (serializedObject == null || boundProperty == null)
|
||||
{
|
||||
UnbindProperty();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the serializedObject is still valid
|
||||
if (serializedObject.targetObject == null)
|
||||
{
|
||||
UnbindProperty();
|
||||
return;
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
// Double-check everything is still valid after the update
|
||||
if (boundProperty == null || boundProperty.serializedObject == null || boundProperty.serializedObject.targetObject == null)
|
||||
{
|
||||
UnbindProperty();
|
||||
return;
|
||||
}
|
||||
|
||||
if (boundProperty.propertyType == SerializedPropertyType.String)
|
||||
{
|
||||
string newValue = boundProperty.stringValue;
|
||||
if (m_Value != newValue && !isUserEditing)
|
||||
{
|
||||
SetValueWithoutNotify(newValue);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugLogger.Instance.LogWarning($"PopupOrTextField: Bound property is not a string. Property path: {boundProperty.propertyPath}");
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
UnbindProperty();
|
||||
}
|
||||
}
|
||||
|
||||
private void UnbindProperty()
|
||||
{
|
||||
boundProperty = null;
|
||||
serializedObject = null;
|
||||
targetObject = null;
|
||||
EditorApplication.update -= UpdateFromSerializedProperty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 226b35944e5bc4908b226f5fb91d99d2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/CustomElements/PopupOrTextField.cs
|
||||
uploadId: 896810
|
||||
@@ -0,0 +1,37 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEngine.UIElements;
|
||||
using Rive.Utils;
|
||||
|
||||
namespace Rive.EditorTools
|
||||
{
|
||||
[CustomPropertyDrawer(typeof(WidthHeightDimensionsAttribute))]
|
||||
internal class WidthHeightDimensionsDrawer : PropertyDrawer
|
||||
{
|
||||
public override VisualElement CreatePropertyGUI(SerializedProperty property)
|
||||
{
|
||||
var attr = attribute as WidthHeightDimensionsAttribute;
|
||||
var label = ReflectionUtils.GetPropertyLabel(property) ?? attr.Label;
|
||||
|
||||
// Get tooltip from TooltipAttribute if present
|
||||
string tooltip = null;
|
||||
var tooltipAttribute = fieldInfo.GetCustomAttributes(typeof(TooltipAttribute), true);
|
||||
if (tooltipAttribute.Length > 0)
|
||||
{
|
||||
tooltip = (tooltipAttribute[0] as TooltipAttribute).tooltip;
|
||||
}
|
||||
|
||||
var field = new WidthHeightDimensionsField(
|
||||
label,
|
||||
attr.WidthLabel,
|
||||
attr.HeightLabel,
|
||||
tooltip
|
||||
);
|
||||
|
||||
field.BindProperty(property);
|
||||
return field;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d32c71bc31ac443d5a36065d1d672d12
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/CustomElements/WidthHeightDimensionsDrawer.cs
|
||||
uploadId: 896810
|
||||
@@ -0,0 +1,50 @@
|
||||
using UnityEngine.UIElements;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Rive.EditorTools
|
||||
{
|
||||
/// <summary>
|
||||
/// A field for editing a Vector2Int representing width and height.
|
||||
/// </summary>
|
||||
internal class WidthHeightDimensionsField : VisualElement
|
||||
{
|
||||
|
||||
public IntegerField WidthField { get; private set; }
|
||||
public IntegerField HeightField { get; private set; }
|
||||
|
||||
public WidthHeightDimensionsField(string label, string widthLabel = "Width", string heightLabel = "Height", string tooltip = null)
|
||||
{
|
||||
var foldout = new Foldout
|
||||
{
|
||||
text = label,
|
||||
tooltip = tooltip,
|
||||
value = true // Start expanded
|
||||
};
|
||||
Add(foldout);
|
||||
|
||||
var container = new VisualElement();
|
||||
foldout.Add(container);
|
||||
|
||||
WidthField = new IntegerField(widthLabel)
|
||||
{
|
||||
style = { marginTop = 4 }
|
||||
};
|
||||
WidthField.AddToClassList(BaseField<int>.alignedFieldUssClassName);
|
||||
container.Add(WidthField);
|
||||
|
||||
HeightField = new IntegerField(heightLabel)
|
||||
{
|
||||
style = { marginTop = 4 }
|
||||
};
|
||||
HeightField.AddToClassList(BaseField<int>.alignedFieldUssClassName);
|
||||
container.Add(HeightField);
|
||||
}
|
||||
|
||||
public void BindProperty(SerializedProperty property)
|
||||
{
|
||||
WidthField.BindProperty(property.FindPropertyRelative("x"));
|
||||
HeightField.BindProperty(property.FindPropertyRelative("y"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 18c92b4c6ab3f42e6a1e827ed99c91fa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/CustomElements/WidthHeightDimensionsField.cs
|
||||
uploadId: 896810
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3793298efdc946ffb5185da184f3cf6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/DataBindingPlaygroundWindow.cs
|
||||
uploadId: 896810
|
||||
383
Packages/app.rive.rive-unity/Editor/Components/MenuItems.cs
Normal file
383
Packages/app.rive.rive-unity/Editor/Components/MenuItems.cs
Normal file
@@ -0,0 +1,383 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using Rive.Components;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
[assembly: InternalsVisibleTo("Rive.Tests.Editor")]
|
||||
namespace Rive.EditorTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom menu items for creating Rive components.
|
||||
/// </summary>
|
||||
//Make internals visible to test assembly
|
||||
internal class MenuItems
|
||||
{
|
||||
internal enum PanelContext
|
||||
{
|
||||
Standalone = 0,
|
||||
Canvas = 1
|
||||
}
|
||||
|
||||
[MenuItem("GameObject/Rive/Rive Panel", false, 10)]
|
||||
static void CreateRivePanel(MenuCommand menuCommand) =>
|
||||
CreateRivePanelInternal(menuCommand, PanelContext.Standalone);
|
||||
|
||||
[MenuItem("GameObject/Rive/Rive Panel (Canvas)", false, 11)]
|
||||
static void CreateRivePanelWithCanvas(MenuCommand menuCommand) =>
|
||||
CreateRivePanelInternal(menuCommand, PanelContext.Canvas);
|
||||
|
||||
[MenuItem("GameObject/Rive/Widgets/Rive Widget", false, 12)]
|
||||
static void CreateRiveWidget(MenuCommand menuCommand)
|
||||
{
|
||||
GameObject widgetObj = new GameObject("Rive Widget", typeof(RiveWidget));
|
||||
|
||||
// If we have a context (selected object), try to parent to it
|
||||
GameObject parent = menuCommand.context as GameObject;
|
||||
if (parent != null)
|
||||
{
|
||||
GameObjectUtility.SetParentAndAlign(widgetObj, parent);
|
||||
|
||||
ConfigureRectTransformToFill(widgetObj.GetComponent<RectTransform>());
|
||||
|
||||
}
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(widgetObj, "Create Rive Widget");
|
||||
Selection.activeObject = widgetObj;
|
||||
}
|
||||
|
||||
[MenuItem("GameObject/Rive/Widgets/Procedural Rive Widget", false, 13)]
|
||||
static void CreateProceduralRiveWidget(MenuCommand menuCommand)
|
||||
{
|
||||
GameObject widgetObj = new GameObject("Procedural Rive Widget", typeof(ProceduralRiveWidget));
|
||||
|
||||
// If we have a context (selected object), try to parent to it
|
||||
GameObject parent = menuCommand.context as GameObject;
|
||||
if (parent != null)
|
||||
{
|
||||
GameObjectUtility.SetParentAndAlign(widgetObj, parent);
|
||||
|
||||
ConfigureRectTransformToFill(widgetObj.GetComponent<RectTransform>());
|
||||
|
||||
}
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(widgetObj, "Create Procedural Rive Widget");
|
||||
Selection.activeObject = widgetObj;
|
||||
}
|
||||
|
||||
|
||||
|
||||
[MenuItem("GameObject/Rive/Render Target Strategies/Atlas Render Target Strategy", false, 21)]
|
||||
static void CreateAtlasRenderTargetStrategy(MenuCommand menuCommand) =>
|
||||
CreateRenderTargetStrategy<AtlasRenderTargetStrategy>(menuCommand);
|
||||
|
||||
[MenuItem("GameObject/Rive/Render Target Strategies/Pooled Render Target Strategy", false, 22)]
|
||||
static void CreatePooledRenderTargetStrategy(MenuCommand menuCommand) =>
|
||||
CreateRenderTargetStrategy<PooledRenderTargetStrategy>(menuCommand);
|
||||
|
||||
private static void CreateRenderTargetStrategy<T>(MenuCommand menuCommand) where T : RenderTargetStrategy
|
||||
{
|
||||
string typeName = typeof(T).Name;
|
||||
string objectName = ObjectNames.NicifyVariableName(typeName);
|
||||
|
||||
GameObject obj = new GameObject(objectName, typeof(T));
|
||||
GameObjectUtility.SetParentAndAlign(obj, menuCommand.context as GameObject);
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(obj, $"Create {objectName}");
|
||||
Selection.activeObject = obj;
|
||||
}
|
||||
|
||||
internal static RivePanel CreateRivePanelInternal(MenuCommand menuCommand, PanelContext context)
|
||||
{
|
||||
GameObject rootObject;
|
||||
GameObject panelObj;
|
||||
|
||||
if (context == PanelContext.Canvas)
|
||||
{
|
||||
// Check if we already have a canvas parent
|
||||
Canvas parentCanvas = null;
|
||||
GameObject contextObj = menuCommand.context as GameObject;
|
||||
if (contextObj != null)
|
||||
{
|
||||
parentCanvas = contextObj.GetComponentInParent<Canvas>();
|
||||
}
|
||||
|
||||
if (parentCanvas != null)
|
||||
{
|
||||
// Use existing canvas as root
|
||||
rootObject = parentCanvas.gameObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create new canvas
|
||||
rootObject = new GameObject("Canvas", typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
|
||||
GameObjectUtility.SetParentAndAlign(rootObject, contextObj);
|
||||
rootObject.GetComponent<Canvas>().renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
}
|
||||
|
||||
panelObj = new GameObject("Rive Panel", typeof(RivePanel), typeof(RiveCanvasRenderer));
|
||||
panelObj.transform.SetParent(rootObject.transform, false);
|
||||
|
||||
var renderer = panelObj.GetComponent<RiveCanvasRenderer>();
|
||||
renderer.RivePanel = panelObj.GetComponent<RivePanel>();
|
||||
|
||||
// Canvas panel fills parent
|
||||
ConfigureRectTransformToFill(panelObj.GetComponent<RectTransform>());
|
||||
}
|
||||
else
|
||||
{
|
||||
panelObj = new GameObject("Rive Panel", typeof(RectTransform), typeof(RivePanel));
|
||||
GameObjectUtility.SetParentAndAlign(panelObj, menuCommand.context as GameObject);
|
||||
rootObject = panelObj;
|
||||
|
||||
// Standalone panel uses absolute size
|
||||
panelObj.GetComponent<RivePanel>().SetDimensions(new Vector2(1920, 1080));
|
||||
}
|
||||
|
||||
// Temporarily disable so that re-enabling the gameobjects triggers a refresh when everything is set up. Otherwise, the RivePanel might not be initialized correctly for editor preview.
|
||||
panelObj.SetActive(false);
|
||||
|
||||
// Create and configure widget. We also want it to fill the parent.
|
||||
GameObject widgetObj = new GameObject("Rive Widget", typeof(RectTransform), typeof(RiveWidget));
|
||||
widgetObj.transform.SetParent(panelObj.transform, false);
|
||||
ConfigureRectTransformToFill(widgetObj.GetComponent<RectTransform>());
|
||||
|
||||
// Re-enable panel after everything is set up so that the editor preview has enough information to render the panel
|
||||
panelObj.SetActive(true);
|
||||
|
||||
// Register undo and select the panel so that the user can start editing it right away
|
||||
Undo.RegisterCreatedObjectUndo(rootObject, $"Create Rive Panel{(context == PanelContext.Canvas ? " with Canvas" : "")}");
|
||||
Selection.activeObject = widgetObj;
|
||||
|
||||
return panelObj.GetComponent<RivePanel>();
|
||||
}
|
||||
|
||||
internal static void ConfigureRectTransformToFill(RectTransform rect)
|
||||
{
|
||||
rect.anchorMin = Vector2.zero;
|
||||
rect.anchorMax = Vector2.one;
|
||||
rect.sizeDelta = Vector2.zero;
|
||||
}
|
||||
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
static void OnLoad()
|
||||
{
|
||||
#if UNITY_6000_3_OR_NEWER
|
||||
DragAndDrop.AddDropHandlerV2(OnSceneDrop);
|
||||
DragAndDrop.AddDropHandlerV2(OnHierarchyDropV2);
|
||||
#else
|
||||
DragAndDrop.AddDropHandler(OnSceneDrop);
|
||||
DragAndDrop.AddDropHandler(OnHierarchyDrop);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static bool ValidateRiveAssetDrag()
|
||||
{
|
||||
if (DragAndDrop.objectReferences.Length != 1)
|
||||
return false;
|
||||
|
||||
return DragAndDrop.objectReferences[0] is Asset;
|
||||
}
|
||||
|
||||
private static DragAndDropVisualMode OnSceneDrop(Object dropUpon, Vector3 worldPosition, Vector2 viewportPosition, Transform parentForDraggedObjects, bool perform)
|
||||
{
|
||||
if (!ValidateRiveAssetDrag())
|
||||
{
|
||||
return DragAndDropVisualMode.None;
|
||||
}
|
||||
|
||||
if (perform)
|
||||
{
|
||||
Asset riveAsset = DragAndDrop.objectReferences[0] as Asset;
|
||||
|
||||
if (riveAsset == null)
|
||||
return DragAndDropVisualMode.Rejected;
|
||||
|
||||
GameObject parentObject = dropUpon as GameObject;
|
||||
Transform parentTransform = parentObject != null ? parentObject.transform : null;
|
||||
HandleAssetDrop(riveAsset, parentTransform);
|
||||
}
|
||||
return DragAndDropVisualMode.Move;
|
||||
}
|
||||
|
||||
private static UnityEngine.EventSystems.EventSystem GetExistingEventSystem()
|
||||
{
|
||||
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
return Object.FindFirstObjectByType<UnityEngine.EventSystems.EventSystem>();
|
||||
#else
|
||||
return Object.FindObjectOfType<UnityEngine.EventSystems.EventSystem>();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
private static void EnsureEventSystemExists()
|
||||
{
|
||||
|
||||
if (GetExistingEventSystem() == null)
|
||||
{
|
||||
GameObject eventSystem = new GameObject("EventSystem",
|
||||
typeof(UnityEngine.EventSystems.EventSystem),
|
||||
typeof(UnityEngine.EventSystems.StandaloneInputModule));
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(eventSystem, "Create EventSystem");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles dropping a Rive asset onto a game object in the heirarchy/scene.
|
||||
/// </summary>
|
||||
/// <param name="riveAsset"> The Rive asset to drop. </param>
|
||||
/// <param name="parent"> The parent transform to drop the asset under. </param>
|
||||
internal static void HandleAssetDrop(Asset riveAsset, Transform parent)
|
||||
{
|
||||
// If the RivePanel is just dropped into the scene, we want to create a standalone panel that displays within a canvas.
|
||||
// However, when the panel is dropped onto a game object with a MeshRenderer, we want to create a standalone RivePanel, then add a RiveTextureRenderer to the meshrenderer game object.
|
||||
|
||||
// Create a group for all undo operations so we can collapse them into a single undo step at the end
|
||||
Undo.IncrementCurrentGroup();
|
||||
var undoGroupIndex = Undo.GetCurrentGroup();
|
||||
|
||||
// Check if we're dropping onto or under an existing RivePanel
|
||||
RivePanel existingPanel = null;
|
||||
if (parent != null)
|
||||
{
|
||||
existingPanel = parent.GetComponent<RivePanel>();
|
||||
|
||||
|
||||
if (existingPanel == null)
|
||||
{
|
||||
|
||||
existingPanel = parent.GetComponentInParent<RivePanel>();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// If we're under an existing panel, we want to create a widget under it, instead of creating a new panel.
|
||||
if (existingPanel != null)
|
||||
{
|
||||
|
||||
RiveWidget parentWidget = parent.GetComponentInParent<RiveWidget>();
|
||||
|
||||
// If the parent is a widget, we want to create the new widget as a sibling to the parent widget.
|
||||
// Nesting widgets works, but we don't want to encourage it as it might lead to unexpected behavior.
|
||||
Transform parentTransform = parentWidget != null ? parentWidget.transform.parent : parent;
|
||||
|
||||
GameObject widgetObj = new GameObject("Rive Widget", typeof(RiveWidget));
|
||||
GameObjectUtility.SetParentAndAlign(widgetObj, parentTransform.gameObject);
|
||||
ConfigureRectTransformToFill(widgetObj.GetComponent<RectTransform>());
|
||||
|
||||
var riveWidget = widgetObj.GetComponent<RiveWidget>();
|
||||
Undo.RecordObject(riveWidget, "Set Rive Asset Reference");
|
||||
riveWidget.SetEditorAssetReference(riveAsset);
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(widgetObj, "Create Rive Widget");
|
||||
Selection.activeObject = widgetObj;
|
||||
|
||||
Undo.CollapseUndoOperations(undoGroupIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
PanelContext context = PanelContext.Canvas;
|
||||
GameObject parentGameObject = parent != null ? parent.gameObject : null;
|
||||
GameObject meshRendererGameObject = parentGameObject;
|
||||
|
||||
if (parent != null)
|
||||
{
|
||||
if (parentGameObject.GetComponent<MeshRenderer>() != null)
|
||||
{
|
||||
context = PanelContext.Standalone;
|
||||
|
||||
// We also clear the parent object so that the panel is created as a standalone object in the scene
|
||||
// This might change in the future, but we do this to avoid a bunch of issues that might come from the parent potentially having non-uniform scale, which would affect the RivePanel's rendering.
|
||||
// We also want to avoid unnecessarily re-drawing the panel when the parent object transform is updated.
|
||||
parentGameObject = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
RivePanel panel = CreateRivePanelInternal(new MenuCommand(parentGameObject), context);
|
||||
Undo.RegisterFullObjectHierarchyUndo(panel.gameObject, "Create Rive Panel");
|
||||
|
||||
var widget = panel.GetComponentInChildren<RiveWidget>();
|
||||
if (widget != null)
|
||||
{
|
||||
Undo.RecordObject(widget, "Set Rive Asset Reference");
|
||||
widget.SetEditorAssetReference(riveAsset);
|
||||
}
|
||||
|
||||
if (context == PanelContext.Standalone && meshRendererGameObject != null)
|
||||
{
|
||||
if (meshRendererGameObject != null)
|
||||
{
|
||||
// Add a RiveTextureRenderer to the meshRendererGameObject object so that the RivePanel is rendered there.
|
||||
RiveTextureRenderer textureRenderer;
|
||||
if (!meshRendererGameObject.TryGetComponent<RiveTextureRenderer>(out textureRenderer))
|
||||
{
|
||||
textureRenderer = Undo.AddComponent<RiveTextureRenderer>(meshRendererGameObject);
|
||||
}
|
||||
|
||||
Undo.RecordObject(textureRenderer, "Set Rive Panel Reference");
|
||||
textureRenderer.RivePanel = panel;
|
||||
}
|
||||
|
||||
// Set the Panel dimensions to match the default Artboard's size so that the RivePanel is rendered with the correct aspect ratio.
|
||||
if (riveAsset.EditorOnlyMetadata.Artboards.Count > 0)
|
||||
{
|
||||
|
||||
FileMetadata.ArtboardMetadata defaultArtboard = riveAsset.EditorOnlyMetadata.Artboards[0];
|
||||
|
||||
panel.SetDimensions(new Vector2(defaultArtboard.Width, defaultArtboard.Height));
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure we have an EventSystem in the scene so that the RivePanel can receive input events
|
||||
EnsureEventSystemExists();
|
||||
|
||||
// Collapse all operations into a single undo step
|
||||
Undo.CollapseUndoOperations(undoGroupIndex);
|
||||
}
|
||||
|
||||
private static DragAndDropVisualMode HandleHierarchyDrop(GameObject parentObject, bool perform)
|
||||
{
|
||||
if (!ValidateRiveAssetDrag())
|
||||
{
|
||||
// If we don't do this, it breaks regular drag and drop in the hierarchy
|
||||
// e.g. dragging a game object into another game object stops working
|
||||
return DragAndDropVisualMode.None;
|
||||
}
|
||||
|
||||
if (perform)
|
||||
{
|
||||
Asset riveAsset = DragAndDrop.objectReferences[0] as Asset;
|
||||
|
||||
if (riveAsset == null)
|
||||
return DragAndDropVisualMode.Rejected;
|
||||
|
||||
Transform parentTransform = parentObject != null ? parentObject.transform : null;
|
||||
HandleAssetDrop(riveAsset, parentTransform);
|
||||
}
|
||||
|
||||
return DragAndDropVisualMode.Move;
|
||||
}
|
||||
|
||||
#if UNITY_6000_3_OR_NEWER
|
||||
private static DragAndDropVisualMode OnHierarchyDropV2(EntityId dropTargetEntityId, HierarchyDropFlags dropMode, Transform parentForDraggedObjects, bool perform)
|
||||
{
|
||||
GameObject parentObject = EditorUtility.EntityIdToObject(dropTargetEntityId) as GameObject;
|
||||
return HandleHierarchyDrop(parentObject, perform);
|
||||
}
|
||||
#else
|
||||
private static DragAndDropVisualMode OnHierarchyDrop(int dropTargetInstanceID, HierarchyDropFlags dropMode, Transform parentForDraggedObjects, bool perform)
|
||||
{
|
||||
GameObject parentObject = EditorUtility.InstanceIDToObject(dropTargetInstanceID) as GameObject;
|
||||
return HandleHierarchyDrop(parentObject, perform);
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3644f9a909df34335a9332c966ac74d1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/MenuItems.cs
|
||||
uploadId: 896810
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Rive.Components;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Rive.EditorTools
|
||||
{
|
||||
[CustomEditor(typeof(PanelContextPreviewManager), true)]
|
||||
internal class PanelContextPreviewManagerEditor : RiveBaseEditor
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a28b8f2410fbe4723957384e2cc23554
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/PanelContextPreviewManagerEditor.cs
|
||||
uploadId: 896810
|
||||
@@ -0,0 +1,14 @@
|
||||
using Rive.Components;
|
||||
using UnityEditor;
|
||||
|
||||
|
||||
namespace Rive.EditorTools
|
||||
{
|
||||
[CustomEditor(typeof(PanelRenderer), true)]
|
||||
internal class PanelRendererInspector : RiveBaseEditor
|
||||
{
|
||||
|
||||
protected PanelRenderer PanelRenderer => target as PanelRenderer;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 858abbdc5eb964808a982516a48d13d3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/PanelRendererInspector.cs
|
||||
uploadId: 896810
|
||||
@@ -0,0 +1,11 @@
|
||||
using Rive.Components;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Rive.EditorTools
|
||||
{
|
||||
[CustomEditor(typeof(PooledRenderTargetStrategy), true)]
|
||||
internal class PooledRenderTextureStrategyInspector : RiveBaseEditor
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 46a5871697e4343d8a234cc693c64687
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- m_styleSheet: {fileID: 7433441132597879392, guid: cc66ac87f8ecb4e3c91384370163ee7b,
|
||||
type: 3}
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/PooledRenderTextureStrategyInspector.cs
|
||||
uploadId: 896810
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "Rive.Editor.Components",
|
||||
"rootNamespace": "Rive.EditorTools.Components",
|
||||
"references": [
|
||||
"GUID:e11e939ddee8146e1976384f79284b41",
|
||||
"GUID:4d624505c28284c90a482e4d6ec34ada",
|
||||
"GUID:0a82aeb665886483c867b7d137563619"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [
|
||||
"RIVE_USING_UGUI"
|
||||
],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.ugui",
|
||||
"expression": "1.0.0",
|
||||
"define": "RIVE_USING_UGUI"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a6f82b4f2b19414aa4548e19d8ad1b8
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/Rive.Editor.Components.asmdef
|
||||
uploadId: 896810
|
||||
445
Packages/app.rive.rive-unity/Editor/Components/RiveBaseEditor.cs
Normal file
445
Packages/app.rive.rive-unity/Editor/Components/RiveBaseEditor.cs
Normal file
@@ -0,0 +1,445 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Rive.Utils;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace Rive.EditorTools
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Base class for custom inspectors for Rive components.
|
||||
/// </summary>
|
||||
internal class RiveBaseEditor : Editor
|
||||
{
|
||||
protected VisualElement rootElement;
|
||||
|
||||
private GameObject m_gameObject;
|
||||
|
||||
private Dictionary<string, VisualElement> sections = new Dictionary<string, VisualElement>();
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
if (target is MonoBehaviour)
|
||||
{
|
||||
m_gameObject = (target as MonoBehaviour).gameObject;
|
||||
}
|
||||
|
||||
|
||||
// Using Editor.update, queue a repaint for the next frame to hide components
|
||||
// Hiding immediately causes issues with the inspector layout
|
||||
EditorApplication.update += FirstRepaint;
|
||||
|
||||
}
|
||||
|
||||
private void FirstRepaint()
|
||||
{
|
||||
HandleHideComponents();
|
||||
|
||||
EditorApplication.update -= FirstRepaint;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void HandleHideComponents()
|
||||
{
|
||||
var hideComponentsAttrs = target.GetType().GetCustomAttributes<HideComponentsAttribute>();
|
||||
var targetComponent = target as MonoBehaviour;
|
||||
|
||||
if (targetComponent != null)
|
||||
{
|
||||
foreach (var attr in hideComponentsAttrs)
|
||||
{
|
||||
CustomInspectorUtils.HideNonInteractiveComponents(
|
||||
targetComponent,
|
||||
new List<Type>(attr.ComponentTypes),
|
||||
this,
|
||||
attr.HideFlags
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public override VisualElement CreateInspectorGUI()
|
||||
{
|
||||
rootElement = new VisualElement();
|
||||
rootElement.styleSheets.Add(StyleHelper.StyleSheet);
|
||||
rootElement.AddToClassList("rive-inspector");
|
||||
|
||||
var serializedFields = GetSerializedFields();
|
||||
|
||||
// Get all fields with InspectorFieldAttribute
|
||||
var attributeFields = serializedFields
|
||||
.Where(f => f.GetCustomAttribute<InspectorFieldAttribute>() != null)
|
||||
.OrderBy(f => f.GetCustomAttribute<InspectorFieldAttribute>().Order);
|
||||
|
||||
// Split into fields with and without sections
|
||||
var sectionFields = attributeFields.Where(f =>
|
||||
!string.IsNullOrEmpty(f.GetCustomAttribute<InspectorFieldAttribute>().SectionId));
|
||||
var nonSectionFields = attributeFields.Where(f =>
|
||||
string.IsNullOrEmpty(f.GetCustomAttribute<InspectorFieldAttribute>().SectionId));
|
||||
|
||||
// Get fields without any attributes
|
||||
var plainFields = serializedFields
|
||||
.Except(attributeFields);
|
||||
|
||||
// Process non-sectioned fields first (both plain and attributed)
|
||||
foreach (var field in plainFields.Concat(nonSectionFields))
|
||||
{
|
||||
var attr = field.GetCustomAttribute<InspectorFieldAttribute>();
|
||||
CreateFieldElement(field, attr, rootElement);
|
||||
}
|
||||
|
||||
// Get sections that have fields
|
||||
var usedSectionIds = sectionFields
|
||||
.Select(f => f.GetCustomAttribute<InspectorFieldAttribute>().SectionId)
|
||||
.Distinct()
|
||||
.ToHashSet();
|
||||
|
||||
CreateSections(usedSectionIds);
|
||||
|
||||
foreach (var field in sectionFields)
|
||||
{
|
||||
var attr = field.GetCustomAttribute<InspectorFieldAttribute>();
|
||||
var container = sections[attr.SectionId];
|
||||
CreateFieldElement(field, attr, container);
|
||||
}
|
||||
|
||||
|
||||
return rootElement;
|
||||
}
|
||||
|
||||
|
||||
private HashSet<FieldInfo> GetSerializedFields()
|
||||
{
|
||||
var fields = new HashSet<FieldInfo>();
|
||||
var currentType = target.GetType();
|
||||
|
||||
// Walk up the inheritance chain until we hit MonoBehaviour
|
||||
while (currentType != typeof(MonoBehaviour) && currentType != null)
|
||||
{
|
||||
var typeFields = currentType
|
||||
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)
|
||||
.Where(f =>
|
||||
(f.GetCustomAttribute<SerializeField>() != null || f.IsPublic) &&
|
||||
f.GetCustomAttribute<HideInInspector>() == null &&
|
||||
IsUnitySerializable(f.FieldType)); // check for Unity-serializable types
|
||||
|
||||
foreach (var field in typeFields)
|
||||
{
|
||||
fields.Add(field);
|
||||
}
|
||||
|
||||
currentType = currentType.BaseType;
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
// Helper method to check if a type is serializable by Unity. We do this to avoid types like Actions, Funcs, etc not showing up in the inspector but still taking up space.
|
||||
private bool IsUnitySerializable(Type type)
|
||||
{
|
||||
if (type == null) return false;
|
||||
|
||||
if (Attribute.IsDefined(type, typeof(SerializableAttribute)))
|
||||
return true;
|
||||
|
||||
if (type.IsPrimitive || type == typeof(string) || type == typeof(decimal))
|
||||
return true;
|
||||
|
||||
if (typeof(UnityEngine.Object).IsAssignableFrom(type))
|
||||
return true;
|
||||
|
||||
if (type.IsEnum)
|
||||
return true;
|
||||
|
||||
if (type.IsValueType && !type.IsPrimitive)
|
||||
return true;
|
||||
|
||||
if (typeof(UnityEngine.Events.UnityEventBase).IsAssignableFrom(type))
|
||||
return true;
|
||||
|
||||
if (type.IsArray)
|
||||
return IsUnitySerializable(type.GetElementType());
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
|
||||
return IsUnitySerializable(type.GetGenericArguments()[0]);
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void CreateSections(HashSet<string> usedSectionIds)
|
||||
{
|
||||
var sectionAttrs = target.GetType()
|
||||
.GetCustomAttributes<InspectorSectionAttribute>()
|
||||
.OrderBy(s => s.Order);
|
||||
|
||||
foreach (var attr in sectionAttrs)
|
||||
{
|
||||
// Only create sections that have fields
|
||||
if (!usedSectionIds.Contains(attr.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
VisualElement section;
|
||||
switch (attr.Style)
|
||||
{
|
||||
case SectionStyle.Foldout:
|
||||
var foldout = new Foldout { text = attr.DisplayName };
|
||||
foldout.viewDataKey = $"RiveFoldout_{target.GetType().Name}_{attr.Id}";
|
||||
// The initial value will be used only if there's no saved state
|
||||
foldout.value = attr.StartExpanded;
|
||||
section = foldout;
|
||||
break;
|
||||
|
||||
case SectionStyle.Header:
|
||||
default:
|
||||
section = new VisualElement();
|
||||
if (!string.IsNullOrEmpty(attr.DisplayName))
|
||||
{
|
||||
var label = new Label(attr.DisplayName);
|
||||
label.AddToClassList(StyleHelper.CLASS_SECTION_LABEL);
|
||||
section.Add(label);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
section.AddToClassList(StyleHelper.CLASS_SECTION);
|
||||
sections[attr.Id] = section;
|
||||
rootElement.Add(section);
|
||||
}
|
||||
}
|
||||
|
||||
public static VisualElement GetVisualElementForField(FieldInfo field, SerializedProperty property, string label = null)
|
||||
{
|
||||
VisualElement element;
|
||||
|
||||
// We do this to show the alignment dropdown because some versions of Unity seems to have issues with the default PropertyDrawer (e.g Unity 2022.3.10)
|
||||
// In those versions, the default PropertyDrawer doesn't show the dropdown, but rather the X and Y fields.
|
||||
// It's possible that this is a bug in Unity, but this is a workaround for now.
|
||||
if (field.FieldType == typeof(Alignment))
|
||||
{
|
||||
var alignmentDrawer = new AlignmentPropertyDrawer();
|
||||
element = alignmentDrawer.CreatePropertyGUI(property);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var propertyField = new PropertyField
|
||||
{
|
||||
bindingPath = field.Name
|
||||
};
|
||||
|
||||
if (label != null)
|
||||
{
|
||||
propertyField.label = label;
|
||||
}
|
||||
|
||||
element = propertyField;
|
||||
}
|
||||
|
||||
|
||||
|
||||
return element;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void CreateFieldElement(FieldInfo field, InspectorFieldAttribute attr, VisualElement container)
|
||||
{
|
||||
string displayName = attr?.DisplayName ?? ObjectNames.NicifyVariableName(field.Name);
|
||||
var property = serializedObject.FindProperty(field.Name);
|
||||
|
||||
VisualElement element = GetVisualElementForField(field, property, displayName);
|
||||
string uniqueId = $"field-{target.GetInstanceID()}-{target.GetType().Name}-{field.Name}";
|
||||
element.name = uniqueId;
|
||||
|
||||
HandleValueChangedIfNeeded(field, element);
|
||||
|
||||
VisualElement fieldRoot = element;
|
||||
if (attr != null && attr.HasHelpUrl)
|
||||
{
|
||||
var fieldContainer = new VisualElement();
|
||||
fieldContainer.AddToClassList(StyleHelper.CLASS_FIELD_CONTAINER);
|
||||
fieldContainer.style.flexDirection = FlexDirection.Row;
|
||||
fieldContainer.style.alignItems = Align.Center;
|
||||
|
||||
element.AddToClassList(StyleHelper.CLASS_FIELD_CONTENT);
|
||||
fieldContainer.Add(element);
|
||||
fieldContainer.Add(CreateHelpButton(attr.HelpUrl, displayName));
|
||||
fieldRoot = fieldContainer;
|
||||
}
|
||||
|
||||
HandleConditionalVisibilityIfNeeded(field, fieldRoot, property);
|
||||
|
||||
element.Bind(serializedObject);
|
||||
fieldRoot.AddToClassList(StyleHelper.CLASS_FIELD);
|
||||
container.Add(fieldRoot);
|
||||
}
|
||||
|
||||
private void HandleValueChangedIfNeeded(FieldInfo field, VisualElement element)
|
||||
{
|
||||
var onValueChangedAttr = field.GetCustomAttribute<OnValueChangedAttribute>();
|
||||
if (onValueChangedAttr != null)
|
||||
{
|
||||
var methodInfo = target.GetType().GetMethod(onValueChangedAttr.CallbackName,
|
||||
BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
|
||||
if (methodInfo != null)
|
||||
{
|
||||
bool isInitializing = true;
|
||||
element.RegisterCallback<AttachToPanelEvent>(evt =>
|
||||
{
|
||||
element.schedule.Execute(() =>
|
||||
{
|
||||
isInitializing = false;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
if (element is PropertyField propertyField)
|
||||
{
|
||||
propertyField.RegisterValueChangeCallback(evt =>
|
||||
{
|
||||
if (!isInitializing || onValueChangedAttr.InvokeOnInitialization)
|
||||
{
|
||||
methodInfo.Invoke(target, null);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// The alignment dropdown is a PopupField<string>
|
||||
// Get the PopupField<string> from the element. It's possible that the passed in element might be a container so we might need to find the PopupField<string> in the children.
|
||||
var popupField = element.Q<PopupField<string>>();
|
||||
|
||||
if (element != null)
|
||||
{
|
||||
popupField.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
if (!isInitializing || onValueChangedAttr.InvokeOnInitialization)
|
||||
{
|
||||
methodInfo.Invoke(target, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleConditionalVisibilityIfNeeded(FieldInfo field, VisualElement element, SerializedProperty property)
|
||||
{
|
||||
var showIfAttr = field.GetCustomAttribute<ShowIfAttribute>();
|
||||
var hideIfAttr = field.GetCustomAttribute<HideIfAttribute>();
|
||||
|
||||
if (showIfAttr == null && hideIfAttr == null) return;
|
||||
string conditionName = showIfAttr?.ConditionName ?? hideIfAttr?.ConditionName;
|
||||
bool isHideIf = hideIfAttr != null;
|
||||
|
||||
void UpdateVisibility()
|
||||
{
|
||||
if (property.serializedObject == null || property.serializedObject.targetObject == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var target = property.serializedObject.targetObject;
|
||||
if (ReflectionUtils.TryGetBoolValue(target, conditionName, out bool condition))
|
||||
{
|
||||
element.style.display = (condition != isHideIf) ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
|
||||
element.RegisterCallback<AttachToPanelEvent>(evt =>
|
||||
{
|
||||
property.serializedObject.Update();
|
||||
UpdateVisibility();
|
||||
});
|
||||
|
||||
UpdateVisibility();
|
||||
// Update visibility whenever the inspector updates
|
||||
scheduledUpdate = element.schedule.Execute(() =>
|
||||
{
|
||||
if (property.serializedObject == null || property.serializedObject.targetObject == null)
|
||||
{
|
||||
// Stop scheduling future updates
|
||||
scheduledUpdate?.Pause();
|
||||
return;
|
||||
}
|
||||
|
||||
property.serializedObject.Update();
|
||||
UpdateVisibility();
|
||||
}).Every(100);
|
||||
}
|
||||
|
||||
private IVisualElementScheduledItem scheduledUpdate;
|
||||
|
||||
private Button CreateHelpButton(string helpUrl, string displayName)
|
||||
{
|
||||
var button = new Button(() =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(helpUrl))
|
||||
{
|
||||
Application.OpenURL(helpUrl);
|
||||
}
|
||||
});
|
||||
|
||||
button.tooltip = "Open documentation for this field";
|
||||
button.focusable = false;
|
||||
button.AddToClassList(StyleHelper.CLASS_FIELD_HELP_BUTTON);
|
||||
|
||||
var iconContent = EditorGUIUtility.IconContent("_Help");
|
||||
if (iconContent?.image != null)
|
||||
{
|
||||
var icon = new Image
|
||||
{
|
||||
image = iconContent.image,
|
||||
scaleMode = ScaleMode.ScaleToFit
|
||||
};
|
||||
button.Add(icon);
|
||||
}
|
||||
else
|
||||
{
|
||||
button.text = "?";
|
||||
}
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (Application.isPlaying) return;
|
||||
|
||||
|
||||
|
||||
bool componentRemoved = m_gameObject != null && m_gameObject.GetComponent(target.GetType()) == null;
|
||||
|
||||
|
||||
//If the component was removed but not the gameobject, let's destroy the required components it added that are hidden
|
||||
// If they're not hidden, then the user can remove them manually.
|
||||
if (componentRemoved)
|
||||
{
|
||||
var hideComponentsAttrs = target.GetType().GetCustomAttributes<HideComponentsAttribute>();
|
||||
foreach (var attr in hideComponentsAttrs)
|
||||
{
|
||||
CustomInspectorUtils.DestroyRequiredHiddenComponents(
|
||||
m_gameObject,
|
||||
target.GetType(),
|
||||
component => (component.hideFlags & attr.HideFlags) != 0
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7b60061c641d4f969e8522e1c71afe7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/RiveBaseEditor.cs
|
||||
uploadId: 896810
|
||||
@@ -0,0 +1,12 @@
|
||||
using Rive.Components;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Rive.EditorTools
|
||||
{
|
||||
[CustomEditor(typeof(RivePanel), true)]
|
||||
internal class RivePanelInspector : RiveBaseEditor
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a405a6d22e0d94955a82a4b7ba363a47
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/RivePanelInspector.cs
|
||||
uploadId: 896810
|
||||
@@ -0,0 +1,42 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UnityEditor.UIElements;
|
||||
using Rive.Components;
|
||||
|
||||
namespace Rive.EditorTools
|
||||
{
|
||||
[CustomEditor(typeof(CanvasRendererRawImage))]
|
||||
internal class RiveRawImageEditor : Editor
|
||||
{
|
||||
public override VisualElement CreateInspectorGUI()
|
||||
{
|
||||
var root = new VisualElement();
|
||||
|
||||
// We want to show the texture field in the inspector when in play mode, but we want it to be read-only.
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
var textureField = new ObjectField("Texture")
|
||||
{
|
||||
objectType = typeof(Texture),
|
||||
value = (target as CanvasRendererRawImage)?.texture,
|
||||
};
|
||||
|
||||
textureField.SetEnabled(false);
|
||||
|
||||
root.Add(textureField);
|
||||
|
||||
// Update the texture field when the selection changes
|
||||
EditorApplication.update += () =>
|
||||
{
|
||||
if (target != null && textureField != null)
|
||||
{
|
||||
textureField.value = (target as CanvasRendererRawImage)?.texture;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4da44f8147bcc4b92b05f99b9c4148f2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/RiveRawImageEditor.cs
|
||||
uploadId: 896810
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Rive.Components;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace Rive.EditorTools
|
||||
{
|
||||
[CustomEditor(typeof(WidgetBehaviour), true)]
|
||||
internal class RiveWidgetInspector : RiveBaseEditor
|
||||
{
|
||||
|
||||
public override VisualElement CreateInspectorGUI()
|
||||
{
|
||||
var root = base.CreateInspectorGUI();
|
||||
|
||||
if (target is RiveWidget widget)
|
||||
{
|
||||
var playgroundRow = new VisualElement();
|
||||
playgroundRow.style.marginTop = 6;
|
||||
|
||||
var playgroundButton = new Button(() =>
|
||||
{
|
||||
DataBindingPlaygroundWindow.Open(widget);
|
||||
})
|
||||
{
|
||||
text = "Open Playground",
|
||||
tooltip = "Open a data binding playground for this widget"
|
||||
};
|
||||
|
||||
playgroundRow.Add(playgroundButton);
|
||||
root.Add(playgroundRow);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e222c1dcc24154d079cbc89601fdd730
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/RiveWidgetInspector.cs
|
||||
uploadId: 896810
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab89f3c9590794a79b784619bcdc32ae
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,62 @@
|
||||
.rive-inspector {
|
||||
padding-top: 9px;
|
||||
}
|
||||
|
||||
.rive-inspector__section {
|
||||
margin-bottom: 9px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.rive-inspector__foldout {
|
||||
}
|
||||
|
||||
.rive-inspector__field {
|
||||
margin-bottom: 3px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
.rive-inspector__field > Label {
|
||||
min-width: 117px;
|
||||
}
|
||||
|
||||
.rive-inspector__field-container {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.rive-inspector__field-content {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.rive-inspector__field-help-button {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 0;
|
||||
margin-left: 4px;
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.rive-inspector__field-help-button > Image {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.rive-inspector__field-help-button:hover {
|
||||
background-color: rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.rive-inspector__section-label {
|
||||
-unity-font-style: bold;
|
||||
margin-bottom: 4px;
|
||||
margin-left: 5.2px;
|
||||
|
||||
}
|
||||
|
||||
.unity-foldout__input > Label {
|
||||
-unity-font-style: bold;
|
||||
margin-left: 1px;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc66ac87f8ecb4e3c91384370163ee7b
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/Styles/RiveInspectorStyleSheet.uss
|
||||
uploadId: 896810
|
||||
@@ -0,0 +1,63 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace Rive
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class for styling Rive components.
|
||||
/// </summary>
|
||||
internal class StyleHelper
|
||||
{
|
||||
// USS Class Names
|
||||
/// <summary>
|
||||
/// The block class name for the Rive inspector.
|
||||
/// </summary>
|
||||
public const string CLASS_BLOCK = "rive-inspector";
|
||||
|
||||
/// <summary>
|
||||
/// The element class name for sections within the Rive inspector.
|
||||
/// </summary>
|
||||
public const string CLASS_SECTION = "rive-inspector__section";
|
||||
|
||||
/// <summary>
|
||||
/// The element class name for section labels within the Rive inspector.
|
||||
/// </summary>
|
||||
public const string CLASS_SECTION_LABEL = "rive-inspector__section-label";
|
||||
|
||||
/// <summary>
|
||||
/// The element class name for fields within the Rive inspector.
|
||||
/// </summary>
|
||||
public const string CLASS_FIELD = "rive-inspector__field";
|
||||
|
||||
/// <summary>
|
||||
/// Container that wraps a field and its optional help button.
|
||||
/// </summary>
|
||||
public const string CLASS_FIELD_CONTAINER = "rive-inspector__field-container";
|
||||
|
||||
/// <summary>
|
||||
/// Class name applied to the primary field element inside a container.
|
||||
/// </summary>
|
||||
public const string CLASS_FIELD_CONTENT = "rive-inspector__field-content";
|
||||
|
||||
/// <summary>
|
||||
/// Class name applied to the help/info buttons.
|
||||
/// </summary>
|
||||
public const string CLASS_FIELD_HELP_BUTTON = "rive-inspector__field-help-button";
|
||||
|
||||
|
||||
private static StyleSheet s_StyleSheet;
|
||||
|
||||
public static StyleSheet StyleSheet
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_StyleSheet == null)
|
||||
{
|
||||
string ussPath = "Packages/app.rive.rive-unity/Editor/Components/Styles/RiveInspectorStyleSheet.uss";
|
||||
s_StyleSheet = AssetDatabase.LoadAssetAtPath<StyleSheet>(ussPath);
|
||||
}
|
||||
return s_StyleSheet;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8a09ca9bb7cb94661be11fbfb6e25ba0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/Styles/StyleHelper.cs
|
||||
uploadId: 896810
|
||||
@@ -0,0 +1,41 @@
|
||||
using Rive.Components;
|
||||
using UnityEditor;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
|
||||
namespace Rive.EditorTools
|
||||
{
|
||||
|
||||
[CustomEditor(typeof(RiveTextureRenderer), true)]
|
||||
internal class TexturePanelRendererEditor : PanelRendererInspector
|
||||
{
|
||||
public override VisualElement CreateInspectorGUI()
|
||||
{
|
||||
var root = base.CreateInspectorGUI() ?? new VisualElement();
|
||||
|
||||
// For worldspace renderers, we display a button to convert materials on the current mesh renderer, if needed.
|
||||
// Makes it easier for users to switch to Rive materials without having to know the right ones to pick.
|
||||
var textureRenderer = (RiveTextureRenderer)target;
|
||||
if (textureRenderer != null && textureRenderer.Renderer != null)
|
||||
{
|
||||
System.Action clickAction = () =>
|
||||
{
|
||||
// This will replace any non-Rive materials with Rive equivalents, even if the existing materials are not Unity defaults.
|
||||
MaterialConversionUtility.ReplaceMaterialsWithRive(textureRenderer.Renderer);
|
||||
};
|
||||
var convertButton = new Button(() => clickAction())
|
||||
{
|
||||
text = "Replace Materials with Rive Materials"
|
||||
};
|
||||
convertButton.name = "RiveConvertMaterialsButton";
|
||||
convertButton.userData = clickAction; // allow tests to invoke without event system/panel
|
||||
convertButton.style.marginTop = 6;
|
||||
root.Add(convertButton);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b792f3d5adc79476e97be1d9ac3fbad0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences:
|
||||
- m_styleSheet: {fileID: 7433441132597879392, guid: cc66ac87f8ecb4e3c91384370163ee7b,
|
||||
type: 3}
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/TexturePanelRendererEditor.cs
|
||||
uploadId: 896810
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7ba5b52221ad74445868d7b7032fa0a3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Rive.EditorTools
|
||||
{
|
||||
internal class CustomInspectorUtils
|
||||
{
|
||||
|
||||
public static void HideNonInteractiveComponents(MonoBehaviour target, List<Type> componentTypes, Editor editor, HideFlags hideFlags = HideFlags.HideInInspector | HideFlags.HideAndDontSave)
|
||||
{
|
||||
if (target == null || componentTypes == null) return;
|
||||
|
||||
foreach (var type in componentTypes)
|
||||
{
|
||||
var component = target.GetComponent(type);
|
||||
if (component != null)
|
||||
{
|
||||
component.hideFlags = hideFlags;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void DestroyRequiredHiddenComponents(GameObject gameObject, Type componentType, Func<UnityEngine.Component, bool> ComponenentFilter = null)
|
||||
{
|
||||
RequireComponent[] requiredComponentsAtts = Attribute.GetCustomAttributes(componentType, typeof(RequireComponent), true) as RequireComponent[];
|
||||
|
||||
foreach (RequireComponent rc in requiredComponentsAtts)
|
||||
{
|
||||
if (rc != null)
|
||||
{
|
||||
Type[] typesToRemove = new Type[] { rc.m_Type0, rc.m_Type1, rc.m_Type2 };
|
||||
foreach (Type type in typesToRemove)
|
||||
{
|
||||
if (type != null)
|
||||
{
|
||||
UnityEngine.Component componentToDestroy = gameObject.GetComponent(type);
|
||||
ComponenentFilter = ComponenentFilter ?? ShouldDestroyComponent;
|
||||
if (componentToDestroy != null && ShouldDestroyComponent(componentToDestroy))
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(componentToDestroy);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ShouldDestroyComponent(UnityEngine.Component component)
|
||||
{
|
||||
// Check if the component has HideFlags that indicate it should be automatically managed
|
||||
return (component.hideFlags & (HideFlags.HideInInspector | HideFlags.HideAndDontSave)) != 0;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc3bdf49da3e34a5089e9857b3d1719e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/Utils/CustomInspectorUtils.cs
|
||||
uploadId: 896810
|
||||
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Rive.EditorTools;
|
||||
using UnityEditor;
|
||||
|
||||
namespace Rive.Utils
|
||||
{
|
||||
internal static class ReflectionUtils
|
||||
{
|
||||
private const BindingFlags DefaultBindingFlags =
|
||||
BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
|
||||
|
||||
public static bool TryGetBoolValue(object target, string memberName, out bool value)
|
||||
{
|
||||
value = false;
|
||||
if (target == null || string.IsNullOrEmpty(memberName)) return false;
|
||||
|
||||
try
|
||||
{
|
||||
var type = target.GetType();
|
||||
|
||||
var field = type.GetField(memberName, DefaultBindingFlags);
|
||||
if (field != null)
|
||||
{
|
||||
value = (bool)field.GetValue(target);
|
||||
return true;
|
||||
}
|
||||
|
||||
var prop = type.GetProperty(memberName, DefaultBindingFlags);
|
||||
if (prop != null)
|
||||
{
|
||||
value = (bool)prop.GetValue(target);
|
||||
return true;
|
||||
}
|
||||
|
||||
var method = type.GetMethod(memberName, DefaultBindingFlags);
|
||||
if (method != null)
|
||||
{
|
||||
value = (bool)method.Invoke(target, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugLogger.Instance.LogError($"Error getting bool value for member '{memberName}': {e.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool TryGetValue<T>(object target, string memberName, out T value)
|
||||
{
|
||||
value = default;
|
||||
if (target == null || string.IsNullOrEmpty(memberName)) return false;
|
||||
|
||||
try
|
||||
{
|
||||
var type = target.GetType();
|
||||
|
||||
var field = type.GetField(memberName, DefaultBindingFlags);
|
||||
if (field != null)
|
||||
{
|
||||
value = (T)field.GetValue(target);
|
||||
return true;
|
||||
}
|
||||
|
||||
var prop = type.GetProperty(memberName, DefaultBindingFlags);
|
||||
if (prop != null)
|
||||
{
|
||||
value = (T)prop.GetValue(target);
|
||||
return true;
|
||||
}
|
||||
|
||||
var method = type.GetMethod(memberName, DefaultBindingFlags);
|
||||
if (method != null)
|
||||
{
|
||||
value = (T)method.Invoke(target, null);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugLogger.Instance.LogError($"Error getting value of type {typeof(T)} for member '{memberName}': {e.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the display name for a serialized property. This accounts for a custom label being set via an InspectorFieldAttribute.
|
||||
/// </summary>
|
||||
/// <param name="property"> The property to get the label for. </param>
|
||||
/// <returns> The display name for the property. </returns>
|
||||
public static string GetPropertyLabel(SerializedProperty property)
|
||||
{
|
||||
if (property == null) return string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
var target = property.serializedObject.targetObject;
|
||||
var fieldInfo = target.GetType().GetField(property.name, DefaultBindingFlags);
|
||||
var inspectorAttr = fieldInfo?.GetCustomAttribute<InspectorFieldAttribute>();
|
||||
|
||||
return inspectorAttr?.DisplayName ?? ObjectNames.NicifyVariableName(property.name);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugLogger.Instance.LogError($"Error getting label for property '{property.name}': {e.Message}");
|
||||
return ObjectNames.NicifyVariableName(property.name);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1a5fa6b3a3b640c5b4d63a810c8fee3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 350858
|
||||
packageName: Rive
|
||||
packageVersion: 0.4.2
|
||||
assetPath: Packages/app.rive.rive-unity/Editor/Components/Utils/ReflectionUtils.cs
|
||||
uploadId: 896810
|
||||
Reference in New Issue
Block a user