Files
o3de/Code/Editor/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp
T
amzn-phist d6b268e84e Remove ResourceSelectorHost and clean up/refactor related bits (#3050)
* Sever dependency on legacy resource selector host

Audio resource selectors (browse dialogs) no longer need to be
registered with the legacy IResourceSelectorHost system.  Set up a new
EBus specifically to handle browse button presses and directly invokes
the dialog.

Signed-off-by: amzn-phist <52085794+amzn-phist@users.noreply.github.com>

* Hook up legacy audio control selector to new EBus

Remaining use of legacy audio selectors (trackview) need to be able to
bypass ResourceSelectorHost now.

Signed-off-by: amzn-phist <52085794+amzn-phist@users.noreply.github.com>

* Removes ResourceSelectorHost and legacy selectors

This removes various Variable types that were tied to resource
selectors, such as GeomCache, Model, Animation, File.  Removes the
ResourceSelectorHost completely.  The two things that still appeared to
have selectors in TrackView are Audio Controls and Texture.  Fixed the
audio control selector to work via EBus and the Texture selector didn't
seem to work at all, but left it in as it was.

Signed-off-by: amzn-phist <52085794+amzn-phist@users.noreply.github.com>

* Make the default audio selector return old value

Signed-off-by: amzn-phist <52085794+amzn-phist@users.noreply.github.com>

* Fix some signed/unsigned comparison warnings

Signed-off-by: amzn-phist <52085794+amzn-phist@users.noreply.github.com>

* Remove deleted function from Editor Mock

Signed-off-by: amzn-phist <52085794+amzn-phist@users.noreply.github.com>

* Change audio selector api to use string_view

Per feedback.

Signed-off-by: amzn-phist <52085794+amzn-phist@users.noreply.github.com>
2021-08-16 12:41:24 -05:00

2007 lines
77 KiB
C++

/*
* 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 "SandboxIntegration.h"
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/RTTI/AttributeReader.h>
#include <AzCore/Slice/SliceComponent.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/algorithm.h>
#include <AzCore/std/numeric.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/Interface/Interface.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Asset/AssetSystemBus.h>
#include <AzFramework/Entity/EntityContextBus.h>
#include <AzFramework/Physics/Material.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/Visibility/BoundsBus.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/API/EditorEntityAPI.h>
#include <AzToolsFramework/API/EntityCompositionRequestBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
#include <AzToolsFramework/AssetBrowser/AssetSelectionModel.h>
#include <AzToolsFramework/Commands/EntityStateCommand.h>
#include <AzToolsFramework/Commands/SelectionCommand.h>
#include <AzToolsFramework/Commands/SliceDetachEntityCommand.h>
#include <AzToolsFramework/Editor/EditorContextMenuBus.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/Entity/SliceEditorEntityOwnershipServiceBus.h>
#include <AzToolsFramework/Slice/SliceRequestBus.h>
#include <AzToolsFramework/Slice/SliceUtilities.h>
#include <AzToolsFramework/ToolsComponents/GenericComponentWrapper.h>
#include <AzToolsFramework/ToolsComponents/EditorLayerComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorVisibilityComponent.h>
#include <AzToolsFramework/Undo/UndoSystem.h>
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiInterface.h>
#include <AzToolsFramework/UI/Layer/AddToLayerMenu.h>
#include <AzToolsFramework/UI/Prefab/PrefabIntegrationInterface.h>
#include <AzToolsFramework/UI/PropertyEditor/InstanceDataHierarchy.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
#include <AzToolsFramework/UI/PropertyEditor/EntityPropertyEditor.hxx>
#include <AzToolsFramework/UI/Layer/NameConflictWarning.hxx>
#include <AzToolsFramework/ViewportSelection/EditorHelpers.h>
#include <MathConversion.h>
#include <Atom/RPI.Public/ViewportContext.h>
#include <Atom/RPI.Public/ViewportContextBus.h>
#include <AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h>
#include "Objects/ComponentEntityObject.h"
#include "ISourceControl.h"
#include "UI/QComponentEntityEditorMainWindow.h"
#include <LmbrCentral/Rendering/EditorLightComponentBus.h>
#include <LmbrCentral/Scripting/TagComponentBus.h>
#include <LmbrCentral/Scripting/EditorTagComponentBus.h>
// Sandbox imports.
#include <Editor/ActionManager.h>
#include <Editor/CryEditDoc.h>
#include <Editor/GameEngine.h>
#include <Editor/DisplaySettings.h>
#include <Editor/IconManager.h>
#include <Editor/Settings.h>
#include <Editor/StringDlg.h>
#include <Editor/QtViewPaneManager.h>
#include <Editor/EditorViewportSettings.h>
#include <Editor/Util/PathUtil.h>
#include "CryEdit.h"
#include "Undo/Undo.h"
#include <QMenu>
#include <QAction>
#include <QWidgetAction>
#include <QHBoxLayout>
#include "MainWindow.h"
#include "Include/IObjectManager.h"
#include <AzCore/std/algorithm.h>
#ifdef CreateDirectory
#undef CreateDirectory
#endif
//////////////////////////////////////////////////////////////////////////
// Gathers all selected entities, culling any that have an ancestor in the selection.
void GetSelectedEntitiesSetWithFlattenedHierarchy(AzToolsFramework::EntityIdSet& out)
{
AzToolsFramework::EntityIdList entities;
AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(
entities,
&AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities);
for (const AZ::EntityId& entityId : entities)
{
bool selectionIncludesTransformHeritage = false;
AZ::EntityId parent = entityId;
do
{
AZ::EntityId nextParentId;
AZ::TransformBus::EventResult(
/*result*/ nextParentId,
/*address*/ parent,
&AZ::TransformBus::Events::GetParentId);
parent = nextParentId;
if (!parent.IsValid())
{
break;
}
for (const AZ::EntityId& parentCheck : entities)
{
if (parentCheck == parent)
{
selectionIncludesTransformHeritage = true;
break;
}
}
} while (parent.IsValid() && !selectionIncludesTransformHeritage);
if (!selectionIncludesTransformHeritage)
{
out.insert(entityId);
}
}
}
SandboxIntegrationManager::SandboxIntegrationManager()
: m_inObjectPickMode(false)
, m_startedUndoRecordingNestingLevel(0)
, m_dc(nullptr)
, m_notificationWindowManager(new AzToolsFramework::SliceOverridesNotificationWindowManager())
{
// Required to receive events from the Cry Engine undo system
GetIEditor()->GetUndoManager()->AddListener(this);
// Only create the PrefabIntegrationManager if prefabs are enabled
bool prefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
prefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (prefabSystemEnabled)
{
m_prefabIntegrationManager = aznew AzToolsFramework::Prefab::PrefabIntegrationManager();
}
}
SandboxIntegrationManager::~SandboxIntegrationManager()
{
GetIEditor()->GetUndoManager()->RemoveListener(this);
delete m_prefabIntegrationManager;
m_prefabIntegrationManager = nullptr;
}
void SandboxIntegrationManager::Setup()
{
AzFramework::AssetCatalogEventBus::Handler::BusConnect();
AzToolsFramework::ToolsApplicationEvents::Bus::Handler::BusConnect();
AzToolsFramework::EditorRequests::Bus::Handler::BusConnect();
AzToolsFramework::EditorWindowRequests::Bus::Handler::BusConnect();
AzToolsFramework::EditorContextMenuBus::Handler::BusConnect();
AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusConnect();
AzToolsFramework::SliceEditorEntityOwnershipServiceNotificationBus::Handler::BusConnect();
AzFramework::DisplayContextRequestBus::Handler::BusConnect();
MainWindow::instance()->GetActionManager()->RegisterActionHandler(ID_FILE_SAVE_SLICE_TO_ROOT, [this]() {
SaveSlice(false);
});
MainWindow::instance()->GetActionManager()->RegisterActionHandler(ID_FILE_SAVE_SELECTED_SLICE, [this]() {
SaveSlice(true);
});
// Keep a reference to the interface EditorEntityUiInterface.
// This is used to register layer entities to their UI handler when the layer component is activated.
m_editorEntityUiInterface = AZ::Interface<AzToolsFramework::EditorEntityUiInterface>::Get();
AZ_Assert((m_editorEntityUiInterface != nullptr),
"SandboxIntegrationManager requires a EditorEntityUiInterface instance to be present on Setup().");
bool prefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
prefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (prefabSystemEnabled)
{
m_prefabIntegrationInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabIntegrationInterface>::Get();
AZ_Assert(
(m_prefabIntegrationInterface != nullptr),
"SandboxIntegrationManager requires a PrefabIntegrationInterface instance to be present on Setup().");
}
m_editorEntityAPI = AZ::Interface<AzToolsFramework::EditorEntityAPI>::Get();
AZ_Assert(m_editorEntityAPI, "SandboxIntegrationManager requires an EditorEntityAPI instance to be present on Setup().");
AzToolsFramework::Layers::EditorLayerComponentNotificationBus::Handler::BusConnect();
}
void SandboxIntegrationManager::SaveSlice(const bool& QuickPushToFirstLevel)
{
AzToolsFramework::EntityIdList selectedEntities;
GetSelectedEntities(selectedEntities);
if (selectedEntities.size() == 0)
{
m_notificationWindowManager->CreateNotificationWindow(AzToolsFramework::SliceOverridesNotificationWindow::EType::TypeError, "Nothing selected - Select a slice entity with overrides and try again");
return;
}
AzToolsFramework::EntityIdList relevantEntities;
AZ::u32 entitiesInSlices;
AZStd::vector<AZ::SliceComponent::SliceInstanceAddress> sliceInstances;
GetEntitiesInSlices(selectedEntities, entitiesInSlices, sliceInstances);
if (entitiesInSlices > 0)
{
// Gather the set of relevant entities from the selected entities and all descendants
AzToolsFramework::EntityIdSet relevantEntitiesSet;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(relevantEntitiesSet,
&AzToolsFramework::ToolsApplicationRequestBus::Events::GatherEntitiesAndAllDescendents, selectedEntities);
relevantEntities.reserve(relevantEntitiesSet.size());
for (AZ::EntityId& id : relevantEntitiesSet)
{
relevantEntities.push_back(id);
}
}
int numEntitiesToAdd = 0;
int numEntitiesToRemove = 0;
int numEntitiesToUpdate = 0;
if (AzToolsFramework::SliceUtilities::SaveSlice(relevantEntities, numEntitiesToAdd, numEntitiesToRemove, numEntitiesToUpdate, QuickPushToFirstLevel))
{
if (numEntitiesToAdd > 0 || numEntitiesToRemove > 0 || numEntitiesToUpdate > 0)
{
m_notificationWindowManager->CreateNotificationWindow(
AzToolsFramework::SliceOverridesNotificationWindow::EType::TypeSuccess,
QString("Save slice to parent - %1 saved successfully").arg(numEntitiesToUpdate + numEntitiesToAdd + numEntitiesToRemove));
}
else
{
m_notificationWindowManager->CreateNotificationWindow(
AzToolsFramework::SliceOverridesNotificationWindow::EType::TypeError,
"Selected has no overrides - Select a slice entity with overrides and try again");
}
}
else
{
m_notificationWindowManager->CreateNotificationWindow(
AzToolsFramework::SliceOverridesNotificationWindow::EType::TypeError,
"Save slice to parent - Failed");
}
}
// This event handler is queued on main thread.
void SandboxIntegrationManager::OnCatalogAssetAdded(const AZ::Data::AssetId& assetId)
{
bool prefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
prefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (!prefabSystemEnabled)
{
AZ::SliceComponent* editorRootSlice = nullptr;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(
editorRootSlice, &AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
AZ_Assert(editorRootSlice, "Editor root slice missing!");
for (auto restoreItr = m_sliceAssetDeletionErrorRestoreInfos.begin(); restoreItr != m_sliceAssetDeletionErrorRestoreInfos.end();)
{
if (restoreItr->m_assetId == assetId)
{
for (const auto& entityRestore : restoreItr->m_entityRestoreInfos)
{
const AZ::EntityId& entityId = entityRestore.first;
const AZ::SliceComponent::EntityRestoreInfo& restoreInfo = entityRestore.second;
AZ::Entity* entity = editorRootSlice->FindEntity(entityId);
if (entity)
{
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Broadcast(
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::RestoreSliceEntity, entity, restoreInfo,
AzToolsFramework::SliceEntityRestoreType::Detached);
}
else
{
AZ_Error(
"DetachSliceEntity", entity, "Unable to find previous detached entity of Id %s. Cannot undo \"Detach\" action.",
entityId.ToString().c_str());
}
}
restoreItr = m_sliceAssetDeletionErrorRestoreInfos.erase(restoreItr);
}
else
{
restoreItr++;
}
}
}
}
// No mutex is used for now because the only
// operation writing to shared resource is queued on main thread.
void SandboxIntegrationManager::OnCatalogAssetRemoved(const AZ::Data::AssetId& assetId, const AZ::Data::AssetInfo& assetInfo)
{
bool isPrefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
// Check to see if the removed slice asset has any instance in the level, then check if
// those dangling instances are directly under the root slice (not sub-slices). If yes,
// detach them and save necessary information so they can be restored when their slice asset
// comes back.
if (!isPrefabSystemEnabled && assetInfo.m_assetType == AZ::AzTypeInfo<AZ::SliceAsset>::Uuid())
{
AZ::SliceComponent* rootSlice = nullptr;
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::BroadcastResult(rootSlice,
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Events::GetEditorRootSlice);
AZ_Assert(rootSlice, "Editor root slice missing!");
AZStd::vector<AZ::EntityId> entitiesToDetach;
const AZ::SliceComponent::SliceList& subSlices = rootSlice->GetSlices();
for (const AZ::SliceComponent::SliceReference& subSliceRef : subSlices)
{
if (subSliceRef.GetSliceAsset().GetId() == assetId)
{
for (const AZ::SliceComponent::SliceInstance& sliceInst : subSliceRef.GetInstances())
{
const AZ::SliceComponent::InstantiatedContainer* instContainer = sliceInst.GetInstantiated();
for (AZ::Entity* entity : instContainer->m_entities)
{
entitiesToDetach.emplace_back(entity->GetId());
}
}
}
}
AZ_Error("Editor", false, "The slice asset %s is deleted from disk, to prevent further data corruption, all of its root level slice instances are detached. "
"Restoring the slice asset on disk will revert the detaching operation.", assetInfo.m_relativePath.c_str());
AZ::SystemTickBus::QueueFunction([this, assetId, entitiesToDetach]() {
AZStd::vector<AZStd::pair<AZ::EntityId, AZ::SliceComponent::EntityRestoreInfo>> restoreInfos;
bool detachSuccess = false;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
detachSuccess, &AzToolsFramework::ToolsApplicationRequestBus::Events::DetachEntities, entitiesToDetach, restoreInfos);
if (detachSuccess)
{
m_sliceAssetDeletionErrorRestoreInfos.emplace_back(assetId, AZStd::move(restoreInfos));
}
});
}
}
void SandboxIntegrationManager::GetEntitiesInSlices(
const AzToolsFramework::EntityIdList& selectedEntities,
AZ::u32& entitiesInSlices,
AZStd::vector<AZ::SliceComponent::SliceInstanceAddress>& sliceInstances)
{
// Identify all slice instances affected by the selected entity set.
entitiesInSlices = 0;
for (const AZ::EntityId& entityId : selectedEntities)
{
AZ::SliceComponent::SliceInstanceAddress sliceAddress;
AzFramework::SliceEntityRequestBus::EventResult(sliceAddress, entityId,
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
if (sliceAddress.IsValid())
{
++entitiesInSlices;
if (sliceInstances.end() == AZStd::find(sliceInstances.begin(), sliceInstances.end(), sliceAddress))
{
sliceInstances.push_back(sliceAddress);
}
}
}
}
void SandboxIntegrationManager::Teardown()
{
AzToolsFramework::Layers::EditorLayerComponentNotificationBus::Handler::BusDisconnect();
AzFramework::DisplayContextRequestBus::Handler::BusDisconnect();
AzToolsFramework::SliceEditorEntityOwnershipServiceNotificationBus::Handler::BusDisconnect();
AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusDisconnect();
AzToolsFramework::EditorContextMenuBus::Handler::BusDisconnect();
AzToolsFramework::EditorWindowRequests::Bus::Handler::BusDisconnect();
AzToolsFramework::EditorRequests::Bus::Handler::BusDisconnect();
AzToolsFramework::ToolsApplicationEvents::Bus::Handler::BusDisconnect();
}
void SandboxIntegrationManager::SetDC(DisplayContext* dc)
{
m_dc = dc;
}
DisplayContext* SandboxIntegrationManager::GetDC()
{
return m_dc;
}
void SandboxIntegrationManager::OnBeginUndo([[maybe_unused]] const char* label)
{
AzToolsFramework::UndoSystem::URSequencePoint* currentBatch = nullptr;
EBUS_EVENT_RESULT(currentBatch, AzToolsFramework::ToolsApplicationRequests::Bus, GetCurrentUndoBatch);
AZ_Assert(currentBatch, "No undo batch is active.");
// Only generate a Sandbox placeholder for root-level undo batches.
if (nullptr == currentBatch->GetParent())
{
// start Cry Undo
if (!CUndo::IsRecording())
{
GetIEditor()->BeginUndo();
// flag that we started recording the undo batch
m_startedUndoRecordingNestingLevel = 1;
}
}
else
{
if (m_startedUndoRecordingNestingLevel)
{
// if we previously started recording the undo, increment the nesting level so we can
// detect when we need to accept the undo in OnEndUndo()
m_startedUndoRecordingNestingLevel++;
}
}
}
void SandboxIntegrationManager::OnEndUndo(const char* label, bool changed)
{
// Add the undo only after we know it's got a legit change, we can't remove undos from the cry undo system so we do it here instead of OnBeginUndo
if (changed && CUndo::IsRecording())
{
CUndo::Record(new CToolsApplicationUndoLink(label));
}
if (m_startedUndoRecordingNestingLevel)
{
m_startedUndoRecordingNestingLevel--;
if (m_startedUndoRecordingNestingLevel == 0)
{
if (changed)
{
// only accept the undo batch that we initially started undo recording on
GetIEditor()->AcceptUndo(label);
}
else
{
GetIEditor()->CancelUndo();
}
}
}
}
void SandboxIntegrationManager::EntityParentChanged(
const AZ::EntityId entityId,
const AZ::EntityId newParentId,
const AZ::EntityId oldParentId)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
if (m_unsavedEntities.find(entityId) != m_unsavedEntities.end())
{
// New layers need the level to be saved.
bool isEntityLayer = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
isEntityLayer,
entityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
if (isEntityLayer)
{
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
entityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::AddLevelSaveDependency);
}
// Don't need to track any other unsaved changes, this is a new entity that hasn't been saved yet.
return;
}
// If an entity is moved to or from a layer, then that layer can only safely be saved when the other layer or level saves, to prevent
// accidental duplication of entities.
// This logic doesn't clear the dependency flag if an entity changes parents multiple times between saves, so if an entity visits many layers
// before finally being saved, it will result in all of those layers saving, too.
AZ::EntityId oldAncestor = oldParentId;
bool wasNotInLayer = false;
AZ::EntityId oldLayer;
do
{
if (!oldAncestor.IsValid())
{
wasNotInLayer = true;
break;
}
bool isOldAncestorLayer = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
isOldAncestorLayer,
oldAncestor,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
if (isOldAncestorLayer)
{
oldLayer = oldAncestor;
break;
}
// Must pass in an invalid id, because if no parent then nothing will modify the id variable to be invalid and stop at the no-parent case
AZ::EntityId nextParentId;
AZ::TransformBus::EventResult(
/*result*/ nextParentId,
/*address*/ oldAncestor,
&AZ::TransformBus::Events::GetParentId);
oldAncestor = nextParentId;
} while (oldAncestor.IsValid());
AZ::EntityId newAncestors = newParentId;
AZ::EntityId newAncestor = newParentId;
bool isGoingToRootScene = false;
AZ::EntityId newLayer;
do
{
if (!newAncestor.IsValid())
{
isGoingToRootScene = true;
break;
}
bool isNewAncestorLayer = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
isNewAncestorLayer,
newAncestor,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
if (isNewAncestorLayer)
{
newLayer = newAncestor;
break;
}
// The parent may not be connected to the bus yet, so start with an invalid entity ID
// to prevent an infinite loop.
AZ::EntityId ancestorParent;
AZ::TransformBus::EventResult(
/*result*/ ancestorParent,
/*address*/ newAncestor,
&AZ::TransformBus::Events::GetParentId);
newAncestor = ancestorParent;
} while (newAncestor.IsValid());
if (oldLayer.IsValid() && newLayer != oldLayer)
{
if (newLayer.IsValid())
{
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
oldLayer,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::AddLayerSaveDependency,
newLayer);
}
else
{
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
oldLayer,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::AddLevelSaveDependency);
}
}
if (newLayer.IsValid() && newLayer != oldLayer)
{
if (oldLayer.IsValid())
{
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
newLayer,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::AddLayerSaveDependency,
oldLayer);
}
else
{
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
newLayer,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::AddLevelSaveDependency);
}
}
}
void SandboxIntegrationManager::OnSaveLevel()
{
m_unsavedEntities.clear();
}
int SandboxIntegrationManager::GetMenuPosition() const
{
return aznumeric_cast<int>(AzToolsFramework::EditorContextMenuOrdering::TOP);
}
void SandboxIntegrationManager::PopulateEditorGlobalContextMenu(QMenu* menu, const AZ::Vector2& point, int flags)
{
if (!IsLevelDocumentOpen())
{
return;
}
if (flags & AzToolsFramework::EditorEvents::eECMF_USE_VIEWPORT_CENTER)
{
CViewport* view = GetIEditor()->GetViewManager()->GetGameViewport();
int width = 0;
int height = 0;
// If there is no 3D Viewport active to aid in the positioning of context menu
// operations, we don't need to store anything but default values here. Any code
// using these numbers for placement should default to the origin when there's
// no 3D viewport to raycast into.
if (view)
{
view->GetDimensions(&width, &height);
}
m_contextMenuViewPoint.Set(width / 2, height / 2);
}
else
{
m_contextMenuViewPoint = point;
}
CGameEngine* gameEngine = GetIEditor()->GetGameEngine();
if (!gameEngine || !gameEngine->IsLevelLoaded())
{
return;
}
menu->setToolTipsVisible(true);
AzToolsFramework::EntityIdList selected;
GetSelectedOrHighlightedEntities(selected);
QAction* action = nullptr;
action = menu->addAction(QObject::tr("Create entity"));
QObject::connect(action, &QAction::triggered, action, [this] { ContextMenu_NewEntity(); });
if (selected.size() == 1)
{
action = menu->addAction(QObject::tr("Create child entity"));
QObject::connect(action, &QAction::triggered, action, [selected]
{
EBUS_EVENT(AzToolsFramework::EditorRequests::Bus, CreateNewEntityAsChild, selected.front());
});
}
bool prefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(prefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (!prefabSystemEnabled)
{
menu->addSeparator();
action = menu->addAction(QObject::tr("Create layer"));
QObject::connect(action, &QAction::triggered, [this] { ContextMenu_NewLayer(); });
AzToolsFramework::EntityIdList entities;
AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(
entities,
&AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities);
SetupLayerContextMenu(menu);
AzToolsFramework::EntityIdSet flattenedSelection = AzToolsFramework::GetCulledEntityHierarchy(entities);
AzToolsFramework::SetupAddToLayerMenu(menu, flattenedSelection, [this] { return ContextMenu_NewLayer(); });
SetupSliceContextMenu(menu);
}
action = menu->addAction(QObject::tr("Duplicate"));
QObject::connect(action, &QAction::triggered, action, [this] { ContextMenu_Duplicate(); });
if (selected.size() == 0)
{
action->setDisabled(true);
}
if (!prefabSystemEnabled)
{
action = menu->addAction(QObject::tr("Delete"));
QObject::connect(action, &QAction::triggered, action, [this] { ContextMenu_DeleteSelected(); });
if (selected.size() == 0)
{
action->setDisabled(true);
}
}
menu->addSeparator();
if (selected.size() > 0)
{
action = menu->addAction(QObject::tr("Open pinned Inspector"));
QObject::connect(action, &QAction::triggered, action, [this, selected]
{
AzToolsFramework::EntityIdSet pinnedEntities(selected.begin(), selected.end());
OpenPinnedInspector(pinnedEntities);
});
if (selected.size() > 0)
{
action = menu->addAction(QObject::tr("Find in Entity Outliner"));
QObject::connect(action, &QAction::triggered, [this, selected]
{
AzToolsFramework::EditorEntityContextNotificationBus::Broadcast(&EditorEntityContextNotification::OnFocusInEntityOutliner, selected);
});
}
menu->addSeparator();
}
}
void SandboxIntegrationManager::OpenPinnedInspector(const AzToolsFramework::EntityIdSet& entities)
{
QDockWidget* dockWidget = InstanceViewPane(LyViewPane::EntityInspectorPinned);
if (dockWidget)
{
QComponentEntityEditorInspectorWindow* editor = static_cast<QComponentEntityEditorInspectorWindow*>(dockWidget->widget());
if (editor && editor->GetPropertyEditor())
{
editor->GetPropertyEditor()->SetOverrideEntityIds(entities);
AZStd::string widgetTitle;
if (entities.size() == 1)
{
AZStd::string entityName;
AZ::ComponentApplicationBus::BroadcastResult(entityName, &AZ::ComponentApplicationBus::Events::GetEntityName, *entities.begin());
widgetTitle = AZStd::string::format("%s Inspector", entityName.c_str());
QObject::connect(editor->GetPropertyEditor(), &AzToolsFramework::EntityPropertyEditor::SelectedEntityNameChanged, [dockWidget](const AZ::EntityId& /*entityId*/, const AZStd::string& name)
{
AZStd::string newTitle = AZStd::string::format("%s Inspector", name.c_str());
dockWidget->setWindowTitle(newTitle.c_str());
});
}
else
{
widgetTitle = AZStd::string::format("%zu Entities - Inspector", entities.size());
}
dockWidget->setWindowTitle(widgetTitle.c_str());
}
}
}
void SandboxIntegrationManager::ClosePinnedInspector(AzToolsFramework::EntityPropertyEditor* editor)
{
QWidget* currentWidget = editor->parentWidget();
while (currentWidget)
{
QDockWidget* dockWidget = qobject_cast<QDockWidget*>(currentWidget);
if (dockWidget)
{
QtViewPaneManager::instance()->ClosePaneInstance(LyViewPane::EntityInspectorPinned, dockWidget);
return;
}
currentWidget = currentWidget->parentWidget();
}
}
void SandboxIntegrationManager::SetupLayerContextMenu(QMenu* menu)
{
// First verify that the selection contains some layers.
AzToolsFramework::EntityIdList selectedEntities;
GetSelectedOrHighlightedEntities(selectedEntities);
// Nothing selected, no layer context menus to add.
if (selectedEntities.empty())
{
return;
}
AZStd::unordered_set<AZ::EntityId> layersInSelection;
for (AZ::EntityId entityId : selectedEntities)
{
bool isLayerEntity = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
isLayerEntity,
entityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
if (isLayerEntity)
{
layersInSelection.insert(entityId);
}
}
// No layers directly selected, do not add context, even if a selected entity
// is a child of a layer.
if (layersInSelection.empty())
{
return;
}
menu->addSeparator();
const int selectedLayerCount = static_cast<int>(layersInSelection.size());
QString saveTitle = QObject::tr("Save layer");
if(selectedLayerCount > 1)
{
saveTitle = QObject::tr("Save %0 layers").arg(selectedLayerCount);
}
QAction* saveLayerAction = menu->addAction(saveTitle);
saveLayerAction->setToolTip(QObject::tr("Save the selected layers."));
QObject::connect(saveLayerAction, &QAction::triggered, [this, layersInSelection] { ContextMenu_SaveLayers(layersInSelection); });
if (layersInSelection.size() == 1)
{
const AZ::EntityId& id = *layersInSelection.begin();
AZ::Outcome<AZStd::string, AZStd::string> layerFullFilePathResult = AZ::Failure(AZStd::string());
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
layerFullFilePathResult,
id,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::GetLayerFullFilePath,
Path::GetPath(GetIEditor()->GetDocument()->GetActivePathName()));
// Only add option to find the layer in the Asset Browser if the layer has been saved to disk
if (layerFullFilePathResult.IsSuccess())
{
AZStd::string fullFilePath = layerFullFilePathResult.GetValue();
QAction* findLayerAssetAction = menu->addAction(QObject::tr("Find layer in Asset Browser"));
findLayerAssetAction->setToolTip(QObject::tr("Selects this layer in the Asset Browser"));
QObject::connect(findLayerAssetAction, &QAction::triggered, [this, fullFilePath] {
QtViewPaneManager::instance()->OpenPane(LyViewPane::AssetBrowser);
AzToolsFramework::AssetBrowser::AssetBrowserViewRequestBus::Broadcast(
&AzToolsFramework::AssetBrowser::AssetBrowserViewRequestBus::Events::ClearFilter);
AzToolsFramework::AssetBrowser::AssetBrowserViewRequestBus::Broadcast(
&AzToolsFramework::AssetBrowser::AssetBrowserViewRequestBus::Events::SelectFileAtPath,
fullFilePath);
});
}
}
}
void SandboxIntegrationManager::SetupSliceContextMenu(QMenu* menu)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Editor);
AzToolsFramework::EntityIdList selectedEntities;
GetSelectedOrHighlightedEntities(selectedEntities);
menu->addSeparator();
if (!selectedEntities.empty())
{
bool anySelectedEntityFromExistingSlice = false;
bool layerInSelection = false;
for (AZ::EntityId entityId : selectedEntities)
{
if (!anySelectedEntityFromExistingSlice)
{
AZ::SliceComponent::SliceInstanceAddress sliceAddress(nullptr, nullptr);
AzFramework::SliceEntityRequestBus::EventResult(sliceAddress, entityId,
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
if (sliceAddress.GetReference())
{
anySelectedEntityFromExistingSlice = true;
}
}
if (!layerInSelection)
{
bool isLayerEntity = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
isLayerEntity,
entityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
if (isLayerEntity)
{
layerInSelection = true;
}
}
// Everything being searched for was found, so exit the loop.
if (layerInSelection && anySelectedEntityFromExistingSlice)
{
break;
}
}
// Layers can't be in slices.
if (!layerInSelection)
{
QAction* createAction = menu->addAction(QObject::tr("Create slice..."));
createAction->setToolTip(QObject::tr("Creates a slice out of the currently selected entities."));
if (anySelectedEntityFromExistingSlice)
{
QObject::connect(createAction, &QAction::triggered, createAction, [this, selectedEntities] { ContextMenu_MakeSlice(selectedEntities); });
}
else
{
QObject::connect(createAction, &QAction::triggered, createAction, [this, selectedEntities] { ContextMenu_InheritSlice(selectedEntities); });
}
}
}
QAction* instantiateAction = menu->addAction(QObject::tr("Instantiate slice..."));
instantiateAction->setToolTip(QObject::tr("Instantiates a pre-existing slice asset into the level"));
QObject::connect(instantiateAction, &QAction::triggered, instantiateAction, [this] { ContextMenu_InstantiateSlice(); });
AzToolsFramework::EditorEvents::Bus::Broadcast(&AzToolsFramework::EditorEvents::Bus::Events::PopulateEditorGlobalContextMenu_SliceSection, menu, AZ::Vector2::CreateZero(),
AzToolsFramework::EditorEvents::eECMF_HIDE_ENTITY_CREATION | AzToolsFramework::EditorEvents::eECMF_USE_VIEWPORT_CENTER);
if (selectedEntities.empty())
{
return;
}
AZ::u32 entitiesInSlices;
AZStd::vector<AZ::SliceComponent::SliceInstanceAddress> sliceInstances;
GetEntitiesInSlices(selectedEntities, entitiesInSlices, sliceInstances);
// Offer slice-related options if any selected entities belong to slice instances.
if (0 == entitiesInSlices)
{
return;
}
// Setup push and revert options (quick push and 'advanced' push UI).
SetupSliceContextMenu_Modify(menu, selectedEntities, entitiesInSlices);
menu->addSeparator();
// PopulateFindSliceMenu takes a callback to run when a slice is selected, which is called before the slice is selected in the asset browser.
// This is so the AssetBrowser can be opened first, which can only be done from a Sandbox module. PopulateFindSliceMenu exists in the AzToolsFramework
// module in SliceUtilities, so it can share logic with similar menus, like Quick Push.
// Similarly, it takes a callback for the SliceRelationshipView.
AzToolsFramework::SliceUtilities::PopulateSliceSubMenus(*menu, selectedEntities,
[]
{
// This will open the AssetBrowser if it's not open, and do nothing if it's already opened.
QtViewPaneManager::instance()->OpenPane(LyViewPane::AssetBrowser);
},
[]
{
//open SliceRelationshipView if necessary, and populate it
QtViewPaneManager::instance()->OpenPane(LyViewPane::SliceRelationships);
});
}
void SandboxIntegrationManager::SetupSliceContextMenu_Modify(QMenu* menu, const AzToolsFramework::EntityIdList& selectedEntities, [[maybe_unused]] const AZ::u32 numEntitiesInSlices)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Editor);
using namespace AzToolsFramework;
// Gather the set of relevant entities from the selected entities and all descendants
AzToolsFramework::EntityIdSet relevantEntitiesSet;
EBUS_EVENT_RESULT(relevantEntitiesSet, AzToolsFramework::ToolsApplicationRequests::Bus, GatherEntitiesAndAllDescendents, selectedEntities);
AzToolsFramework::EntityIdList relevantEntities;
relevantEntities.reserve(relevantEntitiesSet.size());
for (AZ::EntityId& id : relevantEntitiesSet)
{
relevantEntities.push_back(id);
}
SliceUtilities::PopulateQuickPushMenu(*menu, relevantEntities);
SliceUtilities::PopulateDetachMenu(*menu, selectedEntities, relevantEntitiesSet);
bool canRevert = false;
for (AZ::EntityId id : relevantEntitiesSet)
{
bool entityHasOverrides = false;
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(entityHasOverrides, id, &AzToolsFramework::EditorEntityInfoRequestBus::Events::HasSliceEntityOverrides);
if (entityHasOverrides)
{
canRevert = true;
break;
}
}
QAction* revertAction = menu->addAction(QObject::tr("Revert overrides"));
revertAction->setToolTip(QObject::tr("Revert all slice entities and their children to their last saved state."));
QObject::connect(revertAction, &QAction::triggered, [this, relevantEntities]
{
ContextMenu_ResetToSliceDefaults(relevantEntities);
});
revertAction->setEnabled(canRevert);
}
void SandboxIntegrationManager::HandleObjectModeSelection(const AZ::Vector2& point, [[maybe_unused]] int flags, bool& handled)
{
// Todo - Use a custom "edit tool". This will eliminate the need for this bus message entirely, which technically
// makes this feature less intrusive on Sandbox.
// UPDATE: This is now provided by EditorPickEntitySelection when the new Viewport Interaction Model changes are enabled.
if (m_inObjectPickMode)
{
CViewport* view = GetIEditor()->GetViewManager()->GetGameViewport();
const QPoint viewPoint(point.GetX(), point.GetY());
HitContext hitInfo;
hitInfo.view = view;
if (view->HitTest(viewPoint, hitInfo))
{
if (hitInfo.object && (hitInfo.object->GetType() == OBJTYPE_AZENTITY))
{
CComponentEntityObject* entityObject = static_cast<CComponentEntityObject*>(hitInfo.object);
AzToolsFramework::EditorPickModeRequestBus::Broadcast(
&AzToolsFramework::EditorPickModeRequests::PickModeSelectEntity, entityObject->GetAssociatedEntityId());
}
}
AzToolsFramework::EditorPickModeRequestBus::Broadcast(
&AzToolsFramework::EditorPickModeRequests::StopEntityPickMode);
handled = true;
}
}
void SandboxIntegrationManager::UpdateObjectModeCursor(AZ::u32& cursorId, AZStd::string& cursorStr)
{
if (m_inObjectPickMode)
{
cursorId = static_cast<AZ::u64>(STD_CURSOR_HAND);
cursorStr = "Pick an entity...";
}
}
void SandboxIntegrationManager::OnEntityPickModeStarted()
{
m_inObjectPickMode = true;
// Currently this object pick mode is activated only via PropertyEntityIdCtrl picker.
// When the picker button is clicked, we transfer focus to the viewport so the
// spacebar can still be used to activate selection helpers.
if (CViewport* view = GetIEditor()->GetViewManager()->GetGameViewport())
{
view->SetFocus();
}
}
void SandboxIntegrationManager::OnEntityPickModeStopped()
{
m_inObjectPickMode = false;
}
void SandboxIntegrationManager::CreateEditorRepresentation(AZ::Entity* entity)
{
IEditor* editor = GetIEditor();
if (CComponentEntityObject* existingObject = CComponentEntityObject::FindObjectForEntity(entity->GetId()))
{
// Refresh sandbox object if one already exists for this AZ::EntityId.
existingObject->AssignEntity(entity, false);
return;
}
CBaseObjectPtr object = editor->NewObject("ComponentEntity", "", entity->GetName().c_str(), 0.f, 0.f, 0.f, false);
if (object)
{
static_cast<CComponentEntityObject*>(object.get())->AssignEntity(entity);
// If this object type was hidden by category, re-display it.
int hideMask = editor->GetDisplaySettings()->GetObjectHideMask();
hideMask = hideMask & ~(object->GetType());
editor->GetDisplaySettings()->SetObjectHideMask(hideMask);
}
}
bool SandboxIntegrationManager::DestroyEditorRepresentation(AZ::EntityId entityId, bool deleteAZEntity)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
IEditor* editor = GetIEditor();
if (editor->GetObjectManager())
{
CEntityObject* object = nullptr;
EBUS_EVENT_ID_RESULT(object, entityId, AzToolsFramework::ComponentEntityEditorRequestBus, GetSandboxObject);
if (object && (object->GetType() == OBJTYPE_AZENTITY))
{
static_cast<CComponentEntityObject*>(object)->AssignEntity(nullptr, deleteAZEntity);
{
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "SandboxIntegrationManager::DestroyEditorRepresentation:ObjManagerDeleteObject");
editor->GetObjectManager()->DeleteObject(object);
}
return true;
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
void SandboxIntegrationManager::GoToSelectedOrHighlightedEntitiesInViewports()
{
AzToolsFramework::EntityIdList entityIds;
GetSelectedOrHighlightedEntities(entityIds);
GoToEntitiesInViewports(entityIds);
}
//////////////////////////////////////////////////////////////////////////
void SandboxIntegrationManager::GoToSelectedEntitiesInViewports()
{
AzToolsFramework::EntityIdList entityIds;
GetSelectedEntities(entityIds);
GoToEntitiesInViewports(entityIds);
}
bool SandboxIntegrationManager::CanGoToSelectedEntitiesInViewports()
{
AzToolsFramework::EntityIdList entityIds;
GetSelectedEntities(entityIds);
if (entityIds.size() == 0)
{
return false;
}
for (const AZ::EntityId& entityId : entityIds)
{
if (CanGoToEntityOrChildren(entityId))
{
return true;
}
}
return false;
}
bool SandboxIntegrationManager::CanGoToEntityOrChildren(const AZ::EntityId& entityId) const
{
bool isLayerEntity = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
isLayerEntity,
entityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
// If this entity is not a layer
if (!isLayerEntity)
{
// check if the entity exists to determine if we can go to it (e.g. system & internal entities are not visible in the viewport)
AZ::Entity* entity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId);
return entity != nullptr;
}
AZStd::vector<AZ::EntityId> layerChildren;
AZ::TransformBus::EventResult(
/*result*/ layerChildren,
/*address*/ entityId,
&AZ::TransformBus::Events::GetChildren);
for (const AZ::EntityId& childId : layerChildren)
{
if (CanGoToEntityOrChildren(childId))
{
return true;
}
}
return false;
}
AZ::Vector3 SandboxIntegrationManager::GetWorldPositionAtViewportCenter()
{
if (GetIEditor() && GetIEditor()->GetViewManager())
{
CViewport* view = GetIEditor()->GetViewManager()->GetGameViewport();
if (view)
{
int width, height;
view->GetDimensions(&width, &height);
return LYVec3ToAZVec3(view->ViewToWorld(QPoint(width / 2, height / 2)));
}
}
return AZ::Vector3::CreateZero();
}
int SandboxIntegrationManager::GetIconTextureIdFromEntityIconPath(const AZStd::string& entityIconPath)
{
return GetIEditor()->GetIconManager()->GetIconTexture(entityIconPath.c_str());
}
void SandboxIntegrationManager::ClearRedoStack()
{
// We have two separate undo systems that are assumed to be kept in sync,
// So here we tell the legacy Undo system to clear the redo stack and at the same time
// tell the new AZ undo system to clear redo stack ("slice" the stack)
// Clear legacy redo stack
GetIEditor()->ClearRedoStack();
// Clear AZ redo stack
AzToolsFramework::UndoSystem::UndoStack* undoStack = nullptr;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(undoStack, &AzToolsFramework::ToolsApplicationRequestBus::Events::GetUndoStack);
if (undoStack)
{
undoStack->Slice();
}
}
//////////////////////////////////////////////////////////////////////////
void SandboxIntegrationManager::CloneSelection(bool& handled)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
AzToolsFramework::EntityIdList entities;
AzToolsFramework::ToolsApplicationRequests::Bus::BroadcastResult(
entities,
&AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities);
AzToolsFramework::EntityIdSet duplicationSet = AzToolsFramework::GetCulledEntityHierarchy(entities);
if (!duplicationSet.empty())
{
bool prefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(prefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (prefabSystemEnabled)
{
m_editorEntityAPI->DuplicateSelected();
handled = true;
}
else
{
AZStd::unordered_set<AZ::EntityId> clonedEntities;
handled = AzToolsFramework::CloneInstantiatedEntities(duplicationSet, clonedEntities);
m_unsavedEntities.insert(clonedEntities.begin(), clonedEntities.end());
}
}
else
{
handled = false;
}
}
void SandboxIntegrationManager::DeleteSelectedEntities([[maybe_unused]] const bool includeDescendants)
{
AzToolsFramework::EntityIdList selectedEntityIds;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
selectedEntityIds, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities);
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(
&AzToolsFramework::ToolsApplicationRequests::DeleteEntitiesAndAllDescendants, selectedEntityIds);
}
AZ::EntityId SandboxIntegrationManager::CreateNewEntity(AZ::EntityId parentId)
{
AZ::Vector3 position = AZ::Vector3::CreateZero();
bool parentIsValid = parentId.IsValid();
if (parentIsValid)
{
// If a valid parent is a Layer, we should get our position from the viewport as all Layers are positioned at 0,0,0.
bool parentIsLayer = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
parentIsLayer,
parentId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
parentIsValid = !parentIsLayer;
}
// If we have an invalid parent, base new entity's position on the viewport.
if (!parentIsValid)
{
position = GetWorldPositionAtViewportCenter();
}
return CreateNewEntityAtPosition(position, parentId);
}
AZ::EntityId SandboxIntegrationManager::CreateNewEntityAsChild(AZ::EntityId parentId)
{
AZ_Assert(parentId.IsValid(), "Entity created as a child of an invalid parent entity.");
auto newEntityId = CreateNewEntity(parentId);
// Some modules need to know that this special action has taken place; broadcast an event.
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::EntityCreatedAsChild, newEntityId, parentId);
return newEntityId;
}
AZ::EntityId SandboxIntegrationManager::CreateNewEntityAtPosition(const AZ::Vector3& pos, AZ::EntityId parentId)
{
using namespace AzToolsFramework;
bool prefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(prefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
AZ::EntityId newEntityId;
if (!prefabSystemEnabled)
{
const AZStd::string name = AZStd::string::format("Entity%d", GetIEditor()->GetObjectManager()->GetObjectCount() + 1);
EditorEntityContextRequestBus::BroadcastResult(newEntityId, &EditorEntityContextRequests::CreateNewEditorEntity, name.c_str());
if (newEntityId.IsValid())
{
m_unsavedEntities.insert(newEntityId);
AZ::Transform transform = AZ::Transform::CreateIdentity();
transform.SetTranslation(pos);
if (parentId.IsValid())
{
AZ::TransformBus::Event(newEntityId, &AZ::TransformInterface::SetParent, parentId);
AZ::TransformBus::Event(newEntityId, &AZ::TransformInterface::SetLocalTM, transform);
}
else
{
AZ::TransformBus::Event(newEntityId, &AZ::TransformInterface::SetWorldTM, transform);
}
// Select the new entity (and deselect others).
AzToolsFramework::EntityIdList selection = {newEntityId};
ScopedUndoBatch undo("New Entity");
auto selectionCommand = AZStd::make_unique<AzToolsFramework::SelectionCommand>(selection, "");
selectionCommand->SetParent(undo.GetUndoBatch());
selectionCommand.release();
EBUS_EVENT(AzToolsFramework::ToolsApplicationRequests::Bus, SetSelectedEntities, selection);
}
}
else
{
newEntityId = m_prefabIntegrationInterface->CreateNewEntityAtPosition(pos, parentId);
}
return newEntityId;
}
AzFramework::EntityContextId SandboxIntegrationManager::GetEntityContextId()
{
AzFramework::EntityContextId editorEntityContextId = AzFramework::EntityContextId::CreateNull();
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &AzToolsFramework::EditorEntityContextRequestBus::Events::GetEditorEntityContextId);
return editorEntityContextId;
}
//////////////////////////////////////////////////////////////////////////
QWidget* SandboxIntegrationManager::GetAppMainWindow()
{
return MainWindow::instance();
}
QWidget* SandboxIntegrationManager::GetMainWindow()
{
return MainWindow::instance();
}
IEditor* SandboxIntegrationManager::GetEditor()
{
return GetIEditor();
}
//////////////////////////////////////////////////////////////////////////
bool SandboxIntegrationManager::GetUndoSliceOverrideSaveValue()
{
return GetIEditor()->GetEditorSettings()->m_undoSliceOverrideSaveValue;
}
//////////////////////////////////////////////////////////////////////////
bool SandboxIntegrationManager::GetShowCircularDependencyError()
{
return GetIEditor()->GetEditorSettings()->m_showCircularDependencyError;
}
void SandboxIntegrationManager::SetShowCircularDependencyError(const bool& showCircularDependencyError)
{
GetIEditor()->GetEditorSettings()->m_showCircularDependencyError = showCircularDependencyError;
}
//////////////////////////////////////////////////////////////////////////
void SandboxIntegrationManager::LaunchLuaEditor(const char* files)
{
CCryEditApp::instance()->OpenLUAEditor(files);
}
bool SandboxIntegrationManager::IsLevelDocumentOpen()
{
return (GetIEditor() && GetIEditor()->GetDocument() && GetIEditor()->GetDocument()->IsDocumentReady());
}
AZStd::string SandboxIntegrationManager::GetLevelName()
{
return AZStd::string(GetIEditor()->GetGameEngine()->GetLevelName().toUtf8().constData());
}
void SandboxIntegrationManager::OnContextReset()
{
// Deselect everything.
EBUS_EVENT(AzToolsFramework::ToolsApplicationRequests::Bus, SetSelectedEntities, AzToolsFramework::EntityIdList());
std::vector<CBaseObject*> objects;
objects.reserve(128);
IObjectManager* objectManager = GetIEditor()->GetObjectManager();
objectManager->FindObjectsOfType(OBJTYPE_AZENTITY, objects);
for (CBaseObject* object : objects)
{
CComponentEntityObject* componentEntity = static_cast<CComponentEntityObject*>(object);
componentEntity->AssignEntity(nullptr, false);
objectManager->DeleteObject(componentEntity);
}
}
void SandboxIntegrationManager::OnSliceInstantiated(const AZ::Data::AssetId& /*sliceAssetId*/, AZ::SliceComponent::SliceInstanceAddress& sliceAddress, const AzFramework::SliceInstantiationTicket& /*ticket*/)
{
// The instantiated slice isn't valid. Other systems will report this as an error.
// Bail out here, this is nothing to track in this case.
if (!sliceAddress.GetInstance())
{
return;
}
const AZ::SliceComponent::EntityIdToEntityIdMap& sliceInstanceEntityIdMap = sliceAddress.GetInstance()->GetEntityIdMap();
for (const auto& sliceInstantEntityIdPair : sliceInstanceEntityIdMap)
{
// The second in the pair is the local instance's entity ID.
m_unsavedEntities.insert(sliceInstantEntityIdPair.second);
}
}
void SandboxIntegrationManager::OnLayerComponentActivated(AZ::EntityId entityId)
{
m_editorEntityUiInterface->RegisterEntity(entityId, m_layerUiOverrideHandler.GetHandlerId());
}
void SandboxIntegrationManager::OnLayerComponentDeactivated(AZ::EntityId entityId)
{
m_editorEntityUiInterface->UnregisterEntity(entityId);
}
void SandboxIntegrationManager::ContextMenu_NewEntity()
{
AZ::Vector3 worldPosition = AZ::Vector3::CreateZero();
CViewport* view = GetIEditor()->GetViewManager()->GetGameViewport();
// If we don't have a viewport active to aid in placement, the object
// will be created at the origin.
if (view)
{
const QPoint viewPoint(m_contextMenuViewPoint.GetX(), m_contextMenuViewPoint.GetY());
worldPosition = view->GetHitLocation(viewPoint);
}
CreateNewEntityAtPosition(worldPosition);
}
AZ::EntityId SandboxIntegrationManager::ContextMenu_NewLayer()
{
const int objectCount = GetIEditor()->GetObjectManager()->GetObjectCount();
const AZStd::string name = AZStd::string::format("Layer%d", objectCount + 1);
// Make sure the color is created fully opaque.
static QColor newLayerDefaultColor = GetIEditor()->GetColorByName("NewLayerDefaultColor");
AZ::Color newLayerColor(
aznumeric_cast<float>(newLayerDefaultColor.redF()),
aznumeric_cast<float>(newLayerDefaultColor.greenF()),
aznumeric_cast<float>(newLayerDefaultColor.blueF()),
aznumeric_cast<float>(newLayerDefaultColor.alphaF()));
AZ::EntityId newEntityId = AzToolsFramework::Layers::EditorLayerComponent::CreateLayerEntity(
name, newLayerColor, AzToolsFramework::Layers::LayerProperties::SaveFormat::Xml);
if (!newEntityId.IsValid())
{
// CreateLayerEntity already handled reporting errors if it couldn't make a new layer.
return AZ::EntityId();
}
m_unsavedEntities.insert(newEntityId);
return newEntityId;
}
void SandboxIntegrationManager::ContextMenu_SaveLayers(const AZStd::unordered_set<AZ::EntityId>& layers)
{
AZStd::unordered_map<AZStd::string, int> nameConflictMapping;
for (AZ::EntityId layerEntityId : layers)
{
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
layerEntityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::UpdateLayerNameConflictMapping,
nameConflictMapping);
}
if (!nameConflictMapping.empty())
{
AzToolsFramework::Layers::NameConflictWarning* nameConflictWarning = new AzToolsFramework::Layers::NameConflictWarning(GetMainWindow(), nameConflictMapping);
nameConflictWarning->exec();
return;
}
AZStd::unordered_set<AZ::EntityId> allLayersToSave(layers);
bool mustSaveLevel = false;
bool mustSaveOtherContent = false;
for (AZ::EntityId layerEntityId : layers)
{
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
mustSaveOtherContent,
layerEntityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::GatherSaveDependencies,
allLayersToSave,
mustSaveLevel);
}
if (mustSaveOtherContent)
{
QWidget* mainWindow = nullptr;
AzToolsFramework::EditorRequestBus::BroadcastResult(
mainWindow,
&AzToolsFramework::EditorRequestBus::Events::GetMainWindow);
QMessageBox saveAdditionalContentMessage(mainWindow);
saveAdditionalContentMessage.setWindowTitle(QObject::tr("Unsaved content"));
saveAdditionalContentMessage.setText(QObject::tr("You have moved entities to or from the layer(s) that you are trying to save."));
if (mustSaveLevel)
{
saveAdditionalContentMessage.setInformativeText(QObject::tr("The level and all layers will be saved."));
}
else
{
saveAdditionalContentMessage.setInformativeText(QObject::tr("All relevant layers will be saved."));
}
saveAdditionalContentMessage.setIcon(QMessageBox::Warning);
saveAdditionalContentMessage.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel);
saveAdditionalContentMessage.setDefaultButton(QMessageBox::Save);
int saveAdditionalContentMessageResult = saveAdditionalContentMessage.exec();
switch (saveAdditionalContentMessageResult)
{
case QMessageBox::Cancel:
// The user chose to cancel this operation.
return;
case QMessageBox::Save:
default:
break;
}
if (mustSaveLevel)
{
// Saving the level causes all layers to save.
GetIEditor()->GetDocument()->Save();
return;
}
}
QString levelAbsoluteFolder = Path::GetPath(GetIEditor()->GetDocument()->GetActivePathName());
// Not used here, but needed for the ebus event.
AZStd::vector<AZ::Entity*> layerEntities;
AZ::SliceComponent::SliceReferenceToInstancePtrs instancesInLayers;
for (AZ::EntityId layerEntityId : allLayersToSave)
{
AzToolsFramework::Layers::LayerResult layerSaveResult(AzToolsFramework::Layers::LayerResult::CreateSuccess());
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
layerSaveResult,
layerEntityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::WriteLayerAndGetEntities,
levelAbsoluteFolder,
layerEntities,
instancesInLayers);
AzToolsFramework::Layers::EditorLayerComponentRequestBus::Event(
layerEntityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::RestoreEditorData);
layerSaveResult.MessageResult();
m_unsavedEntities.erase(layerEntityId);
}
// Update the unsaved entities list so these entities are no longer tracked as unsaved.
for (const AZ::Entity* entity : layerEntities)
{
m_unsavedEntities.erase(entity->GetId());
}
}
void SandboxIntegrationManager::ContextMenu_MakeSlice(AzToolsFramework::EntityIdList entities)
{
QChar bulletChar(0x2022);
QMessageBox createSliceBox(GetMainWindow());
createSliceBox.setWindowTitle(QObject::tr("Create Slice"));
createSliceBox.setText(QString(QObject::tr("Your selection contains slice instances. What kind of slice do you want to create?"))
+ "\n\n" + QString(bulletChar) + " " + "Fresh slice that doesn't inherit existing slice references."
+ "\n" + QString(bulletChar) + " " + "Nested slice that inherits existing slice references."
+ "\n\n");
createSliceBox.setIcon(QMessageBox::Warning);
QPushButton* freshSliceButton = createSliceBox.addButton(QObject::tr("Fresh Slice"), QMessageBox::ActionRole);
QPushButton* nestedSliceButton = createSliceBox.addButton(QObject::tr("Nested Slice"), QMessageBox::ActionRole);
createSliceBox.addButton(QMessageBox::Cancel);
createSliceBox.exec();
if (createSliceBox.clickedButton() == freshSliceButton)
{
MakeSliceFromEntities(entities, false, GetIEditor()->GetEditorSettings()->sliceSettings.dynamicByDefault);
}
else if (createSliceBox.clickedButton() == nestedSliceButton)
{
ContextMenu_InheritSlice(entities);
}
}
void SandboxIntegrationManager::ContextMenu_InheritSlice(AzToolsFramework::EntityIdList entities)
{
MakeSliceFromEntities(entities, true, GetIEditor()->GetEditorSettings()->sliceSettings.dynamicByDefault);
}
void SandboxIntegrationManager::ContextMenu_InstantiateSlice()
{
AssetSelectionModel selection = AssetSelectionModel::AssetTypeSelection("Slice");
BrowseForAssets(selection);
if (selection.IsValid())
{
auto product = azrtti_cast<const ProductAssetBrowserEntry*>(selection.GetResult());
AZ_Assert(product, "Incorrect entry type selected. Expected product.");
InstantiateSliceFromAssetId(product->GetAssetId());
}
}
void SandboxIntegrationManager::InstantiateSliceFromAssetId(const AZ::Data::AssetId& assetId)
{
AZ::Transform sliceWorldTransform = AZ::Transform::CreateIdentity();
CViewport* view = GetIEditor()->GetViewManager()->GetGameViewport();
// If we don't have a viewport active to aid in placement, the slice
// will be instantiated at the origin.
if (view)
{
const QPoint viewPoint(m_contextMenuViewPoint.GetX(), m_contextMenuViewPoint.GetY());
sliceWorldTransform = AZ::Transform::CreateTranslation(LYVec3ToAZVec3(view->SnapToGrid(view->ViewToWorld(viewPoint))));
}
AzToolsFramework::SliceRequestBus::Broadcast(&AzToolsFramework::SliceRequests::InstantiateSliceFromAssetId, assetId, sliceWorldTransform);
}
//////////////////////////////////////////////////////////////////////////
// Returns true if at least one non-layer entity was found.
bool CollectEntityBoundingBoxesForZoom(const AZ::EntityId& entityId, AABB& selectionBounds)
{
bool isLayerEntity = false;
AzToolsFramework::Layers::EditorLayerComponentRequestBus::EventResult(
isLayerEntity,
entityId,
&AzToolsFramework::Layers::EditorLayerComponentRequestBus::Events::HasLayer);
if (isLayerEntity)
{
// If a layer is in the selection, zoom to its children and ignore the layer itself.
AZStd::vector<AZ::EntityId> layerChildren;
AZ::TransformBus::EventResult(
/*result*/ layerChildren,
/*address*/ entityId,
&AZ::TransformBus::Events::GetChildren);
bool childResults = false;
for (const AZ::EntityId& childId : layerChildren)
{
if (CollectEntityBoundingBoxesForZoom(childId, selectionBounds))
{
// At least one child is not a layer.
childResults = true;
}
}
return childResults;
}
else
{
AABB entityBoundingBox;
CEntityObject* componentEntityObject = nullptr;
AzToolsFramework::ComponentEntityEditorRequestBus::EventResult(
/*result*/ componentEntityObject,
/*address*/ entityId,
&AzToolsFramework::ComponentEntityEditorRequestBus::Events::GetSandboxObject);
if (componentEntityObject)
{
componentEntityObject->GetBoundBox(entityBoundingBox);
selectionBounds.Add(entityBoundingBox.min);
selectionBounds.Add(entityBoundingBox.max);
}
return true;
}
}
//////////////////////////////////////////////////////////////////////////
void SandboxIntegrationManager::GoToEntitiesInViewports(const AzToolsFramework::EntityIdList& entityIds)
{
if (entityIds.empty())
{
return;
}
const AZ::Aabb aabb = AZStd::accumulate(
AZStd::begin(entityIds), AZStd::end(entityIds), AZ::Aabb::CreateNull(), [](AZ::Aabb acc, const AZ::EntityId entityId) {
const AZ::Aabb aabb = AzFramework::CalculateEntityWorldBoundsUnion(AzToolsFramework::GetEntityById(entityId));
acc.AddAabb(aabb);
return acc;
});
float radius;
AZ::Vector3 center;
aabb.GetAsSphere(center, radius);
// minimum center size is 40cm
const float minSelectionRadius = 0.4f;
const float selectionSize = AZ::GetMax(minSelectionRadius, radius);
auto viewportContextManager = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get();
const int viewCount = GetIEditor()->GetViewManager()->GetViewCount(); // legacy call
for (int viewIndex = 0; viewIndex < viewCount; ++viewIndex)
{
if (auto viewportContext = viewportContextManager->GetViewportContextById(viewIndex))
{
const AZ::Transform cameraTransform = viewportContext->GetCameraTransform();
const AZ::Vector3 forward = (center - cameraTransform.GetTranslation()).GetNormalized();
// move camera 25% further back than required
const float centerScale = 1.25f;
// compute new camera transform
const float fov = AzFramework::RetrieveFov(viewportContext->GetCameraProjectionMatrix());
const float fovScale = (1.0f / AZStd::tan(fov * 0.5f));
const float distanceToLookAt = selectionSize * fovScale * centerScale;
const AZ::Transform nextCameraTransform =
AZ::Transform::CreateLookAt(aabb.GetCenter() - (forward * distanceToLookAt), aabb.GetCenter());
AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event(
viewportContext->GetId(),
&AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::InterpolateToTransform, nextCameraTransform,
distanceToLookAt);
}
}
}
void SandboxIntegrationManager::ContextMenu_SelectSlice()
{
AzToolsFramework::EntityIdList selectedEntities;
GetSelectedOrHighlightedEntities(selectedEntities);
AzToolsFramework::EntityIdList newSelectedEntities;
for (const AZ::EntityId& entityId : selectedEntities)
{
AZ::SliceComponent::SliceInstanceAddress sliceAddress;
AzFramework::SliceEntityRequestBus::EventResult(sliceAddress, entityId,
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
if (sliceAddress.IsValid())
{
const AZ::SliceComponent::InstantiatedContainer* instantiated = sliceAddress.GetInstance()->GetInstantiated();
if (instantiated)
{
for (AZ::Entity* entityInSlice : instantiated->m_entities)
{
newSelectedEntities.push_back(entityInSlice->GetId());
}
}
}
}
EBUS_EVENT(AzToolsFramework::ToolsApplicationRequests::Bus,
SetSelectedEntities, newSelectedEntities);
}
void SandboxIntegrationManager::ContextMenu_PushEntitiesToSlice(AzToolsFramework::EntityIdList entities,
AZ::SliceComponent::EntityAncestorList ancestors,
AZ::Data::AssetId targetAncestorId,
bool affectEntireHierarchy)
{
(void)ancestors;
(void)targetAncestorId;
(void)affectEntireHierarchy;
AZ::SerializeContext* serializeContext = nullptr;
EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(serializeContext, "No serialize context");
AzToolsFramework::SliceUtilities::PushEntitiesModal(GetMainWindow(), entities, serializeContext);
}
void SandboxIntegrationManager::ContextMenu_Duplicate()
{
bool handled = true;
AzToolsFramework::EditorRequestBus::Broadcast(&AzToolsFramework::EditorRequests::CloneSelection, handled);
}
void SandboxIntegrationManager::ContextMenu_DeleteSelected()
{
DeleteSelectedEntities(true);
}
void SandboxIntegrationManager::ContextMenu_ResetToSliceDefaults(AzToolsFramework::EntityIdList entities)
{
AzToolsFramework::SliceEditorEntityOwnershipServiceRequestBus::Broadcast(
&AzToolsFramework::SliceEditorEntityOwnershipServiceRequests::ResetEntitiesToSliceDefaults, entities);
}
void SandboxIntegrationManager::GetSelectedEntities(AzToolsFramework::EntityIdList& entities)
{
EBUS_EVENT_RESULT(entities,
AzToolsFramework::ToolsApplicationRequests::Bus,
GetSelectedEntities);
}
void SandboxIntegrationManager::GetSelectedOrHighlightedEntities(AzToolsFramework::EntityIdList& entities)
{
AzToolsFramework::EntityIdList selectedEntities;
AzToolsFramework::EntityIdList highlightedEntities;
EBUS_EVENT_RESULT(selectedEntities,
AzToolsFramework::ToolsApplicationRequests::Bus,
GetSelectedEntities);
EBUS_EVENT_RESULT(highlightedEntities,
AzToolsFramework::ToolsApplicationRequests::Bus,
GetHighlightedEntities);
entities = AZStd::move(selectedEntities);
for (AZ::EntityId highlightedId : highlightedEntities)
{
if (entities.end() == AZStd::find(entities.begin(), entities.end(), highlightedId))
{
entities.push_back(highlightedId);
}
}
}
AZStd::string SandboxIntegrationManager::GetComponentEditorIcon(const AZ::Uuid& componentType, AZ::Component* component)
{
AZStd::string iconPath = GetComponentIconPath(componentType, AZ::Edit::Attributes::Icon, component);
return iconPath;
}
AZStd::string SandboxIntegrationManager::GetComponentIconPath(const AZ::Uuid& componentType,
AZ::Crc32 componentIconAttrib, AZ::Component* component)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
if (componentIconAttrib != AZ::Edit::Attributes::Icon
&& componentIconAttrib != AZ::Edit::Attributes::ViewportIcon
&& componentIconAttrib != AZ::Edit::Attributes::HideIcon)
{
AZ_Warning("SandboxIntegration", false, "Unrecognized component icon attribute!");
}
// return blank path if component shouldn't have icon at all
AZStd::string iconPath;
AZ::SerializeContext* serializeContext = nullptr;
EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(serializeContext, "No serialize context");
auto classData = serializeContext->FindClassData(componentType);
if (classData && classData->m_editData)
{
// check if component icon should be hidden
bool hideIcon = false;
auto editorElementData = classData->m_editData->FindElementData(AZ::Edit::ClassElements::EditorData);
if (editorElementData)
{
if (auto hideIconAttribute = editorElementData->FindAttribute(AZ::Edit::Attributes::HideIcon))
{
auto hideIconAttributeData = azdynamic_cast<const AZ::Edit::AttributeData<bool>*>(hideIconAttribute);
if (hideIconAttributeData)
{
hideIcon = hideIconAttributeData->Get(nullptr);
}
}
}
if (!hideIcon)
{
// component should have icon. start with default
iconPath = GetDefaultComponentEditorIcon();
// check for specific icon
if (editorElementData)
{
if (auto iconAttribute = editorElementData->FindAttribute(componentIconAttrib))
{
if (auto iconAttributeData = azdynamic_cast<const AZ::Edit::AttributeData<const char*>*>(iconAttribute))
{
AZStd::string iconAttributeValue = iconAttributeData->Get(component);
if (!iconAttributeValue.empty())
{
iconPath = AZStd::move(iconAttributeValue);
}
}
auto iconOverrideAttribute = editorElementData->FindAttribute(AZ::Edit::Attributes::DynamicIconOverride);
// If it has an override and we're given an instance, then get any potential override from the instance here
if (component &&
(componentIconAttrib == AZ::Edit::Attributes::Icon || componentIconAttrib == AZ::Edit::Attributes::ViewportIcon) &&
iconOverrideAttribute)
{
AZStd::string iconValue;
AZ::AttributeReader iconReader(const_cast<AZ::Component*>(component), iconOverrideAttribute);
iconReader.Read<AZStd::string>(iconValue);
if (!iconValue.empty())
{
iconPath = AZStd::move(iconValue);
}
}
}
}
// If Qt doesn't know where the relative path is we have to use the more expensive full path
if (!QFile::exists(QString(iconPath.c_str())))
{
// use absolute path if possible
AZStd::string iconFullPath;
bool pathFound = false;
using AssetSysReqBus = AzToolsFramework::AssetSystemRequestBus;
AssetSysReqBus::BroadcastResult(
pathFound, &AssetSysReqBus::Events::GetFullSourcePathFromRelativeProductPath,
iconPath, iconFullPath);
if (pathFound)
{
iconPath = AZStd::move(iconFullPath);
}
}
}
}
return iconPath;
}
void SandboxIntegrationManager::UndoStackFlushed()
{
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(&AzToolsFramework::ToolsApplicationRequestBus::Events::FlushUndo);
}
void SandboxIntegrationManager::MakeSliceFromEntities(const AzToolsFramework::EntityIdList& entities, bool inheritSlices, bool setAsDynamic)
{
// expand the list of entities to include all transform descendant entities
AzToolsFramework::EntityIdSet entitiesAndDescendants;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(entitiesAndDescendants,
&AzToolsFramework::ToolsApplicationRequestBus::Events::GatherEntitiesAndAllDescendents, entities);
const AZStd::string slicesAssetsPath = "@devassets@/Slices";
if (!gEnv->pFileIO->Exists(slicesAssetsPath.c_str()))
{
gEnv->pFileIO->CreatePath(slicesAssetsPath.c_str());
}
char path[AZ_MAX_PATH_LEN] = { 0 };
gEnv->pFileIO->ResolvePath(slicesAssetsPath.c_str(), path, AZ_MAX_PATH_LEN);
AzToolsFramework::SliceUtilities::MakeNewSlice(entitiesAndDescendants, path, inheritSlices, setAsDynamic);
}
void SandboxIntegrationManager::RegisterViewPane(const char* name, const char* category, const AzToolsFramework::ViewPaneOptions& viewOptions, const WidgetCreationFunc& widgetCreationFunc)
{
QtViewPaneManager::instance()->RegisterPane(name, category, widgetCreationFunc, viewOptions);
}
void SandboxIntegrationManager::RegisterCustomViewPane(const char* name, const char* category, const AzToolsFramework::ViewPaneOptions& viewOptions)
{
QtViewPaneManager::instance()->RegisterPane(name, category, nullptr, viewOptions);
}
void SandboxIntegrationManager::UnregisterViewPane(const char* name)
{
QtViewPaneManager::instance()->UnregisterPane(name);
}
QWidget* SandboxIntegrationManager::GetViewPaneWidget(const char* viewPaneName)
{
return FindViewPane<QWidget>(viewPaneName);
}
void SandboxIntegrationManager::OpenViewPane(const char* paneName)
{
const QtViewPane* pane = QtViewPaneManager::instance()->OpenPane(paneName);
if (pane)
{
pane->m_dockWidget->raise();
pane->m_dockWidget->activateWindow();
}
}
QDockWidget* SandboxIntegrationManager::InstanceViewPane(const char* paneName)
{
return QtViewPaneManager::instance()->InstancePane(paneName);
}
void SandboxIntegrationManager::CloseViewPane(const char* paneName)
{
QtViewPaneManager::instance()->ClosePane(paneName);
}
void SandboxIntegrationManager::BrowseForAssets(AssetSelectionModel& selection)
{
AssetBrowserComponentRequestBus::Broadcast(&AssetBrowserComponentRequests::PickAssets, selection, GetMainWindow());
}
bool SandboxIntegrationManager::DisplayHelpersVisible()
{
return GetIEditor()->GetDisplaySettings()->IsDisplayHelpers();
}