diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp index 5ba7382831..8098727177 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.cpp @@ -86,6 +86,45 @@ namespace AzToolsFramework::Prefab return AZ::Success(); } + PrefabFocusOperationResult PrefabFocusHandler::FocusOnParentOfFocusedPrefab( + [[maybe_unused]] AzFramework::EntityContextId entityContextId) + { + // If only one instance is in the hierarchy, this operation is invalid + size_t hierarchySize = m_instanceFocusHierarchy.size(); + if (hierarchySize <= 1) + { + return AZ::Failure( + AZStd::string("Prefab Focus Handler: Could not complete FocusOnParentOfFocusedPrefab operation while focusing on the root.")); + } + + // Retrieve parent of currently focused prefab. + InstanceOptionalReference parentInstance = m_instanceFocusHierarchy[hierarchySize - 2]; + + // Use container entity of parent Instance for focus operations. + AZ::EntityId entityId = parentInstance->get().GetContainerEntityId(); + + // Initialize Undo Batch object + ScopedUndoBatch undoBatch("Edit Prefab"); + + // Clear selection + { + const EntityIdList selectedEntities = EntityIdList{}; + auto selectionUndo = aznew SelectionCommand(selectedEntities, "Clear Selection"); + selectionUndo->SetParent(undoBatch.GetUndoBatch()); + ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, selectedEntities); + } + + // Edit Prefab + { + auto editUndo = aznew PrefabFocusUndo("Edit Prefab"); + editUndo->Capture(entityId); + editUndo->SetParent(undoBatch.GetUndoBatch()); + FocusOnPrefabInstanceOwningEntityId(entityId); + } + + return AZ::Success(); + } + PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index) { if (index < 0 || index >= m_instanceFocusHierarchy.size()) diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h index 9decaed1ec..75b9666389 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusHandler.h @@ -50,6 +50,7 @@ namespace AzToolsFramework::Prefab // PrefabFocusPublicInterface overrides ... PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) override; + PrefabFocusOperationResult FocusOnParentOfFocusedPrefab(AzFramework::EntityContextId entityContextId) override; PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) override; AZ::EntityId GetFocusedPrefabContainerEntityId(AzFramework::EntityContextId entityContextId) const override; bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusPublicInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusPublicInterface.h index 5bd4c6b0f6..2fc9ef6b9a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusPublicInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabFocusPublicInterface.h @@ -30,6 +30,9 @@ namespace AzToolsFramework::Prefab //! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on. virtual PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) = 0; + //! Set the focused prefab instance to the parent of the currently focused prefab instance. Supports undo/redo. + virtual PrefabFocusOperationResult FocusOnParentOfFocusedPrefab(AzFramework::EntityContextId entityContextId) = 0; + //! Set the focused prefab instance to the instance at position index of the current path. Supports undo/redo. //! @param index The index of the instance in the current path that we want the prefab system to focus on. virtual PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) = 0; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp index bb6bdf0ebd..b34bbff298 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.cpp @@ -8,6 +8,7 @@ #include +#include #include #include @@ -92,4 +93,16 @@ namespace AzToolsFramework painter->drawLine(rect.bottomLeft(), rect.bottomRight()); painter->restore(); } + + bool LevelRootUiHandler::OnEntityDoubleClick(AZ::EntityId entityId) const + { + if (auto prefabFocusPublicInterface = AZ::Interface::Get(); + !prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId)) + { + prefabFocusPublicInterface->FocusOnOwningPrefab(entityId); + } + + // Don't propagate event. + return true; + } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h index 1e485572b8..3f7f56670e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/LevelRootUiHandler.h @@ -33,6 +33,7 @@ namespace AzToolsFramework bool CanToggleLockVisibility(AZ::EntityId entityId) const override; bool CanRename(AZ::EntityId entityId) const override; void PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + bool OnEntityDoubleClick(AZ::EntityId entityId) const override; private: Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp index ae0d18b077..951b876347 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -61,6 +62,8 @@ namespace AzToolsFramework { namespace Prefab { + AzFramework::EntityContextId PrefabIntegrationManager::s_editorEntityContextId = AzFramework::EntityContextId::CreateNull(); + ContainerEntityInterface* PrefabIntegrationManager::s_containerEntityInterface = nullptr; EditorEntityUiInterface* PrefabIntegrationManager::s_editorEntityUiInterface = nullptr; PrefabFocusPublicInterface* PrefabIntegrationManager::s_prefabFocusPublicInterface = nullptr; @@ -136,6 +139,9 @@ namespace AzToolsFramework return; } + // Get EditorEntityContextId + EditorEntityContextRequestBus::BroadcastResult(s_editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId); + // Initialize Editor functionality for the Prefab Focus Handler auto prefabFocusInterface = AZ::Interface::Get(); prefabFocusInterface->InitializeEditorInterfaces(); @@ -145,10 +151,14 @@ namespace AzToolsFramework PrefabInstanceContainerNotificationBus::Handler::BusConnect(); AZ::Interface::Register(this); AssetBrowser::AssetBrowserSourceDropBus::Handler::BusConnect(s_prefabFileExtension); + + InitializeShortcuts(); } PrefabIntegrationManager::~PrefabIntegrationManager() { + UninitializeShortcuts(); + AssetBrowser::AssetBrowserSourceDropBus::Handler::BusDisconnect(); AZ::Interface::Unregister(this); PrefabInstanceContainerNotificationBus::Handler::BusDisconnect(); @@ -161,6 +171,74 @@ namespace AzToolsFramework PrefabUserSettings::Reflect(context); } + void PrefabIntegrationManager::InitializeShortcuts() + { + // Open/Edit Prefab (+) + // We also support = to enable easier editing on compact US keyboards. + { + m_actions.emplace_back(AZStd::make_unique(nullptr)); + + m_actions.back()->setShortcuts({ QKeySequence(Qt::Key_Plus), QKeySequence(Qt::Key_Equal) }); + m_actions.back()->setText("Open/Edit Prefab"); + m_actions.back()->setStatusTip("Edit the prefab in focus mode."); + + QObject::connect( + m_actions.back().get(), &QAction::triggered, m_actions.back().get(), + [] + { + AzToolsFramework::EntityIdList selectedEntities; + AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult( + selectedEntities, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities); + + if (selectedEntities.size() != 1) + { + return; + } + + AZ::EntityId selectedEntity = selectedEntities[0]; + + if (!s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity)) + { + return; + } + + if (!s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity)) + { + ContextMenu_EditPrefab(selectedEntity); + } + }); + + EditorActionRequestBus::Broadcast( + &EditorActionRequests::AddActionViaBusCrc, AZ_CRC_CE("com.o3de.action.editortransform.prefabopen"), + m_actions.back().get()); + } + + // Close Prefab (-) + { + m_actions.emplace_back(AZStd::make_unique(nullptr)); + + m_actions.back()->setShortcuts({ QKeySequence(Qt::Key_Minus) }); + m_actions.back()->setText("Close Prefab"); + m_actions.back()->setStatusTip("Close focus mode for this prefab and move one level up."); + + QObject::connect( + m_actions.back().get(), &QAction::triggered, m_actions.back().get(), + [] + { + ContextMenu_ClosePrefab(); + }); + + EditorActionRequestBus::Broadcast( + &EditorActionRequests::AddActionViaBusCrc, AZ_CRC_CE("com.o3de.action.editortransform.prefabclose"), + m_actions.back().get()); + } + } + + void PrefabIntegrationManager::UninitializeShortcuts() + { + m_actions.clear(); + } + int PrefabIntegrationManager::GetMenuPosition() const { return aznumeric_cast(EditorContextMenuOrdering::MIDDLE); @@ -181,16 +259,13 @@ namespace AzToolsFramework AzFramework::ApplicationRequests::Bus::BroadcastResult( prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled); - auto editorEntityContextId = AzFramework::EntityContextId::CreateNull(); - EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId); - // Create Prefab { if (!selectedEntities.empty()) { // Hide if the only selected entity is the Focused Instance Container if (selectedEntities.size() > 1 || - selectedEntities[0] != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId)) + selectedEntities[0] != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(s_editorEntityContextId)) { bool layerInSelection = false; @@ -254,17 +329,30 @@ namespace AzToolsFramework if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity)) { - // Edit Prefab if (!s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity)) { - QAction* editAction = menu->addAction(QObject::tr("Edit Prefab")); + // Edit Prefab + QAction* editAction = menu->addAction(QObject::tr("Open/Edit Prefab")); + editAction->setShortcut(QKeySequence(Qt::Key_Plus)); editAction->setToolTip(QObject::tr("Edit the prefab in focus mode.")); QObject::connect(editAction, &QAction::triggered, editAction, [selectedEntity] { ContextMenu_EditPrefab(selectedEntity); }); + } + else + { + // Close Prefab + QAction* closeAction = menu->addAction(QObject::tr("Close Prefab")); + closeAction->setShortcut(QKeySequence(Qt::Key_Minus)); + closeAction->setToolTip(QObject::tr("Close focus mode for this prefab and move one level up.")); - itemWasShown = true; + QObject::connect( + closeAction, &QAction::triggered, closeAction, + [] + { + ContextMenu_ClosePrefab(); + }); } // Save Prefab @@ -279,9 +367,9 @@ namespace AzToolsFramework QObject::connect(saveAction, &QAction::triggered, saveAction, [selectedEntity] { ContextMenu_SavePrefab(selectedEntity); }); - - itemWasShown = true; } + + itemWasShown = true; } } } @@ -295,7 +383,8 @@ namespace AzToolsFramework QObject::connect(deleteAction, &QAction::triggered, deleteAction, [] { ContextMenu_DeleteSelected(); }); if (selectedEntities.empty() || - (selectedEntities.size() == 1 && selectedEntities[0] == s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId))) + (selectedEntities.size() == 1 && + selectedEntities[0] == s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(s_editorEntityContextId))) { deleteAction->setDisabled(true); } @@ -306,7 +395,7 @@ namespace AzToolsFramework AZ::EntityId selectedEntityId = selectedEntities[0]; if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntityId) && - selectedEntityId != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId)) + selectedEntityId != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(s_editorEntityContextId)) { QAction* detachPrefabAction = menu->addAction(QObject::tr("Detach Prefab...")); QObject::connect( @@ -343,12 +432,9 @@ namespace AzToolsFramework const AZStd::string prefabFilesPath = "@projectroot@/Prefabs"; // Remove focused instance container entity if it's part of the list - auto editorEntityContextId = AzFramework::EntityContextId::CreateNull(); - EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId); - auto focusedContainerIter = AZStd::find( selectedEntities.begin(), selectedEntities.end(), - s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId)); + s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(s_editorEntityContextId)); if (focusedContainerIter != selectedEntities.end()) { selectedEntities.erase(focusedContainerIter); @@ -500,6 +586,11 @@ namespace AzToolsFramework } } + void PrefabIntegrationManager::ContextMenu_ClosePrefab() + { + s_prefabFocusPublicInterface->FocusOnParentOfFocusedPrefab(s_editorEntityContextId); + } + void PrefabIntegrationManager::ContextMenu_EditPrefab(AZ::EntityId containerEntity) { s_prefabFocusPublicInterface->FocusOnOwningPrefab(containerEntity); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h index e8c10c150a..a8d325c4cf 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabIntegrationManager.h @@ -96,11 +96,16 @@ namespace AzToolsFramework static void ContextMenu_CreatePrefab(AzToolsFramework::EntityIdList selectedEntities); static void ContextMenu_InstantiatePrefab(); static void ContextMenu_InstantiateProceduralPrefab(); + static void ContextMenu_ClosePrefab(); static void ContextMenu_EditPrefab(AZ::EntityId containerEntity); static void ContextMenu_SavePrefab(AZ::EntityId containerEntity); static void ContextMenu_DeleteSelected(); static void ContextMenu_DetachPrefab(AZ::EntityId containerEntity); + // Shortcut setup handlers + void InitializeShortcuts(); + void UninitializeShortcuts(); + // Prompt and resolve dialogs static bool QueryUserForPrefabSaveLocation( const AZStd::string& suggestedName, const char* initialTargetDirectory, AZ::u32 prefabUserSettingsId, QWidget* activeWindow, @@ -140,7 +145,10 @@ namespace AzToolsFramework 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; static EditorEntityUiInterface* s_editorEntityUiInterface; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp index 447f94fc15..8bd1b7db04 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.cpp @@ -21,6 +21,8 @@ namespace AzToolsFramework { + AzFramework::EntityContextId PrefabUiHandler::s_editorEntityContextId = AzFramework::EntityContextId::CreateNull(); + const QColor PrefabUiHandler::m_backgroundColor = QColor("#444444"); const QColor PrefabUiHandler::m_backgroundHoverColor = QColor("#5A5A5A"); const QColor PrefabUiHandler::m_backgroundSelectedColor = QColor("#656565"); @@ -47,6 +49,9 @@ namespace AzToolsFramework AZ_Assert(false, "PrefabUiHandler - could not get PrefabFocusPublicInterface on PrefabUiHandler construction."); return; } + + // Get EditorEntityContextId + EditorEntityContextRequestBus::BroadcastResult(s_editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId); } QString PrefabUiHandler::GenerateItemInfoString(AZ::EntityId entityId) const @@ -425,19 +430,23 @@ namespace AzToolsFramework if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId)) { - auto editorEntityContextId = AzFramework::EntityContextId::CreateNull(); - EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId); - - // Go one level up. - int length = m_prefabFocusPublicInterface->GetPrefabFocusPathLength(editorEntityContextId); - m_prefabFocusPublicInterface->FocusOnPathIndex(editorEntityContextId, length - 2); + // Close this prefab and focus on the parent + m_prefabFocusPublicInterface->FocusOnParentOfFocusedPrefab(s_editorEntityContextId); } } bool PrefabUiHandler::OnEntityDoubleClick(AZ::EntityId entityId) const { - // Focus on this prefab - m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId); + if (!m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId)) + { + // Focus on this prefab + m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId); + } + else + { + // Close this prefab and focus on the parent + m_prefabFocusPublicInterface->FocusOnParentOfFocusedPrefab(s_editorEntityContextId); + } // Don't propagate event. return true; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h index 6c78afc5b7..3627449ab4 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabUiHandler.h @@ -10,6 +10,8 @@ #include +#include + namespace AzToolsFramework { @@ -49,6 +51,8 @@ namespace AzToolsFramework static QModelIndex GetLastVisibleChild(const QModelIndex& parent); static QModelIndex Internal_GetLastVisibleChild(const QAbstractItemModel* model, const QModelIndex& index); + static AzFramework::EntityContextId s_editorEntityContextId; + static constexpr int m_prefabCapsuleRadius = 6; static constexpr int m_prefabBorderThickness = 2; static const QColor m_backgroundColor; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.cpp index 52cf3279a4..920e99665d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.cpp @@ -59,12 +59,11 @@ namespace AzToolsFramework::Prefab connect(m_backButton, &QToolButton::clicked, this, [&]() { - if (int length = m_prefabFocusPublicInterface->GetPrefabFocusPathLength(m_editorEntityContextId); length > 1) - { - m_prefabFocusPublicInterface->FocusOnPathIndex(m_editorEntityContextId, length - 2); - } + m_prefabFocusPublicInterface->FocusOnParentOfFocusedPrefab(m_editorEntityContextId); } ); + + m_backButton->setToolTip("Up one level (-)"); } void PrefabViewportFocusPathHandler::OnPrefabFocusChanged()