This commit is contained in:
2026-05-19 17:39:03 +07:00
parent bf0ebe447d
commit 5da832bb57
559 changed files with 69543 additions and 1 deletions

View File

@@ -0,0 +1,10 @@
using Rive.Components;
using UnityEditor;
namespace Rive.EditorTools
{
[CustomEditor(typeof(AtlasRenderTargetStrategy), true)]
internal class AtlasRenderTextureStrategyInspector : RiveBaseEditor
{
}
}

View File

@@ -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

View File

@@ -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;
}
}
}
}
}

View File

@@ -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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 43b94ffc302e04258ab481523df9abaa
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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);
}
}
}
}
}
}
}
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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"));
}
}
}

View File

@@ -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

View File

@@ -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

View 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
}
}

View File

@@ -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

View File

@@ -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
{
}
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -0,0 +1,11 @@
using Rive.Components;
using UnityEditor;
namespace Rive.EditorTools
{
[CustomEditor(typeof(PooledRenderTargetStrategy), true)]
internal class PooledRenderTextureStrategyInspector : RiveBaseEditor
{
}
}

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View 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
}

View File

@@ -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

View File

@@ -0,0 +1,12 @@
using Rive.Components;
using UnityEditor;
namespace Rive.EditorTools
{
[CustomEditor(typeof(RivePanel), true)]
internal class RivePanelInspector : RiveBaseEditor
{
}
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ab89f3c9590794a79b784619bcdc32ae
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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;
}
}
}
}

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7ba5b52221ad74445868d7b7032fa0a3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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;
}
}
}

View File

@@ -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

View File

@@ -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);
}
}
}
}

View File

@@ -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