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