Merge remote-tracking branch 'origin/development' into sc-editor-asset-redux

Signed-off-by: carlitosan <82187351+carlitosan@users.noreply.github.com>
This commit is contained in:
carlitosan
2021-12-02 14:17:35 -08:00
3323 changed files with 90625 additions and 60443 deletions
@@ -10,6 +10,7 @@
#include <AzCore/base.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/Math/Vector2.h>
class CVegetationMap;
struct CVegetationInstance;
@@ -0,0 +1,25 @@
/*
* 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
namespace AzToolsFramework::EmbeddedPython
{
// When using embedded Python, some platforms need to explicitly load the python library.
// For any modules that depend on 3rdParty::Python package, the AZ::Module should inherit this class.
class PythonLoader
{
public:
PythonLoader();
~PythonLoader();
private:
[[maybe_unused]] void* m_embeddedLibPythonHandle{ nullptr };
};
} // namespace AzToolsFramework::EmbeddedPython
@@ -927,6 +927,9 @@ namespace AzToolsFramework
/// Notify that the MainWindow has been fully initialized
virtual void NotifyMainWindowInitialized(QMainWindow* /*mainWindow*/) {}
/// Notify that the Editor has been fully initialized
virtual void NotifyEditorInitialized() {}
/// Signal that an asset should be highlighted / selected
virtual void SelectAsset(const QString& /* assetPath */) {}
};
@@ -30,6 +30,7 @@
#include <AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.h>
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/Entity/ReadOnly/ReadOnlyEntitySystemComponent.h>
#include <AzToolsFramework/FocusMode/FocusModeSystemComponent.h>
#include <AzToolsFramework/Slice/SliceMetadataEntityContextComponent.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponent.h>
@@ -54,6 +55,7 @@
#include <AzToolsFramework/API/ComponentEntityObjectBus.h>
#include <AzToolsFramework/API/EditorCameraBus.h>
#include <AzToolsFramework/API/ViewPaneOptions.h>
#include <AzToolsFramework/API/ViewportEditorModeTrackerNotificationBus.h>
#include <AzToolsFramework/Entity/EditorEntitySearchComponent.h>
#include <AzToolsFramework/Entity/EditorEntitySortComponent.h>
#include <AzToolsFramework/Entity/EditorEntityModelComponent.h>
@@ -215,12 +217,17 @@ namespace AzToolsFramework
, public AZ::BehaviorEBusHandler
{
AZ_EBUS_BEHAVIOR_BINDER(EditorEventsBusHandler, "{352F80BB-469A-40B6-B322-FE57AB51E4DA}", AZ::SystemAllocator,
NotifyRegisterViews);
NotifyRegisterViews, NotifyEditorInitialized);
void NotifyRegisterViews() override
{
Call(FN_NotifyRegisterViews);
}
void NotifyEditorInitialized() override
{
Call(FN_NotifyEditorInitialized);
}
};
} // Internal
@@ -234,12 +241,14 @@ namespace AzToolsFramework
, m_isInIsolationMode(false)
{
ToolsApplicationRequests::Bus::Handler::BusConnect();
AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler::BusConnect();
m_undoCache.RegisterToUndoCacheInterface();
}
ToolsApplication::~ToolsApplication()
{
AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler::BusDisconnect();
ToolsApplicationRequests::Bus::Handler::BusDisconnect();
Stop();
}
@@ -260,6 +269,7 @@ namespace AzToolsFramework
azrtti_typeid<Components::EditorEntityUiSystemComponent>(),
azrtti_typeid<FocusModeSystemComponent>(),
azrtti_typeid<ContainerEntitySystemComponent>(),
azrtti_typeid<ReadOnlyEntitySystemComponent>(),
azrtti_typeid<SliceMetadataEntityContextComponent>(),
azrtti_typeid<Prefab::PrefabSystemComponent>(),
azrtti_typeid<EditorEntityFixupComponent>(),
@@ -378,6 +388,7 @@ namespace AzToolsFramework
ComponentModeFramework::ComponentModeDelegate::Reflect(context);
ViewportInteraction::ViewportInteractionReflect(context);
ViewportEditorModeNotifications::Reflect(context);
Camera::EditorCameraRequests::Reflect(context);
AzToolsFramework::EditorTransformComponentSelectionRequests::Reflect(context);
@@ -445,6 +456,7 @@ namespace AzToolsFramework
->Attribute(AZ::Script::Attributes::Module, "editor")
->Handler<Internal::EditorEventsBusHandler>()
->Event("NotifyRegisterViews", &EditorEvents::NotifyRegisterViews)
->Event("NotifyEditorInitialized", &EditorEvents::NotifyEditorInitialized)
;
behaviorContext->EBus<ViewPaneCallbackBus>("ViewPaneCallbackBus")
@@ -560,6 +572,12 @@ namespace AzToolsFramework
void ToolsApplication::MarkEntitySelected(AZ::EntityId entityId)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
if (m_freezeSelectionUpdates)
{
return;
}
AZ_Assert(entityId.IsValid(), "Invalid entity Id being marked as selected.");
EntityIdList::iterator foundIter = AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), entityId);
@@ -579,6 +597,11 @@ namespace AzToolsFramework
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
if (m_freezeSelectionUpdates)
{
return;
}
EntityIdList entitiesSelected;
entitiesSelected.reserve(entitiesToSelect.size());
@@ -602,6 +625,12 @@ namespace AzToolsFramework
void ToolsApplication::MarkEntityDeselected(AZ::EntityId entityId)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
if (m_freezeSelectionUpdates)
{
return;
}
auto foundIter = AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), entityId);
if (foundIter != m_selectedEntities.end())
{
@@ -619,6 +648,11 @@ namespace AzToolsFramework
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
if (m_freezeSelectionUpdates)
{
return;
}
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::BeforeEntitySelectionChanged);
EntityIdSet entitySetToDeselect(entitiesToDeselect.begin(), entitiesToDeselect.end());
@@ -673,6 +707,11 @@ namespace AzToolsFramework
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
if (m_freezeSelectionUpdates)
{
return;
}
// We're setting the selection set as a batch from an external caller.
// * Filter out any unselectable entities
// * Calculate selection/deselection delta so we can notify specific entities only on change.
@@ -1192,14 +1231,25 @@ namespace AzToolsFramework
AZ::EntityId ToolsApplication::GetCurrentLevelEntityId()
{
AzFramework::EntityContextId editorEntityContextId = AzFramework::EntityContextId::CreateNull();
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &AzToolsFramework::EditorEntityContextRequestBus::Events::GetEditorEntityContextId);
AZ::SliceComponent* rootSliceComponent = nullptr;
AzFramework::SliceEntityOwnershipServiceRequestBus::EventResult(rootSliceComponent, editorEntityContextId,
&AzFramework::SliceEntityOwnershipServiceRequestBus::Events::GetRootSlice);
if (rootSliceComponent && rootSliceComponent->GetMetadataEntity())
if (IsPrefabSystemEnabled())
{
return rootSliceComponent->GetMetadataEntity()->GetId();
if (auto prefabPublicInterface = AZ::Interface<Prefab::PrefabPublicInterface>::Get())
{
return prefabPublicInterface->GetLevelInstanceContainerEntityId();
}
}
else
{
AzFramework::EntityContextId editorEntityContextId = AzFramework::EntityContextId::CreateNull();
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
editorEntityContextId, &AzToolsFramework::EditorEntityContextRequestBus::Events::GetEditorEntityContextId);
AZ::SliceComponent* rootSliceComponent = nullptr;
AzFramework::SliceEntityOwnershipServiceRequestBus::EventResult(
rootSliceComponent, editorEntityContextId, &AzFramework::SliceEntityOwnershipServiceRequestBus::Events::GetRootSlice);
if (rootSliceComponent && rootSliceComponent->GetMetadataEntity())
{
return rootSliceComponent->GetMetadataEntity()->GetId();
}
}
return AZ::EntityId();
@@ -1552,6 +1602,16 @@ namespace AzToolsFramework
}
}
void ToolsApplication::OnPrefabInstancePropagationBegin()
{
m_freezeSelectionUpdates = true;
}
void ToolsApplication::OnPrefabInstancePropagationEnd()
{
m_freezeSelectionUpdates = false;
}
void ToolsApplication::CreateUndosForDirtyEntities()
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
@@ -16,6 +16,7 @@
#include <AzToolsFramework/API/EditorEntityAPI.h>
#include <AzToolsFramework/Application/EditorEntityManager.h>
#include <AzToolsFramework/Commands/PreemptiveUndoCache.h>
#include <AzToolsFramework/Prefab/PrefabPublicNotificationBus.h>
#pragma once
@@ -29,6 +30,7 @@ namespace AzToolsFramework
class ToolsApplication
: public AzFramework::Application
, public ToolsApplicationRequests::Bus::Handler
, public AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler
{
public:
AZ_RTTI(ToolsApplication, "{2895561E-BE90-4CC3-8370-DD46FCF74C01}", AzFramework::Application);
@@ -169,6 +171,14 @@ namespace AzToolsFramework
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// PrefabPublicNotificationBus::Handler
void OnPrefabInstancePropagationBegin() override;
void OnPrefabInstancePropagationEnd() override;
//////////////////////////////////////////////////////////////////////////
void CreateUndosForDirtyEntities();
void ConsistencyCheckUndoCache();
AZ::Aabb m_selectionBounds;
@@ -181,6 +191,7 @@ namespace AzToolsFramework
bool m_isDuringUndoRedo;
bool m_isInIsolationMode;
EntityIdSet m_isolatedEntityIdSet;
bool m_freezeSelectionUpdates = false;
EditorEntityAPI* m_editorEntityAPI = nullptr;
@@ -14,7 +14,7 @@
#include <AzCore/Module/DynamicModuleHandle.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/Asset/AssetUtils.h>
@@ -205,7 +205,7 @@ namespace AzToolsFramework::AssetUtils
return platformConfigFilePathsAdded;
}
AZStd::vector<AZ::IO::Path> GetConfigFiles(AZStd::string_view engineRoot, AZStd::string_view assetRoot, AZStd::string_view projectPath,
AZStd::vector<AZ::IO::Path> GetConfigFiles(AZStd::string_view engineRoot, AZStd::string_view projectPath,
bool addPlatformConfigs, bool addGemsConfigs, AZ::SettingsRegistryInterface* settingsRegistry)
{
constexpr const char* AssetProcessorGamePlatformConfigFileName = "AssetProcessorGamePlatformConfig.ini";
@@ -232,14 +232,13 @@ namespace AzToolsFramework::AssetUtils
Internal::AddGemConfigFiles(gemInfoList, configFiles);
}
AZ::IO::Path assetRootDir(assetRoot);
assetRootDir /= projectPath;
AZ::IO::Path projectRoot(projectPath);
AZ::IO::Path projectConfigFile = assetRootDir / AssetProcessorGamePlatformConfigFileName;
AZ::IO::Path projectConfigFile = projectRoot / AssetProcessorGamePlatformConfigFileName;
configFiles.push_back(projectConfigFile);
// Add a file entry for the Project AssetProcessor setreg file
projectConfigFile = assetRootDir / AssetProcessorGamePlatformConfigSetreg;
projectConfigFile = projectRoot / AssetProcessorGamePlatformConfigSetreg;
configFiles.push_back(projectConfigFile);
return configFiles;
@@ -251,10 +250,10 @@ namespace AzToolsFramework::AssetUtils
AZStd::vector<AZStd::string> tokens;
AZ::StringFunc::Tokenize(relPathFromRoot.c_str(), tokens, AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
AZStd::string validatedPath;
AZ::IO::FixedMaxPath validatedPath;
if (rootPath.empty())
{
AzFramework::ApplicationRequests::Bus::BroadcastResult(validatedPath, &AzFramework::ApplicationRequests::GetEngineRoot);
validatedPath = AZ::Utils::GetEnginePath();
}
else
{
@@ -299,10 +298,7 @@ namespace AzToolsFramework::AssetUtils
break;
}
AZStd::string absoluteFilePath;
AZ::StringFunc::Path::ConstructFull(validatedPath.c_str(), element.c_str(), absoluteFilePath);
validatedPath = absoluteFilePath; // go one step deeper.
validatedPath /= element; // go one step deeper.
}
if (success)
@@ -40,7 +40,7 @@ namespace AzToolsFramework::AssetUtils
//! Also note that if the project has any "game project gems", then those will also be inserted last,
//! and thus have a higher priority than the root or non - project gems.
//! Also note that the game project could be in a different location to the engine therefore we need the assetRoot param.
AZStd::vector<AZ::IO::Path> GetConfigFiles(AZStd::string_view engineRoot, AZStd::string_view assetRoot, AZStd::string_view projectPath,
AZStd::vector<AZ::IO::Path> GetConfigFiles(AZStd::string_view engineRoot, AZStd::string_view projectPath,
bool addPlatformConfigs = true, bool addGemsConfigs = true, AZ::SettingsRegistryInterface* settingsRegistry = nullptr);
//! A utility function which checks the given path starting at the root and updates the relative path to be the actual case correct path.
@@ -234,11 +234,6 @@ namespace AzToolsFramework
return SourceFileDetails("Icons/AssetBrowser/Lua_16.svg");
}
if (AzFramework::StringFunc::Equal(extension.c_str(), ".mtl"))
{
return SourceFileDetails("Icons/AssetBrowser/Material_16.svg");
}
if (AzFramework::StringFunc::Equal(extension.c_str(), AzToolsFramework::SliceUtilities::GetSliceFileExtension().c_str()))
{
return SourceFileDetails("Icons/AssetBrowser/Slice_16.svg");
@@ -20,7 +20,7 @@ AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option")
AZ_POP_DISABLE_WARNING
AZ_CVAR(
bool, ed_useNewAssetBrowserTableView, false, nullptr, AZ::ConsoleFunctorFlags::Null,
bool, ed_useNewAssetBrowserTableView, true, nullptr, AZ::ConsoleFunctorFlags::Null,
"Use the new AssetBrowser TableView for searching assets.");
namespace AzToolsFramework
{
@@ -30,6 +30,7 @@ namespace AzToolsFramework
connect(m_filterModel, &QAbstractItemModel::rowsRemoved, this, &AssetBrowserTableModel::UpdateTableModelMaps);
connect(m_filterModel, &QAbstractItemModel::layoutChanged, this, &AssetBrowserTableModel::UpdateTableModelMaps);
connect(m_filterModel, &AssetBrowserFilterModel::filterChanged, this, &AssetBrowserTableModel::beginResetModel);
connect(m_filterModel, &AssetBrowserFilterModel::filterChanged, this, &AssetBrowserTableModel::UpdateTableModelMaps);
connect(m_filterModel, &QAbstractItemModel::dataChanged, this, &AssetBrowserTableModel::SourceDataChanged);
}
@@ -31,7 +31,10 @@ AZ_POP_DISABLE_WARNING
AZ_CVAR(
bool, ed_hideAssetPickerPathColumn, true, nullptr, AZ::ConsoleFunctorFlags::Null,
"Hide AssetPicker path column for a clearer view.");
AZ_CVAR_EXTERNED(bool, ed_useNewAssetBrowserTableView);
AZ_CVAR(
bool, ed_useNewAssetPickerView, false, nullptr, AZ::ConsoleFunctorFlags::Null,
"Uses the new Asset Picker View.");
namespace AzToolsFramework
{
@@ -118,7 +121,7 @@ namespace AzToolsFramework
m_persistentState = AZ::UserSettings::CreateFind<AzToolsFramework::QWidgetSavedState>(AZ::Crc32(("AssetBrowserTreeView_Dialog_" + name).toUtf8().data()), AZ::UserSettings::CT_GLOBAL);
m_ui->m_assetBrowserTableViewWidget->setVisible(false);
if (ed_useNewAssetBrowserTableView)
if (ed_useNewAssetPickerView)
{
m_ui->m_assetBrowserTreeViewWidget->setVisible(false);
m_ui->m_assetBrowserTableViewWidget->setVisible(true);
@@ -9,11 +9,11 @@
#include <AzCore/EBus/Results.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/SourceThumbnail.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <QString>
namespace AzToolsFramework
@@ -113,11 +113,9 @@ namespace AzToolsFramework
if (iconPathToUse.isEmpty())
{
const char* engineRoot = nullptr;
AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot);
AZ_Assert(engineRoot, "Engine Root not initialized");
AZStd::string iconPath = AZStd::string::format("%s%s", engineRoot, DefaultFileIconPath);
iconPathToUse = iconPath.c_str();
AZ::IO::FixedMaxPath engineRoot = AZ::Utils::GetEnginePath();
AZ_Assert(!engineRoot.empty(), "Engine Root not initialized");
iconPathToUse = (engineRoot / DefaultFileIconPath).c_str();
}
m_pixmap.load(iconPathToUse);
@@ -202,9 +202,9 @@ namespace AzToolsFramework
AZ_TracePrintf(logWindowName, "Creating new asset bundle manifest file \"%s\" for source pak \"%s\".\n", AzFramework::AssetBundleManifest::s_manifestFileName, sourcePak.c_str());
bool manifestSaved = false;
AZStd::string manifestDirectory;
AZStd::vector<AZStd::string> levelDirs;
AzFramework::StringFunc::Path::GetFullPath(sourcePak.c_str(), manifestDirectory);
AssetCatalogRequestBus::BroadcastResult(manifestSaved, &AssetCatalogRequestBus::Events::CreateBundleManifest, outCatalogPath, AZStd::vector<AZStd::string>(), manifestDirectory, AzFramework::AssetBundleManifest::CurrentBundleVersion, levelDirs);
AssetCatalogRequestBus::BroadcastResult(manifestSaved, &AssetCatalogRequestBus::Events::CreateBundleManifest, outCatalogPath,
AZStd::vector<AZStd::string>(), manifestDirectory, AzFramework::AssetBundleManifest::CurrentBundleVersion, AZStd::vector<AZ::IO::Path>{});
AZStd::string manifestPath;
AzFramework::StringFunc::Path::Join(manifestDirectory.c_str(), AzFramework::AssetBundleManifest::s_manifestFileName, manifestPath);
@@ -263,7 +263,7 @@ namespace AzToolsFramework
AZStd::string tempBundleFilePath = bundleFilePath.Native() + "_temp";
AZStd::vector<AZStd::string> dependentBundleNames;
AZStd::vector<AZStd::string> levelDirs;
AZStd::vector<AZ::IO::Path> levelDirs;
AZStd::vector<AZStd::pair<AZStd::string, AZStd::string>> bundlePathDeltaCatalogPair;
bundlePathDeltaCatalogPair.emplace_back(AZStd::make_pair(tempBundleFilePath, DeltaCatalogName));
@@ -515,7 +515,7 @@ namespace AzToolsFramework
return true;
}
bool AssetBundleComponent::AddManifestFileToBundles(const AZStd::vector<AZStd::pair<AZStd::string, AZStd::string>>& bundlePathDeltaCatalogPair, const AZStd::vector<AZStd::string>& dependentBundleNames, const AZStd::string& bundleFolder, const AzToolsFramework::AssetBundleSettings& assetBundleSettings, const AZStd::vector<AZStd::string>& levelDirs)
bool AssetBundleComponent::AddManifestFileToBundles(const AZStd::vector<AZStd::pair<AZStd::string, AZStd::string>>& bundlePathDeltaCatalogPair, const AZStd::vector<AZStd::string>& dependentBundleNames, const AZStd::string& bundleFolder, const AzToolsFramework::AssetBundleSettings& assetBundleSettings, const AZStd::vector<AZ::IO::Path>& levelDirs)
{
if (!MakePath(bundleFolder))
{
@@ -86,7 +86,7 @@ namespace AzToolsFramework
//! Adds the manifest file to all the bundles
//! The parent bundle manifest file is special since it will contain information of all dependent bundles names.
bool AddManifestFileToBundles(const AZStd::vector<AZStd::pair<AZStd::string, AZStd::string>>& bundlePathDeltaCatalogPair, const AZStd::vector<AZStd::string>& dependentBundleNames, const AZStd::string& bundleFolder, const AzToolsFramework::AssetBundleSettings& assetBundleSettings, const AZStd::vector<AZStd::string>& levelDirs);
bool AddManifestFileToBundles(const AZStd::vector<AZStd::pair<AZStd::string, AZStd::string>>& bundlePathDeltaCatalogPair, const AZStd::vector<AZStd::string>& dependentBundleNames, const AZStd::string& bundleFolder, const AzToolsFramework::AssetBundleSettings& assetBundleSettings, const AZStd::vector<AZ::IO::Path>& levelDirs);
//! Adds the delta catalog and any remaining files to the bundle
//! We only create the delta catalog once we are sure about what all the files that will go in it.
@@ -143,7 +143,7 @@ namespace AzToolsFramework
return AssetCatalog::RemoveDeltaCatalog(deltaCatalog);
}
bool PlatformAddressedAssetCatalog::CreateBundleManifest(const AZStd::string& deltaCatalogPath, const AZStd::vector<AZStd::string>& dependentBundleNames, const AZStd::string& fileDirectory, int bundleVersion, const AZStd::vector<AZStd::string>& levelDirs)
bool PlatformAddressedAssetCatalog::CreateBundleManifest(const AZStd::string& deltaCatalogPath, const AZStd::vector<AZStd::string>& dependentBundleNames, const AZStd::string& fileDirectory, int bundleVersion, const AZStd::vector<AZ::IO::Path>& levelDirs)
{
return AssetCatalog::CreateBundleManifest(deltaCatalogPath, dependentBundleNames, fileDirectory, bundleVersion, levelDirs);
}
@@ -66,7 +66,7 @@ namespace AzToolsFramework
bool InsertDeltaCatalogBefore(AZStd::shared_ptr<AzFramework::AssetRegistry> deltaCatalog, AZStd::shared_ptr<AzFramework::AssetRegistry> afterDeltaCatalog) override;
bool RemoveDeltaCatalog(AZStd::shared_ptr<AzFramework::AssetRegistry> deltaCatalog) override;
bool CreateBundleManifest(const AZStd::string& deltaCatalogPath, const AZStd::vector<AZStd::string>& dependentBundleNames, const AZStd::string& fileDirectory, int bundleVersion, const AZStd::vector<AZStd::string>& levelDirs) override;
bool CreateBundleManifest(const AZStd::string& deltaCatalogPath, const AZStd::vector<AZStd::string>& dependentBundleNames, const AZStd::string& fileDirectory, int bundleVersion, const AZStd::vector<AZ::IO::Path>& levelDirs) override;
bool CreateDeltaCatalog(const AZStd::vector<AZStd::string>& files, const AZStd::string& filePath) override;
void AddExtension(const char* extension) override;
@@ -23,6 +23,7 @@
#include <AzToolsFramework/Entity/EditorEntityModelComponent.h>
#include <AzToolsFramework/Entity/EditorEntitySearchComponent.h>
#include <AzToolsFramework/Entity/EditorEntitySortComponent.h>
#include <AzToolsFramework/Entity/ReadOnly/ReadOnlyEntitySystemComponent.h>
#include <AzToolsFramework/FocusMode/FocusModeSystemComponent.h>
#include <AzToolsFramework/PropertyTreeEditor/PropertyTreeEditorComponent.h>
#include <AzToolsFramework/Render/EditorIntersectorComponent.h>
@@ -75,6 +76,7 @@ namespace AzToolsFramework
EditorEntityFixupComponent::CreateDescriptor(),
EntityUtilityComponent::CreateDescriptor(),
ContainerEntitySystemComponent::CreateDescriptor(),
ReadOnlyEntitySystemComponent::CreateDescriptor(),
FocusModeSystemComponent::CreateDescriptor(),
SliceMetadataEntityContextComponent::CreateDescriptor(),
SliceRequestComponent::CreateDescriptor(),
@@ -597,11 +597,13 @@ namespace AzToolsFramework
pte.SetVisibleEnforcement(true);
}
ScopedUndoBatch undo("Modify Entity Property");
PropertyOutcome result = pte.SetProperty(propertyPath, value);
if (result.IsSuccess())
{
PropertyEditorEntityChangeNotificationBus::Event(componentInstance.GetEntityId(), &PropertyEditorEntityChangeNotifications::OnEntityComponentPropertyChanged, componentInstance.GetComponentId());
}
undo.MarkEntityDirty(componentInstance.GetEntityId());
return result;
}
@@ -448,7 +448,11 @@ namespace AzToolsFramework
ViewportUi::ViewportUiRequestBus::Event(
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder,
componentMode.m_componentMode->GetComponentModeName().c_str());
componentMode.m_componentMode->GetComponentModeName().c_str(),
[]
{
ComponentModeSystemRequestBus::Broadcast(&ComponentModeSystemRequests::EndComponentMode);
});
}
RefreshActions();
@@ -55,8 +55,11 @@ namespace AzToolsFramework
GetEntityComponentIdPair(), elementIdsToDisplay);
// create the component mode border with the specific name for this component mode
ViewportUi::ViewportUiRequestBus::Event(
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder,
GetComponentModeName());
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder, GetComponentModeName(),
[]
{
ComponentModeSystemRequestBus::Broadcast(&ComponentModeSystemRequests::EndComponentMode);
});
// set the EntityComponentId for this ComponentMode to active in the ComponentModeViewportUi system
ComponentModeViewportUiRequestBus::Event(
GetComponentType(), &ComponentModeViewportUiRequestBus::Events::SetViewportUiActiveEntityComponentId,
@@ -15,6 +15,7 @@
#include <AzCore/std/sort.h>
#include <AzCore/RTTI/AttributeReader.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/Commands/EntityStateCommand.h>
#include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
@@ -468,6 +469,18 @@ namespace AzToolsFramework
EntityIdList children;
EditorEntityInfoRequestBus::EventResult(children, parentId, &EditorEntityInfoRequestBus::Events::GetChildren);
// If Prefabs are enabled, don't check the order for an invalid parent, just return its children (i.e. the root container entity)
// There will currently always be one root container entity, so there's no order to retrieve
if (!parentId.IsValid())
{
bool isPrefabEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (isPrefabEnabled)
{
return children;
}
}
EntityIdList entityChildOrder;
AZ::EntityId sortEntityId = GetEntityIdForSortInfo(parentId);
EditorEntitySortRequestBus::EventResult(entityChildOrder, sortEntityId, &EditorEntitySortRequestBus::Events::GetChildEntityOrderArray);
@@ -449,16 +449,23 @@ namespace AzToolsFramework
AZStd::unordered_map<AZ::EntityId, AZStd::pair<AZ::EntityId, AZ::u64>>::const_iterator orderItr = m_savedOrderInfo.find(childId);
if (orderItr != m_savedOrderInfo.end() && orderItr->second.first == parentId)
{
bool sortOrderUpdated = AzToolsFramework::RecoverEntitySortInfo(parentId, childId, orderItr->second.second);
m_savedOrderInfo.erase(childId);
// force notify the child sort order changed on the parent entity info, but only if the restore didn't actually modify
// the order internally (and sent ChildEntityOrderArrayUpdated). that may seem heavy handed, and it is, but necessary
// to combat scenarios when the initial override detection returns a false positive (see comment about IDH comparisons
// in OnChildSortOrderChanged) and the slice instance source-to-live mapping hasn't been fully reconstructed yet.
if (!sortOrderUpdated)
bool isPrefabEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
// If prefabs are enabled, rely on the component to do a sanity check instead of restoring the order from the model
if (!isPrefabEnabled)
{
parentInfo.OnChildSortOrderChanged();
bool sortOrderUpdated = AzToolsFramework::RecoverEntitySortInfo(parentId, childId, orderItr->second.second);
m_savedOrderInfo.erase(childId);
// force notify the child sort order changed on the parent entity info, but only if the restore didn't actually modify
// the order internally (and sent ChildEntityOrderArrayUpdated). that may seem heavy handed, and it is, but necessary
// to combat scenarios when the initial override detection returns a false positive (see comment about IDH comparisons
// in OnChildSortOrderChanged) and the slice instance source-to-live mapping hasn't been fully reconstructed yet.
if (!sortOrderUpdated)
{
parentInfo.OnChildSortOrderChanged();
}
}
}
else
@@ -8,9 +8,17 @@
#include "EditorEntitySortComponent.h"
#include "EditorEntityInfoBus.h"
#include "EditorEntityHelpers.h"
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzCore/std/sort.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <AzToolsFramework/Prefab/PrefabPublicRequestBus.h>
#include <AzToolsFramework/Undo/UndoSystem.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzToolsFramework/Entity/EditorEntitySortComponentSerializer.h>
static_assert(sizeof(AZ::u64) == sizeof(AZ::EntityId), "We use AZ::EntityId for Persistent ID, which is a u64 under the hood. These must be the same size otherwise the persistent id will have to be rewritten");
@@ -49,6 +57,12 @@ namespace AzToolsFramework
;
}
}
AZ::JsonRegistrationContext* jsonRegistration = azrtti_cast<AZ::JsonRegistrationContext*>(context);
if (jsonRegistration)
{
jsonRegistration->Serializer<JsonEditorEntitySortComponentSerializer>()->HandlesType<EditorEntitySortComponent>();
}
}
void EditorEntitySortComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
@@ -144,6 +158,12 @@ namespace AzToolsFramework
bool EditorEntitySortComponent::AddChildEntityInternal(const AZ::EntityId& entityId, bool addToBack, EntityOrderArray::iterator insertPosition)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
if (m_ignoreIncomingOrderChanges)
{
return true;
}
auto entityItr = m_childEntityOrderCache.find(entityId);
if (entityItr == m_childEntityOrderCache.end())
{
@@ -159,9 +179,6 @@ namespace AzToolsFramework
}
MarkDirtyAndSendChangedEvent();
// Use the ToolsApplication to mark the entity dirty, this will only do something if we already have an undo batch
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::AddDirtyEntity, GetEntityId());
return true;
}
@@ -179,6 +196,10 @@ namespace AzToolsFramework
else
{
EntityOrderArray::iterator insertPosition = GetFirstSelectedEntityPosition();
if (insertPosition != m_childEntityOrderArray.end())
{
++insertPosition;
}
retval = AddChildEntityInternal(entityId, false, insertPosition);
}
@@ -198,6 +219,12 @@ namespace AzToolsFramework
bool EditorEntitySortComponent::RemoveChildEntity(const AZ::EntityId& entityId)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
if (m_ignoreIncomingOrderChanges)
{
return true;
}
auto entityItr = m_childEntityOrderCache.find(entityId);
if (entityItr != m_childEntityOrderCache.end())
{
@@ -206,9 +233,6 @@ namespace AzToolsFramework
MarkDirtyAndSendChangedEvent();
// Use the ToolsApplication to mark the entity dirty, this will only do something if we already have an undo batch
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::AddDirtyEntity, GetEntityId());
return true;
}
return false;
@@ -250,11 +274,30 @@ namespace AzToolsFramework
}
}
void EditorEntitySortComponent::OnPrefabInstancePropagationBegin()
{
m_ignoreIncomingOrderChanges = true;
}
void EditorEntitySortComponent::OnPrefabInstancePropagationEnd()
{
m_ignoreIncomingOrderChanges = false;
if (m_shouldSanityCheckStateAfterPropagation)
{
SanitizeOrderEntryArray();
m_shouldSanityCheckStateAfterPropagation = false;
}
}
void EditorEntitySortComponent::MarkDirtyAndSendChangedEvent()
{
// mark the order as dirty before sending the ChildEntityOrderArrayUpdated event in order for PrepareSave to be properly handled in the case
// one of the event listeners needs to build the InstanceDataHierarchy
m_entityOrderIsDirty = true;
// Use the ToolsApplication to mark the entity dirty, this will only do something if we already have an undo batch
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::AddDirtyEntity, GetEntityId());
EditorEntitySortNotificationBus::Event(GetEntityId(), &EditorEntitySortNotificationBus::Events::ChildEntityOrderArrayUpdated);
}
@@ -264,10 +307,19 @@ namespace AzToolsFramework
// This is a special case for certain EditorComponents only!
EditorEntitySortRequestBus::Handler::BusConnect(GetEntityId());
EditorEntityContextNotificationBus::Handler::BusConnect();
AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler::BusConnect();
}
void EditorEntitySortComponent::Activate()
{
// Run the post-serialize handler if prefabs are enabled because PostLoad won't be called automatically
bool isPrefabEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (isPrefabEnabled)
{
m_shouldSanityCheckStateAfterPropagation = true;
}
// Send out that the order for our entity is now updated
EditorEntitySortNotificationBus::Event(GetEntityId(), &EditorEntitySortNotificationBus::Events::ChildEntityOrderArrayUpdated);
}
@@ -293,6 +345,73 @@ namespace AzToolsFramework
m_entityOrderIsDirty = false;
}
void EditorEntitySortComponent::SanitizeOrderEntryArray()
{
bool shouldEmitDirtyState = false;
// Remove invalid and duplicate entries that point at non-existent entities
AZStd::unordered_set<AZ::EntityId> duplicateIds;
for (auto it = m_childEntityOrderArray.begin(); it != m_childEntityOrderArray.end();)
{
if (!it->IsValid() || GetEntityById(*it) == nullptr || duplicateIds.contains(*it))
{
it = m_childEntityOrderArray.erase(it);
shouldEmitDirtyState = true;
}
else
{
duplicateIds.insert(*it);
++it;
}
}
// Append any missing children
EntityIdList children;
AZ::TransformBus::EventResult(children, GetEntityId(), &AZ::TransformBus::Events::GetChildren);
for (auto it = m_childEntityOrderArray.begin(); it != m_childEntityOrderArray.end(); ++it)
{
if (auto removedChildrenIt = AZStd::remove(children.begin(), children.end(), *it); removedChildrenIt != children.end())
{
children.erase(removedChildrenIt);
}
}
AZStd::sort(children.begin(), children.end(), [](AZ::EntityId lhs, AZ::EntityId rhs)
{
return GetEntityById(lhs)->GetName() < GetEntityById(rhs)->GetName();
});
if (!children.empty())
{
shouldEmitDirtyState = true;
EntityOrderArray::iterator insertPosition = GetFirstSelectedEntityPosition();
if (insertPosition != m_childEntityOrderArray.end())
{
++insertPosition;
}
m_childEntityOrderArray.insert(insertPosition, children.begin(), children.end());
}
// Clear out the vector to be rebuilt from persistent id
m_childEntityOrderEntryArray.resize(m_childEntityOrderArray.size());
for (size_t i = 0; i < m_childEntityOrderArray.size(); ++i)
{
m_childEntityOrderEntryArray[i] = {
m_childEntityOrderArray[i],
static_cast<AZ::u64>(i)
};
}
RebuildEntityOrderCache();
if (shouldEmitDirtyState)
{
ToolsApplicationRequests::Bus::Broadcast(&ToolsApplicationRequests::Bus::Events::AddDirtyEntity, GetEntityId());
}
m_entityOrderIsDirty = false;
}
void EditorEntitySortComponent::PostLoad()
{
// Clear out the vector to be rebuilt from persistent id
@@ -340,7 +459,7 @@ namespace AzToolsFramework
firstSelectedEntityPos = selectedEntityPos < firstSelectedEntityPos ? selectedEntityPos : firstSelectedEntityPos;
}
return firstSelectedEntityPos == m_childEntityOrderArray.end() ? m_childEntityOrderArray.begin() : firstSelectedEntityPos;
return firstSelectedEntityPos;
}
}
} // namespace AzToolsFramework
@@ -10,6 +10,7 @@
#include "EditorEntitySortBus.h"
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Prefab/PrefabPublicNotificationBus.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AzToolsFramework
@@ -20,7 +21,10 @@ namespace AzToolsFramework
: public AzToolsFramework::Components::EditorComponentBase
, public EditorEntitySortRequestBus::Handler
, public EditorEntityContextNotificationBus::Handler
, public AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler
{
friend class JsonEditorEntitySortComponentSerializer;
public:
AZ_COMPONENT(EditorEntitySortComponent, "{6EA1E03D-68B2-466D-97F7-83998C8C27F0}", EditorComponentBase);
@@ -45,6 +49,9 @@ namespace AzToolsFramework
// EditorEntityContextNotificationBus::Handler
void OnEntityStreamLoadSuccess() override;
//////////////////////////////////////////////////////////////////////////
void OnPrefabInstancePropagationBegin() override;
void OnPrefabInstancePropagationEnd() override;
private:
void MarkDirtyAndSendChangedEvent();
bool AddChildEntityInternal(const AZ::EntityId& entityId, bool addToBack, EntityOrderArray::iterator insertPosition);
@@ -59,6 +66,8 @@ namespace AzToolsFramework
void PrepareSave();
void PostLoad();
void SanitizeOrderEntryArray();
class EntitySortSerializationEvents
: public AZ::SerializeContext::IEventHandler
{
@@ -106,6 +115,8 @@ namespace AzToolsFramework
EntityOrderCache m_childEntityOrderCache; ///< The map of entity id to index for quick look up
bool m_entityOrderIsDirty = true; ///< This flag indicates our stored serialization order data is out of date and must be rebuilt before serialization occurs
bool m_ignoreIncomingOrderChanges = false; ///< This is set when prefab propagation occurs so that non-authored order changes can be ignored
bool m_shouldSanityCheckStateAfterPropagation = false; //< This is set after activation, to queue a cleanup of any invalid state after the next prefab propagation.
};
}
} // namespace AzToolsFramework
@@ -0,0 +1,137 @@
/*
* 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 <AzCore/Serialization/Json/JsonSerializationResult.h>
#include <AzCore/std/sort.h>
#include <AzToolsFramework/Entity/EditorEntitySortComponent.h>
#include <AzToolsFramework/Entity/EditorEntitySortComponentSerializer.h>
namespace AzToolsFramework::Components
{
AZ_CLASS_ALLOCATOR_IMPL(JsonEditorEntitySortComponentSerializer, AZ::SystemAllocator, 0);
AZ::JsonSerializationResult::Result JsonEditorEntitySortComponentSerializer::Load(
void* outputValue,
[[maybe_unused]] const AZ::Uuid& outputValueTypeId,
const rapidjson::Value& inputValue,
AZ::JsonDeserializerContext& context)
{
namespace JSR = AZ::JsonSerializationResult;
AZ_Assert(
azrtti_typeid<EditorEntitySortComponent>() == outputValueTypeId,
"Unable to deserialize EditorEntitySortComponent from json because the provided type is %s.",
outputValueTypeId.ToString<AZStd::string>().c_str());
EditorEntitySortComponent* sortComponentInstance = reinterpret_cast<EditorEntitySortComponent*>(outputValue);
AZ_Assert(sortComponentInstance, "Output value for JsonEditorEntitySortComponentSerializer can't be null.");
JSR::ResultCode result(JSR::Tasks::ReadField);
{
JSR::ResultCode componentIdLoadResult = ContinueLoadingFromJsonObjectField(
&sortComponentInstance->m_id, azrtti_typeid<decltype(sortComponentInstance->m_id)>(), inputValue,
"Id", context);
result.Combine(componentIdLoadResult);
}
{
sortComponentInstance->m_childEntityOrderArray.clear();
JSR::ResultCode enryLoadResult = ContinueLoadingFromJsonObjectField(
&sortComponentInstance->m_childEntityOrderArray,
azrtti_typeid<decltype(sortComponentInstance->m_childEntityOrderArray)>(), inputValue, "Child Entity Order",
context);
// Migrate ChildEntityOrderEntryArray -> ChildEntityOrderArray
if (sortComponentInstance->m_childEntityOrderArray.empty())
{
enryLoadResult = ContinueLoadingFromJsonObjectField(
&sortComponentInstance->m_childEntityOrderEntryArray,
azrtti_typeid<decltype(sortComponentInstance->m_childEntityOrderEntryArray)>(), inputValue,
"ChildEntityOrderEntryArray", context);
AZStd::sort(
sortComponentInstance->m_childEntityOrderEntryArray.begin(),
sortComponentInstance->m_childEntityOrderEntryArray.end(),
[](const EditorEntitySortComponent::EntityOrderEntry& lhs,
const EditorEntitySortComponent::EntityOrderEntry& rhs) -> bool
{
return lhs.m_sortIndex < rhs.m_sortIndex;
});
// Sort by index and copy to the order array, any duplicates or invalid entries will be cleaned up by the sanitization pass
sortComponentInstance->m_childEntityOrderArray.resize(sortComponentInstance->m_childEntityOrderEntryArray.size());
for (size_t i = 0; i < sortComponentInstance->m_childEntityOrderEntryArray.size(); ++i)
{
sortComponentInstance->m_childEntityOrderArray[i] = sortComponentInstance->m_childEntityOrderEntryArray[i].m_entityId;
}
}
sortComponentInstance->RebuildEntityOrderCache();
result.Combine(enryLoadResult);
}
return context.Report(
result,
result.GetProcessing() != JSR::Processing::Halted ? "Successfully loaded EditorEntitySortComponent information."
: "Failed to load EditorEntitySortComponent information.");
}
AZ::JsonSerializationResult::Result JsonEditorEntitySortComponentSerializer::Store(
rapidjson::Value& outputValue,
const void* inputValue,
const void* defaultValue,
[[maybe_unused]] const AZ::Uuid& valueTypeId,
AZ::JsonSerializerContext& context)
{
namespace JSR = AZ::JsonSerializationResult;
AZ_Assert(
azrtti_typeid<EditorEntitySortComponent>() == valueTypeId,
"Unable to Serialize EditorEntitySortComponent because the provided type is %s.",
valueTypeId.ToString<AZStd::string>().c_str());
const EditorEntitySortComponent* sortComponentInstance = reinterpret_cast<const EditorEntitySortComponent*>(inputValue);
AZ_Assert(sortComponentInstance, "Input value for JsonEditorEntitySortComponentSerializer can't be null.");
const EditorEntitySortComponent* defaultsortComponentInstance =
reinterpret_cast<const EditorEntitySortComponent*>(defaultValue);
JSR::ResultCode result(JSR::Tasks::WriteValue);
{
AZ::ScopedContextPath subPathName(context, "m_id");
const AZ::ComponentId* componentId = &sortComponentInstance->m_id;
const AZ::ComponentId* defaultComponentId =
defaultsortComponentInstance ? &defaultsortComponentInstance->m_id : nullptr;
JSR::ResultCode resultComponentId = ContinueStoringToJsonObjectField(
outputValue, "Id", componentId, defaultComponentId, azrtti_typeid<decltype(sortComponentInstance->m_id)>(),
context);
result.Combine(resultComponentId);
}
{
AZ::ScopedContextPath subPathName(context, "m_childEntityOrderArray");
const EntityOrderArray* childEntityOrderArray = &sortComponentInstance->m_childEntityOrderArray;
const EntityOrderArray* defaultChildEntityOrderArray =
defaultsortComponentInstance ? &defaultsortComponentInstance->m_childEntityOrderArray : nullptr;
JSR::ResultCode resultParentEntityId = ContinueStoringToJsonObjectField(
outputValue, "Child Entity Order", childEntityOrderArray, defaultChildEntityOrderArray,
azrtti_typeid<decltype(sortComponentInstance->m_childEntityOrderArray)>(), context);
result.Combine(resultParentEntityId);
}
return context.Report(
result,
result.GetProcessing() != JSR::Processing::Halted ? "Successfully stored EditorEntitySortComponent information."
: "Failed to store EditorEntitySortComponent information.");
}
} // namespace AzToolsFramework::Components
@@ -0,0 +1,31 @@
/*
* 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 <AzCore/Memory/Memory.h>
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
namespace AzToolsFramework::Components
{
class JsonEditorEntitySortComponentSerializer
: public AZ::BaseJsonSerializer
{
public:
AZ_RTTI(JsonEditorEntitySortComponentSerializer, "{5104782E-B34F-4D87-B1DF-BDFB1AF20D58}", BaseJsonSerializer);
AZ_CLASS_ALLOCATOR_DECL;
AZ::JsonSerializationResult::Result Load(
void* outputValue, const AZ::Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
AZ::JsonDeserializerContext& context) override;
AZ::JsonSerializationResult::Result Store(
rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, const AZ::Uuid& valueTypeId,
AZ::JsonSerializerContext& context) override;
};
} // namespace AzToolsFramework::Components
@@ -0,0 +1,63 @@
/*
* 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 <AzCore/Component/EntityId.h>
#include <AzCore/EBus/EBus.h>
#include <AzFramework/Entity/EntityContext.h>
namespace AzToolsFramework
{
//! Used to notify changes of state for read-only entities.
class ReadOnlyEntityPublicNotifications
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using BusIdType = AzFramework::EntityContextId;
//////////////////////////////////////////////////////////////////////////
//! Triggered when an entity's read-only status changes.
//! @param entityId The entity whose status has changed.
//! @param readOnly The read-only state the container was changed to.
virtual void OnReadOnlyEntityStatusChanged([[maybe_unused]] const AZ::EntityId& entityId, [[maybe_unused]] bool readOnly) {}
protected:
~ReadOnlyEntityPublicNotifications() = default;
};
using ReadOnlyEntityPublicNotificationBus = AZ::EBus<ReadOnlyEntityPublicNotifications>;
//! Used by the ReadOnlyEntitySystemComponent to query the read-only state of entities as set by systems using the API.
class ReadOnlyEntityQueryRequests
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using BusIdType = AzFramework::EntityContextId;
//////////////////////////////////////////////////////////////////////////
//! Triggered when an entity's read-only status is queried.
//! Allows multiple systems to weigh in on the read-only status of an entity.
//! @param entityId The entity whose status has changed.
//! @param[out] isReadOnly The output of the query. Should only be changed to true, and left untouched if false.
virtual void IsReadOnly(const AZ::EntityId& entityId, bool& isReadOnly) = 0;
protected:
~ReadOnlyEntityQueryRequests() = default;
};
using ReadOnlyEntityQueryRequestBus = AZ::EBus<ReadOnlyEntityQueryRequests>;
} // namespace AzToolsFramework
@@ -0,0 +1,43 @@
/*
* 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 <AzCore/Interface/Interface.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Entity/EntityContextBus.h>
namespace AzToolsFramework
{
//! An entity registered as read-only cannot be altered in the editor.
class ReadOnlyEntityPublicInterface
{
public:
AZ_RTTI(ReadOnlyEntityPublicInterface, "{921FE15B-6EBD-47F0-8238-BC63318DEDEA}");
//! Returns whether the entity id provided is registered as read-only.
virtual bool IsReadOnly(const AZ::EntityId& entityId) = 0;
};
//! An entity registered as read-only cannot be altered in the editor.
class ReadOnlyEntityQueryInterface
{
public:
AZ_RTTI(ReadOnlyEntityQueryInterface, "{2ACD63C5-1F3E-4DE8-880E-8115F857D329}");
//! Refreshes the cached read-only status for the entities provided.
//! @param entityIds The entityIds whose read-only state will be queried again.
virtual void RefreshReadOnlyState(const EntityIdList& entityIds) = 0;
//! Refreshes the cached read-only status for all entities.
//! Useful when disconnecting a handler at runtime.
virtual void RefreshReadOnlyStateForAllEntities() = 0;
};
} // namespace AzToolsFramework
@@ -0,0 +1,99 @@
/*
* 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 <AzToolsFramework/Entity/ReadOnly/ReadOnlyEntitySystemComponent.h>
#include <AzToolsFramework/Entity/ReadOnly/ReadOnlyEntityBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
namespace AzToolsFramework
{
void ReadOnlyEntitySystemComponent::Activate()
{
AZ::Interface<ReadOnlyEntityQueryInterface>::Register(this);
AZ::Interface<ReadOnlyEntityPublicInterface>::Register(this);
EditorEntityContextNotificationBus::Handler::BusConnect();
}
void ReadOnlyEntitySystemComponent::Deactivate()
{
EditorEntityContextNotificationBus::Handler::BusDisconnect();
AZ::Interface<ReadOnlyEntityPublicInterface>::Unregister(this);
AZ::Interface<ReadOnlyEntityQueryInterface>::Unregister(this);
}
void ReadOnlyEntitySystemComponent::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<ReadOnlyEntitySystemComponent, AZ::Component>()->Version(1);
}
}
void ReadOnlyEntitySystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("ReadOnlyEntityService"));
}
bool ReadOnlyEntitySystemComponent::IsReadOnly(const AZ::EntityId& entityId)
{
if (!m_readOnlystates.contains(entityId))
{
QueryReadOnlyStateForEntity(entityId);
}
return m_readOnlystates[entityId];
}
void ReadOnlyEntitySystemComponent::RefreshReadOnlyState(const EntityIdList& entityIds)
{
for (const AZ::EntityId entityId : entityIds)
{
bool wasReadOnly = m_readOnlystates[entityId];
QueryReadOnlyStateForEntity(entityId);
if (bool isReadOnly = m_readOnlystates[entityId]; wasReadOnly != isReadOnly)
{
ReadOnlyEntityPublicNotificationBus::Broadcast(
&ReadOnlyEntityPublicNotificationBus::Events::OnReadOnlyEntityStatusChanged, entityId, isReadOnly);
}
}
}
void ReadOnlyEntitySystemComponent::RefreshReadOnlyStateForAllEntities()
{
for (auto elem : m_readOnlystates)
{
AZ::EntityId entityId = elem.first;
bool wasReadOnly = m_readOnlystates[entityId];
QueryReadOnlyStateForEntity(entityId);
if (bool isReadOnly = m_readOnlystates[entityId]; wasReadOnly != isReadOnly)
{
ReadOnlyEntityPublicNotificationBus::Broadcast(
&ReadOnlyEntityPublicNotificationBus::Events::OnReadOnlyEntityStatusChanged, entityId, isReadOnly);
}
}
}
void ReadOnlyEntitySystemComponent::OnContextReset()
{
m_readOnlystates.clear();
}
void ReadOnlyEntitySystemComponent::QueryReadOnlyStateForEntity(const AZ::EntityId& entityId)
{
bool isReadOnly = false;
ReadOnlyEntityQueryRequestBus::Broadcast(
&ReadOnlyEntityQueryRequestBus::Events::IsReadOnly, entityId, isReadOnly);
m_readOnlystates[entityId] = isReadOnly;
}
} // namespace AzToolsFramework
@@ -0,0 +1,56 @@
/*
* 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 <AzCore/Component/Component.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/ReadOnly/ReadOnlyEntityInterface.h>
namespace AzToolsFramework
{
//! System Component to track read-only entity registration.
//! An entity registered as ReadOnly cannot be altered in the Editor.
class ReadOnlyEntitySystemComponent final
: public AZ::Component
, private ReadOnlyEntityPublicInterface
, private ReadOnlyEntityQueryInterface
, private EditorEntityContextNotificationBus::Handler
{
public:
AZ_COMPONENT(ReadOnlyEntitySystemComponent, "{B32EB03F-D88F-4B3A-9C16-071AF04DA646}");
ReadOnlyEntitySystemComponent() = default;
virtual ~ReadOnlyEntitySystemComponent() = default;
// AZ::Component overrides ...
void Activate() override;
void Deactivate() override;
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
// ReadOnlyEntityPublicNotifications overrides ...
bool IsReadOnly(const AZ::EntityId& entityId) override;
// ReadOnlyEntityQueryInterface overrides ...
void RefreshReadOnlyState(const EntityIdList& entityIds) override;
void RefreshReadOnlyStateForAllEntities() override;
// EditorEntityContextNotificationBus overrides ...
void OnContextReset() override;
private:
void QueryReadOnlyStateForEntity(const AZ::EntityId& entityId);
AZStd::unordered_map<AZ::EntityId, bool> m_readOnlystates;
};
} // namespace AzToolsFramework
@@ -6,7 +6,7 @@
*
*/
#include <AzToolsFramework/Input/QtEventToAzInputManager.h>
#include <AzToolsFramework/Input/QtEventToAzInputMapper.h>
#include <AzCore/std/smart_ptr/make_shared.h>
@@ -84,12 +84,12 @@ namespace AzToolsFramework
break;
case State::Translating:
{
if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() &&
if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Middle() &&
mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down &&
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Shift() &&
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl())
{
SnapVerticesToTerrain(mouseInteraction);
SnapVerticesToSurface(mouseInteraction);
return true;
}
@@ -109,29 +109,23 @@ namespace AzToolsFramework
}
template<typename Vertex>
void EditorVertexSelectionBase<Vertex>::SnapVerticesToTerrain(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
void EditorVertexSelectionBase<Vertex>::SnapVerticesToSurface(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
{
ScopedUndoBatch surfaceSnapUndo("Snap to Surface");
ScopedUndoBatch::MarkEntityDirty(GetEntityId());
const int viewportId = mouseInteraction.m_mouseInteraction.m_interactionId.m_viewportId;
// get unsnapped terrain position (world space)
AZ::Vector3 worldSurfacePosition = AZ::Vector3::CreateZero();
;
ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult(
worldSurfacePosition, viewportId, &ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::PickTerrain,
mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates);
// get unsnapped surface position (world space)
const AZ::Vector3 worldSurfacePosition = FindClosestPickIntersection(
viewportId, mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates, EditorPickRayLength,
GetDefaultEntityPlacementDistance());
AZ::Transform worldFromLocal;
AZ::TransformBus::EventResult(worldFromLocal, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM);
const AZ::Transform localFromWorld = worldFromLocal.GetInverse();
// convert to local space - snap if enabled
const GridSnapParameters gridSnapParams = GridSnapSettings(viewportId);
const AZ::Vector3 localFinalSurfacePosition = gridSnapParams.m_gridSnap
? CalculateSnappedTerrainPosition(worldSurfacePosition, worldFromLocal, viewportId, gridSnapParams.m_gridSize)
: localFromWorld.TransformPoint(worldSurfacePosition);
// convert to local space
const AZ::Vector3 localFinalSurfacePosition = localFromWorld.TransformPoint(worldSurfacePosition);
SetSelectedPosition(localFinalSurfacePosition);
OnEntityComponentPropertyChanged(GetEntityComponentIdPair());
@@ -171,7 +171,7 @@ namespace AzToolsFramework
//! Snap the selected vertices to the terrain.
//! Note: With a multi-selection the manipulator will be translated to the picked
//! terrain position with all vertices moved relative to it.
void SnapVerticesToTerrain(const ViewportInteraction::MouseInteractionEvent& mouseInteraction);
void SnapVerticesToSurface(const ViewportInteraction::MouseInteractionEvent& mouseInteraction);
//! The Actions provided by the EditorVertexSelection while it is active.
//! e.g. Vertex deletion, duplication etc.
@@ -110,30 +110,6 @@ namespace AzToolsFramework
return unsnappedPosition + CalculateSnappedOffset(unsnappedPosition, snapAxes, snapAxesCount, size);
}
AZ::Vector3 CalculateSnappedTerrainPosition(
const AZ::Vector3& worldSurfacePosition, const AZ::Transform& worldFromLocal, const int viewportId, const float size)
{
const AZ::Transform localFromWorld = worldFromLocal.GetInverse();
const AZ::Vector3 localSurfacePosition = localFromWorld.TransformPoint(worldSurfacePosition);
// snap in xy plane
AZ::Vector3 localSnappedSurfacePosition = localSurfacePosition +
CalculateSnappedOffset(localSurfacePosition, AZ::Vector3::CreateAxisX(), size) +
CalculateSnappedOffset(localSurfacePosition, AZ::Vector3::CreateAxisY(), size);
// find terrain height at xy snapped location
float terrainHeight = 0.0f;
ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult(
terrainHeight, viewportId, &ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::TerrainHeight,
Vector3ToVector2(worldFromLocal.TransformPoint(localSnappedSurfacePosition)));
// set snapped z value to terrain height
AZ::Vector3 localTerrainHeight = localFromWorld.TransformPoint(AZ::Vector3(0.0f, 0.0f, terrainHeight));
localSnappedSurfacePosition.SetZ(localTerrainHeight.GetZ());
return localSnappedSurfacePosition;
}
bool GridSnapping(const int viewportId)
{
bool snapping = false;
@@ -63,11 +63,6 @@ namespace AzToolsFramework
AZ::Vector3 CalculateSnappedPosition(
const AZ::Vector3& unsnappedPosition, const AZ::Vector3* snapAxes, size_t snapAxesCount, float size);
//! For a given point on the terrain, calculate the closest xy position snapped to the grid
//! (z position is aligned to terrain height, not snapped to z grid)
AZ::Vector3 CalculateSnappedTerrainPosition(
const AZ::Vector3& worldSurfacePosition, const AZ::Transform& worldFromLocal, int viewportId, float size);
//! Wrapper for grid snapping and grid size bus calls.
GridSnapParameters GridSnapSettings(int viewportId);
@@ -10,6 +10,7 @@
#include <AzToolsFramework/Manipulators/ManipulatorSnapping.h>
#include <AzToolsFramework/Manipulators/ManipulatorView.h>
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
namespace AzToolsFramework
{
@@ -17,28 +18,16 @@ namespace AzToolsFramework
const AZ::Transform& worldFromLocal,
const AZ::Vector3& worldSurfacePosition,
const AZ::Vector3& localStartPosition,
const bool snapping,
const float gridSize,
const int viewportId)
[[maybe_unused]] const bool snapping,
[[maybe_unused]] const float gridSize,
[[maybe_unused]] const int viewportId)
{
const AZ::Transform worldFromLocalUniform = AzToolsFramework::TransformUniformScale(worldFromLocal);
const AZ::Transform localFromWorldUniform = worldFromLocalUniform.GetInverse();
const AZ::Vector3 localFinalSurfacePosition = snapping
// note: gridSize is not scaled by scaleRecip here as localStartPosition is
// unscaled itself so the position returned by CalculateSnappedTerrainPosition
// must be in the same space (if localStartPosition were also scaled, gridSize
// would need to be multiplied by scaleRecip)
? CalculateSnappedTerrainPosition(worldSurfacePosition, worldFromLocalUniform, viewportId, gridSize)
: localFromWorldUniform.TransformPoint(worldSurfacePosition);
// delta/offset between initial vertex position and terrain pick position
const AZ::Vector3 localSurfaceOffset = localFinalSurfacePosition - localStartPosition;
const AZ::Transform localFromWorld = worldFromLocal.GetInverse();
StartInternal startInternal;
startInternal.m_snapOffset = localSurfaceOffset;
startInternal.m_localPosition = localStartPosition + localSurfaceOffset;
startInternal.m_localHitPosition = localFromWorldUniform.TransformVector(worldSurfacePosition);
startInternal.m_snapOffset = AZ::Vector3::CreateZero();
startInternal.m_localPosition = localStartPosition;
startInternal.m_localHitPosition = localFromWorld.TransformPoint(worldSurfacePosition);
return startInternal;
}
@@ -46,26 +35,19 @@ namespace AzToolsFramework
const StartInternal& startInternal,
const AZ::Transform& worldFromLocal,
const AZ::Vector3& worldSurfacePosition,
const bool snapping,
const float gridSize,
[[maybe_unused]] const bool snapping,
[[maybe_unused]] const float gridSize,
const ViewportInteraction::KeyboardModifiers keyboardModifiers,
const int viewportId)
[[maybe_unused]] const int viewportId)
{
const AZ::Transform worldFromLocalUniform = AzToolsFramework::TransformUniformScale(worldFromLocal);
const AZ::Transform localFromWorldUniform = worldFromLocalUniform.GetInverse();
const float scaleRecip = ScaleReciprocal(worldFromLocalUniform);
const AZ::Vector3 localFinalSurfacePosition = snapping
? CalculateSnappedTerrainPosition(worldSurfacePosition, worldFromLocalUniform, viewportId, gridSize * scaleRecip)
: localFromWorldUniform.TransformPoint(worldSurfacePosition);
const AZ::Transform localFromWorld = worldFromLocal.GetInverse();
const AZ::Vector3 localFinalSurfacePosition = localFromWorld.TransformPoint(worldSurfacePosition);
Action action;
action.m_start.m_localPosition = startInternal.m_localPosition;
action.m_start.m_snapOffset = startInternal.m_snapOffset;
action.m_start.m_snapOffset = AZ::Vector3::CreateZero();
action.m_current.m_localOffset = localFinalSurfacePosition - startInternal.m_localPosition;
// record what modifier keys are held during this action
action.m_modifiers = keyboardModifiers;
action.m_modifiers = keyboardModifiers; // record what modifier keys are held during this action
return action;
}
@@ -78,12 +60,16 @@ namespace AzToolsFramework
{
SetSpace(worldFromLocal);
AttachLeftMouseDownImpl();
// only cast rays against objects (entities/meshes etc.) we can actually see
m_rayRequest.m_onlyVisible = true;
}
void SurfaceManipulator::InstallLeftMouseDownCallback(const MouseActionCallback& onMouseDownCallback)
{
m_onLeftMouseDownCallback = onMouseDownCallback;
}
void SurfaceManipulator::InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback)
{
m_onLeftMouseUpCallback = onMouseUpCallback;
@@ -95,17 +81,30 @@ namespace AzToolsFramework
}
void SurfaceManipulator::OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, float /*rayIntersectionDistance*/)
const ViewportInteraction::MouseInteraction& interaction, [[maybe_unused]] float rayIntersectionDistance)
{
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace());
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
const AzFramework::ViewportId viewportId = interaction.m_interactionId.m_viewportId;
AZ::Vector3 worldSurfacePosition;
ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult(
worldSurfacePosition, interaction.m_interactionId.m_viewportId,
&ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::PickTerrain,
interaction.m_mousePick.m_screenCoordinates);
const auto& entityComponentIdPairs = EntityComponentIdPairs();
m_rayRequest.m_entityFilter.m_ignoreEntities.clear();
m_rayRequest.m_entityFilter.m_ignoreEntities.reserve(entityComponentIdPairs.size());
AZStd::transform(
entityComponentIdPairs.begin(), entityComponentIdPairs.end(),
AZStd::inserter(m_rayRequest.m_entityFilter.m_ignoreEntities, m_rayRequest.m_entityFilter.m_ignoreEntities.begin()),
[](const AZ::EntityComponentIdPair& entityComponentIdPair)
{
return entityComponentIdPair.GetEntityId();
});
// calculate the start and end of the ray
RefreshRayRequest(
m_rayRequest, ViewportInteraction::ViewportScreenToWorldRay(viewportId, interaction.m_mousePick.m_screenCoordinates),
EditorPickRayLength);
const GridSnapParameters gridSnapParams = GridSnapSettings(viewportId);
const AZ::Vector3 worldSurfacePosition = FindClosestPickIntersection(m_rayRequest, GetDefaultEntityPlacementDistance());
m_startInternal = CalculateManipulationDataStart(
worldFromLocalUniformScale, worldSurfacePosition, GetLocalPosition(), gridSnapParams.m_gridSnap, gridSnapParams.m_gridSize,
@@ -123,17 +122,14 @@ namespace AzToolsFramework
{
if (m_onLeftMouseUpCallback)
{
AZ::Vector3 worldSurfacePosition = AZ::Vector3::CreateZero();
ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult(
worldSurfacePosition, interaction.m_interactionId.m_viewportId,
&ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::PickTerrain,
interaction.m_mousePick.m_screenCoordinates);
const AzFramework::ViewportId viewportId = interaction.m_interactionId.m_viewportId;
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
const GridSnapParameters gridSnapParams = GridSnapSettings(viewportId);
const AZ::Vector3 worldSurfacePosition = FindClosestPickIntersection(m_rayRequest, GetDefaultEntityPlacementDistance());
m_onLeftMouseUpCallback(CalculateManipulationDataAction(
m_startInternal, TransformUniformScale(GetSpace()), worldSurfacePosition, gridSnapParams.m_gridSnap,
gridSnapParams.m_gridSize, interaction.m_keyboardModifiers, interaction.m_interactionId.m_viewportId));
gridSnapParams.m_gridSize, interaction.m_keyboardModifiers, viewportId));
}
}
@@ -141,13 +137,15 @@ namespace AzToolsFramework
{
if (m_onMouseMoveCallback)
{
AZ::Vector3 worldSurfacePosition = AZ::Vector3::CreateZero();
ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult(
worldSurfacePosition, interaction.m_interactionId.m_viewportId,
&ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::PickTerrain,
interaction.m_mousePick.m_screenCoordinates);
const AzFramework::ViewportId viewportId = interaction.m_interactionId.m_viewportId;
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
// update the start and end of the ray
RefreshRayRequest(
m_rayRequest, ViewportInteraction::ViewportScreenToWorldRay(viewportId, interaction.m_mousePick.m_screenCoordinates),
EditorPickRayLength);
const GridSnapParameters gridSnapParams = GridSnapSettings(viewportId);
const AZ::Vector3 worldSurfacePosition = FindClosestPickIntersection(m_rayRequest, GetDefaultEntityPlacementDistance());
m_onMouseMoveCallback(CalculateManipulationDataAction(
m_startInternal, TransformUniformScale(GetSpace()), worldSurfacePosition, gridSnapParams.m_gridSnap,
@@ -11,6 +11,7 @@
#include "BaseManipulator.h"
#include <AzCore/Memory/SystemAllocator.h>
#include <AzFramework/Render/GeometryIntersectionStructures.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
namespace AzToolsFramework
@@ -58,10 +59,12 @@ namespace AzToolsFramework
Start m_start;
Current m_current;
ViewportInteraction::KeyboardModifiers m_modifiers;
AZ::Vector3 LocalPosition() const
{
return m_start.m_localPosition + m_current.m_localOffset;
}
AZ::Vector3 LocalPositionOffset() const
{
return m_current.m_localOffset;
@@ -106,6 +109,9 @@ namespace AzToolsFramework
MouseActionCallback m_onLeftMouseUpCallback = nullptr;
MouseActionCallback m_onMouseMoveCallback = nullptr;
//! Cached ray request initialized at mouse down and updated during mouse move.
AzFramework::RenderGeometry::RayRequest m_rayRequest;
static StartInternal CalculateManipulationDataStart(
const AZ::Transform& worldFromLocal,
const AZ::Vector3& worldSurfacePosition,
@@ -20,6 +20,7 @@
#include <AzToolsFramework/Prefab/PrefabPublicNotificationBus.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/Template/Template.h>
#include <AzToolsFramework/Prefab/PrefabPublicRequestBus.h>
namespace AzToolsFramework
{
@@ -244,10 +245,10 @@ namespace AzToolsFramework
selectedEntityIds.erase(entityIdIterator--);
}
}
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, selectedEntityIds);
// Notify Propagation has ended
// Notify Propagation has ended, then update selection (which is frozen during propagation, so this order matters)
PrefabPublicNotificationBus::Broadcast(&PrefabPublicNotifications::OnPrefabInstancePropagationEnd);
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, selectedEntityIds);
}
m_updatingTemplateInstancesInQueue = false;
@@ -28,6 +28,9 @@ namespace AzToolsFramework
inline static const char* EntityIdName = "Id";
inline static const char* EntitiesName = "Entities";
inline static const char* ContainerEntityName = "ContainerEntity";
inline static const char* ComponentsName = "Components";
inline static const char* EntityOrderName = "Child Entity Order";
inline static const char* TypeName = "$type";
/**
* Find Prefab value from given parent value and target value's name.
@@ -34,10 +34,12 @@ namespace AzToolsFramework::Prefab
PrefabPublicNotificationBus::Handler::BusConnect();
AZ::Interface<PrefabFocusInterface>::Register(this);
AZ::Interface<PrefabFocusPublicInterface>::Register(this);
PrefabFocusPublicRequestBus::Handler::BusConnect();
}
PrefabFocusHandler::~PrefabFocusHandler()
{
PrefabFocusPublicRequestBus::Handler::BusDisconnect();
AZ::Interface<PrefabFocusPublicInterface>::Unregister(this);
AZ::Interface<PrefabFocusInterface>::Unregister(this);
PrefabPublicNotificationBus::Handler::BusDisconnect();
@@ -45,6 +47,18 @@ namespace AzToolsFramework::Prefab
EditorEntityInfoNotificationBus::Handler::BusDisconnect();
}
void PrefabFocusHandler::Reflect(AZ::ReflectContext* context)
{
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context); behaviorContext)
{
behaviorContext->EBus<PrefabFocusPublicRequestBus>("PrefabFocusPublicRequestBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Category, "Prefab")
->Attribute(AZ::Script::Attributes::Module, "prefab")
->Event("FocusOnOwningPrefab", &PrefabFocusPublicInterface::FocusOnOwningPrefab);
}
}
void PrefabFocusHandler::InitializeEditorInterfaces()
{
m_containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get();
@@ -98,7 +112,7 @@ namespace AzToolsFramework::Prefab
}
// Retrieve parent of currently focused prefab.
InstanceOptionalReference parentInstance = m_instanceFocusHierarchy[hierarchySize - 2];
InstanceOptionalReference parentInstance = GetReferenceFromContainerEntityId(m_instanceFocusHierarchy[hierarchySize - 2]);
// Use container entity of parent Instance for focus operations.
AZ::EntityId entityId = parentInstance->get().GetContainerEntityId();
@@ -132,7 +146,7 @@ namespace AzToolsFramework::Prefab
return AZ::Failure(AZStd::string("Prefab Focus Handler: Invalid index on FocusOnPathIndex."));
}
InstanceOptionalReference focusedInstance = m_instanceFocusHierarchy[index];
InstanceOptionalReference focusedInstance = GetReferenceFromContainerEntityId(m_instanceFocusHierarchy[index]);
return FocusOnOwningPrefab(focusedInstance->get().GetContainerEntityId());
}
@@ -172,23 +186,18 @@ namespace AzToolsFramework::Prefab
// Close all container entities in the old path.
CloseInstanceContainers(m_instanceFocusHierarchy);
m_focusedInstance = focusedInstance;
// Do not store the container for the root instance, use an invalid EntityId instead.
m_focusedInstanceContainerEntityId = focusedInstance->get().GetParentInstance().has_value() ? focusedInstance->get().GetContainerEntityId() : AZ::EntityId();
m_focusedTemplateId = focusedInstance->get().GetTemplateId();
AZ::EntityId containerEntityId;
if (focusedInstance->get().GetParentInstance() != AZStd::nullopt)
{
containerEntityId = focusedInstance->get().GetContainerEntityId();
}
else
{
containerEntityId = AZ::EntityId();
}
// Focus on the descendants of the container entity in the Editor, if the interface is initialized.
if (m_focusModeInterface)
{
const AZ::EntityId containerEntityId =
(focusedInstance->get().GetParentInstance() != AZStd::nullopt)
? focusedInstance->get().GetContainerEntityId()
: AZ::EntityId();
m_focusModeInterface->SetFocusRoot(containerEntityId);
}
@@ -212,56 +221,65 @@ namespace AzToolsFramework::Prefab
InstanceOptionalReference PrefabFocusHandler::GetFocusedPrefabInstance(
[[maybe_unused]] AzFramework::EntityContextId entityContextId) const
{
return m_focusedInstance;
return GetReferenceFromContainerEntityId(m_focusedInstanceContainerEntityId);
}
AZ::EntityId PrefabFocusHandler::GetFocusedPrefabContainerEntityId([[maybe_unused]] AzFramework::EntityContextId entityContextId) const
{
if (!m_focusedInstance.has_value())
if (m_focusedInstanceContainerEntityId.IsValid())
{
// PrefabFocusHandler has not been initialized yet.
return AZ::EntityId();
return m_focusedInstanceContainerEntityId;
}
return m_focusedInstance->get().GetContainerEntityId();
if (auto instance = GetReferenceFromContainerEntityId(m_focusedInstanceContainerEntityId); instance.has_value())
{
return instance->get().GetContainerEntityId();
}
return AZ::EntityId();
}
bool PrefabFocusHandler::IsOwningPrefabBeingFocused(AZ::EntityId entityId) const
{
if (!m_focusedInstance.has_value())
{
// PrefabFocusHandler has not been initialized yet.
return false;
}
if (!entityId.IsValid())
{
return false;
}
InstanceOptionalReference instance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
if (!instance.has_value())
{
return false;
}
return instance.has_value() && (&instance->get() == &m_focusedInstance->get());
// If this is owned by the root instance, that corresponds to an invalid m_focusedInstanceContainerEntityId.
if (!instance->get().GetParentInstance().has_value())
{
return !m_focusedInstanceContainerEntityId.IsValid();
}
return (instance->get().GetContainerEntityId() == m_focusedInstanceContainerEntityId);
}
bool PrefabFocusHandler::IsOwningPrefabInFocusHierarchy(AZ::EntityId entityId) const
{
if (!m_focusedInstance.has_value())
{
// PrefabFocusHandler has not been initialized yet.
return false;
}
if (!entityId.IsValid())
{
return false;
}
// If the focus is on the root, m_focusedInstanceContainerEntityId will be the invalid id.
// In those case all entities are in the focus hierarchy and should return true.
if (!m_focusedInstanceContainerEntityId.IsValid())
{
return true;
}
InstanceOptionalReference instance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
while (instance.has_value())
{
if (&instance->get() == &m_focusedInstance->get())
if (instance->get().GetContainerEntityId() == m_focusedInstanceContainerEntityId)
{
return true;
}
@@ -296,8 +314,9 @@ namespace AzToolsFramework::Prefab
// Determine if the entityId is the container for any of the instances in the vector.
auto result = AZStd::find_if(
m_instanceFocusHierarchy.begin(), m_instanceFocusHierarchy.end(),
[entityId](const InstanceOptionalReference& instance)
[&, entityId](const AZ::EntityId& containerEntityId)
{
InstanceOptionalReference instance = GetReferenceFromContainerEntityId(containerEntityId);
return (instance->get().GetContainerEntityId() == entityId);
}
);
@@ -322,8 +341,9 @@ namespace AzToolsFramework::Prefab
// Determine if the templateId matches any of the instances in the vector.
auto result = AZStd::find_if(
m_instanceFocusHierarchy.begin(), m_instanceFocusHierarchy.end(),
[templateId](const InstanceOptionalReference& instance)
[&, templateId](const AZ::EntityId& containerEntityId)
{
InstanceOptionalReference instance = GetReferenceFromContainerEntityId(containerEntityId);
return (instance->get().GetTemplateId() == templateId);
}
);
@@ -342,10 +362,17 @@ namespace AzToolsFramework::Prefab
AZStd::list<InstanceOptionalReference> instanceFocusList;
InstanceOptionalReference currentInstance = m_focusedInstance;
InstanceOptionalReference currentInstance = GetReferenceFromContainerEntityId(m_focusedInstanceContainerEntityId);
while (currentInstance.has_value())
{
m_instanceFocusHierarchy.emplace_back(currentInstance);
if (currentInstance->get().GetParentInstance().has_value())
{
m_instanceFocusHierarchy.emplace_back(currentInstance->get().GetContainerEntityId());
}
else
{
m_instanceFocusHierarchy.emplace_back(AZ::EntityId());
}
currentInstance = currentInstance->get().GetParentInstance();
}
@@ -363,42 +390,48 @@ namespace AzToolsFramework::Prefab
size_t index = 0;
size_t maxIndex = m_instanceFocusHierarchy.size() - 1;
for (const InstanceOptionalReference& instance : m_instanceFocusHierarchy)
for (const AZ::EntityId& containerEntityId : m_instanceFocusHierarchy)
{
AZStd::string prefabName;
if (index < maxIndex)
InstanceOptionalReference instance = GetReferenceFromContainerEntityId(containerEntityId);
if (instance.has_value())
{
// Get the filename without the extension (stem).
prefabName = instance->get().GetTemplateSourcePath().Stem().Native();
}
else
{
// Get the full filename.
prefabName = instance->get().GetTemplateSourcePath().Filename().Native();
}
AZStd::string prefabName;
if (prefabSystemComponentInterface->IsTemplateDirty(instance->get().GetTemplateId()))
{
prefabName += "*";
}
if (index < maxIndex)
{
// Get the filename without the extension (stem).
prefabName = instance->get().GetTemplateSourcePath().Stem().Native();
}
else
{
// Get the full filename.
prefabName = instance->get().GetTemplateSourcePath().Filename().Native();
}
m_instanceFocusPath.Append(prefabName);
if (prefabSystemComponentInterface->IsTemplateDirty(instance->get().GetTemplateId()))
{
prefabName += "*";
}
m_instanceFocusPath.Append(prefabName);
}
++index;
}
}
void PrefabFocusHandler::OpenInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const
void PrefabFocusHandler::OpenInstanceContainers(const AZStd::vector<AZ::EntityId>& instances) const
{
// If this is called outside the Editor, this interface won't be initialized.
if (!m_containerEntityInterface)
{
return;
}
for (const InstanceOptionalReference& instance : instances)
for (const AZ::EntityId& containerEntityId : instances)
{
InstanceOptionalReference instance = GetReferenceFromContainerEntityId(containerEntityId);
if (instance.has_value())
{
m_containerEntityInterface->SetContainerOpen(instance->get().GetContainerEntityId(), true);
@@ -406,7 +439,7 @@ namespace AzToolsFramework::Prefab
}
}
void PrefabFocusHandler::CloseInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const
void PrefabFocusHandler::CloseInstanceContainers(const AZStd::vector<AZ::EntityId>& instances) const
{
// If this is called outside the Editor, this interface won't be initialized.
if (!m_containerEntityInterface)
@@ -414,8 +447,10 @@ namespace AzToolsFramework::Prefab
return;
}
for (const InstanceOptionalReference& instance : instances)
for (const AZ::EntityId& containerEntityId : instances)
{
InstanceOptionalReference instance = GetReferenceFromContainerEntityId(containerEntityId);
if (instance.has_value())
{
m_containerEntityInterface->SetContainerOpen(instance->get().GetContainerEntityId(), false);
@@ -423,4 +458,22 @@ namespace AzToolsFramework::Prefab
}
}
InstanceOptionalReference PrefabFocusHandler::GetReferenceFromContainerEntityId(AZ::EntityId containerEntityId) const
{
if (!containerEntityId.IsValid())
{
PrefabEditorEntityOwnershipInterface* prefabEditorEntityOwnershipInterface =
AZ::Interface<PrefabEditorEntityOwnershipInterface>::Get();
if (!prefabEditorEntityOwnershipInterface)
{
return AZStd::nullopt;
}
return prefabEditorEntityOwnershipInterface->GetRootPrefabInstance();
}
return m_instanceEntityMapperInterface->FindOwningInstance(containerEntityId);
}
} // namespace AzToolsFramework::Prefab
@@ -30,8 +30,8 @@ namespace AzToolsFramework::Prefab
//! Handles Prefab Focus mode, determining which prefab file entity changes will target.
class PrefabFocusHandler final
: private PrefabFocusInterface
, private PrefabFocusPublicInterface
: public PrefabFocusPublicRequestBus::Handler
, private PrefabFocusInterface
, private PrefabPublicNotificationBus::Handler
, private EditorEntityContextNotificationBus::Handler
, private EditorEntityInfoNotificationBus::Handler
@@ -42,13 +42,15 @@ namespace AzToolsFramework::Prefab
PrefabFocusHandler();
~PrefabFocusHandler();
static void Reflect(AZ::ReflectContext* context);
// PrefabFocusInterface overrides ...
void InitializeEditorInterfaces() override;
PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) override;
TemplateId GetFocusedPrefabTemplateId(AzFramework::EntityContextId entityContextId) const override;
InstanceOptionalReference GetFocusedPrefabInstance(AzFramework::EntityContextId entityContextId) const override;
// PrefabFocusPublicInterface overrides ...
// PrefabFocusPublicInterface and PrefabFocusPublicRequestBus overrides ...
PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) override;
PrefabFocusOperationResult FocusOnParentOfFocusedPrefab(AzFramework::EntityContextId entityContextId) override;
PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) override;
@@ -73,16 +75,19 @@ namespace AzToolsFramework::Prefab
void RefreshInstanceFocusList();
void RefreshInstanceFocusPath();
void OpenInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const;
void CloseInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const;
void OpenInstanceContainers(const AZStd::vector<AZ::EntityId>& instances) const;
void CloseInstanceContainers(const AZStd::vector<AZ::EntityId>& instances) const;
//! The instance the editor is currently focusing on.
InstanceOptionalReference m_focusedInstance;
InstanceOptionalReference GetReferenceFromContainerEntityId(AZ::EntityId containerEntityId) const;
//! The EntityId of the prefab container entity for the instance the editor is currently focusing on.
AZ::EntityId m_focusedInstanceContainerEntityId = AZ::EntityId();
//! The templateId of the focused instance.
TemplateId m_focusedTemplateId;
//! The list of instances going from the root (index 0) to the focused instance.
AZStd::vector<InstanceOptionalReference> m_instanceFocusHierarchy;
//! A path containing the names of the containers in the instance focus hierarchy, separated with a /.
//! The list of instances going from the root (index 0) to the focused instance,
//! referenced by their prefab container's EntityId.
AZStd::vector<AZ::EntityId> m_instanceFocusHierarchy;
//! A path containing the filenames of the instances in the focus hierarchy, separated with a /.
AZ::IO::Path m_instanceFocusPath;
ContainerEntityInterface* m_containerEntityInterface = nullptr;
@@ -58,4 +58,18 @@ namespace AzToolsFramework::Prefab
virtual const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const = 0;
};
/**
* The primary purpose of this bus is to facilitate writing automated tests for prefab focus mode.
* If you would like to integrate prefabs focus mode into your system, please call PrefabFocusPublicInterface
* for better performance.
*/
class PrefabFocusPublicRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
};
using PrefabFocusPublicRequestBus = AZ::EBus<PrefabFocusPublicInterface, PrefabFocusPublicRequests>;
} // namespace AzToolsFramework::Prefab
@@ -9,6 +9,7 @@
#include <AzCore/Component/TransformBus.h>
#include <AzCore/JSON/stringbuffer.h>
#include <AzCore/JSON/writer.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Utils/TypeHash.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
@@ -29,6 +30,7 @@
#include <AzToolsFramework/Prefab/PrefabUndo.h>
#include <AzToolsFramework/Prefab/PrefabUndoHelpers.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/Entity/EditorEntitySortComponent.h>
#include <QString>
@@ -519,21 +521,18 @@ namespace AzToolsFramework
nestedInstanceLink.has_value(),
"A valid link was not found for one of the instances provided as input for the CreatePrefab operation.");
PrefabDomReference nestedInstanceLinkDom = nestedInstanceLink->get().GetLinkDom();
AZ_Assert(
nestedInstanceLinkDom.has_value(),
"A valid DOM was not found for the link corresponding to one of the instances provided as input for the "
"CreatePrefab operation.");
PrefabDomValueReference nestedInstanceLinkPatches =
PrefabDomUtils::FindPrefabDomValue(nestedInstanceLinkDom->get(), PrefabDomUtils::PatchesName);
AZ_Assert(
nestedInstanceLinkPatches.has_value(),
"A valid DOM for patches was not found for the link corresponding to one of the instances provided as input for the "
"CreatePrefab operation.");
PrefabDom patchesCopyForUndoSupport;
patchesCopyForUndoSupport.CopyFrom(nestedInstanceLinkPatches->get(), patchesCopyForUndoSupport.GetAllocator());
PrefabDomReference nestedInstanceLinkDom = nestedInstanceLink->get().GetLinkDom();
if (nestedInstanceLinkDom.has_value())
{
PrefabDomValueReference nestedInstanceLinkPatches =
PrefabDomUtils::FindPrefabDomValue(nestedInstanceLinkDom->get(), PrefabDomUtils::PatchesName);
if (nestedInstanceLinkPatches.has_value())
{
patchesCopyForUndoSupport.CopyFrom(nestedInstanceLinkPatches->get(), patchesCopyForUndoSupport.GetAllocator());
}
}
PrefabUndoHelpers::RemoveLink(
sourceInstance->GetTemplateId(), targetTemplateId, sourceInstance->GetInstanceAlias(), sourceInstance->GetLinkId(),
AZStd::move(patchesCopyForUndoSupport), undoBatch);
@@ -595,8 +594,41 @@ namespace AzToolsFramework
Instance& entityOwningInstance = owningInstanceOfParentEntity->get();
// Get the template for our owning instance from the root prefab DOM and use that to generate our patch
AZStd::vector<InstanceOptionalConstReference> pathOfInstances;
InstanceOptionalReference rootInstance = owningInstanceOfParentEntity;
while (rootInstance->get().GetParentInstance() != AZStd::nullopt)
{
pathOfInstances.emplace_back(rootInstance);
rootInstance = rootInstance->get().GetParentInstance();
}
AZStd::string aliasPathResult = "";
for (auto instanceIter = pathOfInstances.rbegin(); instanceIter != pathOfInstances.rend(); ++instanceIter)
{
aliasPathResult.append("/Instances/");
aliasPathResult.append((*instanceIter)->get().GetInstanceAlias());
}
PrefabDomPath rootPrefabDomPath(aliasPathResult.c_str());
PrefabDom& rootPrefabTemplateDom = m_prefabSystemComponentInterface->FindTemplateDom(rootInstance->get().GetTemplateId());
auto instanceDomFromRootValue = rootPrefabDomPath.Get(rootPrefabTemplateDom);
if (!instanceDomFromRootValue)
{
return AZ::Failure<AZStd::string>("Could not load Instance DOM from the top level ancestor's DOM.");
}
PrefabDomValueReference instanceDomFromRoot = *instanceDomFromRootValue;
if (!instanceDomFromRoot.has_value())
{
return AZ::Failure<AZStd::string>("Could not load Instance DOM from the top level ancestor's DOM.");
}
PrefabDom instanceDomBeforeUpdate;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBeforeUpdate, entityOwningInstance);
instanceDomBeforeUpdate.CopyFrom(instanceDomFromRoot.value().get(), instanceDomBeforeUpdate.GetAllocator());
ScopedUndoBatch undoBatch("Add Entity");
@@ -674,6 +706,9 @@ namespace AzToolsFramework
bool isInstanceContainerEntity = IsInstanceContainerEntity(entityId) && !IsLevelInstanceContainerEntity(entityId);
bool isNewParentOwnedByDifferentInstance = false;
bool isInFocusTree = m_prefabFocusPublicInterface->IsOwningPrefabInFocusHierarchy(entityId);
bool isOwnedByFocusedPrefabInstance = m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId);
if (beforeParentId != afterParentId)
{
// If the entity parent changed, verify if the owning instance changed too
@@ -727,7 +762,7 @@ namespace AzToolsFramework
}
}
if (isInstanceContainerEntity)
if (isInFocusTree && !isOwnedByFocusedPrefabInstance)
{
if (isNewParentOwnedByDifferentInstance)
{
@@ -1144,6 +1179,10 @@ namespace AzToolsFramework
AZ::EntityId firstEntityIdToDelete = entityIdsNoFocusContainer[0];
InstanceOptionalReference commonOwningInstance = GetOwnerInstanceByEntityId(firstEntityIdToDelete);
if (!commonOwningInstance.has_value())
{
return AZ::Failure(AZStd::string("Cannot delete entities belonging to an invalid instance"));
}
// If the first entity id is a container entity id, then we need to mark its parent as the common owning instance because you
// cannot delete an instance from itself.
@@ -1644,6 +1683,144 @@ namespace AzToolsFramework
return true;
}
void PrefabPublicHandler::AddNewEntityToSortOrder(
Instance& owningInstance,
PrefabDom& domToAddEntityUnder,
const EntityAlias& parentEntityAlias,
const EntityAlias& entityToAddAlias)
{
// Find the parent entity to get its sort order component
auto findParentEntity = [&]() -> rapidjson::Value*
{
if (auto containerEntityIter = domToAddEntityUnder.FindMember(PrefabDomUtils::ContainerEntityName);
containerEntityIter != domToAddEntityUnder.MemberEnd())
{
if (parentEntityAlias == containerEntityIter->value[PrefabDomUtils::EntityIdName].GetString())
{
return &containerEntityIter->value;
}
}
if (auto entitiesIter = domToAddEntityUnder.FindMember(PrefabDomUtils::EntitiesName);
entitiesIter != domToAddEntityUnder.MemberEnd())
{
for (auto entityIter = entitiesIter->value.MemberBegin(); entityIter != entitiesIter->value.MemberEnd(); ++entityIter)
{
if (parentEntityAlias == entityIter->value[PrefabDomUtils::EntityIdName].GetString())
{
return &entityIter->value;
}
}
}
return nullptr;
};
rapidjson::Value* parentEntityValue = findParentEntity();
if (parentEntityValue == nullptr)
{
return;
}
// Get the list of selected entities, we'll insert our duplicated entities after the last selected
// sibling in their parent's list, e.g. for:
// - Entity1
// - Entity2 (selected)
// - Entity3
// - Entity4 (selected)
// - Entity5
// Our duplicate selection command would create duplicate Entity2 and Entity4 and insert them after Entity4:
// - Entity1
// - Entity2
// - Entity3
// - Entity4
// - Entity2 (new, selected after duplicate)
// - Entity4 (new, selected after duplicate)
// - Entity5
AzToolsFramework::EntityIdList selectedEntities;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
selectedEntities, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities);
// Find the EditorEntitySortComponent DOM
auto componentsIter = parentEntityValue->FindMember(PrefabDomUtils::ComponentsName);
if (componentsIter == parentEntityValue->MemberEnd())
{
return;
}
for (auto componentIter = componentsIter->value.MemberBegin(); componentIter != componentIter->value.MemberEnd();
++componentIter)
{
// Check the component type
auto typeFieldIter = componentIter->value.FindMember(PrefabDomUtils::TypeName);
if (typeFieldIter == componentIter->value.MemberEnd())
{
continue;
}
AZ::JsonDeserializerSettings jsonDeserializerSettings;
AZ::Uuid typeId = AZ::Uuid::CreateNull();
AZ::JsonSerialization::LoadTypeId(typeId, typeFieldIter->value);
if (typeId != azrtti_typeid<Components::EditorEntitySortComponent>())
{
continue;
}
// Check for the entity order field
auto orderMembersIter = componentIter->value.FindMember(PrefabDomUtils::EntityOrderName);
if (orderMembersIter == componentIter->value.MemberEnd() || !orderMembersIter->value.IsArray())
{
continue;
}
// Scan for the last selected entity in the list (if any) to determine where to add our entries
rapidjson::Value newOrder(rapidjson::kArrayType);
auto insertValuesAfter = orderMembersIter->value.End();
for (auto orderMemberIter = orderMembersIter->value.Begin(); orderMemberIter != orderMembersIter->value.End();
++orderMemberIter)
{
if (!orderMemberIter->IsString())
{
continue;
}
const char* value = orderMemberIter->GetString();
for (AZ::EntityId selectedEntity : selectedEntities)
{
auto alias = owningInstance.GetEntityAlias(selectedEntity);
if (alias.has_value() && alias.value().get() == value)
{
insertValuesAfter = orderMemberIter;
break;
}
}
}
// Construct our new array with the new order - insertion may happen at end, so check for that in the loop itself
for (auto orderMemberIter = orderMembersIter->value.Begin();; ++orderMemberIter)
{
if (orderMemberIter != orderMembersIter->value.End())
{
newOrder.PushBack(orderMemberIter->Move(), domToAddEntityUnder.GetAllocator());
}
if (orderMemberIter == insertValuesAfter)
{
newOrder.PushBack(
rapidjson::Value(entityToAddAlias.c_str(), domToAddEntityUnder.GetAllocator()),
domToAddEntityUnder.GetAllocator());
}
if (orderMemberIter == orderMembersIter->value.End())
{
break;
}
}
// Replace the order with our newly constructed one
orderMembersIter->value.Swap(newOrder);
break;
}
}
void PrefabPublicHandler::DuplicateNestedEntitiesInInstance(Instance& commonOwningInstance,
const AZStd::vector<AZ::Entity*>& entities, PrefabDom& domToAddDuplicatedEntitiesUnder,
EntityIdList& duplicatedEntityIds, AZStd::unordered_map<EntityAlias, EntityAlias>& oldAliasToNewAliasMap)
@@ -1701,6 +1878,73 @@ namespace AzToolsFramework
PrefabDom entityDomAfter(&domToAddDuplicatedEntitiesUnder.GetAllocator());
entityDomAfter.Parse(newEntityDomString.toUtf8().constData());
EntityAlias parentEntityAlias;
if (auto componentsIter = entityDomAfter.FindMember(PrefabDomUtils::ComponentsName);
componentsIter != entityDomAfter.MemberEnd())
{
auto checkComponent = [&](const rapidjson::Value& value) -> bool
{
if (!value.IsObject())
{
return false;
}
// Check the component type
auto typeFieldIter = value.FindMember(PrefabDomUtils::TypeName);
if (typeFieldIter == value.MemberEnd())
{
return false;
}
AZ::JsonDeserializerSettings jsonDeserializerSettings;
AZ::Uuid typeId = AZ::Uuid::CreateNull();
AZ::JsonSerialization::LoadTypeId(typeId, typeFieldIter->value);
// Prefabs get serialized with the Editor transform component type, check for that
if (typeId != azrtti_typeid<Components::TransformComponent>())
{
return false;
}
if (auto parentEntityIter = value.FindMember("Parent Entity");
parentEntityIter != value.MemberEnd())
{
parentEntityAlias = parentEntityIter->value.GetString();
return true;
}
return false;
};
if (componentsIter->value.IsObject())
{
for (auto componentIter = componentsIter->value.MemberBegin(); componentIter != componentsIter->value.MemberEnd();
++componentIter)
{
if (checkComponent(componentIter->value))
{
break;
}
}
}
else if (componentsIter->value.IsArray())
{
for (auto componentIter = componentsIter->value.Begin(); componentIter != componentsIter->value.End();
++componentIter)
{
if (checkComponent(*componentIter))
{
break;
}
}
}
}
// Insert our entity into its parent's sort order
if (!parentEntityAlias.empty())
{
AddNewEntityToSortOrder(commonOwningInstance, domToAddDuplicatedEntitiesUnder, parentEntityAlias, newEntityAlias);
}
// Add the new Entity DOM to the Entities member of the instance
rapidjson::Value aliasName(newEntityAlias.c_str(), static_cast<rapidjson::SizeType>(newEntityAlias.length()), domToAddDuplicatedEntitiesUnder.GetAllocator());
entitiesIter->value.AddMember(AZStd::move(aliasName), entityDomAfter, domToAddDuplicatedEntitiesUnder.GetAllocator());
@@ -78,6 +78,8 @@ namespace AzToolsFramework
InstanceOptionalReference GetOwnerInstanceByEntityId(AZ::EntityId entityId) const;
bool EntitiesBelongToSameInstance(const EntityIdList& entityIds) const;
void AddNewEntityToSortOrder(Instance& owningInstance, PrefabDom& domToAddEntityUnder,
const EntityAlias& parentEntityAlias, const EntityAlias& entityToAddAlias);
/**
* Duplicate a list of entities owned by a common owning instance by directly
@@ -60,6 +60,7 @@ namespace AzToolsFramework
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabCatchmentProcessor::Reflect(context);
AzToolsFramework::Prefab::PrefabConversionUtils::EditorInfoRemover::Reflect(context);
PrefabPublicRequestHandler::Reflect(context);
PrefabFocusHandler::Reflect(context);
PrefabLoader::Reflect(context);
PrefabSystemScriptingHandler::Reflect(context);
@@ -6,10 +6,10 @@
*
*/
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzCore/Utils/Utils.h>
#include <AzToolsFramework/Thumbnails/SourceControlThumbnail.h>
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
#include <AzFramework/API/ApplicationAPI.h>
namespace AzToolsFramework
{
@@ -68,12 +68,12 @@ namespace AzToolsFramework
SourceControlThumbnail::SourceControlThumbnail(SharedThumbnailKey key)
: Thumbnail(key)
{
const char* engineRoot = nullptr;
AzFramework::ApplicationRequests::Bus::BroadcastResult(engineRoot, &AzFramework::ApplicationRequests::GetEngineRoot);
AZ_Assert(engineRoot, "Engine Root not initialized");
AZ::IO::FixedMaxPath engineRoot = AZ::Utils::GetEnginePath();
AZ_Assert(!engineRoot.empty(), "Engine Root not initialized");
m_writableIconPath = (engineRoot / WRITABLE_ICON_PATH).String();
m_nonWritableIconPath = (engineRoot / NONWRITABLE_ICON_PATH).String();
AzFramework::StringFunc::Path::Join(engineRoot, WRITABLE_ICON_PATH, m_writableIconPath);
AzFramework::StringFunc::Path::Join(engineRoot, NONWRITABLE_ICON_PATH, m_nonWritableIconPath);
BusConnect();
}
@@ -90,8 +90,8 @@ namespace AzToolsFramework
AZ_Assert(sourceControlKey, "Incorrect key type, excpected SourceControlThumbnailKey");
AZStd::string myFileName(sourceControlKey->GetFileName());
AzFramework::StringFunc::Path::Normalize(myFileName);
if (AzFramework::StringFunc::Equal(myFileName.c_str(), filename))
AZ::StringFunc::Path::Normalize(myFileName);
if (AZ::StringFunc::Equal(myFileName.c_str(), filename))
{
Update();
}
@@ -23,7 +23,6 @@ namespace AzToolsFramework
// save 2k at a time :( need a better way to do this.
AZStd::size_t pos = 0;
AZStd::size_t remaining = m_windowState.size();
AZ::u8* charData = (AZ::u8*)windowState.begin();
while (remaining > 0)
{
@@ -31,7 +30,6 @@ namespace AzToolsFramework
m_serializableWindowState.push_back();
m_serializableWindowState.back().assign((AZ::u8*)windowState.begin() + pos, (AZ::u8*)windowState.begin() + pos + bytes_this_gulp);
pos += bytes_this_gulp;
charData += bytes_this_gulp;
remaining -= bytes_this_gulp;
}
}
@@ -362,23 +362,11 @@ namespace AzToolsFramework
// Tick the component app.
AZ::ComponentApplication* pApp = nullptr;
EBUS_EVENT_RESULT(pApp, AZ::ComponentApplicationBus, GetApplication);
if (pApp)
if (pApp && m_ptrTicker)
{
AZStd::chrono::system_clock::time_point now = AZStd::chrono::system_clock::now();
static AZStd::chrono::system_clock::time_point lastUpdate = now;
AZStd::chrono::duration<float> delta = now - lastUpdate;
float deltaTime = delta.count();
lastUpdate = now;
if (m_ptrTicker)
{
AZ::SystemTickBus::ExecuteQueuedEvents();
AZ::SystemTickBus::Broadcast(&AZ::SystemTickEvents::OnSystemTick);
pApp->Tick(deltaTime);
}
AZ::SystemTickBus::ExecuteQueuedEvents();
AZ::SystemTickBus::Broadcast(&AZ::SystemTickEvents::OnSystemTick);
pApp->Tick();
}
m_bTicking = false;
@@ -64,6 +64,9 @@ namespace AzToolsFramework::Prefab
);
m_backButton->setToolTip("Up one level (-)");
// Currently hide this button until we can correctly disable/enable it based on context.
m_backButton->hide();
}
void PrefabViewportFocusPathHandler::OnPrefabFocusChanged()
@@ -45,6 +45,7 @@ AZ_POP_DISABLE_WARNING
#include <AzToolsFramework/AssetBrowser/EBusFindAssetTypeByName.h>
#include <AzToolsFramework/ComponentMode/ComponentModeDelegate.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <AzToolsFramework/Slice/SliceDataFlagsCommand.h>
#include <AzToolsFramework/Slice/SliceMetadataEntityContextBus.h>
@@ -606,6 +607,7 @@ namespace AzToolsFramework
AzToolsFramework::ComponentModeFramework::EditorComponentModeNotificationBus::Handler::BusConnect(
AzToolsFramework::GetEntityContextId());
ViewportEditorModeNotificationsBus::Handler::BusConnect(GetEntityContextId());
}
EntityPropertyEditor::~EntityPropertyEditor()
@@ -618,7 +620,8 @@ namespace AzToolsFramework
AZ::EntitySystemBus::Handler::BusDisconnect();
EditorEntityContextNotificationBus::Handler::BusDisconnect();
AzToolsFramework::ComponentModeFramework::EditorComponentModeNotificationBus::Handler::BusDisconnect();
ViewportEditorModeNotificationsBus::Handler::BusDisconnect();
for (auto& entityId : m_overrideSelectedEntityIds)
{
DisconnectFromEntityBuses(entityId);
@@ -892,25 +895,51 @@ namespace AzToolsFramework
{
if (!m_prefabsAreEnabled)
{
return m_isLevelEntityEditor ? InspectorLayout::LEVEL : InspectorLayout::ENTITY;
return m_isLevelEntityEditor ? InspectorLayout::Level : InspectorLayout::Entity;
}
// Prefabs layout logic
// If this is the container entity for the root instance, treat it like a level entity.
AZ::EntityId levelContainerEntityId = m_prefabPublicInterface->GetLevelInstanceContainerEntityId();
if (AZStd::find(m_selectedEntityIds.begin(), m_selectedEntityIds.end(), levelContainerEntityId) != m_selectedEntityIds.end())
{
if (m_selectedEntityIds.size() > 1)
{
return InspectorLayout::INVALID;
return InspectorLayout::Invalid;
}
else
{
return InspectorLayout::LEVEL;
return InspectorLayout::Level;
}
}
else
{
return InspectorLayout::ENTITY;
// If this is the container entity for the currently focused prefab, utilize a separate layout.
if (auto prefabFocusPublicInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabFocusPublicInterface>::Get())
{
AzFramework::EntityContextId editorEntityContextId = AzFramework::EntityContextId::CreateNull();
EditorEntityContextRequestBus::BroadcastResult(
editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
AZ::EntityId focusedPrefabContainerEntityId =
prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId);
if (AZStd::find(m_selectedEntityIds.begin(), m_selectedEntityIds.end(), focusedPrefabContainerEntityId) !=
m_selectedEntityIds.end())
{
if (m_selectedEntityIds.size() > 1)
{
return InspectorLayout::Invalid;
}
else
{
return InspectorLayout::ContainerEntityOfFocusedPrefab;
}
}
}
}
return InspectorLayout::Entity;
}
void EntityPropertyEditor::UpdateEntityDisplay()
@@ -919,7 +948,7 @@ namespace AzToolsFramework
InspectorLayout layout = GetCurrentInspectorLayout();
if (layout == InspectorLayout::LEVEL)
if (!m_prefabsAreEnabled && layout == InspectorLayout::Level)
{
AZStd::string levelName;
AzToolsFramework::EditorRequestBus::BroadcastResult(levelName, &AzToolsFramework::EditorRequests::GetLevelName);
@@ -961,14 +990,19 @@ namespace AzToolsFramework
InspectorLayout layout = GetCurrentInspectorLayout();
if (layout == InspectorLayout::LEVEL)
if (layout == InspectorLayout::Level)
{
// The Level Inspector should only have a list of selectable components after the
// level entity itself is valid (i.e. "selected").
return selection.empty() ? SelectionEntityTypeInfo::None : SelectionEntityTypeInfo::LevelEntity;
}
if (layout == InspectorLayout::INVALID)
if (layout == InspectorLayout::ContainerEntityOfFocusedPrefab)
{
return selection.empty() ? SelectionEntityTypeInfo::None : SelectionEntityTypeInfo::ContainerEntityOfFocusedPrefab;
}
if (layout == InspectorLayout::Invalid)
{
return SelectionEntityTypeInfo::Mixed;
}
@@ -1138,7 +1172,8 @@ namespace AzToolsFramework
}
}
bool isLevelLayout = GetCurrentInspectorLayout() == InspectorLayout::LEVEL;
bool isLevelLayout = GetCurrentInspectorLayout() == InspectorLayout::Level;
bool isContainerOfFocusedPrefabLayout = GetCurrentInspectorLayout() == InspectorLayout::ContainerEntityOfFocusedPrefab;
m_gui->m_entityDetailsLabel->setText(entityDetailsLabelText);
m_gui->m_entityDetailsLabel->setVisible(entityDetailsVisible);
@@ -1146,10 +1181,14 @@ namespace AzToolsFramework
m_gui->m_entityNameLabel->setVisible(hasEntitiesDisplayed);
m_gui->m_entityIcon->setVisible(hasEntitiesDisplayed);
m_gui->m_pinButton->setVisible(m_overrideSelectedEntityIds.empty() && hasEntitiesDisplayed && !m_isSystemEntityEditor);
m_gui->m_statusLabel->setVisible(hasEntitiesDisplayed && !m_isSystemEntityEditor && !isLevelLayout);
m_gui->m_statusComboBox->setVisible(hasEntitiesDisplayed && !m_isSystemEntityEditor && !isLevelLayout);
m_gui->m_entityIdLabel->setVisible(hasEntitiesDisplayed && !m_isSystemEntityEditor && !isLevelLayout);
m_gui->m_entityIdText->setVisible(hasEntitiesDisplayed && !m_isSystemEntityEditor && !isLevelLayout);
m_gui->m_statusLabel->setVisible(
hasEntitiesDisplayed && !m_isSystemEntityEditor && !isLevelLayout);
m_gui->m_statusComboBox->setVisible(
hasEntitiesDisplayed && !m_isSystemEntityEditor && !isLevelLayout);
m_gui->m_entityIdLabel->setVisible(
hasEntitiesDisplayed && !m_isSystemEntityEditor && !isLevelLayout);
m_gui->m_entityIdText->setVisible(
hasEntitiesDisplayed && !m_isSystemEntityEditor && !isLevelLayout);
bool displayComponentSearchBox = hasEntitiesDisplayed;
if (hasEntitiesDisplayed)
@@ -1157,7 +1196,9 @@ namespace AzToolsFramework
// Build up components to display
SharedComponentArray sharedComponentArray;
BuildSharedComponentArray(sharedComponentArray,
!(selectionEntityTypeInfo == SelectionEntityTypeInfo::OnlyStandardEntities || selectionEntityTypeInfo == SelectionEntityTypeInfo::OnlyPrefabEntities));
!(selectionEntityTypeInfo == SelectionEntityTypeInfo::OnlyStandardEntities ||
selectionEntityTypeInfo == SelectionEntityTypeInfo::OnlyPrefabEntities) ||
selectionEntityTypeInfo == SelectionEntityTypeInfo::ContainerEntityOfFocusedPrefab);
if (sharedComponentArray.size() == 0)
{
@@ -1173,7 +1214,8 @@ namespace AzToolsFramework
UpdateEntityDisplay();
}
m_gui->m_darkBox->setVisible(displayComponentSearchBox && !m_isSystemEntityEditor && !isLevelLayout);
m_gui->m_darkBox->setVisible(
displayComponentSearchBox && !m_isSystemEntityEditor && !isLevelLayout && !isContainerOfFocusedPrefabLayout);
m_gui->m_entitySearchBox->setVisible(displayComponentSearchBox);
bool displayAddComponentMenu = CanAddComponentsToSelection(selectionEntityTypeInfo);
@@ -1557,7 +1599,6 @@ namespace AzToolsFramework
for (size_t entityIndex = 1; entityIndex < m_selectedEntityIds.size(); ++entityIndex)
{
entity = GetSelectedEntityById(m_selectedEntityIds[entityIndex]);
AZ_Assert(entity, "Entity id selected for display but no such entity exists");
if (!entity)
{
continue;
@@ -4663,13 +4704,6 @@ namespace AzToolsFramework
{
if (mimeData->hasFormat(AssetBrowser::AssetBrowserEntry::GetMimeType()))
{
// extra special case: MTLs from FBX drags are ignored. are we dragging a FBX file?
bool isDraggingFBXFile = false;
AssetBrowser::AssetBrowserEntry::ForEachEntryInMimeData<AssetBrowser::SourceAssetBrowserEntry>(mimeData, [&](const AssetBrowser::SourceAssetBrowserEntry* source)
{
isDraggingFBXFile = isDraggingFBXFile || AzFramework::StringFunc::Equal(source->GetExtension().c_str(), ".fbx", false);
});
// the usual case - we only allow asset browser drops of assets that have actually been associated with a kind of component.
AssetBrowser::AssetBrowserEntry::ForEachEntryInMimeData<AssetBrowser::ProductAssetBrowserEntry>(mimeData, [&](const AssetBrowser::ProductAssetBrowserEntry* product)
{
@@ -4681,17 +4715,7 @@ namespace AzToolsFramework
if (canCreateComponent && !componentTypeId.IsNull())
{
// we have a component type that handles this asset.
// but we disallow it if its a MTL file from a FBX and the FBX itself is being dragged. Its still allowed
// to drag the actual MTL.
EBusFindAssetTypeByName materialAssetTypeResult("Material");
AZ::AssetTypeInfoBus::BroadcastResult(materialAssetTypeResult, &AZ::AssetTypeInfo::GetAssetType);
AZ::Data::AssetType materialAssetType = materialAssetTypeResult.GetAssetType();
if ((!isDraggingFBXFile) || (product->GetAssetType() != materialAssetType))
{
callbackFunction(product);
}
callbackFunction(product);
}
});
}
@@ -354,7 +354,8 @@ namespace AzToolsFramework
OnlyLayerEntities,
OnlyPrefabEntities,
Mixed,
LevelEntity
LevelEntity,
ContainerEntityOfFocusedPrefab
};
/**
* Returns what kinds of entities are in the current selection. This is used because mixed selection
@@ -364,7 +365,7 @@ namespace AzToolsFramework
SelectionEntityTypeInfo GetSelectionEntityTypeInfo(const EntityIdList& selection) const;
/**
* Returns true if a selection matching the passed in selection informatation allows components to be added.
* Returns true if a selection matching the passed in selection information allows components to be added.
*/
bool CanAddComponentsToSelection(const SelectionEntityTypeInfo& selectionEntityTypeInfo) const;
@@ -581,9 +582,10 @@ namespace AzToolsFramework
enum class InspectorLayout
{
ENTITY = 0, // All selected entities are regular entities
LEVEL, // The selected entity is the level prefab container entity
INVALID // Other entities are selected alongside the level prefab container entity
Entity = 0, // All selected entities are regular entities.
Level, // The selected entity is the prefab container entity for the level prefab, or the slice level entity.
ContainerEntityOfFocusedPrefab, // The selected entity is the prefab container entity for the focused prefab.
Invalid // Other entities are selected alongside the level prefab container entity.
};
InspectorLayout GetCurrentInspectorLayout() const;
@@ -527,8 +527,8 @@ namespace AzToolsFramework
m_errorButton = nullptr;
}
}
void PropertyAssetCtrl::UpdateErrorButton(const AZStd::string& errorLog)
void PropertyAssetCtrl::UpdateErrorButton()
{
if (m_errorButton)
{
@@ -543,12 +543,17 @@ namespace AzToolsFramework
m_errorButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_errorButton->setFixedSize(QSize(16, 16));
m_errorButton->setMouseTracking(true);
m_errorButton->setIcon(QIcon("Icons/PropertyEditor/error_icon.png"));
m_errorButton->setIcon(QIcon(":/PropertyEditor/Resources/error_icon.png"));
m_errorButton->setToolTip("Show Errors");
// Insert the error button after the asset label
qobject_cast<QHBoxLayout*>(layout())->insertWidget(1, m_errorButton);
}
}
void PropertyAssetCtrl::UpdateErrorButtonWithLog(const AZStd::string& errorLog)
{
UpdateErrorButton();
// Connect pressed to opening the error dialog
// Must capture this for call to QObject::connect
@@ -587,6 +592,21 @@ namespace AzToolsFramework
logDialog->show();
});
}
void PropertyAssetCtrl::UpdateErrorButtonWithMessage(const AZStd::string& message)
{
UpdateErrorButton();
connect(m_errorButton, &QPushButton::clicked, this, [this, message]() {
QMessageBox::critical(nullptr, "Error", message.c_str());
// Without this, the error button would maintain focus after clicking, which left the red error icon in a blue-highlighted state
if (parentWidget())
{
parentWidget()->setFocus();
}
});
}
void PropertyAssetCtrl::ClearAssetInternal()
{
@@ -960,7 +980,6 @@ namespace AzToolsFramework
else
{
const AZ::Data::AssetId assetID = GetCurrentAssetID();
m_currentAssetHint = "";
AZ::Outcome<AssetSystem::JobInfoContainer> jobOutcome = AZ::Failure();
AssetSystemJobRequestBus::BroadcastResult(jobOutcome, &AssetSystemJobRequestBus::Events::GetAssetJobsInfoByAssetID, assetID, false, false);
@@ -1018,7 +1037,7 @@ namespace AzToolsFramework
// In case of failure, render failure icon
case AssetSystem::JobStatus::Failed:
{
UpdateErrorButton(errorLog);
UpdateErrorButtonWithLog(errorLog);
}
break;
@@ -1043,6 +1062,10 @@ namespace AzToolsFramework
m_currentAssetHint = assetPath;
}
}
else
{
UpdateErrorButtonWithMessage(AZStd::string::format("Asset is missing.\n\nID: %s\nHint:%s", assetID.ToString<AZStd::string>().c_str(), GetCurrentAssetHint().c_str()));
}
}
// Get the asset file name
@@ -168,7 +168,9 @@ namespace AzToolsFramework
bool IsCorrectMimeData(const QMimeData* pData, AZ::Data::AssetId* pAssetId = nullptr, AZ::Data::AssetType* pAssetType = nullptr) const;
void ClearErrorButton();
void UpdateErrorButton(const AZStd::string& errorLog);
void UpdateErrorButton();
void UpdateErrorButtonWithLog(const AZStd::string& errorLog);
void UpdateErrorButtonWithMessage(const AZStd::string& message);
virtual const AZStd::string GetFolderSelection() const { return AZStd::string(); }
virtual void SetFolderSelection(const AZStd::string& /* folderPath */) {}
virtual void ClearAssetInternal();
@@ -169,6 +169,10 @@ namespace AzToolsFramework
{
AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&PropertyEditorGUIMessages::Bus::Events::RequestWrite, newCtrl);
});
this->connect(newCtrl, &PropertyControl::editingFinished, this, [newCtrl]()
{
AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&PropertyEditorGUIMessages::Bus::Handler::OnEditingFinished, newCtrl);
});
// note: Qt automatically disconnects objects from each other when either end is destroyed, no need to worry about delete.
// Set the value range to that of ValueType as clamped to the range of QtWidgetValueType
@@ -150,10 +150,7 @@ namespace AzToolsFramework
TypeBeingHandled actualValue = instance;
for (int idx = 0; idx < m_common.GetElementCount(); ++idx)
{
if (elements[idx]->wasValueEditedByUser())
{
actualValue.SetElement(idx, static_cast<float>(elements[idx]->getValue()));
}
actualValue.SetElement(idx, static_cast<float>(elements[idx]->getValue()));
}
instance = actualValue;
}
@@ -1201,8 +1201,6 @@ namespace AzToolsFramework
.arg((item->parent() == nullptr) ? item->m_entity->GetName().c_str() : GetNodeDisplayName(*item->m_node).c_str()));
}
SliceTargetTreeItem* parent = nullptr;
AZStd::vector<SliceAssetPtr> validSliceAssets = GetValidTargetAssetsForField(*item);
// For the selected item populate the tree of all valid slice targets.
@@ -1274,7 +1272,6 @@ namespace AzToolsFramework
selectButton->setChecked(true);
}
parent = sliceItem;
++level;
}
}
@@ -158,7 +158,10 @@ namespace UnitTest
{
// Create & Start a new ToolsApplication if there's no existing one
m_app = CreateTestApplication();
m_app->Start(AzFramework::Application::Descriptor());
AZ::ComponentApplication::StartupParameters startupParameters;
startupParameters.m_loadAssetCatalog = false;
m_app->Start(AzFramework::Application::Descriptor(), startupParameters);
}
// without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
@@ -23,11 +23,11 @@ namespace AzToolsFramework
/// @name Reverse URLs.
/// Used to identify common actions and override them when necessary.
//@{
static const AZ::Crc32 s_backAction = AZ_CRC("com.o3de.action.common.back", 0xd772a2af);
static const AZ::Crc32 s_deleteAction = AZ_CRC("com.o3de.action.common.delete", 0x5731f6cb);
static const AZ::Crc32 s_duplicateAction = AZ_CRC("com.o3de.action.common.duplicate", 0x08ccf461);
static const AZ::Crc32 s_nextComponentMode = AZ_CRC("com.o3de.action.common.nextComponentMode", 0xcc26094f);
static const AZ::Crc32 s_previousComponentMode = AZ_CRC("com.o3de.action.common.previousComponentMode", 0x0d18ff39);
static const AZ::Crc32 s_backAction = AZ_CRC("com.o3de.action.common.back", 0x80c3030f);
static const AZ::Crc32 s_deleteAction = AZ_CRC("com.o3de.action.common.delete", 0x58e78eed);
static const AZ::Crc32 s_duplicateAction = AZ_CRC("com.o3de.action.common.duplicate", 0xbc5a4a23);
static const AZ::Crc32 s_nextComponentMode = AZ_CRC("com.o3de.action.common.nextComponentMode", 0xf9aca3a8);
static const AZ::Crc32 s_previousComponentMode = AZ_CRC("com.o3de.action.common.previousComponentMode", 0x0580eaec);
//@}
/// Specific Action properties to be sent to a type implementing
@@ -6,6 +6,7 @@
*
*/
#include <AzFramework/Render/IntersectorInterface.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
namespace AzToolsFramework
@@ -62,4 +63,47 @@ namespace AzToolsFramework
return circleBoundWidth;
}
AZ::Vector3 FindClosestPickIntersection(const AzFramework::RenderGeometry::RayRequest& rayRequest, const float defaultDistance)
{
AzFramework::RenderGeometry::RayResult renderGeometryIntersectionResult;
AzFramework::RenderGeometry::IntersectorBus::EventResult(
renderGeometryIntersectionResult, AzToolsFramework::GetEntityContextId(),
&AzFramework::RenderGeometry::IntersectorBus::Events::RayIntersect, rayRequest);
// attempt a ray intersection with any visible mesh and return the intersection position if successful
if (renderGeometryIntersectionResult)
{
return renderGeometryIntersectionResult.m_worldPosition;
}
else
{
const AZ::Vector3 rayDirection = (rayRequest.m_endWorldPosition - rayRequest.m_startWorldPosition).GetNormalized();
return rayRequest.m_startWorldPosition + rayDirection * defaultDistance;
}
}
void RefreshRayRequest(
AzFramework::RenderGeometry::RayRequest& rayRequest,
const ViewportInteraction::ProjectedViewportRay& viewportRay,
const float rayLength)
{
AZ_Assert(rayLength > 0.0f, "Invalid ray length passed to RefreshRayRequest");
rayRequest.m_startWorldPosition = viewportRay.origin;
rayRequest.m_endWorldPosition = viewportRay.origin + viewportRay.direction * rayLength;
}
AZ::Vector3 FindClosestPickIntersection(
const AzFramework::ViewportId viewportId,
const AzFramework::ScreenPoint& screenPoint,
const float rayLength,
const float defaultDistance)
{
AzFramework::RenderGeometry::RayRequest ray;
ray.m_onlyVisible = true; // only consider visible objects
RefreshRayRequest(ray, ViewportInteraction::ViewportScreenToWorldRay(viewportId, screenPoint), rayLength);
return FindClosestPickIntersection(ray, defaultDistance);
}
} // namespace AzToolsFramework
@@ -15,13 +15,19 @@
#include <AzFramework/Viewport/CameraState.h>
#include <AzFramework/Viewport/ClickDetector.h>
#include <AzFramework/Viewport/ViewportId.h>
#include <AzFramework/Viewport/ViewportScreen.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Viewport/ViewportTypes.h>
namespace AzFramework
{
struct ScreenPoint;
}
namespace RenderGeometry
{
struct RayRequest;
}
} // namespace AzFramework
namespace AzToolsFramework
{
@@ -162,12 +168,11 @@ namespace AzToolsFramework
//! Multiply by DeviceScalingFactor to get the position in viewport pixel space.
virtual AzFramework::ScreenPoint ViewportWorldToScreen(const AZ::Vector3& worldPosition) = 0;
//! Transforms a point from Qt widget screen space to world space based on the given clip space depth.
//! Depth specifies a relative camera depth to project in the range of [0.f, 1.f].
//! Returns the world space position if successful.
virtual AZStd::optional<AZ::Vector3> ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition, float depth) = 0;
virtual AZ::Vector3 ViewportScreenToWorld(const AzFramework::ScreenPoint& screenPosition) = 0;
//! Casts a point in screen space to a ray in world space originating from the viewport camera frustum's near plane.
//! Returns a ray containing the ray's origin and a direction normal, if successful.
virtual AZStd::optional<ProjectedViewportRay> ViewportScreenToWorldRay(const AzFramework::ScreenPoint& screenPosition) = 0;
virtual ProjectedViewportRay ViewportScreenToWorldRay(const AzFramework::ScreenPoint& screenPosition) = 0;
//! Gets the DPI scaling factor that translates Qt widget space into viewport pixel space.
virtual float DeviceScalingFactor() = 0;
@@ -178,6 +183,26 @@ namespace AzToolsFramework
//! Type to inherit to implement ViewportInteractionRequests.
using ViewportInteractionRequestBus = AZ::EBus<ViewportInteractionRequests, ViewportEBusTraits>;
//! Utility function to return a viewport ray.
inline ProjectedViewportRay ViewportScreenToWorldRay(
const AzFramework::CameraState& cameraState, const AzFramework::ScreenPoint& screenPoint)
{
const AZ::Vector3 rayOrigin = AzFramework::ScreenToWorld(screenPoint, cameraState);
const AZ::Vector3 rayDirection = (rayOrigin - cameraState.m_position).GetNormalized();
return AzToolsFramework::ViewportInteraction::ProjectedViewportRay{ rayOrigin, rayDirection };
}
//! Utility function to return a viewport ray using the ViewportInteractionRequestBus.
inline ProjectedViewportRay ViewportScreenToWorldRay(
const AzFramework::ViewportId viewportId, const AzFramework::ScreenPoint& screenPoint)
{
ProjectedViewportRay viewportRay{};
ViewportInteractionRequestBus::EventResult(
viewportRay, viewportId, &ViewportInteractionRequestBus::Events::ViewportScreenToWorldRay, screenPoint);
return viewportRay;
}
//! Interface to return only viewport specific settings (e.g. snapping).
class ViewportSettingsRequests
{
@@ -229,13 +254,6 @@ namespace AzToolsFramework
class MainEditorViewportInteractionRequests
{
public:
//! Given a point in screen space, return the picked entity (if any).
//! Picked EntityId will be returned, InvalidEntityId will be returned on failure.
virtual AZ::EntityId PickEntity(const AzFramework::ScreenPoint& point) = 0;
//! Given a point in screen space, return the terrain position in world space.
virtual AZ::Vector3 PickTerrain(const AzFramework::ScreenPoint& point) = 0;
//! Return the terrain height given a world position in 2d (xy plane).
virtual float TerrainHeight(const AZ::Vector2& position) = 0;
//! Is the user holding a modifier key to move the manipulator space from local to world.
virtual bool ShowingWorldSpace() = 0;
//! Return the widget to use as the parent for the viewport context menu.
@@ -266,7 +284,6 @@ namespace AzToolsFramework
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
//! Returns the current state of the keyboard modifier keys.
virtual KeyboardModifiers QueryKeyboardModifiers() = 0;
@@ -290,7 +307,6 @@ namespace AzToolsFramework
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
//! Returns the current time in seconds.
//! This interface can be overridden for the purposes of testing to simplify viewport input requests.
@@ -340,6 +356,21 @@ namespace AzToolsFramework
return entityContextId;
}
//! Performs an intersection test against meshes in the scene, if there is a hit (the ray intersects
//! a mesh), that position is returned, otherwise a point projected defaultDistance from the
//! origin of the ray will be returned.
//! @note The intersection will only consider visible objects.
AZ::Vector3 FindClosestPickIntersection(
AzFramework::ViewportId viewportId, const AzFramework::ScreenPoint& screenPoint, float rayLength, float defaultDistance);
//! Overload of FindClosestPickIntersection taking a RenderGeometry::RayRequest directly.
//! @note rayRequest must contain a valid ray/line segment (start/endWorldPosition must not be at the same position).
AZ::Vector3 FindClosestPickIntersection(const AzFramework::RenderGeometry::RayRequest& rayRequest, float defaultDistance);
//! Update the in/out parameter rayRequest based on the latest viewport ray.
void RefreshRayRequest(
AzFramework::RenderGeometry::RayRequest& rayRequest, const ViewportInteraction::ProjectedViewportRay& viewportRay, float rayLength);
//! Maps a mouse interaction event to a ClickDetector event.
//! @note Function only cares about up or down events, all other events are mapped to Nil (ignored).
AzFramework::ClickDetector::ClickEvent ClickDetectorEventFromViewportInteraction(
@@ -148,7 +148,6 @@ namespace AzToolsFramework
return false;
}
EditorHelpers::EditorHelpers(const EditorVisibleEntityDataCache* entityDataCache)
: m_entityDataCache(entityDataCache)
{
@@ -190,7 +189,10 @@ namespace AzToolsFramework
if (helpersVisible)
{
// some components choose to hide their icons (e.g. meshes)
if (!m_entityDataCache->IsVisibleEntityIconHidden(entityCacheIndex))
// we also do not want to test against icons that may not be showing as they're inside a 'closed' entity container
// (these icons only become visible when it is opened for editing)
if (!m_entityDataCache->IsVisibleEntityIconHidden(entityCacheIndex) &&
m_entityDataCache->IsVisibleEntityIndividuallySelectableInViewport(entityCacheIndex))
{
const AZ::Vector3& entityPosition = m_entityDataCache->GetVisibleEntityPosition(entityCacheIndex);
@@ -235,7 +237,7 @@ namespace AzToolsFramework
viewportId, &ViewportInteraction::ViewportMouseCursorRequestBus::Events::SetOverrideCursor,
ViewportInteraction::CursorStyleOverride::Forbidden);
}
if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() &&
mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down ||
mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::DoubleClick)
@@ -9,6 +9,7 @@
#include "EditorSelectionUtil.h"
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/IntersectSegment.h>
@@ -16,10 +17,20 @@
#include <AzToolsFramework/API/ComponentEntitySelectionBus.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
AZ_CVAR(
float,
ed_defaultEntityPlacementDistance,
10.0f,
nullptr,
AZ::ConsoleFunctorFlags::Null,
"The default distance to place an entity from the camera if no intersection is found");
namespace AzToolsFramework
{
// default ray length for picking in the viewport
static const float EditorPickRayLength = 1000.0f;
float GetDefaultEntityPlacementDistance()
{
return ed_defaultEntityPlacementDistance;
}
AZ::Vector3 CalculateCenterOffset(const AZ::EntityId entityId, const EditorTransformComponentSelectionRequests::Pivot pivot)
{
@@ -26,6 +26,9 @@ namespace AzFramework
namespace AzToolsFramework
{
//! Default ray length for picking in the viewport.
inline constexpr float EditorPickRayLength = 1000.0f;
//! Is the pivot at the center of the object (middle of extents) or at the
//! exported authored object root position.
inline bool Centered(const EditorTransformComponentSelectionRequests::Pivot pivot)
@@ -57,6 +60,9 @@ namespace AzToolsFramework
//! Wrapper for EBus call to return the DPI scaling for a given viewport.
float GetScreenDisplayScaling(int viewportId);
//! The default distance an entity is placed from the camera if there is no intersection.
float GetDefaultEntityPlacementDistance();
//! A utility to return the center of several points.
//! Take several positions and store the min and max of each in
//! turn - when all points have been added return the center/midpoint.
@@ -28,6 +28,7 @@
#include <AzToolsFramework/Manipulators/TranslationManipulators.h>
#include <AzToolsFramework/Maths/TransformUtils.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
#include <AzToolsFramework/ToolsComponents/EditorLockComponentBus.h>
#include <AzToolsFramework/ToolsComponents/EditorVisibilityBus.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
@@ -230,8 +231,8 @@ namespace AzToolsFramework
{
return mouseInteraction.m_mouseInteraction.m_mouseButtons.Middle() &&
mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down &&
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl() &&
!mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Alt();
!mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Alt() &&
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl();
}
static bool IndividualDitto(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
@@ -242,12 +243,12 @@ namespace AzToolsFramework
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl();
}
static bool SnapTerrain(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
static bool SnapSurface(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
{
return mouseInteraction.m_mouseInteraction.m_mouseButtons.Middle() &&
mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down &&
(mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Alt() ||
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl());
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Shift() &&
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl();
}
static bool ManipulatorDitto(
@@ -408,7 +409,7 @@ namespace AzToolsFramework
const AzFramework::CameraState cameraState = GetCameraState(viewportId);
for (size_t entityCacheIndex = 0; entityCacheIndex < entityDataCache.VisibleEntityDataCount(); ++entityCacheIndex)
{
if (!entityDataCache.IsVisibleEntitySelectableInViewport(entityCacheIndex))
if (!entityDataCache.IsVisibleEntityIndividuallySelectableInViewport(entityCacheIndex))
{
continue;
}
@@ -891,60 +892,37 @@ namespace AzToolsFramework
prevModifiers = action.m_modifiers;
}
static void HandleAccents(
const bool hasSelectedEntities,
const AZ::EntityId entityIdUnderCursor,
const bool ctrlHeld,
AZ::EntityId& hoveredEntityId,
void HandleAccents(
const AZ::EntityId currentEntityIdUnderCursor,
AZ::EntityId& hoveredEntityIdUnderCursor,
const HandleAccentsContext& handleAccentsContext,
const ViewportInteraction::MouseButtons mouseButtons,
const bool usingBoxSelect)
const AZStd::function<void(AZ::EntityId, bool)>& setEntityAccentedFn)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
const bool invalidMouseButtonHeld = mouseButtons.Middle() || mouseButtons.Right();
const bool hasSelectedEntities = handleAccentsContext.m_hasSelectedEntities;
const bool ctrlHeld = handleAccentsContext.m_ctrlHeld;
const bool boxSelect = handleAccentsContext.m_usingBoxSelect;
const bool stickySelect = handleAccentsContext.m_usingStickySelect;
const bool canSelect = stickySelect ? !hasSelectedEntities || ctrlHeld : true;
if ((hoveredEntityId.IsValid() && hoveredEntityId != entityIdUnderCursor) ||
(hasSelectedEntities && !ctrlHeld && hoveredEntityId.IsValid()) || invalidMouseButtonHeld)
const bool removePreviousAccent =
(currentEntityIdUnderCursor != hoveredEntityIdUnderCursor && hoveredEntityIdUnderCursor.IsValid()) || invalidMouseButtonHeld;
const bool addNextAccent = currentEntityIdUnderCursor.IsValid() && canSelect && !invalidMouseButtonHeld && !boxSelect;
if (removePreviousAccent)
{
if (hoveredEntityId.IsValid())
{
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetEntityHighlighted, hoveredEntityId, false);
hoveredEntityId.SetInvalid();
}
setEntityAccentedFn(hoveredEntityIdUnderCursor, false);
hoveredEntityIdUnderCursor.SetInvalid();
}
if (!invalidMouseButtonHeld && !usingBoxSelect && (!hasSelectedEntities || ctrlHeld))
if (addNextAccent)
{
if (entityIdUnderCursor.IsValid())
{
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetEntityHighlighted, entityIdUnderCursor, true);
hoveredEntityId = entityIdUnderCursor;
}
setEntityAccentedFn(currentEntityIdUnderCursor, true);
hoveredEntityIdUnderCursor = currentEntityIdUnderCursor;
}
}
static AZ::Vector3 PickTerrainPosition(const ViewportInteraction::MouseInteraction& mouseInteraction)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
const int viewportId = mouseInteraction.m_interactionId.m_viewportId;
// get unsnapped terrain position (world space)
AZ::Vector3 worldSurfacePosition;
ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult(
worldSurfacePosition, viewportId, &ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::PickTerrain,
mouseInteraction.m_mousePick.m_screenCoordinates);
// convert to local space - snap if enabled
const GridSnapParameters gridSnapParams = GridSnapSettings(viewportId);
const AZ::Vector3 finalSurfacePosition = gridSnapParams.m_gridSnap
? CalculateSnappedTerrainPosition(worldSurfacePosition, AZ::Transform::CreateIdentity(), viewportId, gridSnapParams.m_gridSize)
: worldSurfacePosition;
return finalSurfacePosition;
}
// is the passed entity id contained with in the entity id list
template<typename EntityIdContainer>
static bool IsEntitySelectedInternal(AZ::EntityId entityId, const EntityIdContainer& selectedEntityIds)
@@ -982,7 +960,7 @@ namespace AzToolsFramework
{
if (auto entityIndex = entityDataCache.GetVisibleEntityIndexFromId(entityId))
{
if (entityDataCache.IsVisibleEntitySelectableInViewport(*entityIndex))
if (entityDataCache.IsVisibleEntityIndividuallySelectableInViewport(*entityIndex))
{
return *entityIndex;
}
@@ -1013,6 +991,15 @@ namespace AzToolsFramework
ToolsApplicationNotificationBus::Broadcast(&ToolsApplicationNotificationBus::Events::InvalidatePropertyDisplay, Refresh_Values);
}
// leaves focus mode by focusing on the parent of the current perfab in the entity outliner
static void LeaveFocusMode()
{
if (auto prefabFocusPublicInterface = AZ::Interface<Prefab::PrefabFocusPublicInterface>::Get())
{
prefabFocusPublicInterface->FocusOnParentOfFocusedPrefab(GetEntityContextId());
}
}
EditorTransformComponentSelection::EditorTransformComponentSelection(const EditorVisibleEntityDataCache* entityDataCache)
: m_entityDataCache(entityDataCache)
{
@@ -1177,8 +1164,10 @@ namespace AzToolsFramework
continue;
}
const AZ::Aabb bound = CalculateEditorEntitySelectionBounds(entityId, viewportInfo);
debugDisplay.DrawSolidBox(bound.GetMin(), bound.GetMax());
if (const AZ::Aabb bound = CalculateEditorEntitySelectionBounds(entityId, viewportInfo); bound.IsValid())
{
debugDisplay.DrawSolidBox(bound.GetMin(), bound.GetMax());
}
}
debugDisplay.DepthTestOn();
@@ -1277,7 +1266,7 @@ namespace AzToolsFramework
m_axisPreview.m_translation = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation();
m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(m_entityIdManipulators.m_manipulators->GetLocalTransform());
// [ref 1.]
// see comment [ref 1.] above
BeginRecordManipulatorCommand();
});
@@ -1312,7 +1301,7 @@ namespace AzToolsFramework
m_axisPreview.m_translation = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation();
m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(m_entityIdManipulators.m_manipulators->GetLocalTransform());
// [ref 1.]
// see comment [ref 1.] above
BeginRecordManipulatorCommand();
});
@@ -1345,7 +1334,7 @@ namespace AzToolsFramework
m_axisPreview.m_translation = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation();
m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(m_entityIdManipulators.m_manipulators->GetLocalTransform());
// [ref 1.]
// see comment [ref 1.] above
BeginRecordManipulatorCommand();
});
@@ -1419,7 +1408,7 @@ namespace AzToolsFramework
m_axisPreview.m_translation = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation();
m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(m_entityIdManipulators.m_manipulators->GetLocalTransform());
// [ref 1.]
// see comment [ref 1.] above
BeginRecordManipulatorCommand();
});
@@ -1801,7 +1790,7 @@ namespace AzToolsFramework
const AzFramework::CameraState cameraState = GetCameraState(viewportId);
const auto cursorEntityIdQuery = m_editorHelpers->FindEntityIdUnderCursor(cameraState, mouseInteraction);
m_cachedEntityIdUnderCursor = cursorEntityIdQuery.ContainerAncestorEntityId();
m_currentEntityIdUnderCursor = cursorEntityIdQuery.ContainerAncestorEntityId();
const auto selectClickEvent = ClickDetectorEventFromViewportInteraction(mouseInteraction);
m_cursorState.SetCurrentPosition(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates);
@@ -1822,7 +1811,7 @@ namespace AzToolsFramework
mouseInteraction.m_mouseInteraction,
AZ::Aabb::CreateFromMinMax(boxPosition - scaledSize, boxPosition + scaledSize)))
{
m_cachedEntityIdUnderCursor = entityId;
m_currentEntityIdUnderCursor = entityId;
}
}
}
@@ -1842,7 +1831,7 @@ namespace AzToolsFramework
return true;
}
const AZ::EntityId entityIdUnderCursor = m_cachedEntityIdUnderCursor;
const AZ::EntityId entityIdUnderCursor = m_currentEntityIdUnderCursor;
if (mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::DoubleClick &&
mouseInteraction.m_mouseInteraction.m_mouseButtons.Left())
@@ -1891,6 +1880,13 @@ namespace AzToolsFramework
if (!m_selectedEntityIds.empty())
{
// try snapping to a surface (mesh) if in Translation mode
if (Input::SnapSurface(mouseInteraction))
{
PerformSnapToSurface(mouseInteraction);
return false;
}
// group copying/alignment to specific entity - 'ditto' position/orientation for group
if (Input::GroupDitto(mouseInteraction) && PerformGroupDitto(entityIdUnderCursor))
{
@@ -1903,13 +1899,6 @@ namespace AzToolsFramework
return false;
}
// try snapping to the terrain (if in Translation mode) and entity wasn't picked
if (Input::SnapTerrain(mouseInteraction))
{
PerformSnapToTerrain(mouseInteraction);
return false;
}
// set manipulator pivot override translation or orientation (update manipulators)
if (Input::ManipulatorDitto(clickOutcome, mouseInteraction))
{
@@ -1997,25 +1986,28 @@ namespace AzToolsFramework
return false;
}
void EditorTransformComponentSelection::PerformSnapToTerrain(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
void EditorTransformComponentSelection::PerformSnapToSurface(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
{
for (AZ::EntityId entityId : m_selectedEntityIds)
for (const AZ::EntityId& entityId : m_selectedEntityIds)
{
ScopedUndoBatch::MarkEntityDirty(entityId);
}
if (m_mode == Mode::Translation)
{
const AZ::Vector3 finalSurfacePosition = PickTerrainPosition(mouseInteraction.m_mouseInteraction);
const AZ::Vector3 worldPosition = FindClosestPickIntersection(
mouseInteraction.m_mouseInteraction.m_interactionId.m_viewportId,
mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates, AzToolsFramework::EditorPickRayLength,
GetDefaultEntityPlacementDistance());
// handle modifier alternatives
if (Input::IndividualDitto(mouseInteraction))
{
CopyTranslationToSelectedEntitiesIndividual(finalSurfacePosition);
CopyTranslationToSelectedEntitiesIndividual(worldPosition);
}
else if (Input::GroupDitto(mouseInteraction))
{
CopyTranslationToSelectedEntitiesGroup(finalSurfacePosition);
CopyTranslationToSelectedEntitiesGroup(worldPosition);
}
}
else if (m_mode == Mode::Rotation)
@@ -3361,9 +3353,23 @@ namespace AzToolsFramework
m_cursorState.Update();
bool stickySelect = false;
ViewportInteraction::ViewportSettingsRequestBus::EventResult(
stickySelect, viewportInfo.m_viewportId, &ViewportInteraction::ViewportSettingsRequestBus::Events::StickySelectEnabled);
HandleAccentsContext handleAccentsContext;
handleAccentsContext.m_ctrlHeld = keyboardModifiers.Ctrl();
handleAccentsContext.m_hasSelectedEntities = !m_selectedEntityIds.empty();
handleAccentsContext.m_usingBoxSelect = m_boxSelect.Active();
handleAccentsContext.m_usingStickySelect = stickySelect;
HandleAccents(
!m_selectedEntityIds.empty(), m_cachedEntityIdUnderCursor, keyboardModifiers.Ctrl(), m_hoveredEntityId,
ViewportInteraction::BuildMouseButtons(QGuiApplication::mouseButtons()), m_boxSelect.Active());
m_currentEntityIdUnderCursor, m_hoveredEntityId, handleAccentsContext,
ViewportInteraction::BuildMouseButtons(QGuiApplication::mouseButtons()),
[](const AZ::EntityId entityId, bool highlighted)
{
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetEntityHighlighted, entityId, highlighted);
});
const ReferenceFrame referenceFrame = m_spaceCluster.m_spaceLock.value_or(ReferenceFrameFromModifiers(keyboardModifiers));
@@ -3507,7 +3513,7 @@ namespace AzToolsFramework
// get the editor cameras current orientation
const int viewportId = viewportInfo.m_viewportId;
const AzFramework::CameraState editorCameraState = GetCameraState(viewportId);
const AZ::Matrix3x3& editorCameraOrientation = AZ::Matrix3x3::CreateFromMatrix4x4(AzFramework::CameraTransform(editorCameraState));
const AZ::Matrix3x3& editorCameraOrientation = AZ::Matrix3x3::CreateFromMatrix3x4(AzFramework::CameraTransform(editorCameraState));
// create a gizmo camera transform about the origin matching the orientation of the editor camera
// (10 units back in the y axis to produce an orbit effect)
@@ -3604,6 +3610,17 @@ namespace AzToolsFramework
m_selectedEntityIds.clear();
m_selectedEntityIds.reserve(selectedEntityIds.size());
AZStd::copy(selectedEntityIds.begin(), selectedEntityIds.end(), AZStd::inserter(m_selectedEntityIds, m_selectedEntityIds.end()));
// Do not create manipulators for the container entity of the focused prefab.
if (auto prefabFocusPublicInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabFocusPublicInterface>::Get())
{
AzFramework::EntityContextId editorEntityContextId = GetEntityContextId();
if (AZ::EntityId focusRoot = prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId);
focusRoot.IsValid())
{
m_selectedEntityIds.erase(focusRoot);
}
}
}
void EditorTransformComponentSelection::OnTransformChanged(
@@ -3694,7 +3711,8 @@ namespace AzToolsFramework
case ViewportEditorMode::Focus:
{
ViewportUi::ViewportUiRequestBus::Event(
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder, "Focus Mode");
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder, "Focus Mode",
LeaveFocusMode);
}
break;
case ViewportEditorMode::Default:
@@ -3723,7 +3741,8 @@ namespace AzToolsFramework
if (editorModeState.IsModeActive(ViewportEditorMode::Focus))
{
ViewportUi::ViewportUiRequestBus::Event(
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder, "Focus Mode");
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder, "Focus Mode",
LeaveFocusMode);
}
}
break;
@@ -308,7 +308,7 @@ namespace AzToolsFramework
bool PerformGroupDitto(AZ::EntityId entityId);
bool PerformIndividualDitto(AZ::EntityId entityId);
void PerformManipulatorDitto(AZ::EntityId entityId);
void PerformSnapToTerrain(const ViewportInteraction::MouseInteractionEvent& mouseInteraction);
void PerformSnapToSurface(const ViewportInteraction::MouseInteractionEvent& mouseInteraction);
//! Responsible for keeping the space cluster in sync with the current reference frame.
void UpdateSpaceCluster(ReferenceFrame referenceFrame);
@@ -317,7 +317,7 @@ namespace AzToolsFramework
void SetAllViewportUiVisible(bool visible);
AZ::EntityId m_hoveredEntityId; //!< What EntityId is the mouse currently hovering over (if any).
AZ::EntityId m_cachedEntityIdUnderCursor; //!< Store the EntityId on each mouse move for use in Display.
AZ::EntityId m_currentEntityIdUnderCursor; //!< Store the EntityId on each mouse move for use in Display.
AZ::EntityId m_editorCameraComponentEntityId; //!< The EditorCameraComponent EntityId if it is set.
EntityIdSet m_selectedEntityIds; //!< Represents the current entities in the selection.
@@ -357,6 +357,23 @@ namespace AzToolsFramework
bool m_viewportUiVisible = true; //!< Used to hide/show the viewport ui elements.
};
//! Bundles viewport state that impacts how accents are added/removed in HandleAccents.
struct HandleAccentsContext
{
bool m_hasSelectedEntities;
bool m_ctrlHeld;
bool m_usingBoxSelect;
bool m_usingStickySelect;
};
//! Updates whether accents (icon highlights) are added/removed for a given entity based on the cursor position.
void HandleAccents(
AZ::EntityId currentEntityIdUnderCursor,
AZ::EntityId& hoveredEntityIdUnderCursor,
const HandleAccentsContext& handleAccentsContext,
ViewportInteraction::MouseButtons mouseButtons,
const AZStd::function<void(AZ::EntityId, bool)>& setEntityAccentedFn);
//! The ETCS (EntityTransformComponentSelection) namespace contains functions and data used exclusively by
//! the EditorTransformComponentSelection type. Functions in this namespace are exposed to facilitate testing
//! and should not be used outside of EditorTransformComponentSelection or EditorTransformComponentSelectionTests.
@@ -293,12 +293,10 @@ namespace AzToolsFramework
return m_impl->m_visibleEntityDatas[index].m_iconHidden;
}
bool EditorVisibleEntityDataCache::IsVisibleEntitySelectableInViewport(size_t index) const
bool EditorVisibleEntityDataCache::IsVisibleEntityIndividuallySelectableInViewport(const size_t index) const
{
return m_impl->m_visibleEntityDatas[index].m_visible
&& !m_impl->m_visibleEntityDatas[index].m_locked
&& m_impl->m_visibleEntityDatas[index].m_inFocus
&& !m_impl->m_visibleEntityDatas[index].m_descendantOfClosedContainer;
return m_impl->m_visibleEntityDatas[index].m_visible && !m_impl->m_visibleEntityDatas[index].m_locked &&
m_impl->m_visibleEntityDatas[index].m_inFocus && !m_impl->m_visibleEntityDatas[index].m_descendantOfClosedContainer;
}
AZStd::optional<size_t> EditorVisibleEntityDataCache::GetVisibleEntityIndexFromId(const AZ::EntityId entityId) const
@@ -55,7 +55,10 @@ namespace AzToolsFramework
bool IsVisibleEntityVisible(size_t index) const;
bool IsVisibleEntitySelected(size_t index) const;
bool IsVisibleEntityIconHidden(size_t index) const;
bool IsVisibleEntitySelectableInViewport(size_t index) const;
//! Returns true if the entity is individually selectable (none of its ancestors are a closed container entity).
//! @note It may still be desirable to be able to 'click' an entity that is a descendant of a closed container
//! to select the container itself, not the individual entity.
bool IsVisibleEntityIndividuallySelectableInViewport(size_t index) const;
AZStd::optional<size_t> GetVisibleEntityIndexFromId(AZ::EntityId entityId) const;
@@ -62,9 +62,6 @@ namespace AzToolsFramework::ViewportUi::Internal
return;
}
// set hover to true by default
action->setProperty("IconHasHoverEffect", true);
// add the action
addAction(action);
@@ -20,7 +20,9 @@
namespace AzToolsFramework::ViewportUi::Internal
{
const static int HighlightBorderSize = 5;
const static char* HighlightBorderColor = "#4A90E2";
const static char* const HighlightBorderColor = "#4A90E2";
const static int HighlightBorderBackButtonIconSize = 20;
const static char* const HighlightBorderBackButtonIconFile = "X_axis.svg";
static void UnparentWidgets(ViewportUiElementIdInfoLookup& viewportUiElementIdInfoLookup)
{
@@ -62,6 +64,7 @@ namespace AzToolsFramework::ViewportUi::Internal
, m_fullScreenLayout(&m_uiOverlay)
, m_uiOverlayLayout()
, m_viewportBorderText(&m_uiOverlay)
, m_viewportBorderBackButton(&m_uiOverlay)
{
}
@@ -254,7 +257,7 @@ namespace AzToolsFramework::ViewportUi::Internal
auto viewportUiMapElement = m_viewportUiElements.find(elementId);
if (viewportUiMapElement != m_viewportUiElements.end())
{
viewportUiMapElement->second.m_widget->setVisible(false);
viewportUiMapElement->second.m_widget->hide();
viewportUiMapElement->second.m_widget->setParent(nullptr);
m_viewportUiElements.erase(viewportUiMapElement);
}
@@ -269,7 +272,7 @@ namespace AzToolsFramework::ViewportUi::Internal
{
if (ViewportUiElementInfo element = GetViewportUiElementInfo(elementId); element.m_widget)
{
element.m_widget->setVisible(true);
element.m_widget->show();
}
}
@@ -277,7 +280,7 @@ namespace AzToolsFramework::ViewportUi::Internal
{
if (ViewportUiElementInfo element = GetViewportUiElementInfo(elementId); element.m_widget)
{
element.m_widget->setVisible(false);
element.m_widget->hide();
}
}
@@ -291,27 +294,34 @@ namespace AzToolsFramework::ViewportUi::Internal
return false;
}
void ViewportUiDisplay::CreateViewportBorder(const AZStd::string& borderTitle)
void ViewportUiDisplay::CreateViewportBorder(
const AZStd::string& borderTitle, AZStd::optional<ViewportUiBackButtonCallback> backButtonCallback)
{
const AZStd::string styleSheet = AZStd::string::format(
"border: %dpx solid %s; border-top: %dpx solid %s;", HighlightBorderSize, HighlightBorderColor, ViewportUiTopBorderSize,
HighlightBorderColor);
m_uiOverlay.setStyleSheet(styleSheet.c_str());
m_uiOverlay.setStyleSheet(QString("border: %1px solid %2; border-top: %3px solid %4;")
.arg(
QString::number(HighlightBorderSize), HighlightBorderColor,
QString::number(ViewportUiTopBorderSize), HighlightBorderColor));
m_uiOverlayLayout.setContentsMargins(
HighlightBorderSize + ViewportUiOverlayMargin, ViewportUiTopBorderSize + ViewportUiOverlayMargin,
HighlightBorderSize + ViewportUiOverlayMargin, HighlightBorderSize + ViewportUiOverlayMargin);
m_viewportBorderText.setVisible(true);
m_viewportBorderText.show();
m_viewportBorderText.setText(borderTitle.c_str());
UpdateUiOverlayGeometry();
// only display the back button if a callback was provided
m_viewportBorderBackButtonCallback = backButtonCallback;
m_viewportBorderBackButton.setVisible(m_viewportBorderBackButtonCallback.has_value());
}
void ViewportUiDisplay::RemoveViewportBorder()
{
m_viewportBorderText.setVisible(false);
m_viewportBorderText.hide();
m_uiOverlay.setStyleSheet("border: none;");
m_uiOverlayLayout.setContentsMargins(
ViewportUiOverlayMargin, ViewportUiOverlayMargin + ViewportUiOverlayTopMarginPadding, ViewportUiOverlayMargin,
ViewportUiOverlayMargin);
m_viewportBorderBackButtonCallback.reset();
m_viewportBorderBackButton.hide();
}
void ViewportUiDisplay::PositionViewportUiElementFromWorldSpace(ViewportUiElementId elementId, const AZ::Vector3& pos)
@@ -350,23 +360,46 @@ namespace AzToolsFramework::ViewportUi::Internal
{
m_uiMainWindow.setObjectName(QString("ViewportUiWindow"));
ConfigureWindowForViewportUi(&m_uiMainWindow);
m_uiMainWindow.setVisible(false);
m_uiMainWindow.hide();
m_uiOverlay.setObjectName(QString("ViewportUiOverlay"));
m_uiMainWindow.setCentralWidget(&m_uiOverlay);
m_uiOverlay.setVisible(false);
m_uiOverlay.hide();
// remove any spacing and margins from the UI Overlay Layout
m_fullScreenLayout.setSpacing(0);
m_fullScreenLayout.setContentsMargins(0, 0, 0, 0);
m_fullScreenLayout.addLayout(&m_uiOverlayLayout, 0, 0, 1, 1);
// format the label which will appear on top of the highlight border
AZStd::string styleSheet = AZStd::string::format("background-color: %s; border: none;", HighlightBorderColor);
m_viewportBorderText.setStyleSheet(styleSheet.c_str());
// style the label which will appear on top of the highlight border
m_viewportBorderText.setStyleSheet(QString("background-color: %1; border: none").arg(HighlightBorderColor));
m_viewportBorderText.setFixedHeight(ViewportUiTopBorderSize);
m_viewportBorderText.setVisible(false);
m_viewportBorderText.hide();
m_fullScreenLayout.addWidget(&m_viewportBorderText, 0, 0, Qt::AlignTop | Qt::AlignHCenter);
m_viewportBorderBackButton.setAutoRaise(true); // hover highlight
m_viewportBorderBackButton.hide();
QIcon backButtonIcon(QString(":/stylesheet/img/UI20/toolbar/%1").arg(HighlightBorderBackButtonIconFile));
m_viewportBorderBackButton.setIcon(backButtonIcon);
m_viewportBorderBackButton.setIconSize(QSize(HighlightBorderBackButtonIconSize, HighlightBorderBackButtonIconSize));
// setup the handler for the back button to call the user provided callback (if any)
QObject::connect(
&m_viewportBorderBackButton, &QToolButton::clicked,
[this]
{
if (m_viewportBorderBackButtonCallback.has_value())
{
// we need to swap out the existing back button callback because it will be reset in RemoveViewportBorder()
// so preserve the lifetime with this temporary callback until after the call to RemoveViewportBorder()
AZStd::optional<ViewportUiBackButtonCallback> backButtonCallback;
m_viewportBorderBackButtonCallback.swap(backButtonCallback);
RemoveViewportBorder();
(*backButtonCallback)();
}
});
m_fullScreenLayout.addWidget(&m_viewportBorderBackButton, 0, 0, Qt::AlignTop | Qt::AlignRight);
}
void ViewportUiDisplay::PrepareWidgetForViewportUi(QPointer<QWidget> widget)
@@ -414,16 +447,9 @@ namespace AzToolsFramework::ViewportUi::Internal
region += m_uiOverlay.childrenRegion();
// set viewport ui visibility depending on if elements are present
if (region.isEmpty() || !UiDisplayEnabled())
{
m_uiMainWindow.setVisible(false);
m_uiOverlay.setVisible(false);
}
else
{
m_uiMainWindow.setVisible(true);
m_uiOverlay.setVisible(true);
}
const bool visible = !region.isEmpty() && UiDisplayEnabled();
m_uiMainWindow.setVisible(visible);
m_uiOverlay.setVisible(visible);
m_uiMainWindow.setMask(region);
}
@@ -17,6 +17,7 @@
#include <QLabel>
#include <QMainWindow>
#include <QPointer>
#include <QToolButton>
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option")
#include <QGridLayout>
@@ -89,7 +90,7 @@ namespace AzToolsFramework::ViewportUi::Internal
AZStd::shared_ptr<QWidget> GetViewportUiElement(ViewportUiElementId elementId);
bool IsViewportUiElementVisible(ViewportUiElementId elementId);
void CreateViewportBorder(const AZStd::string& borderTitle);
void CreateViewportBorder(const AZStd::string& borderTitle, AZStd::optional<ViewportUiBackButtonCallback> backButtonCallback);
void RemoveViewportBorder();
private:
@@ -113,7 +114,10 @@ namespace AzToolsFramework::ViewportUi::Internal
QWidget m_uiOverlay; //!< The UI Overlay which displays Viewport UI Elements.
QGridLayout m_fullScreenLayout; //!< The layout which extends across the full screen.
ViewportUiDisplayLayout m_uiOverlayLayout; //!< The layout used for optionally anchoring Viewport UI Elements.
QLabel m_viewportBorderText; //!< The text used for the viewport border.
QLabel m_viewportBorderText; //!< The text used for the viewport highlight border.
QToolButton m_viewportBorderBackButton; //!< The button to return from the viewport highlight border (only displayed if callback provided).
//! The optional callback for when the viewport highlight border back button is pressed.
AZStd::optional<ViewportUiBackButtonCallback> m_viewportBorderBackButtonCallback;
QWidget* m_renderOverlay;
QPointer<QWidget> m_fullScreenWidget; //!< Reference to the widget attached to m_fullScreenLayout if any.
@@ -240,9 +240,10 @@ namespace AzToolsFramework::ViewportUi
}
}
void ViewportUiManager::CreateViewportBorder(const AZStd::string& borderTitle)
void ViewportUiManager::CreateViewportBorder(
const AZStd::string& borderTitle, AZStd::optional<ViewportUiBackButtonCallback> backButtonCallback)
{
m_viewportUi->CreateViewportBorder(borderTitle);
m_viewportUi->CreateViewportBorder(borderTitle, backButtonCallback);
}
void ViewportUiManager::RemoveViewportBorder()
@@ -50,7 +50,8 @@ namespace AzToolsFramework::ViewportUi
void RegisterTextFieldCallback(TextFieldId textFieldId, AZ::Event<AZStd::string>::Handler& handler) override;
void RemoveTextField(TextFieldId textFieldId) override;
void SetTextFieldVisible(TextFieldId textFieldId, bool visible) override;
void CreateViewportBorder(const AZStd::string& borderTitle) override;
void CreateViewportBorder(
const AZStd::string& borderTitle, AZStd::optional<ViewportUiBackButtonCallback> backButtonCallback) override;
void RemoveViewportBorder() override;
void PressButton(ClusterId clusterId, ButtonId buttonId) override;
void PressButton(SwitcherId switcherId, ButtonId buttonId) override;
@@ -22,6 +22,9 @@ namespace AzToolsFramework::ViewportUi
using SwitcherId = IdType<struct SwitcherIdType>;
using TextFieldId = IdType<struct TextFieldIdType>;
//! Callback function for viewport UI back button.
using ViewportUiBackButtonCallback = AZStd::function<void()>;
inline const ViewportUiElementId InvalidViewportUiElementId = ViewportUiElementId(0);
inline const ButtonId InvalidButtonId = ButtonId(0);
inline const ClusterId InvalidClusterId = ClusterId(0);
@@ -95,9 +98,9 @@ namespace AzToolsFramework::ViewportUi
virtual void RemoveTextField(TextFieldId textFieldId) = 0;
//! Sets the visibility of the text field.
virtual void SetTextFieldVisible(TextFieldId textFieldId, bool visible) = 0;
//! Create the highlight border for Component Mode.
virtual void CreateViewportBorder(const AZStd::string& borderTitle) = 0;
//! Remove the highlight border for Component Mode.
//! Create the highlight border with optional back button to exit the given editor mode.
virtual void CreateViewportBorder(const AZStd::string& borderTitle, AZStd::optional<ViewportUiBackButtonCallback> backButtonCallback) = 0;
//! Remove the highlight border.
virtual void RemoveViewportBorder() = 0;
//! Invoke a button press on a cluster.
virtual void PressButton(ClusterId clusterId, ButtonId buttonId) = 0;
@@ -22,8 +22,6 @@ namespace AzToolsFramework::ViewportUi::Internal
// Add am empty active button (is set in the call to SetActiveMode)
m_activeButton = new QToolButton();
// No hover effect for the main button as it's not clickable
m_activeButton->setProperty("IconHasHoverEffect", false);
m_activeButton->setCheckable(false);
m_activeButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
addWidget(m_activeButton);
@@ -56,9 +54,6 @@ namespace AzToolsFramework::ViewportUi::Internal
return;
}
// set hover to true by default
action->setProperty("IconHasHoverEffect", true);
// add the action
addAction(action);
@@ -47,6 +47,7 @@ set(FILES
API/EntityCompositionRequestBus.h
API/EntityCompositionNotificationBus.h
API/EditorViewportIconDisplayInterface.h
API/PythonLoader.h
API/ViewPaneOptions.h
API/ViewportEditorModeTrackerInterface.h
Application/Ticker.h
@@ -147,6 +148,8 @@ set(FILES
Entity/EditorEntitySortBus.h
Entity/EditorEntitySortComponent.cpp
Entity/EditorEntitySortComponent.h
Entity/EditorEntitySortComponentSerializer.cpp
Entity/EditorEntitySortComponentSerializer.h
Entity/EditorEntityTransformBus.h
Entity/PrefabEditorEntityOwnershipInterface.h
Entity/PrefabEditorEntityOwnershipService.h
@@ -156,6 +159,10 @@ set(FILES
Entity/SliceEditorEntityOwnershipServiceBus.h
Entity/EntityUtilityComponent.h
Entity/EntityUtilityComponent.cpp
Entity/ReadOnly/ReadOnlyEntityInterface.h
Entity/ReadOnly/ReadOnlyEntityBus.h
Entity/ReadOnly/ReadOnlyEntitySystemComponent.cpp
Entity/ReadOnly/ReadOnlyEntitySystemComponent.h
Fingerprinting/TypeFingerprinter.h
Fingerprinting/TypeFingerprinter.cpp
FocusMode/FocusModeInterface.h
@@ -768,8 +775,8 @@ set(FILES
PythonTerminal/ScriptTermDialog.cpp
PythonTerminal/ScriptTermDialog.h
PythonTerminal/ScriptTermDialog.ui
Input/QtEventToAzInputManager.h
Input/QtEventToAzInputManager.cpp
Input/QtEventToAzInputMapper.h
Input/QtEventToAzInputMapper.cpp
Script/LuaSymbolsReporterBus.h
Script/LuaSymbolsReporterSystemComponent.h
Script/LuaSymbolsReporterSystemComponent.cpp
@@ -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
*
*/
#include <AzToolsFramework/API/PythonLoader.h>
namespace AzToolsFramework::EmbeddedPython
{
PythonLoader::PythonLoader()
{
}
PythonLoader::~PythonLoader()
{
}
}
@@ -0,0 +1,34 @@
/*
* 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 <AzToolsFramework/API/PythonLoader.h>
#include <AzCore/Debug/Trace.h>
#include <dlfcn.h>
namespace AzToolsFramework::EmbeddedPython
{
PythonLoader::PythonLoader()
{
constexpr char libPythonName[] = "libpython3.7m.so.1.0";
m_embeddedLibPythonHandle = dlopen(libPythonName, RTLD_NOW | RTLD_GLOBAL);
if (m_embeddedLibPythonHandle == nullptr)
{
[[maybe_unused]] const char* err = dlerror();
AZ_Error("PythonLoader", false, "Failed to load %s with error: %s\n", libPythonName, err ? err : "Unknown Error");
}
}
PythonLoader::~PythonLoader()
{
if (m_embeddedLibPythonHandle)
{
dlclose(m_embeddedLibPythonHandle);
}
}
} // namespace AzToolsFramework::EmbeddedPython
@@ -7,4 +7,5 @@
#
set(FILES
AzToolsFramework/API/PythonLoader_Linux.cpp
)
@@ -7,4 +7,5 @@
#
set(FILES
../Common/Default/AzToolsFramework/API/PythonLoader_Default.cpp
)
@@ -7,4 +7,5 @@
#
set(FILES
../Common/Default/AzToolsFramework/API/PythonLoader_Default.cpp
)
@@ -8,6 +8,8 @@
#include <Tests/BoundsTestComponent.h>
#include <AzCore/Math/Obb.h>
#include <AzCore/Math/IntersectSegment.h>
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
namespace UnitTest
@@ -40,6 +42,9 @@ namespace UnitTest
{
AzFramework::BoundsRequestBus::Handler::BusConnect(GetEntityId());
AzToolsFramework::EditorComponentSelectionRequestsBus::Handler::BusConnect(GetEntityId());
// default local bounds to unit cube
m_localBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-0.5f), AZ::Vector3(0.5f));
}
void BoundsTestComponent::Deactivate()
@@ -57,7 +62,53 @@ namespace UnitTest
AZ::Aabb BoundsTestComponent::GetLocalBounds()
{
return AZ::Aabb::CreateFromMinMax(AZ::Vector3(-0.5f), AZ::Vector3(0.5f));
return m_localBounds;
}
void RenderGeometryIntersectionTestComponent::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<RenderGeometryIntersectionTestComponent, BoundsTestComponent>()->Version(1);
}
}
void RenderGeometryIntersectionTestComponent::Activate()
{
BoundsTestComponent::Activate();
const AZ::EntityId entityId = GetEntityId();
AzFramework::EntityContextId contextId = AzFramework::EntityContextId::CreateNull();
AzFramework::EntityIdContextQueryBus::EventResult(contextId, entityId, &AzFramework::EntityIdContextQueries::GetOwningContextId);
AzFramework::RenderGeometry::IntersectionRequestBus::Handler::BusConnect({entityId, contextId});
}
void RenderGeometryIntersectionTestComponent::Deactivate()
{
AzFramework::RenderGeometry::IntersectionRequestBus::Handler::BusDisconnect();
BoundsTestComponent::Deactivate();
}
AzFramework::RenderGeometry::RayResult RenderGeometryIntersectionTestComponent::RenderGeometryIntersect(
const AzFramework::RenderGeometry::RayRequest& ray)
{
AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity();
AZ::TransformBus::EventResult(worldFromLocal, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM);
AzFramework::RenderGeometry::RayResult rayResult;
float t = 0.0f;
const AZ::Obb obb = GetLocalBounds().GetTransformedObb(worldFromLocal);
const AZ::Vector3 rayDirection = ray.m_endWorldPosition - ray.m_startWorldPosition;
if (AZ::Intersect::IntersectRayObb(ray.m_startWorldPosition, rayDirection, obb, t))
{
rayResult.m_worldPosition = ray.m_startWorldPosition + rayDirection * t;
rayResult.m_entityAndComponent = AZ::EntityComponentIdPair(GetEntityId(), GetId());
rayResult.m_distance = t;
rayResult.m_uv = AZ::Vector2::CreateZero();
rayResult.m_worldNormal = AZ::Vector3::CreateZero();
}
return rayResult;
}
} // namespace UnitTest
@@ -8,6 +8,7 @@
#pragma once
#include <AzFramework/Render/GeometryIntersectionBus.h>
#include <AzFramework/Visibility/BoundsBus.h>
#include <AzToolsFramework/API/ComponentEntitySelectionBus.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
@@ -41,5 +42,24 @@ namespace UnitTest
// BoundsRequestBus overrides ...
AZ::Aabb GetWorldBounds() override;
AZ::Aabb GetLocalBounds() override;
AZ::Aabb m_localBounds; //!< Local bounds that can be modified for certain tests (defaults to unit cube).
};
class RenderGeometryIntersectionTestComponent
: public BoundsTestComponent
, public AzFramework::RenderGeometry::IntersectionRequestBus::Handler
{
public:
AZ_EDITOR_COMPONENT(RenderGeometryIntersectionTestComponent, "{6F46B5BF-60DF-4BDD-9BA7-9658E85B99C2}", BoundsTestComponent);
static void Reflect(AZ::ReflectContext* context);
// AZ::Component overrides ...
void Activate() override;
void Deactivate() override;
// IntersectionRequestBus overrides ...
AzFramework::RenderGeometry::RayResult RenderGeometryIntersect(const AzFramework::RenderGeometry::RayRequest& ray) override;
};
} // namespace UnitTest
@@ -1116,7 +1116,6 @@ namespace UnitTest
SerializeContext* GetSerializeContext() override { return m_serializeContext.get(); }
BehaviorContext* GetBehaviorContext() override { return nullptr; }
JsonRegistrationContext* GetJsonRegistrationContext() override { return nullptr; }
const char* GetAppRoot() const override { return nullptr; }
const char* GetEngineRoot() const override { return nullptr; }
const char* GetExecutableFolder() const override { return nullptr; }
void EnumerateEntities(const EntityCallback& /*callback*/) override {}
@@ -38,7 +38,7 @@
#include <AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.h>
#include <AzToolsFramework/ViewportUi/ViewportUiManager.h>
#include<Tests/BoundsTestComponent.h>
#include <Tests/BoundsTestComponent.h>
namespace AZ
{
@@ -493,12 +493,8 @@ namespace UnitTest
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
AzToolsFramework::EntityIdList selectedEntities;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
selectedEntities, &AzToolsFramework::ToolsApplicationRequestBus::Events::GetSelectedEntities);
AzToolsFramework::EntityIdList expectedSelectedEntities = { entity4, entity5, entity6 };
const AzToolsFramework::EntityIdList selectedEntities = SelectedEntities();
const AzToolsFramework::EntityIdList expectedSelectedEntities = { entity4, entity5, entity6 };
EXPECT_THAT(selectedEntities, UnorderedElementsAreArray(expectedSelectedEntities));
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
@@ -527,12 +523,8 @@ namespace UnitTest
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Then
AzToolsFramework::EntityIdList selectedEntities;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
selectedEntities, &AzToolsFramework::ToolsApplicationRequestBus::Events::GetSelectedEntities);
AzToolsFramework::EntityIdList expectedSelectedEntities = { m_entityId1, entity2, entity3, entity4 };
const AzToolsFramework::EntityIdList selectedEntities = SelectedEntities();
const AzToolsFramework::EntityIdList expectedSelectedEntities = { m_entityId1, entity2, entity3, entity4 };
EXPECT_THAT(selectedEntities, UnorderedElementsAreArray(expectedSelectedEntities));
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
@@ -946,6 +938,42 @@ namespace UnitTest
EXPECT_THAT(selectedEntitiesAfter, UnorderedElementsAre(m_entityId1));
}
TEST_F(
EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, BoundsBetweenCameraAndNearClipPlaneDoesNotIntersectMouseRay)
{
// move camera to 10 units along the y-axis
AzFramework::SetCameraTransform(m_cameraState, AZ::Transform::CreateTranslation(AZ::Vector3::CreateAxisY(10.0f)));
// send a very narrow bounds for entity1
AZ::Entity* entity1 = AzToolsFramework::GetEntityById(m_entityId1);
auto* boundTestComponent = entity1->FindComponent<BoundsTestComponent>();
boundTestComponent->m_localBounds =
AZ::Aabb::CreateFromMinMax(AZ::Vector3(-0.5f, -0.0025f, -0.5f), AZ::Vector3(0.5f, 0.0025f, 0.5f));
// move entity1 in front of the camera between it and the near clip plane
AZ::TransformBus::Event(
m_entityId1, &AZ::TransformBus::Events::SetWorldTM, AZ::Transform::CreateTranslation(AZ::Vector3::CreateAxisY(10.05f)));
// move entity2 behind entity1
AZ::TransformBus::Event(
m_entityId2, &AZ::TransformBus::Events::SetWorldTM, AZ::Transform::CreateTranslation(AZ::Vector3::CreateAxisY(15.0f)));
const auto entity2ScreenPosition = AzFramework::WorldToScreen(AzToolsFramework::GetWorldTranslation(m_entityId2), m_cameraState);
// click the entity in the viewport
m_actionDispatcher->SetStickySelect(true)
->CameraState(m_cameraState)
->MousePosition(entity2ScreenPosition)
->CameraState(m_cameraState)
->MouseLButtonDown()
->MouseLButtonUp();
// ensure entity1 is not selected as it is before the near clip plane
using ::testing::UnorderedElementsAreArray;
const AzToolsFramework::EntityIdList selectedEntities = SelectedEntities();
const AzToolsFramework::EntityIdList expectedSelectedEntities = { m_entityId2 };
EXPECT_THAT(selectedEntities, UnorderedElementsAreArray(expectedSelectedEntities));
}
class EditorTransformComponentSelectionViewportPickingManipulatorTestFixtureParam
: public EditorTransformComponentSelectionViewportPickingManipulatorTestFixture
, public ::testing::WithParamInterface<bool>
@@ -1635,7 +1663,7 @@ namespace UnitTest
const AZ::Transform finalEntityTransform = AzToolsFramework::GetWorldTransform(m_entityId1);
// ensure final world positions match
EXPECT_TRUE(finalEntityTransform.IsClose(finalTransformWorld, 0.01f));
EXPECT_THAT(finalEntityTransform, IsCloseTolerance(finalTransformWorld, 0.01f));
}
TEST_F(EditorTransformComponentSelectionManipulatorTestFixture, TranslatingEntityWithLinearManipulatorNotifiesOnEntityTransformChanged)
@@ -2753,4 +2781,368 @@ namespace UnitTest
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
TEST(HandleAccents, CurrentValidEntityIdBecomesHoveredWithNoSelectionAndUnstickySelect)
{
namespace azvi = AzToolsFramework::ViewportInteraction;
const AZ::EntityId currentEntityId = AZ::EntityId(12345);
AZ::EntityId hoveredEntityEntityId;
AzToolsFramework::HandleAccentsContext handleAccentsContext;
handleAccentsContext.m_ctrlHeld = false;
handleAccentsContext.m_hasSelectedEntities = false;
handleAccentsContext.m_usingBoxSelect = false;
handleAccentsContext.m_usingStickySelect = false;
bool currentEntityIdAccentAdded = false;
AzToolsFramework::HandleAccents(
currentEntityId, hoveredEntityEntityId, handleAccentsContext, azvi::MouseButtonsFromButton(azvi::MouseButton::None),
[&currentEntityIdAccentAdded, currentEntityId](const AZ::EntityId entityId, const bool accent)
{
if (entityId == currentEntityId && accent)
{
currentEntityIdAccentAdded = true;
}
});
using ::testing::Eq;
using ::testing::IsTrue;
EXPECT_THAT(currentEntityId, Eq(hoveredEntityEntityId));
EXPECT_THAT(currentEntityIdAccentAdded, IsTrue());
}
TEST(HandleAccents, CurrentValidEntityIdBecomesHoveredWithSelectionAndUnstickySelect)
{
namespace azvi = AzToolsFramework::ViewportInteraction;
const AZ::EntityId currentEntityId = AZ::EntityId(12345);
AZ::EntityId hoveredEntityEntityId;
AzToolsFramework::HandleAccentsContext handleAccentsContext;
handleAccentsContext.m_ctrlHeld = false;
handleAccentsContext.m_hasSelectedEntities = true;
handleAccentsContext.m_usingBoxSelect = false;
handleAccentsContext.m_usingStickySelect = false;
bool currentEntityIdAccentAdded = false;
AzToolsFramework::HandleAccents(
currentEntityId, hoveredEntityEntityId, handleAccentsContext, azvi::MouseButtonsFromButton(azvi::MouseButton::None),
[&currentEntityIdAccentAdded, currentEntityId](const AZ::EntityId entityId, const bool accent)
{
if (entityId == currentEntityId && accent)
{
currentEntityIdAccentAdded = true;
}
});
using ::testing::Eq;
using ::testing::IsTrue;
EXPECT_THAT(currentEntityId, Eq(hoveredEntityEntityId));
EXPECT_THAT(currentEntityIdAccentAdded, IsTrue());
}
TEST(HandleAccents, CurrentValidEntityIdDoesNotBecomeHoveredWithSelectionUnstickySelectAndInvalidButton)
{
namespace azvi = AzToolsFramework::ViewportInteraction;
const AZ::EntityId currentEntityId = AZ::EntityId(12345);
AZ::EntityId hoveredEntityEntityId = AZ::EntityId(54321);
AzToolsFramework::HandleAccentsContext handleAccentsContext;
handleAccentsContext.m_ctrlHeld = false;
handleAccentsContext.m_hasSelectedEntities = false;
handleAccentsContext.m_usingBoxSelect = false;
handleAccentsContext.m_usingStickySelect = false;
bool hoveredEntityIdAccentRemoved = false;
AzToolsFramework::HandleAccents(
currentEntityId, hoveredEntityEntityId, handleAccentsContext, azvi::MouseButtonsFromButton(azvi::MouseButton::Middle),
[&hoveredEntityIdAccentRemoved, hoveredEntityEntityId](const AZ::EntityId entityId, const bool accent)
{
if (entityId == hoveredEntityEntityId && !accent)
{
hoveredEntityIdAccentRemoved = true;
}
});
using ::testing::Eq;
using ::testing::IsFalse;
using ::testing::IsTrue;
EXPECT_THAT(hoveredEntityEntityId.IsValid(), IsFalse());
EXPECT_THAT(hoveredEntityIdAccentRemoved, IsTrue());
}
TEST(HandleAccents, CurrentValidEntityIdDoesNotBecomeHoveredWithSelectionUnstickySelectAndDoingBoxSelect)
{
namespace azvi = AzToolsFramework::ViewportInteraction;
const AZ::EntityId currentEntityId = AZ::EntityId(12345);
AZ::EntityId hoveredEntityEntityId = AZ::EntityId(54321);
AzToolsFramework::HandleAccentsContext handleAccentsContext;
handleAccentsContext.m_ctrlHeld = false;
handleAccentsContext.m_hasSelectedEntities = false;
handleAccentsContext.m_usingBoxSelect = true;
handleAccentsContext.m_usingStickySelect = false;
bool hoveredEntityIdAccentRemoved = false;
AzToolsFramework::HandleAccents(
currentEntityId, hoveredEntityEntityId, handleAccentsContext, azvi::MouseButtonsFromButton(azvi::MouseButton::None),
[&hoveredEntityIdAccentRemoved, hoveredEntityEntityId](const AZ::EntityId entityId, const bool accent)
{
if (entityId == hoveredEntityEntityId && !accent)
{
hoveredEntityIdAccentRemoved = true;
}
});
using ::testing::Eq;
using ::testing::IsFalse;
using ::testing::IsTrue;
EXPECT_THAT(hoveredEntityEntityId.IsValid(), IsFalse());
EXPECT_THAT(hoveredEntityIdAccentRemoved, IsTrue());
}
// mimics the mouse moving off of hovered entity onto a new entity with sticky select enabled
TEST(HandleAccents, CurrentValidEntityIdDoesNotBecomeHoveredWithSelectionAndStickySelect)
{
namespace azvi = AzToolsFramework::ViewportInteraction;
const AZ::EntityId currentEntityId = AZ::EntityId(12345);
AZ::EntityId hoveredEntityEntityId = AZ::EntityId(54321);
AzToolsFramework::HandleAccentsContext handleAccentsContext;
handleAccentsContext.m_ctrlHeld = false;
handleAccentsContext.m_hasSelectedEntities = true;
handleAccentsContext.m_usingBoxSelect = false;
handleAccentsContext.m_usingStickySelect = true;
bool hoveredEntityIdAccentRemoved = false;
AzToolsFramework::HandleAccents(
currentEntityId, hoveredEntityEntityId, handleAccentsContext, azvi::MouseButtonsFromButton(azvi::MouseButton::None),
[&hoveredEntityIdAccentRemoved, hoveredEntityEntityId](const AZ::EntityId entityId, const bool accent)
{
if (entityId == hoveredEntityEntityId && !accent)
{
hoveredEntityIdAccentRemoved = true;
}
});
using ::testing::Eq;
using ::testing::IsFalse;
using ::testing::IsTrue;
EXPECT_THAT(hoveredEntityIdAccentRemoved, IsTrue());
EXPECT_THAT(hoveredEntityEntityId.IsValid(), IsFalse());
}
TEST(HandleAccents, CurrentValidEntityIdDoesBecomeHoveredWithSelectionAndStickySelectAndCtrl)
{
namespace azvi = AzToolsFramework::ViewportInteraction;
const AZ::EntityId currentEntityId = AZ::EntityId(12345);
AZ::EntityId hoveredEntityEntityId = AZ::EntityId(54321);
AzToolsFramework::HandleAccentsContext handleAccentsContext;
handleAccentsContext.m_ctrlHeld = true;
handleAccentsContext.m_hasSelectedEntities = true;
handleAccentsContext.m_usingBoxSelect = false;
handleAccentsContext.m_usingStickySelect = true;
bool currentEntityIdAccentAdded = false;
bool hoveredEntityIdAccentRemoved = false;
AzToolsFramework::HandleAccents(
currentEntityId, hoveredEntityEntityId, handleAccentsContext, azvi::MouseButtonsFromButton(azvi::MouseButton::None),
[&hoveredEntityIdAccentRemoved, &currentEntityIdAccentAdded, currentEntityId,
hoveredEntityEntityId](const AZ::EntityId entityId, const bool accent)
{
if (entityId == currentEntityId && accent)
{
currentEntityIdAccentAdded = true;
}
if (entityId == hoveredEntityEntityId && !accent)
{
hoveredEntityIdAccentRemoved = true;
}
});
using ::testing::Eq;
using ::testing::IsFalse;
using ::testing::IsTrue;
EXPECT_THAT(currentEntityIdAccentAdded, IsTrue());
EXPECT_THAT(hoveredEntityIdAccentRemoved, IsTrue());
EXPECT_THAT(hoveredEntityEntityId, Eq(AZ::EntityId(12345)));
}
class EditorTransformComponentSelectionRenderGeometryIntersectionFixture : public ToolsApplicationFixture
{
public:
void SetUpEditorFixtureImpl() override
{
auto* app = GetApplication();
// register a simple component implementing BoundsRequestBus and EditorComponentSelectionRequestsBus
app->RegisterComponentDescriptor(BoundsTestComponent::CreateDescriptor());
// register a component implementing RenderGeometry::IntersectionRequestBus
app->RegisterComponentDescriptor(RenderGeometryIntersectionTestComponent::CreateDescriptor());
auto createEntityWithGeometryIntersectionFn = [](const char* entityName)
{
AZ::Entity* entity = nullptr;
AZ::EntityId entityId = CreateDefaultEditorEntity(entityName, &entity);
entity->Deactivate();
entity->CreateComponent<RenderGeometryIntersectionTestComponent>();
entity->Activate();
return entityId;
};
m_entityIdGround = createEntityWithGeometryIntersectionFn("Entity1");
m_entityIdBox = createEntityWithGeometryIntersectionFn("Entity2");
if (auto* ground = AzToolsFramework::GetEntityById(m_entityIdGround)->FindComponent<RenderGeometryIntersectionTestComponent>())
{
ground->m_localBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-10.0f, -10.0f, -0.5f), AZ::Vector3(10.0f, 10.0f, 0.5f));
}
AzToolsFramework::SetWorldTransform(m_entityIdGround, AZ::Transform::CreateTranslation(AZ::Vector3(0.0f, 10.0f, 5.0f)));
if (auto* box = AzToolsFramework::GetEntityById(m_entityIdBox)->FindComponent<RenderGeometryIntersectionTestComponent>())
{
box->m_localBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-0.5f), AZ::Vector3(0.5f));
}
AzToolsFramework::SetWorldTransform(
m_entityIdBox,
AZ::Transform::CreateFromMatrix3x3AndTranslation(
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(45.0f)), AZ::Vector3(0.0f, 10.0f, 7.0f)));
}
AZ::EntityId m_entityIdGround;
AZ::EntityId m_entityIdBox;
};
using EditorTransformComponentSelectionRenderGeometryIntersectionManipulatorFixture =
IndirectCallManipulatorViewportInteractionFixtureMixin<EditorTransformComponentSelectionRenderGeometryIntersectionFixture>;
TEST_F(
EditorTransformComponentSelectionRenderGeometryIntersectionManipulatorFixture, BoxCanBePlacedOnMeshSurfaceUsingSurfaceManipulator)
{
// camera (go to position format) - 0.00, 20.00, 12.00, -35.00, -180.00
m_cameraState.m_viewportSize = AZ::Vector2(1280.0f, 720.0f);
AzFramework::SetCameraTransform(
m_cameraState,
AZ::Transform::CreateFromMatrix3x3AndTranslation(
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(-180.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-35.0f)),
AZ::Vector3(0.0f, 20.0f, 12.0f)));
// the initial starting position of the entity
const auto initialTransformWorld = AzToolsFramework::GetWorldTransform(m_entityIdBox);
// where the entity should end up (snapped to the larger ground surface)
const auto finalTransformWorld =
AZ::Transform::CreateFromQuaternionAndTranslation(initialTransformWorld.GetRotation(), AZ::Vector3(2.5f, 12.5f, 5.5f));
// calculate the position in screen space of the initial position of the entity
const auto initialPositionScreen = AzFramework::WorldToScreen(initialTransformWorld.GetTranslation(), m_cameraState);
// calculate the position in screen space of the final position of the entity
const auto finalPositionScreen = AzFramework::WorldToScreen(finalTransformWorld.GetTranslation(), m_cameraState);
// select the entity (this will cause the manipulators to appear in EditorTransformComponentSelection)
AzToolsFramework::SelectEntity(m_entityIdBox);
// press and drag the mouse (starting where the surface manipulator is)
m_actionDispatcher->CameraState(m_cameraState)
->MousePosition(initialPositionScreen)
->MouseLButtonDown()
->MousePosition(finalPositionScreen)
->MouseLButtonUp();
// read back the position of the entity now
const AZ::Transform finalEntityTransform = AzToolsFramework::GetWorldTransform(m_entityIdBox);
// ensure final world positions match
EXPECT_THAT(finalEntityTransform, IsCloseTolerance(finalTransformWorld, 0.01f));
}
TEST_F(
EditorTransformComponentSelectionRenderGeometryIntersectionManipulatorFixture,
SurfaceManipulatorFollowsMouseAtDefaultEditorDistanceFromCameraWhenNoMeshIntersection)
{
// camera (go to position format) - 0.00, 25.00, 12.00, 0.00, -180.00
m_cameraState.m_viewportSize = AZ::Vector2(1280.0f, 720.0f);
AzFramework::SetCameraTransform(
m_cameraState,
AZ::Transform::CreateFromMatrix3x3AndTranslation(
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(-180.0f)), AZ::Vector3(0.0f, 25.0f, 12.0f)));
// the initial starting position of the entity
const auto initialTransformWorld = AzToolsFramework::GetWorldTransform(m_entityIdBox);
// where the entity should end up (default distance away from the camera/near clip under where the mouse is)
const auto finalTransformWorld =
AZ::Transform::CreateFromQuaternionAndTranslation(initialTransformWorld.GetRotation(), AZ::Vector3(0.0f, 14.9f, 12.0f));
// calculate the position in screen space of the initial position of the entity
const auto initialPositionScreen = AzFramework::WorldToScreen(initialTransformWorld.GetTranslation(), m_cameraState);
// calculate the position in screen space of the final position of the entity
const auto finalPositionScreen = AzFramework::WorldToScreen(finalTransformWorld.GetTranslation(), m_cameraState);
// select the entity (this will cause the manipulators to appear in EditorTransformComponentSelection)
AzToolsFramework::SelectEntity(m_entityIdBox);
// press and drag the mouse (starting where the surface manipulator is)
m_actionDispatcher->CameraState(m_cameraState)
->MousePosition(initialPositionScreen)
->MouseLButtonDown()
->MousePosition(finalPositionScreen)
->MouseLButtonUp();
// read back the position of the entity now
const AZ::Transform finalEntityTransform = AzToolsFramework::GetWorldTransform(m_entityIdBox);
const auto viewportRay = AzToolsFramework::ViewportInteraction::ViewportScreenToWorldRay(m_cameraState, initialPositionScreen);
const auto distanceAway = (finalEntityTransform.GetTranslation() - viewportRay.origin).GetLength();
// ensure final world positions match
EXPECT_THAT(finalEntityTransform, IsCloseTolerance(finalTransformWorld, 0.01f));
// ensure distance away is what we expect
EXPECT_NEAR(distanceAway, AzToolsFramework::GetDefaultEntityPlacementDistance(), 0.001f);
}
TEST_F(
EditorTransformComponentSelectionRenderGeometryIntersectionManipulatorFixture,
MiddleMouseButtonWithShiftAndCtrlHeldOnMeshSurfaceWillSnapSelectedEntityToIntersectionPoint)
{
// camera (go to position format) - 21.00, 8.00, 11.00, -22.00, 150.00
m_cameraState.m_viewportSize = AZ::Vector2(1280.0f, 720.0f);
AzFramework::SetCameraTransform(
m_cameraState,
AZ::Transform::CreateFromMatrix3x3AndTranslation(
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(150.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-22.0f)),
AZ::Vector3(21.0f, 8.0f, 11.0f)));
// position the ground entity
AzToolsFramework::SetWorldTransform(
m_entityIdGround,
AZ::Transform::CreateFromMatrix3x3AndTranslation(
AZ::Matrix3x3::CreateRotationY(AZ::DegToRad(40.0f)) * AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(60.0f)),
AZ::Vector3(14.0f, -6.0f, 5.0f)));
// select the other entity (a 1x1x1 box)
AzToolsFramework::SelectEntity(m_entityIdBox);
// expected world position (value taken from editor scenario)
const auto expectedWorldPosition = AZ::Vector3(13.606657f, -2.6753534f, 5.9827675f);
const auto screenPosition = AzFramework::WorldToScreen(expectedWorldPosition, m_cameraState);
// perform snap action
m_actionDispatcher->CameraState(m_cameraState)
->MousePosition(screenPosition)
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Control)
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Shift)
->MouseMButtonDown();
// read back the current entity transform after placement
const AZ::Transform finalEntityTransform = AzToolsFramework::GetWorldTransform(m_entityIdBox);
EXPECT_THAT(finalEntityTransform.GetTranslation(), IsCloseTolerance(expectedWorldPosition, 0.01f));
}
} // namespace UnitTest
@@ -10,21 +10,23 @@
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/Viewport/ViewportScreen.h>
#include <AzManipulatorTestFramework/AzManipulatorTestFramework.h>
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkTestHelpers.h>
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkUtils.h>
#include <AzManipulatorTestFramework/ImmediateModeActionDispatcher.h>
#include <AzManipulatorTestFramework/IndirectManipulatorViewportInteraction.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Manipulators/EditorVertexSelection.h>
#include <AzToolsFramework/Manipulators/HoverSelection.h>
#include <AzToolsFramework/Manipulators/ManipulatorManager.h>
#include <AzToolsFramework/Manipulators/TranslationManipulators.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h>
#include <AzManipulatorTestFramework/AzManipulatorTestFramework.h>
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkUtils.h>
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkTestHelpers.h>
#include <AzManipulatorTestFramework/IndirectManipulatorViewportInteraction.h>
#include <AzManipulatorTestFramework/ImmediateModeActionDispatcher.h>
#include <Tests/Utils/Printers.h>
using namespace AzToolsFramework;
#include <Tests/BoundsTestComponent.h>
namespace UnitTest
{
@@ -41,20 +43,65 @@ namespace UnitTest
void Disconnect();
// FixedVerticesRequestBus/VariableVerticesRequestBus ...
bool GetVertex(size_t index, AZ::Vector3& vertex) const override { return m_vertexContainer.GetVertex(index, vertex); }
bool UpdateVertex(size_t index, const AZ::Vector3& vertex) override { return m_vertexContainer.UpdateVertex(index, vertex); };
void AddVertex(const AZ::Vector3& vertex) override { m_vertexContainer.AddVertex(vertex); }
bool InsertVertex(size_t index, const AZ::Vector3& vertex) override { return m_vertexContainer.InsertVertex(index, vertex); }
bool RemoveVertex(size_t index) override { return m_vertexContainer.RemoveVertex(index); }
void SetVertices(const AZStd::vector<AZ::Vector3>& vertices) override { m_vertexContainer.SetVertices(vertices); };
void ClearVertices() override { m_vertexContainer.Clear(); }
size_t Size() const override { return m_vertexContainer.Size(); }
bool Empty() const override { return m_vertexContainer.Empty(); }
bool GetVertex(size_t index, AZ::Vector3& vertex) const override;
bool UpdateVertex(size_t index, const AZ::Vector3& vertex) override;
void AddVertex(const AZ::Vector3& vertex) override;
bool InsertVertex(size_t index, const AZ::Vector3& vertex) override;
bool RemoveVertex(size_t index) override;
void SetVertices(const AZStd::vector<AZ::Vector3>& vertices) override;
void ClearVertices() override;
size_t Size() const override;
bool Empty() const override;
private:
AZ::VertexContainer<AZ::Vector3> m_vertexContainer;
};
bool TestVariableVerticesVertexContainer::GetVertex(size_t index, AZ::Vector3& vertex) const
{
return m_vertexContainer.GetVertex(index, vertex);
}
bool TestVariableVerticesVertexContainer::UpdateVertex(size_t index, const AZ::Vector3& vertex)
{
return m_vertexContainer.UpdateVertex(index, vertex);
}
void TestVariableVerticesVertexContainer::AddVertex(const AZ::Vector3& vertex)
{
m_vertexContainer.AddVertex(vertex);
}
bool TestVariableVerticesVertexContainer::InsertVertex(size_t index, const AZ::Vector3& vertex)
{
return m_vertexContainer.InsertVertex(index, vertex);
}
bool TestVariableVerticesVertexContainer::RemoveVertex(size_t index)
{
return m_vertexContainer.RemoveVertex(index);
}
void TestVariableVerticesVertexContainer::SetVertices(const AZStd::vector<AZ::Vector3>& vertices)
{
m_vertexContainer.SetVertices(vertices);
}
void TestVariableVerticesVertexContainer::ClearVertices()
{
m_vertexContainer.Clear();
}
size_t TestVariableVerticesVertexContainer::Size() const
{
return m_vertexContainer.Size();
}
bool TestVariableVerticesVertexContainer::Empty() const
{
return m_vertexContainer.Empty();
}
void TestVariableVerticesVertexContainer::Connect(const AZ::EntityId entityId)
{
AZ::VariableVerticesRequestBus<AZ::Vector3>::Handler::BusConnect(entityId);
@@ -67,17 +114,18 @@ namespace UnitTest
AZ::VariableVerticesRequestBus<AZ::Vector3>::Handler::BusDisconnect();
}
class TestEditorVertexSelectionVariable
: public EditorVertexSelectionVariable<AZ::Vector3>
class TestEditorVertexSelectionVariable : public AzToolsFramework::EditorVertexSelectionVariable<AZ::Vector3>
{
public:
AZ_CLASS_ALLOCATOR(TestEditorVertexSelectionVariable, AZ::SystemAllocator, 0)
void ShowVertexDeletionWarning() override { /*noop*/ }
void ShowVertexDeletionWarning() override
{
// noop
}
};
class EditorVertexSelectionFixture
: public ToolsApplicationFixture
class EditorVertexSelectionFixture : public ToolsApplicationFixture
{
public:
void SetUpEditorFixtureImpl() override
@@ -111,26 +159,25 @@ namespace UnitTest
void EditorVertexSelectionFixture::RecreateVertexSelection()
{
namespace aztf = AzToolsFramework;
m_vertexSelection.Create(
AZ::EntityComponentIdPair(m_entityId, TestComponentId),
g_mainManipulatorManagerId, AZStd::make_unique<NullHoverSelection>(),
TranslationManipulators::Dimensions::Three, ConfigureTranslationManipulatorAppearance3d);
AZ::EntityComponentIdPair(m_entityId, TestComponentId), aztf::g_mainManipulatorManagerId,
AZStd::make_unique<aztf::NullHoverSelection>(), aztf::TranslationManipulators::Dimensions::Three,
aztf::ConfigureTranslationManipulatorAppearance3d);
}
void EditorVertexSelectionFixture::PopulateVertices()
{
for (size_t vertIndex = 0; vertIndex < EditorVertexSelectionFixture::VertexCount; ++vertIndex)
{
InsertVertexAfter(
AZ::EntityComponentIdPair(m_entityId, TestComponentId), 0, AZ::Vector3::CreateZero());
AzToolsFramework::InsertVertexAfter(AZ::EntityComponentIdPair(m_entityId, TestComponentId), 0, AZ::Vector3::CreateZero());
}
}
void EditorVertexSelectionFixture::ClearVertices()
{
for (size_t vertIndex = 0; vertIndex < EditorVertexSelectionFixture::VertexCount; ++vertIndex)
{
SafeRemoveVertex<AZ::Vector3>(
AZ::EntityComponentIdPair(m_entityId, TestComponentId), 0);
AzToolsFramework::SafeRemoveVertex<AZ::Vector3>(AZ::EntityComponentIdPair(m_entityId, TestComponentId), 0);
}
}
@@ -187,7 +234,7 @@ namespace UnitTest
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// When
// just provide a placeholder mouse interaction event in this case
m_vertexSelection.SnapVerticesToTerrain(ViewportInteraction::MouseInteractionEvent{});
m_vertexSelection.SnapVerticesToSurface(AzToolsFramework::ViewportInteraction::MouseInteractionEvent{});
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -196,8 +243,7 @@ namespace UnitTest
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
}
using EditorVertexSelectionManipulatorFixture =
IndirectCallManipulatorViewportInteractionFixtureMixin<EditorVertexSelectionFixture>;
using EditorVertexSelectionManipulatorFixture = IndirectCallManipulatorViewportInteractionFixtureMixin<EditorVertexSelectionFixture>;
TEST_F(EditorVertexSelectionManipulatorFixture, CannotDeleteAllVertices)
{
@@ -205,25 +251,23 @@ namespace UnitTest
const auto entityComponentIdPair = AZ::EntityComponentIdPair(m_entityId, TestComponentId);
const float horizontalPositions[] = {-1.5f, -0.5f, 0.5f, 1.5f};
for (size_t vertIndex = 0; vertIndex < std::size(horizontalPositions); ++vertIndex)
const float horizontalPositions[] = { -1.5f, -0.5f, 0.5f, 1.5f };
for (size_t vertIndex = 0; vertIndex < AZStd::size(horizontalPositions); ++vertIndex)
{
InsertVertexAfter(
entityComponentIdPair, vertIndex, AZ::Vector3(horizontalPositions[vertIndex], 5.0f, 0.0f));
AzToolsFramework::InsertVertexAfter(entityComponentIdPair, vertIndex, AZ::Vector3(horizontalPositions[vertIndex], 5.0f, 0.0f));
}
// rebuild the vertex selection after adding the new verts
// rebuild the vertex selection after adding the new vertices
RecreateVertexSelection();
// build a vector of the vertex positions in screen space
AZStd::vector<AzFramework::ScreenPoint> vertexScreenPositions;
for (size_t vertIndex = 0; vertIndex < std::size(horizontalPositions); ++vertIndex)
for (size_t vertIndex = 0; vertIndex < AZStd::size(horizontalPositions); ++vertIndex)
{
AZ::Vector3 localVertex;
AZ::Vector3 localVertex = AZ::Vector3::CreateZero();
bool found = false;
AZ::FixedVerticesRequestBus<AZ::Vector3>::EventResult(
found, m_entityId, &AZ::FixedVerticesRequestBus<AZ::Vector3>::Handler::GetVertex,
vertIndex, localVertex);
found, m_entityId, &AZ::FixedVerticesRequestBus<AZ::Vector3>::Handler::GetVertex, vertIndex, localVertex);
if (found)
{
@@ -266,9 +310,9 @@ namespace UnitTest
const auto entityComponentIdPair = AZ::EntityComponentIdPair(m_entityId, TestComponentId);
// add a single vertex (in front of the camera)
InsertVertexAfter(entityComponentIdPair, 0, AZ::Vector3::CreateAxisY(5.0f));
AzToolsFramework::InsertVertexAfter(entityComponentIdPair, 0, AZ::Vector3::CreateAxisY(5.0f));
// rebuild the vertex selection after adding the new verts
// rebuild the vertex selection after adding the new vertices
RecreateVertexSelection();
AzFramework::ScreenPoint vertexScreenPosition;
@@ -299,4 +343,132 @@ namespace UnitTest
// deleting the last vertex through a manipulator is disallowed - size should remain the same
EXPECT_THAT(vertexCountAfter, Eq(1));
}
static AZ::EntityId CreateEntityForVertexIntersectionPlacement(EditorVertexSelectionManipulatorFixture& fixture)
{
auto* app = fixture.GetApplication();
app->RegisterComponentDescriptor(BoundsTestComponent::CreateDescriptor());
app->RegisterComponentDescriptor(RenderGeometryIntersectionTestComponent::CreateDescriptor());
AZ::Entity* entityGround = nullptr;
AZ::EntityId entityIdGround = CreateDefaultEditorEntity("EntityGround", &entityGround);
entityGround->Deactivate();
auto ground = entityGround->CreateComponent<RenderGeometryIntersectionTestComponent>();
entityGround->Activate();
ground->m_localBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-10.0f, -10.0f, -0.5f), AZ::Vector3(10.0f, 10.0f, 0.5f));
return entityIdGround;
}
static AZStd::vector<AzFramework::ScreenPoint> SetupVertices(
const AZ::EntityId entityId, EditorVertexSelectionManipulatorFixture& fixture)
{
const auto entityComponentIdPair = AZ::EntityComponentIdPair(entityId, TestComponentId);
const float horizontalPositions[] = { -3.0f, -1.0f, 1.0f, 3.0f };
for (size_t vertIndex = 0; vertIndex < AZStd::size(horizontalPositions); ++vertIndex)
{
AzToolsFramework::InsertVertexAfter(entityComponentIdPair, vertIndex, AZ::Vector3(horizontalPositions[vertIndex], 0.0f, 0.0f));
}
// rebuild the vertex selection after adding the new vertices
fixture.RecreateVertexSelection();
// build a vector of the vertex positions in screen space
AZStd::vector<AzFramework::ScreenPoint> vertexScreenPositions;
for (size_t vertIndex = 0; vertIndex < AZStd::size(horizontalPositions); ++vertIndex)
{
AZ::Vector3 localVertex;
bool found = false;
AZ::FixedVerticesRequestBus<AZ::Vector3>::EventResult(
found, entityId, &AZ::FixedVerticesRequestBus<AZ::Vector3>::Handler::GetVertex, vertIndex, localVertex);
if (found)
{
const AZ::Vector3 worldVertex = AzToolsFramework::GetWorldTransform(entityId).TransformPoint(localVertex);
vertexScreenPositions.push_back(AzFramework::WorldToScreen(worldVertex, fixture.m_cameraState));
}
}
return vertexScreenPositions;
}
AzToolsFramework::ViewportInteraction::MouseInteractionEvent BuildMiddleMouseDownEvent(
const AzFramework::ScreenPoint& screenPosition, const AzFramework::ViewportId viewportId)
{
AzToolsFramework::ViewportInteraction::MousePick mousePick;
mousePick.m_screenCoordinates = screenPosition;
AzToolsFramework::ViewportInteraction::MouseInteraction mouseInteraction;
mouseInteraction.m_interactionId.m_cameraId = AZ::EntityId();
mouseInteraction.m_interactionId.m_viewportId = viewportId;
mouseInteraction.m_mouseButtons =
AzToolsFramework::ViewportInteraction::MouseButtonsFromButton(AzToolsFramework::ViewportInteraction::MouseButton::Middle);
mouseInteraction.m_mousePick = mousePick;
mouseInteraction.m_keyboardModifiers = AzToolsFramework::ViewportInteraction::KeyboardModifiers(
static_cast<AZ::u32>(AzToolsFramework::ViewportInteraction::KeyboardModifier::Shift) |
static_cast<AZ::u32>(AzToolsFramework::ViewportInteraction::KeyboardModifier::Ctrl));
return AzToolsFramework::ViewportInteraction::MouseInteractionEvent(
mouseInteraction, AzToolsFramework::ViewportInteraction::MouseEvent::Down, /*captured=*/false);
}
TEST_F(EditorVertexSelectionManipulatorFixture, VertexPlacedWhereIntersectionPointIsFoundWithCustomReferenceSpace)
{
const AZ::EntityId entityIdGround = CreateEntityForVertexIntersectionPlacement(*this);
// position ground
AzToolsFramework::SetWorldTransform(
entityIdGround,
AZ::Transform::CreateFromMatrix3x3AndTranslation(
AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-20.0f)) * AZ::Matrix3x3::CreateRotationY(AZ::DegToRad(-40.0f)) *
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(60.0f)),
AZ::Vector3(14.0f, -6.0f, 5.0f)));
// camera (go to position format) - 12.00, 18.00, 16.00, -38.00, -175.00
m_cameraState.m_viewportSize = AZ::Vector2(1280.0f, 720.0f);
AzFramework::SetCameraTransform(
m_cameraState,
AZ::Transform::CreateFromMatrix3x3AndTranslation(
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(-175.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-38.0f)),
AZ::Vector3(12.0f, 18.0f, 16.0f)));
// create orientated and scaled transform for vertex selection entity transform
auto vertexSelectionTransform = AZ::Transform::CreateFromMatrix3x3AndTranslation(
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(45.0f)), AZ::Vector3(14.0f, 7.0f, 5.0f));
vertexSelectionTransform.MultiplyByUniformScale(3.0f);
// set the initial starting position of the vertex selection
AzToolsFramework::SetWorldTransform(m_entityId, vertexSelectionTransform);
auto vertexScreenPositions = SetupVertices(m_entityId, *this);
// press and drag the mouse (starting where the surface manipulator is)
// select each vertex (by holding ctrl)
m_actionDispatcher->CameraState(m_cameraState)->MousePosition(vertexScreenPositions[0])->MouseLButtonDown()->MouseLButtonUp();
const auto finalPositionWorld = AZ::Vector3(14.3573294f, -8.94695091f, 7.08627319f);
// calculate the position in screen space of the final position of the entity
const auto finalPositionScreen = AzFramework::WorldToScreen(finalPositionWorld, m_cameraState);
auto middleMouseDownEvent =
BuildMiddleMouseDownEvent(finalPositionScreen, m_viewportManipulatorInteraction->GetViewportInteraction().GetViewportId());
// explicitly handle mouse event in vertex selection instance
m_vertexSelection.HandleMouse(middleMouseDownEvent);
// read back the position of the vertex now
AZ::Vector3 localVertex = AZ::Vector3::CreateZero();
bool found = false;
AZ::FixedVerticesRequestBus<AZ::Vector3>::EventResult(
found, m_entityId, &AZ::FixedVerticesRequestBus<AZ::Vector3>::Handler::GetVertex, 0, localVertex);
// transform to world space
const AZ::Vector3 worldVertex = vertexSelectionTransform.TransformPoint(localVertex);
EXPECT_THAT(found, ::testing::IsTrue());
// ensure final world positions match
EXPECT_THAT(worldVertex, IsCloseTolerance(finalPositionWorld, 0.01f));
}
} // namespace UnitTest
@@ -0,0 +1,125 @@
/*
* 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 <Tests/Entity/ReadOnly/ReadOnlyEntityFixture.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
namespace AzToolsFramework
{
void ReadOnlyEntityFixture::SetUpEditorFixtureImpl()
{
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
// in the unit tests.
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
m_readOnlyEntityPublicInterface = AZ::Interface<ReadOnlyEntityPublicInterface>::Get();
ASSERT_TRUE(m_readOnlyEntityPublicInterface != nullptr);
GenerateTestHierarchy();
}
void ReadOnlyEntityFixture::TearDownEditorFixtureImpl()
{
}
void ReadOnlyEntityFixture::GenerateTestHierarchy()
{
/*
* Root
* |_ Child
* |_ GrandChild1
* |_ GrandChild2
*/
m_entityMap[RootEntityName] = CreateEditorEntity(RootEntityName, AZ::EntityId());
m_entityMap[ChildEntityName] = CreateEditorEntity(ChildEntityName, m_entityMap[RootEntityName]);
m_entityMap[GrandChild1EntityName] = CreateEditorEntity(GrandChild1EntityName, m_entityMap[ChildEntityName]);
m_entityMap[GrandChild2EntityName] = CreateEditorEntity(GrandChild2EntityName, m_entityMap[ChildEntityName]);
}
AZ::EntityId ReadOnlyEntityFixture::CreateEditorEntity(const char* name, AZ::EntityId parentId)
{
AZ::Entity* entity = nullptr;
UnitTest::CreateDefaultEditorEntity(name, &entity);
// Parent
AZ::TransformBus::Event(entity->GetId(), &AZ::TransformInterface::SetParent, parentId);
return entity->GetId();
}
ReadOnlyHandlerAlwaysTrue::ReadOnlyHandlerAlwaysTrue()
{
auto editorEntityContextId = AzFramework::EntityContextId::CreateNull();
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
ReadOnlyEntityQueryRequestBus::Handler::BusConnect(editorEntityContextId);
}
ReadOnlyHandlerAlwaysTrue::~ReadOnlyHandlerAlwaysTrue()
{
ReadOnlyEntityQueryRequestBus::Handler::BusDisconnect();
if (auto readOnlyEntityQueryInterface = AZ::Interface<ReadOnlyEntityQueryInterface>::Get())
{
readOnlyEntityQueryInterface->RefreshReadOnlyStateForAllEntities();
}
}
void ReadOnlyHandlerAlwaysTrue::IsReadOnly([[maybe_unused]] const AZ::EntityId& entityId, bool& isReadOnly)
{
isReadOnly = true;
}
ReadOnlyHandlerAlwaysFalse::ReadOnlyHandlerAlwaysFalse()
{
auto editorEntityContextId = AzFramework::EntityContextId::CreateNull();
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
ReadOnlyEntityQueryRequestBus::Handler::BusConnect(editorEntityContextId);
}
ReadOnlyHandlerAlwaysFalse::~ReadOnlyHandlerAlwaysFalse()
{
ReadOnlyEntityQueryRequestBus::Handler::BusDisconnect();
if (auto readOnlyEntityQueryInterface = AZ::Interface<ReadOnlyEntityQueryInterface>::Get())
{
readOnlyEntityQueryInterface->RefreshReadOnlyStateForAllEntities();
}
}
ReadOnlyHandlerEntityId::ReadOnlyHandlerEntityId(AZ::EntityId entityId)
: m_entityId(entityId)
{
auto editorEntityContextId = AzFramework::EntityContextId::CreateNull();
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
ReadOnlyEntityQueryRequestBus::Handler::BusConnect(editorEntityContextId);
}
ReadOnlyHandlerEntityId::~ReadOnlyHandlerEntityId()
{
ReadOnlyEntityQueryRequestBus::Handler::BusDisconnect();
if (auto readOnlyEntityQueryInterface = AZ::Interface<ReadOnlyEntityQueryInterface>::Get())
{
readOnlyEntityQueryInterface->RefreshReadOnlyStateForAllEntities();
}
}
void ReadOnlyHandlerEntityId::IsReadOnly(const AZ::EntityId& entityId, bool& isReadOnly)
{
if (entityId == m_entityId)
{
isReadOnly = true;
}
}
}
@@ -0,0 +1,78 @@
/*
* 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 <AzCore/Component/TransformBus.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <AzToolsFramework/Entity/ReadOnly/ReadOnlyEntityBus.h>
#include <AzToolsFramework/Entity/ReadOnly/ReadOnlyEntityInterface.h>
namespace AzToolsFramework
{
class ReadOnlyEntityFixture
: public UnitTest::ToolsApplicationFixture
{
protected:
void SetUpEditorFixtureImpl() override;
void TearDownEditorFixtureImpl() override;
void GenerateTestHierarchy();
AZ::EntityId CreateEditorEntity(const char* name, AZ::EntityId parentId);
AZStd::unordered_map<AZStd::string, AZ::EntityId> m_entityMap;
ReadOnlyEntityPublicInterface* m_readOnlyEntityPublicInterface = nullptr;
public:
inline static const char* RootEntityName = "Root";
inline static const char* ChildEntityName = "Child";
inline static const char* GrandChild1EntityName = "GrandChild1";
inline static const char* GrandChild2EntityName = "GrandChild2";
};
class ReadOnlyHandlerAlwaysTrue
: public ReadOnlyEntityQueryRequestBus::Handler
{
public:
ReadOnlyHandlerAlwaysTrue();
~ReadOnlyHandlerAlwaysTrue();
// ReadOnlyEntityQueryNotificationBus overrides ...
void IsReadOnly(const AZ::EntityId& entityId, bool& isReadOnly) override;
};
class ReadOnlyHandlerAlwaysFalse
: public ReadOnlyEntityQueryRequestBus::Handler
{
public:
ReadOnlyHandlerAlwaysFalse();
~ReadOnlyHandlerAlwaysFalse();
// ReadOnlyEntityQueryNotificationBus overrides ...
void IsReadOnly([[maybe_unused]] const AZ::EntityId& entityId, [[maybe_unused]] bool& isReadOnly) override {}
};
class ReadOnlyHandlerEntityId
: public ReadOnlyEntityQueryRequestBus::Handler
{
public:
ReadOnlyHandlerEntityId(AZ::EntityId entityId);
~ReadOnlyHandlerEntityId();
// ReadOnlyEntityQueryNotificationBus overrides ...
void IsReadOnly(const AZ::EntityId& entityId, bool& isReadOnly) override;
private:
AZ::EntityId m_entityId;
};
}
@@ -0,0 +1,99 @@
/*
* 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 <Tests/Entity/ReadOnly/ReadOnlyEntityFixture.h>
namespace AzToolsFramework
{
TEST_F(ReadOnlyEntityFixture, NoHandlerEntityIsNotReadOnlyByDefault)
{
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
}
TEST_F(ReadOnlyEntityFixture, SingleHandlerEntityIsReadOnly)
{
// Create a handler that sets all entities to read-only.
ReadOnlyHandlerAlwaysTrue alwaysTrueHandler;
// All entities should be marked read-only now.
EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[RootEntityName]));
EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild1EntityName]));
EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild2EntityName]));
}
TEST_F(ReadOnlyEntityFixture, SingleHandlerEntityIsNotReadOnly)
{
// Create a handler that sets all entities to read-only.
ReadOnlyHandlerAlwaysFalse alwaysFalseHandler;
// All entities should not be marked read-only now.
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[RootEntityName]));
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild1EntityName]));
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild2EntityName]));
}
TEST_F(ReadOnlyEntityFixture, SingleHandlerWithLogic)
{
// Create a handler that sets just the child entity to read-only.
ReadOnlyHandlerEntityId entityIdHandler(m_entityMap[ChildEntityName]);
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[RootEntityName]));
EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild1EntityName]));
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild2EntityName]));
}
TEST_F(ReadOnlyEntityFixture, TwoHandlersCanOverlap)
{
// Create two handlers that set different entities to read-only.
ReadOnlyHandlerEntityId entityIdHandler1(m_entityMap[ChildEntityName]);
ReadOnlyHandlerEntityId entityIdHandler2(m_entityMap[GrandChild2EntityName]);
// Both entities should be marked as read-only, while others aren't.
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[RootEntityName]));
EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild1EntityName]));
EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild2EntityName]));
}
TEST_F(ReadOnlyEntityFixture, EnsureCacheIsRefreshedCorrectly)
{
// Verify the child entity is not marked as read-only
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
// Create a handler that sets the child entity to read-only.
ReadOnlyHandlerEntityId entityIdHandler(m_entityMap[ChildEntityName]);
// Communicate to the ReadOnlyEntitySystemComponent that the read-only state for the child entity may have changed.
// Note that this operation would usually be executed by the handler, hence the Query interface call.
if (auto readOnlyEntityQueryInterface = AZ::Interface<ReadOnlyEntityQueryInterface>::Get())
{
readOnlyEntityQueryInterface->RefreshReadOnlyState({ m_entityMap[ChildEntityName] });
}
// Verify the child entity is marked as read-only
EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
}
TEST_F(ReadOnlyEntityFixture, EnsureCacheIsClearedCorrectly)
{
{
// Create a handler that sets the child entity to read-only.
ReadOnlyHandlerEntityId entityIdHandler(m_entityMap[ChildEntityName]);
// Verify the child entity is marked as read-only
EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
}
// When the handler goes out of scope, it calls RefreshReadOnlyStateForAllEntities and refreshes the cache.
// Verify the child entity is no longer marked as read-only
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
}
}
@@ -710,7 +710,7 @@ namespace UnitTest
auto getEnumData = [&ec](const AzToolsFramework::InstanceDataNode& node) -> Uuid
{
Uuid id;
Uuid id = Uuid::CreateNull();
auto attribute = node.GetElementMetadata()->FindAttribute(AZ_CRC("EnumType"));
auto attributeData = azrtti_cast<AttributeData<AZ::TypeId>*>(attribute);
if (attributeData)
@@ -0,0 +1,150 @@
/*
* 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 <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <Prefab/PrefabTestComponent.h>
#include <Prefab/PrefabTestDomUtils.h>
#include <Prefab/PrefabTestFixture.h>
namespace UnitTest
{
using PrefabDeleteTest = PrefabTestFixture;
TEST_F(PrefabDeleteTest, DeleteEntitiesInInstance_DeleteSingleEntitySucceeds)
{
PrefabEntityResult createEntityResult = m_prefabPublicInterface->CreateEntity(AZ::EntityId(), AZ::Vector3());
// Verify that a valid entity is created.
AZ::EntityId testEntityId = createEntityResult.GetValue();
ASSERT_TRUE(testEntityId.IsValid());
AZ::Entity* testEntity = AzToolsFramework::GetEntityById(testEntityId);
ASSERT_TRUE(testEntity != nullptr);
m_prefabPublicInterface->DeleteEntitiesInInstance(AzToolsFramework::EntityIdList{ testEntityId });
// Verify that entity can't be found after deletion.
testEntity = AzToolsFramework::GetEntityById(testEntityId);
EXPECT_TRUE(testEntity == nullptr);
}
TEST_F(PrefabDeleteTest, DeleteEntitiesInInstance_DeleteSinglePrefabSucceeds)
{
PrefabEntityResult createEntityResult = m_prefabPublicInterface->CreateEntity(AZ::EntityId(), AZ::Vector3());
// Verify that a valid entity is created.
AZ::EntityId createdEntityId = createEntityResult.GetValue();
ASSERT_TRUE(createdEntityId.IsValid());
AZ::Entity* createdEntity = AzToolsFramework::GetEntityById(createdEntityId);
ASSERT_TRUE(createdEntity != nullptr);
// Rather than hardcode a path, use a path from settings registry since that will work on all platforms.
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
AZ::IO::FixedMaxPath path;
registry->Get(path.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
CreatePrefabResult createPrefabResult =
m_prefabPublicInterface->CreatePrefabInMemory(AzToolsFramework::EntityIdList{ createdEntityId }, path);
AZ::EntityId createdPrefabContainerId = createPrefabResult.GetValue();
ASSERT_TRUE(createdPrefabContainerId.IsValid());
AZ::Entity* prefabContainerEntity = AzToolsFramework::GetEntityById(createdPrefabContainerId);
ASSERT_TRUE(prefabContainerEntity != nullptr);
// Verify that the prefab container entity and the entity within are deleted.
m_prefabPublicInterface->DeleteEntitiesInInstance(AzToolsFramework::EntityIdList{ createdPrefabContainerId });
prefabContainerEntity = AzToolsFramework::GetEntityById(createdPrefabContainerId);
EXPECT_TRUE(prefabContainerEntity == nullptr);
createdEntity = AzToolsFramework::GetEntityById(createdEntityId);
EXPECT_TRUE(createdEntity == nullptr);
}
TEST_F(PrefabDeleteTest, DeleteEntitiesAndAllDescendantsInInstance_DeletingEntityDeletesChildEntityToo)
{
PrefabEntityResult parentEntityCreationResult = m_prefabPublicInterface->CreateEntity(AZ::EntityId(), AZ::Vector3());
// Verify that valid parent entity is created.
AZ::EntityId parentEntityId = parentEntityCreationResult.GetValue();
ASSERT_TRUE(parentEntityId.IsValid());
AZ::Entity* parentEntity = AzToolsFramework::GetEntityById(parentEntityId);
ASSERT_TRUE(parentEntity != nullptr);
// Verify that valid child entity is created.
PrefabEntityResult childEntityCreationResult = m_prefabPublicInterface->CreateEntity(parentEntityId, AZ::Vector3());
AZ::EntityId childEntityId = childEntityCreationResult.GetValue();
ASSERT_TRUE(childEntityId.IsValid());
AZ::Entity* childEntity = AzToolsFramework::GetEntityById(childEntityId);
ASSERT_TRUE(childEntity != nullptr);
// PrefabTestFixture won't add required editor components by default. Hence we add them here.
AddRequiredEditorComponents(childEntity);
AddRequiredEditorComponents(parentEntity);
// Parent the child entity under the parent entity.
AZ::TransformBus::Event(childEntityId, &AZ::TransformBus::Events::SetParent, parentEntityId);
// Delete parent entity and its children.
m_prefabPublicInterface->DeleteEntitiesAndAllDescendantsInInstance(AzToolsFramework::EntityIdList{ parentEntityId });
// Verify that both the parent and child entities are deleted.
parentEntity = AzToolsFramework::GetEntityById(parentEntityId);
EXPECT_TRUE(parentEntity == nullptr);
childEntity = AzToolsFramework::GetEntityById(childEntityId);
EXPECT_TRUE(childEntity == nullptr);
}
TEST_F(PrefabDeleteTest, DeleteEntitiesAndAllDescendantsInInstance_DeletingEntityDeletesChildPrefabToo)
{
PrefabEntityResult entityToBePutUnderPrefabResult = m_prefabPublicInterface->CreateEntity(AZ::EntityId(), AZ::Vector3());
// Verify that a valid entity is created that will be put in a prefab later.
AZ::EntityId entityToBePutUnderPrefabId = entityToBePutUnderPrefabResult.GetValue();
ASSERT_TRUE(entityToBePutUnderPrefabId.IsValid());
AZ::Entity* entityToBePutUnderPrefab = AzToolsFramework::GetEntityById(entityToBePutUnderPrefabId);
ASSERT_TRUE(entityToBePutUnderPrefab != nullptr);
// Verify that a valid parent entity is created.
PrefabEntityResult parentEntityCreationResult = m_prefabPublicInterface->CreateEntity(AZ::EntityId(), AZ::Vector3());
AZ::EntityId parentEntityId = parentEntityCreationResult.GetValue();
ASSERT_TRUE(parentEntityId.IsValid());
AZ::Entity* parentEntity = AzToolsFramework::GetEntityById(parentEntityId);
ASSERT_TRUE(parentEntity != nullptr);
// Rather than hardcode a path, use a path from settings registry since that will work on all platforms.
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
AZ::IO::FixedMaxPath path;
registry->Get(path.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
CreatePrefabResult createPrefabResult =
m_prefabPublicInterface->CreatePrefabInMemory(AzToolsFramework::EntityIdList{ entityToBePutUnderPrefabId }, path);
// Verify that a valid prefab container entity is created.
AZ::EntityId createdPrefabContainerId = createPrefabResult.GetValue();
ASSERT_TRUE(createdPrefabContainerId.IsValid());
AZ::Entity* prefabContainerEntity = AzToolsFramework::GetEntityById(createdPrefabContainerId);
ASSERT_TRUE(prefabContainerEntity != nullptr);
// PrefabTestFixture won't add required editor components by default. Hence we add them here.
AddRequiredEditorComponents(parentEntity);
AddRequiredEditorComponents(prefabContainerEntity);
// Parent the prefab under the parent entity.
AZ::TransformBus::Event(createdPrefabContainerId, &AZ::TransformBus::Events::SetParent, parentEntityId);
// Delete the parent entity.
m_prefabPublicInterface->DeleteEntitiesAndAllDescendantsInInstance(AzToolsFramework::EntityIdList{ parentEntityId });
// Validate that the parent and the prefab under it and the entity inside the prefab are all deleted.
parentEntity = AzToolsFramework::GetEntityById(parentEntityId);
ASSERT_TRUE(parentEntity == nullptr);
entityToBePutUnderPrefab = AzToolsFramework::GetEntityById(entityToBePutUnderPrefabId);
ASSERT_TRUE(entityToBePutUnderPrefab == nullptr);
prefabContainerEntity = AzToolsFramework::GetEntityById(createdPrefabContainerId);
EXPECT_TRUE(prefabContainerEntity == nullptr);
}
} // namespace UnitTest
@@ -106,7 +106,9 @@ namespace UnitTest
inline static const char* Passenger2EntityName = "Passenger2";
};
TEST_F(PrefabFocusTests, PrefabFocus_FocusOnOwningPrefab_RootContainer)
// Test was disabled because the implementation of GetFocusedPrefabInstance now relies on the Prefab EOS,
// which is not used by our test environment. This can be restored once Instance handles are implemented.
TEST_F(PrefabFocusTests, DISABLED_PrefabFocus_FocusOnOwningPrefab_RootContainer)
{
// Verify FocusOnOwningPrefab works when passing the container entity of the root prefab.
{
@@ -121,7 +123,9 @@ namespace UnitTest
}
}
TEST_F(PrefabFocusTests, PrefabFocus_FocusOnOwningPrefab_RootEntity)
// Test was disabled because the implementation of GetFocusedPrefabInstance now relies on the Prefab EOS,
// which is not used by our test environment. This can be restored once Instance handles are implemented.
TEST_F(PrefabFocusTests, DISABLED_PrefabFocus_FocusOnOwningPrefab_RootEntity)
{
// Verify FocusOnOwningPrefab works when passing a nested entity of the root prefab.
{
@@ -57,6 +57,11 @@ namespace UnitTest
return AZStd::make_unique<PrefabTestToolsApplication>("PrefabTestApplication");
}
void PrefabTestFixture::PropagateAllTemplateChanges()
{
m_prefabSystemComponent->OnSystemTick();
}
AZ::Entity* PrefabTestFixture::CreateEntity(const char* entityName, const bool shouldActivate)
{
// Circumvent the EntityContext system and generate a new entity with a transformcomponent
@@ -125,4 +130,13 @@ namespace UnitTest
EXPECT_EQ(entityInInstance->GetState(), AZ::Entity::State::Active);
}
}
void PrefabTestFixture::AddRequiredEditorComponents(AZ::Entity* entity)
{
ASSERT_TRUE(entity != nullptr);
entity->Deactivate();
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
&AzToolsFramework::EditorEntityContextRequests::AddRequiredComponents, *entity);
entity->Activate();
}
}
@@ -52,6 +52,8 @@ namespace UnitTest
AZStd::unique_ptr<ToolsTestApplication> CreateTestApplication() override;
void PropagateAllTemplateChanges();
AZ::Entity* CreateEntity(const char* entityName, const bool shouldActivate = true);
void CompareInstances(const Instance& instanceA, const Instance& instanceB, bool shouldCompareLinkIds = true,
@@ -62,6 +64,8 @@ namespace UnitTest
//! Validates that all entities within a prefab instance are in 'Active' state.
void ValidateInstanceEntitiesActive(Instance& instance);
void AddRequiredEditorComponents(AZ::Entity* entity);
PrefabSystemComponent* m_prefabSystemComponent = nullptr;
PrefabLoaderInterface* m_prefabLoaderInterface = nullptr;
PrefabPublicInterface* m_prefabPublicInterface = nullptr;
@@ -6,7 +6,9 @@
*
*/
#include "IntegerPrimtitiveTestConfig.h"
#include <AzToolsFramework/UI/PropertyEditor/PropertyIntCtrlCommon.h>
#include <AzToolsFramework/UI/PropertyEditor/QtWidgetLimits.h>
#include <Tests/IntegerPrimtitiveTestConfig.h>
namespace UnitTest
{
@@ -156,6 +156,16 @@ namespace UnitTest
EXPECT_STREQ(tooltip.toStdString().c_str(), expected.str().c_str());
}
void EmitWidgetValueChanged()
{
emit m_widget->valueChanged(ValueType(0));
}
void EmitWidgetEditingFinished()
{
emit m_widget->editingFinished();
}
AZStd::unique_ptr<QWidget> m_dummyWidget;
AZStd::unique_ptr<HandlerAPI> m_handler;
WidgetType* m_widget;
@@ -47,4 +47,71 @@ namespace UnitTest
{
this->HandlerMinMaxLessLimit_ModifyHandler_ExpectSuccessAndValidLessLimitToolTipString();
}
struct PropertyEditorHandler
: public AzToolsFramework::PropertyEditorGUIMessages::Bus::Handler
{
PropertyEditorHandler()
{
AzToolsFramework::PropertyEditorGUIMessages::Bus::Handler::BusConnect();
}
~PropertyEditorHandler()
{
AzToolsFramework::PropertyEditorGUIMessages::Bus::Handler::BusDisconnect();
}
// AzToolsFramework::PropertyEditorGUIMessages::Bus overrides ...
void RequestWrite([[maybe_unused]] QWidget* editorGUI) override
{
m_requestWriteCallCount++;
}
void RequestRefresh([[maybe_unused]] PropertyModificationRefreshLevel level) override
{
}
void AddElementsToParentContainer(
[[maybe_unused]] QWidget* editorGUI,
[[maybe_unused]] size_t numElements,
[[maybe_unused]] const InstanceDataNode::FillDataClassCallback& fillDataCallback) override
{
}
void RequestPropertyNotify([[maybe_unused]] QWidget* editorGUI) override
{
}
void OnEditingFinished([[maybe_unused]]QWidget* editorGUI) override
{
m_onEditingFinishedCallCount++;
}
int m_requestWriteCallCount = 0;
int m_onEditingFinishedCallCount = 0;
};
TYPED_TEST(PropertySpinCtrlFixture, SpinBoxWidgetValueChangedInvokesPropertyEditorGUIMessages)
{
// setup the event handler
PropertyEditorHandler eventHandler;
// trigger the QT signal
this->EmitWidgetValueChanged();
// there should be at least 1 call to RequestWrite.
EXPECT_GT(eventHandler.m_requestWriteCallCount, 0);
}
TYPED_TEST(PropertySpinCtrlFixture, SpinBoxWidgetEditingFinishedInvokesPropertyEditorGUIMessages)
{
// setup the event handler
PropertyEditorHandler eventHandler;
// trigger the QT signal
this->EmitWidgetEditingFinished();
// there should be at least 1 call to OnEditingFinished.
EXPECT_GT(eventHandler.m_onEditingFinishedCallCount, 0);
}
} // namespace UnitTest
@@ -128,7 +128,7 @@ namespace UnitTest
void ProcessDeferredUpdates()
{
// Force a prefab propagation for updates that are deferred to the next tick.
m_prefabSystemComponent->OnSystemTick();
PropagateAllTemplateChanges();
// Ensure the model process its entity update queue
m_model->ProcessEntityUpdates();
@@ -36,11 +36,6 @@ namespace UnitTest
: public ComponentApplication
{
public:
void SetExecutableFolder(const char* path)
{
m_exeDirectory = path;
}
void SetSettingsRegistrySpecializations(SettingsRegistryInterface::Specializations& specializations) override
{
ComponentApplication::SetSettingsRegistrySpecializations(specializations);
@@ -0,0 +1,76 @@
/*
* 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 <AzFramework/Viewport/ViewportScreen.h>
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkTestHelpers.h>
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkUtils.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
#include <Tests/BoundsTestComponent.h>
namespace UnitTest
{
class IndirectCallViewportInteractionIntersectionFixture : public ToolsApplicationFixture
{
public:
void SetUpEditorFixtureImpl() override
{
auto* app = GetApplication();
// register a simple component implementing BoundsRequestBus and EditorComponentSelectionRequestsBus
app->RegisterComponentDescriptor(BoundsTestComponent::CreateDescriptor());
// register a component implementing RenderGeometry::IntersectionRequestBus
app->RegisterComponentDescriptor(RenderGeometryIntersectionTestComponent::CreateDescriptor());
AZ::Entity* entityGround = nullptr;
m_entityIdGround = CreateDefaultEditorEntity("EntityGround", &entityGround);
entityGround->Deactivate();
auto ground = entityGround->CreateComponent<RenderGeometryIntersectionTestComponent>();
entityGround->Activate();
ground->m_localBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-10.0f, -10.0f, -0.5f), AZ::Vector3(10.0f, 10.0f, 0.5f));
}
AZ::EntityId m_entityIdGround;
};
using IndirectCallManipulatorViewportInteractionIntersectionFixture =
IndirectCallManipulatorViewportInteractionFixtureMixin<IndirectCallViewportInteractionIntersectionFixture>;
TEST_F(IndirectCallManipulatorViewportInteractionIntersectionFixture, FindClosestPickIntersectionReturnsExpectedSurfacePoint)
{
// camera - 21.00, 8.00, 11.00, -22.00, 150.00
m_cameraState.m_viewportSize = AZ::Vector2(1280.0f, 720.0f);
AzFramework::SetCameraTransform(
m_cameraState,
AZ::Transform::CreateFromMatrix3x3AndTranslation(
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(150.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-22.0f)),
AZ::Vector3(21.0f, 8.0f, 11.0f)));
m_actionDispatcher->CameraState(m_cameraState);
AzToolsFramework::SetWorldTransform(
m_entityIdGround,
AZ::Transform::CreateFromMatrix3x3AndTranslation(
AZ::Matrix3x3::CreateRotationY(AZ::DegToRad(40.0f)) * AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(60.0f)),
AZ::Vector3(14.0f, -6.0f, 5.0f)));
// expected world position (value taken from editor scenario)
const auto expectedWorldPosition = AZ::Vector3(13.606657f, -2.6753534f, 5.9827675f);
const auto screenPosition = AzFramework::WorldToScreen(expectedWorldPosition, m_cameraState);
// perform ray intersection against mesh
const auto worldIntersectionPoint = AzToolsFramework::FindClosestPickIntersection(
m_viewportManipulatorInteraction->GetViewportInteraction().GetViewportId(), screenPosition,
AzToolsFramework::EditorPickRayLength, AzToolsFramework::GetDefaultEntityPlacementDistance());
EXPECT_THAT(worldIntersectionPoint, IsCloseTolerance(expectedWorldPosition, 0.01f));
}
} // namespace UnitTest

Some files were not shown because too many files have changed in this diff Show More