using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Rendering; namespace UnityEditor.Rendering { internal static class RemoveComponentUtils { public static IEnumerable ComponentDependencies(Component component) => component.gameObject .GetComponents() .Where(c => c != component && c.GetType() .GetCustomAttributes(typeof(RequireComponent), true) .Count(att => att is RequireComponent rc && (rc.m_Type0 == component.GetType() || rc.m_Type1 == component.GetType() || rc.m_Type2 == component.GetType())) > 0); public static bool CanRemoveComponent(Component component, IEnumerable dependencies) { if (dependencies.Count() == 0) return true; Component firstDependency = dependencies.First(); string error = $"Can't remove {component.GetType().Name} because {firstDependency.GetType().Name} depends on it."; EditorUtility.DisplayDialog("Can't remove component", error, "Ok"); return false; } } /// /// Helper methods for overriding contextual menus /// static class ContextualMenuDispatcher { [MenuItem("CONTEXT/ReflectionProbe/Remove Component")] static void RemoveReflectionProbeComponent(MenuCommand command) { RemoveComponent(command); } [MenuItem("CONTEXT/Light/Remove Component")] static void RemoveLightComponent(MenuCommand command) { RemoveComponent(command); } [MenuItem("CONTEXT/Camera/Remove Component")] static void RemoveCameraComponent(MenuCommand command) { RemoveComponent(command); } [InitializeOnLoadMethod] static void RegisterAdditionalDataMenus() { foreach (var additionalData in TypeCache.GetTypesDerivedFrom()) { if (additionalData.GetCustomAttributes(typeof(RequireComponent), true).FirstOrDefault() is RequireComponent rc) { string types = rc.m_Type0.Name; if (rc.m_Type1 != null) types += $", {rc.m_Type1.Name}"; if (rc.m_Type2 != null) types += $", {rc.m_Type2.Name}"; MenuManager.AddMenuItem($"CONTEXT/{additionalData.Name}/Remove Component", string.Empty, false, 0, () => EditorUtility.DisplayDialog($"Remove {additionalData.Name} is blocked", $"You can not delete this component, you will have to remove the {types}.", "OK"), () => true); } } } static void RemoveComponent(MenuCommand command) where T : Component { T comp = command.context as T; if (!DispatchRemoveComponent(comp)) { //preserve built-in behavior if (RemoveComponentUtils.CanRemoveComponent(comp, RemoveComponentUtils.ComponentDependencies(comp))) Undo.DestroyObjectImmediate(command.context); } } static bool DispatchRemoveComponent(T component) where T : Component { try { var instance = new RemoveAdditionalDataContextualMenu(); instance.RemoveComponent(component, RemoveComponentUtils.ComponentDependencies(component)); return true; } catch { return false; } } } /// /// Interface that should be used with [ScriptableRenderPipelineExtension(type))] attribute to dispatch ContextualMenu calls on the different SRPs /// /// This must be a component that require AdditionalData in your SRP [Obsolete("The menu items are handled automatically for components with the AdditionalComponentData attribute", false)] public interface IRemoveAdditionalDataContextualMenu where T : Component { /// /// Remove the given component /// /// The component to remove /// Dependencies. void RemoveComponent(T component, IEnumerable dependencies); } internal class RemoveAdditionalDataContextualMenu where T : Component { /// /// Remove the given component /// /// The component to remove /// Dependencies. public void RemoveComponent(T component, IEnumerable dependencies) { var additionalDatas = dependencies .Where(c => c != component && typeof(IAdditionalData).IsAssignableFrom(c.GetType())) .ToList(); if (!RemoveComponentUtils.CanRemoveComponent(component, dependencies.Where(c => !additionalDatas.Contains(c)))) return; var isAssetEditing = EditorUtility.IsPersistent(component); try { if (isAssetEditing) { AssetDatabase.StartAssetEditing(); } Undo.SetCurrentGroupName($"Remove {typeof(T)} additional data components"); // The components with RequireComponent(typeof(T)) also contain the AdditionalData attribute, proceed with the remove foreach (var additionalDataComponent in additionalDatas) { if (additionalDataComponent != null) { Undo.DestroyObjectImmediate(additionalDataComponent); } } Undo.DestroyObjectImmediate(component); } finally { if (isAssetEditing) { AssetDatabase.StopAssetEditing(); } } } } }