using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using UnityEngine.UIElements;
using UnityEditor.UIElements;
namespace Unity.Cinemachine.Editor
{
[CustomEditor(typeof(CinemachineBlenderSettings))]
class CinemachineBlenderSettingsEditor : UnityEditor.Editor
{
CinemachineBlenderSettings Target => target as CinemachineBlenderSettings;
///
/// Called when building the Camera popup menus, to get the domain of possible
/// cameras. If no delegate is set, will find all top-level virtual cameras in the scene,
/// i.e. vcams that are not feeding a specific mixer.
///
public GetAllVirtualCamerasDelegate GetAllVirtualCameras = GetToplevelCameras;
public delegate void GetAllVirtualCamerasDelegate(List list);
// Get all top-level virtual cameras
static void GetToplevelCameras(List list)
{
var candidates = Resources.FindObjectsOfTypeAll();
for (var i = 0; i < candidates.Length; ++i)
if (candidates[i].ParentCamera == null)
list.Add(candidates[i]);
}
public override VisualElement CreateInspectorGUI()
{
var ux = new VisualElement();
var header = ux.AddChild(new VisualElement { style = { flexDirection = FlexDirection.Row, marginBottom = -2 } });
FormatElement(true,
header.AddChild(new Label("From")),
header.AddChild(new Label("To")),
header.AddChild(new Label("Blend")));
header.AddToClassList("unity-collection-view--with-border");
var list = ux.AddChild(new ListView()
{
reorderable = true,
reorderMode = ListViewReorderMode.Animated,
showAddRemoveFooter = true,
showBorder = true,
showBoundCollectionSize = false,
showFoldoutHeader = false,
style = { borderTopWidth = 0, marginLeft = 0 },
});
var elements = serializedObject.FindProperty(() => Target.CustomBlends);
list.BindProperty(elements);
// Gather the camera candidates
var availableCameras = new List();
Dictionary cameraIndexLookup = new();
list.TrackAnyUserActivity(() =>
{
var allCameras = new List();
GetAllVirtualCameras(allCameras);
availableCameras.Clear();
availableCameras.Add(string.Empty);
availableCameras.Add(CinemachineBlenderSettings.kBlendFromAnyCameraLabel);
for (int i = 0; i < allCameras.Count; ++i)
if (allCameras[i] != null && !availableCameras.Contains(allCameras[i].Name))
availableCameras.Add(allCameras[i].Name);
list.RefreshItems(); // rebuild the list
});
list.makeItem = () => new BindableElement { style = { flexDirection = FlexDirection.Row }};
list.bindItem = (row, index) =>
{
// Remove children - items get recycled
for (int i = row.childCount - 1; i >= 0; --i)
row.RemoveAt(i);
var def = new CinemachineBlenderSettings.CustomBlend();
var element = index < elements.arraySize ? elements.GetArrayElementAtIndex(index) : null;
if (!IsUnityNull(element))
{
var from = row.AddChild(CreateCameraPopup(element.FindPropertyRelative(() => def.From)));
var to = row.AddChild(CreateCameraPopup(element.FindPropertyRelative(() => def.To)));
var blend = row.AddChild(new PropertyField(element.FindPropertyRelative(() => def.Blend), ""));
FormatElement(false, from, to, blend);
((BindableElement)row).BindProperty(element); // bind must be done at the end
}
};
// Local function
static void FormatElement(bool isHeader, VisualElement e1, VisualElement e2, VisualElement e3)
{
e1.style.marginLeft = isHeader ? 2 * InspectorUtility.SingleLineHeight - 3: 0;
e1.style.flexBasis = InspectorUtility.SingleLineHeight;
e1.style.flexGrow = 3;
e2.style.flexBasis = 1;
e2.style.flexGrow = 3;
e3.style.flexBasis = 1;
e3.style.flexGrow = 2;
}
// Local function
VisualElement CreateCameraPopup(SerializedProperty p)
{
var row = new VisualElement { style = { flexDirection = FlexDirection.Row, flexGrow = 1 }};
var textField = row.AddChild(new TextField { isDelayed = true, style = { flexGrow = 1, flexBasis = 20 }});
textField.BindProperty(p);
if (availableCameras.FindIndex(x => x == p.stringValue) < 0)
row.AddChild(InspectorUtility.MiniHelpIcon("No in-scene camera matches this name"));
var popup = row.AddChild(InspectorUtility.MiniDropdownButton(
"Choose from currently-available cameras", new ContextualMenuManipulator((evt) =>
{
for (int i = 0; i < availableCameras.Count; ++i)
evt.menu.AppendAction(availableCameras[i],
(action) =>
{
p.stringValue = action.name;
p.serializedObject.ApplyModifiedProperties();
});
})));
popup.style.marginRight = 5;
return row;
}
// Local function
static bool IsUnityNull(object obj)
{
// Checks whether an object is null or Unity pseudo-null
// without having to cast to UnityEngine.Object manually
return obj == null || ((obj is UnityEngine.Object) && ((UnityEngine.Object)obj) == null);
}
return ux;
}
}
}