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