diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorEntityAPI.h b/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorEntityAPI.h index 3b5e5c9dc3..9ab732938c 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorEntityAPI.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/API/EditorEntityAPI.h @@ -8,14 +8,13 @@ #pragma once -#include #include #include +#include + namespace AzToolsFramework { - using EntityIdList = AZStd::vector; - /*! * EditorEntityAPI * Handles basic Entity operations diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/API/EntityCompositionNotificationBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/API/EntityCompositionNotificationBus.h index f44ccb2806..420f66e54d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/API/EntityCompositionNotificationBus.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/API/EntityCompositionNotificationBus.h @@ -8,20 +8,19 @@ #pragma once #include -#include -#include #include #include +#include +#include + +#include namespace AzToolsFramework { - using EntityIdList = AZStd::vector; - class EntityCompositionNotifications : public AZ::EBusTraits { public: - /*! * Notification that the specified entities are about to have their composition changed due to user interaction in the editor * diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/API/ToolsApplicationAPI.h b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ToolsApplicationAPI.h index bca68a6c0a..f84ff5f312 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/API/ToolsApplicationAPI.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ToolsApplicationAPI.h @@ -8,40 +8,40 @@ #pragma once #include +#include +#include #include #include #include #include -#include -#include -#include +#include #include #include -#include -#include -#include #include +#include +#include +#include + namespace AZ { class Entity; class Vector2; - class Entity; -} +} // namespace AZ -class QMenu; -class QWidget; +struct IEditor; class QApplication; class QDockWidget; class QMainWindow; -struct IEditor; +class QMenu; +class QWidget; namespace AzToolsFramework { struct ViewPaneOptions; - class PreemptiveUndoCache; class EntityPropertyEditor; + class PreemptiveUndoCache; namespace UndoSystem { @@ -54,10 +54,7 @@ namespace AzToolsFramework class AssetSelectionModel; } - using EntityIdList = AZStd::vector; - using EntityList = AZStd::vector; using ClassDataList = AZStd::vector; - using EntityIdSet = AZStd::unordered_set; //! Return true to accept this type of component. using ComponentFilter = AZStd::function; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityTransformBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityTransformBus.h index 17739b5098..78ebbb039f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityTransformBus.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EditorEntityTransformBus.h @@ -10,10 +10,10 @@ #include +#include + namespace AzToolsFramework { - using EntityIdList = AZStd::vector; - //! Notifications about entity transform changes from the editor. class EditorTransformChangeNotifications : public AZ::EBusTraits { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EntityTypes.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EntityTypes.h new file mode 100644 index 0000000000..826fe4417e --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/EntityTypes.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include + +namespace AzToolsFramework +{ + using EntityIdList = AZStd::vector; + using EntityIdSet = AZStd::unordered_set; + using EntityList = AZStd::vector; + +} // namespace AZ diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h index 2d3db0ea72..8506d32d5b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -97,7 +98,6 @@ namespace AzToolsFramework , private AzFramework::SliceEntityRequestBus::MultiHandler { public: - using EntityList = AzFramework::EntityList; using OnEntitiesAddedCallback = AzFramework::OnEntitiesAddedCallback; using OnEntitiesRemovedCallback = AzFramework::OnEntitiesRemovedCallback; using ValidateEntitiesCallback = AzFramework::ValidateEntitiesCallback; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/SliceEditorEntityOwnershipService.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/SliceEditorEntityOwnershipService.h index 1399b46066..4b39a0b263 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/SliceEditorEntityOwnershipService.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/SliceEditorEntityOwnershipService.h @@ -9,13 +9,13 @@ #pragma once #include +#include #include #include namespace AzToolsFramework { - using EntityIdSet = AZStd::unordered_set; using EntityIdToEntityIdMap = AZStd::unordered_map; class SliceEditorEntityOwnershipService diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/SliceEditorEntityOwnershipServiceBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/SliceEditorEntityOwnershipServiceBus.h index 4d7ea86b49..38f81ce6dc 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/SliceEditorEntityOwnershipServiceBus.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/SliceEditorEntityOwnershipServiceBus.h @@ -8,10 +8,11 @@ #pragma once -#include #include +#include #include #include +#include namespace AZ { @@ -20,9 +21,6 @@ namespace AZ namespace AzToolsFramework { - using EntityIdList = AZStd::vector; - using EntityList = AZStd::vector; - /** * Indicates how an entity was removed from its slice instance, so the said entity can be restored properly. */ diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h index 74d207af64..2a565830bd 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/FocusMode/FocusModeInterface.h @@ -8,17 +8,17 @@ #pragma once +#include #include #include -#include #include #include +#include + namespace AzToolsFramework { - using EntityIdList = AZStd::vector; - //! FocusModeInterface //! Interface to handle the Editor Focus Mode. class FocusModeInterface diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h index ebc75dc5cf..64c7cb3d2a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/Instance.h @@ -18,6 +18,7 @@ #include #include #include +#include #include namespace AZ @@ -62,7 +63,6 @@ namespace AzToolsFramework using AliasToInstanceMap = AZStd::unordered_map>; using AliasToEntityMap = AZStd::unordered_map>; - using EntityList = AZStd::vector; Instance(); explicit Instance(AZStd::unique_ptr containerEntity); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceEntityScrubber.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceEntityScrubber.cpp index ca0bb829ca..e62fc710fc 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceEntityScrubber.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceEntityScrubber.cpp @@ -12,7 +12,7 @@ namespace AzToolsFramework { namespace Prefab { - InstanceEntityScrubber::InstanceEntityScrubber(Instance::EntityList& entities) + InstanceEntityScrubber::InstanceEntityScrubber(EntityList& entities) : m_entities(entities) {} diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceEntityScrubber.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceEntityScrubber.h index 394261e902..4124811795 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceEntityScrubber.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceEntityScrubber.h @@ -10,6 +10,7 @@ #include #include +#include #include namespace AZ @@ -19,8 +20,6 @@ namespace AZ namespace AzToolsFramework { - using EntityList = AZStd::vector; - namespace Prefab { //! Collects the entities added during deserialization @@ -29,12 +28,12 @@ namespace AzToolsFramework public: AZ_TYPE_INFO(InstanceEntityScrubber, "{0BC12562-C240-48AD-89C6-EDF572C9B485}"); - explicit InstanceEntityScrubber(Instance::EntityList& entities); + explicit InstanceEntityScrubber(EntityList& entities); void AddEntitiesToScrub(const EntityList& entities); private: - Instance::EntityList& m_entities; + EntityList& m_entities; }; } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp index ea1af5cccb..336ff32498 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp @@ -154,7 +154,7 @@ namespace AzToolsFramework continue; } - Instance::EntityList newEntities; + EntityList newEntities; // Climb up to the root of the instance hierarchy from this instance InstanceOptionalConstReference rootInstance = *instanceToUpdate; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.cpp index d1d3491156..4ca1979d81 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.cpp @@ -284,7 +284,7 @@ namespace AzToolsFramework } bool LoadInstanceFromPrefabDom( - Instance& instance, Instance::EntityList& newlyAddedEntities, const PrefabDom& prefabDom, LoadFlags flags) + Instance& instance, EntityList& newlyAddedEntities, const PrefabDom& prefabDom, LoadFlags flags) { // When entities are rebuilt they are first destroyed. As a result any assets they were exclusively holding on to will // be released and reloaded once the entities are built up again. By suspending asset release temporarily the asset reload diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.h index d2234f423b..fe58416788 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabDomUtils.h @@ -133,7 +133,7 @@ namespace AzToolsFramework * @return bool on whether the operation succeeded. */ bool LoadInstanceFromPrefabDom( - Instance& instance, Instance::EntityList& newlyAddedEntities, const PrefabDom& prefabDom, + Instance& instance, EntityList& newlyAddedEntities, const PrefabDom& prefabDom, LoadFlags flags = LoadFlags::None); inline PrefabDomPath GetPrefabDomInstancePath(const char* instanceName) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index a1a7ac9844..4b6a6dd039 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -22,8 +23,6 @@ class QString; namespace AzToolsFramework { - using EntityList = AZStd::vector; - namespace Prefab { class Instance; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h index 2eb0bfa8e6..bab6da0f3f 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicInterface.h @@ -13,10 +13,10 @@ #include #include +#include + namespace AzToolsFramework { - using EntityIdList = AZStd::vector; - namespace UndoSystem { class URSequencePoint; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestBus.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestBus.h index cf7665bdd7..779d5af9c6 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestBus.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicRequestBus.h @@ -9,7 +9,6 @@ #pragma once #include -#include #include #include #include @@ -18,10 +17,10 @@ #include #include +#include + namespace AzToolsFramework { - using EntityIdList = AZStd::vector; - namespace Prefab { using CreatePrefabResult = AZ::Outcome; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp index 3234aa8b2d..8b246161b2 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp @@ -319,7 +319,7 @@ namespace AzToolsFramework } auto newInstance = AZStd::make_unique(parent); - Instance::EntityList newEntities; + EntityList newEntities; if (!PrefabDomUtils::LoadInstanceFromPrefabDom(*newInstance, newEntities, instantiatingTemplate->get().GetPrefabDom())) { AZ_Error("Prefab", false, diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h index b547e9bdd2..e52da2127a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -36,8 +37,6 @@ namespace AZ namespace AzToolsFramework { - using EntityList = AZStd::vector; - namespace Prefab { using InstanceList = AZStd::vector; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/EditorInfoRemover.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/EditorInfoRemover.h index dbad58d2f5..b1bc7131b5 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/EditorInfoRemover.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Spawnable/EditorInfoRemover.h @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -50,7 +51,6 @@ namespace AzToolsFramework::Prefab::PrefabConversionUtils static void Reflect(AZ::ReflectContext* context); protected: - using EntityList = AZStd::vector; static void GetEntitiesFromInstance(AzToolsFramework::Prefab::Instance& instance, EntityList& hierarchyEntities); static bool ReadComponentAttribute( diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp index dfa8ad14c9..6e8bad0217 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -8,21 +8,14 @@ #include +#include +#include #include #include -#include -#include -#include -#include #include #include -#include -#include -#include -#include -#include #include #include #include @@ -32,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -39,27 +33,11 @@ #include #include -#include -#include -#include -#include - #include -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include -#include #include -#include -#include namespace AzToolsFramework { @@ -72,31 +50,6 @@ namespace AzToolsFramework PrefabFocusPublicInterface* PrefabIntegrationManager::s_prefabFocusPublicInterface = nullptr; PrefabLoaderInterface* PrefabIntegrationManager::s_prefabLoaderInterface = nullptr; PrefabPublicInterface* PrefabIntegrationManager::s_prefabPublicInterface = nullptr; - PrefabSystemComponentInterface* PrefabIntegrationManager::s_prefabSystemComponentInterface = nullptr; - - const AZStd::string PrefabIntegrationManager::s_prefabFileExtension = ".prefab"; - - static const char* const ClosePrefabDialog = "ClosePrefabDialog"; - static const char* const FooterSeparatorLine = "FooterSeparatorLine"; - static const char* const PrefabSavedMessageFrame = "PrefabSavedMessageFrame"; - static const char* const PrefabSavePreferenceHint = "PrefabSavePreferenceHint"; - static const char* const PrefabSaveWarningFrame = "PrefabSaveWarningFrame"; - static const char* const SaveDependentPrefabsCard = "SaveDependentPrefabsCard"; - static const char* const SavePrefabDialog = "SavePrefabDialog"; - static const char* const UnsavedPrefabFileName = "UnsavedPrefabFileName"; - - - void PrefabUserSettings::Reflect(AZ::ReflectContext* context) - { - AZ::SerializeContext* serializeContext = azrtti_cast(context); - if (serializeContext) - { - serializeContext->Class() - ->Version(1) - ->Field("m_saveLocation", &PrefabUserSettings::m_saveLocation) - ->Field("m_autoNumber", &PrefabUserSettings::m_autoNumber); - } - } PrefabIntegrationManager::PrefabIntegrationManager() { @@ -128,13 +81,6 @@ namespace AzToolsFramework return; } - s_prefabSystemComponentInterface = AZ::Interface::Get(); - if (s_prefabSystemComponentInterface == nullptr) - { - AZ_Assert(false, "Prefab - could not get PrefabSystemComponentInterface on PrefabIntegrationManager construction."); - return; - } - s_prefabFocusPublicInterface = AZ::Interface::Get(); if (s_prefabFocusPublicInterface == nullptr) { @@ -143,7 +89,9 @@ namespace AzToolsFramework } m_readOnlyEntityPublicInterface = AZ::Interface::Get(); - AZ_Assert(m_readOnlyEntityPublicInterface, "Prefab - could not get ReadOnlyEntityPublicInterface on PrefabIntegrationManager construction."); + AZ_Assert( + m_readOnlyEntityPublicInterface, + "Prefab - could not get ReadOnlyEntityPublicInterface on PrefabIntegrationManager construction."); // Get EditorEntityContextId EditorEntityContextRequestBus::BroadcastResult(s_editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId); @@ -156,7 +104,6 @@ namespace AzToolsFramework EditorEventsBus::Handler::BusConnect(); PrefabInstanceContainerNotificationBus::Handler::BusConnect(); AZ::Interface::Register(this); - AssetBrowser::AssetBrowserSourceDropBus::Handler::BusConnect(s_prefabFileExtension); EditorEntityContextNotificationBus::Handler::BusConnect(); InitializeShortcuts(); @@ -167,7 +114,6 @@ namespace AzToolsFramework UninitializeShortcuts(); EditorEntityContextNotificationBus::Handler::BusDisconnect(); - AssetBrowser::AssetBrowserSourceDropBus::Handler::BusDisconnect(); AZ::Interface::Unregister(this); PrefabInstanceContainerNotificationBus::Handler::BusDisconnect(); EditorEventsBus::Handler::BusDisconnect(); @@ -257,7 +203,8 @@ namespace AzToolsFramework return "Prefabs"; } - void PrefabIntegrationManager::PopulateEditorGlobalContextMenu(QMenu* menu, [[maybe_unused]] const AZ::Vector2& point, [[maybe_unused]] int flags) + void PrefabIntegrationManager::PopulateEditorGlobalContextMenu( + QMenu* menu, [[maybe_unused]] const AZ::Vector2& point, [[maybe_unused]] int flags) { AzToolsFramework::EntityIdList selectedEntities; AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult( @@ -330,9 +277,12 @@ namespace AzToolsFramework QAction* createAction = menu->addAction(QObject::tr("Create Prefab...")); createAction->setToolTip(QObject::tr("Creates a prefab out of the currently selected entities.")); - QObject::connect(createAction, &QAction::triggered, createAction, [selectedEntities] { - ContextMenu_CreatePrefab(selectedEntities); - }); + QObject::connect( + createAction, &QAction::triggered, createAction, + [selectedEntities] + { + ContextMenu_CreatePrefab(selectedEntities); + }); itemWasShown = true; } @@ -348,7 +298,11 @@ namespace AzToolsFramework instantiateAction->setToolTip(QObject::tr("Instantiates a prefab file in the scene.")); QObject::connect( - instantiateAction, &QAction::triggered, instantiateAction, [] { ContextMenu_InstantiatePrefab(); }); + instantiateAction, &QAction::triggered, instantiateAction, + [] + { + ContextMenu_InstantiatePrefab(); + }); // Instantiate Procedural Prefab if (AZ::Prefab::ProceduralPrefabAsset::UseProceduralPrefabs()) @@ -357,7 +311,11 @@ namespace AzToolsFramework action->setToolTip(QObject::tr("Instantiates a procedural prefab file in a prefab.")); QObject::connect( - action, &QAction::triggered, action, [] { ContextMenu_InstantiateProceduralPrefab(); }); + action, &QAction::triggered, action, + [] + { + ContextMenu_InstantiateProceduralPrefab(); + }); } itemWasShown = true; @@ -433,9 +391,12 @@ namespace AzToolsFramework QAction* saveAction = menu->addAction(QObject::tr("Save Prefab to file")); saveAction->setToolTip(QObject::tr("Save the changes to the prefab to disk.")); - QObject::connect(saveAction, &QAction::triggered, saveAction, [selectedEntity] { - ContextMenu_SavePrefab(selectedEntity); - }); + QObject::connect( + saveAction, &QAction::triggered, saveAction, + [selectedEntity] + { + ContextMenu_SavePrefab(selectedEntity); + }); } itemWasShown = true; @@ -454,7 +415,12 @@ namespace AzToolsFramework !readOnlyEntityInSelection) { QAction* deleteAction = menu->addAction(QObject::tr("Delete")); - QObject::connect(deleteAction, &QAction::triggered, deleteAction, [] { ContextMenu_DeleteSelected(); }); + QObject::connect( + deleteAction, &QAction::triggered, deleteAction, + [] + { + ContextMenu_DeleteSelected(); + }); } // Detach Prefab @@ -477,16 +443,6 @@ namespace AzToolsFramework s_prefabFocusPublicInterface->FocusOnOwningPrefab(AZ::EntityId()); } - void PrefabIntegrationManager::HandleSourceFileType(AZStd::string_view sourceFilePath, AZ::EntityId parentId, AZ::Vector3 position) const - { - auto instantiatePrefabOutcome = s_prefabPublicInterface->InstantiatePrefab(sourceFilePath, parentId, position); - - if (!instantiatePrefabOutcome.IsSuccess()) - { - WarnUserOfError("Prefab Instantiation Error", instantiatePrefabOutcome.GetError()); - } - } - void PrefabIntegrationManager::OnStartPlayInEditorBegin() { // Focus on the root prefab (AZ::EntityId() will default to it) @@ -501,8 +457,7 @@ namespace AzToolsFramework [&]() { s_containerEntityInterface->RefreshAllContainerEntities(s_editorEntityContextId); - } - ); + }); } void PrefabIntegrationManager::ContextMenu_CreatePrefab(AzToolsFramework::EntityIdList selectedEntities) @@ -553,7 +508,8 @@ namespace AzToolsFramework if (hasExternalReferences) { bool useAllReferencedEntities = false; - bool continueCreation = QueryAndPruneMissingExternalReferences(entitiesToIncludeInAsset, allReferencedEntities, useAllReferencedEntities); + bool continueCreation = + QueryAndPruneMissingExternalReferences(entitiesToIncludeInAsset, allReferencedEntities, useAllReferencedEntities); if (!continueCreation) { // User canceled the operation @@ -576,17 +532,19 @@ namespace AzToolsFramework { AZ::EntityId commonRoot; bool hasCommonRoot = false; - AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(hasCommonRoot, - &AzToolsFramework::ToolsApplicationRequests::FindCommonRoot, entitiesToIncludeInAsset, commonRoot, &prefabRootEntities); - if (hasCommonRoot && commonRoot.IsValid() && entitiesToIncludeInAsset.find(commonRoot) != entitiesToIncludeInAsset.end()) + AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult( + hasCommonRoot, &AzToolsFramework::ToolsApplicationRequests::FindCommonRoot, entitiesToIncludeInAsset, commonRoot, + &prefabRootEntities); + if (hasCommonRoot && commonRoot.IsValid() && + entitiesToIncludeInAsset.find(commonRoot) != entitiesToIncludeInAsset.end()) { prefabRootEntities.insert(prefabRootEntities.begin(), commonRoot); } } - GenerateSuggestedFilenameFromEntities(prefabRootEntities, suggestedName); + PrefabSaveHandler::GenerateSuggestedFilenameFromEntities(prefabRootEntities, suggestedName); - if (!QueryUserForPrefabSaveLocation( + if (!PrefabSaveHandler::QueryUserForPrefabSaveLocation( suggestedName, targetDirectory, AZ_CRC("PrefabUserSettings"), activeWindow, prefabName, prefabFilePath)) { // User canceled prefab creation, or error prevented continuation. @@ -598,14 +556,14 @@ namespace AzToolsFramework if (!createPrefabOutcome.IsSuccess()) { - WarnUserOfError("Prefab Creation Error", createPrefabOutcome.GetError()); + WarningDialog("Prefab Creation Error", createPrefabOutcome.GetError()); } } void PrefabIntegrationManager::ContextMenu_InstantiatePrefab() { AZStd::string prefabFilePath; - bool hasUserSelectedValidSourceFile = QueryUserForPrefabFilePath(prefabFilePath); + bool hasUserSelectedValidSourceFile = PrefabSaveHandler::QueryUserForPrefabFilePath(prefabFilePath); if (hasUserSelectedValidSourceFile) { @@ -629,7 +587,7 @@ namespace AzToolsFramework auto createPrefabOutcome = s_prefabPublicInterface->InstantiatePrefab(prefabFilePath, parentId, position); if (!createPrefabOutcome.IsSuccess()) { - WarnUserOfError("Prefab Instantiation Error",createPrefabOutcome.GetError()); + WarningDialog("Prefab Instantiation Error", createPrefabOutcome.GetError()); } } } @@ -637,7 +595,7 @@ namespace AzToolsFramework void PrefabIntegrationManager::ContextMenu_InstantiateProceduralPrefab() { AZStd::string prefabAssetPath; - bool hasUserForProceduralPrefabAsset = QueryUserForProceduralPrefabAsset(prefabAssetPath); + bool hasUserForProceduralPrefabAsset = PrefabSaveHandler::QueryUserForProceduralPrefabAsset(prefabAssetPath); if (hasUserForProceduralPrefabAsset) { @@ -659,7 +617,7 @@ namespace AzToolsFramework auto createPrefabOutcome = s_prefabPublicInterface->InstantiatePrefab(prefabAssetPath, parentId, position); if (!createPrefabOutcome.IsSuccess()) { - WarnUserOfError("Procedural Prefab Instantiation Error", createPrefabOutcome.GetError()); + WarningDialog("Procedural Prefab Instantiation Error", createPrefabOutcome.GetError()); } } } @@ -682,7 +640,7 @@ namespace AzToolsFramework if (!savePrefabOutcome.IsSuccess()) { - WarnUserOfError("Prefab Save Error", savePrefabOutcome.GetError()); + WarningDialog("Prefab Save Error", savePrefabOutcome.GetError()); } } @@ -695,401 +653,22 @@ namespace AzToolsFramework s_prefabPublicInterface->DeleteEntitiesAndAllDescendantsInInstance(selectedEntityIds); if (!deleteSelectedResult.IsSuccess()) { - WarnUserOfError("Delete selected entities error", deleteSelectedResult.GetError()); + WarningDialog("Delete selected entities error", deleteSelectedResult.GetError()); } } void PrefabIntegrationManager::ContextMenu_DetachPrefab(AZ::EntityId containerEntity) { - PrefabOperationResult detachPrefabResult = - s_prefabPublicInterface->DetachPrefab(containerEntity); + PrefabOperationResult detachPrefabResult = s_prefabPublicInterface->DetachPrefab(containerEntity); if (!detachPrefabResult.IsSuccess()) { - WarnUserOfError("Detach Prefab error", detachPrefabResult.GetError()); - } - } - - void PrefabIntegrationManager::GenerateSuggestedFilenameFromEntities(const EntityIdList& entityIds, AZStd::string& outName) - { - AZ_PROFILE_FUNCTION(AzToolsFramework); - - AZStd::string suggestedName; - - for (const AZ::EntityId& entityId : entityIds) - { - if (!AppendEntityToSuggestedFilename(suggestedName, entityId)) - { - break; - } - } - - if (suggestedName.size() == 0 || AzFramework::StringFunc::Utf8::CheckNonAsciiChar(suggestedName)) - { - suggestedName = "NewPrefab"; - } - - outName = suggestedName; - } - - bool PrefabIntegrationManager::AppendEntityToSuggestedFilename(AZStd::string& filename, AZ::EntityId entityId) - { - // When naming a prefab after its entities, we stop appending additional names once we've reached this cutoff length - size_t prefabNameCutoffLength = 32; - AzToolsFramework::EntityIdSet usedNameEntities; - - if (usedNameEntities.find(entityId) == usedNameEntities.end()) - { - AZ::Entity* entity = nullptr; - AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId); - if (entity) - { - AZStd::string entityNameFiltered = entity->GetName(); - - // Convert spaces in entity names to underscores - for (size_t i = 0; i < entityNameFiltered.size(); ++i) - { - char& character = entityNameFiltered.at(i); - if (character == ' ') - { - character = '_'; - } - } - - filename.append(entityNameFiltered); - usedNameEntities.insert(entityId); - if (filename.size() > prefabNameCutoffLength) - { - return false; - } - } - } - - return true; - } - - bool PrefabIntegrationManager::QueryUserForPrefabSaveLocation( - const AZStd::string& suggestedName, - const char* initialTargetDirectory, - AZ::u32 prefabUserSettingsId, - QWidget* activeWindow, - AZStd::string& outPrefabName, - AZStd::string& outPrefabFilePath - ) - { - AZStd::string saveAsInitialSuggestedDirectory; - if (!GetPrefabSaveLocation(saveAsInitialSuggestedDirectory, prefabUserSettingsId)) - { - saveAsInitialSuggestedDirectory = initialTargetDirectory; - } - - AZStd::string saveAsInitialSuggestedFullPath; - GenerateSuggestedPrefabPath(suggestedName, saveAsInitialSuggestedDirectory, saveAsInitialSuggestedFullPath); - - QString saveAs; - AZStd::string targetPath; - QFileInfo prefabSaveFileInfo; - QString prefabName; - while (true) - { - { - AZ_PROFILE_FUNCTION(AzToolsFramework); - saveAs = QFileDialog::getSaveFileName(nullptr, QString("Save As..."), saveAsInitialSuggestedFullPath.c_str(), QString("Prefabs (*.prefab)")); - } - - prefabSaveFileInfo = saveAs; - prefabName = prefabSaveFileInfo.baseName(); - if (saveAs.isEmpty()) - { - return false; - } - - targetPath = saveAs.toUtf8().constData(); - if (AzFramework::StringFunc::Utf8::CheckNonAsciiChar(targetPath)) - { - WarnUserOfError( - "Prefab Creation Failed.", - "Unicode file name is not supported. \r\n" - "Please use ASCII characters to name your prefab." - ); - return false; - } - - PrefabSaveResult saveResult = IsPrefabPathValidForAssets(activeWindow, saveAs, saveAsInitialSuggestedFullPath); - if (saveResult == PrefabSaveResult::Cancel) - { - // The error was already reported if this failed. - return false; - } - else if (saveResult == PrefabSaveResult::Continue) - { - // The prefab save name is valid, continue with the save attempt. - break; - } + WarningDialog("Detach Prefab error", detachPrefabResult.GetError()); } - - // If the prefab already exists, notify the user and bail - AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance(); - if (fileIO && fileIO->Exists(targetPath.c_str())) - { - const AZStd::string message = AZStd::string::format( - "You are attempting to overwrite an existing prefab: \"%s\".\r\n\r\n" - "This will damage instances or cascades of this prefab. \r\n\r\n" - "Instead, either push entities/fields to the prefab, or save to a different location.", - targetPath.c_str()); - - WarnUserOfError("Prefab Already Exists", message); - return false; - } - - // We prevent users from creating a new prefab with the same relative path that's already - // been used by an existing prefab in other places (e.g. Gems) because the AssetProcessor - // generates asset ids based on relative paths. This is unnecessary once AssetProcessor - // starts to generate UUID to every asset regardless of paths. - { - AZStd::string prefabRelativeName; - bool relativePathFound; - AssetSystemRequestBus::BroadcastResult(relativePathFound, &AssetSystemRequestBus::Events::GetRelativeProductPathFromFullSourceOrProductPath, targetPath, prefabRelativeName); - - AZ::Data::AssetId prefabAssetId; - AZ::Data::AssetCatalogRequestBus::BroadcastResult(prefabAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, prefabRelativeName.c_str(), AZ::Data::s_invalidAssetType, false); - if (prefabAssetId.IsValid()) - { - const AZStd::string message = AZStd::string::format( - "A prefab with the relative path \"%s\" already exists in the Asset Database. \r\n\r\n" - "Overriding it will damage instances or cascades of this prefab. \r\n\r\n" - "Instead, either push entities/fields to the prefab, or save to a different location.", - prefabRelativeName.c_str()); - - WarnUserOfError("Prefab Path Error", message); - return false; - } - } - - AZStd::string saveDir(prefabSaveFileInfo.absoluteDir().absolutePath().toUtf8().constData()); - SetPrefabSaveLocation(saveDir, prefabUserSettingsId); - - outPrefabName = prefabName.toUtf8().constData(); - outPrefabFilePath = targetPath.c_str(); - - return true; } - bool PrefabIntegrationManager::QueryUserForPrefabFilePath(AZStd::string& outPrefabFilePath) - { - AssetSelectionModel selection; - - // Note, stringfilter will match every source file CONTAINING ".prefab". - // If this causes issues, we will need to create a new filter class for regex matching. - // We'll need to check if the file contents are actually a prefab later in the flow anyways, - // so this should not be an issue. - StringFilter* stringFilter = new StringFilter(); - stringFilter->SetName("Prefab"); - stringFilter->SetFilterString(".prefab"); - stringFilter->SetFilterPropagation(AssetBrowserEntryFilter::PropagateDirection::Down); - auto stringFilterPtr = FilterConstType(stringFilter); - - EntryTypeFilter* sourceFilter = new EntryTypeFilter(); - sourceFilter->SetName("Source"); - sourceFilter->SetEntryType(AssetBrowserEntry::AssetEntryType::Source); - sourceFilter->SetFilterPropagation(AssetBrowserEntryFilter::PropagateDirection::Down); - auto sourceFilterPtr = FilterConstType(sourceFilter); - - CompositeFilter* compositeFilter = new CompositeFilter(CompositeFilter::LogicOperatorType::AND); - compositeFilter->SetName("Prefab"); - compositeFilter->AddFilter(sourceFilterPtr); - compositeFilter->AddFilter(stringFilterPtr); - auto compositeFilterPtr = FilterConstType(compositeFilter); - - selection.SetDisplayFilter(compositeFilterPtr); - selection.SetSelectionFilter(compositeFilterPtr); - - AssetBrowserComponentRequestBus::Broadcast(&AssetBrowserComponentRequests::PickAssets, selection, AzToolsFramework::GetActiveWindow()); - - if (!selection.IsValid()) - { - // User closed the dialog without selecting, just return. - return false; - } - - auto source = azrtti_cast(selection.GetResult()); - - if (source == nullptr) - { - AZ_Assert(false, "Prefab - Incorrect entry type selected during prefab instantiation. Expected source."); - return false; - } - - outPrefabFilePath = source->GetFullPath(); - return true; - } - - bool PrefabIntegrationManager::QueryUserForProceduralPrefabAsset(AZStd::string& outPrefabAssetPath) - { - using namespace AzToolsFramework; - auto selection = AssetBrowser::AssetSelectionModel::AssetTypeSelection(azrtti_typeid()); - EditorRequests::Bus::Broadcast(&AzToolsFramework::EditorRequests::BrowseForAssets, selection); - - if (!selection.IsValid()) - { - return false; - } - - auto product = azrtti_cast(selection.GetResult()); - if (product == nullptr) - { - return false; - } - outPrefabAssetPath = product->GetRelativePath(); - return true; - } - - void PrefabIntegrationManager::WarnUserOfError(AZStd::string_view title, AZStd::string_view message) - { - QWidget* activeWindow = QApplication::activeWindow(); - - QMessageBox::warning( - activeWindow, - QString(title.data()), - QString(message.data()), - QMessageBox::Ok, - QMessageBox::Ok - ); - } - - PrefabIntegrationManager::PrefabSaveResult PrefabIntegrationManager::IsPrefabPathValidForAssets(QWidget* activeWindow, - QString prefabPath, AZStd::string& retrySavePath) - { - bool assetSetFoldersRetrieved = false; - AZStd::vector assetSafeFolders; - AzToolsFramework::AssetSystemRequestBus::BroadcastResult( - assetSetFoldersRetrieved, - &AzToolsFramework::AssetSystemRequestBus::Events::GetAssetSafeFolders, - assetSafeFolders); - - if (!assetSetFoldersRetrieved) - { - // If the asset safe list couldn't be retrieved, don't block the user but warn them. - AZ_Warning("Prefab", false, "Unable to verify that the prefab file to create is in a valid path."); - } - else - { - AZ::IO::FixedMaxPath lexicallyNormalPath = AZ::IO::PathView(prefabPath.toUtf8().constData()).LexicallyNormal(); - - bool isPathSafeForAssets = false; - for (const AZStd::string& assetSafeFolder : assetSafeFolders) - { - AZ::IO::PathView assetSafeFolderView(assetSafeFolder); - // Check if the prefabPath is relative to the safe asset directory. - // The Path classes are being used to make this check case insensitive. - if (lexicallyNormalPath.IsRelativeTo(assetSafeFolderView)) - { - isPathSafeForAssets = true; - break; - } - } - - if (!isPathSafeForAssets) - { - // Put an error in the console, so the log files have info about this error, or the user can look up the error after dismissing it. - AZStd::string errorMessage = "You can only save prefabs to either your game project folder or the Gems folder. Update the location and try again.\n\n" - "You can also review and update your save locations in the AssetProcessorPlatformConfig.ini file."; - AZ_Error("Prefab", false, errorMessage.c_str()); - - // Display a pop-up, the logs are easy to miss. This will make sure a user who encounters this error immediately knows their prefab save has failed. - QMessageBox msgBox(activeWindow); - msgBox.setIcon(QMessageBox::Icon::Warning); - msgBox.setTextFormat(Qt::RichText); - msgBox.setWindowTitle(QObject::tr("Invalid save location")); - msgBox.setText(QObject::tr(errorMessage.c_str())); - msgBox.setStandardButtons(QMessageBox::Cancel | QMessageBox::Retry); - msgBox.setDefaultButton(QMessageBox::Retry); - const int response = msgBox.exec(); - switch (response) - { - case QMessageBox::Retry: - // If the user wants to retry, they probably want to save to a valid location, - // so set the suggested save path to a known valid location. - if (assetSafeFolders.size() > 0) - { - retrySavePath = assetSafeFolders[0]; - } - return PrefabSaveResult::Retry; - case QMessageBox::Cancel: - default: - return PrefabSaveResult::Cancel; - } - } - } - // Valid prefab save location, continue with the save attempt. - return PrefabSaveResult::Continue; - } - - void PrefabIntegrationManager::GenerateSuggestedPrefabPath(const AZStd::string& prefabName, const AZStd::string& targetDirectory, AZStd::string& suggestedFullPath) - { - // Generate full suggested path from prefabName - if given NewPrefab as prefabName, - // NewPrefab_001.prefab would be tried, and if that already existed we would suggest - // the first unused number value (NewPrefab_002.prefab etc.) - AZStd::string normalizedTargetDirectory = targetDirectory; - AZ::StringFunc::Path::Normalize(normalizedTargetDirectory); - - // Convert spaces in entity names to underscores - AZStd::string prefabNameFiltered = prefabName; - AZ::StringFunc::Replace(prefabNameFiltered, ' ', '_'); - - auto settings = AZ::UserSettings::CreateFind(AZ_CRC("PrefabUserSettings"), AZ::UserSettings::CT_LOCAL); - if (settings->m_autoNumber) - { - AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance(); - - const AZ::u32 maxPrefabNumber = 1000; - for (AZ::u32 prefabNumber = 1; prefabNumber < maxPrefabNumber; ++prefabNumber) - { - AZStd::string possiblePath; - AZ::StringFunc::Path::Join( - normalizedTargetDirectory.c_str(), - AZStd::string::format("%s_%3.3u%s", prefabNameFiltered.c_str(), prefabNumber, s_prefabFileExtension.c_str()).c_str(), - possiblePath - ); - - if (!fileIO || !fileIO->Exists(possiblePath.c_str())) - { - suggestedFullPath = possiblePath; - break; - } - } - } - else - { - // use the entity name as the file name regardless of it already existing, the OS will ask the user to overwrite the file in that case. - AZ::StringFunc::Path::Join( - normalizedTargetDirectory.c_str(), - AZStd::string::format("%s%s", prefabNameFiltered.c_str(), s_prefabFileExtension.c_str()).c_str(), - suggestedFullPath - ); - } - } - - void PrefabIntegrationManager::SetPrefabSaveLocation(const AZStd::string& path, AZ::u32 settingsId) - { - auto settings = AZ::UserSettings::CreateFind(settingsId, AZ::UserSettings::CT_LOCAL); - settings->m_saveLocation = path; - } - - bool PrefabIntegrationManager::GetPrefabSaveLocation(AZStd::string& path, AZ::u32 settingsId) - { - auto settings = AZ::UserSettings::Find(settingsId, AZ::UserSettings::CT_LOCAL); - if (settings) - { - path = settings->m_saveLocation; - return true; - } - - return false; - } - - void PrefabIntegrationManager::GatherAllReferencedEntitiesAndCompare(const EntityIdSet& entities, - EntityIdSet& entitiesAndReferencedEntities, bool& hasExternalReferences) + void PrefabIntegrationManager::GatherAllReferencedEntitiesAndCompare( + const EntityIdSet& entities, EntityIdSet& entitiesAndReferencedEntities, bool& hasExternalReferences) { AZ::SerializeContext* serializeContext; AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext); @@ -1130,7 +709,8 @@ namespace AzToolsFramework } } - const AZ::Edit::ElementData* classEditData = classData ? classData->FindElementData(AZ::Edit::ClassElements::EditorData) : nullptr; + const AZ::Edit::ElementData* classEditData = + classData ? classData->FindElementData(AZ::Edit::ClassElements::EditorData) : nullptr; if (classEditData) { AZ::Edit::Attribute* slicePushAttribute = classEditData->FindAttribute(AZ::Edit::Attributes::SliceFlags); @@ -1146,7 +726,8 @@ namespace AzToolsFramework return sliceFlags; } - void PrefabIntegrationManager::GatherAllReferencedEntities(EntityIdSet& entitiesWithReferences, AZ::SerializeContext& serializeContext) + void PrefabIntegrationManager::GatherAllReferencedEntities( + EntityIdSet& entitiesWithReferences, AZ::SerializeContext& serializeContext) { AZ_PROFILE_FUNCTION(AzToolsFramework); @@ -1172,18 +753,20 @@ namespace AzToolsFramework { AZStd::vector parentStack; parentStack.reserve(30); - auto beginCB = [&](void* ptr, const AZ::SerializeContext::ClassData* classData, const AZ::SerializeContext::ClassElement* elementData) -> bool + auto beginCB = [&](void* ptr, const AZ::SerializeContext::ClassData* classData, + const AZ::SerializeContext::ClassElement* elementData) -> bool { parentStack.push_back(classData); - AZ::u32 sliceFlags = GetSliceFlags(elementData ? elementData->m_editData : nullptr, classData ? classData->m_editData : nullptr); + AZ::u32 sliceFlags = + GetSliceFlags(elementData ? elementData->m_editData : nullptr, classData ? classData->m_editData : nullptr); // Skip any class or element marked as don't gather references if (0 != (sliceFlags & AZ::Edit::SliceFlags::DontGatherReference)) { return false; } - + if (classData->m_typeId == AZ::SerializeTypeInfo::GetUuid()) { if (!parentStack.empty() && parentStack.back()->m_typeId == AZ::SerializeTypeInfo::GetUuid()) @@ -1192,8 +775,9 @@ namespace AzToolsFramework } else { - AZ::EntityId* entityIdPtr = (elementData->m_flags & AZ::SerializeContext::ClassElement::FLG_POINTER) ? - *reinterpret_cast(ptr) : reinterpret_cast(ptr); + AZ::EntityId* entityIdPtr = (elementData->m_flags & AZ::SerializeContext::ClassElement::FLG_POINTER) + ? *reinterpret_cast(ptr) + : reinterpret_cast(ptr); if (entityIdPtr) { const AZ::EntityId id = *entityIdPtr; @@ -1219,26 +803,15 @@ namespace AzToolsFramework }; AZ::SerializeContext::EnumerateInstanceCallContext callContext( - beginCB, - endCB, - &serializeContext, - AZ::SerializeContext::ENUM_ACCESS_FOR_READ, - nullptr - ); - - serializeContext.EnumerateInstanceConst( - &callContext, - entity, - azrtti_typeid(), - nullptr, - nullptr - ); + beginCB, endCB, &serializeContext, AZ::SerializeContext::ENUM_ACCESS_FOR_READ, nullptr); + + serializeContext.EnumerateInstanceConst(&callContext, entity, azrtti_typeid(), nullptr, nullptr); } } } - bool PrefabIntegrationManager::QueryAndPruneMissingExternalReferences(EntityIdSet& entities, EntityIdSet& selectedAndReferencedEntities, - bool& useReferencedEntities, bool defaultMoveExternalRefs) + bool PrefabIntegrationManager::QueryAndPruneMissingExternalReferences( + EntityIdSet& entities, EntityIdSet& selectedAndReferencedEntities, bool& useReferencedEntities, bool defaultMoveExternalRefs) { AZ_PROFILE_FUNCTION(AzToolsFramework); useReferencedEntities = false; @@ -1278,9 +851,9 @@ namespace AzToolsFramework AZ_PROFILE_FUNCTION(AzToolsFramework); const AZStd::string message = AZStd::string::format( - "Entity references may not be valid if the entity IDs change or if the entities do not exist when the prefab is instantiated.\r\n\r\nSelected Entities\n%s\nReferenced Entities\n%s\n", - includedEntities.c_str(), - referencedEntities.c_str()); + "Entity references may not be valid if the entity IDs change or if the entities do not exist when the prefab is " + "instantiated.\r\n\r\nSelected Entities\n%s\nReferenced Entities\n%s\n", + includedEntities.c_str(), referencedEntities.c_str()); QMessageBox msgBox(AzToolsFramework::GetActiveWindow()); msgBox.setWindowTitle("External Entity References"); @@ -1361,243 +934,21 @@ namespace AzToolsFramework } else { - WarnUserOfError("Entity Creation Error", createResult.GetError()); + WarningDialog("Entity Creation Error", createResult.GetError()); return AZ::EntityId(); } } int PrefabIntegrationManager::ExecuteClosePrefabDialog(TemplateId templateId) { - if (s_prefabSystemComponentInterface->AreDirtyTemplatesPresent(templateId)) - { - auto prefabSaveSelectionDialog = ConstructClosePrefabDialog(templateId); - - int prefabSaveSelection = prefabSaveSelectionDialog->exec(); - - if (prefabSaveSelection == QDialog::Accepted) - { - SavePrefabsInDialog(prefabSaveSelectionDialog.get()); - } - - return prefabSaveSelection; - } - - return QDialogButtonBox::DestructiveRole; + return m_prefabSaveHandler.ExecuteClosePrefabDialog(templateId); } void PrefabIntegrationManager::ExecuteSavePrefabDialog(TemplateId templateId, bool useSaveAllPrefabsPreference) { - auto prefabTemplate = s_prefabSystemComponentInterface->FindTemplate(templateId); - AZ::IO::Path prefabTemplatePath = prefabTemplate->get().GetFilePath(); - - if (s_prefabSystemComponentInterface->IsTemplateDirty(templateId)) - { - if (s_prefabLoaderInterface->SaveTemplate(templateId) == false) - { - AZ_Error("Prefab", false, "Template '%s' could not be saved successfully.", prefabTemplatePath.c_str()); - return; - } - } - - if (s_prefabSystemComponentInterface->AreDirtyTemplatesPresent(templateId)) - { - if (useSaveAllPrefabsPreference) - { - SaveAllPrefabsPreference saveAllPrefabsPreference = s_prefabLoaderInterface->GetSaveAllPrefabsPreference(); - - if (saveAllPrefabsPreference == SaveAllPrefabsPreference::SaveAll) - { - s_prefabSystemComponentInterface->SaveAllDirtyTemplates(templateId); - return; - } - else if (saveAllPrefabsPreference == SaveAllPrefabsPreference::SaveNone) - { - return; - } - } - - AZStd::unique_ptr savePrefabDialog = ConstructSavePrefabDialog(templateId, useSaveAllPrefabsPreference); - if (savePrefabDialog) - { - int prefabSaveSelection = savePrefabDialog->exec(); - - if (prefabSaveSelection == QDialog::Accepted) - { - SavePrefabsInDialog(savePrefabDialog.get()); - } - } - } - } - - void PrefabIntegrationManager::SavePrefabsInDialog(QDialog* unsavedPrefabsDialog) - { - QList unsavedPrefabFileLabels = unsavedPrefabsDialog->findChildren(UnsavedPrefabFileName); - if (unsavedPrefabFileLabels.size() > 0) - { - for (const QLabel* unsavedPrefabFileLabel : unsavedPrefabFileLabels) - { - AZStd::string unsavedPrefabFileName = unsavedPrefabFileLabel->property("FilePath").toString().toUtf8().data(); - AzToolsFramework::Prefab::TemplateId unsavedPrefabTemplateId = - s_prefabSystemComponentInterface->GetTemplateIdFromFilePath(unsavedPrefabFileName.data()); - [[maybe_unused]] bool isTemplateSavedSuccessfully = s_prefabLoaderInterface->SaveTemplate(unsavedPrefabTemplateId); - AZ_Error("Prefab", isTemplateSavedSuccessfully, "Prefab '%s' could not be saved successfully.", unsavedPrefabFileName.c_str()); - } - } - } - - AZStd::unique_ptr PrefabIntegrationManager::ConstructSavePrefabDialog(TemplateId templateId, bool useSaveAllPrefabsPreference) - { - AZStd::unique_ptr savePrefabDialog = AZStd::make_unique(AzToolsFramework::GetActiveWindow()); - - savePrefabDialog->setWindowTitle("Unsaved files detected"); - - // Main Content section begins. - savePrefabDialog->setObjectName(SavePrefabDialog); - QBoxLayout* contentLayout = new QVBoxLayout(savePrefabDialog.get()); - - QFrame* prefabSavedMessageFrame = new QFrame(savePrefabDialog.get()); - QHBoxLayout* prefabSavedMessageLayout = new QHBoxLayout(savePrefabDialog.get()); - prefabSavedMessageFrame->setObjectName(PrefabSavedMessageFrame); - prefabSavedMessageFrame->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); - - // Add a checkMark icon next to the level entities saved message. - QPixmap checkMarkIcon(QString(":/Notifications/checkmark.svg")); - QLabel* prefabSavedSuccessfullyIconContainer = new QLabel(savePrefabDialog.get()); - prefabSavedSuccessfullyIconContainer->setPixmap(checkMarkIcon); - prefabSavedSuccessfullyIconContainer->setFixedWidth(checkMarkIcon.width()); - - // Add a message that level entities are saved successfully. - - auto prefabTemplate = s_prefabSystemComponentInterface->FindTemplate(templateId); - AZ::IO::Path prefabTemplatePath = prefabTemplate->get().GetFilePath(); - QLabel* prefabSavedSuccessfullyLabel = new QLabel( - QString("Prefab '%1' has been saved. Do you want to save the below dependent prefabs too?").arg(prefabTemplatePath.c_str()), - savePrefabDialog.get()); - prefabSavedMessageLayout->addWidget(prefabSavedSuccessfullyIconContainer); - prefabSavedMessageLayout->addWidget(prefabSavedSuccessfullyLabel); - prefabSavedMessageFrame->setLayout(prefabSavedMessageLayout); - contentLayout->addWidget(prefabSavedMessageFrame); - - AZStd::unique_ptr unsavedPrefabsContainer = ConstructUnsavedPrefabsCard(templateId); - contentLayout->addWidget(unsavedPrefabsContainer.release()); - - contentLayout->addStretch(); - - // Footer section begins. - QHBoxLayout* footerLayout = new QHBoxLayout(savePrefabDialog.get()); - - if (useSaveAllPrefabsPreference) - { - QFrame* footerSeparatorLine = new QFrame(savePrefabDialog.get()); - footerSeparatorLine->setObjectName(FooterSeparatorLine); - footerSeparatorLine->setFrameShape(QFrame::HLine); - contentLayout->addWidget(footerSeparatorLine); - - QLabel* prefabSavePreferenceHint = new QLabel( - "You can prevent this window from showing in the future by updating your global save preferences.", - savePrefabDialog.get()); - prefabSavePreferenceHint->setToolTip( - "Go to 'Edit > Editor Settings > Global Preferences... > Global save preferences' to update your preference"); - prefabSavePreferenceHint->setObjectName(PrefabSavePreferenceHint); - footerLayout->addWidget(prefabSavePreferenceHint); - } - - QDialogButtonBox* prefabSaveConfirmationButtons = - new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::No, savePrefabDialog.get()); - footerLayout->addWidget(prefabSaveConfirmationButtons); - contentLayout->addLayout(footerLayout); - connect(prefabSaveConfirmationButtons, &QDialogButtonBox::accepted, savePrefabDialog.get(), &QDialog::accept); - connect(prefabSaveConfirmationButtons, &QDialogButtonBox::rejected, savePrefabDialog.get(), &QDialog::reject); - AzQtComponents::StyleManager::setStyleSheet(savePrefabDialog->parentWidget(), QStringLiteral("style:Editor.qss")); - - savePrefabDialog->setLayout(contentLayout); - return AZStd::move(savePrefabDialog); - } - - AZStd::shared_ptr PrefabIntegrationManager::ConstructClosePrefabDialog(TemplateId templateId) - { - AZStd::shared_ptr closePrefabDialog = AZStd::make_shared(AzToolsFramework::GetActiveWindow()); - closePrefabDialog->setWindowTitle("Unsaved files detected"); - AZStd::weak_ptr closePrefabDialogWeakPtr(closePrefabDialog); - closePrefabDialog->setObjectName(ClosePrefabDialog); - - // Main Content section begins. - QVBoxLayout* contentLayout = new QVBoxLayout(closePrefabDialog.get()); - QFrame* prefabSaveWarningFrame = new QFrame(closePrefabDialog.get()); - QHBoxLayout* levelEntitiesSaveQuestionLayout = new QHBoxLayout(closePrefabDialog.get()); - prefabSaveWarningFrame->setObjectName(PrefabSaveWarningFrame); - - // Add a warning icon next to save prefab warning. - prefabSaveWarningFrame->setLayout(levelEntitiesSaveQuestionLayout); - QPixmap warningIcon(QString(":/Notifications/warning.svg")); - QLabel* warningIconContainer = new QLabel(closePrefabDialog.get()); - warningIconContainer->setPixmap(warningIcon); - warningIconContainer->setFixedWidth(warningIcon.width()); - levelEntitiesSaveQuestionLayout->addWidget(warningIconContainer); - - // Ask user if they want to save entities in level. - QLabel* prefabSaveQuestionLabel = new QLabel("Do you want to save the below unsaved prefabs?", closePrefabDialog.get()); - levelEntitiesSaveQuestionLayout->addWidget(prefabSaveQuestionLabel); - contentLayout->addWidget(prefabSaveWarningFrame); - - auto templateToSave = s_prefabSystemComponentInterface->FindTemplate(templateId); - AZ::IO::Path templateToSaveFilePath = templateToSave->get().GetFilePath(); - AZStd::unique_ptr unsavedPrefabsCard = ConstructUnsavedPrefabsCard(templateId); - contentLayout->addWidget(unsavedPrefabsCard.release()); - - contentLayout->addStretch(); - - QHBoxLayout* footerLayout = new QHBoxLayout(closePrefabDialog.get()); - - QDialogButtonBox* prefabSaveConfirmationButtons = new QDialogButtonBox( - QDialogButtonBox::Save | QDialogButtonBox::Discard | QDialogButtonBox::Cancel, closePrefabDialog.get()); - footerLayout->addWidget(prefabSaveConfirmationButtons); - contentLayout->addLayout(footerLayout); - QObject::connect(prefabSaveConfirmationButtons, &QDialogButtonBox::accepted, closePrefabDialog.get(), &QDialog::accept); - QObject::connect(prefabSaveConfirmationButtons, &QDialogButtonBox::rejected, closePrefabDialog.get(), &QDialog::reject); - QObject::connect( - prefabSaveConfirmationButtons, &QDialogButtonBox::clicked, closePrefabDialog.get(), - [closePrefabDialogWeakPtr, prefabSaveConfirmationButtons](QAbstractButton* button) - { - int prefabSaveSelection = prefabSaveConfirmationButtons->buttonRole(button); - closePrefabDialogWeakPtr.lock()->done(prefabSaveSelection); - }); - AzQtComponents::StyleManager::setStyleSheet(closePrefabDialog.get(), QStringLiteral("style:Editor.qss")); - closePrefabDialog->setLayout(contentLayout); - return closePrefabDialog; + m_prefabSaveHandler.ExecuteSavePrefabDialog(templateId, useSaveAllPrefabsPreference); } - AZStd::unique_ptr PrefabIntegrationManager::ConstructUnsavedPrefabsCard(TemplateId templateId) - { - FlowLayout* unsavedPrefabsLayout = new FlowLayout(nullptr); + } // namespace Prefab - AZStd::set dirtyTemplatePaths = s_prefabSystemComponentInterface->GetDirtyTemplatePaths(templateId); - - for (AZ::IO::PathView dirtyTemplatePath : dirtyTemplatePaths) - { - QLabel* prefabNameLabel = - new QLabel(QString("%1").arg(dirtyTemplatePath.Filename().Native().data()), AzToolsFramework::GetActiveWindow()); - prefabNameLabel->setObjectName(UnsavedPrefabFileName); - prefabNameLabel->setWordWrap(true); - prefabNameLabel->setToolTip(dirtyTemplatePath.Native().data()); - prefabNameLabel->setProperty("FilePath", dirtyTemplatePath.Native().data()); - unsavedPrefabsLayout->addWidget(prefabNameLabel); - } - - AZStd::unique_ptr unsavedPrefabsContainer = AZStd::make_unique(AzToolsFramework::GetActiveWindow()); - unsavedPrefabsContainer->setObjectName(SaveDependentPrefabsCard); - unsavedPrefabsContainer->setTitle("Unsaved Prefabs"); - unsavedPrefabsContainer->header()->setHasContextMenu(false); - unsavedPrefabsContainer->header()->setIcon(QIcon(QStringLiteral(":/Entity/prefab_edit.svg"))); - - QFrame* unsavedPrefabsFrame = new QFrame(unsavedPrefabsContainer.get()); - unsavedPrefabsFrame->setLayout(unsavedPrefabsLayout); - QScrollArea* unsavedPrefabsScrollArea = new QScrollArea(unsavedPrefabsContainer.get()); - unsavedPrefabsScrollArea->setWidget(unsavedPrefabsFrame); - unsavedPrefabsScrollArea->setWidgetResizable(true); - unsavedPrefabsContainer->setContentWidget(unsavedPrefabsScrollArea); - - return AZStd::move(unsavedPrefabsContainer); - } - } -} +} // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h index 800873349a..bb8fa227a8 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h @@ -9,23 +9,18 @@ #pragma once #include -#include #include -#include #include #include -#include -#include #include #include #include +#include #include #include #include -#include - namespace AzToolsFramework { class ContainerEntityInterface; @@ -35,30 +30,13 @@ namespace AzToolsFramework { class PrefabFocusPublicInterface; class PrefabLoaderInterface; - - //! Structure for saving/retrieving user settings related to prefab workflows. - class PrefabUserSettings - : public AZ::UserSettings - { - public: - AZ_CLASS_ALLOCATOR(PrefabUserSettings, AZ::SystemAllocator, 0); - AZ_RTTI(PrefabUserSettings, "{E17A6128-E2C3-4501-B1AD-B8BB0D315602}", AZ::UserSettings); - - AZStd::string m_saveLocation; - bool m_autoNumber = false; //!< Should the name of the prefab file be automatically numbered. e.g PrefabName_001.prefab vs PrefabName.prefab. - - PrefabUserSettings() = default; - - static void Reflect(AZ::ReflectContext* context); - }; + class PrefabPublicInterface; class PrefabIntegrationManager final : public EditorContextMenuBus::Handler , public EditorEventsBus::Handler - , public AssetBrowser::AssetBrowserSourceDropBus::Handler , public PrefabInstanceContainerNotificationBus::Handler , public PrefabIntegrationInterface - , public QObject , private EditorEntityContextNotificationBus::Handler { public: @@ -77,9 +55,6 @@ namespace AzToolsFramework // EditorEventsBus overrides ... void OnEscape() override; - // EntityOutlinerSourceDropHandlingBus overrides ... - void HandleSourceFileType(AZStd::string_view sourceFilePath, AZ::EntityId parentId, AZ::Vector3 position) const override; - // EditorEntityContextNotificationBus overrides ... void OnStartPlayInEditorBegin() override; void OnStopPlayInEditor() override; @@ -94,6 +69,9 @@ namespace AzToolsFramework void ExecuteSavePrefabDialog(TemplateId templateId, bool useSaveAllPrefabsPreference) override; private: + // Handles the UI for prefab save operations. + PrefabSaveHandler m_prefabSaveHandler; + // Used to handle the UI for the level root. LevelRootUiHandler m_levelRootUiHandler; @@ -120,27 +98,6 @@ namespace AzToolsFramework void InitializeShortcuts(); void UninitializeShortcuts(); - // Prompt and resolve dialogs - static bool QueryUserForPrefabSaveLocation( - const AZStd::string& suggestedName, const char* initialTargetDirectory, AZ::u32 prefabUserSettingsId, QWidget* activeWindow, - AZStd::string& outPrefabName, AZStd::string& outPrefabFilePath); - static bool QueryUserForPrefabFilePath(AZStd::string& outPrefabFilePath); - static bool QueryUserForProceduralPrefabAsset(AZStd::string& outPrefabAssetPath); - static void WarnUserOfError(AZStd::string_view title, AZStd::string_view message); - - // Path and filename generation - static void GenerateSuggestedFilenameFromEntities(const EntityIdList& entities, AZStd::string& outName); - static bool AppendEntityToSuggestedFilename(AZStd::string& filename, AZ::EntityId entityId); - - enum class PrefabSaveResult - { - Continue, - Retry, - Cancel - }; - static PrefabSaveResult IsPrefabPathValidForAssets(QWidget* activeWindow, QString prefabPath, AZStd::string& retrySavePath); - static void GenerateSuggestedPrefabPath(const AZStd::string& prefabName, const AZStd::string& targetDirectory, AZStd::string& suggestedFullPath); - // Reference detection static void GatherAllReferencedEntitiesAndCompare(const EntityIdSet& entities, EntityIdSet& entitiesAndReferencedEntities, bool& hasExternalReferences); @@ -148,20 +105,10 @@ namespace AzToolsFramework static bool QueryAndPruneMissingExternalReferences(EntityIdSet& entities, EntityIdSet& selectedAndReferencedEntities, bool& useReferencedEntities, bool defaultMoveExternalRefs = false); - // Settings management - static void SetPrefabSaveLocation(const AZStd::string& path, AZ::u32 settingsId); - static bool GetPrefabSaveLocation(AZStd::string& path, AZ::u32 settingsId); - static AZ::u32 GetSliceFlags(const AZ::Edit::ElementData* editData, const AZ::Edit::ClassData* classData); - AZStd::shared_ptr ConstructClosePrefabDialog(TemplateId templateId); - AZStd::unique_ptr ConstructUnsavedPrefabsCard(TemplateId templateId); - AZStd::unique_ptr ConstructSavePrefabDialog(TemplateId templateId, bool useSaveAllPrefabsPreference); - void SavePrefabsInDialog(QDialog* unsavedPrefabsDialog); - AZStd::vector> m_actions; - static const AZStd::string s_prefabFileExtension; static AzFramework::EntityContextId s_editorEntityContextId; static ContainerEntityInterface* s_containerEntityInterface; @@ -169,7 +116,6 @@ namespace AzToolsFramework static PrefabFocusPublicInterface* s_prefabFocusPublicInterface; static PrefabLoaderInterface* s_prefabLoaderInterface; static PrefabPublicInterface* s_prefabPublicInterface; - static PrefabSystemComponentInterface* s_prefabSystemComponentInterface; ReadOnlyEntityPublicInterface* m_readOnlyEntityPublicInterface = nullptr; }; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabSaveLoadHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabSaveLoadHandler.cpp new file mode 100644 index 0000000000..7234165076 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabSaveLoadHandler.cpp @@ -0,0 +1,722 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AzToolsFramework +{ + namespace Prefab + { + PrefabLoaderInterface* PrefabSaveHandler::s_prefabLoaderInterface = nullptr; + PrefabPublicInterface* PrefabSaveHandler::s_prefabPublicInterface = nullptr; + PrefabSystemComponentInterface* PrefabSaveHandler::s_prefabSystemComponentInterface = nullptr; + + const AZStd::string PrefabSaveHandler::s_prefabFileExtension = ".prefab"; + + static const char* const ClosePrefabDialog = "ClosePrefabDialog"; + static const char* const FooterSeparatorLine = "FooterSeparatorLine"; + static const char* const PrefabSavedMessageFrame = "PrefabSavedMessageFrame"; + static const char* const PrefabSavePreferenceHint = "PrefabSavePreferenceHint"; + static const char* const PrefabSaveWarningFrame = "PrefabSaveWarningFrame"; + static const char* const SaveDependentPrefabsCard = "SaveDependentPrefabsCard"; + static const char* const SavePrefabDialog = "SavePrefabDialog"; + static const char* const UnsavedPrefabFileName = "UnsavedPrefabFileName"; + + void PrefabUserSettings::Reflect(AZ::ReflectContext* context) + { + AZ::SerializeContext* serializeContext = azrtti_cast(context); + if (serializeContext) + { + serializeContext->Class() + ->Version(1) + ->Field("m_saveLocation", &PrefabUserSettings::m_saveLocation) + ->Field("m_autoNumber", &PrefabUserSettings::m_autoNumber); + } + } + + PrefabSaveHandler::PrefabSaveHandler() + { + s_prefabLoaderInterface = AZ::Interface::Get(); + if (s_prefabLoaderInterface == nullptr) + { + AZ_Assert(false, "Prefab - could not get PrefabLoaderInterface on PrefabSaveHandler construction."); + return; + } + + s_prefabPublicInterface = AZ::Interface::Get(); + if (s_prefabPublicInterface == nullptr) + { + AZ_Assert(false, "Prefab - could not get PrefabPublicInterface on PrefabSaveHandler construction."); + return; + } + + s_prefabSystemComponentInterface = AZ::Interface::Get(); + if (s_prefabSystemComponentInterface == nullptr) + { + AZ_Assert(false, "Prefab - could not get PrefabSystemComponentInterface on PrefabSaveHandler construction."); + return; + } + + AssetBrowser::AssetBrowserSourceDropBus::Handler::BusConnect(s_prefabFileExtension); + } + + PrefabSaveHandler::~PrefabSaveHandler() + { + AssetBrowser::AssetBrowserSourceDropBus::Handler::BusDisconnect(); + } + + bool PrefabSaveHandler::GetPrefabSaveLocation(AZStd::string& path, AZ::u32 settingsId) + { + auto settings = AZ::UserSettings::Find(settingsId, AZ::UserSettings::CT_LOCAL); + if (settings) + { + path = settings->m_saveLocation; + return true; + } + + return false; + } + + void PrefabSaveHandler::SetPrefabSaveLocation(const AZStd::string& path, AZ::u32 settingsId) + { + auto settings = AZ::UserSettings::CreateFind(settingsId, AZ::UserSettings::CT_LOCAL); + settings->m_saveLocation = path; + } + + void PrefabSaveHandler::HandleSourceFileType( + AZStd::string_view sourceFilePath, AZ::EntityId parentId, AZ::Vector3 position) const + { + auto instantiatePrefabOutcome = s_prefabPublicInterface->InstantiatePrefab(sourceFilePath, parentId, position); + + if (!instantiatePrefabOutcome.IsSuccess()) + { + WarningDialog("Prefab Instantiation Error", instantiatePrefabOutcome.GetError()); + } + } + + int PrefabSaveHandler::ExecuteClosePrefabDialog(TemplateId templateId) + { + if (s_prefabSystemComponentInterface->AreDirtyTemplatesPresent(templateId)) + { + auto prefabSaveSelectionDialog = ConstructClosePrefabDialog(templateId); + + int prefabSaveSelection = prefabSaveSelectionDialog->exec(); + + if (prefabSaveSelection == QDialog::Accepted) + { + SavePrefabsInDialog(prefabSaveSelectionDialog.get()); + } + + return prefabSaveSelection; + } + + return QDialogButtonBox::DestructiveRole; + } + + void PrefabSaveHandler::ExecuteSavePrefabDialog(TemplateId templateId, bool useSaveAllPrefabsPreference) + { + auto prefabTemplate = s_prefabSystemComponentInterface->FindTemplate(templateId); + AZ::IO::Path prefabTemplatePath = prefabTemplate->get().GetFilePath(); + + if (s_prefabSystemComponentInterface->IsTemplateDirty(templateId)) + { + if (s_prefabLoaderInterface->SaveTemplate(templateId) == false) + { + AZ_Error("Prefab", false, "Template '%s' could not be saved successfully.", prefabTemplatePath.c_str()); + return; + } + } + + if (s_prefabSystemComponentInterface->AreDirtyTemplatesPresent(templateId)) + { + if (useSaveAllPrefabsPreference) + { + SaveAllPrefabsPreference saveAllPrefabsPreference = s_prefabLoaderInterface->GetSaveAllPrefabsPreference(); + + if (saveAllPrefabsPreference == SaveAllPrefabsPreference::SaveAll) + { + s_prefabSystemComponentInterface->SaveAllDirtyTemplates(templateId); + return; + } + else if (saveAllPrefabsPreference == SaveAllPrefabsPreference::SaveNone) + { + return; + } + } + + AZStd::unique_ptr savePrefabDialog = ConstructSavePrefabDialog(templateId, useSaveAllPrefabsPreference); + if (savePrefabDialog) + { + int prefabSaveSelection = savePrefabDialog->exec(); + + if (prefabSaveSelection == QDialog::Accepted) + { + SavePrefabsInDialog(savePrefabDialog.get()); + } + } + } + } + + bool PrefabSaveHandler::QueryUserForPrefabFilePath(AZStd::string& outPrefabFilePath) + { + AssetSelectionModel selection; + + // Note, string filter will match every source file CONTAINING ".prefab". + // If this causes issues, we will need to create a new filter class for regex matching. + // We'll need to check if the file contents are actually a prefab later in the flow anyways, + // so this should not be an issue. + StringFilter* stringFilter = new StringFilter(); + stringFilter->SetName("Prefab"); + stringFilter->SetFilterString(".prefab"); + stringFilter->SetFilterPropagation(AssetBrowserEntryFilter::PropagateDirection::Down); + auto stringFilterPtr = FilterConstType(stringFilter); + + EntryTypeFilter* sourceFilter = new EntryTypeFilter(); + sourceFilter->SetName("Source"); + sourceFilter->SetEntryType(AssetBrowserEntry::AssetEntryType::Source); + sourceFilter->SetFilterPropagation(AssetBrowserEntryFilter::PropagateDirection::Down); + auto sourceFilterPtr = FilterConstType(sourceFilter); + + CompositeFilter* compositeFilter = new CompositeFilter(CompositeFilter::LogicOperatorType::AND); + compositeFilter->SetName("Prefab"); + compositeFilter->AddFilter(sourceFilterPtr); + compositeFilter->AddFilter(stringFilterPtr); + auto compositeFilterPtr = FilterConstType(compositeFilter); + + selection.SetDisplayFilter(compositeFilterPtr); + selection.SetSelectionFilter(compositeFilterPtr); + + AssetBrowserComponentRequestBus::Broadcast( + &AssetBrowserComponentRequests::PickAssets, selection, AzToolsFramework::GetActiveWindow()); + + if (!selection.IsValid()) + { + // User closed the dialog without selecting, just return. + return false; + } + + auto source = azrtti_cast(selection.GetResult()); + + if (source == nullptr) + { + AZ_Assert(false, "Prefab - Incorrect entry type selected during prefab instantiation. Expected source."); + return false; + } + + outPrefabFilePath = source->GetFullPath(); + return true; + } + + bool PrefabSaveHandler::QueryUserForProceduralPrefabAsset(AZStd::string& outPrefabAssetPath) + { + using namespace AzToolsFramework; + auto selection = AssetBrowser::AssetSelectionModel::AssetTypeSelection(azrtti_typeid()); + EditorRequests::Bus::Broadcast(&AzToolsFramework::EditorRequests::BrowseForAssets, selection); + + if (!selection.IsValid()) + { + return false; + } + + auto product = azrtti_cast(selection.GetResult()); + if (product == nullptr) + { + return false; + } + outPrefabAssetPath = product->GetRelativePath(); + return true; + } + + PrefabSaveHandler::PrefabSaveResult PrefabSaveHandler::IsPrefabPathValidForAssets( + QWidget* activeWindow, QString prefabPath, AZStd::string& retrySavePath) + { + bool assetSetFoldersRetrieved = false; + AZStd::vector assetSafeFolders; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult( + assetSetFoldersRetrieved, &AzToolsFramework::AssetSystemRequestBus::Events::GetAssetSafeFolders, assetSafeFolders); + + if (!assetSetFoldersRetrieved) + { + // If the asset safe list couldn't be retrieved, don't block the user but warn them. + AZ_Warning("Prefab", false, "Unable to verify that the prefab file to create is in a valid path."); + } + else + { + AZ::IO::FixedMaxPath lexicallyNormalPath = AZ::IO::PathView(prefabPath.toUtf8().constData()).LexicallyNormal(); + + bool isPathSafeForAssets = false; + for (const AZStd::string& assetSafeFolder : assetSafeFolders) + { + AZ::IO::PathView assetSafeFolderView(assetSafeFolder); + // Check if the prefabPath is relative to the safe asset directory. + // The Path classes are being used to make this check case insensitive. + if (lexicallyNormalPath.IsRelativeTo(assetSafeFolderView)) + { + isPathSafeForAssets = true; + break; + } + } + + if (!isPathSafeForAssets) + { + // Put an error in the console, so the log files have info about this error, or the user can look up the error after + // dismissing it. + AZStd::string errorMessage = + "You can only save prefabs to either your game project folder or the Gems folder. Update the location and try " + "again.\n\n" + "You can also review and update your save locations in the AssetProcessorPlatformConfig.ini file."; + AZ_Error("Prefab", false, errorMessage.c_str()); + + // Display a pop-up, the logs are easy to miss. This will make sure a user who encounters this error immediately knows + // their prefab save has failed. + QMessageBox msgBox(activeWindow); + msgBox.setIcon(QMessageBox::Icon::Warning); + msgBox.setTextFormat(Qt::RichText); + msgBox.setWindowTitle(QObject::tr("Invalid save location")); + msgBox.setText(QObject::tr(errorMessage.c_str())); + msgBox.setStandardButtons(QMessageBox::Cancel | QMessageBox::Retry); + msgBox.setDefaultButton(QMessageBox::Retry); + const int response = msgBox.exec(); + switch (response) + { + case QMessageBox::Retry: + // If the user wants to retry, they probably want to save to a valid location, + // so set the suggested save path to a known valid location. + if (assetSafeFolders.size() > 0) + { + retrySavePath = assetSafeFolders[0]; + } + return PrefabSaveResult::Retry; + case QMessageBox::Cancel: + default: + return PrefabSaveResult::Cancel; + } + } + } + // Valid prefab save location, continue with the save attempt. + return PrefabSaveResult::Continue; + } + + void PrefabSaveHandler::GenerateSuggestedPrefabPath( + const AZStd::string& prefabName, const AZStd::string& targetDirectory, AZStd::string& suggestedFullPath) + { + // Generate full suggested path from prefabName - if given NewPrefab as prefabName, + // NewPrefab_001.prefab would be tried, and if that already existed we would suggest + // the first unused number value (NewPrefab_002.prefab etc.) + AZStd::string normalizedTargetDirectory = targetDirectory; + AZ::StringFunc::Path::Normalize(normalizedTargetDirectory); + + // Convert spaces in entity names to underscores + AZStd::string prefabNameFiltered = prefabName; + AZ::StringFunc::Replace(prefabNameFiltered, ' ', '_'); + + auto settings = AZ::UserSettings::CreateFind(AZ_CRC("PrefabUserSettings"), AZ::UserSettings::CT_LOCAL); + if (settings->m_autoNumber) + { + AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance(); + + const AZ::u32 maxPrefabNumber = 1000; + for (AZ::u32 prefabNumber = 1; prefabNumber < maxPrefabNumber; ++prefabNumber) + { + AZStd::string possiblePath; + AZ::StringFunc::Path::Join( + normalizedTargetDirectory.c_str(), + AZStd::string::format("%s_%3.3u%s", prefabNameFiltered.c_str(), prefabNumber, s_prefabFileExtension.c_str()) + .c_str(), + possiblePath); + + if (!fileIO || !fileIO->Exists(possiblePath.c_str())) + { + suggestedFullPath = possiblePath; + break; + } + } + } + else + { + // use the entity name as the file name regardless of it already existing, the OS will ask the user to overwrite the file in + // that case. + AZ::StringFunc::Path::Join( + normalizedTargetDirectory.c_str(), + AZStd::string::format("%s%s", prefabNameFiltered.c_str(), s_prefabFileExtension.c_str()).c_str(), suggestedFullPath); + } + } + + bool PrefabSaveHandler::QueryUserForPrefabSaveLocation( + const AZStd::string& suggestedName, + const char* initialTargetDirectory, + AZ::u32 prefabUserSettingsId, + QWidget* activeWindow, + AZStd::string& outPrefabName, + AZStd::string& outPrefabFilePath) + { + AZStd::string saveAsInitialSuggestedDirectory; + if (!GetPrefabSaveLocation(saveAsInitialSuggestedDirectory, prefabUserSettingsId)) + { + saveAsInitialSuggestedDirectory = initialTargetDirectory; + } + + AZStd::string saveAsInitialSuggestedFullPath; + GenerateSuggestedPrefabPath(suggestedName, saveAsInitialSuggestedDirectory, saveAsInitialSuggestedFullPath); + + QString saveAs; + AZStd::string targetPath; + QFileInfo prefabSaveFileInfo; + QString prefabName; + while (true) + { + { + AZ_PROFILE_FUNCTION(AzToolsFramework); + saveAs = QFileDialog::getSaveFileName( + nullptr, QString("Save As..."), saveAsInitialSuggestedFullPath.c_str(), QString("Prefabs (*.prefab)")); + } + + prefabSaveFileInfo = saveAs; + prefabName = prefabSaveFileInfo.baseName(); + if (saveAs.isEmpty()) + { + return false; + } + + targetPath = saveAs.toUtf8().constData(); + if (AzFramework::StringFunc::Utf8::CheckNonAsciiChar(targetPath)) + { + WarningDialog( + "Prefab Creation Failed.", + "Unicode file name is not supported. \r\n" + "Please use ASCII characters to name your prefab."); + return false; + } + + PrefabSaveResult saveResult = IsPrefabPathValidForAssets(activeWindow, saveAs, saveAsInitialSuggestedFullPath); + if (saveResult == PrefabSaveResult::Cancel) + { + // The error was already reported if this failed. + return false; + } + else if (saveResult == PrefabSaveResult::Continue) + { + // The prefab save name is valid, continue with the save attempt. + break; + } + } + + // If the prefab already exists, notify the user and bail + AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance(); + if (fileIO && fileIO->Exists(targetPath.c_str())) + { + const AZStd::string message = AZStd::string::format( + "You are attempting to overwrite an existing prefab: \"%s\".\r\n\r\n" + "This will damage instances or cascades of this prefab. \r\n\r\n" + "Instead, either push entities/fields to the prefab, or save to a different location.", + targetPath.c_str()); + + WarningDialog("Prefab Already Exists", message); + return false; + } + + // We prevent users from creating a new prefab with the same relative path that's already + // been used by an existing prefab in other places (e.g. Gems) because the AssetProcessor + // generates asset ids based on relative paths. This is unnecessary once AssetProcessor + // starts to generate UUID to every asset regardless of paths. + { + AZStd::string prefabRelativeName; + bool relativePathFound; + AssetSystemRequestBus::BroadcastResult( + relativePathFound, &AssetSystemRequestBus::Events::GetRelativeProductPathFromFullSourceOrProductPath, targetPath, + prefabRelativeName); + + AZ::Data::AssetId prefabAssetId; + AZ::Data::AssetCatalogRequestBus::BroadcastResult( + prefabAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, prefabRelativeName.c_str(), + AZ::Data::s_invalidAssetType, false); + if (prefabAssetId.IsValid()) + { + const AZStd::string message = AZStd::string::format( + "A prefab with the relative path \"%s\" already exists in the Asset Database. \r\n\r\n" + "Overriding it will damage instances or cascades of this prefab. \r\n\r\n" + "Instead, either push entities/fields to the prefab, or save to a different location.", + prefabRelativeName.c_str()); + + WarningDialog("Prefab Path Error", message); + return false; + } + } + + AZStd::string saveDir(prefabSaveFileInfo.absoluteDir().absolutePath().toUtf8().constData()); + SetPrefabSaveLocation(saveDir, prefabUserSettingsId); + + outPrefabName = prefabName.toUtf8().constData(); + outPrefabFilePath = targetPath.c_str(); + + return true; + } + + void PrefabSaveHandler::GenerateSuggestedFilenameFromEntities(const EntityIdList& entityIds, AZStd::string& outName) + { + AZ_PROFILE_FUNCTION(AzToolsFramework); + + AZStd::string suggestedName; + + for (const AZ::EntityId& entityId : entityIds) + { + if (!AppendEntityToSuggestedFilename(suggestedName, entityId)) + { + break; + } + } + + if (suggestedName.size() == 0 || AzFramework::StringFunc::Utf8::CheckNonAsciiChar(suggestedName)) + { + suggestedName = "NewPrefab"; + } + + outName = suggestedName; + } + + bool PrefabSaveHandler::AppendEntityToSuggestedFilename(AZStd::string& filename, AZ::EntityId entityId) + { + // When naming a prefab after its entities, we stop appending additional names once we've reached this cutoff length + size_t prefabNameCutoffLength = 32; + AzToolsFramework::EntityIdSet usedNameEntities; + + if (usedNameEntities.find(entityId) == usedNameEntities.end()) + { + AZ::Entity* entity = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId); + if (entity) + { + AZStd::string entityNameFiltered = entity->GetName(); + + // Convert spaces in entity names to underscores + for (size_t i = 0; i < entityNameFiltered.size(); ++i) + { + char& character = entityNameFiltered.at(i); + if (character == ' ') + { + character = '_'; + } + } + + filename.append(entityNameFiltered); + usedNameEntities.insert(entityId); + if (filename.size() > prefabNameCutoffLength) + { + return false; + } + } + } + + return true; + } + + void PrefabSaveHandler::SavePrefabsInDialog(QDialog* unsavedPrefabsDialog) + { + QList unsavedPrefabFileLabels = unsavedPrefabsDialog->findChildren(UnsavedPrefabFileName); + if (unsavedPrefabFileLabels.size() > 0) + { + for (const QLabel* unsavedPrefabFileLabel : unsavedPrefabFileLabels) + { + AZStd::string unsavedPrefabFileName = unsavedPrefabFileLabel->property("FilePath").toString().toUtf8().data(); + AzToolsFramework::Prefab::TemplateId unsavedPrefabTemplateId = + s_prefabSystemComponentInterface->GetTemplateIdFromFilePath(unsavedPrefabFileName.data()); + [[maybe_unused]] bool isTemplateSavedSuccessfully = s_prefabLoaderInterface->SaveTemplate(unsavedPrefabTemplateId); + AZ_Error("Prefab", isTemplateSavedSuccessfully, "Prefab '%s' could not be saved successfully.", unsavedPrefabFileName.c_str()); + } + } + } + + AZStd::unique_ptr PrefabSaveHandler::ConstructSavePrefabDialog(TemplateId templateId, bool useSaveAllPrefabsPreference) + { + AZStd::unique_ptr savePrefabDialog = AZStd::make_unique(AzToolsFramework::GetActiveWindow()); + + savePrefabDialog->setWindowTitle("Unsaved files detected"); + + // Main Content section begins. + savePrefabDialog->setObjectName(SavePrefabDialog); + QBoxLayout* contentLayout = new QVBoxLayout(savePrefabDialog.get()); + + QFrame* prefabSavedMessageFrame = new QFrame(savePrefabDialog.get()); + QHBoxLayout* prefabSavedMessageLayout = new QHBoxLayout(savePrefabDialog.get()); + prefabSavedMessageFrame->setObjectName(PrefabSavedMessageFrame); + prefabSavedMessageFrame->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + + // Add a checkMark icon next to the level entities saved message. + QPixmap checkMarkIcon(QString(":/Notifications/checkmark.svg")); + QLabel* prefabSavedSuccessfullyIconContainer = new QLabel(savePrefabDialog.get()); + prefabSavedSuccessfullyIconContainer->setPixmap(checkMarkIcon); + prefabSavedSuccessfullyIconContainer->setFixedWidth(checkMarkIcon.width()); + + // Add a message that level entities are saved successfully. + + auto prefabTemplate = s_prefabSystemComponentInterface->FindTemplate(templateId); + AZ::IO::Path prefabTemplatePath = prefabTemplate->get().GetFilePath(); + QLabel* prefabSavedSuccessfullyLabel = new QLabel( + QString("Prefab '%1' has been saved. Do you want to save the below dependent prefabs too?").arg(prefabTemplatePath.c_str()), + savePrefabDialog.get()); + prefabSavedMessageLayout->addWidget(prefabSavedSuccessfullyIconContainer); + prefabSavedMessageLayout->addWidget(prefabSavedSuccessfullyLabel); + prefabSavedMessageFrame->setLayout(prefabSavedMessageLayout); + contentLayout->addWidget(prefabSavedMessageFrame); + + AZStd::unique_ptr unsavedPrefabsContainer = ConstructUnsavedPrefabsCard(templateId); + contentLayout->addWidget(unsavedPrefabsContainer.release()); + + contentLayout->addStretch(); + + // Footer section begins. + QHBoxLayout* footerLayout = new QHBoxLayout(savePrefabDialog.get()); + + if (useSaveAllPrefabsPreference) + { + QFrame* footerSeparatorLine = new QFrame(savePrefabDialog.get()); + footerSeparatorLine->setObjectName(FooterSeparatorLine); + footerSeparatorLine->setFrameShape(QFrame::HLine); + contentLayout->addWidget(footerSeparatorLine); + + QLabel* prefabSavePreferenceHint = new QLabel( + "You can prevent this window from showing in the future by updating your global save preferences.", + savePrefabDialog.get()); + prefabSavePreferenceHint->setToolTip( + "Go to 'Edit > Editor Settings > Global Preferences... > Global save preferences' to update your preference"); + prefabSavePreferenceHint->setObjectName(PrefabSavePreferenceHint); + footerLayout->addWidget(prefabSavePreferenceHint); + } + + QDialogButtonBox* prefabSaveConfirmationButtons = + new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::No, savePrefabDialog.get()); + footerLayout->addWidget(prefabSaveConfirmationButtons); + contentLayout->addLayout(footerLayout); + connect(prefabSaveConfirmationButtons, &QDialogButtonBox::accepted, savePrefabDialog.get(), &QDialog::accept); + connect(prefabSaveConfirmationButtons, &QDialogButtonBox::rejected, savePrefabDialog.get(), &QDialog::reject); + AzQtComponents::StyleManager::setStyleSheet(savePrefabDialog->parentWidget(), QStringLiteral("style:Editor.qss")); + + savePrefabDialog->setLayout(contentLayout); + return AZStd::move(savePrefabDialog); + } + + AZStd::shared_ptr PrefabSaveHandler::ConstructClosePrefabDialog(TemplateId templateId) + { + AZStd::shared_ptr closePrefabDialog = AZStd::make_shared(AzToolsFramework::GetActiveWindow()); + closePrefabDialog->setWindowTitle("Unsaved files detected"); + AZStd::weak_ptr closePrefabDialogWeakPtr(closePrefabDialog); + closePrefabDialog->setObjectName(ClosePrefabDialog); + + // Main Content section begins. + QVBoxLayout* contentLayout = new QVBoxLayout(closePrefabDialog.get()); + QFrame* prefabSaveWarningFrame = new QFrame(closePrefabDialog.get()); + QHBoxLayout* levelEntitiesSaveQuestionLayout = new QHBoxLayout(closePrefabDialog.get()); + prefabSaveWarningFrame->setObjectName(PrefabSaveWarningFrame); + + // Add a warning icon next to save prefab warning. + prefabSaveWarningFrame->setLayout(levelEntitiesSaveQuestionLayout); + QPixmap warningIcon(QString(":/Notifications/warning.svg")); + QLabel* warningIconContainer = new QLabel(closePrefabDialog.get()); + warningIconContainer->setPixmap(warningIcon); + warningIconContainer->setFixedWidth(warningIcon.width()); + levelEntitiesSaveQuestionLayout->addWidget(warningIconContainer); + + // Ask user if they want to save entities in level. + QLabel* prefabSaveQuestionLabel = new QLabel("Do you want to save the below unsaved prefabs?", closePrefabDialog.get()); + levelEntitiesSaveQuestionLayout->addWidget(prefabSaveQuestionLabel); + contentLayout->addWidget(prefabSaveWarningFrame); + + auto templateToSave = s_prefabSystemComponentInterface->FindTemplate(templateId); + AZ::IO::Path templateToSaveFilePath = templateToSave->get().GetFilePath(); + AZStd::unique_ptr unsavedPrefabsCard = ConstructUnsavedPrefabsCard(templateId); + contentLayout->addWidget(unsavedPrefabsCard.release()); + + contentLayout->addStretch(); + + QHBoxLayout* footerLayout = new QHBoxLayout(closePrefabDialog.get()); + + QDialogButtonBox* prefabSaveConfirmationButtons = new QDialogButtonBox( + QDialogButtonBox::Save | QDialogButtonBox::Discard | QDialogButtonBox::Cancel, closePrefabDialog.get()); + footerLayout->addWidget(prefabSaveConfirmationButtons); + contentLayout->addLayout(footerLayout); + QObject::connect(prefabSaveConfirmationButtons, &QDialogButtonBox::accepted, closePrefabDialog.get(), &QDialog::accept); + QObject::connect(prefabSaveConfirmationButtons, &QDialogButtonBox::rejected, closePrefabDialog.get(), &QDialog::reject); + QObject::connect( + prefabSaveConfirmationButtons, &QDialogButtonBox::clicked, closePrefabDialog.get(), + [closePrefabDialogWeakPtr, prefabSaveConfirmationButtons](QAbstractButton* button) + { + int prefabSaveSelection = prefabSaveConfirmationButtons->buttonRole(button); + closePrefabDialogWeakPtr.lock()->done(prefabSaveSelection); + }); + AzQtComponents::StyleManager::setStyleSheet(closePrefabDialog.get(), QStringLiteral("style:Editor.qss")); + closePrefabDialog->setLayout(contentLayout); + return closePrefabDialog; + } + + AZStd::unique_ptr PrefabSaveHandler::ConstructUnsavedPrefabsCard(TemplateId templateId) + { + FlowLayout* unsavedPrefabsLayout = new FlowLayout(nullptr); + + AZStd::set dirtyTemplatePaths = s_prefabSystemComponentInterface->GetDirtyTemplatePaths(templateId); + + for (AZ::IO::PathView dirtyTemplatePath : dirtyTemplatePaths) + { + QLabel* prefabNameLabel = + new QLabel(QString("%1").arg(dirtyTemplatePath.Filename().Native().data()), AzToolsFramework::GetActiveWindow()); + prefabNameLabel->setObjectName(UnsavedPrefabFileName); + prefabNameLabel->setWordWrap(true); + prefabNameLabel->setToolTip(dirtyTemplatePath.Native().data()); + prefabNameLabel->setProperty("FilePath", dirtyTemplatePath.Native().data()); + unsavedPrefabsLayout->addWidget(prefabNameLabel); + } + + AZStd::unique_ptr unsavedPrefabsContainer = AZStd::make_unique(AzToolsFramework::GetActiveWindow()); + unsavedPrefabsContainer->setObjectName(SaveDependentPrefabsCard); + unsavedPrefabsContainer->setTitle("Unsaved Prefabs"); + unsavedPrefabsContainer->header()->setHasContextMenu(false); + unsavedPrefabsContainer->header()->setIcon(QIcon(QStringLiteral(":/Entity/prefab_edit.svg"))); + + QFrame* unsavedPrefabsFrame = new QFrame(unsavedPrefabsContainer.get()); + unsavedPrefabsFrame->setLayout(unsavedPrefabsLayout); + QScrollArea* unsavedPrefabsScrollArea = new QScrollArea(unsavedPrefabsContainer.get()); + unsavedPrefabsScrollArea->setWidget(unsavedPrefabsFrame); + unsavedPrefabsScrollArea->setWidgetResizable(true); + unsavedPrefabsContainer->setContentWidget(unsavedPrefabsScrollArea); + + return AZStd::move(unsavedPrefabsContainer); + } + } +} diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabSaveLoadHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabSaveLoadHandler.h new file mode 100644 index 0000000000..0a7cd2c582 --- /dev/null +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabSaveLoadHandler.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +#include +#include +#include + +#include + +namespace AzToolsFramework +{ + namespace Prefab + { + class PrefabLoaderInterface; + class PrefabPublicInterface; + class PrefabSystemComponentInterface; + + //! Structure for saving/retrieving user settings related to prefab workflows. + class PrefabUserSettings : public AZ::UserSettings + { + public: + AZ_CLASS_ALLOCATOR(PrefabUserSettings, AZ::SystemAllocator, 0); + AZ_RTTI(PrefabUserSettings, "{E17A6128-E2C3-4501-B1AD-B8BB0D315602}", AZ::UserSettings); + + AZStd::string m_saveLocation; + bool m_autoNumber = + false; //!< Should the name of the prefab file be automatically numbered. e.g PrefabName_001.prefab vs PrefabName.prefab. + + PrefabUserSettings() = default; + + static void Reflect(AZ::ReflectContext* context); + }; + + //! Class to handle dialogs and windows related to prefab save operations. + class PrefabSaveHandler final + : public QObject + , public AssetBrowser::AssetBrowserSourceDropBus::Handler + { + public: + AZ_CLASS_ALLOCATOR(PrefabSaveHandler, AZ::SystemAllocator, 0); + + PrefabSaveHandler(); + ~PrefabSaveHandler(); + + // Settings management + static bool GetPrefabSaveLocation(AZStd::string& path, AZ::u32 settingsId); + static void SetPrefabSaveLocation(const AZStd::string& path, AZ::u32 settingsId); + + // EntityOutlinerSourceDropHandlingBus overrides ... + void HandleSourceFileType(AZStd::string_view sourceFilePath, AZ::EntityId parentId, AZ::Vector3 position) const override; + + // Dialogs + int ExecuteClosePrefabDialog(TemplateId templateId); + void ExecuteSavePrefabDialog(TemplateId templateId, bool useSaveAllPrefabsPreference); + static bool QueryUserForPrefabFilePath(AZStd::string& outPrefabFilePath); + static bool QueryUserForProceduralPrefabAsset(AZStd::string& outPrefabAssetPath); + static bool QueryUserForPrefabSaveLocation( + const AZStd::string& suggestedName, + const char* initialTargetDirectory, + AZ::u32 prefabUserSettingsId, + QWidget* activeWindow, + AZStd::string& outPrefabName, + AZStd::string& outPrefabFilePath); + + // Path and filename generation + enum class PrefabSaveResult + { + Continue, + Retry, + Cancel + }; + + static void GenerateSuggestedFilenameFromEntities(const EntityIdList& entities, AZStd::string& outName); + static bool AppendEntityToSuggestedFilename(AZStd::string& filename, AZ::EntityId entityId); + static PrefabSaveResult IsPrefabPathValidForAssets(QWidget* activeWindow, QString prefabPath, AZStd::string& retrySavePath); + static void GenerateSuggestedPrefabPath( + const AZStd::string& prefabName, const AZStd::string& targetDirectory, AZStd::string& suggestedFullPath); + + private: + AZStd::shared_ptr ConstructClosePrefabDialog(TemplateId templateId); + AZStd::unique_ptr ConstructUnsavedPrefabsCard(TemplateId templateId); + AZStd::unique_ptr ConstructSavePrefabDialog(TemplateId templateId, bool useSaveAllPrefabsPreference); + void SavePrefabsInDialog(QDialog* unsavedPrefabsDialog); + + static const AZStd::string s_prefabFileExtension; + + static PrefabLoaderInterface* s_prefabLoaderInterface; + static PrefabPublicInterface* s_prefabPublicInterface; + static PrefabSystemComponentInterface* s_prefabSystemComponentInterface; + }; + } +} diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/UICore/WidgetHelpers.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/UICore/WidgetHelpers.h index 0c326f9640..379a19739e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/UICore/WidgetHelpers.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/UICore/WidgetHelpers.h @@ -11,11 +11,12 @@ #include #include +#include namespace AzToolsFramework { - /// Gets the currently active window, or the Editor's main window if there is no active window. - /// Can be used to guarantee that modal dialogs have a valid parent in every context. + //! Gets the currently active window, or the Editor's main window if there is no active window. + //! Can be used to guarantee that modal dialogs have a valid parent in every context. inline QWidget* GetActiveWindow() { QWidget* activeWindow = QApplication::activeWindow(); @@ -26,5 +27,12 @@ namespace AzToolsFramework return activeWindow; } + + //! Create a simple modal dialog with a warning message. + inline void WarningDialog(AZStd::string_view title, AZStd::string_view message) + { + QMessageBox::warning(GetActiveWindow(), QString(title.data()), QString(message.data()), QMessageBox::Ok, QMessageBox::Ok); + } + } // namespace AzToolsFramework diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h index 0465a7920f..8d3cef890a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -36,8 +37,6 @@ namespace AzToolsFramework { class EditorVisibleEntityDataCacheInterface; - using EntityIdSet = AZStd::unordered_set; //!< Alias for unordered_set of EntityIds. - //! Entity related data required by manipulators during action. struct EntityIdManipulatorLookup { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake index 3947008ce7..e5e09f7cb3 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake @@ -153,6 +153,7 @@ set(FILES Entity/SliceEditorEntityOwnershipService.h Entity/SliceEditorEntityOwnershipService.cpp Entity/SliceEditorEntityOwnershipServiceBus.h + Entity/EntityTypes.h Entity/EntityUtilityComponent.h Entity/EntityUtilityComponent.cpp Entity/ReadOnly/ReadOnlyEntityInterface.h @@ -758,6 +759,8 @@ set(FILES UI/Prefab/PrefabIntegrationManager.h UI/Prefab/PrefabIntegrationManager.cpp UI/Prefab/PrefabIntegrationInterface.h + UI/Prefab/PrefabSaveLoadHandler.h + UI/Prefab/PrefabSaveLoadHandler.cpp UI/Prefab/PrefabUiHandler.h UI/Prefab/PrefabUiHandler.cpp UI/Prefab/PrefabViewportFocusPathHandler.h diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabAssetFixupTests.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabAssetFixupTests.cpp index 4f2a19faec..a0c8f7a317 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabAssetFixupTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabAssetFixupTests.cpp @@ -176,7 +176,7 @@ namespace UnitTest TEST_F(PrefabFixupTest, Test_LoadInstanceFromPrefabDom_Overload3) { Instance instance; - Instance::EntityList entityList; + AzToolsFramework::EntityList entityList; (PrefabDomUtils::LoadInstanceFromPrefabDom(instance, entityList, m_prefabDom)); CheckInstance(instance); diff --git a/Code/Tools/SerializeContextTools/SliceConverter.cpp b/Code/Tools/SerializeContextTools/SliceConverter.cpp index cfb1998f48..3d94a6e023 100644 --- a/Code/Tools/SerializeContextTools/SliceConverter.cpp +++ b/Code/Tools/SerializeContextTools/SliceConverter.cpp @@ -553,7 +553,7 @@ namespace AZ // Create a new unmodified prefab Instance for the nested slice instance. auto nestedInstance = AZStd::make_unique(AZStd::move(instanceAlias)); - AzToolsFramework::Prefab::Instance::EntityList newEntities; + AzToolsFramework::EntityList newEntities; if (!AzToolsFramework::Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom( *nestedInstance, newEntities, nestedTemplate->get().GetPrefabDom())) { diff --git a/Gems/Prefab/PrefabBuilder/PrefabBuilderComponent.cpp b/Gems/Prefab/PrefabBuilder/PrefabBuilderComponent.cpp index 71464501ba..a79f9e4cdb 100644 --- a/Gems/Prefab/PrefabBuilder/PrefabBuilderComponent.cpp +++ b/Gems/Prefab/PrefabBuilder/PrefabBuilderComponent.cpp @@ -76,7 +76,7 @@ namespace AZ::Prefab // Deserialize all of the entities and their components (for this prefab only) auto newInstance = AZStd::make_unique(); - AzToolsFramework::Prefab::Instance::EntityList entities; + AzToolsFramework::EntityList entities; if (AzToolsFramework::Prefab::PrefabDomUtils::LoadInstanceFromPrefabDom(*newInstance, entities, genericDocument)) { // Add the fingerprint of all the components and their types