Files
BABA_YAGA/Packages/app.rive.rive-unity/Editor/Components/MenuItems.cs

383 lines
16 KiB
C#
Raw Normal View History

2026-05-19 17:39:03 +07:00
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
}
}