using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
using Object = UnityEngine.Object;
namespace Unity.Cinemachine.Editor
{
///
/// Helpers for drawing CmComponentBase or CmExtension inspectors.
///
static class CmPipelineComponentInspectorUtility
{
const string k_NeedTarget = "A Tracking Target is required in the CinemachineCamera.";
const string k_NeedLookAt = "A LookAt Target is required in the CinemachineCamera.";
const string k_NeedGroupTarget = "The Tracking Target in the CinemachineCamera must be a Target Group.";
const string k_NeedGroupLookAt = "The LookAt Target in the CinemachineCamera must be a Target Group.";
const string k_NeedCamera = "This component is intended to be used only with a CinemachineCamera.";
const string k_AddCamera = "Add CinemachineCamera";
const string k_DuplicateComponent = "This component is redundant and will be ignored.";
///
/// Add help box for CinemachineComponentBase or CinemachineExtension editors,
/// prompting to solve a missing CinemachineCamera component or a missing tracking target
///
public static void AddMissingCmCameraHelpBox(this UnityEditor.Editor editor, VisualElement ux)
{
// Look for a RequiredTargetAttribute
var requiredTargets = RequiredTargetAttribute.RequiredTargets.None;
var a = editor.target.GetType().GetCustomAttribute();
if (a != null)
requiredTargets = a.RequiredTarget;
var targets = editor.targets;
var noCameraHelp = ux.AddChild(InspectorUtility.HelpBoxWithButton(
k_NeedCamera, HelpBoxMessageType.Warning,
k_AddCamera, () => AddCmCameraToTargets(targets)));
var targetText = string.Empty;
var lookAtText = string.Empty;
switch (requiredTargets)
{
case RequiredTargetAttribute.RequiredTargets.Tracking: targetText = k_NeedTarget; break;
case RequiredTargetAttribute.RequiredTargets.LookAt: targetText = k_NeedTarget; lookAtText = k_NeedLookAt; break;
case RequiredTargetAttribute.RequiredTargets.GroupLookAt: targetText = k_NeedGroupTarget; lookAtText = k_NeedGroupLookAt; break;
}
var noTargetHelp = targetText.Length > 0 ? ux.AddChild(new HelpBox(targetText, HelpBoxMessageType.Warning)) : null;
var noLookAtHelp = lookAtText.Length > 0 ? ux.AddChild(new HelpBox(lookAtText, HelpBoxMessageType.Warning)) : null;
var duplicateHelp = ux.AddChild(new HelpBox(k_DuplicateComponent, HelpBoxMessageType.Error));
// Update state
ux.TrackAnyUserActivity(() =>
{
if (editor == null || editor.target == null)
return; // target was deleted
var noCamera = false;
var noTarget = false;
var noLookAtTarget = false;
var isDuplicate = false;
for (int i = 0; i < targets.Length && !noCamera; ++i)
{
if (targets[i] is CinemachineComponentBase c)
{
var vcam = c.VirtualCamera;
noCamera |= vcam == null || vcam is CinemachineCameraManagerBase;
if (vcam != null)
vcam.UpdateTargetCache();
switch (requiredTargets)
{
case RequiredTargetAttribute.RequiredTargets.Tracking: noTarget |= c.FollowTarget == null; break;
case RequiredTargetAttribute.RequiredTargets.LookAt: noLookAtTarget |= c.LookAtTarget == null; break;
case RequiredTargetAttribute.RequiredTargets.GroupLookAt:
noLookAtTarget |= c.LookAtTargetAsGroup == null || !c.LookAtTargetAsGroup.IsValid; break;
}
if (vcam != null && vcam.GetCinemachineComponent(c.Stage) != c)
isDuplicate = true;
}
else if (targets[i] is CinemachineExtension x)
{
var vcam = x.ComponentOwner;
noCamera |= vcam == null;
if (vcam != null)
{
vcam.UpdateTargetCache();
switch (requiredTargets)
{
case RequiredTargetAttribute.RequiredTargets.Tracking: noTarget |= vcam.Follow == null; break;
case RequiredTargetAttribute.RequiredTargets.LookAt: noLookAtTarget |= vcam.LookAt == null; break;
case RequiredTargetAttribute.RequiredTargets.GroupLookAt:
noLookAtTarget |= vcam.LookAtTargetAsGroup == null || !vcam.LookAtTargetAsGroup.IsValid; break;
}
}
}
else if (targets[i] is MonoBehaviour b)
noCamera |= !b.TryGetComponent(out _);
}
noCameraHelp?.SetVisible(noCamera);
noTargetHelp?.SetVisible(noTarget && !noCamera);
noLookAtHelp?.SetVisible(noLookAtTarget && !noCamera);
duplicateHelp?.SetVisible(isDuplicate);
});
}
static void AddCmCameraToTargets(Object[] targets)
{
for (int i = 0; i < targets.Length; ++i)
{
if (targets[i] is CinemachineComponentBase c)
{
if (c.VirtualCamera == null)
Undo.AddComponent(c.gameObject);
}
else if (targets[i] is CinemachineExtension x)
{
if (x != null && x.ComponentOwner == null)
Undo.AddComponent(x.gameObject).AddExtension(x);
}
else if (targets[i] is MonoBehaviour b)
{
if (!b.TryGetComponent(out _))
Undo.AddComponent(b.gameObject);
}
}
}
static List s_AllAxisControllerTypes;
public static void AddInputControllerHelp(
this UnityEditor.Editor editor, VisualElement ux, string text)
{
if (s_AllAxisControllerTypes == null)
{
var allTypes = ReflectionHelpers.GetTypesInAllDependentAssemblies(
(Type t) => typeof(IInputAxisController).IsAssignableFrom(t) && !t.IsAbstract
&& typeof(MonoBehaviour).IsAssignableFrom(t)
&& t.GetCustomAttribute() == null);
s_AllAxisControllerTypes = new();
var iter = allTypes.GetEnumerator();
while (iter.MoveNext())
s_AllAxisControllerTypes.Add(iter.Current);
}
ContextualMenuManipulator menu = null;
if (s_AllAxisControllerTypes.Count > 1)
{
menu = new ContextualMenuManipulator((evt) =>
{
for (int i = 0; i < s_AllAxisControllerTypes.Count; ++i)
{
var t = s_AllAxisControllerTypes[i];
evt.menu.AppendAction(ObjectNames.NicifyVariableName(t.Name), (action) => AddController(t));
}
});
}
var help = ux.AddChild(InspectorUtility.HelpBoxWithButton(
text, HelpBoxMessageType.Info, "Add Input Controller",
() =>
{
if (s_AllAxisControllerTypes.Count == 1)
AddController(s_AllAxisControllerTypes[0]);
},
menu));
ux.TrackAnyUserActivity(() =>
{
if (editor == null)
return; // target was deleted
var noHandler = false;
for (int i = 0; i < editor.targets.Length; ++i)
if (editor.targets[i] is IInputAxisResetSource src)
noHandler |= !src.HasResetHandler;
help.SetVisible(noHandler);
});
// Local fucntion
void AddController(Type controllerType)
{
Undo.SetCurrentGroupName("Add Input Controller");
for (int i = 0; i < editor.targets.Length; ++i)
{
if (editor.targets[i] is IInputAxisResetSource src && !src.HasResetHandler)
{
var t = editor.targets[i] as MonoBehaviour;
if (!t.TryGetComponent(out var c))
Undo.AddComponent(t.gameObject, controllerType);
else if (c is MonoBehaviour b && !b.enabled)
{
Undo.RecordObject(b, "enable controller");
b.enabled = true;
}
}
}
};
}
public static void OnGUI_DrawOnscreenTargetMarker(Vector3 worldPoint, Camera camera)
{
var c = camera.WorldToScreenPoint(worldPoint);
c.y = Screen.height - c.y;
if (c.z > 0)
{
var oldColor = GUI.color;
var r = new Rect(c, Vector2.zero).Inflated(Vector2.one * CinemachineComposerPrefs.TargetSize.Value);
GUI.color = new Color(0, 0, 0, CinemachineComposerPrefs.OverlayOpacity.Value);
GUI.DrawTexture(r.Inflated(new Vector2(1, 1)), Texture2D.whiteTexture, ScaleMode.StretchToFill);
var color = CinemachineComposerPrefs.TargetColour.Value;
GUI.color = color;
GUI.DrawTexture(r, Texture2D.whiteTexture, ScaleMode.StretchToFill);
GUI.color = oldColor;
}
}
public static void OnGUI_DrawOnscreenGroupSizeMarker(
Bounds groupBounds, Matrix4x4 cameraViewMatrix, Camera camera)
{
var c = groupBounds.center; c.z -= groupBounds.extents.z; groupBounds.center = c;
var e = groupBounds.extents; e.z = 0; groupBounds.extents = e;
c = camera.WorldToScreenPoint(cameraViewMatrix.MultiplyPoint3x4(c));
if (c.z < 0)
return;
e = camera.WorldToScreenPoint(cameraViewMatrix.MultiplyPoint3x4(groupBounds.center + e));
var groupSize = new Vector2(Mathf.Abs(e.x - c.x), Mathf.Abs(e.y - c.y));
var radius = Mathf.Max(groupSize.x, groupSize.y);
if (radius > CinemachineComposerPrefs.TargetSize.Value)
{
var oldColor = GUI.color;
var color = CinemachineComposerPrefs.TargetColour.Value;
color.a = Mathf.Lerp(1f, CinemachineComposerPrefs.OverlayOpacity.Value, (radius - 10f) / 100f);
GUI.color = color;
c.y = camera.pixelHeight - c.y;
var r = new Rect(c, Vector2.zero).Inflated(groupSize);
GUI.DrawTexture(r, GetTargetMarkerTex(), ScaleMode.StretchToFill);
GUI.color = oldColor;
}
}
static Texture2D s_TargetMarkerTex = null;
static Texture2D GetTargetMarkerTex()
{
if (s_TargetMarkerTex == null)
{
// Create a texture from scratch!
// Oh gawd there has to be a nicer way to do this
const int size = 128;
const float th = 1f;
const float radius = size / 2 - th;
var pix = new Color32[size * size];
var center = new Vector2(size-1, size-1) / 2;
for (int y = 0; y < size; ++y)
{
for (int x = 0; x < size; ++x)
{
float d = Vector2.Distance(new Vector2(x, y), center);
d = Mathf.Abs((d - radius) / th);
var a = Mathf.Clamp01(1 - d);
pix[y * size + x] = new Color(1, 1, 1, a);
}
}
s_TargetMarkerTex = new Texture2D(size, size);
s_TargetMarkerTex.SetPixels32(pix);
s_TargetMarkerTex.Apply();
}
return s_TargetMarkerTex;
}
#if !CINEMACHINE_NO_CM2_SUPPORT
/// IMGUI support - to be removed when IMGUI is gone
public static void IMGUI_DrawMissingCmCameraHelpBox(this UnityEditor.Editor editor)
{
// Look for a RequiredTargetAttribute
var requiredTargets = RequiredTargetAttribute.RequiredTargets.None;
var a = editor.target.GetType().GetCustomAttribute();
if (a != null)
requiredTargets = a.RequiredTarget;
bool noCamera = false;
bool noTarget = false;
var targets = editor.targets;
for (int i = 0; i < targets.Length && !noCamera; ++i)
{
if (targets[i] is CinemachineComponentBase c)
{
var vcam = c.VirtualCamera;
noCamera |= vcam == null || vcam is CinemachineCameraManagerBase;
if (vcam != null)
vcam.UpdateTargetCache();
switch (requiredTargets)
{
case RequiredTargetAttribute.RequiredTargets.Tracking: noTarget |= c.FollowTarget == null; break;
case RequiredTargetAttribute.RequiredTargets.LookAt: noTarget |= c.LookAtTarget == null; break;
case RequiredTargetAttribute.RequiredTargets.GroupLookAt:
noTarget |= c.LookAtTargetAsGroup == null || !c.LookAtTargetAsGroup.IsValid; break;
}
}
else if (targets[i] is CinemachineExtension x)
{
var vcam = x.ComponentOwner;
noCamera |= vcam == null;
if (vcam != null)
{
vcam.UpdateTargetCache();
switch (requiredTargets)
{
case RequiredTargetAttribute.RequiredTargets.Tracking: noTarget |= vcam.Follow == null; break;
case RequiredTargetAttribute.RequiredTargets.LookAt: noTarget |= vcam.LookAt == null; break;
case RequiredTargetAttribute.RequiredTargets.GroupLookAt:
noTarget |= vcam.LookAtTargetAsGroup == null || !vcam.LookAtTargetAsGroup.IsValid; break;
}
}
}
}
if (noCamera)
{
InspectorUtility.HelpBoxWithButton(
k_NeedCamera, MessageType.Warning,
new GUIContent(k_AddCamera), () => AddCmCameraToTargets(targets));
EditorGUILayout.Space();
}
else if (noTarget)
{
var text = string.Empty;
switch (requiredTargets)
{
case RequiredTargetAttribute.RequiredTargets.Tracking: text = k_NeedTarget; break;
case RequiredTargetAttribute.RequiredTargets.LookAt: text = k_NeedLookAt; break;
case RequiredTargetAttribute.RequiredTargets.GroupLookAt: text = k_NeedGroupLookAt; break;
}
if (text.Length > 0)
EditorGUILayout.HelpBox(text, MessageType.Warning);
EditorGUILayout.Space();
}
}
#endif
}
}