#if UNITY_2021_2_OR_NEWER using System; using UnityEditor; using UnityEditor.EditorTools; using UnityEditor.Overlays; using UnityEditor.Toolbars; using UnityEngine; using UnityEngine.UIElements; using System.Collections.Generic; namespace Cinemachine.Editor { #if UNITY_2022_1_OR_NEWER /// /// 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() { } private protected string GetIconPath() { m_State.refreshIcon = m_State.isProSkin != EditorGUIUtility.isProSkin; m_State.isProSkin = EditorGUIUtility.isProSkin; return ScriptableObjectUtility.CinemachineRealativeInstallPath + "/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 ToolState { 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("Packages/com.unity.cinemachine/Gizmos/cm_logo.png")] public class CinemachineToolSettingsOverlay : Overlay, ICreateToolbar { static readonly string[] k_CmToolbarItems = { FreelookRigSelection.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 conent. 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 FreelookRigSelection : EditorToolbarDropdown { public const string id = "FreelookRigSelection/Dropdown"; public static int SelectedRig; Texture2D[] m_Icons; public FreelookRigSelection() { tooltip = "Freelook Rig Selection"; clicked += FreelookRigSelectionMenu; EditorApplication.update += ShadowSelectedRigName; EditorApplication.update += DisplayIfRequired; m_Icons = new Texture2D[] { AssetDatabase.LoadAssetAtPath(ScriptableObjectUtility.CinemachineRealativeInstallPath + "/Editor/EditorResources/Handles/FreelookRigTop.png"), AssetDatabase.LoadAssetAtPath(ScriptableObjectUtility.CinemachineRealativeInstallPath + "/Editor/EditorResources/Handles/FreelookRigMiddle.png"), AssetDatabase.LoadAssetAtPath(ScriptableObjectUtility.CinemachineRealativeInstallPath + "/Editor/EditorResources/Handles/FreelookRigBottom.png"), }; } ~FreelookRigSelection() { clicked -= FreelookRigSelectionMenu; EditorApplication.update -= ShadowSelectedRigName; EditorApplication.update -= DisplayIfRequired; } Type m_FreelookRigSelectionType = typeof(FreelookRigSelection); void DisplayIfRequired() => style.display = CinemachineSceneToolUtility.IsToolRequired(m_FreelookRigSelectionType) ? DisplayStyle.Flex : DisplayStyle.None; // text is currently only visibly in Panel mode due to this bug: https://jira.unity3d.com/browse/STO-2278 void ShadowSelectedRigName() { var index = Mathf.Clamp(SelectedRig, 0, CinemachineFreeLookEditor.RigNames.Length - 1); text = CinemachineFreeLookEditor.RigNames[index].text; icon = m_Icons[index]; } void FreelookRigSelectionMenu() { var menu = new GenericMenu(); for (var i = 0; i < CinemachineFreeLookEditor.RigNames.Length; ++i) { var rigIndex = i; // vital to capture the index here for the lambda below menu.AddItem(CinemachineFreeLookEditor.RigNames[i], false, () => { SelectedRig = rigIndex; var active = Selection.activeObject as GameObject; if (active != null) { var freelook = active.GetComponent(); if (freelook != null) CinemachineFreeLookEditor.SetSelectedRig(freelook, rigIndex); } }); } menu.DropDown(worldBound); } } #endif #else /// /// To display a CinemachineExclusiveEditorToolbarToggle in the Cinemachine Toolbar. /// [Overlay(typeof(SceneView), "Cinemachine")] [Icon("Packages/com.unity.cinemachine/Gizmos/cm_logo.png")] class CinemachineToolbarOverlay : ToolbarOverlay { public CinemachineToolbarOverlay() : base( //FreelookRigSelection.id, FoVTool.id, FarNearClipTool.id, FollowOffsetTool.id, TrackedObjectOffsetTool.id ) { CinemachineSceneToolUtility.RegisterToolbarIsDisplayedHandler(() => displayed); CinemachineSceneToolUtility.RegisterToolbarDisplayHandler(v => { if (displayed == v) { return false; } displayed = v; return true; }); } } /// /// Creates a toggle tool on the Cinemachine toolbar that is exclusive with other /// CinemachineExclusiveEditorToolbarToggles. Meaning, that maximum one /// CinemachineExclusiveEditorToolbarToggle can be active at any time. /// abstract class CinemachineExclusiveEditorToolbarToggle : EditorToolbarToggle { protected CinemachineExclusiveEditorToolbarToggle() { var type = GetType(); this.RegisterValueChangedCallback( v => CinemachineSceneToolUtility.SetTool(v.newValue, type)); CinemachineSceneToolUtility.RegisterExclusiveToolHandlers(type, isOn => value = isOn, display => style.display = display ? DisplayStyle.Flex : DisplayStyle.None); } } [EditorToolbarElement(id, typeof(SceneView))] class FoVTool : CinemachineExclusiveEditorToolbarToggle { public const string id = "FoVTool/Toggle"; public FoVTool() { icon = AssetDatabase.LoadAssetAtPath(ScriptableObjectUtility.CinemachineRealativeInstallPath + "/Editor/EditorResources/Handles/Dark/FOV.png"); tooltip = "Field of View Tool"; } } [EditorToolbarElement(id, typeof(SceneView))] class FarNearClipTool : CinemachineExclusiveEditorToolbarToggle { public const string id = "FarNearClipTool/Toggle"; public FarNearClipTool() { icon = AssetDatabase.LoadAssetAtPath(ScriptableObjectUtility.CinemachineRealativeInstallPath + "/Editor/EditorResources/Handles/Dark/FarNearClip.png"); tooltip = "Far/Near Clip Tool"; } } [EditorToolbarElement(id, typeof(SceneView))] class FollowOffsetTool : CinemachineExclusiveEditorToolbarToggle { public const string id = "FollowOffsetTool/Toggle"; public FollowOffsetTool() { icon = AssetDatabase.LoadAssetAtPath(ScriptableObjectUtility.CinemachineRealativeInstallPath + "/Editor/EditorResources/Handles/Dark/FollowOffset.png"); tooltip = "Follow Offset Tool"; } } [EditorToolbarElement(id, typeof(SceneView))] class TrackedObjectOffsetTool : CinemachineExclusiveEditorToolbarToggle { public const string id = "TrackedObjectOffsetTool/Toggle"; public TrackedObjectOffsetTool() { icon = AssetDatabase.LoadAssetAtPath(ScriptableObjectUtility.CinemachineRealativeInstallPath + "/Editor/EditorResources/Handles/Dark/TrackedObjectOffset.png"); tooltip = "Tracked Object Offset Tool"; } } /// /// Creates a toggle tool on the Cinemachine toolbar. /// abstract class CinemachineEditorToolbarToggle : EditorToolbarToggle { protected CinemachineEditorToolbarToggle() { CinemachineSceneToolUtility.RegisterToolHandlers(GetType(), isOn => value = isOn, display => style.display = display ? DisplayStyle.Flex : DisplayStyle.None); } } #if false [EditorToolbarElement(id, typeof(SceneView))] class FreelookRigSelection : EditorToolbarDropdown { public const string id = "FreelookRigSelection/Dropdown"; public static int SelectedRig; Texture2D[] m_Icons; public FreelookRigSelection() { tooltip = "Freelook Rig Selection"; clicked += FreelookRigSelectionMenu; CinemachineSceneToolUtility.RegisterToolHandlers(GetType(), isOn => {}, display => style.display = display ? DisplayStyle.Flex : DisplayStyle.None); EditorApplication.update += ShadowSelectedRigName; m_Icons = new Texture2D[] { AssetDatabase.LoadAssetAtPath(ScriptableObjectUtility.CinemachineRealativeInstallPath + "/Editor/EditorResources/Handles/FreelookRigTop.png"), AssetDatabase.LoadAssetAtPath(ScriptableObjectUtility.CinemachineRealativeInstallPath + "/Editor/EditorResources/Handles/FreelookRigMiddle.png"), AssetDatabase.LoadAssetAtPath(ScriptableObjectUtility.CinemachineRealativeInstallPath + "/Editor/EditorResources/Handles/FreelookRigBottom.png"), }; } ~FreelookRigSelection() { clicked -= FreelookRigSelectionMenu; EditorApplication.update -= ShadowSelectedRigName; } void ShadowSelectedRigName() { var index = Mathf.Clamp(SelectedRig, 0, CinemachineFreeLookEditor.RigNames.Length - 1); icon = m_Icons[index]; text = CinemachineFreeLookEditor.RigNames[index].text; } void FreelookRigSelectionMenu() { var menu = new GenericMenu(); for (var i = 0; i < CinemachineFreeLookEditor.RigNames.Length; ++i) { var rigIndex = i; // vital to capture the index here for the lambda below menu.AddItem(CinemachineFreeLookEditor.RigNames[i], false, () => { SelectedRig = rigIndex; var active = Selection.activeObject as GameObject; if (active != null) { var freelook = active.GetComponent(); if (freelook != null) CinemachineFreeLookEditor.SetSelectedRig(freelook, rigIndex); } }); } menu.DropDown(worldBound); } } #endif #endif } #endif