using System;
using UnityEditor;
using UnityEditor.EditorTools;
using UnityEditor.Overlays;
using UnityEditor.Toolbars;
using UnityEngine;
using UnityEngine.UIElements;
using System.Collections.Generic;
namespace Unity.Cinemachine.Editor
{
///
/// This is a generic Tool class for Cinemachine tools.
/// To create a new tool, inherit from CinemachineTool and implement GetIcon().
/// Your new tool will need to have the [EditorTool("tool-name", typeof(CinemachineVirtualCameraBase))] attribute.
///
/// A tool will be drawn iff it has been registered using CinemachineSceneToolUtility.RegisterTool.
/// This is generally done in the OnEnable function of the editor script of the cinemachine component
/// (CinemachineVirtualCamera, CinemachineComponentBase), for which the tool was meant.
/// To unregister, call CinemachineSceneToolUtility.UnregisterTool in the same script's OnDisable function.
///
/// To draw the handles related to the tool, you need to implement your drawing function and call it in the
/// editor script's OnSceneGUI function. An alternative for drawing handles is to override this function's
/// OnToolGUI or OnDrawHandles functions (see EditorTool or IDrawSelectedHandles docs for more information).
///
/// To check, if a tool has been enabled/disabled in the editor script, use CinemachineSceneToolUtility.IsToolActive.
///
public abstract class CinemachineTool : EditorTool, IDrawSelectedHandles
{
GUIContent m_IconContent;
/// Implement this to set your Tool's icon and tooltip.
/// A GUIContent with an icon set.
protected abstract GUIContent GetIcon();
/// This lets the editor find the icon of the tool.
public override GUIContent toolbarIcon
{
get
{
if (m_IconContent == null || m_State.refreshIcon)
{
m_IconContent = GetIcon();
m_State.refreshIcon = false;
}
return m_IconContent;
}
}
/// This is called when the Tool is selected in the editor.
public override void OnActivated()
{
base.OnActivated();
CinemachineSceneToolUtility.SetTool(true, GetType());
m_State.refreshIcon = true;
m_State.isSelected = true;
}
/// This is called when the Tool is deselected in the editor.
public override void OnWillBeDeactivated()
{
base.OnWillBeDeactivated();
CinemachineSceneToolUtility.SetTool(false, GetType());
m_State.refreshIcon = true;
m_State.isSelected = false;
}
/// This checks whether this tool should be displayed or not.
/// True, when this tool is to be drawn. False, otherwise.
public override bool IsAvailable() => CinemachineSceneToolUtility.IsToolRequired(GetType());
/// Implement IDrawSelectedHandles to draw gizmos for this tool even if it is not the active tool.
public void OnDrawHandles()
{
}
/// Get the path to the tool's icon asset.
/// The path to the icon asset.
private protected string GetIconPath()
{
m_State.refreshIcon = m_State.isProSkin != EditorGUIUtility.isProSkin;
m_State.isProSkin = EditorGUIUtility.isProSkin;
return $"{CinemachineCore.kPackageRoot}/Editor/EditorResources/Handles/" +
(m_State.isProSkin ?
(m_State.isSelected ? "Dark-Selected" : "Dark") :
(m_State.isSelected ? "Light-Selected" : "Light")) + "/";
}
struct ToolState
{
public bool isSelected;
public bool isProSkin;
public bool refreshIcon;
}
ToolState m_State = new() { refreshIcon = true };
}
[EditorTool("Field of View Tool", typeof(CinemachineVirtualCameraBase))]
class FoVTool : CinemachineTool
{
protected override GUIContent GetIcon() =>
new GUIContent
{
image = AssetDatabase.LoadAssetAtPath(GetIconPath() + "FOV.png"),
tooltip = "Field of View Tool",
};
}
[EditorTool("Far-Near Clip Tool", typeof(CinemachineVirtualCameraBase))]
class FarNearClipTool : CinemachineTool
{
protected override GUIContent GetIcon() =>
new GUIContent
{
image = AssetDatabase.LoadAssetAtPath(GetIconPath() + "FarNearClip.png"),
tooltip = "Far/Near Clip Tool",
};
}
[EditorTool("Follow Offset Tool", typeof(CinemachineVirtualCameraBase))]
class FollowOffsetTool : CinemachineTool
{
protected override GUIContent GetIcon() =>
new GUIContent
{
image = AssetDatabase.LoadAssetAtPath(GetIconPath() + "FollowOffset.png"),
tooltip = "Follow Offset Tool",
};
}
[EditorTool("Tracked Object Offset Tool", typeof(CinemachineVirtualCameraBase))]
class TrackedObjectOffsetTool : CinemachineTool
{
protected override GUIContent GetIcon() =>
new GUIContent
{
image = AssetDatabase.LoadAssetAtPath(GetIconPath() + "TrackedObjectOffset.png"),
tooltip = "Tracked Object Offset Tool",
};
}
#if false // We disable this tool window, because it has only one thing in it, which isn't so useful and is a bit confusing tbh
///
/// To add your custom tools (EditorToolbarElement) to the Cinemachine Tool Settings toolbar,
/// set CinemachineToolSettingsOverlay.customToolbarItems with your custom tools' IDs.
///
/// By default, CinemachineToolSettingsOverlay.customToolbarItems is null.
///
[Overlay(typeof(SceneView), "Cinemachine Tool Settings")]
[Icon(CinemachineCore.kPackageRoot + "/Editor/EditorResources/Icons/CmCamera@256.png")]
public class CinemachineToolSettingsOverlay : Overlay, ICreateToolbar
{
static readonly string[] k_CmToolbarItems = { OrbitalFollowOrbitSelection.id };
///
/// Override this method to return your visual element content.
/// By default, this draws the same visual element as the HorizontalToolbar
///
/// VisualElement for the Panel content.
public override VisualElement CreatePanelContent() => CreateContent(Layout.HorizontalToolbar);
/// Set this with your custom tools' IDs.
public static string[] customToolbarItems = null;
/// The list of tools that this toolbar is going to contain.
public IEnumerable toolbarElements
{
get
{
if (customToolbarItems != null)
{
var toolbarItems = new List(k_CmToolbarItems);
toolbarItems.AddRange(customToolbarItems);
return toolbarItems;
}
return k_CmToolbarItems;
}
}
}
[EditorToolbarElement(id, typeof(SceneView))]
class OrbitalFollowOrbitSelection : EditorToolbarDropdown
{
public const string id = "OrbitalFollowOrbitSelection/Dropdown";
static int s_SelectedOrbit;
Texture2D[] m_Icons;
public OrbitalFollowOrbitSelection()
{
tooltip = "OrbitalFollow Orbit Selection";
clicked += OrbitalFollowOrbitSelectionMenu;
EditorApplication.update += DisplayAndUpdateOrbitIfRequired;
m_Icons = new Texture2D[]
{
AssetDatabase.LoadAssetAtPath($"{CinemachineCore.kPackageRoot}/Editor/EditorResources/Handles/FreelookRigTop.png"),
AssetDatabase.LoadAssetAtPath($"{CinemachineCore.kPackageRoot}/Editor/EditorResources/Handles/FreelookRigMiddle.png"),
AssetDatabase.LoadAssetAtPath($"{CinemachineCore.kPackageRoot}/Editor/EditorResources/Handles/FreelookRigBottom.png"),
};
}
~OrbitalFollowOrbitSelection()
{
clicked -= OrbitalFollowOrbitSelectionMenu;
EditorApplication.update -= DisplayAndUpdateOrbitIfRequired;
}
readonly Type m_OrbitalFollowSelectionType = typeof(OrbitalFollowOrbitSelection);
void DisplayAndUpdateOrbitIfRequired()
{
var active = Selection.activeObject as GameObject;
if (active != null)
{
if (active.TryGetComponent(out var orbitalFollow)
&& CinemachineSceneToolUtility.IsToolRequired(m_OrbitalFollowSelectionType)
&& orbitalFollow.OrbitStyle == CinemachineOrbitalFollow.OrbitStyles.ThreeRing)
{
style.display = DisplayStyle.Flex; // display menu
var verticalAxis = orbitalFollow.VerticalAxis;
var centerToleranceRange = Mathf.Min(Mathf.Abs(verticalAxis.Range.x - verticalAxis.Center),
Mathf.Abs(verticalAxis.Range.y - verticalAxis.Center)) / 2f;
s_SelectedOrbit = (Math.Abs(verticalAxis.Value - verticalAxis.Center) < centerToleranceRange)
? 1
: (verticalAxis.Value > verticalAxis.Center ? 0 : 2);
text = CinemachineOrbitalFollowEditor.orbitNames[s_SelectedOrbit].text;
icon = m_Icons[s_SelectedOrbit];
return;
}
}
style.display = DisplayStyle.None; // hide menu
}
void OrbitalFollowOrbitSelectionMenu()
{
var menu = new GenericMenu();
for (var i = 0; i < CinemachineOrbitalFollowEditor.orbitNames.Length; ++i)
{
var rigIndex = i; // need to capture index for the lambda below
menu.AddItem(CinemachineOrbitalFollowEditor.orbitNames[i], false, () =>
{
s_SelectedOrbit = rigIndex;
var active = Selection.activeObject as GameObject;
if (active != null)
{
if (active.TryGetComponent(out var orbitalFollow))
{
orbitalFollow.VerticalAxis.Value = s_SelectedOrbit switch
{
0 => orbitalFollow.VerticalAxis.Range.y,
2 => orbitalFollow.VerticalAxis.Range.x,
_ => orbitalFollow.VerticalAxis.Center
};
}
}
});
}
menu.DropDown(worldBound);
}
}
#endif
}