Merge branch 'development' of https://github.com/o3de/o3de into GroupToggleSwitch

This commit is contained in:
Jose
2021-07-13 14:51:56 -05:00
827 changed files with 22468 additions and 26141 deletions
+18 -5
View File
@@ -27,11 +27,17 @@ namespace AZ
template <typename... Params>
EventHandler<Params...>::EventHandler(const EventHandler& rhs)
: m_callback(rhs.m_callback)
, m_event(rhs.m_event)
{
// Copy the callback function, then perform a Connect with the new event
if (rhs.m_event)
// Copy the callback and event, then perform a Connect to the event
if (m_callback && m_event)
{
rhs.m_event->Connect(*this);
m_event->Connect(*this);
}
else
{
// It was not possible to connect to the event, set it to nullptr
m_event = nullptr;
}
}
@@ -65,9 +71,16 @@ namespace AZ
{
Disconnect();
m_callback = rhs.m_callback;
if (rhs.m_event)
m_event = rhs.m_event;
// Copy the callback and event, then perform a Connect to the event
if (m_callback && m_event)
{
rhs.m_event->Connect(*this);
m_event->Connect(*this);
}
else
{
// It was not possible to connect to the event, set it to nullptr
m_event = nullptr;
}
}
@@ -23,6 +23,8 @@ namespace AZ
}
JsonUuidSerializer::JsonUuidSerializer()
: m_zeroUuidString(AZ::Uuid::CreateNull().ToString<AZStd::string>())
, m_zeroUuidStringNoDashes(AZ::Uuid::CreateNull().ToString<AZStd::string>(true, false))
{
m_uuidFormat = AZStd::regex(R"(\{[a-f0-9]{8}-?[a-f0-9]{4}-?[a-f0-9]{4}-?[a-f0-9]{4}-?[a-f0-9]{12}\})",
AZStd::regex_constants::icase | AZStd::regex_constants::optimize);
@@ -53,7 +55,7 @@ namespace AZ
Uuid* valAsUuid = reinterpret_cast<Uuid*>(outputValue);
if (IsExplicitDefault(inputValue))
if (IsExplicitDefault(inputValue) || (inputValue == m_zeroUuidString.c_str()) || (inputValue == m_zeroUuidStringNoDashes.c_str()))
{
*valAsUuid = AZ::Uuid::CreateNull();
return MessageResult("Uuid value set to default of null.", JSR::ResultCode(JSR::Tasks::ReadField, JSR::Outcomes::DefaultsUsed));
@@ -47,6 +47,8 @@ namespace AZ
const Uuid& valueTypeId, JsonSerializerContext& context);
private:
const AZStd::string m_zeroUuidString;
const AZStd::string m_zeroUuidStringNoDashes;
AZStd::regex m_uuidFormat;
};
} // namespace AZ
@@ -3252,6 +3252,7 @@ LUA_API const Node* lua_getDummyNode()
else if (Internal::LuaScriptNumber<short>::FromStack(param, result)) return result;
else if (Internal::LuaScriptNumber<AZ::s64>::FromStack(param, result)) return result;
else if (Internal::LuaScriptNumber<long>::FromStack(param, result)) return result;
else if (Internal::LuaScriptNumber<AZ::s8>::FromStack(param, result)) return result;
else if (Internal::LuaScriptNumber<unsigned char>::FromStack(param, result)) return result;
else if (Internal::LuaScriptNumber<unsigned short>::FromStack(param, result)) return result;
else if (Internal::LuaScriptNumber<unsigned int>::FromStack(param, result)) return result;
@@ -32,6 +32,15 @@ namespace AZ
{
if (m_hashes.size() < m_hashes.max_size())
{
constexpr size_t MaxTagSize = TagName{}.max_size();
if (specialization.size() > MaxTagSize)
{
AZ_Error("Settings Registry", false, R"(Cannot append Specialization tag of "%.*s.")"
" It is longer than the MaxTagNameSize of %zu", AZ_STRING_ARG(specialization),
MaxTagSize);
return false;
}
m_names.push_back(TagName{ specialization });
TagName& tag = m_names.back();
AZStd::to_lower(tag.begin(), tag.end());
@@ -49,7 +49,7 @@ namespace AZ
class Specializations
{
public:
static constexpr size_t MaxTagNameSize = 32;
static constexpr size_t MaxTagNameSize = 64;
static constexpr size_t MaxCount = 15;
static constexpr size_t NotFound = static_cast<size_t>(-1);
using TagName = AZStd::fixed_string<MaxTagNameSize>;
@@ -146,7 +146,7 @@ namespace AZ {
AZStd::mutex g_dbgLoadingMutex;
HANDLE g_currentProcess = 0; /// We deal with only one process for now.
CRITICAL_SECTION g_csDbgHelpDll; /// All dbg help functions are single threaded, so we need to control the access.
AZStd::fixed_vector<SymbolStorage::ModuleInfo, 256> g_moduleInfo;
AZStd::fixed_vector<SymbolStorage::ModuleInfo, 2048> g_moduleInfo;
// reserve 4k of scratch space so that we can get some callstack information without any allocations and as little stack frame usage as possible.
const size_t g_scratchSpaceSize = 2048;
@@ -257,6 +257,48 @@ namespace UnitTest
EXPECT_FALSE(testEvent1.HasHandlerConnected());
EXPECT_TRUE(testEvent2.HasHandlerConnected());
}
TEST_F(EventTests, TestHandlerCopyConstructorOperator)
{
int32_t invokedCounter = 0;
AZ::Event<> testEvent;
AZ::Event<>::Handler testHandler([&invokedCounter]() { invokedCounter++; });
testHandler.Connect(testEvent);
AZ::Event<>::Handler testHandler2(testHandler);
EXPECT_TRUE(testHandler.IsConnected());
EXPECT_TRUE(testHandler2.IsConnected());
EXPECT_TRUE(invokedCounter == 0);
testEvent.Signal();
EXPECT_TRUE(invokedCounter == 2);
}
TEST_F(EventTests, TestHandlerCopyAssignmentOperator)
{
int32_t invokedCounter = 0;
AZ::Event<> testEvent;
AZ::Event<>::Handler testHandler([&invokedCounter]() { invokedCounter++; });
testHandler.Connect(testEvent);
AZ::Event<>::Handler testHandler2;
EXPECT_TRUE(testHandler.IsConnected());
EXPECT_FALSE(testHandler2.IsConnected());
testHandler2 = testHandler;
EXPECT_TRUE(testHandler2.IsConnected());
EXPECT_TRUE(invokedCounter == 0);
testEvent.Signal();
EXPECT_TRUE(invokedCounter == 2);
}
}
#if defined(HAVE_BENCHMARK)
@@ -1155,7 +1155,7 @@ namespace Benchmark
class StorageDriveWindowsFixture : public benchmark::Fixture
{
public:
constexpr static char* TestFileName = "StreamerBenchmark.bin";
constexpr static const char* TestFileName = "StreamerBenchmark.bin";
constexpr static size_t FileSize = 64_mib;
void SetupStreamer(bool enableFileSharing)
@@ -454,8 +454,8 @@ namespace AzFramework
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceKeyboard::InputDeviceKeyboard()
: InputDevice(Id)
InputDeviceKeyboard::InputDeviceKeyboard(AzFramework::InputDeviceId id)
: InputDevice(id)
, m_modifierKeyStates(AZStd::make_shared<ModifierKeyStates>())
, m_allChannelsById()
, m_keyChannelsById()
@@ -240,7 +240,7 @@ namespace AzFramework
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
InputDeviceKeyboard();
InputDeviceKeyboard(AzFramework::InputDeviceId id = Id);
////////////////////////////////////////////////////////////////////////////////////////////
// Disable copying
@@ -95,8 +95,8 @@ namespace AzFramework
}
////////////////////////////////////////////////////////////////////////////////////////////////
InputDeviceMouse::InputDeviceMouse()
: InputDevice(Id)
InputDeviceMouse::InputDeviceMouse(AzFramework::InputDeviceId id)
: InputDevice(id)
, m_allChannelsById()
, m_buttonChannelsById()
, m_movementChannelsById()
@@ -111,7 +111,7 @@ namespace AzFramework
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
explicit InputDeviceMouse();
explicit InputDeviceMouse(AzFramework::InputDeviceId id = Id);
////////////////////////////////////////////////////////////////////////////////////////////
// Disable copying
@@ -84,7 +84,7 @@ namespace Physics
if (serializeContext)
{
serializeContext->Class<CharacterConfiguration, AzPhysics::SimulatedBodyConfiguration>()
->Version(2)
->Version(3)
->Field("CollisionLayer", &CharacterConfiguration::m_collisionLayer)
->Field("CollisionGroupId", &CharacterConfiguration::m_collisionGroupId)
->Field("Material", &CharacterConfiguration::m_materialSelection)
@@ -94,6 +94,7 @@ namespace Physics
->Field("MinDistance", &CharacterConfiguration::m_minimumMovementDistance)
->Field("MaxSpeed", &CharacterConfiguration::m_maximumSpeed)
->Field("ColliderTag", &CharacterConfiguration::m_colliderTag)
->Field("ApplyMoveOnPhysicsTick", &CharacterConfiguration::m_applyMoveOnPhysicsTick)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
@@ -127,6 +128,9 @@ namespace Physics
->Attribute(AZ::Edit::Attributes::Step, 1.0f)
->DataElement(AZ::Edit::UIHandlers::Default, &CharacterConfiguration::m_colliderTag,
"Collider Tag", "Used to identify the collider associated with the character controller")
->DataElement(AZ::Edit::UIHandlers::Default, &CharacterConfiguration::m_applyMoveOnPhysicsTick,
"Apply Move On Tick", "Requests to add velocity will be accumulated and applied once on the physics pre-simulation tick "
"If unticked, explicit call to apply requested velocity is required")
;
}
}
@@ -76,6 +76,7 @@ namespace Physics
AZStd::string m_colliderTag; //!< Used to identify the collider associated with the character controller.
AZStd::shared_ptr<Physics::ShapeConfiguration> m_shapeConfig; //!< The shape to use when creating the character controller.
AZStd::vector<AZStd::shared_ptr<Physics::Shape>> m_colliders; //!< The list of colliders to attach to the character controller.
bool m_applyMoveOnPhysicsTick = true; //!< Should accumulated velocity be applied on the physics tick.
};
/// Basic implementation of common character-style needs as a WorldBody. Is not a full-functional ship-ready
@@ -136,6 +136,7 @@ namespace AzPhysics
->Property("Distance", BehaviorValueProperty(&RayCastRequest::m_distance))
->Property("Start", BehaviorValueProperty(&RayCastRequest::m_start))
->Property("Direction", BehaviorValueProperty(&RayCastRequest::m_direction))
->Property("ReportMultipleHits", BehaviorValueProperty(&RayCastRequest::m_reportMultipleHits))
;
}
}
@@ -153,6 +154,45 @@ namespace AzPhysics
->Field("ReportMultipleHits", &ShapeCastRequest::m_reportMultipleHits)
;
}
if (auto* behaviorContext = azdynamic_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<ShapeCastRequest>("ShapeCastRequest")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Category, "PhysX")
->Property("Distance", BehaviorValueProperty(&ShapeCastRequest::m_distance))
->Property("Start", BehaviorValueProperty(&ShapeCastRequest::m_start))
->Property("Direction", BehaviorValueProperty(&ShapeCastRequest::m_direction))
;
behaviorContext->Method(
"CreateSphereCastRequest",
[](float radius, const AZ::Transform& startPose, const AZ::Vector3& direction, float distance,
SceneQuery::QueryType queryType, CollisionGroup collisionGroup)
{
return ShapeCastRequestHelpers::CreateSphereCastRequest(
radius, startPose, direction, distance, queryType, collisionGroup, nullptr);
});
behaviorContext->Method(
"CreateBoxCastRequest",
[](const AZ::Vector3& boxDimensions, const AZ::Transform& startPose, const AZ::Vector3& direction, float distance,
SceneQuery::QueryType queryType, CollisionGroup collisionGroup)
{
return ShapeCastRequestHelpers::CreateBoxCastRequest(
boxDimensions, startPose, direction, distance, queryType, collisionGroup, nullptr);
});
behaviorContext->Method(
"CreateCapsuleCastRequest",
[](float capsuleRadius, float capsuleHeight, const AZ::Transform& startPose, const AZ::Vector3& direction, float distance,
SceneQuery::QueryType queryType, CollisionGroup collisionGroup)
{
return ShapeCastRequestHelpers::CreateCapsuleCastRequest(
capsuleRadius, capsuleHeight, startPose, direction, distance, queryType, collisionGroup, nullptr);
});
}
}
namespace ShapeCastRequestHelpers
@@ -219,6 +259,40 @@ namespace AzPhysics
->Field("ShapeConfiguration", &OverlapRequest::m_shapeConfiguration)
;
}
if (auto* behaviorContext = azdynamic_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<OverlapRequest>("OverlapRequest")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Category, "PhysX")
->Property("Pose", BehaviorValueProperty(&OverlapRequest::m_pose))
;
behaviorContext->Method(
"CreateSphereOverlapRequest",
[](float radius, const AZ::Transform& pose)
{
return OverlapRequestHelpers::CreateSphereOverlapRequest(
radius, pose, nullptr);
});
behaviorContext->Method(
"CreateBoxOverlapRequest",
[](const AZ::Vector3& boxDimensions, const AZ::Transform& pose)
{
return OverlapRequestHelpers::CreateBoxOverlapRequest(
boxDimensions, pose, nullptr);
});
behaviorContext->Method(
"CreateCapsuleOverlapRequest",
[](float height, float radius, const AZ::Transform& pose)
{
return OverlapRequestHelpers::CreateCapsuleOverlapRequest(
height, radius, pose, nullptr);
});
}
}
namespace OverlapRequestHelpers
@@ -504,7 +504,7 @@ namespace Physics
}
}
const AZ::Data::Asset<Physics::MaterialLibraryAsset>& MaterialSelection::GetMaterialLibrary()
AZ::Data::Asset<Physics::MaterialLibraryAsset> MaterialSelection::GetMaterialLibrary()
{
if (auto* physicsSystem = AZ::Interface<AzPhysics::SystemInterface>::Get())
{
@@ -516,7 +516,7 @@ namespace Physics
return s_invalidMaterialLibrary;
}
const AZ::Data::AssetId& MaterialSelection::GetMaterialLibraryId()
AZ::Data::AssetId MaterialSelection::GetMaterialLibraryId()
{
return GetMaterialLibrary().GetId();
}
@@ -306,8 +306,8 @@ namespace Physics
void SyncSelectionToMaterialLibrary();
static const AZ::Data::Asset<Physics::MaterialLibraryAsset>& GetMaterialLibrary();
static const AZ::Data::AssetId& GetMaterialLibraryId();
static AZ::Data::Asset<Physics::MaterialLibraryAsset> GetMaterialLibrary();
static AZ::Data::AssetId GetMaterialLibraryId();
bool AreMaterialSlotsReadOnly() const;
@@ -48,6 +48,7 @@ namespace AzPhysics
->Attribute(AZ::Script::Attributes::Category, "Physics")
->Method("GetOnGravityChangeEvent", getOnGravityChange)
->Attribute(AZ::Script::Attributes::AzEventDescription, gravityChangedEventDescription)
->Method("QueryScene", &Scene::QueryScene)
;
}
}
@@ -41,7 +41,7 @@ namespace AzPhysics
{"Tick time"} // Parameters
};
behaviorContext->Class<SystemInterface>("System Interface")
behaviorContext->Class<SystemInterface>("PhysicsSystemInterface")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Category, "PhysX")
@@ -49,7 +49,21 @@ namespace AzPhysics
->Attribute(AZ::Script::Attributes::AzEventDescription, presimulateEventDescription)
->Method("GetOnPostsimulateEvent", getOnPostsimulateEvent)
->Attribute(AZ::Script::Attributes::AzEventDescription, postsimulateEventDescription)
;
->Method("GetSceneHandle", &SystemInterface::GetSceneHandle)
->Method("GetScene", &SystemInterface::GetScene);
behaviorContext->Method(
"GetPhysicsSystem",
[]()
{
return AZ::Interface<AzPhysics::SystemInterface>::Get();
});
behaviorContext
->Constant("DefaultPhysicsSceneName", BehaviorConstant(DefaultPhysicsSceneName))
->Constant("DefaultPhysicsSceneId", BehaviorConstant(DefaultPhysicsSceneId))
->Constant("EditorPhysicsSceneName", BehaviorConstant(EditorPhysicsSceneName))
->Constant("EditorPhysicsSceneId", BehaviorConstant(EditorPhysicsSceneId));
}
}
} // namespace AzPhysics
@@ -27,7 +27,7 @@ namespace AzFramework
AZStd::string m_ipAddress;
// The port number for the session.
uint16_t m_port;
uint16_t m_port = 0;
};
//! SessionConnectionConfig
@@ -35,7 +35,7 @@ namespace AzFramework
struct PlayerConnectionConfig
{
// A unique identifier for player connection.
uint32_t m_playerConnectionId;
uint32_t m_playerConnectionId = 0;
// A unique identifier for registered player in session.
AZStd::string m_playerSessionId;
@@ -37,7 +37,7 @@ namespace AzFramework
AZStd::string m_sessionName;
// The maximum number of players that can be connected simultaneously to the session.
uint64_t m_maxPlayer;
uint64_t m_maxPlayer = 0;
};
//! SearchSessionsRequest
@@ -58,7 +58,7 @@ namespace AzFramework
AZStd::string m_sortExpression;
// The maximum number of results to return.
uint8_t m_maxResult;
uint8_t m_maxResult = 0;
// A token that indicates the start of the next sequential page of results.
AZStd::string m_nextToken;
@@ -24,10 +24,10 @@ namespace AzFramework
virtual ~SessionConfig() = default;
// A time stamp indicating when this session was created. Format is a number expressed in Unix time as milliseconds.
uint64_t m_creationTime;
uint64_t m_creationTime = 0;
// A time stamp indicating when this data object was terminated. Same format as creation time.
uint64_t m_terminationTime;
uint64_t m_terminationTime = 0;
// A unique identifier for a player or entity creating the session.
AZStd::string m_creatorId;
@@ -48,13 +48,13 @@ namespace AzFramework
AZStd::string m_ipAddress;
// The port number for the session.
uint16_t m_port;
uint16_t m_port = 0;
// The maximum number of players that can be connected simultaneously to the session.
uint64_t m_maxPlayer;
uint64_t m_maxPlayer = 0;
// Number of players currently in the session.
uint64_t m_currentPlayer;
uint64_t m_currentPlayer = 0;
// Current status of the session.
AZStd::string m_status;
@@ -747,19 +747,24 @@ namespace AzFramework
return button == inputChannelId;
});
if (inputChannelId == InputDeviceMouse::Movement::X)
// accept active mouse channel updates, inactive movement channels will just have a 0 delta
if (inputChannel.IsActive())
{
return HorizontalMotionEvent{ aznumeric_cast<int>(inputChannel.GetValue()) };
if (inputChannelId == InputDeviceMouse::Movement::X)
{
return HorizontalMotionEvent{ aznumeric_cast<int>(inputChannel.GetValue()) };
}
else if (inputChannelId == InputDeviceMouse::Movement::Y)
{
return VerticalMotionEvent{ aznumeric_cast<int>(inputChannel.GetValue()) };
}
else if (inputChannelId == InputDeviceMouse::Movement::Z)
{
return ScrollEvent{ inputChannel.GetValue() };
}
}
else if (inputChannelId == InputDeviceMouse::Movement::Y)
{
return VerticalMotionEvent{ aznumeric_cast<int>(inputChannel.GetValue()) };
}
else if (inputChannelId == InputDeviceMouse::Movement::Z)
{
return ScrollEvent{ inputChannel.GetValue() };
}
else if (wasMouseButton || InputDeviceKeyboard::IsKeyboardDevice(inputDeviceId))
if (wasMouseButton || InputDeviceKeyboard::IsKeyboardDevice(inputDeviceId))
{
return DiscreteInputEvent{ inputChannelId, inputChannel.GetState() };
}
@@ -116,6 +116,11 @@ namespace AzFramework
SetFullScreenState(!GetFullScreenState());
}
float NativeWindow::GetDpiScaleFactor() const
{
return m_pimpl->GetDpiScaleFactor();
}
/*static*/ bool NativeWindow::GetFullScreenStateOfDefaultWindow()
{
NativeWindowHandle defaultWindowHandle = nullptr;
@@ -228,4 +233,10 @@ namespace AzFramework
return false;
}
float NativeWindow::Implementation::GetDpiScaleFactor() const
{
// For platforms that aren't DPI-aware, we simply return a 1.0 ratio for no scaling
return 1.0f;
}
} // namespace AzFramework
@@ -128,6 +128,7 @@ namespace AzFramework
void SetFullScreenState(bool fullScreenState) override;
bool CanToggleFullScreenState() const override;
void ToggleFullScreenState() override;
float GetDpiScaleFactor() const override;
//! Get the full screen state of the default window.
//! \return True if the default window is currently in full screen, false otherwise.
@@ -169,6 +170,7 @@ namespace AzFramework
virtual bool GetFullScreenState() const;
virtual void SetFullScreenState(bool fullScreenState);
virtual bool CanToggleFullScreenState() const;
virtual float GetDpiScaleFactor() const;
protected:
uint32_t m_width = 0;
@@ -68,6 +68,11 @@ namespace AzFramework
//! Toggle the full screen state of the window.
virtual void ToggleFullScreenState() = 0;
//! Returns a scalar multiplier representing how many dots-per-inch this window has, compared
//! to a "standard" value of 96, the default for Windows in a DPI unaware setting. This can
//! be used to scale user interface elements to ensure legibility on high density displays.
virtual float GetDpiScaleFactor() const = 0;
};
using WindowRequestBus = AZ::EBus<WindowRequests>;
@@ -87,6 +92,9 @@ namespace AzFramework
//! This is called once when the window is Activated and also called if the user resizes the window.
virtual void OnWindowResized(uint32_t width, uint32_t height) { AZ_UNUSED(width); AZ_UNUSED(height); };
//! This is called if the window's underyling DPI scaling factor changes.
virtual void OnDpiScaleFactorChanged(float dpiScaleFactor) { AZ_UNUSED(dpiScaleFactor); }
//! This is called when the window is deactivated from code or if the user closes the window.
virtual void OnWindowClosed() {};
};
@@ -12,5 +12,5 @@
#define AZ_TRAIT_AZFRAMEWORK_USE_ERRNO_T_TYPEDEF 1
#define AZ_TRAIT_AZFRAMEWORK_USE_POSIX_LOCALTIME_R 0
#define AZ_TRAIT_AZFRAMEWORK_PYTHON_SHELL "python.sh"
#define AZ_TRAIT_AZFRAMEWORK_USE_PROJECT_MANAGER 0
#define AZ_TRAIT_AZFRAMEWORK_USE_PROJECT_MANAGER 1
#define AZ_TRAIT_AZFRAMEWORK_PROCESSLAUNCH_DEFAULT 0
@@ -12,5 +12,5 @@
#define AZ_TRAIT_AZFRAMEWORK_USE_ERRNO_T_TYPEDEF 0
#define AZ_TRAIT_AZFRAMEWORK_USE_POSIX_LOCALTIME_R 1
#define AZ_TRAIT_AZFRAMEWORK_PYTHON_SHELL "python.sh"
#define AZ_TRAIT_AZFRAMEWORK_USE_PROJECT_MANAGER 0
#define AZ_TRAIT_AZFRAMEWORK_USE_PROJECT_MANAGER 1
#define AZ_TRAIT_AZFRAMEWORK_PROCESSLAUNCH_DEFAULT 0
@@ -8,6 +8,7 @@
#include <AzFramework/Input/Buses/Notifications/RawInputNotificationBus_Windows.h>
#include <AzFramework/Windowing/NativeWindow.h>
#include <AzCore/Module/DynamicModuleHandle.h>
#include <AzCore/PlatformIncl.h>
namespace AzFramework
@@ -17,7 +18,7 @@ namespace AzFramework
{
public:
AZ_CLASS_ALLOCATOR(NativeWindowImpl_Win32, AZ::SystemAllocator, 0);
NativeWindowImpl_Win32() = default;
NativeWindowImpl_Win32();
~NativeWindowImpl_Win32() override;
// NativeWindow::Implementation overrides...
@@ -33,6 +34,7 @@ namespace AzFramework
bool GetFullScreenState() const override;
void SetFullScreenState(bool fullScreenState) override;
bool CanToggleFullScreenState() const override { return true; }
float GetDpiScaleFactor() const override;
private:
static DWORD ConvertToWin32WindowStyleMask(const WindowStyleMasks& styleMasks);
@@ -49,6 +51,9 @@ namespace AzFramework
RECT m_windowRectToRestoreOnFullScreenExit; //!< The position and size of the window to restore when exiting full screen.
UINT m_windowStyleToRestoreOnFullScreenExit; //!< The style(s) of the window to restore when exiting full screen.
bool m_isInBorderlessWindowFullScreenState = false; //!< Was a borderless window used to enter full screen state?
using GetDpiForWindowType = UINT(HWND hwnd);
GetDpiForWindowType* m_getDpiFunction = nullptr;
};
const char* NativeWindowImpl_Win32::s_defaultClassName = "O3DEWin32Class";
@@ -58,6 +63,15 @@ namespace AzFramework
return aznew NativeWindowImpl_Win32();
}
NativeWindowImpl_Win32::NativeWindowImpl_Win32()
{
// Attempt to load GetDpiForWindow from user32 at runtime, available on Windows 10+ versions >= 1607
if (auto user32module = AZ::DynamicModuleHandle::Create("user32"); user32module->Load(false))
{
m_getDpiFunction = user32module->GetFunction<GetDpiForWindowType*>("GetDpiForWindow");
}
}
NativeWindowImpl_Win32::~NativeWindowImpl_Win32()
{
DestroyWindow(m_win32Handle);
@@ -237,6 +251,12 @@ namespace AzFramework
// Send all other WM_SYSKEYDOWN messages to the default WndProc.
break;
}
case WM_DPICHANGED:
{
const float newScaleFactor = nativeWindowImpl->GetDpiScaleFactor();
WindowNotificationBus::Event(nativeWindowImpl->GetWindowHandle(), &WindowNotificationBus::Events::OnDpiScaleFactorChanged, newScaleFactor);
break;
}
default:
return DefWindowProc(hWnd, message, wParam, lParam);
break;
@@ -330,6 +350,17 @@ namespace AzFramework
}
}
float NativeWindowImpl_Win32::GetDpiScaleFactor() const
{
constexpr UINT defaultDotsPerInch = 96;
UINT dotsPerInch = defaultDotsPerInch;
if (m_getDpiFunction)
{
dotsPerInch = m_getDpiFunction(m_win32Handle);
}
return aznumeric_cast<float>(dotsPerInch) / aznumeric_cast<float>(defaultDotsPerInch);
}
void NativeWindowImpl_Win32::EnterBorderlessWindowFullScreen()
{
if (m_isInBorderlessWindowFullScreenState)
@@ -41,7 +41,7 @@ namespace AzNetworking
void TcpSocketManager::ProcessEvents(AZ::TimeMs maxBlockMs, const SocketEventCallback& readCallback, const SocketEventCallback& writeCallback)
{
if(static_cast<int32_t>(m_maxFd) <= 0 && m_socketFds.empty())
if(static_cast<int32_t>(m_maxFd) <= 0 || m_socketFds.empty())
{
// There are no available sockets to process
return;
@@ -3288,15 +3288,20 @@ namespace AzQtComponents
}
// Untab tabbed dock widgets before restoring, as the restore only works on dock widgets parented directly to the main window
const QList<QDockWidget*> dockWidgets = m_mainWindow->findChildren<QDockWidget*>();
for (QDockWidget* dockWidget : dockWidgets)
for (QDockWidget* dockWidget : m_mainWindow->findChildren<QDockWidget*>(
QRegularExpression(QString("%1.*").arg(m_tabContainerIdentifierPrefix)), Qt::FindChildrenRecursively))
{
if (QStackedWidget* stackedWidget = qobject_cast<QStackedWidget*>(dockWidget->parentWidget()))
DockTabWidget* tabWidget = qobject_cast<DockTabWidget*>(dockWidget->widget());
if (!tabWidget)
{
if (AzQtComponents::DockTabWidget* tabWidget = qobject_cast<AzQtComponents::DockTabWidget*>(stackedWidget->parentWidget()))
{
tabWidget->removeTab(dockWidget);
}
continue;
}
// Remove the tabs from the tab widget (we don't actually want to close them, which could delete them at this point)
int numTabs = tabWidget->count();
for (int i = 0; i < numTabs; ++i)
{
tabWidget->removeTab(0);
}
}
@@ -1303,6 +1303,10 @@ namespace AzQtComponents
}
break;
case QStyle::SP_MessageBoxInformation:
return QIcon(QString::fromUtf8(":/stylesheet/img/UI20/Info.svg"));
break;
default:
break;
}
@@ -123,6 +123,7 @@ QPlainTextEdit:focus
@import "LineEdit.qss";
@import "Menu.qss";
@import "MenuBar.qss";
@import "MessageBox.qss";
@import "ProgressBar.qss";
@import "PushButton.qss";
@import "QDockWidget.qss";
@@ -0,0 +1,21 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
/* correct the padding around the two main labels to give space at the borders */
QMessageBox QLabel#qt_msgbox_label
{
padding-top: 20px;
padding-right: 20px;
padding-bottom: 20px;
}
QMessageBox QLabel#qt_msgboxex_icon_label
{
padding-left: 20px;
padding-top: 20px;
}
@@ -354,17 +354,17 @@
<file>img/UI20/toolbar/Grid.svg</file>
<file>img/UI20/toolbar/Lighting.svg</file>
<file>img/UI20/toolbar/Load.svg</file>
<file>img/UI20/toolbar/Local.svg</file>
<file>img/UI20/toolbar/Local.svg</file>
<file>img/UI20/toolbar/Locked.svg</file>
<file>img/UI20/toolbar/Locked_Status.svg</file>
<file>img/UI20/toolbar/LUA.svg</file>
<file>img/UI20/toolbar/LUA.svg</file>
<file>img/UI20/toolbar/Material.svg</file>
<file>img/UI20/toolbar/Measure.svg</file>
<file>img/UI20/toolbar/Move.svg</file>
<file>img/UI20/toolbar/Object_follow_terrain.svg</file>
<file>img/UI20/toolbar/Object_height.svg</file>
<file>img/UI20/toolbar/Object_list.svg</file>
<file>img/UI20/toolbar/Parent.svg</file>
<file>img/UI20/toolbar/Parent.svg</file>
<file>img/UI20/toolbar/particle.svg</file>
<file>img/UI20/toolbar/Play.svg</file>
<file>img/UI20/toolbar/Redo.svg</file>
@@ -385,7 +385,7 @@
<file>img/UI20/toolbar/undo.svg</file>
<file>img/UI20/toolbar/Unlocked.svg</file>
<file>img/UI20/toolbar/Vertex_snapping.svg</file>
<file>img/UI20/toolbar/World.svg</file>
<file>img/UI20/toolbar/World.svg</file>
<file>img/UI20/toolbar/X_axis.svg</file>
<file>img/UI20/toolbar/Y_axis.svg</file>
<file>img/UI20/toolbar/Z_axis.svg</file>
@@ -459,6 +459,7 @@
<file>Widgets/ComboBoxConfig.ini</file>
<file>Widgets/Menu.qss</file>
<file>Widgets/MenuBar.qss</file>
<file>Widgets/MessageBox.qss</file>
<file>Widgets/ProgressBarConfig.ini</file>
<file>Widgets/ProgressBar.qss</file>
<file>Widgets/PushButton.qss</file>
@@ -36,8 +36,47 @@ namespace AzToolsFramework
/// Retrieve the main application window.
virtual QWidget* GetAppMainWindow() { return nullptr; }
};
using EditorWindowRequestBus = AZ::EBus<EditorWindowRequests>;
class EditorWindowUIRequests : public AZ::EBusTraits
{
public:
using Bus = AZ::EBus<EditorWindowUIRequests>;
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
//////////////////////////////////////////////////////////////////////////
/// Enable/Disable the Editor UI.
virtual void SetEditorUiEnabled([[maybe_unused]] bool enable) {}
};
using EditorWindowUIRequestBus = AZ::EBus<EditorWindowUIRequests>;
using EnableUiFunction = AZStd::function<void(bool)>;
/// Helper for EditorWindowRequests to be used as a
/// member instead of inheriting from EBus directly.
class EditorWindowRequestBusImpl
: public EditorWindowUIRequestBus::Handler
{
public:
/// Set the function to be called when entering ImGui Mode.
void SetEnableEditorUiFunc(const EnableUiFunction enableEditorUiFunc)
{
m_enableEditorUiFunc = AZStd::move(enableEditorUiFunc);
}
private:
// EditorWindowRequestBus
void SetEditorUiEnabled( [[maybe_unused]] bool enable) override
{
m_enableEditorUiFunc(enable);
}
EnableUiFunction m_enableEditorUiFunc; ///< Function to call when entering ImGui Mode.
};
} // namespace AzToolsFramework
#endif // AZTOOLSFRAMEWORK_EDITORWINDOWREQUESTBUS_H
@@ -222,6 +222,21 @@ namespace AzToolsFramework
void AssetBrowserTreeView::UpdateAfterFilter(bool hasFilter, bool selectFirstValidEntry)
{
const QModelIndexList& selectedIndexes = selectionModel()->selectedRows();
// If we've cleared the filter but had something selected, ensure it stays selected and visible.
if (!hasFilter && !selectedIndexes.isEmpty())
{
QModelIndex curIndex = selectedIndexes[0];
m_expandToEntriesByDefault = true;
m_treeStateSaver->ApplySnapshot();
setCurrentIndex(curIndex);
scrollTo(curIndex);
return;
}
// Flag our default expansion state so that we expand down to source entries after filtering
m_expandToEntriesByDefault = hasFilter;
// Then ask our state saver to apply its current snapshot again, falling back on asking us if entries should be expanded or not
@@ -597,22 +597,23 @@ namespace AzToolsFramework
{
if (m_rootInstance && m_playInEditorData.m_isEnabled)
{
auto end = m_playInEditorData.m_deactivatedEntities.rend();
for (auto it = m_playInEditorData.m_deactivatedEntities.rbegin(); it != end; ++it)
{
AZ_Assert(*it, "Invalid entity added to list for re-activation after play-in-editor stopped.");
(*it)->Activate();
}
m_playInEditorData.m_deactivatedEntities.clear();
AZ_Assert(m_playInEditorData.m_entities.IsSet(),
"Invalid Game Mode Entities Container encountered after play-in-editor stopped. "
"Confirm that the container was initialized correctly");
m_playInEditorData.m_entities.DespawnAllEntities();
m_playInEditorData.m_entities.Alert(
[assets = AZStd::move(m_playInEditorData.m_assets)]([[maybe_unused]]uint32_t generation) mutable
[assets = AZStd::move(m_playInEditorData.m_assets),
deactivatedEntities = AZStd::move(m_playInEditorData.m_deactivatedEntities)]
([[maybe_unused]]uint32_t generation) mutable
{
auto end = deactivatedEntities.rend();
for (auto it = deactivatedEntities.rbegin(); it != end; ++it)
{
AZ_Assert(*it, "Invalid entity added to list for re-activation after play-in-editor stopped.");
(*it)->Activate();
}
for (auto& asset : assets)
{
if (asset)
@@ -624,13 +625,21 @@ namespace AzToolsFramework
}
}
AZ::ScriptSystemRequestBus::Broadcast(&AZ::ScriptSystemRequests::GarbageCollect);
// This is a workaround until the replacement for GameEntityContext is done
AzFramework::GameEntityContextEventBus::Broadcast(&AzFramework::GameEntityContextEventBus::Events::OnGameEntitiesReset);
});
m_playInEditorData.m_entities.Clear();
// This is a workaround until the replacement for GameEntityContext is done
AzFramework::GameEntityContextEventBus::Broadcast(&AzFramework::GameEntityContextEventBus::Events::OnGameEntitiesReset);
}
// Game entity cleanup is queued onto the next tick via the DespawnEntities call.
// To avoid both game entities and Editor entities active at the same time
// we flush the tick queue to ensure the game entities are cleared first.
// The Alert callback that follows the DespawnEntities call will then reactivate the editor entities
// This should be considered temporary as a move to a less rigid event sequence that supports async entity clean up
// is the desired direction forward.
AZ::TickBus::ExecuteQueuedEvents();
m_playInEditorData.m_isEnabled = false;
}
@@ -0,0 +1,397 @@
/*
* 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/Input/QtEventToAzInputManager.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/Input/Buses/Notifications/InputChannelNotificationBus.h>
#include <AzFramework/Input/Buses/Requests/InputChannelRequestBus.h>
#include <QApplication>
#include <QCursor>
#include <QEvent>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QWidget>
namespace AzToolsFramework
{
void QtEventToAzInputMapper::InitializeKeyMappings()
{
// This assumes modifier keys (ctrl/shift/alt) map to the left control/shift/alt keys as Qt provides no way to disambiguate
// in a platform agnostic manner. This could be expanded later with a PAL mapping from native scan codes acquired from
// QKeyEvents, if needed.
m_keyMappings = { {
{ Qt::Key_0, AzFramework::InputDeviceKeyboard::Key::Alphanumeric0 },
{ Qt::Key_1, AzFramework::InputDeviceKeyboard::Key::Alphanumeric1 },
{ Qt::Key_2, AzFramework::InputDeviceKeyboard::Key::Alphanumeric2 },
{ Qt::Key_3, AzFramework::InputDeviceKeyboard::Key::Alphanumeric3 },
{ Qt::Key_4, AzFramework::InputDeviceKeyboard::Key::Alphanumeric4 },
{ Qt::Key_5, AzFramework::InputDeviceKeyboard::Key::Alphanumeric5 },
{ Qt::Key_6, AzFramework::InputDeviceKeyboard::Key::Alphanumeric6 },
{ Qt::Key_7, AzFramework::InputDeviceKeyboard::Key::Alphanumeric7 },
{ Qt::Key_8, AzFramework::InputDeviceKeyboard::Key::Alphanumeric8 },
{ Qt::Key_9, AzFramework::InputDeviceKeyboard::Key::Alphanumeric9 },
{ Qt::Key_A, AzFramework::InputDeviceKeyboard::Key::AlphanumericA },
{ Qt::Key_B, AzFramework::InputDeviceKeyboard::Key::AlphanumericB },
{ Qt::Key_C, AzFramework::InputDeviceKeyboard::Key::AlphanumericC },
{ Qt::Key_D, AzFramework::InputDeviceKeyboard::Key::AlphanumericD },
{ Qt::Key_E, AzFramework::InputDeviceKeyboard::Key::AlphanumericE },
{ Qt::Key_F, AzFramework::InputDeviceKeyboard::Key::AlphanumericF },
{ Qt::Key_G, AzFramework::InputDeviceKeyboard::Key::AlphanumericG },
{ Qt::Key_H, AzFramework::InputDeviceKeyboard::Key::AlphanumericH },
{ Qt::Key_I, AzFramework::InputDeviceKeyboard::Key::AlphanumericI },
{ Qt::Key_J, AzFramework::InputDeviceKeyboard::Key::AlphanumericJ },
{ Qt::Key_K, AzFramework::InputDeviceKeyboard::Key::AlphanumericK },
{ Qt::Key_L, AzFramework::InputDeviceKeyboard::Key::AlphanumericL },
{ Qt::Key_M, AzFramework::InputDeviceKeyboard::Key::AlphanumericM },
{ Qt::Key_N, AzFramework::InputDeviceKeyboard::Key::AlphanumericN },
{ Qt::Key_O, AzFramework::InputDeviceKeyboard::Key::AlphanumericO },
{ Qt::Key_P, AzFramework::InputDeviceKeyboard::Key::AlphanumericP },
{ Qt::Key_Q, AzFramework::InputDeviceKeyboard::Key::AlphanumericQ },
{ Qt::Key_R, AzFramework::InputDeviceKeyboard::Key::AlphanumericR },
{ Qt::Key_S, AzFramework::InputDeviceKeyboard::Key::AlphanumericS },
{ Qt::Key_T, AzFramework::InputDeviceKeyboard::Key::AlphanumericT },
{ Qt::Key_U, AzFramework::InputDeviceKeyboard::Key::AlphanumericU },
{ Qt::Key_V, AzFramework::InputDeviceKeyboard::Key::AlphanumericV },
{ Qt::Key_W, AzFramework::InputDeviceKeyboard::Key::AlphanumericW },
{ Qt::Key_X, AzFramework::InputDeviceKeyboard::Key::AlphanumericX },
{ Qt::Key_Y, AzFramework::InputDeviceKeyboard::Key::AlphanumericY },
{ Qt::Key_Z, AzFramework::InputDeviceKeyboard::Key::AlphanumericZ },
{ Qt::Key_Backspace, AzFramework::InputDeviceKeyboard::Key::EditBackspace },
{ Qt::Key_CapsLock, AzFramework::InputDeviceKeyboard::Key::EditCapsLock },
{ Qt::Key_Enter, AzFramework::InputDeviceKeyboard::Key::EditEnter },
{ Qt::Key_Space, AzFramework::InputDeviceKeyboard::Key::EditSpace },
{ Qt::Key_Tab, AzFramework::InputDeviceKeyboard::Key::EditTab },
{ Qt::Key_Escape, AzFramework::InputDeviceKeyboard::Key::Escape },
{ Qt::Key_F1, AzFramework::InputDeviceKeyboard::Key::Function01 },
{ Qt::Key_F2, AzFramework::InputDeviceKeyboard::Key::Function02 },
{ Qt::Key_F3, AzFramework::InputDeviceKeyboard::Key::Function03 },
{ Qt::Key_F4, AzFramework::InputDeviceKeyboard::Key::Function04 },
{ Qt::Key_F5, AzFramework::InputDeviceKeyboard::Key::Function05 },
{ Qt::Key_F6, AzFramework::InputDeviceKeyboard::Key::Function06 },
{ Qt::Key_F7, AzFramework::InputDeviceKeyboard::Key::Function07 },
{ Qt::Key_F8, AzFramework::InputDeviceKeyboard::Key::Function08 },
{ Qt::Key_F9, AzFramework::InputDeviceKeyboard::Key::Function09 },
{ Qt::Key_F10, AzFramework::InputDeviceKeyboard::Key::Function10 },
{ Qt::Key_F11, AzFramework::InputDeviceKeyboard::Key::Function11 },
{ Qt::Key_F12, AzFramework::InputDeviceKeyboard::Key::Function12 },
{ Qt::Key_F13, AzFramework::InputDeviceKeyboard::Key::Function13 },
{ Qt::Key_F14, AzFramework::InputDeviceKeyboard::Key::Function14 },
{ Qt::Key_F15, AzFramework::InputDeviceKeyboard::Key::Function15 },
{ Qt::Key_F16, AzFramework::InputDeviceKeyboard::Key::Function16 },
{ Qt::Key_F17, AzFramework::InputDeviceKeyboard::Key::Function17 },
{ Qt::Key_F18, AzFramework::InputDeviceKeyboard::Key::Function18 },
{ Qt::Key_F19, AzFramework::InputDeviceKeyboard::Key::Function19 },
{ Qt::Key_F20, AzFramework::InputDeviceKeyboard::Key::Function20 },
{ Qt::Key_Alt, AzFramework::InputDeviceKeyboard::Key::ModifierAltL },
{ Qt::Key_Control, AzFramework::InputDeviceKeyboard::Key::ModifierCtrlL },
{ Qt::Key_Shift, AzFramework::InputDeviceKeyboard::Key::ModifierShiftL },
{ Qt::Key_Super_L, AzFramework::InputDeviceKeyboard::Key::ModifierSuperL },
{ Qt::Key_Super_R, AzFramework::InputDeviceKeyboard::Key::ModifierSuperR },
{ Qt::Key_Down, AzFramework::InputDeviceKeyboard::Key::NavigationArrowDown },
{ Qt::Key_Left, AzFramework::InputDeviceKeyboard::Key::NavigationArrowLeft },
{ Qt::Key_Right, AzFramework::InputDeviceKeyboard::Key::NavigationArrowRight },
{ Qt::Key_Up, AzFramework::InputDeviceKeyboard::Key::NavigationArrowUp },
{ Qt::Key_Delete, AzFramework::InputDeviceKeyboard::Key::NavigationDelete },
{ Qt::Key_End, AzFramework::InputDeviceKeyboard::Key::NavigationEnd },
{ Qt::Key_Home, AzFramework::InputDeviceKeyboard::Key::NavigationHome },
{ Qt::Key_Insert, AzFramework::InputDeviceKeyboard::Key::NavigationInsert },
{ Qt::Key_PageDown, AzFramework::InputDeviceKeyboard::Key::NavigationPageDown },
{ Qt::Key_PageUp, AzFramework::InputDeviceKeyboard::Key::NavigationPageUp },
{ Qt::Key_Apostrophe, AzFramework::InputDeviceKeyboard::Key::PunctuationApostrophe },
{ Qt::Key_Backslash, AzFramework::InputDeviceKeyboard::Key::PunctuationBackslash },
{ Qt::Key_BracketLeft, AzFramework::InputDeviceKeyboard::Key::PunctuationBracketL },
{ Qt::Key_BracketRight, AzFramework::InputDeviceKeyboard::Key::PunctuationBracketR },
{ Qt::Key_Comma, AzFramework::InputDeviceKeyboard::Key::PunctuationComma },
{ Qt::Key_Equal, AzFramework::InputDeviceKeyboard::Key::PunctuationEquals },
{ Qt::Key_hyphen, AzFramework::InputDeviceKeyboard::Key::PunctuationHyphen },
{ Qt::Key_Period, AzFramework::InputDeviceKeyboard::Key::PunctuationPeriod },
{ Qt::Key_Semicolon, AzFramework::InputDeviceKeyboard::Key::PunctuationSemicolon },
{ Qt::Key_Slash, AzFramework::InputDeviceKeyboard::Key::PunctuationSlash },
{ Qt::Key_QuoteLeft, AzFramework::InputDeviceKeyboard::Key::PunctuationTilde },
{ Qt::Key_Pause, AzFramework::InputDeviceKeyboard::Key::WindowsSystemPause },
{ Qt::Key_Print, AzFramework::InputDeviceKeyboard::Key::WindowsSystemPrint },
{ Qt::Key_ScrollLock, AzFramework::InputDeviceKeyboard::Key::WindowsSystemScrollLock },
} };
}
void QtEventToAzInputMapper::InitializeMouseButtonMappings()
{
m_mouseButtonMappings = { {
{ Qt::MouseButton::LeftButton, AzFramework::InputDeviceMouse::Button::Left },
{ Qt::MouseButton::RightButton, AzFramework::InputDeviceMouse::Button::Right },
{ Qt::MouseButton::MiddleButton, AzFramework::InputDeviceMouse::Button::Middle },
{ Qt::MouseButton::ExtraButton1, AzFramework::InputDeviceMouse::Button::Other1 },
{ Qt::MouseButton::ExtraButton2, AzFramework::InputDeviceMouse::Button::Other2 },
} };
}
// Currently this is only set for modifier keys.
// This should only be expanded sparingly, any keys handled here will not be bubbled up to the shortcut system.
// ex: If Key_S was here, the viewport would consume S key presses before the application could process a QAction with a Ctrl+S
// shortcut.
void QtEventToAzInputMapper::InitializeHighPriorityKeys()
{
m_highPriorityKeys = { Qt::Key_Alt, Qt::Key_Control, Qt::Key_Shift, Qt::Key_Super_L, Qt::Key_Super_R };
}
QtEventToAzInputMapper::EditorQtKeyboardDevice::EditorQtKeyboardDevice(AzFramework::InputDeviceId id)
: AzFramework::InputDeviceKeyboard(id)
{
// Disable all platform native processing in favor of our Qt event handling
SetImplementation(nullptr);
}
QtEventToAzInputMapper::EditorQtMouseDevice::EditorQtMouseDevice(AzFramework::InputDeviceId id)
: AzFramework::InputDeviceMouse(id)
{
// Disable all platform native processing in favor of our Qt event handling
SetImplementation(nullptr);
}
QtEventToAzInputMapper::QtEventToAzInputMapper(QWidget* sourceWidget, int syntheticDeviceId)
: QObject(sourceWidget)
, m_sourceWidget(sourceWidget)
, m_keyboardModifiers(AZStd::make_shared<AzFramework::ModifierKeyStates>())
, m_cursorPosition(AZStd::make_shared<AzFramework::InputChannel::PositionData2D>())
{
InitializeKeyMappings();
InitializeMouseButtonMappings();
InitializeHighPriorityKeys();
// Add an arbitrary offset to our device index to avoid collision with real physical device index.
// We still have to use the keyboard and mouse device channel names because input channels are only addressed
// by their own name and their device index, so overlapping input channels between devices would conflict.
constexpr AZ::u32 syntheticDeviceOffset = 1000;
const AzFramework::InputDeviceId keyboardDeviceId(
AzFramework::InputDeviceKeyboard::Id.GetName(), syntheticDeviceId + syntheticDeviceOffset);
const AzFramework::InputDeviceId mouseDeviceId(
AzFramework::InputDeviceMouse::Id.GetName(), syntheticDeviceId + syntheticDeviceOffset);
m_keyboardDevice = AZStd::make_unique<EditorQtKeyboardDevice>(keyboardDeviceId);
m_mouseDevice = AZStd::make_unique<EditorQtMouseDevice>(mouseDeviceId);
AddChannels(m_keyboardDevice->m_allChannelsById);
AddChannels(m_mouseDevice->m_allChannelsById);
// Install a global event filter to ensure we don't miss mouse and key release events.
QApplication::instance()->installEventFilter(this);
}
bool QtEventToAzInputMapper::HandlesInputEvent(const AzFramework::InputChannel& channel) const
{
const AzFramework::InputChannelId& channelId = channel.GetInputChannelId();
if (channelId == AzFramework::InputDeviceMouse::Movement::X || channelId == AzFramework::InputDeviceMouse::Movement::Y)
{
return false;
}
// We map keyboard and mouse events from Qt, so flag all events coming from those devices
// as handled by our synthetic event system.
const AzFramework::InputDeviceId& deviceId = channel.GetInputDevice().GetInputDeviceId();
return deviceId.GetNameCrc32() == AzFramework::InputDeviceMouse::Id.GetNameCrc32() ||
deviceId.GetNameCrc32() == AzFramework::InputDeviceKeyboard::Id.GetNameCrc32();
}
void QtEventToAzInputMapper::SetEnabled(bool enabled)
{
m_enabled = enabled;
if (!enabled)
{
// Send an internal focus change event to reset our input state to fresh if we're disabled.
HandleFocusChange(nullptr);
}
}
bool QtEventToAzInputMapper::eventFilter(QObject* object, QEvent* event)
{
// Abort if processing isn't enabled.
if (!m_enabled)
{
return false;
}
// Because there's no "end" to mouse movement and wheel events, we reset mouse movement channels that have been opened
// during the next processed non-mouse event.
if (m_mouseChannelsNeedUpdate && event->type() != QEvent::Type::MouseMove && event->type() != QEvent::Type::Wheel)
{
m_cursorPosition->m_normalizedPositionDelta = AZ::Vector2::CreateZero();
ProcessPendingMouseEvents();
m_mouseChannelsNeedUpdate = false;
}
// 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 && event->type() != QEvent::Type::KeyRelease && event->type() != QEvent::Type::MouseButtonRelease)
{
return false;
}
// If our focus changes, go ahead and reset all input devices.
if (event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut)
{
HandleFocusChange(event);
}
// Map key events to input channels.
// ShortcutOverride is used in lieu of KeyPress for high priority input channels like Alt
// that need to be accepted and stopped before they bubble up and cause unintended behavior.
else if (
event->type() == QEvent::Type::KeyPress || event->type() == QEvent::Type::KeyRelease ||
event->type() == QEvent::Type::ShortcutOverride)
{
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
HandleKeyEvent(keyEvent);
}
// Map mouse events to input channels.
else if (event->type() == QEvent::Type::MouseButtonPress || event->type() == QEvent::Type::MouseButtonRelease || event->type() == QEvent::Type::MouseButtonDblClick)
{
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
HandleMouseButtonEvent(mouseEvent);
}
// Map mouse movement to the movement input channels.
// This includes SystemCursorPosition alongside Movement::X and Movement::Y.
else if (event->type() == QEvent::Type::MouseMove)
{
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
HandleMouseMoveEvent(mouseEvent);
}
// Map wheel events to the mouse Z movement channel.
else if (event->type() == QEvent::Type::Wheel)
{
QWheelEvent* wheelEvent = static_cast<QWheelEvent*>(event);
HandleWheelEvent(wheelEvent);
}
return false;
}
void QtEventToAzInputMapper::NotifyUpdateChannelIfNotIdle(const AzFramework::InputChannel* channel, QEvent* event)
{
if (channel->GetState() != AzFramework::InputChannel::State::Idle)
{
emit InputChannelUpdated(channel, event);
}
}
void QtEventToAzInputMapper::ProcessPendingMouseEvents()
{
auto systemCursorChannel =
GetInputChannel<AzFramework::InputChannelDeltaWithSharedPosition2D>(AzFramework::InputDeviceMouse::SystemCursorPosition);
auto mouseWheelChannel =
GetInputChannel<AzFramework::InputChannelDeltaWithSharedPosition2D>(AzFramework::InputDeviceMouse::Movement::Z);
systemCursorChannel->ProcessRawInputEvent(m_cursorPosition->m_normalizedPositionDelta.GetLength());
mouseWheelChannel->ProcessRawInputEvent(0.f);
NotifyUpdateChannelIfNotIdle(systemCursorChannel, nullptr);
NotifyUpdateChannelIfNotIdle(mouseWheelChannel, nullptr);
}
void QtEventToAzInputMapper::HandleMouseButtonEvent(QMouseEvent* mouseEvent)
{
const Qt::MouseButton button = mouseEvent->button();
if (auto buttonIt = m_mouseButtonMappings.find(button); buttonIt != m_mouseButtonMappings.end())
{
auto buttonChannel = GetInputChannel<AzFramework::InputChannelDigitalWithSharedPosition2D>(buttonIt->second);
if (buttonChannel)
{
if (mouseEvent->type() != QEvent::Type::MouseButtonRelease)
{
buttonChannel->UpdateState(true);
}
else
{
buttonChannel->UpdateState(false);
}
NotifyUpdateChannelIfNotIdle(buttonChannel, mouseEvent);
}
}
}
void QtEventToAzInputMapper::HandleMouseMoveEvent(QMouseEvent* mouseEvent)
{
const QPoint mousePos = mouseEvent->pos();
const float normalizedX = aznumeric_cast<float>(mousePos.x()) / aznumeric_cast<float>(m_sourceWidget->width());
const float normalizedY = aznumeric_cast<float>(mousePos.y()) / aznumeric_cast<float>(m_sourceWidget->height());
const AZ::Vector2 normalizedPosition(normalizedX, normalizedY);
m_cursorPosition->m_normalizedPositionDelta = normalizedPosition - m_cursorPosition->m_normalizedPosition;
m_cursorPosition->m_normalizedPosition = normalizedPosition;
ProcessPendingMouseEvents();
m_mouseChannelsNeedUpdate = true;
}
void QtEventToAzInputMapper::HandleKeyEvent(QKeyEvent* keyEvent)
{
// Ignore key repeat events, they're unrelated to actual physical button presses.
if (keyEvent->isAutoRepeat())
{
return;
}
const Qt::Key key = static_cast<Qt::Key>(keyEvent->key());
// For ShortcutEvent, only continue processing if we're in the HighPriorityKeys set.
if (keyEvent->type() != QEvent::Type::ShortcutOverride || m_highPriorityKeys.find(key) != m_highPriorityKeys.end())
{
if (auto keyIt = m_keyMappings.find(key); keyIt != m_keyMappings.end())
{
auto keyChannel = GetInputChannel<AzFramework::InputChannelDigitalWithSharedModifierKeyStates>(keyIt->second);
if (keyChannel)
{
if (keyEvent->type() == QEvent::Type::KeyPress || keyEvent->type() == QEvent::Type::ShortcutOverride)
{
keyChannel->UpdateState(true);
}
else
{
keyChannel->UpdateState(false);
}
NotifyUpdateChannelIfNotIdle(keyChannel, keyEvent);
}
}
}
}
void QtEventToAzInputMapper::HandleWheelEvent(QWheelEvent* wheelEvent)
{
auto cursorZChannel =
GetInputChannel<AzFramework::InputChannelDeltaWithSharedPosition2D>(AzFramework::InputDeviceMouse::Movement::Z);
const QPoint angleDelta = wheelEvent->angleDelta();
// Check both angles, as the alt modifier can change the wheel direction.
int wheelAngle = angleDelta.x();
if (wheelAngle == 0)
{
wheelAngle = angleDelta.y();
}
cursorZChannel->ProcessRawInputEvent(aznumeric_cast<float>(wheelAngle));
NotifyUpdateChannelIfNotIdle(cursorZChannel, wheelEvent);
m_mouseChannelsNeedUpdate = true;
}
void QtEventToAzInputMapper::HandleFocusChange(QEvent* event)
{
for (auto& channelData : m_channels)
{
// If resetting the input device changed the channel state, submit it to the mapped channel list
// for processing.
if (channelData.second->IsActive())
{
channelData.second->UpdateState(false);
NotifyUpdateChannelIfNotIdle(channelData.second, event);
}
}
m_mouseChannelsNeedUpdate = false;
}
} // namespace AzToolsFramework
@@ -0,0 +1,152 @@
/*
* 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
#if !defined(Q_MOC_RUN)
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzFramework/Input/Channels/InputChannel.h>
#include <AzFramework/Input/Channels/InputChannelDeltaWithSharedPosition2D.h>
#include <AzFramework/Input/Channels/InputChannelDigitalWithSharedModifierKeyStates.h>
#include <AzFramework/Input/Channels/InputChannelDigitalWithSharedPosition2D.h>
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include <QEvent>
#include <QObject>
#endif //! defined(Q_MOC_RUN)
class QWidget;
class QKeyEvent;
class QMouseEvent;
class QWheelEvent;
namespace AzToolsFramework
{
//! Maps events from the Qt input system to synthetic InputChannels in AzFramework
//! that can be used by AzFramework::ViewportControllers.
class QtEventToAzInputMapper final : public QObject
{
Q_OBJECT
public:
QtEventToAzInputMapper(QWidget* sourceWidget, int syntheticDeviceId = 0);
~QtEventToAzInputMapper() = default;
//! Queries whether a given input channel has a synthetic equivalent mapped
//! by this system.
//! \returns true if the channel is handled by MapQtEventToAzInput.
bool HandlesInputEvent(const AzFramework::InputChannel& channel) const;
//! Sets whether or not this input mapper should be updating its input channels from Qt events.
void SetEnabled(bool enabled);
// QObject overrides...
bool eventFilter(QObject* object, QEvent* event) override;
signals:
//! This signal fires whenever the state of the specified input channel changes.
//! This is determined by Qt events dispatched to the source widget.
//! \param channel The AZ input channel that has been updated.
//! \param event The underlying Qt event that triggered this change, if applicable.
void InputChannelUpdated(const AzFramework::InputChannel* channel, QEvent* event);
private:
// Gets an input channel of the specified type by ID.
template<class TInputChannel>
TInputChannel* GetInputChannel(const AzFramework::InputChannelId& id)
{
auto channelIt = m_channels.find(id);
if (channelIt != m_channels.end())
{
return static_cast<TInputChannel*>(channelIt->second);
}
return nullptr;
}
// Adds channels from the specified channel container to our input channel ID -> input channel lookup table.
// Used for rapid lookup.
template <class TContainer>
void AddChannels(const TContainer& container)
{
for (const auto& channelData : container)
{
// Break const as we're taking these input channels from devices we own.
m_channels.emplace(channelData.first, const_cast<AzFramework::InputChannel*>(channelData.second));
}
}
// Our synthetic Keyboard device, does no internal keyboard handling and instead listens to this class for updates.
class EditorQtKeyboardDevice : public AzFramework::InputDeviceKeyboard
{
public:
EditorQtKeyboardDevice(AzFramework::InputDeviceId id);
friend class QtEventToAzInputMapper;
};
// Our synthetic Mouse device, does no internal keyboard handling and instead listens to this class for updates.
class EditorQtMouseDevice : public AzFramework::InputDeviceMouse
{
public:
EditorQtMouseDevice(AzFramework::InputDeviceId id);
friend class QtEventToAzInputMapper;
};
// Emits InputChannelUpdated if channel has transitioned in state (i.e. has gone from active to inactive or vice versa).
void NotifyUpdateChannelIfNotIdle(const AzFramework::InputChannel* channel, QEvent* event);
// Processes any pending mouse movement events, this allows mouse movement channels to close themselves.
void ProcessPendingMouseEvents();
// Handle mouse click events.
void HandleMouseButtonEvent(QMouseEvent* mouseEvent);
// Handle mouse move events.
void HandleMouseMoveEvent(QMouseEvent* mouseEvent);
// Handles key press / release events (or ShortcutOverride events for keys listed in m_highPriorityKeys).
void HandleKeyEvent(QKeyEvent* keyEvent);
// Handles mouse wheel events.
void HandleWheelEvent(QWheelEvent* wheelEvent);
// Handles focus change events.
void HandleFocusChange(QEvent* event);
// Populates m_keyMappings.
void InitializeKeyMappings();
// Populates m_mouseButtonMappings.
void InitializeMouseButtonMappings();
// Populates m_highPriorityKeys.
void InitializeHighPriorityKeys();
// The current keyboard modifier state used by our synthetic key input channels.
AZStd::shared_ptr<AzFramework::ModifierKeyStates> m_keyboardModifiers;
// The current normalized cursor position used by our synthetic system cursor event.
AZStd::shared_ptr<AzFramework::InputChannel::PositionData2D> m_cursorPosition;
// A lookup table for Qt key -> AZ input channel.
AZStd::unordered_map<Qt::Key, AzFramework::InputChannelId> m_keyMappings;
// A lookup table for Qt mouse button -> AZ input channel.
AZStd::unordered_map<Qt::MouseButton, AzFramework::InputChannelId> m_mouseButtonMappings;
// A set of high priority keys that need to be processed at the ShortcutOverride level instead of the
// KeyEvent level. This prevents e.g. the main menu bar from processing a press of the "alt" key when the
// viewport consumes the event.
AZStd::unordered_set<Qt::Key> m_highPriorityKeys;
// A lookup table for AZ input channel ID -> physical input channel on our mouse or keyboard device.
AZStd::unordered_map<AzFramework::InputChannelId, AzFramework::InputChannel*> m_channels;
// The source widget to map events from, used to calculate the relative mouse position within the widget bounds.
QWidget* m_sourceWidget;
// Flags when mouse movement channels have been opened and may need to be closed (as there are no movement ended events).
bool m_mouseChannelsNeedUpdate = false;
// Flags whether or not Qt events should currently be processed.
bool m_enabled = true;
// Our viewport-specific AZ devices. We control their internal input channel states.
AZStd::unique_ptr<EditorQtMouseDevice> m_mouseDevice;
AZStd::unique_ptr<EditorQtKeyboardDevice> m_keyboardDevice;
};
} // namespace AzToolsFramework
@@ -47,7 +47,7 @@ namespace AzToolsFramework
return valueIterator->value;
}
bool StoreInstanceInPrefabDom(const Instance& instance, PrefabDom& prefabDom)
bool StoreInstanceInPrefabDom(const Instance& instance, PrefabDom& prefabDom, StoreInstanceFlags flags)
{
InstanceEntityIdMapper entityIdMapper;
entityIdMapper.SetStoringInstance(instance);
@@ -58,6 +58,11 @@ namespace AzToolsFramework
settings.m_metadata.Add(static_cast<AZ::JsonEntityIdSerializer::JsonEntityIdMapper*>(&entityIdMapper));
settings.m_metadata.Add(&entityIdMapper);
if ((flags & StoreInstanceFlags::StripDefaultValues) != StoreInstanceFlags::StripDefaultValues)
{
settings.m_keepDefaults = true;
}
AZ::JsonSerializationResult::ResultCode result =
AZ::JsonSerialization::Store(prefabDom, prefabDom.GetAllocator(), instance, settings);
@@ -36,13 +36,25 @@ namespace AzToolsFramework
PrefabDomValueReference FindPrefabDomValue(PrefabDomValue& parentValue, const char* valueName);
PrefabDomValueConstReference FindPrefabDomValue(const PrefabDomValue& parentValue, const char* valueName);
enum class StoreInstanceFlags : uint8_t
{
//! No flags used during the call to LoadInstanceFromPrefabDom.
None = 0,
//! By default an instance will be stored with default values. In cases where we want to store less json without defaults
//! such as saving to disk, this flag will control that behavior.
StripDefaultValues = 1 << 0
};
AZ_DEFINE_ENUM_BITWISE_OPERATORS(StoreInstanceFlags);
/**
* Stores a valid Prefab Instance within a Prefab Dom. Useful for generating Templates
* @param instance The instance to store
* @param prefabDom The prefabDom that will be used to store the Instance data
* @param flags Controls behavior such as whether to store default values
* @return bool on whether the operation succeeded
*/
bool StoreInstanceInPrefabDom(const Instance& instance, PrefabDom& prefabDom);
bool StoreInstanceInPrefabDom(const Instance& instance, PrefabDom& prefabDom, StoreInstanceFlags flags = StoreInstanceFlags::None);
enum class LoadInstanceFlags : uint8_t
{
@@ -52,13 +64,13 @@ namespace AzToolsFramework
//! unique, e.g. when they are duplicates of live entities, this flag will assign them a random new id.
AssignRandomEntityId = 1 << 0
};
AZ_DEFINE_ENUM_BITWISE_OPERATORS(LoadInstanceFlags)
AZ_DEFINE_ENUM_BITWISE_OPERATORS(LoadInstanceFlags);
/**
* Loads a valid Prefab Instance from a Prefab Dom. Useful for generating Instances.
* @param instance The Instance to load.
* @param prefabDom The prefabDom that will be used to load the Instance data.
* @param shouldClearContainers Whether to clear containers in Instance while loading.
* @param flags Controls behavior such as random entity id assignment.
* @return bool on whether the operation succeeded.
*/
bool LoadInstanceFromPrefabDom(
@@ -172,7 +172,7 @@ namespace AzToolsFramework
progressedFilePathsSet.emplace(relativePath);
// Get 'Instances' value from Template.
bool isLoadedWithErrors = false;
bool isLoadSuccessful = true;
PrefabDomValueReference instancesReference = newTemplate.GetInstancesValue();
if (instancesReference.has_value())
{
@@ -185,7 +185,7 @@ namespace AzToolsFramework
{
if (!LoadNestedInstance(instanceIterator, newTemplateId, progressedFilePathsSet))
{
isLoadedWithErrors = true;
isLoadSuccessful = false;
AZ_Error(
"Prefab", false,
"PrefabLoader::LoadTemplate - "
@@ -196,7 +196,10 @@ namespace AzToolsFramework
}
}
}
newTemplate.MarkAsLoadedWithErrors(isLoadedWithErrors);
isLoadSuccessful &= SanitizeLoadedTemplate(newTemplate.GetPrefabDom());
newTemplate.MarkAsLoadedWithErrors(!isLoadSuccessful);
// Un-mark the file as being in progress.
progressedFilePathsSet.erase(originPath);
@@ -277,6 +280,63 @@ namespace AzToolsFramework
return !nestedTemplateReference->get().IsLoadedWithErrors();
}
bool PrefabLoader::SanitizeLoadedTemplate(PrefabDomReference loadedTemplateDom)
{
// Prefabs are stored to disk with default values stripped. However, while in memory, we need those default values to be
// present to make patches work consistently. To accomplish this, we'll instantiate the Dom, then serialize the instance
// back into a Dom with all of the default values preserved.
// Note that this is the default behavior in Prefab serialization, so we don't need to specify StoreInstanceFlags.
if (!loadedTemplateDom)
{
return false;
}
Instance loadedPrefabInstance;
if (!PrefabDomUtils::LoadInstanceFromPrefabDom(loadedPrefabInstance, loadedTemplateDom->get()))
{
return false;
}
PrefabDom storedPrefabDom(&loadedTemplateDom->get().GetAllocator());
if (!PrefabDomUtils::StoreInstanceInPrefabDom(loadedPrefabInstance, storedPrefabDom))
{
return false;
}
loadedTemplateDom->get().CopyFrom(storedPrefabDom, loadedTemplateDom->get().GetAllocator());
return true;
}
bool PrefabLoader::SanitizeSavingTemplate(PrefabDomReference savingTemplateDom)
{
// Prefabs are stored in memory with default values spelled out to make patches work consistently. However, when we store them
// to disk, we strip those default values to save on file size. To accomplish this, we'll instantiate the Dom, then serialize
// the instance back into a Dom with all of the default values stripped.
if (!savingTemplateDom)
{
return false;
}
Instance savingPrefabInstance;
if (!PrefabDomUtils::LoadInstanceFromPrefabDom(savingPrefabInstance, savingTemplateDom->get()))
{
return false;
}
PrefabDom storedPrefabDom(&savingTemplateDom->get().GetAllocator());
if (!PrefabDomUtils::StoreInstanceInPrefabDom(savingPrefabInstance, storedPrefabDom,
PrefabDomUtils::StoreInstanceFlags::StripDefaultValues))
{
return false;
}
savingTemplateDom->get().CopyFrom(storedPrefabDom, savingTemplateDom->get().GetAllocator());
return true;
}
bool PrefabLoader::SaveTemplate(TemplateId templateId)
{
const auto& domAndFilepath = StoreTemplateIntoFileFormat(templateId);
@@ -395,7 +455,7 @@ namespace AzToolsFramework
// Make a copy of a our prefab DOM where nested instances become file references with patch data
PrefabDom templateDomToSave;
if (!templateToSave.CopyTemplateIntoPrefabFileFormat(templateDomToSave))
if (!CopyTemplateIntoPrefabFileFormat(templateToSave, templateDomToSave))
{
AZ_Error(
"Prefab", false,
@@ -410,6 +470,80 @@ namespace AzToolsFramework
return { { AZStd::move(templateDomToSave), templateToSave.GetFilePath() } };
}
bool PrefabLoader::CopyTemplateIntoPrefabFileFormat(TemplateReference templateRef, PrefabDom& output)
{
AZ_Assert(
templateRef.has_value(),
"CopyTemplateIntoPrefabFileFormat called on empty template reference."
);
PrefabDom& prefabDom = templateRef->get().GetPrefabDom();
// Start by making a copy of our dom
output.CopyFrom(prefabDom, prefabDom.GetAllocator());
SanitizeSavingTemplate(output);
for (const LinkId& linkId : templateRef->get().GetLinks())
{
AZStd::optional<AZStd::reference_wrapper<Link>> findLinkResult = m_prefabSystemComponentInterface->FindLink(linkId);
if (!findLinkResult.has_value())
{
AZ_Error(
"Prefab", false,
"Link with id %llu could not be found while attempting to store "
"Prefab Template with source path %s in Prefab File format. "
"Unable to proceed.",
linkId, templateRef->get().GetFilePath().c_str());
return false;
}
if (!findLinkResult->get().IsValid())
{
AZ_Error(
"Prefab", false,
"Link with id %llu and is invalid during attempt to store "
"Prefab Template with source path %s in Prefab File format. "
"Unable to Proceed.",
linkId, templateRef->get().GetFilePath().c_str());
return false;
}
Link& link = findLinkResult->get();
PrefabDomPath instancePath = link.GetInstancePath();
PrefabDom& linkDom = link.GetLinkDom();
// Get the instance value of the Template copy
// This currently stores a fully realized nested Template Dom
PrefabDomValue* instanceValue = instancePath.Get(output);
if (!instanceValue)
{
AZ_Error(
"Prefab", false,
"Template::CopyTemplateIntoPrefabFileFormat: Unable to recover nested instance Dom value from link with id %llu "
"while attempting to store a collapsed version of a Prefab Template with source path %s. Unable to proceed.",
linkId, templateRef->get().GetFilePath().c_str());
return false;
}
// Copy the contents of the Link to overwrite our Template Dom copies Instance
// The instance is now "collapsed" as it contains the file reference and patches from the link
instanceValue->CopyFrom(linkDom, prefabDom.GetAllocator());
}
// Remove Source parameter from the dom. It will be added on file load, and should not be stored to disk.
PrefabDomPath sourcePath = PrefabDomPath((AZStd::string("/") + PrefabDomUtils::SourceName).c_str());
sourcePath.Erase(output);
return true;
}
bool PrefabLoader::IsValidPrefabPath(AZ::IO::PathView path)
{
// Check for OS invalid character and paths ending on '/' '\\' separators as final char
@@ -25,6 +25,8 @@ namespace AzToolsFramework
namespace Prefab
{
class PrefabSystemComponentInterface;
class Template;
using TemplateReference = AZStd::optional<AZStd::reference_wrapper<Template>>;
/**
* The Prefab Loader helps saving/loading Prefab files.
@@ -106,6 +108,16 @@ namespace AzToolsFramework
static bool IsValidPrefabPath(AZ::IO::PathView path);
private:
/**
* Copies the template dom provided and manipulates it into the proper format to be saved to disk.
* @param templateRef The template whose dom we want to transform into the proper format to be saved to disk.
* @param[out] output The PrefabDom reference we want to store the result into.
* @return True if the operation was completed correctly, false otherwise.
*/
bool CopyTemplateIntoPrefabFileFormat(
TemplateReference templateRef,
PrefabDom& output
);
/**
* Load Prefab Template from given file path to memory and return the id of loaded Template.
@@ -141,6 +153,20 @@ namespace AzToolsFramework
TemplateId targetTemplateId,
AZStd::unordered_set<AZ::IO::Path>& progressedFilePathsSet);
/*
* Manipulate the provided PrefabDom into the right format to be stored in memory for editor usage.
* @param loadedTemplateDom The template to manipulate. Changes will be applied in place.
* @return True if the manipulations where applied correctly, false otherwise.
*/
bool SanitizeLoadedTemplate(PrefabDomReference loadedTemplateDom);
/*
* Manipulate the provided PrefabDom into the right format to be stored to disk.
* @param savingTemplateDom The template to manipulate. Changes will be applied in place.
* @return True if the manipulations where applied correctly, false otherwise.
*/
bool SanitizeSavingTemplate(PrefabDomReference savingTemplateDom);
//! Retrieves Dom content and its path from a template id
AZStd::optional<AZStd::pair<PrefabDom, AZ::IO::Path>> StoreTemplateIntoFileFormat(TemplateId templateId);
@@ -631,13 +631,16 @@ namespace AzToolsFramework
//member itself, so we need to move instancesValue to the correct position for the next insert
memberFound = instancesValue->get().FindMember(PrefabDomUtils::InstancesName);
instancesValue = memberFound->value;
instancesValue->get().SetObject();
}
else
{
instancesValue = memberFound->value;
}
if (!instancesValue->get().IsObject())
{
instancesValue->get().SetObject();
}
// Only add the instance if it's not there already
if (instancesValue->get().FindMember(rapidjson::StringRef(instanceAlias.c_str())) == instancesValue->get().MemberEnd())
{
@@ -138,77 +138,6 @@ namespace AzToolsFramework
return m_prefabDom;
}
bool Template::CopyTemplateIntoPrefabFileFormat(PrefabDom& output)
{
// Start by making a copy of our dom
output.CopyFrom(m_prefabDom, m_prefabDom.GetAllocator());
PrefabSystemComponentInterface* prefabSystemComponentInterface =
AZ::Interface<PrefabSystemComponentInterface>::Get();
AZ_Assert(prefabSystemComponentInterface,
"Prefab - Prefab System Component Interface is null while attempting "
"to copy Template associated with Prefab file %s into Prefab File format",
m_filePath.c_str());
for (const LinkId& linkId : m_links)
{
AZStd::optional<AZStd::reference_wrapper<Link>> findLinkResult =
prefabSystemComponentInterface->FindLink(linkId);
if (!findLinkResult.has_value())
{
AZ_Error("Prefab", false,
"Link with id %llu could not be found while attempting to store "
"Prefab Template with source path %s in Prefab File format. "
"Unable to proceed.",
linkId, m_filePath.c_str());
return false;
}
if (!findLinkResult->get().IsValid())
{
AZ_Error("Prefab", false,
"Link with id %llu and is invalid during attempt to store "
"Prefab Template with source path %s in Prefab File format. "
"Unable to Proceed.",
linkId, m_filePath.c_str());
return false;
}
Link& link = findLinkResult->get();
PrefabDomPath instancePath = link.GetInstancePath();
PrefabDom& linkDom = link.GetLinkDom();
// Get the instance value of the Template copy
// This currently stores a fully realized nested Template Dom
PrefabDomValue* instanceValue = instancePath.Get(output);
if (!instanceValue)
{
AZ_Error("Prefab", false,
"Template::CopyTemplateIntoPrefabFileFormat: Unable to recover nested instance Dom value from link with id %llu "
"while attempting to store a collapsed version of a Prefab Template with source path %s. Unable to proceed.",
linkId, m_filePath.c_str());
return false;
}
// Copy the contents of the Link to overwrite our Template Dom copies Instance
// The instance is now "collapsed" as it contains the file reference and patches from the link
instanceValue->CopyFrom(linkDom, m_prefabDom.GetAllocator());
}
// Remove Source parameter from the dom. It will be added on file load, and should not be stored to disk.
PrefabDomPath sourcePath = PrefabDomPath((AZStd::string("/") + PrefabDomUtils::SourceName).c_str());
sourcePath.Erase(output);
return true;
}
PrefabDomValueReference Template::GetInstancesValue()
{
if (!IsValid())
@@ -58,8 +58,6 @@ namespace AzToolsFramework
PrefabDom& GetPrefabDom();
const PrefabDom& GetPrefabDom() const;
bool CopyTemplateIntoPrefabFileFormat(PrefabDom& output);
PrefabDomValueReference GetInstancesValue();
PrefabDomValueConstReference GetInstancesValue() const;
@@ -151,6 +151,12 @@ namespace LegacyFramework
specializations.Append("tools");
}
void Application::CreateReflectionManager()
{
AZ::ComponentApplication::CreateReflectionManager();
GetSerializeContext()->CreateEditContext();
}
int Application::Run(const ApplicationDesc& desc)
{
if (!AZ::AllocatorInstance<AZ::OSAllocator>::IsReady())
@@ -56,6 +56,8 @@ namespace LegacyFramework
virtual int Run(const ApplicationDesc& desc);
Application();
void CreateReflectionManager() override;
protected:
// ------------------------------------------------------------------
@@ -34,7 +34,24 @@ namespace AzToolsFramework
bool EntityOutlinerSortFilterProxyModel::lessThan(const QModelIndex& leftIndex, const QModelIndex& rightIndex) const
{
return sourceModel()->data(leftIndex).toString() < sourceModel()->data(rightIndex).toString();
if (leftIndex.isValid() && rightIndex.isValid())
{
QVariant leftData = sourceModel()->data(leftIndex);
QVariant rightData = sourceModel()->data(rightIndex);
// make sure to compare the correct data types for sorting the current column
AZ_Assert(leftData.type() == rightData.type(), "EntityOutlinerSortFilterProxyModel::lessThan types do not agree!");
if (static_cast<QMetaType::Type>(leftData.type()) == QMetaType::QString)
{
return leftData.toString() < rightData.toString();
}
else if (static_cast<QMetaType::Type>(leftData.type()) == QMetaType::ULongLong)
{
return leftData.toULongLong() < rightData.toULongLong();
}
AZ_Error("Editor", false, "Error! Unhandled type \"%s\" in EntityOutlinerSortFilterProxyModel::lessThan", leftData.typeName());
}
return false;
}
void EntityOutlinerSortFilterProxyModel::sort(int /*column*/, Qt::SortOrder /*order*/)
@@ -291,10 +291,12 @@ namespace AzToolsFramework
GetEntityContextId());
EditorEntityInfoNotificationBus::Handler::BusConnect();
Prefab::PrefabPublicNotificationBus::Handler::BusConnect();
EditorWindowUIRequestBus::Handler::BusConnect();
}
EntityOutlinerWidget::~EntityOutlinerWidget()
{
EditorWindowUIRequestBus::Handler::BusDisconnect();
Prefab::PrefabPublicNotificationBus::Handler::BusDisconnect();
ComponentModeFramework::EditorComponentModeNotificationBus::Handler::BusDisconnect();
EditorEntityInfoNotificationBus::Handler::BusDisconnect();
@@ -1106,16 +1108,25 @@ namespace AzToolsFramework
AzQtComponents::SetWidgetInteractEnabled(entityOutlinerUi->m_searchWidget, on);
}
void EntityOutlinerWidget::EnableUi(bool enable)
{
SetEntityOutlinerState(m_gui, enable);
setEnabled(enable);
}
void EntityOutlinerWidget::SetEditorUiEnabled(bool enable)
{
EnableUi(enable);
}
void EntityOutlinerWidget::EnteredComponentMode([[maybe_unused]] const AZStd::vector<AZ::Uuid>& componentModeTypes)
{
SetEntityOutlinerState(m_gui, false);
setEnabled(false);
EnableUi(false);
}
void EntityOutlinerWidget::LeftComponentMode([[maybe_unused]] const AZStd::vector<AZ::Uuid>& componentModeTypes)
{
setEnabled(true);
SetEntityOutlinerState(m_gui, true);
EnableUi(true);
}
void EntityOutlinerWidget::OnPrefabInstancePropagationBegin()
@@ -11,6 +11,7 @@
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/base.h>
#include <AzToolsFramework/API/EditorWindowRequestBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/ComponentMode/EditorComponentModeBus.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
@@ -58,6 +59,7 @@ namespace AzToolsFramework
, private EditorEntityInfoNotificationBus::Handler
, private ComponentModeFramework::EditorComponentModeNotificationBus::Handler
, private Prefab::PrefabPublicNotificationBus::Handler
, private EditorWindowUIRequestBus::Handler
{
Q_OBJECT;
public:
@@ -105,6 +107,9 @@ namespace AzToolsFramework
void OnPrefabInstancePropagationBegin() override;
void OnPrefabInstancePropagationEnd() override;
// EditorWindowUIRequestBus overrides
void SetEditorUiEnabled(bool enable) override;
// Build a selection object from the given entities. Entities already in the Widget's selection buffers are ignored.
template <class EntityIdCollection>
QItemSelection BuildSelectionFromEntities(const EntityIdCollection& entityIds);
@@ -155,6 +160,7 @@ namespace AzToolsFramework
AZ::EntityId GetEntityIdFromIndex(const QModelIndex& index) const;
QModelIndex GetIndexFromEntityId(const AZ::EntityId& entityId) const;
void ExtractEntityIdsFromSelection(const QItemSelection& selection, EntityIdList& entityIdList) const;
void EnableUi(bool enable);
// OutlinerModelNotificationBus::Handler
// Receive notification from the outliner model that we should scroll
@@ -376,6 +376,7 @@ namespace AzToolsFramework
ToolsApplicationEvents::Bus::Handler::BusConnect();
AZ::EntitySystemBus::Handler::BusConnect();
EntityPropertyEditorRequestBus::Handler::BusConnect();
EditorWindowUIRequestBus::Handler::BusConnect();
m_spacer = nullptr;
m_emptyIcon = QIcon();
@@ -421,6 +422,7 @@ namespace AzToolsFramework
{
qApp->removeEventFilter(this);
EditorWindowUIRequestBus::Handler::BusDisconnect();
EntityPropertyEditorRequestBus::Handler::BusDisconnect();
ToolsApplicationEvents::Bus::Handler::BusDisconnect();
AZ::EntitySystemBus::Handler::BusDisconnect();
@@ -4961,6 +4963,27 @@ namespace AzToolsFramework
EnableDisableComponentActions(widget, actions, false);
}
void EntityPropertyEditor::SetEditorUiEnabled(bool enable)
{
if (enable)
{
EnableComponentActions(this, m_entityComponentActions);
}
else
{
DisableComponentActions(this, m_entityComponentActions);
}
m_disabled = !enable;
SetPropertyEditorState(m_gui, enable);
for (auto componentEditor : m_componentEditors)
{
AzQtComponents::SetWidgetInteractEnabled(componentEditor, enable);
}
// record the selected state after entering/leaving component mode
SaveComponentEditorState();
}
void EntityPropertyEditor::EnteredComponentMode(const AZStd::vector<AZ::Uuid>& componentModeTypes)
{
DisableComponentActions(this, m_entityComponentActions);
@@ -21,6 +21,7 @@
#include <AzCore/Asset/AssetCommon.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
#include <AzToolsFramework/Undo/UndoSystem.h>
#include <AzToolsFramework/API/EditorWindowRequestBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/API/EntityPropertyEditorRequestsBus.h>
#include <AzToolsFramework/ComponentMode/EditorComponentModeBus.h>
@@ -108,6 +109,7 @@ namespace AzToolsFramework
, public EditorInspectorComponentNotificationBus::MultiHandler
, private AzToolsFramework::ComponentModeFramework::EditorComponentModeNotificationBus::Handler
, public AZ::EntitySystemBus::Handler
, private EditorWindowUIRequestBus::Handler
{
Q_OBJECT;
public:
@@ -208,6 +210,9 @@ namespace AzToolsFramework
void GetSelectedEntities(EntityIdList& selectedEntityIds) override;
void SetNewComponentId(AZ::ComponentId componentId) override;
// EditorWindowRequestBus overrides
void SetEditorUiEnabled(bool enable) override;
bool IsEntitySelected(const AZ::EntityId& id) const;
bool IsSingleEntitySelected(const AZ::EntityId& id) const;
@@ -22,11 +22,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.amazon.action.common.back", 0xd772a2af);
static const AZ::Crc32 s_deleteAction = AZ_CRC("com.amazon.action.common.delete", 0x5731f6cb);
static const AZ::Crc32 s_duplicateAction = AZ_CRC("com.amazon.action.common.duplicate", 0x08ccf461);
static const AZ::Crc32 s_nextComponentMode = AZ_CRC("com.amazon.action.common.nextComponentMode", 0xcc26094f);
static const AZ::Crc32 s_previousComponentMode = AZ_CRC("com.amazon.action.common.previousComponentMode", 0x0d18ff39);
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);
//@}
/// Specific Action properties to be sent to a type implementing
@@ -98,7 +98,7 @@ namespace AzToolsFramework
{
}
AZ::Crc32 m_uri; //!< Unique identifier for the Action. (In the form 'com.amazon.action.---").
AZ::Crc32 m_uri; //!< Unique identifier for the Action. (In the form 'com.o3de.action.---").
AZStd::vector<AZStd::function<void()>> m_callbacks; //!< Callbacks associated with this Action (note: with multi-selections
//!< there will be a callback per Entity/Component).
AZStd::unique_ptr<QAction> m_action; //!< The QAction associated with the overrideWidget for all ComponentMode actions.
@@ -724,6 +724,8 @@ set(FILES
PythonTerminal/ScriptTermDialog.cpp
PythonTerminal/ScriptTermDialog.h
PythonTerminal/ScriptTermDialog.ui
Input/QtEventToAzInputManager.h
Input/QtEventToAzInputManager.cpp
)
# Prevent the following files from being grouped in UNITY builds
@@ -196,7 +196,7 @@ namespace AzToolsFramework
AZStd::vector<AzToolsFramework::ActionOverride> PlaceHolderComponentMode::PopulateActionsImpl()
{
const AZ::Crc32 placeHolderComponentModeAction = AZ_CRC_CE("com.amazon.action.placeholder.test");
const AZ::Crc32 placeHolderComponentModeAction = AZ_CRC_CE("com.o3de.action.placeholder.test");
return AZStd::vector<AzToolsFramework::ActionOverride>
{
@@ -428,8 +428,7 @@ namespace UnitTest
ASSERT_TRUE(PrefabDomUtils::StoreInstanceInPrefabDom(*newInstance, updatedDom));
newTemplateDom.CopyFrom(updatedDom, newTemplateDom.GetAllocator());
// Validate that the prefabTestComponent in the Template's DOM doesn't have a BoolProperty.
// Even though we changed the property to false, it won't be serialized out because it's a default value.
// Validate that the value of the BoolProperty of the prefabTestComponent in the Template's DOM has changed.
entityComponents = PrefabTestDomUtils::GetPrefabDomComponents(newTemplateDom, newTemplateEntityAliases.front());
ASSERT_TRUE(entityComponents != nullptr && entityComponents->IsObject());
EXPECT_EQ(entityComponents->MemberCount(), 2);
@@ -440,7 +439,7 @@ namespace UnitTest
PrefabDomValueConstReference wheelEntityComponentBoolPropertyValue =
PrefabDomUtils::FindPrefabDomValue(wheelEntityComponentValue->get(), PrefabTestDomUtils::BoolPropertyName);
ASSERT_FALSE(wheelEntityComponentBoolPropertyValue.has_value());
ASSERT_TRUE(wheelEntityComponentBoolPropertyValue.has_value() && wheelEntityComponentBoolPropertyValue->get() == false);
// Update Template's Instances and validate if all Instances have no BoolProperty under their prefabTestComponents in entities.
m_instanceUpdateExecutorInterface->AddTemplateInstancesToQueue(newTemplateId);
@@ -145,7 +145,7 @@ namespace UnitTest
EntityAlias entityAlias = wheelTemplateEntityAliases.front();
PrefabDomValue* wheelEntityComponents =
PrefabTestDomUtils::GetPrefabDomComponentsPath(entityAlias).Get(wheelTemplateDom);
ASSERT_TRUE(wheelEntityComponents == nullptr);
ASSERT_TRUE(wheelEntityComponents->IsArray() && wheelEntityComponents->Size() == 0);
// Create an axle with 0 entities and 1 wheel instance.
AZStd::unique_ptr<Instance> wheel1UnderAxle = m_prefabSystemComponent->InstantiatePrefab(wheelTemplateId);
@@ -340,7 +340,7 @@ namespace UnitTest
// Validate that the wheel entity does not have a component under it.
wheelEntityComponents = PrefabTestDomUtils::GetPrefabDomComponentsPath(entityAlias).Get(wheelTemplateDom);
ASSERT_TRUE(wheelEntityComponents == nullptr);
ASSERT_TRUE(wheelEntityComponents->IsArray() && wheelEntityComponents->Size() == 0);
// Validate that the wheels under the axle have the same DOM as the wheel template.
PrefabTestDomUtils::ValidatePrefabDomInstances(wheelInstanceAliasesUnderAxle, axleTemplateDom, wheelTemplateDom);
@@ -399,15 +399,14 @@ namespace UnitTest
m_prefabSystemComponent->UpdatePrefabTemplate(wheelTemplateId, updatedWheelInstanceDom);
m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
// Validate that the prefabTestComponent in the wheel template DOM doesn't have a BoolProperty.
// Even though we changed the property to false, it won't be serialized out because it's a default value.
// Validate that the BoolProperty of the prefabTestComponent in the wheel template DOM is set to false.
wheelEntityComponents = PrefabTestDomUtils::GetPrefabDomComponentsPath(entityAlias).Get(wheelTemplateDom);
ASSERT_TRUE(wheelEntityComponents != nullptr && wheelEntityComponents->IsObject());
EXPECT_EQ(wheelEntityComponents->MemberCount(), 1);
PrefabDomValueReference wheelEntityComponentBoolPropertyValue =
PrefabDomUtils::FindPrefabDomValue(wheelEntityComponents->MemberBegin()->value, PrefabTestDomUtils::BoolPropertyName);
ASSERT_FALSE(wheelEntityComponentBoolPropertyValue.has_value());
ASSERT_TRUE(wheelEntityComponentBoolPropertyValue.has_value() && wheelEntityComponentBoolPropertyValue->get() == false);
// Validate that the wheels under the axle have the same DOM as the wheel template.
PrefabTestDomUtils::ValidatePrefabDomInstances(wheelInstanceAliasesUnderAxle, axleTemplateDom, wheelTemplateDom);