Merge remote-tracking branch 'upstream/stabilization/2110' into Prism/show-gem-repos-update

Signed-off-by: Alex Peterson <26804013+AMZN-alexpete@users.noreply.github.com>

# Conflicts:
#	Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp
This commit is contained in:
Alex Peterson
2021-11-03 12:24:34 -07:00
119 changed files with 2211 additions and 736 deletions
+3 -1
View File
@@ -1124,7 +1124,9 @@ void EditorViewportWidget::OnTitleMenu(QMenu* menu)
action = menu->addAction(tr("Create camera entity from current view"));
connect(action, &QAction::triggered, this, &EditorViewportWidget::OnMenuCreateCameraEntityFromCurrentView);
if (!gameEngine || !gameEngine->IsLevelLoaded())
const auto prefabEditorEntityOwnershipInterface = AZ::Interface<AzToolsFramework::PrefabEditorEntityOwnershipInterface>::Get();
if (!gameEngine || !gameEngine->IsLevelLoaded() ||
(prefabEditorEntityOwnershipInterface && !prefabEditorEntityOwnershipInterface->IsRootPrefabAssigned()))
{
action->setEnabled(false);
action->setToolTip(tr(AZ::ViewportHelpers::TextCantCreateCameraNoLevel));
@@ -85,6 +85,7 @@ namespace UnitTest
m_rootWidget = AZStd::make_unique<QWidget>();
m_rootWidget->setFixedSize(QSize(100, 100));
QApplication::setActiveWindow(m_rootWidget.get());
m_controllerList = AZStd::make_shared<AzFramework::ViewportControllerList>();
m_controllerList->RegisterViewportContext(TestViewportId);
@@ -100,6 +101,8 @@ namespace UnitTest
m_controllerList.reset();
m_rootWidget.reset();
QApplication::setActiveWindow(nullptr);
AllocatorsTestFixture::TearDown();
}
@@ -110,7 +113,7 @@ namespace UnitTest
const AzFramework::ViewportId ViewportManipulatorControllerFixture::TestViewportId = AzFramework::ViewportId(0);
TEST_F(ViewportManipulatorControllerFixture, An_event_is_not_propagated_to_the_viewport_when_a_manipulator_handles_it_first)
TEST_F(ViewportManipulatorControllerFixture, AnEventIsNotPropagatedToTheViewportWhenAManipulatorHandlesItFirst)
{
// forward input events to our controller list
QObject::connect(
@@ -151,4 +154,77 @@ namespace UnitTest
editorInteractionViewportFake.Disconnect();
}
TEST_F(ViewportManipulatorControllerFixture, ChangingFocusDoesNotClearInput)
{
bool endedEvent = false;
// detect input events and ensure that the Alt key press does not end before the end of the test
QObject::connect(
m_inputChannelMapper.get(), &AzToolsFramework::QtEventToAzInputMapper::InputChannelUpdated, m_rootWidget.get(),
[&endedEvent](const AzFramework::InputChannel* inputChannel, [[maybe_unused]] QEvent* event)
{
if (inputChannel->GetInputChannelId() == AzFramework::InputDeviceKeyboard::Key::ModifierAltL &&
inputChannel->IsStateEnded())
{
endedEvent = true;
}
});
// given
auto* secondaryWidget = new QWidget(m_rootWidget.get());
m_rootWidget->show();
secondaryWidget->show();
m_rootWidget->setFocus();
// simulate a key press when root widget has focus
QTest::keyPress(m_rootWidget.get(), Qt::Key_Alt, Qt::KeyboardModifier::AltModifier);
// when
// change focus to secondary widget
secondaryWidget->setFocus();
// then
// the alt key was not released (cleared)
EXPECT_FALSE(endedEvent);
}
// note: Application State Change includes events such as switching to another application or minimizing
// the current application
TEST_F(ViewportManipulatorControllerFixture, ApplicationStateChangeDoesClearInput)
{
bool endedEvent = false;
// detect input events and ensure that the Alt key press does not end before the end of the test
QObject::connect(
m_inputChannelMapper.get(), &AzToolsFramework::QtEventToAzInputMapper::InputChannelUpdated, m_rootWidget.get(),
[&endedEvent](const AzFramework::InputChannel* inputChannel, [[maybe_unused]] QEvent* event)
{
if (inputChannel->GetInputChannelId() == AzFramework::InputDeviceKeyboard::Key::AlphanumericW &&
inputChannel->IsStateEnded())
{
endedEvent = true;
}
});
// given
auto* secondaryWidget = new QWidget(m_rootWidget.get());
m_rootWidget->show();
secondaryWidget->show();
m_rootWidget->setFocus();
// simulate a key press when root widget has focus
QTest::keyPress(m_rootWidget.get(), Qt::Key_W);
// when
// simulate changing the window state
QApplicationStateChangeEvent applicationStateChangeEvent(Qt::ApplicationState::ApplicationInactive);
QCoreApplication::sendEvent(m_rootWidget.get(), &applicationStateChangeEvent);
// then
// the key was released (cleared)
EXPECT_TRUE(endedEvent);
}
} // namespace UnitTest
@@ -38,6 +38,7 @@
#include <AzToolsFramework/Commands/EntityStateCommand.h>
#include <AzToolsFramework/Commands/SelectionCommand.h>
#include <AzToolsFramework/Commands/SliceDetachEntityCommand.h>
#include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
#include <AzToolsFramework/Editor/EditorContextMenuBus.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
@@ -642,6 +643,9 @@ void SandboxIntegrationManager::PopulateEditorGlobalContextMenu(QMenu* menu, con
AzToolsFramework::EntityIdList selected;
GetSelectedOrHighlightedEntities(selected);
bool prefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(prefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
QAction* action = nullptr;
// when nothing is selected, entity is created at root level
@@ -658,18 +662,20 @@ void SandboxIntegrationManager::PopulateEditorGlobalContextMenu(QMenu* menu, con
// when a single entity is selected, entity is created as its child
else if (selected.size() == 1)
{
action = menu->addAction(QObject::tr("Create entity"));
QObject::connect(
action, &QAction::triggered, action,
[selected]
{
EBUS_EVENT(AzToolsFramework::EditorRequests::Bus, CreateNewEntityAsChild, selected.front());
});
auto containerEntityInterface = AZ::Interface<AzToolsFramework::ContainerEntityInterface>::Get();
if (!prefabSystemEnabled || (containerEntityInterface && containerEntityInterface->IsContainerOpen(selected.front())))
{
action = menu->addAction(QObject::tr("Create entity"));
QObject::connect(
action, &QAction::triggered, action,
[selected]
{
AzToolsFramework::EditorRequestBus::Broadcast(&AzToolsFramework::EditorRequestBus::Handler::CreateNewEntityAsChild, selected.front());
}
);
}
}
bool prefabSystemEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(prefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (!prefabSystemEnabled)
{
menu->addSeparator();
@@ -383,36 +383,36 @@ namespace AZ
AZ_MATH_INLINE bool CmpAllEq(__m128 arg1, __m128 arg2, int32_t mask)
{
const __m128i compare = CastToInt(CmpNeq(arg1, arg2));
return (_mm_movemask_epi8(compare) & mask) == 0;
const __m128 compare = CmpEq(arg1, arg2);
return (_mm_movemask_ps(compare) & mask) == mask;
}
AZ_MATH_INLINE bool CmpAllLt(__m128 arg1, __m128 arg2, int32_t mask)
{
const __m128i compare = CastToInt(CmpGtEq(arg1, arg2));
return (_mm_movemask_epi8(compare) & mask) == 0;
const __m128 compare = CmpLt(arg1, arg2);
return (_mm_movemask_ps(compare) & mask) == mask;
}
AZ_MATH_INLINE bool CmpAllLtEq(__m128 arg1, __m128 arg2, int32_t mask)
{
const __m128i compare = CastToInt(CmpGt(arg1, arg2));
return (_mm_movemask_epi8(compare) & mask) == 0;
const __m128 compare = CmpLtEq(arg1, arg2);
return (_mm_movemask_ps(compare) & mask) == mask;
}
AZ_MATH_INLINE bool CmpAllGt(__m128 arg1, __m128 arg2, int32_t mask)
{
const __m128i compare = CastToInt(CmpLtEq(arg1, arg2));
return (_mm_movemask_epi8(compare) & mask) == 0;
const __m128 compare = CmpGt(arg1, arg2);
return (_mm_movemask_ps(compare) & mask) == mask;
}
AZ_MATH_INLINE bool CmpAllGtEq(__m128 arg1, __m128 arg2, int32_t mask)
{
const __m128i compare = CastToInt(CmpLt(arg1, arg2));
return (_mm_movemask_epi8(compare) & mask) == 0;
const __m128 compare = CmpGtEq(arg1, arg2);
return (_mm_movemask_ps(compare) & mask) == mask;
}
@@ -331,31 +331,32 @@ namespace AZ
AZ_MATH_INLINE bool Vec1::CmpAllEq(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllEq(arg1, arg2, 0x000F);
// Only check the first bit for Vector1
return Sse::CmpAllEq(arg1, arg2, 0b0001);
}
AZ_MATH_INLINE bool Vec1::CmpAllLt(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllLt(arg1, arg2, 0x000F);
return Sse::CmpAllLt(arg1, arg2, 0b0001);
}
AZ_MATH_INLINE bool Vec1::CmpAllLtEq(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllLtEq(arg1, arg2, 0x000F);
return Sse::CmpAllLtEq(arg1, arg2, 0b0001);
}
AZ_MATH_INLINE bool Vec1::CmpAllGt(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllGt(arg1, arg2, 0x000F);
return Sse::CmpAllGt(arg1, arg2, 0b0001);
}
AZ_MATH_INLINE bool Vec1::CmpAllGtEq(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllGtEq(arg1, arg2, 0x000F);
return Sse::CmpAllGtEq(arg1, arg2, 0b0001);
}
@@ -397,7 +398,7 @@ namespace AZ
AZ_MATH_INLINE bool Vec1::CmpAllEq(Int32ArgType arg1, Int32ArgType arg2)
{
return Sse::CmpAllEq(arg1, arg2, 0x000F);
return Sse::CmpAllEq(arg1, arg2, 0b0001);
}
@@ -383,31 +383,32 @@ namespace AZ
AZ_MATH_INLINE bool Vec2::CmpAllEq(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllEq(arg1, arg2, 0x00FF);
// Only check the first two bits for Vector2
return Sse::CmpAllEq(arg1, arg2, 0b0011);
}
AZ_MATH_INLINE bool Vec2::CmpAllLt(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllLt(arg1, arg2, 0x00FF);
return Sse::CmpAllLt(arg1, arg2, 0b0011);
}
AZ_MATH_INLINE bool Vec2::CmpAllLtEq(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllLtEq(arg1, arg2, 0x00FF);
return Sse::CmpAllLtEq(arg1, arg2, 0b0011);
}
AZ_MATH_INLINE bool Vec2::CmpAllGt(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllGt(arg1, arg2, 0x00FF);
return Sse::CmpAllGt(arg1, arg2, 0b0011);
}
AZ_MATH_INLINE bool Vec2::CmpAllGtEq(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllGtEq(arg1, arg2, 0x00FF);
return Sse::CmpAllGtEq(arg1, arg2, 0b0011);
}
@@ -419,31 +419,32 @@ namespace AZ
AZ_MATH_INLINE bool Vec3::CmpAllEq(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllEq(arg1, arg2, 0x0FFF);
// Only check the first three bits for Vector3
return Sse::CmpAllEq(arg1, arg2, 0b0111);
}
AZ_MATH_INLINE bool Vec3::CmpAllLt(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllLt(arg1, arg2, 0x0FFF);
return Sse::CmpAllLt(arg1, arg2, 0b0111);
}
AZ_MATH_INLINE bool Vec3::CmpAllLtEq(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllLtEq(arg1, arg2, 0x0FFF);
return Sse::CmpAllLtEq(arg1, arg2, 0b0111);
}
AZ_MATH_INLINE bool Vec3::CmpAllGt(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllGt(arg1, arg2, 0x0FFF);
return Sse::CmpAllGt(arg1, arg2, 0b0111);
}
AZ_MATH_INLINE bool Vec3::CmpAllGtEq(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllGtEq(arg1, arg2, 0x0FFF);
return Sse::CmpAllGtEq(arg1, arg2, 0b0111);
}
@@ -485,7 +486,7 @@ namespace AZ
AZ_MATH_INLINE bool Vec3::CmpAllEq(Int32ArgType arg1, Int32ArgType arg2)
{
return Sse::CmpAllEq(arg1, arg2, 0x0FFF);
return Sse::CmpAllEq(arg1, arg2, 0b0111);
}
@@ -455,31 +455,32 @@ namespace AZ
AZ_MATH_INLINE bool Vec4::CmpAllEq(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllEq(arg1, arg2, 0xFFFF);
// Check the first four bits for Vector4
return Sse::CmpAllEq(arg1, arg2, 0b1111);
}
AZ_MATH_INLINE bool Vec4::CmpAllLt(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllLt(arg1, arg2, 0xFFFF);
return Sse::CmpAllLt(arg1, arg2, 0b1111);
}
AZ_MATH_INLINE bool Vec4::CmpAllLtEq(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllLtEq(arg1, arg2, 0xFFFF);
return Sse::CmpAllLtEq(arg1, arg2, 0b1111);
}
AZ_MATH_INLINE bool Vec4::CmpAllGt(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllGt(arg1, arg2, 0xFFFF);
return Sse::CmpAllGt(arg1, arg2, 0b1111);
}
AZ_MATH_INLINE bool Vec4::CmpAllGtEq(FloatArgType arg1, FloatArgType arg2)
{
return Sse::CmpAllGtEq(arg1, arg2, 0xFFFF);
return Sse::CmpAllGtEq(arg1, arg2, 0b1111);
}
@@ -521,7 +522,7 @@ namespace AZ
AZ_MATH_INLINE bool Vec4::CmpAllEq(Int32ArgType arg1, Int32ArgType arg2)
{
return Sse::CmpAllEq(arg1, arg2, 0xFFFF);
return Sse::CmpAllEq(arg1, arg2, 0b1111);
}
@@ -87,6 +87,8 @@ void ScriptSystemComponent::Activate()
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::AddExtension, "lua");
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::AddExtension, "luac");
AZ::Data::AssetCatalogRequestBus::Broadcast(
&AZ::Data::AssetCatalogRequests::EnableCatalogForAsset, AZ::AzTypeInfo<AZ::ScriptAsset>::Uuid());
if (Data::AssetManager::Instance().IsReady())
{
+1 -1
View File
@@ -59,7 +59,7 @@ namespace AZ::Utils
{
// Fix the size value of the fixed string by calculating the c-string length using char traits
absolutePath.resize_no_construct(AZStd::char_traits<char>::length(absolutePath.data()));
return srcPath;
return absolutePath;
}
return AZStd::nullopt;
@@ -635,6 +635,7 @@ namespace AZ
size_t longestMatch = 0;
size_t bufStringLength = inBuffer.size();
AZStd::string_view longestAlias;
AZStd::string_view longestResolvedAlias;
for (const auto& [alias, resolvedAlias] : m_aliases)
{
@@ -653,6 +654,7 @@ namespace AZ
{
longestMatch = resolvedAlias.size();
longestAlias = alias;
longestResolvedAlias = resolvedAlias;
}
}
}
@@ -661,7 +663,10 @@ namespace AZ
// rearrange the buffer to have
// [alias][old path]
size_t aliasSize = longestAlias.size();
size_t charsToAbsorb = longestMatch;
// If the resolved alias ends in a path separator, do not consume it.
const bool resolvedAliasEndsInPathSeparator = (longestResolvedAlias.ends_with(AZ::IO::PosixPathSeparator) ||
longestResolvedAlias.ends_with(AZ::IO::WindowsPathSeparator));
const size_t charsToAbsorb = resolvedAliasEndsInPathSeparator ? longestMatch - 1 : longestMatch;
size_t remainingData = bufStringLength - charsToAbsorb;
size_t finalStringSize = aliasSize + remainingData;
if (finalStringSize >= outBufferLength)
@@ -9,19 +9,71 @@
#include <AzCore/IO/Path/Path.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Console/IConsole.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/std/tuple.h>
#include <errno.h>
#include <cerrno>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/prctl.h>
#include <unistd.h>
AZ_CVAR(bool, ap_tether_lifetime, true, nullptr, AZ::ConsoleFunctorFlags::Null,
"If enabled, a parent process that launches the AP will terminate the AP on exit");
namespace AzFramework::AssetSystem::Platform
{
void AllowAssetProcessorToForeground()
{}
[[noreturn]] static void LaunchAssetProcessorDirectly(const AZ::IO::FixedMaxPath& assetProcessorPath, AZStd::string_view engineRoot, AZStd::string_view projectPath)
{
AZStd::fixed_vector<const char*, 5> args {
assetProcessorPath.c_str(),
"--start-hidden",
};
// Add the engine path to the launch command if not empty
AZ::IO::FixedMaxPathString engineRootArg;
if (!engineRoot.empty())
{
// No need to quote these paths, this code calls exec directly and
// does not go through shell string interpolation
engineRootArg = AZ::IO::FixedMaxPathString{"--engine-path="} + AZ::IO::FixedMaxPathString{engineRoot};
args.push_back(engineRootArg.data());
}
// Add the active project path to the launch command if not empty
AZ::IO::FixedMaxPathString projectPathArg;
if (!projectPath.empty())
{
projectPathArg = AZ::IO::FixedMaxPathString{"--regset=/Amazon/AzCore/Bootstrap/project_path="} + AZ::IO::FixedMaxPathString{projectPath};
args.push_back(projectPathArg.data());
}
// Make sure this is at the end
args.push_back(nullptr); // argv itself needs to be null-terminated
execv(args[0], const_cast<char**>(args.data()));
// exec* family of functions only return on error
fprintf(stderr, "Asset Processor failed with error: %s\n", strerror(errno));
_exit(1);
}
static pid_t LaunchAssetProcessorDaemonized(const AZ::IO::FixedMaxPath& assetProcessorPath, AZStd::string_view engineRoot, AZStd::string_view projectPath)
{
// detach the child from parent
setsid();
const pid_t secondChildPid = fork();
if (secondChildPid == 0)
{
LaunchAssetProcessorDirectly(assetProcessorPath, engineRoot, projectPath);
}
return secondChildPid;
}
bool LaunchAssetProcessor(AZStd::string_view executableDirectory, AZStd::string_view engineRoot,
AZStd::string_view projectPath)
{
@@ -40,7 +92,8 @@ namespace AzFramework::AssetSystem::Platform
}
}
pid_t firstChildPid = fork();
const pid_t parentPid = getpid();
const pid_t firstChildPid = fork();
if (firstChildPid == 0)
{
// redirect output to dev/null so it doesn't hijack an existing console window
@@ -53,51 +106,33 @@ namespace AzFramework::AssetSystem::Platform
AZ::IO::FileDescriptorRedirector stderrRedirect(STDERR_FILENO);
stderrRedirect.RedirectTo(devNull, mode);
// detach the child from parent
setsid();
pid_t secondChildPid = fork();
if (secondChildPid == 0)
if (ap_tether_lifetime)
{
AZStd::array args {
assetProcessorPath.c_str(), assetProcessorPath.c_str(), "--start-hidden",
static_cast<const char*>(nullptr), static_cast<const char*>(nullptr), static_cast<const char*>(nullptr)
};
int optionalArgPos = 3;
// Add the engine path to the launch command if not empty
AZ::IO::FixedMaxPathString engineRootArg;
if (!engineRoot.empty())
prctl(PR_SET_PDEATHSIG, SIGTERM);
if (getppid() != parentPid)
{
engineRootArg = AZ::IO::FixedMaxPathString::format(R"(--engine-path="%.*s")",
aznumeric_cast<int>(engineRoot.size()), engineRoot.data());
args[optionalArgPos++] = engineRootArg.data();
_exit(1);
}
LaunchAssetProcessorDirectly(assetProcessorPath, engineRoot, projectPath);
}
else
{
const pid_t secondChildPid = LaunchAssetProcessorDaemonized(assetProcessorPath, engineRoot, projectPath);
stdoutRedirect.Reset();
stderrRedirect.Reset();
// Add the active project path to the launch command if not empty
AZ::IO::FixedMaxPathString projectPathArg;
if (!projectPath.empty())
{
projectPathArg = AZ::IO::FixedMaxPathString::format(R"(--regset="/Amazon/AzCore/Bootstrap/project_path=%.*s")",
aznumeric_cast<int>(projectPath.size()), projectPath.data());
args[optionalArgPos++] = projectPathArg.data();
}
AZStd::apply(execl, args);
// exec* family of functions only exit on error
AZ_Error("AssetSystemComponent", false, "Asset Processor failed with error: %s", strerror(errno));
_exit(1);
// exit the transient child with proper return code
int ret = (secondChildPid < 0) ? 1 : 0;
_exit(ret);
}
stdoutRedirect.Reset();
stderrRedirect.Reset();
// exit the transient child with proper return code
int ret = (secondChildPid < 0) ? 1 : 0;
_exit(ret);
}
else if (firstChildPid > 0)
{
if (ap_tether_lifetime)
{
return true;
}
// wait for first child to exit to ensure the second child was started
int status = 0;
pid_t ret = waitpid(firstChildPid, &status, 0);
@@ -106,4 +141,4 @@ namespace AzFramework::AssetSystem::Platform
return false;
}
}
} // namespace AzFramework::AssetSystem::Platform
@@ -102,7 +102,7 @@ namespace AzToolsFramework
AzFramework::AssetCatalogEventBus::Handler::BusDisconnect();
AZ::TickBus::Handler::BusDisconnect();
AssetSystemBus::Handler::BusDisconnect();
m_assetBrowserModel.release();
m_assetBrowserModel.reset();
EntryCache::DestroyInstance();
}
@@ -26,8 +26,12 @@ namespace AzToolsFramework
AZ::Interface<ContainerEntityInterface>::Unregister(this);
}
void ContainerEntitySystemComponent::Reflect([[maybe_unused]] AZ::ReflectContext* context)
void ContainerEntitySystemComponent::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<ContainerEntitySystemComponent, AZ::Component>()->Version(1);
}
}
void ContainerEntitySystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
@@ -47,8 +47,12 @@ namespace AzToolsFramework
AZ::Interface<FocusModeInterface>::Unregister(this);
}
void FocusModeSystemComponent::Reflect([[maybe_unused]] AZ::ReflectContext* context)
void FocusModeSystemComponent::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<FocusModeSystemComponent, AZ::Component>()->Version(1);
}
}
void FocusModeSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
@@ -210,8 +210,8 @@ namespace AzToolsFramework
m_enabled = enabled;
if (!enabled)
{
// Send an internal focus change event to reset our input state to fresh if we're disabled.
HandleFocusChange(nullptr);
// Clear input channels to reset our input state if we're disabled.
ClearInputChannels(nullptr);
}
}
@@ -246,7 +246,7 @@ namespace AzToolsFramework
if (eventType == QEvent::Type::MouseMove)
{
// clear override cursor when moving outside of the viewport
// Clear override cursor when moving outside of the viewport
const auto* mouseEvent = static_cast<const QMouseEvent*>(event);
if (m_overrideCursor && !m_sourceWidget->geometry().contains(m_sourceWidget->mapFromGlobal(mouseEvent->globalPos())))
{
@@ -255,6 +255,13 @@ namespace AzToolsFramework
}
}
// If the application state changes (e.g. we have alt-tabbed or minimized the
// main editor window) then ensure all input channels are cleared
if (eventType == QEvent::ApplicationStateChange)
{
ClearInputChannels(event);
}
// Only accept mouse & key release events that originate from an object that is not our target widget,
// as we don't want to erroneously intercept user input meant for another component.
if (object != m_sourceWidget && eventType != QEvent::Type::KeyRelease && eventType != QEvent::Type::MouseButtonRelease)
@@ -264,9 +271,6 @@ namespace AzToolsFramework
if (eventType == QEvent::FocusIn || eventType == QEvent::FocusOut)
{
// If our focus changes, go ahead and reset all input devices.
HandleFocusChange(event);
// If we focus in on the source widget and the mouse is contained in its
// bounds, refresh the cached cursor position to ensure it is up to date (this
// ensures cursor positions are refreshed correctly with context menu focus changes)
@@ -451,7 +455,7 @@ namespace AzToolsFramework
NotifyUpdateChannelIfNotIdle(cursorZChannel, wheelEvent);
}
void QtEventToAzInputMapper::HandleFocusChange(QEvent* event)
void QtEventToAzInputMapper::ClearInputChannels(QEvent* event)
{
for (auto& channelData : m_channels)
{
@@ -138,8 +138,9 @@ namespace AzToolsFramework
void HandleKeyEvent(QKeyEvent* keyEvent);
// Handles mouse wheel events.
void HandleWheelEvent(QWheelEvent* wheelEvent);
// Handles focus change events.
void HandleFocusChange(QEvent* event);
// Clear all input channels (set all channel states to 'ended').
void ClearInputChannels(QEvent* event);
// Populates m_keyMappings.
void InitializeKeyMappings();
@@ -46,11 +46,10 @@ namespace AzToolsFramework
//! Updates the template links (updating instances) for the given template and triggers propagation on its instances.
//! @param providedPatch The patch to apply to the template.
//! @param templateId The id of the template to update.
//! @param immediate An optional flag whether to apply the patch immediately (needed for Undo/Redos) or wait until next system tick.
//! @param instanceToExclude An optional reference to an instance of the template being updated that should not be refreshes as part of propagation.
//! Defaults to nullopt, which means that all instances will be refreshed.
//! @return True if the template was patched correctly, false if the operation failed.
virtual bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
virtual bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
virtual void ApplyPatchesToInstance(const AZ::EntityId& entityId, PrefabDom& patches, const Instance& instanceToAddPatches) = 0;
@@ -156,7 +156,7 @@ namespace AzToolsFramework
}
}
bool InstanceToTemplatePropagator::PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, bool immediate, InstanceOptionalReference instanceToExclude)
bool InstanceToTemplatePropagator::PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalReference instanceToExclude)
{
PrefabDom& templateDomReference = m_prefabSystemComponentInterface->FindTemplateDom(templateId);
@@ -178,7 +178,7 @@ namespace AzToolsFramework
(result.GetOutcome() != AZ::JsonSerializationResult::Outcomes::PartialSkip),
"Some of the patches were not successfully applied.");
m_prefabSystemComponentInterface->SetTemplateDirtyFlag(templateId, true);
m_prefabSystemComponentInterface->PropagateTemplateChanges(templateId, immediate, instanceToExclude);
m_prefabSystemComponentInterface->PropagateTemplateChanges(templateId, instanceToExclude);
return true;
}
}
@@ -33,7 +33,7 @@ namespace AzToolsFramework
InstanceOptionalReference GetTopMostInstanceInHierarchy(AZ::EntityId entityId) override;
bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
void ApplyPatchesToInstance(const AZ::EntityId& entityId, PrefabDom& patches, const Instance& instanceToAddPatches) override;
@@ -52,7 +52,7 @@ namespace AzToolsFramework
AZ::Interface<InstanceUpdateExecutorInterface>::Unregister(this);
}
void InstanceUpdateExecutor::AddTemplateInstancesToQueue(TemplateId instanceTemplateId, bool immediate, InstanceOptionalReference instanceToExclude)
void InstanceUpdateExecutor::AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalReference instanceToExclude)
{
auto findInstancesResult =
m_templateInstanceMapperInterface->FindInstancesOwnedByTemplate(instanceTemplateId);
@@ -79,11 +79,6 @@ namespace AzToolsFramework
m_instancesUpdateQueue.emplace_back(instance);
}
}
if (immediate)
{
UpdateTemplateInstancesInQueue();
}
}
void InstanceUpdateExecutor::RemoveTemplateInstanceFromQueue(const Instance* instance)
@@ -31,7 +31,7 @@ namespace AzToolsFramework
explicit InstanceUpdateExecutor(int instanceCountToUpdateInBatch = 0);
void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
bool UpdateTemplateInstancesInQueue() override;
virtual void RemoveTemplateInstanceFromQueue(const Instance* instance) override;
@@ -23,7 +23,7 @@ namespace AzToolsFramework
virtual ~InstanceUpdateExecutorInterface() = default;
// Add all Instances of Template with given Id into a queue for updating them later.
virtual void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
virtual void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
// Update Instances in the waiting queue.
virtual bool UpdateTemplateInstancesInQueue() = 0;
@@ -86,6 +86,45 @@ namespace AzToolsFramework::Prefab
return AZ::Success();
}
PrefabFocusOperationResult PrefabFocusHandler::FocusOnParentOfFocusedPrefab(
[[maybe_unused]] AzFramework::EntityContextId entityContextId)
{
// If only one instance is in the hierarchy, this operation is invalid
size_t hierarchySize = m_instanceFocusHierarchy.size();
if (hierarchySize <= 1)
{
return AZ::Failure(
AZStd::string("Prefab Focus Handler: Could not complete FocusOnParentOfFocusedPrefab operation while focusing on the root."));
}
// Retrieve parent of currently focused prefab.
InstanceOptionalReference parentInstance = m_instanceFocusHierarchy[hierarchySize - 2];
// Use container entity of parent Instance for focus operations.
AZ::EntityId entityId = parentInstance->get().GetContainerEntityId();
// Initialize Undo Batch object
ScopedUndoBatch undoBatch("Edit Prefab");
// Clear selection
{
const EntityIdList selectedEntities = EntityIdList{};
auto selectionUndo = aznew SelectionCommand(selectedEntities, "Clear Selection");
selectionUndo->SetParent(undoBatch.GetUndoBatch());
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, selectedEntities);
}
// Edit Prefab
{
auto editUndo = aznew PrefabFocusUndo("Edit Prefab");
editUndo->Capture(entityId);
editUndo->SetParent(undoBatch.GetUndoBatch());
FocusOnPrefabInstanceOwningEntityId(entityId);
}
return AZ::Success();
}
PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index)
{
if (index < 0 || index >= m_instanceFocusHierarchy.size())
@@ -50,6 +50,7 @@ namespace AzToolsFramework::Prefab
// PrefabFocusPublicInterface overrides ...
PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) override;
PrefabFocusOperationResult FocusOnParentOfFocusedPrefab(AzFramework::EntityContextId entityContextId) override;
PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) override;
AZ::EntityId GetFocusedPrefabContainerEntityId(AzFramework::EntityContextId entityContextId) const override;
bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const override;
@@ -30,6 +30,9 @@ namespace AzToolsFramework::Prefab
//! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on.
virtual PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) = 0;
//! Set the focused prefab instance to the parent of the currently focused prefab instance. Supports undo/redo.
virtual PrefabFocusOperationResult FocusOnParentOfFocusedPrefab(AzFramework::EntityContextId entityContextId) = 0;
//! Set the focused prefab instance to the instance at position index of the current path. Supports undo/redo.
//! @param index The index of the instance in the current path that we want the prefab system to focus on.
virtual PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) = 0;
@@ -12,11 +12,12 @@
#include <AzCore/Utils/TypeHash.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityIdMapper.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
@@ -565,6 +566,7 @@ namespace AzToolsFramework
parentId = m_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId);
}
// If the parent entity isn't owned by a prefab instance, bail.
InstanceOptionalReference owningInstanceOfParentEntity = GetOwnerInstanceByEntityId(parentId);
if (!owningInstanceOfParentEntity)
{
@@ -572,6 +574,14 @@ namespace AzToolsFramework
"Cannot add entity because the owning instance of parent entity with id '%llu' could not be found.",
static_cast<AZ::u64>(parentId)));
}
// If the parent entity is a closed container, bail.
if (auto containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get(); !containerEntityInterface->IsContainerOpen(parentId))
{
return AZ::Failure(AZStd::string::format(
"Cannot add entity because the parent entity (id '%llu') is a closed container entity.",
static_cast<AZ::u64>(parentId)));
}
EntityAlias entityAlias = Instance::GenerateEntityAlias();
@@ -1051,7 +1061,7 @@ namespace AzToolsFramework
DuplicateNestedEntitiesInInstance(commonOwningInstance->get(),
entities, instanceDomAfter, duplicatedEntityAndInstanceIds, duplicateEntityAliasMap);
PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity/Instance duplication", false);
PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity/Instance duplication");
command->SetParent(undoBatch.GetUndoBatch());
command->Capture(instanceDomBefore, instanceDomAfter, commonOwningInstance->get().GetTemplateId());
command->Redo();
@@ -1322,7 +1332,7 @@ namespace AzToolsFramework
Prefab::PrefabDom instanceDomAfter;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, parentInstance);
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment", false);
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment");
command->Capture(instanceDomBefore, instanceDomAfter, parentTemplateId);
command->SetParent(undoBatch.GetUndoBatch());
{
@@ -159,10 +159,10 @@ namespace AzToolsFramework
newInstance->SetTemplateId(newTemplateId);
}
}
void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, bool immediate, InstanceOptionalReference instanceToExclude)
void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, InstanceOptionalReference instanceToExclude)
{
UpdatePrefabInstances(templateId, immediate, instanceToExclude);
UpdatePrefabInstances(templateId, instanceToExclude);
auto templateIdToLinkIdsIterator = m_templateToLinkIdsMap.find(templateId);
if (templateIdToLinkIdsIterator != m_templateToLinkIdsMap.end())
@@ -191,9 +191,9 @@ namespace AzToolsFramework
}
}
void PrefabSystemComponent::UpdatePrefabInstances(TemplateId templateId, bool immediate, InstanceOptionalReference instanceToExclude)
void PrefabSystemComponent::UpdatePrefabInstances(TemplateId templateId, InstanceOptionalReference instanceToExclude)
{
m_instanceUpdateExecutor.AddTemplateInstancesToQueue(templateId, immediate, instanceToExclude);
m_instanceUpdateExecutor.AddTemplateInstancesToQueue(templateId, instanceToExclude);
}
void PrefabSystemComponent::UpdateLinkedInstances(AZStd::queue<LinkIds>& linkIdsQueue)
@@ -231,17 +231,16 @@ namespace AzToolsFramework
*/
void UpdatePrefabTemplate(TemplateId templateId, const PrefabDom& updatedDom) override;
void PropagateTemplateChanges(TemplateId templateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
void PropagateTemplateChanges(TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
/**
* Updates all Instances owned by a Template.
*
* @param templateId The id of the Template owning Instances to update.
* @param immediate An optional flag whether to apply the patch immediately (needed for Undo/Redos) or wait until next system tick.
* @param instanceToExclude An optional reference to an instance of the template being updated that should not be refreshes as part of propagation.
* Defaults to nullopt, which means that all instances will be refreshed.
* @param instanceToExclude An optional reference to an instance of the template being updated that should not be refreshed
* as part of propagation.Defaults to nullopt, which means that all instances will be refreshed.
*/
void UpdatePrefabInstances(TemplateId templateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt);
void UpdatePrefabInstances(TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt);
private:
AZ_DISABLE_COPY_MOVE(PrefabSystemComponent);
@@ -67,7 +67,7 @@ namespace AzToolsFramework
virtual PrefabDom& FindTemplateDom(TemplateId templateId) = 0;
virtual void UpdatePrefabTemplate(TemplateId templateId, const PrefabDom& updatedDom) = 0;
virtual void PropagateTemplateChanges(TemplateId templateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
virtual void PropagateTemplateChanges(TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
virtual AZStd::unique_ptr<Instance> InstantiatePrefab(
AZ::IO::PathView filePath, InstanceOptionalReference parent = AZStd::nullopt) = 0;
@@ -17,16 +17,17 @@ namespace AzToolsFramework
{
PrefabUndoBase::PrefabUndoBase(const AZStd::string& undoOperationName)
: UndoSystem::URSequencePoint(undoOperationName)
, m_changed(true)
, m_templateId(InvalidTemplateId)
{
m_instanceToTemplateInterface = AZ::Interface<InstanceToTemplateInterface>::Get();
AZ_Assert(m_instanceToTemplateInterface, "Failed to grab instance to template interface");
}
//PrefabInstanceUndo
PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName, bool useImmediatePropagation)
PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName)
: PrefabUndoBase(undoOperationName)
{
m_useImmediatePropagation = useImmediatePropagation;
}
void PrefabUndoInstance::Capture(
@@ -42,12 +43,12 @@ namespace AzToolsFramework
void PrefabUndoInstance::Undo()
{
m_instanceToTemplateInterface->PatchTemplate(m_undoPatch, m_templateId, m_useImmediatePropagation);
m_instanceToTemplateInterface->PatchTemplate(m_undoPatch, m_templateId);
}
void PrefabUndoInstance::Redo()
{
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId, m_useImmediatePropagation);
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId);
}
@@ -90,7 +91,7 @@ namespace AzToolsFramework
void PrefabUndoEntityUpdate::Undo()
{
[[maybe_unused]] bool isPatchApplicationSuccessful =
m_instanceToTemplateInterface->PatchTemplate(m_undoPatch, m_templateId, true);
m_instanceToTemplateInterface->PatchTemplate(m_undoPatch, m_templateId);
AZ_Error(
"Prefab", isPatchApplicationSuccessful,
@@ -101,7 +102,7 @@ namespace AzToolsFramework
void PrefabUndoEntityUpdate::Redo()
{
[[maybe_unused]] bool isPatchApplicationSuccessful =
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId, true);
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId);
AZ_Error(
"Prefab", isPatchApplicationSuccessful,
@@ -112,7 +113,7 @@ namespace AzToolsFramework
void PrefabUndoEntityUpdate::Redo(InstanceOptionalReference instanceToExclude)
{
[[maybe_unused]] bool isPatchApplicationSuccessful =
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId, false, instanceToExclude);
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId, instanceToExclude);
AZ_Error(
"Prefab", isPatchApplicationSuccessful,
@@ -328,7 +329,7 @@ namespace AzToolsFramework
//propagate the link changes
link->get().UpdateTarget();
m_prefabSystemComponentInterface->PropagateTemplateChanges(link->get().GetTargetTemplateId(), false, instanceToExclude);
m_prefabSystemComponentInterface->PropagateTemplateChanges(link->get().GetTargetTemplateId(), instanceToExclude);
//mark as dirty
m_prefabSystemComponentInterface->SetTemplateDirtyFlag(link->get().GetTargetTemplateId(), true);
@@ -29,15 +29,14 @@ namespace AzToolsFramework
bool Changed() const override { return m_changed; }
protected:
TemplateId m_templateId = InvalidTemplateId;
TemplateId m_templateId;
PrefabDom m_redoPatch;
PrefabDom m_undoPatch;
InstanceToTemplateInterface* m_instanceToTemplateInterface = nullptr;
bool m_changed = true;
bool m_useImmediatePropagation = true;
bool m_changed;
};
//! handles the addition and removal of entities from instances
@@ -45,7 +44,7 @@ namespace AzToolsFramework
: public PrefabUndoBase
{
public:
explicit PrefabUndoInstance(const AZStd::string& undoOperationName, bool useImmediatePropagation = true);
explicit PrefabUndoInstance(const AZStd::string& undoOperationName);
void Capture(
const PrefabDom& initialState,
@@ -23,7 +23,7 @@ namespace AzToolsFramework
PrefabDom instanceDomAfterUpdate;
PrefabDomUtils::StoreInstanceInPrefabDom(instance, instanceDomAfterUpdate);
PrefabUndoInstance* state = aznew Prefab::PrefabUndoInstance(undoMessage, false);
PrefabUndoInstance* state = aznew Prefab::PrefabUndoInstance(undoMessage);
state->Capture(instanceDomBeforeUpdate, instanceDomAfterUpdate, instance.GetTemplateId());
state->SetParent(undoBatch);
state->Redo();
@@ -43,6 +43,7 @@
#include <AzToolsFramework/API/ComponentEntityObjectBus.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserSourceDropBus.h>
#include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
@@ -764,10 +765,21 @@ namespace AzToolsFramework
return canHandleData;
}
bool EntityOutlinerListModel::CanDropMimeDataAssets(const QMimeData* data, Qt::DropAction /*action*/, int /*row*/, int /*column*/, const QModelIndex& /*parent*/) const
bool EntityOutlinerListModel::CanDropMimeDataAssets(
const QMimeData* data,
[[maybe_unused]] Qt::DropAction action,
[[maybe_unused]] int row,
[[maybe_unused]] int column,
const QModelIndex& parent) const
{
using namespace AzToolsFramework;
// Disable dropping assets on closed container entities.
AZ::EntityId parentId = GetEntityFromIndex(parent);
if (auto containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get();
!containerEntityInterface->IsContainerOpen(parentId))
{
return false;
}
if (data->hasFormat(AssetBrowser::AssetBrowserEntry::GetMimeType()))
{
return DecodeAssetMimeData(data);
@@ -788,8 +800,15 @@ namespace AzToolsFramework
return false;
}
// If the parent entity is a closed container, bail.
if (auto containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get();
!containerEntityInterface->IsContainerOpen(assignParentId))
{
return false;
}
// Source Files
if (sourceFiles.size() > 0)
if (!sourceFiles.empty())
{
// Get position (center of viewport). If no viewport is available, (0,0,0) will be used.
AZ::Vector3 viewportCenterPosition = AZ::Vector3::CreateZero();
@@ -943,13 +962,15 @@ namespace AzToolsFramework
{
return false;
}
const int count = rowCount(parent);
AZ::EntityId newParentId = GetEntityFromIndex(parent);
AZ::EntityId beforeEntityId = GetEntityFromIndex(index(row, 0, parent));
AZ::EntityId beforeEntityId = (row >= 0 && row < count) ? GetEntityFromIndex(index(row, 0, parent)) : AZ::EntityId();
EntityIdList topLevelEntityIds;
topLevelEntityIds.reserve(entityIdListContainer.m_entityIds.size());
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::FindTopLevelEntityIdsInactive, entityIdListContainer.m_entityIds, topLevelEntityIds);
if (!ReparentEntities(newParentId, topLevelEntityIds, beforeEntityId))
const auto appendActionForInvalid = newParentId.IsValid() && (row >= count) ? AppendEnd : AppendBeginning;
if (!ReparentEntities(newParentId, topLevelEntityIds, beforeEntityId, appendActionForInvalid))
{
return false;
}
@@ -971,6 +992,12 @@ namespace AzToolsFramework
return false;
}
// If the new parent is a closed container, bail.
if (auto containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get(); !containerEntityInterface->IsContainerOpen(newParentId))
{
return false;
}
// Ignore entities not owned by the editor context. It is assumed that all entities belong
// to the same context since multiple selection doesn't span across views.
for (const AZ::EntityId& entityId : selectedEntityIds)
@@ -1046,7 +1073,7 @@ namespace AzToolsFramework
return true;
}
bool EntityOutlinerListModel::ReparentEntities(const AZ::EntityId& newParentId, const EntityIdList &selectedEntityIds, const AZ::EntityId& beforeEntityId)
bool EntityOutlinerListModel::ReparentEntities(const AZ::EntityId& newParentId, const EntityIdList &selectedEntityIds, const AZ::EntityId& beforeEntityId, ReparentForInvalid forInvalid)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
if (!CanReparentEntities(newParentId, selectedEntityIds))
@@ -1056,10 +1083,18 @@ namespace AzToolsFramework
m_isFilterDirty = true;
ScopedUndoBatch undo("Reparent Entities");
//capture child entity order before re-parent operation, which will automatically add order info if not present
EntityOrderArray entityOrderArray = GetEntityChildOrder(newParentId);
//search for the insertion entity in the order array
const auto beforeEntityItr = AZStd::find(entityOrderArray.begin(), entityOrderArray.end(), beforeEntityId);
const bool hasInvalidIndex = beforeEntityItr == entityOrderArray.end();
if (hasInvalidIndex && forInvalid == None)
{
return false;
}
ScopedUndoBatch undo("Reparent Entities");
// The new parent is dirty due to sort change(s)
undo.MarkEntityDirty(GetEntityIdForSortInfo(newParentId));
@@ -1088,9 +1123,7 @@ namespace AzToolsFramework
}
}
//search for the insertion entity in the order array
auto beforeEntityItr = AZStd::find(entityOrderArray.begin(), entityOrderArray.end(), beforeEntityId);
//replace order info matching selection with bad values rather than remove to preserve layout
for (auto& id : entityOrderArray)
{
@@ -1100,17 +1133,25 @@ namespace AzToolsFramework
}
}
if (newParentId.IsValid())
//if adding to a valid parent entity, insert at the found entity location or at the head/tail depending on placeAtTail flag
if (hasInvalidIndex)
{
//if adding to a valid parent entity, insert at the found entity location or at the head of the container
auto insertItr = beforeEntityItr != entityOrderArray.end() ? beforeEntityItr : entityOrderArray.begin();
entityOrderArray.insert(insertItr, processedEntityIds.begin(), processedEntityIds.end());
}
else
switch(forInvalid)
{
case AppendEnd:
entityOrderArray.insert(entityOrderArray.end(), processedEntityIds.begin(), processedEntityIds.end());
break;
case AppendBeginning:
entityOrderArray.insert(entityOrderArray.begin(), processedEntityIds.begin(), processedEntityIds.end());
break;
default:
AZ_Assert(false, "Unexpected type for ReparentForInvalid");
break;
}
}
else
{
//if adding to an invalid parent entity (the root), insert at the found entity location or at the tail of the container
auto insertItr = beforeEntityItr != entityOrderArray.end() ? beforeEntityItr : entityOrderArray.end();
entityOrderArray.insert(insertItr, processedEntityIds.begin(), processedEntityIds.end());
entityOrderArray.insert(beforeEntityItr, processedEntityIds.begin(), processedEntityIds.end());
}
//remove placeholder entity ids
@@ -72,6 +72,13 @@ namespace AzToolsFramework
ColumnCount //!< Total number of columns
};
enum ReparentForInvalid
{
None, //!< For an invalid location the entity does not change location
AppendEnd, //!< Append Item to end of target parent list
AppendBeginning, //!< Append Item to the beginning of target parent list
};
// Note: the ColumnSortIndex column isn't shown, hence the -1 and the need for a separate counter.
// A wrong column count number causes refresh issues and hover mismatch on model update.
static const int VisibleColumnCount = ColumnCount - 1;
@@ -162,7 +169,7 @@ namespace AzToolsFramework
// Buffer Processing Slots - These are called using single-shot events when the buffers begin to fill.
bool CanReparentEntities(const AZ::EntityId& newParentId, const EntityIdList& selectedEntityIds) const;
bool ReparentEntities(const AZ::EntityId& newParentId, const EntityIdList& selectedEntityIds, const AZ::EntityId& beforeEntityId = AZ::EntityId());
bool ReparentEntities(const AZ::EntityId& newParentId, const EntityIdList& selectedEntityIds, const AZ::EntityId& beforeEntityId = AZ::EntityId(), ReparentForInvalid forInvalid = None);
//! Use the current filter setting and re-evaluate the filter.
void InvalidateFilter();
@@ -8,6 +8,7 @@
#include <AzToolsFramework/UI/Prefab/LevelRootUiHandler.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx>
@@ -92,4 +93,16 @@ namespace AzToolsFramework
painter->drawLine(rect.bottomLeft(), rect.bottomRight());
painter->restore();
}
bool LevelRootUiHandler::OnEntityDoubleClick(AZ::EntityId entityId) const
{
if (auto prefabFocusPublicInterface = AZ::Interface<Prefab::PrefabFocusPublicInterface>::Get();
!prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
{
prefabFocusPublicInterface->FocusOnOwningPrefab(entityId);
}
// Don't propagate event.
return true;
}
}
@@ -33,6 +33,7 @@ namespace AzToolsFramework
bool CanToggleLockVisibility(AZ::EntityId entityId) const override;
bool CanRename(AZ::EntityId entityId) const override;
void PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
bool OnEntityDoubleClick(AZ::EntityId entityId) const override;
private:
Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr;
@@ -35,6 +35,7 @@
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiInterface.h>
#include <AzToolsFramework/UI/Prefab/PrefabIntegrationInterface.h>
#include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
#include <AzToolsFramework/Viewport/ActionBus.h>
#include <AzQtComponents/Components/Widgets/CheckBox.h>
#include <AzQtComponents/Components/FlowLayout.h>
@@ -61,6 +62,8 @@ namespace AzToolsFramework
{
namespace Prefab
{
AzFramework::EntityContextId PrefabIntegrationManager::s_editorEntityContextId = AzFramework::EntityContextId::CreateNull();
ContainerEntityInterface* PrefabIntegrationManager::s_containerEntityInterface = nullptr;
EditorEntityUiInterface* PrefabIntegrationManager::s_editorEntityUiInterface = nullptr;
PrefabFocusPublicInterface* PrefabIntegrationManager::s_prefabFocusPublicInterface = nullptr;
@@ -136,6 +139,9 @@ namespace AzToolsFramework
return;
}
// Get EditorEntityContextId
EditorEntityContextRequestBus::BroadcastResult(s_editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
// Initialize Editor functionality for the Prefab Focus Handler
auto prefabFocusInterface = AZ::Interface<PrefabFocusInterface>::Get();
prefabFocusInterface->InitializeEditorInterfaces();
@@ -145,10 +151,14 @@ namespace AzToolsFramework
PrefabInstanceContainerNotificationBus::Handler::BusConnect();
AZ::Interface<PrefabIntegrationInterface>::Register(this);
AssetBrowser::AssetBrowserSourceDropBus::Handler::BusConnect(s_prefabFileExtension);
InitializeShortcuts();
}
PrefabIntegrationManager::~PrefabIntegrationManager()
{
UninitializeShortcuts();
AssetBrowser::AssetBrowserSourceDropBus::Handler::BusDisconnect();
AZ::Interface<PrefabIntegrationInterface>::Unregister(this);
PrefabInstanceContainerNotificationBus::Handler::BusDisconnect();
@@ -161,6 +171,74 @@ namespace AzToolsFramework
PrefabUserSettings::Reflect(context);
}
void PrefabIntegrationManager::InitializeShortcuts()
{
// Open/Edit Prefab (+)
// We also support = to enable easier editing on compact US keyboards.
{
m_actions.emplace_back(AZStd::make_unique<QAction>(nullptr));
m_actions.back()->setShortcuts({ QKeySequence(Qt::Key_Plus), QKeySequence(Qt::Key_Equal) });
m_actions.back()->setText("Open/Edit Prefab");
m_actions.back()->setStatusTip("Edit the prefab in focus mode.");
QObject::connect(
m_actions.back().get(), &QAction::triggered, m_actions.back().get(),
[]
{
AzToolsFramework::EntityIdList selectedEntities;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
selectedEntities, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities);
if (selectedEntities.size() != 1)
{
return;
}
AZ::EntityId selectedEntity = selectedEntities[0];
if (!s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity))
{
return;
}
if (!s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity))
{
ContextMenu_EditPrefab(selectedEntity);
}
});
EditorActionRequestBus::Broadcast(
&EditorActionRequests::AddActionViaBusCrc, AZ_CRC_CE("com.o3de.action.editortransform.prefabopen"),
m_actions.back().get());
}
// Close Prefab (-)
{
m_actions.emplace_back(AZStd::make_unique<QAction>(nullptr));
m_actions.back()->setShortcuts({ QKeySequence(Qt::Key_Minus) });
m_actions.back()->setText("Close Prefab");
m_actions.back()->setStatusTip("Close focus mode for this prefab and move one level up.");
QObject::connect(
m_actions.back().get(), &QAction::triggered, m_actions.back().get(),
[]
{
ContextMenu_ClosePrefab();
});
EditorActionRequestBus::Broadcast(
&EditorActionRequests::AddActionViaBusCrc, AZ_CRC_CE("com.o3de.action.editortransform.prefabclose"),
m_actions.back().get());
}
}
void PrefabIntegrationManager::UninitializeShortcuts()
{
m_actions.clear();
}
int PrefabIntegrationManager::GetMenuPosition() const
{
return aznumeric_cast<int>(EditorContextMenuOrdering::MIDDLE);
@@ -181,16 +259,13 @@ namespace AzToolsFramework
AzFramework::ApplicationRequests::Bus::BroadcastResult(
prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
auto editorEntityContextId = AzFramework::EntityContextId::CreateNull();
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
// Create Prefab
{
if (!selectedEntities.empty())
{
// Hide if the only selected entity is the Focused Instance Container
if (selectedEntities.size() > 1 ||
selectedEntities[0] != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId))
selectedEntities[0] != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(s_editorEntityContextId))
{
bool layerInSelection = false;
@@ -254,17 +329,30 @@ namespace AzToolsFramework
if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity))
{
// Edit Prefab
if (!s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity))
{
QAction* editAction = menu->addAction(QObject::tr("Edit Prefab"));
// Edit Prefab
QAction* editAction = menu->addAction(QObject::tr("Open/Edit Prefab"));
editAction->setShortcut(QKeySequence(Qt::Key_Plus));
editAction->setToolTip(QObject::tr("Edit the prefab in focus mode."));
QObject::connect(editAction, &QAction::triggered, editAction, [selectedEntity] {
ContextMenu_EditPrefab(selectedEntity);
});
}
else
{
// Close Prefab
QAction* closeAction = menu->addAction(QObject::tr("Close Prefab"));
closeAction->setShortcut(QKeySequence(Qt::Key_Minus));
closeAction->setToolTip(QObject::tr("Close focus mode for this prefab and move one level up."));
itemWasShown = true;
QObject::connect(
closeAction, &QAction::triggered, closeAction,
[]
{
ContextMenu_ClosePrefab();
});
}
// Save Prefab
@@ -279,9 +367,9 @@ namespace AzToolsFramework
QObject::connect(saveAction, &QAction::triggered, saveAction, [selectedEntity] {
ContextMenu_SavePrefab(selectedEntity);
});
itemWasShown = true;
}
itemWasShown = true;
}
}
}
@@ -295,7 +383,8 @@ namespace AzToolsFramework
QObject::connect(deleteAction, &QAction::triggered, deleteAction, [] { ContextMenu_DeleteSelected(); });
if (selectedEntities.empty() ||
(selectedEntities.size() == 1 && selectedEntities[0] == s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId)))
(selectedEntities.size() == 1 &&
selectedEntities[0] == s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(s_editorEntityContextId)))
{
deleteAction->setDisabled(true);
}
@@ -306,7 +395,7 @@ namespace AzToolsFramework
AZ::EntityId selectedEntityId = selectedEntities[0];
if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntityId) &&
selectedEntityId != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId))
selectedEntityId != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(s_editorEntityContextId))
{
QAction* detachPrefabAction = menu->addAction(QObject::tr("Detach Prefab..."));
QObject::connect(
@@ -343,12 +432,9 @@ namespace AzToolsFramework
const AZStd::string prefabFilesPath = "@projectroot@/Prefabs";
// Remove focused instance container entity if it's part of the list
auto editorEntityContextId = AzFramework::EntityContextId::CreateNull();
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
auto focusedContainerIter = AZStd::find(
selectedEntities.begin(), selectedEntities.end(),
s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId));
s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(s_editorEntityContextId));
if (focusedContainerIter != selectedEntities.end())
{
selectedEntities.erase(focusedContainerIter);
@@ -500,6 +586,11 @@ namespace AzToolsFramework
}
}
void PrefabIntegrationManager::ContextMenu_ClosePrefab()
{
s_prefabFocusPublicInterface->FocusOnParentOfFocusedPrefab(s_editorEntityContextId);
}
void PrefabIntegrationManager::ContextMenu_EditPrefab(AZ::EntityId containerEntity)
{
s_prefabFocusPublicInterface->FocusOnOwningPrefab(containerEntity);
@@ -96,11 +96,16 @@ namespace AzToolsFramework
static void ContextMenu_CreatePrefab(AzToolsFramework::EntityIdList selectedEntities);
static void ContextMenu_InstantiatePrefab();
static void ContextMenu_InstantiateProceduralPrefab();
static void ContextMenu_ClosePrefab();
static void ContextMenu_EditPrefab(AZ::EntityId containerEntity);
static void ContextMenu_SavePrefab(AZ::EntityId containerEntity);
static void ContextMenu_DeleteSelected();
static void ContextMenu_DetachPrefab(AZ::EntityId containerEntity);
// Shortcut setup handlers
void InitializeShortcuts();
void UninitializeShortcuts();
// Prompt and resolve dialogs
static bool QueryUserForPrefabSaveLocation(
const AZStd::string& suggestedName, const char* initialTargetDirectory, AZ::u32 prefabUserSettingsId, QWidget* activeWindow,
@@ -140,7 +145,10 @@ namespace AzToolsFramework
AZStd::unique_ptr<QDialog> ConstructSavePrefabDialog(TemplateId templateId, bool useSaveAllPrefabsPreference);
void SavePrefabsInDialog(QDialog* unsavedPrefabsDialog);
AZStd::vector<AZStd::unique_ptr<QAction>> m_actions;
static const AZStd::string s_prefabFileExtension;
static AzFramework::EntityContextId s_editorEntityContextId;
static ContainerEntityInterface* s_containerEntityInterface;
static EditorEntityUiInterface* s_editorEntityUiInterface;
@@ -21,6 +21,8 @@
namespace AzToolsFramework
{
AzFramework::EntityContextId PrefabUiHandler::s_editorEntityContextId = AzFramework::EntityContextId::CreateNull();
const QColor PrefabUiHandler::m_backgroundColor = QColor("#444444");
const QColor PrefabUiHandler::m_backgroundHoverColor = QColor("#5A5A5A");
const QColor PrefabUiHandler::m_backgroundSelectedColor = QColor("#656565");
@@ -47,6 +49,9 @@ namespace AzToolsFramework
AZ_Assert(false, "PrefabUiHandler - could not get PrefabFocusPublicInterface on PrefabUiHandler construction.");
return;
}
// Get EditorEntityContextId
EditorEntityContextRequestBus::BroadcastResult(s_editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
}
QString PrefabUiHandler::GenerateItemInfoString(AZ::EntityId entityId) const
@@ -425,19 +430,23 @@ namespace AzToolsFramework
if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
{
auto editorEntityContextId = AzFramework::EntityContextId::CreateNull();
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
// Go one level up.
int length = m_prefabFocusPublicInterface->GetPrefabFocusPathLength(editorEntityContextId);
m_prefabFocusPublicInterface->FocusOnPathIndex(editorEntityContextId, length - 2);
// Close this prefab and focus on the parent
m_prefabFocusPublicInterface->FocusOnParentOfFocusedPrefab(s_editorEntityContextId);
}
}
bool PrefabUiHandler::OnEntityDoubleClick(AZ::EntityId entityId) const
{
// Focus on this prefab
m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId);
if (!m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
{
// Focus on this prefab
m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId);
}
else
{
// Close this prefab and focus on the parent
m_prefabFocusPublicInterface->FocusOnParentOfFocusedPrefab(s_editorEntityContextId);
}
// Don't propagate event.
return true;
@@ -10,6 +10,8 @@
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.h>
#include <AzFramework/Entity/EntityContextBus.h>
namespace AzToolsFramework
{
@@ -49,6 +51,8 @@ namespace AzToolsFramework
static QModelIndex GetLastVisibleChild(const QModelIndex& parent);
static QModelIndex Internal_GetLastVisibleChild(const QAbstractItemModel* model, const QModelIndex& index);
static AzFramework::EntityContextId s_editorEntityContextId;
static constexpr int m_prefabCapsuleRadius = 6;
static constexpr int m_prefabBorderThickness = 2;
static const QColor m_backgroundColor;
@@ -59,12 +59,11 @@ namespace AzToolsFramework::Prefab
connect(m_backButton, &QToolButton::clicked, this,
[&]()
{
if (int length = m_prefabFocusPublicInterface->GetPrefabFocusPathLength(m_editorEntityContextId); length > 1)
{
m_prefabFocusPublicInterface->FocusOnPathIndex(m_editorEntityContextId, length - 2);
}
m_prefabFocusPublicInterface->FocusOnParentOfFocusedPrefab(m_editorEntityContextId);
}
);
m_backButton->setToolTip("Up one level (-)");
}
void PrefabViewportFocusPathHandler::OnPrefabFocusChanged()
@@ -80,8 +80,9 @@ namespace AzToolsFramework
private:
AZStd::string m_message; //!< Message to display for fading text.
float m_opacity = 1.0f; //!< The opacity of the invalid click message.
AzFramework::ScreenPoint m_invalidClickPosition; //!< The position to display the invalid click message.
float m_opacity = 0.0f; //!< The opacity of the invalid click message.
//! The position to display the invalid click message.
AzFramework::ScreenPoint m_invalidClickPosition = AzFramework::ScreenPoint(0, 0);
};
//! Interface to begin invalid click feedback (will run all added InvalidClick behaviors).
@@ -28,9 +28,12 @@ namespace UnitTest
return true;
}
void BoundsTestComponent::Reflect([[maybe_unused]] AZ::ReflectContext* context)
void BoundsTestComponent::Reflect(AZ::ReflectContext* context)
{
// noop
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<BoundsTestComponent, EditorComponentBase>()->Version(1);
}
}
void BoundsTestComponent::Activate()
@@ -42,5 +42,4 @@ namespace UnitTest
AZ::Aabb GetWorldBounds() override;
AZ::Aabb GetLocalBounds() override;
};
} // namespace UnitTest
@@ -266,7 +266,7 @@ namespace O3DE::ProjectManager
PythonBindingsInterface::Get()->AddProject(projectInfo.m_path);
#ifdef TEMPLATE_GEM_CONFIGURATION_ENABLED
const GemCatalogScreen::EnableDisableGemsResult gemResult = m_gemCatalogScreen->EnableDisableGemsForProject(m_projectInfo.m_path);
const GemCatalogScreen::EnableDisableGemsResult gemResult = m_gemCatalogScreen->EnableDisableGemsForProject(projectInfo.m_path);
if (gemResult == GemCatalogScreen::EnableDisableGemsResult::Failed)
{
QMessageBox::critical(this, tr("Failed to configure gems"), tr("Failed to configure gems for template."));
@@ -63,9 +63,6 @@ namespace O3DE::ProjectManager
QPushButton* m_secondaryButton = nullptr;
#endif // TEMPLATE_GEM_CONFIGURATION_ENABLED
QString m_projectTemplatePath;
ProjectInfo m_projectInfo;
NewProjectSettingsScreen* m_newProjectSettingsScreen = nullptr;
GemCatalogScreen* m_gemCatalogScreen = nullptr;
GemRepoScreen* m_gemRepoScreen = nullptr;
@@ -32,10 +32,10 @@ namespace O3DE::ProjectManager
: ScreenWidget(parent)
{
m_gemModel = new GemModel(this);
m_proxModel = new GemSortFilterProxyModel(m_gemModel, this);
m_proxyModel = new GemSortFilterProxyModel(m_gemModel, this);
// default to sort by gem name
m_proxModel->setSortRole(GemModel::RoleName);
m_proxyModel->setSortRole(GemModel::RoleName);
QVBoxLayout* vLayout = new QVBoxLayout();
vLayout->setMargin(0);
@@ -44,7 +44,7 @@ namespace O3DE::ProjectManager
m_downloadController = new DownloadController();
m_headerWidget = new GemCatalogHeaderWidget(m_gemModel, m_proxModel, m_downloadController);
m_headerWidget = new GemCatalogHeaderWidget(m_gemModel, m_proxyModel, m_downloadController);
vLayout->addWidget(m_headerWidget);
connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged);
@@ -55,10 +55,12 @@ namespace O3DE::ProjectManager
hLayout->setMargin(0);
vLayout->addLayout(hLayout);
m_gemListView = new GemListView(m_proxModel, m_proxModel->GetSelectionModel(), this);
m_gemListView = new GemListView(m_proxyModel, m_proxyModel->GetSelectionModel(), this);
m_gemInspector = new GemInspector(m_gemModel, this);
m_gemInspector->setFixedWidth(240);
connect(m_gemInspector, &GemInspector::TagClicked, this, &GemCatalogScreen::SelectGem);
QWidget* filterWidget = new QWidget(this);
filterWidget->setFixedWidth(240);
m_filterWidgetLayout = new QVBoxLayout();
@@ -66,7 +68,7 @@ namespace O3DE::ProjectManager
m_filterWidgetLayout->setSpacing(0);
filterWidget->setLayout(m_filterWidgetLayout);
GemListHeaderWidget* listHeaderWidget = new GemListHeaderWidget(m_proxModel);
GemListHeaderWidget* listHeaderWidget = new GemListHeaderWidget(m_proxyModel);
QVBoxLayout* middleVLayout = new QVBoxLayout();
middleVLayout->setMargin(0);
@@ -89,17 +91,18 @@ namespace O3DE::ProjectManager
m_gemsToRegisterWithProject.clear();
FillModel(projectPath);
if (m_filterWidget)
{
m_filterWidget->hide();
m_filterWidget->deleteLater();
}
m_proxModel->ResetFilters();
m_proxyModel->ResetFilters();
m_proxModel->sort(/*column=*/0);
m_filterWidget = new GemFilterWidget(m_proxModel);
m_filterWidgetLayout->addWidget(m_filterWidget);
if (m_filterWidget)
{
m_filterWidget->ResetAllFilters();
}
else
{
m_filterWidget = new GemFilterWidget(m_proxyModel);
m_filterWidgetLayout->addWidget(m_filterWidget);
}
m_headerWidget->ReinitForProject();
@@ -253,6 +256,20 @@ namespace O3DE::ProjectManager
}
}
void GemCatalogScreen::SelectGem(const QString& gemName)
{
QModelIndex modelIndex = m_gemModel->FindIndexByNameString(gemName);
if (!m_proxyModel->filterAcceptsRow(modelIndex.row(), QModelIndex()))
{
m_proxyModel->ResetFilters();
m_filterWidget->ResetAllFilters();
}
QModelIndex proxyIndex = m_proxyModel->mapFromSource(modelIndex);
m_proxyModel->GetSelectionModel()->select(proxyIndex, QItemSelectionModel::ClearAndSelect);
m_gemListView->scrollTo(proxyIndex);
}
void GemCatalogScreen::hideEvent(QHideEvent* event)
{
ScreenWidget::hideEvent(event);
@@ -49,6 +49,7 @@ namespace O3DE::ProjectManager
public slots:
void OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies);
void OnAddGemClicked();
void SelectGem(const QString& gemName);
protected:
void hideEvent(QHideEvent* event) override;
@@ -69,7 +70,7 @@ namespace O3DE::ProjectManager
GemInspector* m_gemInspector = nullptr;
GemModel* m_gemModel = nullptr;
GemCatalogHeaderWidget* m_headerWidget = nullptr;
GemSortFilterProxyModel* m_proxModel = nullptr;
GemSortFilterProxyModel* m_proxyModel = nullptr;
QVBoxLayout* m_filterWidgetLayout = nullptr;
GemFilterWidget* m_filterWidget = nullptr;
DownloadController* m_downloadController = nullptr;
@@ -213,11 +213,99 @@ namespace O3DE::ProjectManager
m_filterLayout->setContentsMargins(0, 0, 0, 0);
filterSection->setLayout(m_filterLayout);
ResetAllFilters();
}
void GemFilterWidget::ResetAllFilters()
{
ResetGemStatusFilter();
AddGemOriginFilter();
AddTypeFilter();
AddPlatformFilter();
AddFeatureFilter();
ResetGemOriginFilter();
ResetTypeFilter();
ResetPlatformFilter();
ResetFeatureFilter();
}
void GemFilterWidget::ResetFilterWidget(
FilterCategoryWidget*& filterPtr,
const QString& filterName,
const QVector<QString>& elementNames,
const QVector<int>& elementCounts,
int defaultShowCount)
{
bool wasCollapsed = false;
if (filterPtr)
{
wasCollapsed = filterPtr->IsCollapsed();
}
FilterCategoryWidget* filterWidget = new FilterCategoryWidget(
filterName, elementNames, elementCounts, /*showAllLessButton=*/defaultShowCount != 4, /*collapsed*/ wasCollapsed,
/*defaultShowCount=*/defaultShowCount);
if (filterPtr)
{
m_filterLayout->replaceWidget(filterPtr, filterWidget);
}
else
{
m_filterLayout->addWidget(filterWidget);
}
filterPtr->deleteLater();
filterPtr = filterWidget;
}
template<typename filterType, typename filterFlagsType>
void GemFilterWidget::ResetSimpleOrFilter(
FilterCategoryWidget*& filterPtr,
const QString& filterName,
int numFilterElements,
bool (*filterMatcher)(GemModel*, filterType, int),
QString (*typeStringGetter)(filterType),
filterFlagsType (GemSortFilterProxyModel::*filterFlagsGetter)() const,
void (GemSortFilterProxyModel::*filterFlagsSetter)(const filterFlagsType&))
{
QVector<QString> elementNames;
QVector<int> elementCounts;
const int numGems = m_gemModel->rowCount();
for (int filterIndex = 0; filterIndex < numFilterElements; ++filterIndex)
{
const filterType gemFilterToBeCounted = static_cast<filterType>(1 << filterIndex);
int gemFilterCount = 0;
for (int gemIndex = 0; gemIndex < numGems; ++gemIndex)
{
// If filter matches increment filter count
gemFilterCount += filterMatcher(m_gemModel, gemFilterToBeCounted, gemIndex);
}
elementNames.push_back(typeStringGetter(gemFilterToBeCounted));
elementCounts.push_back(gemFilterCount);
}
// Replace existing filter and delete old one
ResetFilterWidget(filterPtr, filterName, elementNames, elementCounts);
const QList<QAbstractButton*> buttons = filterPtr->GetButtonGroup()->buttons();
for (int i = 0; i < buttons.size(); ++i)
{
const filterType gemFilter = static_cast<filterType>(1 << i);
QAbstractButton* button = buttons[i];
connect(
button, &QAbstractButton::toggled, this,
[=](bool checked)
{
filterFlagsType gemFilters = (m_filterProxyModel->*filterFlagsGetter)();
if (checked)
{
gemFilters |= gemFilter;
}
else
{
gemFilters &= ~gemFilter;
}
(m_filterProxyModel->*filterFlagsSetter)(gemFilters);
});
}
}
void GemFilterWidget::ResetGemStatusFilter()
@@ -241,25 +329,7 @@ namespace O3DE::ProjectManager
elementNames.push_back(GemSortFilterProxyModel::GetGemActiveString(GemSortFilterProxyModel::GemActive::Inactive));
elementCounts.push_back(totalGems - enabledGemTotal);
bool wasCollapsed = false;
if (m_statusFilter)
{
wasCollapsed = m_statusFilter->IsCollapsed();
}
FilterCategoryWidget* filterWidget =
new FilterCategoryWidget("Status", elementNames, elementCounts, /*showAllLessButton=*/false, /*collapsed*/wasCollapsed);
if (m_statusFilter)
{
m_filterLayout->replaceWidget(m_statusFilter, filterWidget);
}
else
{
m_filterLayout->addWidget(filterWidget);
}
m_statusFilter->deleteLater();
m_statusFilter = filterWidget;
ResetFilterWidget(m_statusFilter, "Status", elementNames, elementCounts);
const QList<QAbstractButton*> buttons = m_statusFilter->GetButtonGroup()->buttons();
@@ -317,157 +387,42 @@ namespace O3DE::ProjectManager
connect(activeButton, &QAbstractButton::toggled, this, updateGemActive);
}
void GemFilterWidget::AddGemOriginFilter()
void GemFilterWidget::ResetGemOriginFilter()
{
QVector<QString> elementNames;
QVector<int> elementCounts;
const int numGems = m_gemModel->rowCount();
for (int originIndex = 0; originIndex < GemInfo::NumGemOrigins; ++originIndex)
{
const GemInfo::GemOrigin gemOriginToBeCounted = static_cast<GemInfo::GemOrigin>(1 << originIndex);
int gemOriginCount = 0;
for (int gemIndex = 0; gemIndex < numGems; ++gemIndex)
ResetSimpleOrFilter<GemInfo::GemOrigin, GemInfo::GemOrigins>
(
m_originFilter, "Provider", GemInfo::NumGemOrigins,
[](GemModel* gemModel, GemInfo::GemOrigin origin, int gemIndex)
{
const GemInfo::GemOrigin gemOrigin = m_gemModel->GetGemOrigin(m_gemModel->index(gemIndex, 0));
// Is the gem of the given origin?
if (gemOriginToBeCounted == gemOrigin)
{
gemOriginCount++;
}
}
elementNames.push_back(GemInfo::GetGemOriginString(gemOriginToBeCounted));
elementCounts.push_back(gemOriginCount);
}
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Provider", elementNames, elementCounts, /*showAllLessButton=*/false);
m_filterLayout->addWidget(filterWidget);
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
for (int i = 0; i < buttons.size(); ++i)
{
const GemInfo::GemOrigin gemOrigin = static_cast<GemInfo::GemOrigin>(1 << i);
QAbstractButton* button = buttons[i];
connect(button, &QAbstractButton::toggled, this, [=](bool checked)
{
GemInfo::GemOrigins gemOrigins = m_filterProxyModel->GetGemOrigins();
if (checked)
{
gemOrigins |= gemOrigin;
}
else
{
gemOrigins &= ~gemOrigin;
}
m_filterProxyModel->SetGemOrigins(gemOrigins);
});
}
return origin == gemModel->GetGemOrigin(gemModel->index(gemIndex, 0));
},
&GemInfo::GetGemOriginString, &GemSortFilterProxyModel::GetGemOrigins, &GemSortFilterProxyModel::SetGemOrigins
);
}
void GemFilterWidget::AddTypeFilter()
void GemFilterWidget::ResetTypeFilter()
{
QVector<QString> elementNames;
QVector<int> elementCounts;
const int numGems = m_gemModel->rowCount();
for (int typeIndex = 0; typeIndex < GemInfo::NumTypes; ++typeIndex)
{
const GemInfo::Type type = static_cast<GemInfo::Type>(1 << typeIndex);
int typeGemCount = 0;
for (int gemIndex = 0; gemIndex < numGems; ++gemIndex)
ResetSimpleOrFilter<GemInfo::Type, GemInfo::Types>(
m_typeFilter, "Type", GemInfo::NumTypes,
[](GemModel* gemModel, GemInfo::Type type, int gemIndex)
{
const GemInfo::Types types = m_gemModel->GetTypes(m_gemModel->index(gemIndex, 0));
// Is type (Asset, Code, Tool) part of the gem?
if (types & type)
{
typeGemCount++;
}
}
elementNames.push_back(GemInfo::GetTypeString(type));
elementCounts.push_back(typeGemCount);
}
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Type", elementNames, elementCounts, /*showAllLessButton=*/false);
m_filterLayout->addWidget(filterWidget);
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
for (int i = 0; i < buttons.size(); ++i)
{
const GemInfo::Type type = static_cast<GemInfo::Type>(1 << i);
QAbstractButton* button = buttons[i];
connect(button, &QAbstractButton::toggled, this, [=](bool checked)
{
GemInfo::Types types = m_filterProxyModel->GetTypes();
if (checked)
{
types |= type;
}
else
{
types &= ~type;
}
m_filterProxyModel->SetTypes(types);
});
}
return static_cast<bool>(type & gemModel->GetTypes(gemModel->index(gemIndex, 0)));
},
&GemInfo::GetTypeString, &GemSortFilterProxyModel::GetTypes, &GemSortFilterProxyModel::SetTypes);
}
void GemFilterWidget::AddPlatformFilter()
void GemFilterWidget::ResetPlatformFilter()
{
QVector<QString> elementNames;
QVector<int> elementCounts;
const int numGems = m_gemModel->rowCount();
for (int platformIndex = 0; platformIndex < GemInfo::NumPlatforms; ++platformIndex)
{
const GemInfo::Platform platform = static_cast<GemInfo::Platform>(1 << platformIndex);
int platformGemCount = 0;
for (int gemIndex = 0; gemIndex < numGems; ++gemIndex)
ResetSimpleOrFilter<GemInfo::Platform, GemInfo::Platforms>(
m_platformFilter, "Supported Platforms", GemInfo::NumPlatforms,
[](GemModel* gemModel, GemInfo::Platform platform, int gemIndex)
{
const GemInfo::Platforms platforms = m_gemModel->GetPlatforms(m_gemModel->index(gemIndex, 0));
// Is platform supported?
if (platforms & platform)
{
platformGemCount++;
}
}
elementNames.push_back(GemInfo::GetPlatformString(platform));
elementCounts.push_back(platformGemCount);
}
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Supported Platforms", elementNames, elementCounts, /*showAllLessButton=*/false);
m_filterLayout->addWidget(filterWidget);
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
for (int i = 0; i < buttons.size(); ++i)
{
const GemInfo::Platform platform = static_cast<GemInfo::Platform>(1 << i);
QAbstractButton* button = buttons[i];
connect(button, &QAbstractButton::toggled, this, [=](bool checked)
{
GemInfo::Platforms platforms = m_filterProxyModel->GetPlatforms();
if (checked)
{
platforms |= platform;
}
else
{
platforms &= ~platform;
}
m_filterProxyModel->SetPlatforms(platforms);
});
}
return static_cast<bool>(platform & gemModel->GetPlatforms(gemModel->index(gemIndex, 0)));
},
&GemInfo::GetPlatformString, &GemSortFilterProxyModel::GetPlatforms, &GemSortFilterProxyModel::SetPlatforms);
}
void GemFilterWidget::AddFeatureFilter()
void GemFilterWidget::ResetFeatureFilter()
{
// Alphabetically sorted, unique features and their number of occurrences in the gem database.
QMap<QString, int> uniqueFeatureCounts;
@@ -497,11 +452,15 @@ namespace O3DE::ProjectManager
elementCounts.push_back(iterator.value());
}
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Features", elementNames, elementCounts,
/*showAllLessButton=*/true, false, /*defaultShowCount=*/5);
m_filterLayout->addWidget(filterWidget);
ResetFilterWidget(m_featureFilter, "Features", elementNames, elementCounts, /*defaultShowCount=*/5);
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
for (QMetaObject::Connection& connection : m_featureTagConnections)
{
disconnect(connection);
}
m_featureTagConnections.clear();
const QList<QAbstractButton*> buttons = m_featureFilter->GetButtonGroup()->buttons();
for (int i = 0; i < buttons.size(); ++i)
{
const QString& feature = elementNames[i];
@@ -523,13 +482,13 @@ namespace O3DE::ProjectManager
});
// Sync the UI state with the proxy model filtering.
connect(m_filterProxyModel, &GemSortFilterProxyModel::OnInvalidated, this, [=]
m_featureTagConnections.push_back(connect(m_filterProxyModel, &GemSortFilterProxyModel::OnInvalidated, this, [=]
{
const QSet<QString>& filteredFeatureTags = m_filterProxyModel->GetFeatures();
const bool isChecked = filteredFeatureTags.contains(button->text());
QSignalBlocker signalsBlocker(button);
button->setChecked(isChecked);
});
}));
}
}
} // namespace O3DE::ProjectManager
@@ -66,17 +66,41 @@ namespace O3DE::ProjectManager
~GemFilterWidget() = default;
public slots:
void ResetAllFilters();
void ResetGemStatusFilter();
private:
void AddGemOriginFilter();
void AddTypeFilter();
void AddPlatformFilter();
void AddFeatureFilter();
void ResetGemOriginFilter();
void ResetTypeFilter();
void ResetPlatformFilter();
void ResetFeatureFilter();
void ResetFilterWidget(
FilterCategoryWidget*& filterPtr,
const QString& filterName,
const QVector<QString>& elementNames,
const QVector<int>& elementCounts,
int defaultShowCount = 4);
template<typename filterType, typename filterFlagsType>
void ResetSimpleOrFilter(
FilterCategoryWidget*& filterPtr,
const QString& filterName,
int numFilterElements,
bool (*filterMatcher)(GemModel*, filterType, int),
QString (*typeStringGetter)(filterType),
filterFlagsType (GemSortFilterProxyModel::*filterFlagsGetter)() const,
void (GemSortFilterProxyModel::*filterFlagsSetter)(const filterFlagsType&));
QVBoxLayout* m_filterLayout = nullptr;
GemModel* m_gemModel = nullptr;
GemSortFilterProxyModel* m_filterProxyModel = nullptr;
FilterCategoryWidget* m_statusFilter = nullptr;
FilterCategoryWidget* m_originFilter = nullptr;
FilterCategoryWidget* m_typeFilter = nullptr;
FilterCategoryWidget* m_platformFilter = nullptr;
FilterCategoryWidget* m_featureFilter = nullptr;
QVector<QMetaObject::Connection> m_featureTagConnections;
};
} // namespace O3DE::ProjectManager
@@ -175,6 +175,7 @@ namespace O3DE::ProjectManager
// Depending gems
m_dependingGems = new GemsSubWidget();
connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [=](const QString& tag){ emit TagClicked(tag); });
m_mainLayout->addWidget(m_dependingGems);
m_mainLayout->addSpacing(20);
@@ -40,6 +40,9 @@ namespace O3DE::ProjectManager
inline constexpr static const char* s_headerColor = "#FFFFFF";
inline constexpr static const char* s_textColor = "#DDDDDD";
signals:
void TagClicked(const QString& tag);
private slots:
void OnSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
@@ -63,6 +63,7 @@ namespace O3DE::ProjectManager
appendRow(item);
const QModelIndex modelIndex = index(rowCount()-1, 0);
m_nameToIndexMap[gemInfo.m_displayName] = modelIndex;
m_nameToIndexMap[gemInfo.m_name] = modelIndex;
}
@@ -207,6 +207,8 @@ namespace O3DE::ProjectManager
void GemSortFilterProxyModel::ResetFilters()
{
m_searchString.clear();
m_gemSelectedFilter = GemSelected::NoFilter;
m_gemActiveFilter = GemActive::NoFilter;
m_gemOriginFilter = {};
m_platformFilter = {};
m_typeFilter = {};
@@ -33,6 +33,7 @@ namespace O3DE::ProjectManager
m_layout->addWidget(m_textLabel);
m_tagWidget = new TagContainerWidget();
connect(m_tagWidget, &TagContainerWidget::TagClicked, this, [=](const QString& tag){ emit TagClicked(tag); });
m_layout->addWidget(m_tagWidget);
}
@@ -22,10 +22,15 @@ namespace O3DE::ProjectManager
class GemsSubWidget
: public QWidget
{
Q_OBJECT // AUTOMOC
public:
GemsSubWidget(QWidget* parent = nullptr);
void Update(const QString& title, const QString& text, const QStringList& gemNames);
signals:
void TagClicked(const QString& tag);
private:
QLabel* m_titleLabel = nullptr;
QLabel* m_textLabel = nullptr;
@@ -18,6 +18,11 @@ namespace O3DE::ProjectManager
setObjectName("TagWidget");
}
void TagWidget::mousePressEvent([[maybe_unused]] QMouseEvent* event)
{
emit(TagClicked(text()));
}
TagContainerWidget::TagContainerWidget(QWidget* parent)
: QWidget(parent)
{
@@ -45,7 +50,9 @@ namespace O3DE::ProjectManager
foreach (const QString& tag, tags)
{
flowLayout->addWidget(new TagWidget(tag));
TagWidget* tagWidget = new TagWidget(tag);
connect(tagWidget, &TagWidget::TagClicked, this, [=](const QString& tag){ emit TagClicked(tag); });
flowLayout->addWidget(tagWidget);
}
}
} // namespace O3DE::ProjectManager
@@ -25,6 +25,12 @@ namespace O3DE::ProjectManager
public:
explicit TagWidget(const QString& text, QWidget* parent = nullptr);
~TagWidget() = default;
signals:
void TagClicked(const QString& tag);
protected:
void mousePressEvent(QMouseEvent* event) override;
};
// Widget containing multiple tags, automatically wrapping based on the size
@@ -38,5 +44,8 @@ namespace O3DE::ProjectManager
~TagContainerWidget() = default;
void Update(const QStringList& tags);
signals:
void TagClicked(const QString& tag);
};
} // namespace O3DE::ProjectManager