// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using System; using System.Collections.Generic; using FlaxEngine; using Object = FlaxEngine.Object; namespace FlaxEditor.Actions { /// /// Implementation of used to break/restore connection for the collection of and objects. /// /// /// This action assumes that all objects in the given actor hierarchy are using the same prefab asset. /// /// [Serializable] sealed class BreakPrefabLinkAction : IUndoAction { private struct Item { public Guid ID; public Guid PrefabID; public Guid PrefabObjectID; public Item(SceneObject obj, List nestedPrefabLinks) { ID = obj.ID; PrefabID = obj.PrefabID; PrefabObjectID = obj.PrefabObjectID; if (nestedPrefabLinks != null) { // Check if this object comes from another nested prefab (to break link only from the top-level prefab) Item nested; nested.ID = ID; Editor.GetPrefabNestedObject(ref PrefabID, ref PrefabObjectID, out nested.PrefabID, out nested.PrefabObjectID); if (nested.PrefabID != Guid.Empty && nested.PrefabObjectID != Guid.Empty) nestedPrefabLinks.Add(nested); } } } [Serialize] private readonly bool _isBreak; [Serialize] private Guid _actorId; [Serialize] private List _items = new(); private BreakPrefabLinkAction(bool isBreak, Guid actorId) { _isBreak = isBreak; _actorId = actorId; } private BreakPrefabLinkAction(bool isBreak, Actor actor) { _isBreak = isBreak; _actorId = actor.ID; CollectIds(actor); } /// /// Creates a new undo action that in state for breaking prefab connection. /// /// The target actor. /// The action. public static BreakPrefabLinkAction Break(Actor actor) { if (actor == null) throw new ArgumentNullException(nameof(actor)); return new BreakPrefabLinkAction(true, actor.ID); } /// /// Creates a new undo action that in state for linked prefab connection. Action on perform will undo that. /// /// The target actor. /// The action. public static BreakPrefabLinkAction Linked(Actor actor) { if (actor == null) throw new ArgumentNullException(nameof(actor)); if (!actor.HasPrefabLink) throw new Exception("Cannot register missing prefab link."); return new BreakPrefabLinkAction(false, actor); } /// public string ActionString => _isBreak ? "Break prefab link" : "Link prefab"; /// public void Do() { if (_isBreak) DoBreak(); else DoLink(); } /// public void Undo() { if (_isBreak) DoLink(); else DoBreak(); } /// public void Dispose() { _items.Clear(); } private void DoLink() { var actor = Object.Find(ref _actorId); if (actor == null) throw new Exception("Cannot link prefab. Missing actor."); Link(_items); Refresh(actor); } private void Link(List items) { for (int i = 0; i < items.Count; i++) { var item = items[i]; var obj = Object.Find(ref item.ID); if (obj != null) SceneObject.Internal_LinkPrefab(Object.GetUnmanagedPtr(obj), ref item.PrefabID, ref item.PrefabObjectID); } } private void CollectIds(Actor actor, List nestedPrefabLinks = null) { _items.Add(new Item(actor, nestedPrefabLinks)); for (int i = 0; i < actor.ChildrenCount; i++) CollectIds(actor.GetChild(i), nestedPrefabLinks); for (int i = 0; i < actor.ScriptsCount; i++) _items.Add(new Item(actor.GetScript(i), nestedPrefabLinks)); } private void Refresh(Actor actor) { Editor.Instance.Scene.MarkSceneEdited(actor.Scene); Editor.Instance.Windows.PropertiesWin.Presenter.BuildLayout(); } private void DoBreak() { var actor = Object.Find(ref _actorId); if (actor == null) throw new Exception("Cannot break prefab link. Missing actor."); if (!actor.HasPrefabLink) throw new Exception("Cannot break missing prefab link."); // Cache 'prev' state and extract any nested prefab instances to remain _items.Clear(); var nestedPrefabLinks = new List(); CollectIds(actor, nestedPrefabLinks); // Break prefab linkage actor.BreakPrefabLink(); // Restore prefab link for nested instances Link(nestedPrefabLinks); Refresh(actor); } } }