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,632 @@
using UnityEngine;
using UnityEngine.Rendering;
using UnityEditor;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
using System.Linq;
using Rive.EditorTools;
using System;
namespace Rive
{
[CustomEditor(typeof(Asset))]
public class AssetEditor : Editor
{
File m_file;
private Artboard m_artboard;
private StateMachine m_stateMachine;
private double m_lastTime = 0.0;
public override bool HasPreviewGUI() => true;
public override bool RequiresConstantRepaint()
{
return true;
}
private enum AssetReferenceType
{
Embedded = 0,
Referenced = 1
}
public override VisualElement CreateInspectorGUI()
{
var root = new VisualElement();
var riveAsset = (Asset)target;
// File Assets Section
var embeddedFoldout = new Foldout { text = "File Assets", value = false };
root.Add(embeddedFoldout);
foreach (var embeddedAsset in riveAsset.EmbeddedAssets)
{
var assetContainer = new VisualElement();
assetContainer.style.paddingBottom = 30;
embeddedFoldout.Add(assetContainer);
// Asset Type
var enumField = new EnumField("Type:", embeddedAsset.AssetType);
enumField.SetEnabled(false);
assetContainer.Add(enumField);
// Asset Name
var nameField = new TextField("Name:") { value = embeddedAsset.Name };
// For text fields, make them readonly instead of using SetEnabled(false) to allow for copying the text
StyleAsReadonly(nameField);
nameField.isReadOnly = true;
assetContainer.Add(nameField);
// Asset ID
var idField = new TextField("ID:") { value = embeddedAsset.Id.ToString() };
StyleAsReadonly(idField);
idField.isReadOnly = true;
assetContainer.Add(idField);
// Asset Reference Type
var referenceType = embeddedAsset.InBandBytesSize > 0 ? AssetReferenceType.Embedded : AssetReferenceType.Referenced;
var referenceTypeField = new EnumField("Reference Type:", referenceType);
referenceTypeField.SetEnabled(false);
assetContainer.Add(referenceTypeField);
// Asset Data
if (referenceType == AssetReferenceType.Embedded)
{
var embeddedField = new TextField("Embedded Size:")
{
value = FormatBytes(embeddedAsset.InBandBytesSize),
tooltip = "The size of the asset data embedded in the Rive file."
};
StyleAsReadonly(embeddedField);
embeddedField.isReadOnly = true;
assetContainer.Add(embeddedField);
}
else
{
var assetField = new ObjectField("Referenced Asset")
{
objectType = GetAssetType(embeddedAsset.AssetType),
value = embeddedAsset.OutOfBandAsset,
};
// Allow referenced assets to be updated in the editor
assetField.RegisterValueChangedCallback(evt =>
{
var newValue = evt.newValue as OutOfBandAsset;
Asset asset = target as Asset;
if (asset == null)
{
return;
}
Undo.RecordObject(this, "Updated Referenced Asset");
AssetImporter.SetOobAssetReference((Asset)target, embeddedAsset.Id, newValue);
});
assetContainer.Add(assetField);
}
}
// Artboard Metadata
if (riveAsset.EditorOnlyMetadata != null && riveAsset.EditorOnlyMetadata.Artboards.Count > 0)
{
var contentsFoldout = new Foldout { text = "Artboard Metadata", value = false };
root.Add(contentsFoldout);
for (int i = 0; i < riveAsset.EditorOnlyMetadata.Artboards.Count; i++)
{
bool isDefaultArtboard = i == 0;
var artboard = riveAsset.EditorOnlyMetadata.Artboards[i];
// Create a foldout for each artboard
string artboardLabel = artboard.Name + (isDefaultArtboard ? " (Default)" : "");
var artboardFoldout = new Foldout { text = artboardLabel, value = false };
artboardFoldout.style.paddingLeft = 8;
artboardFoldout.style.paddingRight = 8;
contentsFoldout.Add(artboardFoldout);
var artboardContainer = new VisualElement();
artboardFoldout.Add(artboardContainer);
AddCopyToClipboardMenu(artboardFoldout, artboard.Name, "Copy Artboard Name");
// Artboard Size
var sizeContainer = new VisualElement();
sizeContainer.style.flexDirection = FlexDirection.Row;
sizeContainer.style.marginLeft = 15;
sizeContainer.style.marginTop = 5;
artboardContainer.Add(sizeContainer);
var sizeLabel = new Label("Size:");
sizeLabel.style.marginRight = 8;
sizeContainer.Add(sizeLabel);
var sizeValueLabel = new Label($"{artboard.Width} x {artboard.Height}");
sizeContainer.Add(sizeValueLabel);
// State Machines Container
var stateMachinesContainer = new VisualElement();
stateMachinesContainer.style.marginLeft = 15;
stateMachinesContainer.style.marginTop = 10;
artboardContainer.Add(stateMachinesContainer);
foreach (var stateMachine in artboard.StateMachines)
{
var smContainer = new VisualElement();
smContainer.style.marginBottom = 10;
// State Machine Header
var smHeader = new VisualElement();
smHeader.style.flexDirection = FlexDirection.Row;
smHeader.style.alignItems = Align.Center;
smContainer.Add(smHeader);
var smLabel = new Label("State Machine:");
smLabel.style.marginRight = 8;
smHeader.Add(smLabel);
var smNameField = new TextField();
smNameField.value = stateMachine.Name;
StyleAsReadonly(smNameField);
smNameField.isReadOnly = true;
smNameField.SetEnabled(true);
smNameField.style.flexGrow = 1;
smHeader.Add(smNameField);
// Inputs
if (stateMachine.Inputs.Count > 0)
{
var inputsContainer = new VisualElement();
inputsContainer.style.marginLeft = 15;
inputsContainer.style.marginTop = 5;
smContainer.Add(inputsContainer);
var inputsLabel = new Label("Inputs:");
inputsLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
inputsLabel.style.marginBottom = 5;
inputsContainer.Add(inputsLabel);
foreach (var input in stateMachine.Inputs)
{
var inputContainer = new VisualElement();
inputContainer.style.flexDirection = FlexDirection.Row;
inputContainer.style.alignItems = Align.Center;
inputContainer.style.marginBottom = 2;
var typeLabel = new Label(input.Type);
typeLabel.style.marginRight = 8;
typeLabel.style.width = 60;
var nameField = new TextField();
nameField.value = input.Name;
StyleAsReadonly(nameField);
nameField.isReadOnly = true;
nameField.SetEnabled(true);
nameField.style.flexGrow = 1;
inputContainer.Add(typeLabel);
inputContainer.Add(nameField);
inputsContainer.Add(inputContainer);
}
}
stateMachinesContainer.Add(smContainer);
}
if (artboard.DefaultViewModel != null && !String.IsNullOrEmpty(artboard.DefaultViewModel.Name))
{
var defaultVMContainer = new VisualElement();
defaultVMContainer.style.flexDirection = FlexDirection.Row;
defaultVMContainer.style.alignItems = Align.Center;
defaultVMContainer.style.marginLeft = 15;
defaultVMContainer.style.marginBottom = 5;
artboardContainer.Add(defaultVMContainer);
var defaultVMLabel = new Label("Default View Model:");
defaultVMLabel.style.marginRight = 8;
defaultVMContainer.Add(defaultVMLabel);
var defaultVMNameField = new TextField();
defaultVMNameField.value = artboard.DefaultViewModel.Name;
StyleAsReadonly(defaultVMNameField);
defaultVMNameField.isReadOnly = true;
defaultVMNameField.SetEnabled(true);
defaultVMNameField.style.flexGrow = 1;
defaultVMContainer.Add(defaultVMNameField);
}
}
}
// View Models Section
if (riveAsset.EditorOnlyMetadata != null && riveAsset.EditorOnlyMetadata.ViewModels.Count > 0)
{
var viewModelsFoldout = new Foldout { text = "View Models", value = false };
root.Add(viewModelsFoldout);
foreach (var viewModel in riveAsset.EditorOnlyMetadata.ViewModels)
{
var viewModelFoldout = new Foldout { text = viewModel.Name, value = false };
viewModelFoldout.style.paddingLeft = 8;
viewModelFoldout.style.paddingRight = 8;
viewModelsFoldout.Add(viewModelFoldout);
AddCopyToClipboardMenu(viewModelFoldout, viewModel.Name, "Copy View Model Name");
// Properties
if (viewModel.Properties.Count > 0)
{
var propertiesContainer = new VisualElement();
propertiesContainer.style.marginLeft = 15;
propertiesContainer.style.marginTop = 5;
viewModelFoldout.Add(propertiesContainer);
var propertiesLabel = new Label("Properties:");
propertiesLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
propertiesLabel.style.marginBottom = 5;
propertiesContainer.Add(propertiesLabel);
foreach (var property in viewModel.Properties)
{
var propertyContainer = new VisualElement();
propertyContainer.style.flexDirection = FlexDirection.Row;
propertyContainer.style.alignItems = Align.Center;
propertyContainer.style.marginBottom = 2;
var typeLabel = new Label(GetViewModelPropertyTypeLabel(property));
typeLabel.style.marginRight = 8;
typeLabel.style.minWidth = 60;
var nameField = new TextField();
nameField.value = property.Name;
StyleAsReadonly(nameField);
nameField.isReadOnly = true;
nameField.SetEnabled(true);
nameField.style.flexGrow = 1;
propertyContainer.Add(typeLabel);
propertyContainer.Add(nameField);
propertiesContainer.Add(propertyContainer);
}
}
// Instance Names
if (viewModel.InstanceNames.Count > 0)
{
var instancesContainer = new VisualElement();
instancesContainer.style.marginLeft = 15;
instancesContainer.style.marginTop = 10;
instancesContainer.style.marginBottom = 10;
viewModelFoldout.Add(instancesContainer);
var instancesLabel = new Label("Instances:");
instancesLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
instancesLabel.style.marginBottom = 5;
instancesContainer.Add(instancesLabel);
foreach (var instanceName in viewModel.InstanceNames)
{
var instanceField = new TextField();
instanceField.value = instanceName;
StyleAsReadonly(instanceField);
instanceField.isReadOnly = true;
instanceField.SetEnabled(true);
instancesContainer.Add(instanceField);
}
}
}
}
// Enums Section
if (riveAsset.EditorOnlyMetadata.Enums.Count > 0)
{
var enumsFoldout = new Foldout { text = "Enums", value = false };
root.Add(enumsFoldout);
foreach (var enumData in riveAsset.EditorOnlyMetadata.Enums)
{
// Create a foldout for each enum type
var enumFoldout = new Foldout { text = enumData.Name, value = false };
enumFoldout.style.paddingLeft = 8;
enumFoldout.style.paddingRight = 8;
enumsFoldout.Add(enumFoldout);
AddCopyToClipboardMenu(enumFoldout, enumData.Name, "Copy Enum Name");
// Values
var valuesContainer = new VisualElement();
valuesContainer.style.marginLeft = 15;
valuesContainer.style.marginTop = 5;
valuesContainer.style.marginBottom = 10;
enumFoldout.Add(valuesContainer);
var valuesLabel = new Label("Values:");
valuesLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
valuesLabel.style.marginBottom = 5;
valuesContainer.Add(valuesLabel);
foreach (var value in enumData.Values)
{
var valueField = new TextField();
valueField.value = value;
StyleAsReadonly(valueField);
valueField.isReadOnly = true;
valueField.SetEnabled(true);
valuesContainer.Add(valueField);
}
}
}
return root;
}
private void AddCopyToClipboardMenu(Foldout foldout, string textToCopy, string itemLabel = null)
{
if (string.IsNullOrEmpty(textToCopy))
{
return;
}
itemLabel = itemLabel ?? $"Copy \"{foldout.text}\"";
foldout.AddManipulator(new ContextualMenuManipulator((ContextualMenuPopulateEvent evt) =>
{
evt.menu.AppendAction(itemLabel, (action) =>
{
GUIUtility.systemCopyBuffer = textToCopy;
});
}));
}
private string GetViewModelPropertyTypeLabel(FileMetadata.ViewModelPropertyMetadata property)
{
// We want to display the type of the property, and if it's a ViewModel type, we also want to display the nested ViewModel name.
if (property.Type == ViewModelDataType.ViewModel)
{
return $"{property.Type.ToString()} ({property.NestedViewModelName})";
}
else if (property.Type == ViewModelDataType.Enum && !string.IsNullOrEmpty(property.EnumTypeName))
{
return $"{property.Type.ToString()} ({property.EnumTypeName})";
}
return $"{property.Type.ToString()}";
}
private void StyleAsReadonly(VisualElement element)
{
element.style.opacity = 0.5f;
}
private System.Type GetAssetType(EmbeddedAssetType assetType)
{
switch (assetType)
{
case EmbeddedAssetType.Font:
return typeof(FontOutOfBandAsset);
case EmbeddedAssetType.Image:
return typeof(ImageOutOfBandAsset);
case EmbeddedAssetType.Audio:
return typeof(AudioOutOfBandAsset);
default:
return typeof(System.Object);
}
}
public override Texture2D RenderStaticPreview(
string assetPath,
UnityEngine.Object[] subAssets,
int width,
int height
)
{
RenderTexture prev = RenderTexture.active;
var rect = new Rect(0, 0, width, height);
RenderTexture rt = Render(rect, true);
if (rt != null)
{
RenderTexture sourceForRead = rt;
RenderTexture temp = null;
// Static preview: use the runtime decode material (Rive/UI/Default) in Linear color space.
// This decodes gamma to linear, which works correctly with ReadPixels→Texture2D path.
// We DON'T use the pass-through shader here because the blit+ReadPixels seems to cause issues, and leads to nothing rendering for the static preview.
// TODO: Remove this once we have a proper way to display the texture in Linear color space.
if (Rive.TextureHelper.ProjectNeedsColorSpaceFix)
{
var mat = Rive.TextureHelper.GammaToLinearUIMaterial;
if (mat != null)
{
temp = RenderTexture.GetTemporary(rt.width, rt.height, 0, RenderTextureFormat.ARGB32);
Graphics.Blit(rt, temp, mat);
sourceForRead = temp;
}
}
RenderTexture.active = sourceForRead;
Texture2D tex = new Texture2D(width, height);
tex.ReadPixels(rect, 0, 0);
tex.Apply(true);
RenderTexture.active = prev;
if (temp != null)
{
RenderTexture.ReleaseTemporary(temp);
}
RenderTexture.ReleaseTemporary(rt);
return tex;
}
return null;
}
RenderTexture Render(Rect rect, bool isStatic = false)
{
int width = (int)rect.width;
int height = (int)rect.height;
var descriptor = Rive.TextureHelper.Descriptor(width, height);
RenderTexture rt = RenderTexture.GetTemporary(descriptor);
var cmb = new CommandBuffer();
cmb.SetRenderTarget(rt);
if (m_file == null)
{
var riveAsset = (Rive.Asset)target;
m_file = Rive.File.Load(riveAsset);
m_artboard = m_file?.Artboard(0);
m_stateMachine = m_artboard?.StateMachine();
}
if (m_artboard != null)
{
var rq = new RenderQueue(
UnityEngine.SystemInfo.graphicsDeviceType == GraphicsDeviceType.Metal
? null
: rt
);
var renderer = rq.Renderer();
renderer.Align(Fit.Contain, Alignment.Center, m_artboard);
renderer.Draw(m_artboard);
renderer.AddToCommandBuffer(cmb);
if (!isStatic)
{
var now = EditorApplication.timeSinceStartup;
double time = now - m_lastTime;
m_stateMachine?.Advance((float)(now - m_lastTime));
m_lastTime = now;
}
else
{
m_stateMachine?.Advance(0.0f);
}
}
var prev = RenderTexture.active;
Graphics.ExecuteCommandBuffer(cmb);
GL.InvalidateState();
cmb.Clear();
if (isStatic && FlipY())
{
RenderTexture temp = RenderTexture.GetTemporary(
width,
height,
0,
RenderTextureFormat.Default,
RenderTextureReadWrite.Default
);
temp.Create();
Graphics.Blit(rt, temp, new Vector2(1, -1), new Vector2(0, 1));
RenderTexture.ReleaseTemporary(rt);
rt = temp;
}
// Caller releases the temporary RT
return rt;
}
public override void OnPreviewGUI(Rect rect, GUIStyle background)
{
if (Event.current.type == EventType.Repaint)
{
RenderTexture rt = Render(rect);
var drawRect = FlipY()
? new Rect(rect.x, rect.y + rect.height, rect.width, -rect.height)
: rect;
// Live preview: use a simple pass-through shader in Linear color space.
// Rive outputs gamma values, and it looks like EditorGUI.DrawPreviewTexture expects sRGB input.
// We DON'T use the decode material here because it would decode to linear, causing burnt/dark colors.
// The pass-through shader (Hidden/Rive/Editor/SRGBEncodePreview) just returns the texture unchanged.
// TODO: Remove this once we have a proper way to display the texture in Linear color space.
var mat = (Rive.TextureHelper.ProjectNeedsColorSpaceFix ? GetEncodePreviewMaterial() : null);
UnityEditor.EditorGUI.DrawPreviewTexture(drawRect, rt, mat);
RenderTexture.ReleaseTemporary(rt);
}
}
private static Material s_encodePreviewMaterial;
private static Material GetEncodePreviewMaterial()
{
if (s_encodePreviewMaterial != null) return s_encodePreviewMaterial;
var shader = Shader.Find("Hidden/Rive/Editor/SRGBEncodePreview");
if (shader == null) return null;
s_encodePreviewMaterial = new Material(shader)
{
name = "Rive_Editor_SRGBEncodePreview",
hideFlags = HideFlags.HideAndDontSave
};
return s_encodePreviewMaterial;
}
private void UnloadPreview()
{
m_stateMachine = null;
m_artboard = null;
if (m_file != null)
{
m_file.Dispose();
m_file = null;
}
}
public void OnDisable()
{
var riveAsset = (Rive.Asset)target;
UnloadPreview();
}
private static bool FlipY()
{
switch (UnityEngine.SystemInfo.graphicsDeviceType)
{
case GraphicsDeviceType.Metal:
case GraphicsDeviceType.Vulkan:
case GraphicsDeviceType.Direct3D11:
return true;
default:
return false;
}
}
static string FormatBytes(uint byteCount)
{
string[] sizes = { "B", "KB", "MB", "GB", "TB" };
int order = 0;
while (byteCount >= 1024 && order < sizes.Length - 1)
{
order++;
byteCount /= 1024;
}
// Adjust the format string to your preferences. For example "{0:0.#}{1}" would
// show a single decimal place, and no space.
return string.Format("{0:0.##} {1}", byteCount, sizes[order]);
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: c0c0086d5bb044ad3ad1fd3d80d1a5e4
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/AssetEditor.cs
uploadId: 896810

View File

@@ -0,0 +1,308 @@
using UnityEngine;
using System.Collections.Generic;
using UnityEditor.AssetImporters;
using UnityEditor;
using System;
using System.Linq;
using Rive.Utils;
namespace Rive
{
[ScriptedImporter(version: 2, ext: "riv")]
public class AssetImporter : ScriptedImporter
{
[System.Serializable]
private class OobReferenceData
{
/// <summary>
/// ID of the referenced asset in Rive
/// </summary>
public uint assetIdInRiv;
/// <summary>
/// GUID of the referenced asset in Unity
/// </summary>
public string assetGuid;
}
[System.Serializable]
private class UserDataContainer
{
/// <summary>
/// List of stored OOB references to override OutOfBandAsset references on the generated Asset
/// </summary>
public List<OobReferenceData> oobReferences = new List<OobReferenceData>();
/// <summary>
/// Remove a stored OOB reference from the list
/// </summary>
/// <param name="assetId"> ID of the referenced asset in Rive </param>
public void RemoveOobReference(uint assetId)
{
oobReferences.RemoveAll(r => r.assetIdInRiv == assetId);
}
/// <summary>
/// Add a new OOB reference to the list
/// </summary>
/// <param name="assetId"> ID of the referenced asset in Rive </param>
/// <param name="assetGuid"> GUID of the referenced asset in Unity </param>
public void AddOobReference(uint assetId, string assetGuid)
{
// Remove any existing reference with the same ID
RemoveOobReference(assetId);
oobReferences.Add(new OobReferenceData
{
assetIdInRiv = assetId,
assetGuid = assetGuid
});
}
}
delegate void Callback();
void AfterUpdate(Callback call)
{
void callback()
{
if (!EditorApplication.isCompiling && !EditorApplication.isUpdating)
{
call();
EditorApplication.update -= callback;
}
}
EditorApplication.update += callback;
}
public static string[] fontExtensions = FontOobAssetExtensions.FontExtensions;
public static string[] imageExtensions = ImageOobAssetExtensions.ImageExtensions;
public static string[] audioExtensions = AudioOobAssetExtensions.AudioExtensions;
private EmbeddedAssetDataLoader embeddedAssetDataLoader = new EmbeddedAssetDataLoader();
public override void OnImportAsset(AssetImportContext ctx)
{
bool isFirstImport = importSettingsMissing;
var assetPath = ctx.assetPath;
byte[] bytes = System.IO.File.ReadAllBytes(assetPath);
// Deserialize stored references from userData
// The Asset class is recreated on every import, so changes made to the Asset class will be lost on reimport. To get around this, we store reference information about oob asset overrides in the importer userData to keep them between imports.
// This lets us keep track of the custom assets provided by the user. The userData is stored in the meta file.
var userDataContainer = string.IsNullOrEmpty(userData)
? new UserDataContainer()
: JsonUtility.FromJson<UserDataContainer>(userData);
int oobAssetCount = 0;
List<EmbeddedAssetData> assets = new List<EmbeddedAssetData>();
foreach (var embeddedAsset in embeddedAssetDataLoader.LoadEmbeddedAssetDataFromRiveFileBytes(bytes))
{
// Check if we have a stored reference
var storedReference = userDataContainer.oobReferences
.FirstOrDefault(r => r.assetIdInRiv == embeddedAsset.Id);
// If the asset at the index is not a referenced asset, then we clear the stored reference as we only want to store references to oob assets set to `Referenced`
if (embeddedAsset.InBandBytesSize > 0)
{
userDataContainer.RemoveOobReference(embeddedAsset.Id);
storedReference = null;
}
if (storedReference != null)
{
var oobAssetPath = AssetDatabase.GUIDToAssetPath(storedReference.assetGuid);
if (!string.IsNullOrEmpty(assetPath))
{
var referencedAsset = AssetDatabase.LoadAssetAtPath<OutOfBandAsset>(oobAssetPath);
if (referencedAsset != null)
{
embeddedAsset.OutOfBandAsset = referencedAsset;
ctx.DependsOnArtifact(oobAssetPath);
}
}
}
else
{
var basePath = System.IO.Path.GetDirectoryName(assetPath);
switch (embeddedAsset.AssetType)
{
case EmbeddedAssetType.Font:
HandleAsset(embeddedAsset, basePath, fontExtensions, typeof(FontOutOfBandAsset), ctx, ref oobAssetCount);
break;
case EmbeddedAssetType.Image:
HandleAsset(embeddedAsset, basePath, imageExtensions, typeof(ImageOutOfBandAsset), ctx, ref oobAssetCount);
break;
case EmbeddedAssetType.Audio:
HandleAsset(embeddedAsset, basePath, audioExtensions, typeof(AudioOutOfBandAsset), ctx, ref oobAssetCount);
break;
}
}
assets.Add(embeddedAsset);
}
var file = Asset.Create(bytes, assets.ToArray());
ctx.AddObjectToAsset("rive", file);
if (oobAssetCount != 0 && isFirstImport)
{
// The file seems to have out of band assets, try to resolve
// them. We only do this on first import so the user can go
// manually change auto-detected OOB assets to use a different
// importer if they want.
AfterUpdate(() =>
{
ImportOutOfBandAssets(assetPath);
});
}
ctx.SetMainObject(file);
}
internal static void SetOobAssetReference(Asset targetRiveAsset, uint assetId, OutOfBandAsset oobAsset)
{
var importer = AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(targetRiveAsset)) as AssetImporter;
if (importer == null)
{
DebugLogger.Instance.LogError("Could not get AssetImporter for target Rive asset");
return;
}
string userDataToApply = importer.userData;
var container = string.IsNullOrEmpty(userDataToApply)
? new UserDataContainer()
: JsonUtility.FromJson<UserDataContainer>(userDataToApply);
container.RemoveOobReference(assetId);
// If the asset is null, we still want to store the reference as the user might have set it to null on purpose.
// If we don't store it, the importer will try to auto-detect a matching asset in the directory on the next import.
var path = oobAsset != null ? AssetDatabase.GetAssetPath(oobAsset) : null;
var guid = path != null ? AssetDatabase.AssetPathToGUID(path) : null;
container.AddOobReference(assetId, guid);
// Clear out any items in the list that are no longer valid (e.g assetIdInRiv is no longer in the file)
container.oobReferences.RemoveAll(r => targetRiveAsset.EmbeddedAssets.All(e => e.Id != r.assetIdInRiv));
userDataToApply = JsonUtility.ToJson(container);
importer.userData = userDataToApply;
importer.SaveAndReimport();
}
private void HandleAsset(EmbeddedAssetData embeddedAsset, string basePath, string[] extensions, Type assetType, AssetImportContext ctx, ref int oobAssetCount)
{
// If this is a reimport and the asset already has a valid OutOfBandAsset, keep it
if (embeddedAsset.OutOfBandAsset != null)
{
ctx.DependsOnArtifact(AssetDatabase.GetAssetPath(embeddedAsset.OutOfBandAsset));
oobAssetCount++;
return;
}
// Only try to auto-detect assets if there isn't one already assigned
foreach (var path in AssetPaths(basePath, embeddedAsset.Name, embeddedAsset.Id, extensions))
{
if (System.IO.File.Exists(path))
{
oobAssetCount++;
ctx.DependsOnArtifact(path);
if (!String.IsNullOrEmpty(AssetDatabase.AssetPathToGUID(path)))
{
var referencedAsset = AssetDatabase.LoadAssetAtPath(path, assetType);
if (referencedAsset != null)
{
embeddedAsset.OutOfBandAsset = referencedAsset as OutOfBandAsset;
}
}
break;
}
}
}
// Pre-import any out of band assets.
private void ImportOutOfBandAssets(string assetPath)
{
var bytes = System.IO.File.ReadAllBytes(assetPath);
var basePath = System.IO.Path.GetDirectoryName(assetPath);
foreach (var embeddedAsset in embeddedAssetDataLoader.LoadEmbeddedAssetDataFromRiveFileBytes(bytes))
{
switch (embeddedAsset.AssetType)
{
case EmbeddedAssetType.Font:
ImportAssetGeneric<FontAssetImporter>(embeddedAsset, basePath, fontExtensions);
break;
case EmbeddedAssetType.Image:
ImportAssetGeneric<ImageAssetImporter>(embeddedAsset, basePath, imageExtensions);
break;
case EmbeddedAssetType.Audio:
ImportAssetGeneric<AudioAssetImporter>(embeddedAsset, basePath, audioExtensions);
break;
}
}
}
private void ImportAssetGeneric<T>(EmbeddedAssetData embeddedAsset, string basePath, string[] extensions) where T : ScriptedImporter
{
foreach (var path in AssetPaths(basePath, embeddedAsset.Name, embeddedAsset.Id, extensions))
{
if (System.IO.File.Exists(path) && AssetDatabase.GetImporterOverride(path) == null)
{
AssetDatabase.SetImporterOverride<T>(path);
AssetDatabase.ImportAsset(path);
break;
}
}
}
string[] AssetPaths(string basePath, string name, uint id, string[] extensions)
{
List<string> paths = new List<string>();
foreach (var extension in extensions)
{
paths.Add(
basePath
+ System.IO.Path.DirectorySeparatorChar
+ System.IO.Path.GetFileNameWithoutExtension(name)
+ "-"
+ id
+ "."
+ extension
);
paths.Add(
basePath
+ System.IO.Path.DirectorySeparatorChar
+ System.IO.Path.GetFileNameWithoutExtension(name)
+ "."
+ extension
);
}
return paths.ToArray();
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 043c4b240e8f94f828b8b81fa436f7f0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: df0df084c4eaa4149a9c988c71b85313, type: 3}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 350858
packageName: Rive
packageVersion: 0.4.2
assetPath: Packages/app.rive.rive-unity/Editor/AssetImporter.cs
uploadId: 896810

View File

@@ -0,0 +1,28 @@
using UnityEngine;
using UnityEditor.AssetImporters;
namespace Rive
{
internal static class AudioOobAssetExtensions
{
public const string WAV = "wav";
public const string MP3 = "mp3";
public const string FLAC = "flac";
public static readonly string[] AudioExtensions = new[] { WAV, MP3, FLAC };
}
[ScriptedImporter(1, null, new string[] { AudioOobAssetExtensions.WAV, AudioOobAssetExtensions.MP3, AudioOobAssetExtensions.FLAC })]
public class AudioAssetImporter : ScriptedImporter
{
public override void OnImportAsset(AssetImportContext ctx)
{
byte[] bytesToAssign = System.IO.File.ReadAllBytes(ctx.assetPath);
AudioOutOfBandAsset file = OutOfBandAsset.Create<AudioOutOfBandAsset>(bytesToAssign);
ctx.AddObjectToAsset("rive-audio", file);
ctx.SetMainObject(file);
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 20badb765cb0b4f4dadbcd748172bc88
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/AudioAssetImporter.cs
uploadId: 896810

View File

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

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

View File

@@ -0,0 +1,28 @@
using UnityEngine;
using UnityEditor.AssetImporters;
namespace Rive
{
internal static class FontOobAssetExtensions
{
public const string TTF = "ttf";
public const string OTF = "otf";
public static readonly string[] FontExtensions = new[] { TTF, OTF };
}
[ScriptedImporter(2, null, new string[] { FontOobAssetExtensions.TTF, FontOobAssetExtensions.OTF })]
public class FontAssetImporter : ScriptedImporter
{
public override void OnImportAsset(AssetImportContext ctx)
{
byte[] bytesToAssign = System.IO.File.ReadAllBytes(ctx.assetPath);
FontOutOfBandAsset file = OutOfBandAsset.Create<FontOutOfBandAsset>(bytesToAssign);
ctx.AddObjectToAsset("rive-font", file);
ctx.SetMainObject(file);
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: e35fd16d945b04aaa9bbb2d5c7224cf9
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/FontAssetImporter.cs
uploadId: 896810

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,147 @@
fileFormatVersion: 2
guid: f377684eb5623462e80ebba320696f74
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 350858
packageName: Rive
packageVersion: 0.4.2
assetPath: Packages/app.rive.rive-unity/Editor/Icons/d_rive.png
uploadId: 896810

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,147 @@
fileFormatVersion: 2
guid: d85a4632cb63547b38008947f3e6571b
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 350858
packageName: Rive
packageVersion: 0.4.2
assetPath: Packages/app.rive.rive-unity/Editor/Icons/d_rive@2x.png
uploadId: 896810

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,147 @@
fileFormatVersion: 2
guid: df0df084c4eaa4149a9c988c71b85313
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 350858
packageName: Rive
packageVersion: 0.4.2
assetPath: Packages/app.rive.rive-unity/Editor/Icons/rive.png
uploadId: 896810

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,147 @@
fileFormatVersion: 2
guid: b448b542b6b2945cb97bbcb34bebfd17
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 350858
packageName: Rive
packageVersion: 0.4.2
assetPath: Packages/app.rive.rive-unity/Editor/Icons/rive@2x.png
uploadId: 896810

View File

@@ -0,0 +1,28 @@
using UnityEngine;
using UnityEditor.AssetImporters;
namespace Rive
{
internal static class ImageOobAssetExtensions
{
public const string PNG = "png";
public const string JPG = "jpg";
public const string JPEG = "jpeg";
public const string WEBP = "webp";
public static readonly string[] ImageExtensions = new[] { PNG, JPG, JPEG, WEBP };
}
[ScriptedImporter(2, new string[] { ImageOobAssetExtensions.WEBP }, new string[] { ImageOobAssetExtensions.PNG, ImageOobAssetExtensions.JPG, ImageOobAssetExtensions.JPEG, ImageOobAssetExtensions.WEBP })]
public class ImageAssetImporter : ScriptedImporter
{
public override void OnImportAsset(AssetImportContext ctx)
{
byte[] bytesToAssign = System.IO.File.ReadAllBytes(ctx.assetPath);
ImageOutOfBandAsset file = OutOfBandAsset.Create<ImageOutOfBandAsset>(bytesToAssign);
ctx.AddObjectToAsset("rive-image", file);
ctx.SetMainObject(file);
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: b2f3a627015694711ac8598da5383316
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/ImageAssetImporter.cs
uploadId: 896810

View File

@@ -0,0 +1,159 @@
using System;
using System.IO;
using Rive.Utils;
using UnityEditor;
using UnityEngine;
namespace Rive
{
/// <summary>
/// Custom editor for ImageOutOfBandAsset that displays a preview of the image.
/// </summary>
[CustomEditor(typeof(ImageOutOfBandAsset))]
public class ImageOutOfBandAssetEditor : Editor
{
private enum PreviewMode
{
Contain = 0,
Cover = 1
}
private PreviewMode previewMode = PreviewMode.Contain;
public override Texture2D RenderStaticPreview(string assetPath, UnityEngine.Object[] subAssets, int width, int height)
{
var asset = (ImageOutOfBandAsset)target;
if (asset == null || asset.Bytes == null || asset.Bytes.Length == 0)
{
return null;
}
// WebP images are not currently supported for previews because they are not natively supported by the Unity engine, custom support will be added in the future.
if (IsWebp(assetPath))
{
return null;
}
Texture2D originalTexture = LoadOriginalTexture(asset.Bytes);
if (originalTexture == null)
{
return null;
}
Vector2Int newSize = CalculateNewSize(originalTexture.width, originalTexture.height, width, height);
Texture2D resizedTexture = ResizeTexture(originalTexture, newSize.x, newSize.y);
Texture2D previewTexture = CreatePreviewTexture(resizedTexture, width, height);
UnityEngine.Object.DestroyImmediate(originalTexture);
UnityEngine.Object.DestroyImmediate(resizedTexture);
return previewTexture;
}
private Texture2D LoadOriginalTexture(byte[] bytes)
{
Texture2D texture = new Texture2D(2, 2);
if (texture.LoadImage(bytes))
{
return texture;
}
DebugLogger.Instance.LogWarning("Failed to load image preview for ImageOutOfBandAsset");
return null;
}
private Vector2Int CalculateNewSize(int originalWidth, int originalHeight, int targetWidth, int targetHeight)
{
float aspectRatio = (float)originalWidth / originalHeight;
float targetAspectRatio = (float)targetWidth / targetHeight;
switch (previewMode)
{
case PreviewMode.Contain:
if (targetAspectRatio > aspectRatio)
{
return new Vector2Int(
Mathf.RoundToInt(targetHeight * aspectRatio),
targetHeight
);
}
else
{
return new Vector2Int(
targetWidth,
Mathf.RoundToInt(targetWidth / aspectRatio)
);
}
case PreviewMode.Cover:
if (targetAspectRatio > aspectRatio)
{
return new Vector2Int(
targetWidth,
Mathf.RoundToInt(targetWidth / aspectRatio)
);
}
else
{
return new Vector2Int(
Mathf.RoundToInt(targetHeight * aspectRatio),
targetHeight
);
}
default:
DebugLogger.Instance.LogWarning($"Unsupported preview mode: {previewMode}. Falling back to Contain mode.");
goto case PreviewMode.Contain;
}
}
private Texture2D ResizeTexture(Texture2D originalTexture, int newWidth, int newHeight)
{
RenderTexture rt = RenderTexture.GetTemporary(newWidth, newHeight, 0, RenderTextureFormat.Default, RenderTextureReadWrite.Linear);
// Set the filter mode to bilinear to match the previous scaling method
rt.filterMode = FilterMode.Bilinear;
RenderTexture.active = rt;
Graphics.Blit(originalTexture, rt);
Texture2D resizedTexture = new Texture2D(newWidth, newHeight);
resizedTexture.ReadPixels(new Rect(0, 0, newWidth, newHeight), 0, 0);
resizedTexture.Apply();
RenderTexture.active = null;
RenderTexture.ReleaseTemporary(rt);
return resizedTexture;
}
private Texture2D CreatePreviewTexture(Texture2D resizedTexture, int width, int height)
{
Texture2D previewTexture = new Texture2D(width, height, TextureFormat.RGBA32, false);
// Fill the background with transparency
UnityEngine.Color[] fillPixels = new UnityEngine.Color[width * height];
for (int i = 0; i < fillPixels.Length; i++)
fillPixels[i] = UnityEngine.Color.clear;
previewTexture.SetPixels(fillPixels);
// Center the resized image
int x = (width - resizedTexture.width) / 2;
int y = (height - resizedTexture.height) / 2;
// Copy the resized image to the center of the preview texture
previewTexture.SetPixels32(x, y, resizedTexture.width, resizedTexture.height, resizedTexture.GetPixels32());
previewTexture.Apply();
return previewTexture;
}
private bool IsWebp(string assetPath)
{
if (string.IsNullOrEmpty(assetPath))
{
return false;
}
return string.Equals(System.IO.Path.GetExtension(assetPath), ".webp", StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: fb25ae926319f442e819f03e76fe3903
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/ImageOutOfBandAssetEditor.cs
uploadId: 896810

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

@@ -0,0 +1,186 @@
fileFormatVersion: 2
guid: 233ae908c7a8b4e469e848c97a45f4a6
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 1
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 1024
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: VisionOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: tvOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 350858
packageName: Rive
packageVersion: 0.4.2
assetPath: Packages/app.rive.rive-unity/Editor/Images/rive-preview-image.png
uploadId: 896810

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 KiB

View File

@@ -0,0 +1,186 @@
fileFormatVersion: 2
guid: e3703478f7a284efb81797fb6fb6820f
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Android
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: VisionOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: tvOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 350858
packageName: Rive
packageVersion: 0.4.2
assetPath: Packages/app.rive.rive-unity/Editor/Images/welcome-banner.png
uploadId: 896810

View File

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

View File

@@ -0,0 +1,144 @@
using System;
using System.Linq;
using Rive.EditorTools;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
namespace Rive.EditorTools
{
/// <summary>
/// Adds a menu item to copy useful environment information to the clipboard for support tickets.
/// </summary>
internal static class SupportInfoMenu
{
private const string MenuPath = "Tools/Rive/Copy Support Info";
[MenuItem(MenuPath, priority = 1000)]
private static void CopySupportInfo()
{
try
{
string supportInfo = GenerateSupportInfo();
EditorGUIUtility.systemCopyBuffer = supportInfo;
EditorUtility.DisplayDialog("Rive", "Support info copied to clipboard.", "OK");
}
catch (Exception ex)
{
Debug.LogError($"Failed to copy Rive support info: {ex}");
}
}
private static string GenerateSupportInfo()
{
string unityVersion = Application.unityVersion;
BuildTarget activeBuildTarget = EditorUserBuildSettings.activeBuildTarget;
var targetGroup = BuildPipeline.GetBuildTargetGroup(activeBuildTarget);
var apis = PlayerSettings.GetGraphicsAPIs(activeBuildTarget);
string graphicsApis = apis != null && apis.Length > 0
? string.Join(", ", apis.Select(api => api.ToString()).ToArray())
: "Auto (Unity default)";
string renderPipeline = GetRenderPipelineDescription();
string operatingSystem = SystemInfo.operatingSystem;
string graphicsDevice = SystemInfo.graphicsDeviceName + " (" + SystemInfo.graphicsDeviceType + ")";
string riveVersion = GetPackageVersion(Rive.EditorTools.PackageInfo.PACKAGE_NAME);
return
"Rive Unity Support Info\n" +
"------------------------\n" +
$"Unity Version: {unityVersion}\n" +
$"Active Build Target: {activeBuildTarget}\n" +
$"Build Target Group: {targetGroup}\n" +
$"Graphics APIs: {graphicsApis}\n" +
$"Render Pipeline: {renderPipeline}\n" +
$"OS: {operatingSystem}\n" +
$"GPU: {graphicsDevice}\n" +
$"Rive Plugin: {Rive.EditorTools.PackageInfo.PACKAGE_NAME} {riveVersion}\n";
}
private static string GetRenderPipelineDescription()
{
var asset = GraphicsSettings.currentRenderPipeline;
if (asset == null)
{
return "Built-in Render Pipeline";
}
var srpType = asset.GetType();
string pipelineName = srpType.Name;
// Try to fetch version via known properties if available
string version = null;
var versionProperty = srpType.GetProperty("version") ?? srpType.GetProperty("Version");
if (versionProperty != null)
{
try
{
var value = versionProperty.GetValue(asset, null);
version = value != null ? value.ToString() : null;
}
catch { }
}
// Fallback to package if known SRPs
if (string.IsNullOrEmpty(version))
{
string packageId = null;
if (srpType.FullName.Contains("UniversalRenderPipeline"))
{
packageId = "com.unity.render-pipelines.universal";
}
else if (srpType.FullName.Contains("HDRenderPipeline"))
{
packageId = "com.unity.render-pipelines.high-definition";
}
if (!string.IsNullOrEmpty(packageId))
{
version = GetPackageVersion(packageId);
}
}
return string.IsNullOrEmpty(version) ? pipelineName : pipelineName + " " + version;
}
private static string GetPackageVersion(string packageName)
{
// Use UnityEditor.PackageManager for reliable version when available.
try
{
var request = UnityEditor.PackageManager.Client.List(true, true);
// We've added a busy-wait with timeout to avoid async flow in menu command
var start = DateTime.UtcNow;
while (!request.IsCompleted)
{
if ((DateTime.UtcNow - start).TotalSeconds > 5)
{
break;
}
}
if (request.IsCompleted && request.Status == UnityEditor.PackageManager.StatusCode.Success)
{
var pkg = request.Result.FirstOrDefault(p => p.name == packageName);
if (pkg != null)
{
return pkg.version;
}
}
}
catch
{
}
return "(version unknown)";
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 544d95f9bf7bf475bb599e5cb74f4015
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/Menu/SupportInfoMenu.cs
uploadId: 896810

View File

@@ -0,0 +1,58 @@
using Rive.Utils;
using UnityEditor;
using UnityEditor.PackageManager;
namespace Rive.EditorTools
{
/// <summary>
/// Checks if the Rive package was updated and shows a dialog to restart the Unity Editor.
/// We do this because the Rive plugin is a native plugin and the Unity Editor needs to be restarted in order unload the old version and load the new one.
/// </summary>
[InitializeOnLoad]
internal class PackageVersionChecker
{
static PackageVersionChecker()
{
Events.registeredPackages += OnPackagesRegistered;
}
private static void OnPackagesRegistered(PackageRegistrationEventArgs args)
{
var updatedPackage = FindByName(args.changedTo);
if (updatedPackage != null)
{
ShowRestartDialog(updatedPackage.version);
}
}
private static UnityEditor.PackageManager.PackageInfo FindByName(System.Collections.Generic.IEnumerable<UnityEditor.PackageManager.PackageInfo> packages)
{
foreach (var package in packages)
{
if (package != null && package.name == Rive.EditorTools.PackageInfo.PACKAGE_NAME)
{
return package;
}
}
return null;
}
private static void ShowRestartDialog(string newVersion)
{
EditorUtility.DisplayDialog(
"Package Update Detected",
$"The Rive plugin has been updated to version {newVersion}.\n\n" +
"Please restart Unity to load the new version.",
"OK"
);
DebugLogger.Instance.LogWarning(
$"[{Rive.EditorTools.PackageInfo.PACKAGE_NAME}] Package updated to {newVersion}. " +
"Please restart the Unity Editor to make sure the new version is fully loaded. If you skip this step, you might run into issues, and riv files may not work properly."
);
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 86ee58136b6f64281b6cd0aaa150fd1d
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/PackageVersionChecker.cs
uploadId: 896810

View File

@@ -0,0 +1,19 @@
{
"name": "Rive.Editor",
"rootNamespace": "Rive",
"references": [
"GUID:0a82aeb665886483c867b7d137563619",
"GUID:df380645f10b7bc4b97d4f5eb6303d95"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,14 @@
fileFormatVersion: 2
guid: e11e939ddee8146e1976384f79284b41
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 350858
packageName: Rive
packageVersion: 0.4.2
assetPath: Packages/app.rive.rive-unity/Editor/Rive.Editor.asmdef
uploadId: 896810

View File

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

View File

@@ -0,0 +1,60 @@
// Editor-only shader for Rive asset preview in the Unity Inspector.
// This is a simple pass-through shader used ONLY for live preview in Linear color space projects in AssetEditor.cs.
//
// Why pass-through?
// - Rive outputs gamma into the RenderTexture
// - EditorGUI.DrawPreviewTexture expects sRGB input for correct display
// - We just pass the values through unchanged to avoid color conversion issues
//
// Note: Static preview uses a different path (Rive/UI/Default decode material + ReadPixels) so this shader is not used there.
Shader "Hidden/Rive/Editor/SRGBEncodePreview"
{
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Overlay" }
Pass
{
ZWrite Off
Cull Off
ZTest Always
Blend One Zero
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
v2f vert(appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
sampler2D _MainTex;
float4 frag(v2f i) : SV_Target
{
// Simple pass-through: Rive RenderTexture contains gamma values,
// return them unchanged for correct display in EditorGUI preview
float4 c = tex2D(_MainTex, i.uv);
return c;
}
ENDHLSL
}
}
}

View File

@@ -0,0 +1,16 @@
fileFormatVersion: 2
guid: c21beb37ff9a947549f53fb49764de5a
ShaderImporter:
externalObjects: {}
defaultTextures: []
nonModifiableTextures: []
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 350858
packageName: Rive
packageVersion: 0.4.2
assetPath: Packages/app.rive.rive-unity/Editor/Shaders/SRGBEncodePreview.shader
uploadId: 896810

View File

@@ -0,0 +1,209 @@
#if UNITY_EDITOR && UNITY_WEBGL
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using System.Collections.Generic;
using System.Linq;
namespace Rive.EditorTools
{
/// Handles WebGL native plugin selection based on Unity version.
/// Different Unity versions require different Emscripten-compiled libraries:
/// - Unity 2022.x and earlier use Emscripten 3.1.8
/// - Unity 2023.x (Unity 6) uses Emscripten 3.1.38
/// If we don't match the emscripten library Unity uses, the build will fail with an error like:
/// - Building Library\Bee\artifacts\WebGL\build\debug_WebGL_wasm\build.js failed with output:
/// - wasm-ld: error: Library/PackageCache/app.rive.rive-unity/Runtime/Libraries/WebGL/librive_wasm.a(artboard.o): undefined symbol: std::__2::__vector_base_common<true>::__throw_length_error() const
// - emcc: error: 'C:/6000.0.26f1/Editor/Data/PlaybackEngines/WebGLSupport/BuildTools/Emscripten/llvm\wasm-ld.exe @C:\Users\AppData\Local\Temp\emscripten_7f06ey06.rsp.utf-8' failed (returned 1)
///
/// The ideal way to do this would've been to use BuildUtilities.RegisterShouldIncludeInBuildCallback, but that is only called for managed plugins and not native plugins: https://docs.unity3d.com/ScriptReference/PackageManager.BuildUtilities.RegisterShouldIncludeInBuildCallback.html
/// The other ideal way would've been to use `Define Constraints`, but that also doesn't work for native plugins: https://discussions.unity.com/t/define-constraints-are-not-filtering-plugins-pluginimporter-defineconstraints-also-has-no-effect/873361/5
///
/// This preprocessor ensures the correct library is included during WebGL builds by temporarily copying the appropriate libraries to the project's Plugin folder during the build
internal class WebGLBuildPreprocessor : IPreprocessBuildWithReport, IPostprocessBuildWithReport
{
private const string PACKAGE_NAME = PackageInfo.PACKAGE_NAME;
private const string TEMP_PLUGINS_PATH = "Assets/Plugins/WebGL/Rive";
private const string CREATED_FOLDERS_PREF = "RiveCreatedPluginFolders";
public int callbackOrder => 0;
private static BuildReport currentBuildReport;
// We use this to cleanup the plugin files in case of build failure
// This is necessary because the IPostprocessBuildWithReport callback is not called when the build fails, only when it succeeds
private static void OnEditorUpdate()
{
if (currentBuildReport != null && (currentBuildReport.summary.result == BuildResult.Failed || currentBuildReport.summary.result == BuildResult.Cancelled))
{
// Unsubscribe first to prevent any potential multiple calls
EditorApplication.update -= OnEditorUpdate;
CleanupPluginFiles();
currentBuildReport = null;
}
}
private void TrackCreatedFolder(string path)
{
var createdFolders = new HashSet<string>(
SessionState.GetString(CREATED_FOLDERS_PREF, "").Split(
new[] { '|' }, System.StringSplitOptions.RemoveEmptyEntries)
);
createdFolders.Add(path);
SessionState.SetString(CREATED_FOLDERS_PREF, string.Join("|", createdFolders));
}
private static bool WasCreatedByUs(string path)
{
var createdFolders = SessionState.GetString(CREATED_FOLDERS_PREF, "").Split(
new[] { '|' }, System.StringSplitOptions.RemoveEmptyEntries
);
return System.Array.IndexOf(createdFolders, path) != -1;
}
private static void ClearFolderTracking(string path)
{
var createdFolders = new HashSet<string>(
SessionState.GetString(CREATED_FOLDERS_PREF, "").Split(
new[] { '|' }, System.StringSplitOptions.RemoveEmptyEntries)
);
createdFolders.Remove(path);
SessionState.SetString(CREATED_FOLDERS_PREF, string.Join("|", createdFolders));
}
private static void CleanupBuildPrefs()
{
SessionState.EraseString(CREATED_FOLDERS_PREF);
}
public void OnPreprocessBuild(BuildReport report)
{
if (report.summary.platform != BuildTarget.WebGL)
return;
// Store the build report so we can cleanup the plugin files in case of build failure
currentBuildReport = report;
EditorApplication.update += OnEditorUpdate;
// Clear any leftover prefs from previous builds that might have failed
CleanupBuildPrefs();
bool isUnity6OrNewer = UnityEngine.Application.unityVersion.StartsWith("6000") ||
UnityEngine.Application.unityVersion.StartsWith("2023");
string emscriptenVersion = isUnity6OrNewer ? "3.1.38" : "3.1.8";
string sourcePath = System.IO.Path.Combine("Packages", PACKAGE_NAME, "Runtime/Libraries/WebGL", $"emscripten_{emscriptenVersion}");
if (!System.IO.Directory.Exists(sourcePath))
{
throw new BuildFailedException($"Rive: Could not find WebGL libraries at {sourcePath}");
}
// Create and track directories we need so we can clean them up later
string[] folders = { "Assets/Plugins", "Assets/Plugins/WebGL", TEMP_PLUGINS_PATH };
foreach (string folder in folders)
{
if (!AssetDatabase.IsValidFolder(folder))
{
System.IO.Directory.CreateDirectory(folder);
TrackCreatedFolder(folder);
}
}
// Copy all .a files and configure them for WebGL
foreach (string file in System.IO.Directory.GetFiles(sourcePath, "*.a"))
{
string fileName = System.IO.Path.GetFileName(file);
string destFile = System.IO.Path.Combine(TEMP_PLUGINS_PATH, fileName);
System.IO.File.Copy(file, destFile, true);
AssetDatabase.ImportAsset(destFile);
var importer = AssetImporter.GetAtPath(destFile) as PluginImporter;
if (importer != null)
{
importer.SetCompatibleWithAnyPlatform(false);
importer.SetCompatibleWithPlatform(BuildTarget.WebGL, true);
importer.SaveAndReimport();
}
}
AssetDatabase.Refresh();
}
private static bool IsDirectoryEmpty(string path)
{
return !AssetDatabase.FindAssets(string.Empty, new[] { path }).Any();
}
private static void DeleteAssetPath(string path)
{
if (AssetDatabase.DeleteAsset(path))
{
ClearFolderTracking(path);
}
}
public void OnPostprocessBuild(BuildReport report)
{
if (report.summary.platform != BuildTarget.WebGL)
return;
try
{
CleanupPluginFiles();
}
finally
{
// Unsubscribe from editor update since we're handling the cleanup here
EditorApplication.update -= OnEditorUpdate;
currentBuildReport = null;
}
}
private static void CleanupPluginFiles()
{
try
{
if (AssetDatabase.IsValidFolder(TEMP_PLUGINS_PATH))
{
DeleteAssetPath(TEMP_PLUGINS_PATH);
}
// Check and clean up parent directories if empty and created by us
string webglPath = "Assets/Plugins/WebGL";
if (AssetDatabase.IsValidFolder(webglPath) &&
IsDirectoryEmpty(webglPath) &&
WasCreatedByUs(webglPath))
{
DeleteAssetPath(webglPath);
string pluginsPath = "Assets/Plugins";
if (AssetDatabase.IsValidFolder(pluginsPath) &&
IsDirectoryEmpty(pluginsPath) &&
WasCreatedByUs(pluginsPath))
{
DeleteAssetPath(pluginsPath);
}
}
AssetDatabase.Refresh();
}
finally
{
// Cleanup prefs to avoid stale data on next build
CleanupBuildPrefs();
}
}
}
}
#endif

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 34218ff81ba79467081fa78310f8e39d
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/WebGLBuildPreprocessor.cs
uploadId: 896810

View File

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

View File

@@ -0,0 +1,225 @@
using System;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace Rive.EditorTools
{
/// <summary>
/// Shows a welcome popup when the Unity Editor opens (once per session) unless the user opts out.
/// Also available through the Rive menu for later reference.
/// </summary>
internal class RiveQuickStartWindow : EditorWindow
{
private const string ShowOnStartKey = "Rive.Editor.Welcome.ShowOnStart";
private const string SessionShownKey = "Rive.Editor.Welcome.ShownThisSession";
private string _version;
private static void ShowFromMenu()
{
CreateWindow(string.Empty).ShowUtility();
}
[MenuItem("Window/Rive/Quick Start", priority = 1000)]
private static void ShowFromToolsMenu()
{
ShowFromMenu();
}
[InitializeOnLoadMethod]
private static void ShowOnEditorOpen()
{
if (!IsShowOnStart())
{
return;
}
if (SessionState.GetBool(SessionShownKey, false))
{
return;
}
SessionState.SetBool(SessionShownKey, true);
// Delay to ensure the editor UI is fully initialized before showing.
EditorApplication.delayCall += () =>
{
var window = CreateWindow(string.Empty);
window.ShowUtility();
window.Focus();
};
}
internal static bool IsShowOnStart()
{
return EditorPrefs.GetBool(ShowOnStartKey, true);
}
private static void SetShowOnStart(bool show)
{
EditorPrefs.SetBool(ShowOnStartKey, show);
}
private static RiveQuickStartWindow CreateWindow(string version)
{
var window = CreateInstance<RiveQuickStartWindow>();
window._version = version;
window.titleContent = new GUIContent("Rive for Unity");
window.minSize = new Vector2(420, 520);
return window;
}
public void CreateGUI()
{
BuildUI();
}
private void BuildUI()
{
var root = rootVisualElement;
root.Clear();
root.style.flexDirection = FlexDirection.Column;
root.style.paddingLeft = 16;
root.style.paddingRight = 16;
root.style.paddingTop = 12;
root.style.paddingBottom = 12;
var scroll = new ScrollView { verticalScrollerVisibility = ScrollerVisibility.Auto };
scroll.style.flexGrow = 1;
scroll.style.marginTop = 6;
root.Add(scroll);
var logo = AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/app.rive.rive-unity/Editor/Images/welcome-banner.png");
if (logo != null)
{
var logoContainer = new VisualElement();
logoContainer.style.paddingTop = 0;
logoContainer.style.paddingBottom = 0;
logoContainer.style.paddingLeft = 0;
logoContainer.style.paddingRight = 0;
logoContainer.style.alignItems = Align.Center;
logoContainer.style.marginBottom = 18;
logoContainer.style.maxHeight = 200;
var logoImage = new Image
{
image = logo,
scaleMode = ScaleMode.ScaleAndCrop
};
logoImage.style.alignSelf = Align.Center;
logoImage.style.borderTopLeftRadius = 7;
logoImage.style.borderTopRightRadius = 7;
logoImage.style.borderBottomLeftRadius = 7;
logoImage.style.borderBottomRightRadius = 7;
logoImage.style.overflow = Overflow.Hidden;
logoContainer.Add(logoImage);
scroll.Add(logoContainer);
}
var subtitle = new Label("Rive is a new way to build menus, HUDs, and 2D graphics for games, with rich interactivity and state-driven animation.");
subtitle.style.whiteSpace = WhiteSpace.Normal;
subtitle.style.marginBottom = 12;
scroll.Add(subtitle);
scroll.Add(CreateSection(
null,
("Getting Started Guide", InspectorDocLinks.UnityGettingStarted, "Open the getting started guide for the Unity runtime"),
("Website", InspectorDocLinks.RiveWebsite, "Open the Rive website"),
("Support", InspectorDocLinks.RiveUnitySupport, "Open the issue tracker for the Rive Unity runtime")
));
var footer = new VisualElement();
footer.style.flexShrink = 0;
footer.style.marginTop = 8;
footer.style.paddingTop = 8;
footer.style.borderTopWidth = 1;
footer.style.borderTopColor = new UnityEngine.Color(0f, 0f, 0f, 0.1f);
root.Add(footer);
var autoShowToggle = new Toggle("Show this window at startup")
{
value = IsShowOnStart()
};
autoShowToggle.RegisterValueChangedCallback(evt =>
{
SetShowOnStart(evt.newValue);
});
autoShowToggle.style.marginBottom = 8;
autoShowToggle.tooltip = "If enabled, this quick-start window will open when the editor starts (once per session).";
footer.Add(autoShowToggle);
var note = new Label("Reopen this window later via Window > Rive > Quick Start.");
note.style.color = new UnityEngine.Color(0.45f, 0.45f, 0.45f);
note.style.marginBottom = 4;
note.style.marginLeft = 3;
note.style.alignSelf = Align.FlexStart;
footer.Add(note);
}
private VisualElement CreateSection(string title, params (string label, string url, string tooltip)[] links)
{
var container = new VisualElement();
container.style.marginTop = 8;
if (!String.IsNullOrEmpty(title))
{
var header = new Label(title);
header.style.unityFontStyleAndWeight = FontStyle.Bold;
header.style.fontSize = 13;
header.style.marginBottom = 4;
container.Add(header);
}
foreach (var (label, url, tooltip) in links)
{
if (string.IsNullOrEmpty(url))
{
var suggestion = new Label($"• {label}");
suggestion.style.color = new UnityEngine.Color(0.55f, 0.55f, 0.55f);
suggestion.style.marginBottom = 4;
container.Add(suggestion);
}
else
{
var btn = CreateLinkButton(label, url, tooltip);
btn.style.marginBottom = 4;
container.Add(btn);
}
}
return container;
}
private Button CreateLinkButton(string text, string url, string tooltip = "")
{
var button = new Button();
button.text = text;
button.style.height = 28;
button.style.justifyContent = Justify.FlexStart;
button.style.paddingLeft = 10;
if (!string.IsNullOrEmpty(tooltip))
{
button.tooltip = tooltip;
}
if (string.IsNullOrEmpty(url))
{
button.SetEnabled(false);
}
else
{
button.clicked += () => Application.OpenURL(url);
}
return button;
}
}
}

View File

@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 3fa3b72758f07491e9cc011de8171f3d
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/Windows/RiveQuickStartWindow.cs
uploadId: 896810