Merge remote-tracking branch 'upstream/stabilization/2110' into nvsickle/FixEntityOrdering

This commit is contained in:
nvsickle
2021-11-08 09:12:51 -08:00
222 changed files with 2256 additions and 1401 deletions
@@ -13,9 +13,32 @@
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/Render/IntersectorInterface.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
#include <AzToolsFramework/ViewportSelection/EditorTransformComponentSelectionRequestBus.h>
#include <EditorViewportSettings.h>
AZ_CVAR(
bool,
ed_cameraPinDefaultOrbit,
true,
nullptr,
AZ::ConsoleFunctorFlags::Null,
"Sets whether the default orbit point moves with the camera or not");
AZ_CVAR(
bool,
ed_cameraDefaultOrbitAxesOrtho,
true,
nullptr,
AZ::ConsoleFunctorFlags::Null,
"Sets whether to draw the default orbit point as orthographic or not");
AZ_CVAR(
float,
ed_cameraDefaultOrbitFadeDuration,
0.5f,
nullptr,
AZ::ConsoleFunctorFlags::Null,
"Sets how long the default orbit point should take to appear and disappear");
namespace SandboxEditor
{
static AzFramework::TranslateCameraInputChannelIds BuildTranslateCameraInputChannelIds()
@@ -174,7 +197,7 @@ namespace SandboxEditor
return SandboxEditor::CameraScrollSpeed();
};
const auto pivotFn = []
const auto pivotFn = []() -> AZStd::optional<AZ::Vector3>
{
// use the manipulator transform as the pivot point
AZStd::optional<AZ::Transform> entityPivot;
@@ -187,8 +210,7 @@ namespace SandboxEditor
return entityPivot->GetTranslation();
}
// otherwise just use the identity
return AZ::Vector3::CreateZero();
return AZStd::nullopt;
};
m_firstPersonFocusCamera =
@@ -199,9 +221,26 @@ namespace SandboxEditor
m_orbitCamera = AZStd::make_shared<AzFramework::OrbitCameraInput>(SandboxEditor::CameraOrbitChannelId());
m_orbitCamera->SetPivotFn(
[pivotFn]([[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& direction)
[this, pivotFn](const AZ::Vector3& position, const AZ::Vector3& direction)
{
return pivotFn();
// return the pivot
if (auto pivot = pivotFn())
{
return pivot.value();
}
// start ticking and drawing (for the default pivot)
AZ::TickBus::Handler::BusConnect();
AzFramework::ViewportDebugDisplayEventBus::Handler::BusConnect(AzToolsFramework::GetEntityContextId());
m_defaultOrbiting = true;
// calculate the default orbit point
if (!ed_cameraPinDefaultOrbit || m_orbitCamera->Beginning())
{
m_defaultOrbitPoint = position + direction * SandboxEditor::CameraDefaultOrbitDistance();
}
return m_defaultOrbitPoint;
});
m_orbitRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(SandboxEditor::CameraOrbitLookChannelId());
@@ -306,4 +345,67 @@ namespace SandboxEditor
m_viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::ClearReferenceFrame);
}
}
void EditorModularViewportCameraComposer::OnTick(const float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
const float delta = [duration = &ed_cameraDefaultOrbitFadeDuration, deltaTime] {
if (*duration == 0.0f) {
return 1.0f;
}
return deltaTime / *duration;
}();
if (m_defaultOrbiting)
{
m_defaultOrbitOpacity = AZStd::min(m_defaultOrbitOpacity + delta, 1.0f);
}
else
{
m_defaultOrbitOpacity = AZStd::max(m_defaultOrbitOpacity - delta, 0.0f);
if (m_defaultOrbitOpacity == 0.0f)
{
AZ::TickBus::Handler::BusDisconnect();
AzFramework::ViewportDebugDisplayEventBus::Handler::BusDisconnect();
}
}
m_defaultOrbiting = false;
}
static void DrawTransformAxis(
AzFramework::DebugDisplayRequests& display,
const AzFramework::CameraState& cameraState,
const AZ::Vector3& pivot,
const float axisLength,
const float alpha)
{
const int prevState = display.GetState();
display.DepthWriteOff();
display.DepthTestOff();
display.CullOff();
const float orthoScale =
ed_cameraDefaultOrbitAxesOrtho ? AzToolsFramework::CalculateScreenToWorldMultiplier(pivot, cameraState) : 1.0f;
display.SetColor(AZ::Color::CreateFromVector3AndFloat(AZ::Colors::Red.GetAsVector3(), alpha));
display.DrawLine(pivot, pivot + AZ::Vector3::CreateAxisX() * axisLength * orthoScale);
display.SetColor(AZ::Color::CreateFromVector3AndFloat(AZ::Colors::LawnGreen.GetAsVector3(), alpha));
display.DrawLine(pivot, pivot + AZ::Vector3::CreateAxisY() * axisLength * orthoScale);
display.SetColor(AZ::Color::CreateFromVector3AndFloat(AZ::Colors::Blue.GetAsVector3(), alpha));
display.DrawLine(pivot, pivot + AZ::Vector3::CreateAxisZ() * axisLength * orthoScale);
display.DepthWriteOn();
display.DepthTestOn();
display.CullOn();
display.SetState(prevState);
}
void EditorModularViewportCameraComposer::DisplayViewport(
[[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay)
{
DrawTransformAxis(
debugDisplay, AzToolsFramework::GetCameraState(viewportInfo.m_viewportId), m_defaultOrbitPoint, 1.0f, m_defaultOrbitOpacity);
}
} // namespace SandboxEditor
@@ -9,6 +9,8 @@
#pragma once
#include <AtomToolsFramework/Viewport/ModularViewportCameraController.h>
#include <AzCore/Component/TickBus.h>
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <AzFramework/Viewport/CameraInput.h>
#include <AzToolsFramework/API/EditorCameraBus.h>
#include <EditorModularViewportCameraComposerBus.h>
@@ -20,6 +22,8 @@ namespace SandboxEditor
class EditorModularViewportCameraComposer
: private EditorModularViewportCameraComposerNotificationBus::Handler
, private Camera::EditorCameraNotificationBus::Handler
, private AzFramework::ViewportDebugDisplayEventBus::Handler
, private AZ::TickBus::Handler
{
public:
SANDBOX_API explicit EditorModularViewportCameraComposer(AzFramework::ViewportId viewportId);
@@ -29,6 +33,12 @@ namespace SandboxEditor
SANDBOX_API AZStd::shared_ptr<AtomToolsFramework::ModularViewportCameraController> CreateModularViewportCameraController();
private:
// AzFramework::ViewportDebugDisplayEventBus overrides ...
void DisplayViewport(const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay) override;
// AZ::TickBus overrides ...
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
//! Setup all internal camera inputs.
void SetupCameras();
@@ -52,5 +62,9 @@ namespace SandboxEditor
AZStd::shared_ptr<AzFramework::FocusCameraInput> m_orbitFocusCamera;
AzFramework::ViewportId m_viewportId;
float m_defaultOrbitOpacity = 0.0f; //!< The default orbit axes opacity (to fade in and out).
AZ::Vector3 m_defaultOrbitPoint = AZ::Vector3::CreateZero(); //!< The orbit point to use when no entity is selected.
bool m_defaultOrbiting = false; //!< Is the camera default orbiting (orbiting when there's no selected entity).
};
} // namespace SandboxEditor
@@ -61,7 +61,7 @@ static AZStd::vector<AZStd::string> GetEditorInputNames()
void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serialize)
{
serialize.Class<CameraMovementSettings>()
->Version(3)
->Version(4)
->Field("TranslateSpeed", &CameraMovementSettings::m_translateSpeed)
->Field("RotateSpeed", &CameraMovementSettings::m_rotateSpeed)
->Field("BoostMultiplier", &CameraMovementSettings::m_boostMultiplier)
@@ -76,9 +76,8 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial
->Field("OrbitYawRotationInverted", &CameraMovementSettings::m_orbitYawRotationInverted)
->Field("PanInvertedX", &CameraMovementSettings::m_panInvertedX)
->Field("PanInvertedY", &CameraMovementSettings::m_panInvertedY)
->Field("DefaultPositionX", &CameraMovementSettings::m_defaultCameraPositionX)
->Field("DefaultPositionY", &CameraMovementSettings::m_defaultCameraPositionY)
->Field("DefaultPositionZ", &CameraMovementSettings::m_defaultCameraPositionZ);
->Field("DefaultPosition", &CameraMovementSettings::m_defaultPosition)
->Field("DefaultOrbitDistance", &CameraMovementSettings::m_defaultOrbitDistance);
serialize.Class<CameraInputSettings>()
->Version(2)
@@ -159,14 +158,12 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial
AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_captureCursorLook, "Camera Capture Look Cursor",
"Should the cursor be captured (hidden) while performing free look")
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_defaultCameraPositionX, "Default X Camera Position",
"Default X Camera Position when a level is opened")
AZ::Edit::UIHandlers::Vector3, &CameraMovementSettings::m_defaultPosition, "Default Camera Position",
"Default Camera Position when a level is first opened")
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_defaultCameraPositionY, "Default Y Camera Position",
"Default Y Camera Position when a level is opened")
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_defaultCameraPositionZ, "Default Z Camera Position",
"Default Z Camera Position when a level is opened");
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_defaultOrbitDistance, "Default Orbit Distance",
"The default distance to orbit about when there is no entity selected")
->Attribute(AZ::Edit::Attributes::Min, minValue);
editContext->Class<CameraInputSettings>("Camera Input Settings", "")
->DataElement(
@@ -283,12 +280,8 @@ void CEditorPreferencesPage_ViewportCamera::OnApply()
SandboxEditor::SetCameraOrbitYawRotationInverted(m_cameraMovementSettings.m_orbitYawRotationInverted);
SandboxEditor::SetCameraPanInvertedX(m_cameraMovementSettings.m_panInvertedX);
SandboxEditor::SetCameraPanInvertedY(m_cameraMovementSettings.m_panInvertedY);
SandboxEditor::SetDefaultCameraEditorPosition(
AZ::Vector3(
m_cameraMovementSettings.m_defaultCameraPositionX,
m_cameraMovementSettings.m_defaultCameraPositionY,
m_cameraMovementSettings.m_defaultCameraPositionZ
));
SandboxEditor::SetCameraDefaultEditorPosition(m_cameraMovementSettings.m_defaultPosition);
SandboxEditor::SetCameraDefaultOrbitDistance(m_cameraMovementSettings.m_defaultOrbitDistance);
SandboxEditor::SetCameraTranslateForwardChannelId(m_cameraInputSettings.m_translateForwardChannelId);
SandboxEditor::SetCameraTranslateBackwardChannelId(m_cameraInputSettings.m_translateBackwardChannelId);
@@ -325,11 +318,8 @@ void CEditorPreferencesPage_ViewportCamera::InitializeSettings()
m_cameraMovementSettings.m_orbitYawRotationInverted = SandboxEditor::CameraOrbitYawRotationInverted();
m_cameraMovementSettings.m_panInvertedX = SandboxEditor::CameraPanInvertedX();
m_cameraMovementSettings.m_panInvertedY = SandboxEditor::CameraPanInvertedY();
AZ::Vector3 defaultCameraPosition = SandboxEditor::DefaultEditorCameraPosition();
m_cameraMovementSettings.m_defaultCameraPositionX = defaultCameraPosition.GetX();
m_cameraMovementSettings.m_defaultCameraPositionY = defaultCameraPosition.GetY();
m_cameraMovementSettings.m_defaultCameraPositionZ = defaultCameraPosition.GetZ();
m_cameraMovementSettings.m_defaultPosition = SandboxEditor::CameraDefaultEditorPosition();
m_cameraMovementSettings.m_defaultOrbitDistance = SandboxEditor::CameraDefaultOrbitDistance();
m_cameraInputSettings.m_translateForwardChannelId = SandboxEditor::CameraTranslateForwardChannelId().GetName();
m_cameraInputSettings.m_translateBackwardChannelId = SandboxEditor::CameraTranslateBackwardChannelId().GetName();
@@ -9,9 +9,12 @@
#pragma once
#include "Include/IPreferencesPage.h"
#include <AzCore/Math/Vector3.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <QIcon>
inline AZ::Crc32 EditorPropertyVisibility(const bool enabled)
@@ -43,6 +46,7 @@ private:
{
AZ_TYPE_INFO(CameraMovementSettings, "{60B8C07E-5F48-4171-A50B-F45558B5CCA1}")
AZ::Vector3 m_defaultPosition;
float m_translateSpeed;
float m_rotateSpeed;
float m_scrollSpeed;
@@ -50,16 +54,14 @@ private:
float m_panSpeed;
float m_boostMultiplier;
float m_rotateSmoothness;
bool m_rotateSmoothing;
float m_translateSmoothness;
bool m_translateSmoothing;
float m_defaultOrbitDistance;
bool m_captureCursorLook;
bool m_orbitYawRotationInverted;
bool m_panInvertedX;
bool m_panInvertedY;
float m_defaultCameraPositionX;
float m_defaultCameraPositionY;
float m_defaultCameraPositionZ;
bool m_rotateSmoothing;
bool m_translateSmoothing;
AZ::Crc32 RotateSmoothingVisibility() const
{
+17 -6
View File
@@ -38,6 +38,7 @@ namespace SandboxEditor
constexpr AZStd::string_view CameraTranslateSmoothingSetting = "/Amazon/Preferences/Editor/Camera/TranslateSmoothing";
constexpr AZStd::string_view CameraRotateSmoothingSetting = "/Amazon/Preferences/Editor/Camera/RotateSmoothing";
constexpr AZStd::string_view CameraCaptureCursorLookSetting = "/Amazon/Preferences/Editor/Camera/CaptureCursorLook";
constexpr AZStd::string_view CameraDefaultOrbitDistanceSetting = "/Amazon/Preferences/Editor/Camera/DefaultOrbitDistance";
constexpr AZStd::string_view CameraTranslateForwardIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateForwardId";
constexpr AZStd::string_view CameraTranslateBackwardIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateBackwardId";
constexpr AZStd::string_view CameraTranslateLeftIdSetting = "/Amazon/Preferences/Editor/Camera/CameraTranslateLeftId";
@@ -114,15 +115,15 @@ namespace SandboxEditor
return AZStd::make_unique<EditorViewportSettingsCallbacksImpl>();
}
AZ::Vector3 DefaultEditorCameraPosition()
AZ::Vector3 CameraDefaultEditorPosition()
{
float xPosition = aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionX, 0.0));
float yPosition = aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionY, -10.0));
float zPosition = aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionZ, 4.0));
return AZ::Vector3(xPosition, yPosition, zPosition);
return AZ::Vector3(
aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionX, 0.0)),
aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionY, -10.0)),
aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionZ, 4.0)));
}
void SetDefaultCameraEditorPosition(const AZ::Vector3 defaultCameraPosition)
void SetCameraDefaultEditorPosition(const AZ::Vector3& defaultCameraPosition)
{
SetRegistry(CameraDefaultStartingPositionX, defaultCameraPosition.GetX());
SetRegistry(CameraDefaultStartingPositionY, defaultCameraPosition.GetY());
@@ -359,6 +360,16 @@ namespace SandboxEditor
SetRegistry(CameraCaptureCursorLookSetting, capture);
}
float CameraDefaultOrbitDistance()
{
return aznumeric_cast<float>(GetRegistry(CameraDefaultOrbitDistanceSetting, 20.0));
}
void SetCameraDefaultOrbitDistance(const float distance)
{
SetRegistry(CameraDefaultOrbitDistanceSetting, distance);
}
AzFramework::InputChannelId CameraTranslateForwardChannelId()
{
return AzFramework::InputChannelId(
+6 -3
View File
@@ -33,9 +33,6 @@ namespace SandboxEditor
//! event will fire when a value in the settings registry (editorpreferences.setreg) is modified.
SANDBOX_API AZStd::unique_ptr<EditorViewportSettingsCallbacks> CreateEditorViewportSettingsCallbacks();
SANDBOX_API AZ::Vector3 DefaultEditorCameraPosition();
SANDBOX_API void SetDefaultCameraEditorPosition(AZ::Vector3 defaultCameraPosition);
SANDBOX_API AZ::u64 MaxItemsShownInAssetBrowserSearch();
SANDBOX_API void SetMaxItemsShownInAssetBrowserSearch(AZ::u64 numberOfItemsShown);
@@ -105,6 +102,12 @@ namespace SandboxEditor
SANDBOX_API bool CameraCaptureCursorForLook();
SANDBOX_API void SetCameraCaptureCursorForLook(bool capture);
SANDBOX_API AZ::Vector3 CameraDefaultEditorPosition();
SANDBOX_API void SetCameraDefaultEditorPosition(const AZ::Vector3& position);
SANDBOX_API float CameraDefaultOrbitDistance();
SANDBOX_API void SetCameraDefaultOrbitDistance(float distance);
SANDBOX_API AzFramework::InputChannelId CameraTranslateForwardChannelId();
SANDBOX_API void SetCameraTranslateForwardChannelId(AZStd::string_view cameraTranslateForwardId);
+9 -9
View File
@@ -620,9 +620,9 @@ void EditorViewportWidget::OnEditorNotifyEvent(EEditorNotifyEvent event)
break;
case eNotify_OnEndNewScene:
PopDisableRendering();
{
PopDisableRendering();
Matrix34 viewTM;
viewTM.SetIdentity();
@@ -638,9 +638,9 @@ void EditorViewportWidget::OnEditorNotifyEvent(EEditorNotifyEvent event)
break;
case eNotify_OnEndTerrainCreate:
PopDisableRendering();
{
PopDisableRendering();
Matrix34 viewTM;
viewTM.SetIdentity();
@@ -2021,10 +2021,6 @@ void EditorViewportWidget::SetDefaultCamera()
GetViewManager()->SetCameraObjectId(GUID_NULL);
SetName(m_defaultViewName);
// Set the default Editor Camera position.
m_defaultViewTM.SetTranslation(Vec3(m_editorViewportSettings.DefaultEditorCameraPosition()));
SetViewTM(m_defaultViewTM);
// Synchronize the configured editor viewport FOV to the default camera
if (m_viewPane)
{
@@ -2041,6 +2037,10 @@ void EditorViewportWidget::SetDefaultCamera()
atomViewportRequests->PushView(contextName, m_defaultView);
}
// Set the default Editor Camera position.
m_defaultViewTM.SetTranslation(Vec3(m_editorViewportSettings.DefaultEditorCameraPosition()));
SetViewTM(m_defaultViewTM);
PostCameraSet();
}
@@ -2527,7 +2527,7 @@ bool EditorViewportSettings::StickySelectEnabled() const
AZ::Vector3 EditorViewportSettings::DefaultEditorCameraPosition() const
{
return SandboxEditor::DefaultEditorCameraPosition();
return SandboxEditor::CameraDefaultEditorPosition();
}
AZ_CVAR_EXTERNED(bool, ed_previewGameInFullscreen_once);
@@ -10,6 +10,8 @@
#ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
#include <AzFramework/XcbEventHandler.h>
#include <AzFramework/XcbConnectionManager.h>
#include <qpa/qplatformnativeinterface.h>
#endif
namespace Editor
@@ -23,16 +25,34 @@ namespace Editor
return nullptr;
}
xcb_connection_t* EditorQtApplicationXcb::GetXcbConnectionFromQt()
{
QPlatformNativeInterface* native = platformNativeInterface();
AZ_Warning("EditorQtApplicationXcb", native, "Unable to retrieve the native platform interface");
if (!native)
{
return nullptr;
}
return reinterpret_cast<xcb_connection_t*>(native->nativeResourceForIntegration(QByteArray("connection")));
}
void EditorQtApplicationXcb::OnStartPlayInEditor()
{
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
interface->SetEnableXInput(GetXcbConnectionFromQt(), true);
}
void EditorQtApplicationXcb::OnStopPlayInEditor()
{
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
interface->SetEnableXInput(GetXcbConnectionFromQt(), false);
}
bool EditorQtApplicationXcb::nativeEventFilter([[maybe_unused]] const QByteArray& eventType, void* message, long*)
{
if (GetIEditor()->IsInGameMode())
{
#ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
// We need to handle RAW Input events in a separate loop. This is a workaround to enable XInput2 RAW Inputs using Editor mode.
// TODO To have this call here might be not be perfect.
AzFramework::XcbEventHandlerBus::Broadcast(&AzFramework::XcbEventHandler::PollSpecialEvents);
// Now handle the rest of the events.
AzFramework::XcbEventHandlerBus::Broadcast(
&AzFramework::XcbEventHandler::HandleXcbEvent, static_cast<xcb_generic_event_t*>(message));
#endif
@@ -6,19 +6,35 @@
*
*/
#if !defined(Q_MOC_RUN)
#include <Editor/Core/QtEditorApplication.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#endif
using xcb_connection_t = struct xcb_connection_t;
namespace Editor
{
class EditorQtApplicationXcb : public EditorQtApplication
class EditorQtApplicationXcb
: public EditorQtApplication
, public AzToolsFramework::EditorEntityContextNotificationBus::Handler
{
Q_OBJECT
public:
EditorQtApplicationXcb(int& argc, char** argv)
: EditorQtApplication(argc, argv)
{
// Connect bus to listen for OnStart/StopPlayInEditor events
AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusConnect();
}
xcb_connection_t* GetXcbConnectionFromQt();
///////////////////////////////////////////////////////////////////////
// AzToolsFramework::EditorEntityContextNotificationBus overrides
void OnStartPlayInEditor() override;
void OnStopPlayInEditor() override;
// QAbstractNativeEventFilter:
bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override;
};
@@ -806,12 +806,20 @@ namespace AzFramework
[[maybe_unused]] float scrollDelta,
[[maybe_unused]] float deltaTime)
{
const auto pivot = m_pivotFn();
if (!pivot.has_value())
{
EndActivation();
return targetCamera;
}
if (Beginning())
{
// as the camera starts, record the camera we would like to end up as
m_nextCamera.m_offset = m_offsetFn(m_pivotFn().GetDistance(targetCamera.Translation()));
m_nextCamera.m_offset = m_offsetFn(pivot.value().GetDistance(targetCamera.Translation()));
const auto angles =
EulerAngles(AZ::Matrix3x3::CreateFromMatrix3x4(AZ::Matrix3x4::CreateLookAt(targetCamera.Translation(), m_pivotFn())));
EulerAngles(AZ::Matrix3x3::CreateFromMatrix3x4(AZ::Matrix3x4::CreateLookAt(targetCamera.Translation(), pivot.value())));
m_nextCamera.m_pitch = angles.GetX();
m_nextCamera.m_yaw = angles.GetZ();
m_nextCamera.m_pivot = targetCamera.m_pivot;
@@ -651,7 +651,7 @@ namespace AzFramework
class FocusCameraInput : public CameraInput
{
public:
using PivotFn = AZStd::function<AZ::Vector3()>;
using PivotFn = AZStd::function<AZStd::optional<AZ::Vector3>()>;
FocusCameraInput(const InputChannelId& focusChannelId, FocusOffsetFn offsetFn);
@@ -10,6 +10,8 @@
#include <AzFramework/XcbEventHandler.h>
#include <AzFramework/XcbInterface.h>
#include <xcb/xinput.h>
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -34,6 +36,31 @@ namespace AzFramework
return m_xcbConnection.get();
}
void SetEnableXInput(xcb_connection_t* connection, bool enable) override
{
struct Mask
{
xcb_input_event_mask_t head;
xcb_input_xi_event_mask_t mask;
};
const Mask mask {
/*.head=*/{
/*.device_id=*/XCB_INPUT_DEVICE_ALL_MASTER,
/*.mask_len=*/1
},
/*.mask=*/ enable ?
(xcb_input_xi_event_mask_t)(XCB_INPUT_XI_EVENT_MASK_RAW_MOTION | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE) :
(xcb_input_xi_event_mask_t)XCB_NONE
};
const xcb_setup_t* xcbSetup = xcb_get_setup(connection);
const xcb_screen_t* xcbScreen = xcb_setup_roots_iterator(xcbSetup).data;
xcb_input_xi_select_events(connection, xcbScreen->root, 1, &mask.head);
xcb_flush(connection);
}
private:
XcbUniquePtr<xcb_connection_t, xcb_disconnect> m_xcbConnection = nullptr;
};
@@ -24,6 +24,9 @@ namespace AzFramework
virtual ~XcbConnectionManager() = default;
virtual xcb_connection_t* GetXcbConnection() const = 0;
//! Enables/Disables XInput Raw Input events.
virtual void SetEnableXInput(xcb_connection_t* connection, bool enable) = 0;
};
class XcbConnectionManagerBusTraits
@@ -23,9 +23,6 @@ namespace AzFramework
virtual ~XcbEventHandler() = default;
virtual void HandleXcbEvent(xcb_generic_event_t* event) = 0;
// ATTN This is used as a workaround for RAW Input events when using the Editor.
virtual void PollSpecialEvents(){};
};
class XcbEventHandlerBusTraits : public AZ::EBusTraits
@@ -13,21 +13,68 @@
namespace AzFramework
{
xcb_window_t GetSystemCursorFocusWindow()
xcb_window_t GetSystemCursorFocusWindow(xcb_connection_t* connection)
{
void* systemCursorFocusWindow = nullptr;
AzFramework::InputSystemCursorConstraintRequestBus::BroadcastResult(
systemCursorFocusWindow, &AzFramework::InputSystemCursorConstraintRequests::GetSystemCursorConstraintWindow);
if (!systemCursorFocusWindow)
if (systemCursorFocusWindow)
{
return XCB_NONE;
return static_cast<xcb_window_t>(reinterpret_cast<uint64_t>(systemCursorFocusWindow));
}
// TODO Clang compile error because cast .... loses information. On GNU/Linux HWND is void* and on 64-bit
// machines its obviously 64 bit but we receive the window id from m_renderOverlay.winId() which is xcb_window_t 32-bit.
// EWMH-compliant window managers set the "_NET_ACTIVE_WINDOW" property
// of the X server's root window to the currently active window. This
// retrieves value of that property.
return static_cast<xcb_window_t>(reinterpret_cast<uint64_t>(systemCursorFocusWindow));
// Get the atom for the _NET_ACTIVE_WINDOW property
constexpr int propertyNameLength = 18;
xcb_generic_error_t* error = nullptr;
XcbStdFreePtr<xcb_intern_atom_reply_t> activeWindowAtom {xcb_intern_atom_reply(
connection,
xcb_intern_atom(connection, /*only_if_exists=*/ 1, propertyNameLength, "_NET_ACTIVE_WINDOW"),
&error
)};
if (!activeWindowAtom || error)
{
if (error)
{
AZ_Warning("XcbInput", false, "Retrieving _NET_ACTIVE_WINDOW atom failed : Error code %d", error->error_code);
free(error);
}
return XCB_WINDOW_NONE;
}
// Get the root window
const xcb_window_t rootWId = xcb_setup_roots_iterator(xcb_get_setup(connection)).data->root;
// Fetch the value of the root window's _NET_ACTIVE_WINDOW property
XcbStdFreePtr<xcb_get_property_reply_t> property {xcb_get_property_reply(
connection,
xcb_get_property(
/*c=*/connection,
/*_delete=*/ 0,
/*window=*/rootWId,
/*property=*/activeWindowAtom->atom,
/*type=*/XCB_ATOM_WINDOW,
/*long_offset=*/0,
/*long_length=*/1
),
&error
)};
if (!property || error)
{
if (error)
{
AZ_Warning("XcbInput", false, "Retrieving _NET_ACTIVE_WINDOW atom failed : Error code %d", error->error_code);
free(error);
}
return XCB_WINDOW_NONE;
}
return *static_cast<xcb_window_t*>(xcb_get_property_value(property.get()));
}
xcb_connection_t* XcbInputDeviceMouse::s_xcbConnection = nullptr;
@@ -39,8 +86,7 @@ namespace AzFramework
: InputDeviceMouse::Implementation(inputDevice)
, m_systemCursorState(SystemCursorState::Unknown)
, m_systemCursorPositionNormalized(0.5f, 0.5f)
, m_prevConstraintWindow(XCB_NONE)
, m_focusWindow(XCB_NONE)
, m_focusWindow(XCB_WINDOW_NONE)
, m_cursorShown(true)
{
XcbEventHandlerBus::Handler::BusConnect();
@@ -57,14 +103,14 @@ namespace AzFramework
InputDeviceMouse::Implementation* XcbInputDeviceMouse::Create(InputDeviceMouse& inputDevice)
{
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
const auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
if (!interface)
{
AZ_Warning("XcbInput", false, "XCB interface not available");
return nullptr;
}
s_xcbConnection = AzFramework::XcbConnectionManagerInterface::Get()->GetXcbConnection();
s_xcbConnection = interface->GetXcbConnection();
if (!s_xcbConnection)
{
AZ_Warning("XcbInput", false, "XCB connection not available");
@@ -126,7 +172,7 @@ namespace AzFramework
// Get window information.
const XcbStdFreePtr<xcb_get_geometry_reply_t> xcbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) };
if (!xcbGeometryReply)
{
@@ -137,7 +183,7 @@ namespace AzFramework
xcb_translate_coordinates(s_xcbConnection, window, s_xcbScreen->root, 0, 0);
const XcbStdFreePtr<xcb_translate_coordinates_reply_t> xkbTranslateCoordReply{ xcb_translate_coordinates_reply(
s_xcbConnection, translate_coord, NULL) };
s_xcbConnection, translate_coord, nullptr) };
if (!xkbTranslateCoordReply)
{
@@ -173,11 +219,11 @@ namespace AzFramework
for (const auto& barrier : m_activeBarriers)
{
xcb_void_cookie_t cookie = xcb_xfixes_create_pointer_barrier_checked(
s_xcbConnection, barrier.id, window, barrier.x0, barrier.y0, barrier.x1, barrier.y1, barrier.direction, 0, NULL);
const XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(s_xcbConnection, cookie) };
s_xcbConnection, barrier.id, window, barrier.x0, barrier.y0, barrier.x1, barrier.y1, barrier.direction, 0, nullptr);
const XcbStdFreePtr<xcb_generic_error_t> xcbError{ xcb_request_check(s_xcbConnection, cookie) };
AZ_Warning(
"XcbInput", !xkbError, "XFixes, failed to create barrier %d at (%d %d %d %d)", barrier.id, barrier.x0, barrier.y0,
"XcbInput", !xcbError, "XFixes, failed to create barrier %d at (%d %d %d %d)", barrier.id, barrier.x0, barrier.y0,
barrier.x1, barrier.y1);
}
}
@@ -207,7 +253,7 @@ namespace AzFramework
const xcb_xfixes_query_version_cookie_t query_cookie = xcb_xfixes_query_version(s_xcbConnection, 5, 0);
xcb_generic_error_t* error = NULL;
xcb_generic_error_t* error = nullptr;
const XcbStdFreePtr<xcb_xfixes_query_version_reply_t> xkbQueryRequestReply{ xcb_xfixes_query_version_reply(
s_xcbConnection, query_cookie, &error) };
@@ -244,7 +290,7 @@ namespace AzFramework
const xcb_input_xi_query_version_cookie_t query_version_cookie = xcb_input_xi_query_version(s_xcbConnection, 2, 2);
xcb_generic_error_t* error = NULL;
xcb_generic_error_t* error = nullptr;
const XcbStdFreePtr<xcb_input_xi_query_version_reply_t> xkbQueryRequestReply{ xcb_input_xi_query_version_reply(
s_xcbConnection, query_version_cookie, &error) };
@@ -268,40 +314,13 @@ namespace AzFramework
return m_xInputInitialized;
}
void XcbInputDeviceMouse::SetEnableXInput(bool enable)
{
struct
{
xcb_input_event_mask_t head;
int mask;
} mask;
mask.head.deviceid = XCB_INPUT_DEVICE_ALL;
mask.head.mask_len = 1;
if (enable)
{
mask.mask = XCB_INPUT_XI_EVENT_MASK_RAW_MOTION | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS |
XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE | XCB_INPUT_XI_EVENT_MASK_MOTION | XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS |
XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE;
}
else
{
mask.mask = XCB_NONE;
}
xcb_input_xi_select_events(s_xcbConnection, s_xcbScreen->root, 1, &mask.head);
xcb_flush(s_xcbConnection);
}
void XcbInputDeviceMouse::SetSystemCursorState(SystemCursorState systemCursorState)
{
if (systemCursorState != m_systemCursorState)
{
m_systemCursorState = systemCursorState;
m_focusWindow = GetSystemCursorFocusWindow();
m_focusWindow = GetSystemCursorFocusWindow(s_xcbConnection);
HandleCursorState(m_focusWindow, systemCursorState);
}
@@ -309,52 +328,10 @@ namespace AzFramework
void XcbInputDeviceMouse::HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState)
{
bool confined = false, cursorShown = true;
switch (systemCursorState)
{
case SystemCursorState::ConstrainedAndHidden:
{
//!< Constrained to the application's main window and hidden
confined = true;
cursorShown = false;
}
break;
case SystemCursorState::ConstrainedAndVisible:
{
//!< Constrained to the application's main window and visible
confined = true;
}
break;
case SystemCursorState::UnconstrainedAndHidden:
{
//!< Free to move outside the main window but hidden while inside
cursorShown = false;
}
break;
case SystemCursorState::UnconstrainedAndVisible:
{
//!< Free to move outside the application's main window and visible
}
case SystemCursorState::Unknown:
default:
break;
}
// ATTN GetSystemCursorFocusWindow when getting out of the play in editor will return XCB_NONE
// We need however the window id to reset the cursor.
if (XCB_NONE == window && (confined || cursorShown))
{
// Reuse the previous window to reset states.
window = m_prevConstraintWindow;
m_prevConstraintWindow = XCB_NONE;
}
else
{
// Remember the window we used to modify cursor and barrier states.
m_prevConstraintWindow = window;
}
SetEnableXInput(!cursorShown);
const bool confined = (systemCursorState == SystemCursorState::ConstrainedAndHidden) ||
(systemCursorState == SystemCursorState::ConstrainedAndVisible);
const bool cursorShown = (systemCursorState == SystemCursorState::ConstrainedAndVisible) ||
(systemCursorState == SystemCursorState::UnconstrainedAndVisible);
CreateBarriers(window, confined);
ShowCursor(window, cursorShown);
@@ -368,26 +345,26 @@ namespace AzFramework
void XcbInputDeviceMouse::SetSystemCursorPositionNormalizedInternal(xcb_window_t window, AZ::Vector2 positionNormalized)
{
// TODO Basically not done at all. Added only the basic functions needed.
const XcbStdFreePtr<xcb_get_geometry_reply_t> xkbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
const XcbStdFreePtr<xcb_get_geometry_reply_t> xcbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) };
if (!xkbGeometryReply)
if (!xcbGeometryReply)
{
return;
}
const int16_t x = static_cast<int16_t>(positionNormalized.GetX() * xkbGeometryReply->width);
const int16_t y = static_cast<int16_t>(positionNormalized.GetY() * xkbGeometryReply->height);
const int16_t x = static_cast<int16_t>(positionNormalized.GetX() * xcbGeometryReply->width);
const int16_t y = static_cast<int16_t>(positionNormalized.GetY() * xcbGeometryReply->height);
xcb_warp_pointer(s_xcbConnection, XCB_NONE, window, 0, 0, 0, 0, x, y);
xcb_warp_pointer(s_xcbConnection, XCB_WINDOW_NONE, window, 0, 0, 0, 0, x, y);
xcb_flush(s_xcbConnection);
}
void XcbInputDeviceMouse::SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized)
{
const xcb_window_t window = GetSystemCursorFocusWindow();
if (XCB_NONE == window)
const xcb_window_t window = GetSystemCursorFocusWindow(s_xcbConnection);
if (XCB_WINDOW_NONE == window)
{
return;
}
@@ -401,7 +378,7 @@ namespace AzFramework
const xcb_query_pointer_cookie_t pointer = xcb_query_pointer(s_xcbConnection, window);
const XcbStdFreePtr<xcb_query_pointer_reply_t> xkbQueryPointerReply{ xcb_query_pointer_reply(s_xcbConnection, pointer, NULL) };
const XcbStdFreePtr<xcb_query_pointer_reply_t> xkbQueryPointerReply{ xcb_query_pointer_reply(s_xcbConnection, pointer, nullptr) };
if (!xkbQueryPointerReply)
{
@@ -409,7 +386,7 @@ namespace AzFramework
}
const XcbStdFreePtr<xcb_get_geometry_reply_t> xkbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) };
if (!xkbGeometryReply)
{
@@ -429,8 +406,8 @@ namespace AzFramework
AZ::Vector2 XcbInputDeviceMouse::GetSystemCursorPositionNormalized() const
{
const xcb_window_t window = GetSystemCursorFocusWindow();
if (XCB_NONE == window)
const xcb_window_t window = GetSystemCursorFocusWindow(s_xcbConnection);
if (XCB_WINDOW_NONE == window)
{
return AZ::Vector2::CreateZero();
}
@@ -455,11 +432,11 @@ namespace AzFramework
cookie = xcb_xfixes_hide_cursor_checked(s_xcbConnection, window);
}
const XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(s_xcbConnection, cookie) };
const XcbStdFreePtr<xcb_generic_error_t> xcbError{ xcb_request_check(s_xcbConnection, cookie) };
if (xkbError)
if (xcbError)
{
AZ_Warning("XcbInput", false, "ShowCursor failed: %d", xkbError->error_code);
AZ_Warning("XcbInput", false, "ShowCursor failed: %d", xcbError->error_code);
return;
}
@@ -500,14 +477,6 @@ namespace AzFramework
}
}
void XcbInputDeviceMouse::HandlePointerMotionEvents(const xcb_generic_event_t* event)
{
const xcb_input_motion_event_t* mouseMotionEvent = reinterpret_cast<const xcb_input_motion_event_t*>(event);
m_systemCursorPosition[0] = mouseMotionEvent->event_x;
m_systemCursorPosition[1] = mouseMotionEvent->event_y;
}
void XcbInputDeviceMouse::HandleRawInputEvents(const xcb_ge_generic_event_t* event)
{
const xcb_ge_generic_event_t* genericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(event);
@@ -552,78 +521,20 @@ namespace AzFramework
}
}
void XcbInputDeviceMouse::PollSpecialEvents()
{
while (xcb_generic_event_t* genericEvent = xcb_poll_for_queued_event(s_xcbConnection))
{
// TODO Is the following correct? If we are showing the cursor, don't poll RAW Input events.
switch (genericEvent->response_type & ~0x80)
{
case XCB_GE_GENERIC:
{
const xcb_ge_generic_event_t* geGenericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(genericEvent);
// Only handle raw inputs if we have focus.
// Handle Raw Input events first.
if ((geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) ||
(geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) ||
(geGenericEvent->event_type == XCB_INPUT_RAW_MOTION))
{
HandleRawInputEvents(geGenericEvent);
free(genericEvent);
}
}
break;
}
}
}
void XcbInputDeviceMouse::HandleXcbEvent(xcb_generic_event_t* event)
{
switch (event->response_type & ~0x80)
{
// QT5 is using by default XInput which means we do need to check for XCB_GE_GENERIC event to parse all mouse related events.
// XInput raw events are sent from the server as a XCB_GE_GENERIC
// event. A XCB_GE_GENERIC event is typecast to a
// xcb_ge_generic_event_t, which is distinct from a
// xcb_generic_event_t, and exists so that X11 extensions can extend
// the event emission beyond the size that a normal X11 event could
// contain.
case XCB_GE_GENERIC:
{
const xcb_ge_generic_event_t* genericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(event);
// Handling RAW Inputs here works in GameMode but not in Editor mode because QT is
// not handling RAW input events and passing to.
if (!m_cursorShown)
{
// Handle Raw Input events first.
if ((genericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) ||
(genericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) || (genericEvent->event_type == XCB_INPUT_RAW_MOTION))
{
HandleRawInputEvents(genericEvent);
}
}
else
{
switch (genericEvent->event_type)
{
case XCB_INPUT_BUTTON_PRESS:
{
const xcb_input_button_press_event_t* mouseButtonEvent =
reinterpret_cast<const xcb_input_button_press_event_t*>(genericEvent);
HandleButtonPressEvents(mouseButtonEvent->detail, true);
}
break;
case XCB_INPUT_BUTTON_RELEASE:
{
const xcb_input_button_release_event_t* mouseButtonEvent =
reinterpret_cast<const xcb_input_button_release_event_t*>(genericEvent);
HandleButtonPressEvents(mouseButtonEvent->detail, false);
}
break;
case XCB_INPUT_MOTION:
{
HandlePointerMotionEvents(event);
}
break;
}
}
HandleRawInputEvents(genericEvent);
}
break;
case XCB_FOCUS_IN:
@@ -634,6 +545,9 @@ namespace AzFramework
m_focusWindow = focusInEvent->event;
HandleCursorState(m_focusWindow, m_systemCursorState);
}
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
interface->SetEnableXInput(interface->GetXcbConnection(), true);
}
break;
case XCB_FOCUS_OUT:
@@ -644,7 +558,10 @@ namespace AzFramework
ProcessRawEventQueues();
ResetInputChannelStates();
m_focusWindow = XCB_NONE;
m_focusWindow = XCB_WINDOW_NONE;
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
interface->SetEnableXInput(interface->GetXcbConnection(), false);
}
break;
}
@@ -65,9 +65,6 @@ namespace AzFramework
//! \ref AzFramework::InputDeviceMouse::Implementation::TickInputDevice
void TickInputDevice() override;
//! This method is called by the Editor to accommodate some events with the Editor. Never called in Game mode.
void PollSpecialEvents() override;
//! Handle X11 events.
void HandleXcbEvent(xcb_generic_event_t* event) override;
@@ -77,9 +74,6 @@ namespace AzFramework
//! Initialize XInput extension. Used for raw input during confinement and showing/hiding the cursor.
static bool InitializeXInput();
//! Enables/Disables XInput Raw Input events.
void SetEnableXInput(bool enable);
//! Create barriers.
void CreateBarriers(xcb_window_t window, bool create);
@@ -98,9 +92,6 @@ namespace AzFramework
//! Handle button press/release events.
void HandleButtonPressEvents(uint32_t detail, bool pressed);
//! Handle motion notify events.
void HandlePointerMotionEvents(const xcb_generic_event_t* event);
//! Will set cursor states and confinement modes.
void HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState);
@@ -160,7 +151,6 @@ namespace AzFramework
AZ::Vector2 m_cursorHiddenPosition;
AZ::Vector2 m_systemCursorPositionNormalized;
uint32_t m_systemCursorPosition[MAX_XI_RAW_AXIS];
static xcb_connection_t* s_xcbConnection;
static xcb_screen_t* s_xcbScreen;
@@ -171,9 +161,6 @@ namespace AzFramework
//! Will be true if the xinput2 extension could be initialized.
static bool m_xInputInitialized;
//! The window that had focus
xcb_window_t m_prevConstraintWindow;
//! The current window that has focus
xcb_window_t m_focusWindow;
@@ -10,6 +10,8 @@
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzFramework/Process/ProcessWatcher.h>
#include <sys/types.h>
#include <unistd.h>
@@ -24,14 +26,20 @@ namespace AzFramework::AssetSystem::Platform
AZ::IO::FixedMaxPath assetProcessorPath{ executableDirectory };
// In Mac the Editor and game is within a bundle, so the path to the sibling app
// has to go up from the Contents/MacOS folder the binary is in
assetProcessorPath /= "../../../AssetProcessor.app";
assetProcessorPath /= "../../../AssetProcessor.app/Contents/MacOS/AssetProcessor";
assetProcessorPath = assetProcessorPath.LexicallyNormal();
if (!AZ::IO::SystemFile::Exists(assetProcessorPath.c_str()))
{
// Check for existence of one under a "bin" directory, i.e. engineRoot is an SDK structure.
assetProcessorPath =
AZ::IO::FixedMaxPath{engineRoot} / "bin" / AZ_TRAIT_OS_PLATFORM_NAME / AZ_BUILD_CONFIGURATION_TYPE / "AssetProcessor.app";
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
if (AZ::IO::FixedMaxPath installedBinariesPath;
settingsRegistry->Get(installedBinariesPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_InstalledBinaryFolder))
{
// Check for existence of one under a "bin" directory, i.e. engineRoot is an SDK structure.
assetProcessorPath = AZ::IO::FixedMaxPath{ engineRoot } / installedBinariesPath / "AssetProcessor.app/Contents/MacOS/AssetProcessor";
}
}
if (!AZ::IO::SystemFile::Exists(assetProcessorPath.c_str()))
{
@@ -39,23 +47,21 @@ namespace AzFramework::AssetSystem::Platform
}
}
auto fullLaunchCommand = AZ::IO::FixedMaxPathString::format(R"(open -g "%s" --args --start-hidden)", assetProcessorPath.c_str());
AZStd::string commandLineParams;
// Add the engine path to the launch command if not empty
if (!engineRoot.empty())
{
fullLaunchCommand += R"( --engine-path=")";
fullLaunchCommand += engineRoot;
fullLaunchCommand += '"';
commandLineParams += AZStd::string::format("\"--engine-path=\"%s\"\"", engineRoot.data());
}
// Add the active project path to the launch command if not empty
if (!projectPath.empty())
{
fullLaunchCommand += R"( --project-path=")";
fullLaunchCommand += projectPath;
fullLaunchCommand += '"';
commandLineParams += AZStd::string::format(" \"--regset=/Amazon/AzCore/Bootstrap/project_path=\"%s\"\"", projectPath.data());
}
return system(fullLaunchCommand.c_str()) == 0;
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
processLaunchInfo.m_processExecutableString = AZStd::move(assetProcessorPath.Native());
processLaunchInfo.m_commandlineParameters = commandLineParams;
return AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
}
}
@@ -11,6 +11,13 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_0_VALUE_PARAMS()) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{};
return value;
}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_1_VALUE_PARAMS(p0)) {
@@ -25,3 +32,38 @@ ACTION_TEMPLATE(ReturnMalloc,
*value = T{ p0, p1 };
return value;
}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_3_VALUE_PARAMS(p0, p1, p2)) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{ p0, p1, p2 };
return value;
}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_4_VALUE_PARAMS(p0, p1, p2, p3)) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{ p0, p1, p2, p3 };
return value;
}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4)) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{ p0, p1, p2, p3, p4 };
return value;
}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5)) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{ p0, p1, p2, p3, p4, p5 };
return value;
}
ACTION_TEMPLATE(ReturnMalloc,
HAS_1_TEMPLATE_PARAMS(typename, T),
AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6)) {
T* value = static_cast<T*>(malloc(sizeof(T)));
*value = T{ p0, p1, p2, p3, p4, p5, p6 };
return value;
}
@@ -32,6 +32,82 @@ xcb_generic_error_t* xcb_request_check(xcb_connection_t* c, xcb_void_cookie_t co
{
return MockXcbInterface::Instance()->xcb_request_check(c, cookie);
}
const xcb_setup_t* xcb_get_setup(xcb_connection_t *c)
{
return MockXcbInterface::Instance()->xcb_get_setup(c);
}
xcb_screen_iterator_t xcb_setup_roots_iterator(const xcb_setup_t* R)
{
return MockXcbInterface::Instance()->xcb_setup_roots_iterator(R);
}
const xcb_query_extension_reply_t* xcb_get_extension_data(xcb_connection_t* c, xcb_extension_t* ext)
{
return MockXcbInterface::Instance()->xcb_get_extension_data(c, ext);
}
int xcb_flush(xcb_connection_t *c)
{
return MockXcbInterface::Instance()->xcb_flush(c);
}
xcb_query_pointer_cookie_t xcb_query_pointer(xcb_connection_t* c, xcb_window_t window)
{
return MockXcbInterface::Instance()->xcb_query_pointer(c, window);
}
xcb_query_pointer_reply_t* xcb_query_pointer_reply(xcb_connection_t* c, xcb_query_pointer_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_query_pointer_reply(c, cookie, e);
}
xcb_get_geometry_cookie_t xcb_get_geometry(xcb_connection_t* c, xcb_drawable_t drawable)
{
return MockXcbInterface::Instance()->xcb_get_geometry(c, drawable);
}
xcb_get_geometry_reply_t* xcb_get_geometry_reply(xcb_connection_t* c, xcb_get_geometry_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_get_geometry_reply(c, cookie, e);
}
xcb_void_cookie_t xcb_warp_pointer(
xcb_connection_t* c,
xcb_window_t src_window,
xcb_window_t dst_window,
int16_t src_x,
int16_t src_y,
uint16_t src_width,
uint16_t src_height,
int16_t dst_x,
int16_t dst_y)
{
return MockXcbInterface::Instance()->xcb_warp_pointer(c, src_window, dst_window, src_x, src_y, src_width, src_height, dst_x, dst_y);
}
xcb_intern_atom_cookie_t xcb_intern_atom(xcb_connection_t* c, uint8_t only_if_exists, uint16_t name_len, const char* name)
{
return MockXcbInterface::Instance()->xcb_intern_atom(c, only_if_exists, name_len, name);
}
xcb_intern_atom_reply_t* xcb_intern_atom_reply(xcb_connection_t* c, xcb_intern_atom_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_intern_atom_reply(c, cookie, e);
}
xcb_get_property_cookie_t xcb_get_property(
xcb_connection_t* c,
uint8_t _delete,
xcb_window_t window,
xcb_atom_t property,
xcb_atom_t type,
uint32_t long_offset,
uint32_t long_length)
{
return MockXcbInterface::Instance()->xcb_get_property(c, _delete, window, property, type, long_offset, long_length);
}
xcb_get_property_reply_t* xcb_get_property_reply(xcb_connection_t* c, xcb_get_property_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_get_property_reply(c, cookie, e);
}
void* xcb_get_property_value(const xcb_get_property_reply_t* R)
{
return MockXcbInterface::Instance()->xcb_get_property_value(R);
}
uint32_t xcb_generate_id(xcb_connection_t *c)
{
return MockXcbInterface::Instance()->xcb_generate_id(c);
}
// ----------------------------------------------------------------------------
// xcb-xkb
@@ -116,4 +192,76 @@ xkb_state_component xkb_state_update_mask(
state, depressed_mods, latched_mods, locked_mods, depressed_layout, latched_layout, locked_layout);
}
// ----------------------------------------------------------------------------
// xcb-xfixes
xcb_xfixes_query_version_cookie_t xcb_xfixes_query_version(
xcb_connection_t* c, uint32_t client_major_version, uint32_t client_minor_version)
{
return MockXcbInterface::Instance()->xcb_xfixes_query_version(c, client_major_version, client_minor_version);
}
xcb_xfixes_query_version_reply_t* xcb_xfixes_query_version_reply(
xcb_connection_t* c, xcb_xfixes_query_version_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_xfixes_query_version_reply(c, cookie, e);
}
xcb_void_cookie_t xcb_xfixes_show_cursor_checked(xcb_connection_t* c, xcb_window_t window)
{
return MockXcbInterface::Instance()->xcb_xfixes_show_cursor_checked(c, window);
}
xcb_void_cookie_t xcb_xfixes_hide_cursor_checked(xcb_connection_t* c, xcb_window_t window)
{
return MockXcbInterface::Instance()->xcb_xfixes_hide_cursor_checked(c, window);
}
xcb_void_cookie_t xcb_xfixes_delete_pointer_barrier_checked(xcb_connection_t* c, xcb_xfixes_barrier_t barrier)
{
return MockXcbInterface::Instance()->xcb_xfixes_delete_pointer_barrier_checked(c, barrier);
}
xcb_translate_coordinates_cookie_t xcb_translate_coordinates(xcb_connection_t* c, xcb_window_t src_window, xcb_window_t dst_window, int16_t src_x, int16_t src_y)
{
return MockXcbInterface::Instance()->xcb_translate_coordinates(c, src_window, dst_window, src_x, src_y);
}
xcb_translate_coordinates_reply_t* xcb_translate_coordinates_reply(xcb_connection_t* c, xcb_translate_coordinates_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_translate_coordinates_reply(c, cookie, e);
}
xcb_void_cookie_t xcb_xfixes_create_pointer_barrier_checked(
xcb_connection_t* c,
xcb_xfixes_barrier_t barrier,
xcb_window_t window,
uint16_t x1,
uint16_t y1,
uint16_t x2,
uint16_t y2,
uint32_t directions,
uint16_t num_devices,
const uint16_t* devices)
{
return MockXcbInterface::Instance()->xcb_xfixes_create_pointer_barrier_checked(c, barrier, window, x1, y1, x2, y2, directions, num_devices, devices);
}
// ----------------------------------------------------------------------------
// xcb-xinput
xcb_input_xi_query_version_cookie_t xcb_input_xi_query_version(xcb_connection_t* c, uint16_t major_version, uint16_t minor_version)
{
return MockXcbInterface::Instance()->xcb_input_xi_query_version(c, major_version, minor_version);
}
xcb_input_xi_query_version_reply_t* xcb_input_xi_query_version_reply(
xcb_connection_t* c, xcb_input_xi_query_version_cookie_t cookie, xcb_generic_error_t** e)
{
return MockXcbInterface::Instance()->xcb_input_xi_query_version_reply(c, cookie, e);
}
xcb_void_cookie_t xcb_input_xi_select_events(
xcb_connection_t* c, xcb_window_t window, uint16_t num_mask, const xcb_input_event_mask_t* masks)
{
return MockXcbInterface::Instance()->xcb_input_xi_select_events(c, window, num_mask, masks);
}
int xcb_input_raw_button_press_axisvalues_length (const xcb_input_raw_button_press_event_t *R)
{
return MockXcbInterface::Instance()->xcb_input_raw_button_press_axisvalues_length(R);
}
xcb_input_fp3232_t* xcb_input_raw_button_press_axisvalues_raw(const xcb_input_raw_button_press_event_t* R)
{
return MockXcbInterface::Instance()->xcb_input_raw_button_press_axisvalues_raw(R);
}
}
@@ -18,6 +18,8 @@
#undef explicit
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-x11.h>
#include <xcb/xfixes.h>
#include <xcb/xinput.h>
#include "Printers.h"
@@ -62,6 +64,37 @@ public:
MOCK_CONST_METHOD1(xcb_disconnect, void(xcb_connection_t* c));
MOCK_CONST_METHOD1(xcb_poll_for_event, xcb_generic_event_t*(xcb_connection_t* c));
MOCK_CONST_METHOD2(xcb_request_check, xcb_generic_error_t*(xcb_connection_t* c, xcb_void_cookie_t cookie));
MOCK_CONST_METHOD1(xcb_get_setup, const xcb_setup_t*(xcb_connection_t *c));
MOCK_CONST_METHOD1(xcb_setup_roots_iterator, xcb_screen_iterator_t(const xcb_setup_t* R));
MOCK_CONST_METHOD2(xcb_get_extension_data, const xcb_query_extension_reply_t*(xcb_connection_t* c, xcb_extension_t* ext));
MOCK_CONST_METHOD1(xcb_flush, int(xcb_connection_t *c));
MOCK_CONST_METHOD2(xcb_query_pointer, xcb_query_pointer_cookie_t(xcb_connection_t* c, xcb_window_t window));
MOCK_CONST_METHOD3(xcb_query_pointer_reply, xcb_query_pointer_reply_t*(xcb_connection_t* c, xcb_query_pointer_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD2(xcb_get_geometry, xcb_get_geometry_cookie_t(xcb_connection_t* c, xcb_drawable_t drawable));
MOCK_CONST_METHOD3(xcb_get_geometry_reply, xcb_get_geometry_reply_t*(xcb_connection_t* c, xcb_get_geometry_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD9(xcb_warp_pointer, xcb_void_cookie_t(
xcb_connection_t* c,
xcb_window_t src_window,
xcb_window_t dst_window,
int16_t src_x,
int16_t src_y,
uint16_t src_width,
uint16_t src_height,
int16_t dst_x,
int16_t dst_y));
MOCK_CONST_METHOD4(xcb_intern_atom, xcb_intern_atom_cookie_t(xcb_connection_t* c, uint8_t only_if_exists, uint16_t name_len, const char* name));
MOCK_CONST_METHOD3(xcb_intern_atom_reply, xcb_intern_atom_reply_t*(xcb_connection_t* c, xcb_intern_atom_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD7(xcb_get_property, xcb_get_property_cookie_t(
xcb_connection_t* c,
uint8_t _delete,
xcb_window_t window,
xcb_atom_t property,
xcb_atom_t type,
uint32_t long_offset,
uint32_t long_length));
MOCK_CONST_METHOD3(xcb_get_property_reply, xcb_get_property_reply_t*(xcb_connection_t* c, xcb_get_property_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD1(xcb_get_property_value, void*(const xcb_get_property_reply_t* R));
MOCK_CONST_METHOD1(xcb_generate_id, uint32_t(xcb_connection_t *c));
// xcb-xkb
MOCK_CONST_METHOD3(xcb_xkb_use_extension, xcb_xkb_use_extension_cookie_t(xcb_connection_t* c, uint16_t wantedMajor, uint16_t wantedMinor));
@@ -83,6 +116,33 @@ public:
MOCK_CONST_METHOD4(xkb_state_key_get_utf8, int(xkb_state* state, xkb_keycode_t key, char* buffer, size_t size));
MOCK_CONST_METHOD7(xkb_state_update_mask, xkb_state_component(xkb_state* state, xkb_mod_mask_t depressed_mods, xkb_mod_mask_t latched_mods, xkb_mod_mask_t locked_mods, xkb_layout_index_t depressed_layout, xkb_layout_index_t latched_layout, xkb_layout_index_t locked_layout));
// xcb-xfixes
MOCK_CONST_METHOD3(xcb_xfixes_query_version, xcb_xfixes_query_version_cookie_t(xcb_connection_t* c, uint32_t client_major_version, uint32_t client_minor_version));
MOCK_CONST_METHOD3(xcb_xfixes_query_version_reply, xcb_xfixes_query_version_reply_t*(xcb_connection_t* c, xcb_xfixes_query_version_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD2(xcb_xfixes_show_cursor_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window));
MOCK_CONST_METHOD2(xcb_xfixes_hide_cursor_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window));
MOCK_CONST_METHOD2(xcb_xfixes_delete_pointer_barrier_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_xfixes_barrier_t barrier));
MOCK_CONST_METHOD5(xcb_translate_coordinates, xcb_translate_coordinates_cookie_t(xcb_connection_t* c, xcb_window_t src_window, xcb_window_t dst_window, int16_t src_x, int16_t src_y));
MOCK_CONST_METHOD3(xcb_translate_coordinates_reply, xcb_translate_coordinates_reply_t*(xcb_connection_t* c, xcb_translate_coordinates_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD10(xcb_xfixes_create_pointer_barrier_checked, xcb_void_cookie_t(
xcb_connection_t* c,
xcb_xfixes_barrier_t barrier,
xcb_window_t window,
uint16_t x1,
uint16_t y1,
uint16_t x2,
uint16_t y2,
uint32_t directions,
uint16_t num_devices,
const uint16_t* devices));
// xcb-xinput
MOCK_CONST_METHOD3(xcb_input_xi_query_version, xcb_input_xi_query_version_cookie_t(xcb_connection_t* c, uint16_t major_version, uint16_t minor_version));
MOCK_CONST_METHOD3(xcb_input_xi_query_version_reply, xcb_input_xi_query_version_reply_t*(xcb_connection_t* c, xcb_input_xi_query_version_cookie_t cookie, xcb_generic_error_t** e));
MOCK_CONST_METHOD4(xcb_input_xi_select_events, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window, uint16_t num_mask, const xcb_input_event_mask_t* masks));
MOCK_CONST_METHOD1(xcb_input_raw_button_press_axisvalues_length, int(const xcb_input_raw_button_press_event_t* R));
MOCK_CONST_METHOD1(xcb_input_raw_button_press_axisvalues_raw, xcb_input_fp3232_t*(const xcb_input_raw_button_press_event_t* R));
private:
static inline MockXcbInterface* self = nullptr;
};
@@ -22,6 +22,12 @@ namespace AzFramework
public:
void SetUp() override;
template<typename T>
static xcb_generic_event_t MakeEvent(T event)
{
return *reinterpret_cast<xcb_generic_event_t*>(&event);
}
protected:
testing::NiceMock<MockXcbInterface> m_interface;
xcb_connection_t m_connection{};
@@ -21,12 +21,6 @@
#include "XcbBaseTestFixture.h"
#include "XcbTestApplication.h"
template<typename T>
xcb_generic_event_t MakeEvent(T event)
{
return *reinterpret_cast<xcb_generic_event_t*>(&event);
}
namespace AzFramework
{
// Sets up default behavior for mock keyboard responses to xcb methods
@@ -0,0 +1,545 @@
/*
* 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 <gtest/gtest.h>
#include <gmock/gmock.h>
#include <xcb/xcb.h>
#include <AzFramework/Input/Buses/Requests/InputSystemCursorRequestBus.h>
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include "XcbBaseTestFixture.h"
#include "XcbTestApplication.h"
#include "Matchers.h"
#include "Actions.h"
namespace AzFramework
{
// Sets up default behavior for mock keyboard responses to xcb methods
class XcbInputDeviceMouseTests
: public XcbBaseTestFixture
{
public:
void SetUp() override
{
using testing::Eq;
using testing::Field;
using testing::Return;
using testing::StrEq;
using testing::_;
XcbBaseTestFixture::SetUp();
ON_CALL(m_interface, xcb_get_setup(&m_connection))
.WillByDefault(Return(&s_xcbSetup));
ON_CALL(m_interface, xcb_setup_roots_iterator(&s_xcbSetup))
.WillByDefault(Return(xcb_screen_iterator_t{&s_xcbScreen}));
ON_CALL(m_interface, xcb_get_extension_data(&m_connection, &xcb_xfixes_id))
.WillByDefault(Return(&s_xfixesExtensionReply));
ON_CALL(m_interface, xcb_xfixes_query_version_reply(&m_connection, _, _))
.WillByDefault(ReturnMalloc<xcb_xfixes_query_version_reply_t>(
/*response_type=*/(uint8_t)XCB_XFIXES_QUERY_VERSION,
/*pad0=*/(uint8_t)0,
/*sequence=*/(uint16_t)1,
/*length=*/0u,
/*major_version=*/5u,
/*minor_version=*/0u
));
ON_CALL(m_interface, xcb_get_extension_data(&m_connection, &xcb_input_id))
.WillByDefault(Return(&s_xfixesExtensionReply));
ON_CALL(m_interface, xcb_input_xi_query_version_reply(&m_connection, _, _))
.WillByDefault(ReturnMalloc<xcb_input_xi_query_version_reply_t>(
/*response_type=*/(uint8_t)XCB_INPUT_XI_QUERY_VERSION,
/*pad0=*/(uint8_t)0,
/*sequence=*/(uint16_t)1,
/*length=*/0u,
/*major_version=*/(uint16_t)2,
/*minor_version=*/(uint16_t)2
));
// Set the default focus window
EXPECT_CALL(m_interface, xcb_intern_atom(&m_connection, 1, 18, StrEq("_NET_ACTIVE_WINDOW")))
.WillRepeatedly(Return(xcb_intern_atom_cookie_t{/*.sequence=*/ 1}));
ON_CALL(m_interface, xcb_intern_atom_reply(&m_connection, Field(&xcb_intern_atom_cookie_t::sequence, Eq(1)), _))
.WillByDefault(ReturnMalloc<xcb_intern_atom_reply_t>(
/*response_type=*/(uint8_t)XCB_INTERN_ATOM,
/*pad0=*/(uint8_t)0,
/*sequence=*/(uint16_t)1,
/*length=*/0u,
/*xcb_atom_t=*/s_netActiveWindowAtom
));
ON_CALL(m_interface, xcb_get_property(&m_connection, 0, s_rootWindow, s_netActiveWindowAtom, XCB_ATOM_WINDOW, 0, 1))
.WillByDefault(Return(xcb_get_property_cookie_t{/*.sequence=*/ s_getActiveWindowPropertySequence}));
ON_CALL(m_interface, xcb_get_property_reply(&m_connection, Field(&xcb_get_property_cookie_t::sequence, Eq(s_getActiveWindowPropertySequence)), _))
.WillByDefault(ReturnMalloc<xcb_get_property_reply_t>(
/*response_type=*/(uint8_t)XCB_GET_PROPERTY,
/*format=*/(uint8_t)0,
/*sequence=*/(uint16_t)s_getActiveWindowPropertySequence,
/*length=*/0u,
/*type=*/XCB_ATOM_WINDOW,
/*bytes_after=*/0u,
/*value_len=*/1u
));
ON_CALL(m_interface, xcb_get_property_value(Field(&xcb_get_property_reply_t::sequence, Eq(s_getActiveWindowPropertySequence))))
.WillByDefault(Return(const_cast<xcb_window_t*>(&s_nullWindow)));
ON_CALL(m_interface, xcb_get_geometry(&m_connection, _))
.WillByDefault(Return(xcb_get_geometry_cookie_t{/*.sequence=*/1}));
ON_CALL(m_interface, xcb_get_geometry_reply(&m_connection, Field(&xcb_get_geometry_cookie_t::sequence, Eq(1)), _))
.WillByDefault(ReturnMalloc<xcb_get_geometry_reply_t>(s_defaultWindowGeometry));
}
void PumpApplication()
{
m_application.PumpSystemEventLoopUntilEmpty();
m_application.TickSystem();
m_application.Tick();
}
protected:
static constexpr inline uint8_t s_xinputMajorOpcode = 131;
static constexpr inline xcb_window_t s_rootWindow = 1;
static constexpr inline xcb_window_t s_nullWindow = XCB_WINDOW_NONE;
static constexpr inline xcb_input_device_id_t s_virtualCorePointerId = 2;
static constexpr inline xcb_input_device_id_t s_physicalPointerDeviceId = 3;
static constexpr inline uint16_t s_screenWidthInPixels = 3840;
static constexpr inline uint16_t s_screenHeightInPixels = 2160;
static constexpr inline uint16_t s_getActiveWindowPropertySequence = 2160;
static constexpr inline xcb_atom_t s_netActiveWindowAtom = 1;
static constexpr inline xcb_setup_t s_xcbSetup{
/*.status=*/1,
/*.pad0=*/0,
/*.protocol_major_version=*/11,
/*.protocol_minor_version=*/0,
};
static inline xcb_screen_t s_xcbScreen{
/*.root=*/s_rootWindow,
/*.default_colormap=*/32,
/*.white_pixel=*/16777215,
/*.black_pixel=*/0,
/*.current_input_masks=*/0,
/*.width_in_pixels=*/s_screenWidthInPixels,
/*.height_in_pixels=*/s_screenHeightInPixels,
/*.width_in_millimeters=*/602,
/*.height_in_millimeters=*/341,
};
static constexpr inline xcb_query_extension_reply_t s_xfixesExtensionReply{
/*.response_type=*/XCB_QUERY_EXTENSION,
/*.pad0=*/0,
/*.sequence=*/1,
/*.length=*/0,
/*.present=*/1,
};
static constexpr inline xcb_query_extension_reply_t s_xinputExtensionReply{
/*.response_type=*/XCB_QUERY_EXTENSION,
/*.pad0=*/0,
/*.sequence=*/1,
/*.length=*/0,
/*.present=*/1,
/*.major_opcode=*/s_xinputMajorOpcode,
};
static constexpr inline xcb_get_geometry_reply_t s_defaultWindowGeometry{
/*.response_type=*/XCB_GET_GEOMETRY,
/*.depth=*/0,
/*.sequence=*/1,
/*.length=*/0,
/*.root=*/s_rootWindow,
/*.x=*/100,
/*.y=*/100,
/*.width=*/100,
/*.height=*/100,
/*.border_width=*/3,
/*.pad0[2]=*/{},
};
XcbTestApplication m_application{
/*enabledGamepadsCount=*/0,
/*keyboardEnabled=*/false,
/*motionEnabled=*/false,
/*mouseEnabled=*/true,
/*touchEnabled=*/false,
/*virtualKeyboardEnabled=*/false
};
};
struct MouseButtonTestData
{
xcb_button_index_t m_button;
};
class XcbInputDeviceMouseButtonTests
: public XcbInputDeviceMouseTests
, public testing::WithParamInterface<MouseButtonTestData>
{
public:
static InputChannelId GetInputChannelIdForButton(const xcb_button_index_t button)
{
switch (button)
{
case XCB_BUTTON_INDEX_1:
return InputDeviceMouse::Button::Left;
case XCB_BUTTON_INDEX_2:
return InputDeviceMouse::Button::Right;
case XCB_BUTTON_INDEX_3:
return InputDeviceMouse::Button::Middle;
}
return InputChannelId{};
}
AZStd::array<InputChannelId, 4> GetIdleChannelIdsForButton(const xcb_button_index_t button)
{
switch (button)
{
case XCB_BUTTON_INDEX_1:
return { InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 };
case XCB_BUTTON_INDEX_2:
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 };
case XCB_BUTTON_INDEX_3:
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 };
case XCB_BUTTON_INDEX_4:
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other2 };
case XCB_BUTTON_INDEX_5:
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1 };
}
return AZStd::array<InputChannelId, 4>();
}
};
TEST_P(XcbInputDeviceMouseButtonTests, ButtonInputChannelsUpdateStateFromXcbEvents)
{
using testing::Each;
using testing::Eq;
using testing::NotNull;
using testing::Property;
using testing::Return;
// Set the expectations for the events that will be generated
// nullptr entries represent when the event queue is empty, and will cause
// PumpSystemEventLoopUntilEmpty to return
//
// Event pointers are freed by the calling code, so these actions
// malloc new copies
//
// The xcb mouse does not react to the `XCB_BUTTON_PRESS` /
// `XCB_BUTTON_RELEASE` events, but it will still receive those events
// from the X server.
EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_input_raw_button_press_event_t{
/*response_type=*/XCB_GE_GENERIC,
/*extension=*/s_xinputMajorOpcode,
/*sequence=*/4,
/*length=*/2,
/*event_type=*/XCB_INPUT_RAW_BUTTON_PRESS,
/*deviceid=*/s_virtualCorePointerId,
/*time=*/3984920,
/*detail=*/GetParam().m_button,
/*sourceid=*/s_physicalPointerDeviceId,
/*valuators_len=*/2,
/*flags=*/0,
/*pad0[4]=*/{},
/*full_sequence=*/4
})))
.WillOnce(Return(nullptr))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_button_press_event_t{
/*response_type=*/XCB_BUTTON_PRESS,
/*detail=*/static_cast<xcb_button_t>(GetParam().m_button),
/*sequence=*/4,
/*time=*/3984920,
/*root=*/s_rootWindow,
/*event=*/119537664,
/*child=*/0,
/*root_x=*/55,
/*root_y=*/1099,
/*event_x=*/55,
/*event_y=*/55,
/*state=*/0,
/*same_screen=*/1
})))
.WillOnce(Return(nullptr))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_input_raw_button_release_event_t{
/*response_type=*/XCB_GE_GENERIC,
/*extension=*/s_xinputMajorOpcode,
/*sequence=*/4,
/*length=*/2,
/*event_type=*/XCB_INPUT_RAW_BUTTON_RELEASE,
/*deviceid=*/s_virtualCorePointerId,
/*time=*/3984964,
/*detail=*/GetParam().m_button,
/*sourceid=*/s_physicalPointerDeviceId,
/*valuators_len=*/2,
/*flags=*/0,
/*pad0[4]=*/{},
/*full_sequence=*/4
})))
.WillOnce(Return(nullptr))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_button_release_event_t{
/*response_type=*/XCB_BUTTON_RELEASE,
/*detail=*/static_cast<xcb_button_t>(GetParam().m_button),
/*sequence=*/4,
/*time=*/3984964,
/*root=*/s_rootWindow,
/*event=*/119537664,
/*child=*/0,
/*root_x=*/55,
/*root_y=*/1099,
/*event_x=*/55,
/*event_y=*/55,
/*state=*/XCB_KEY_BUT_MASK_BUTTON_1,
/*same_screen=*/1
})))
.WillOnce(Return(nullptr))
;
m_application.Start();
InputSystemCursorRequestBus::Event(
InputDeviceMouse::Id,
&InputSystemCursorRequests::SetSystemCursorState,
SystemCursorState::ConstrainedAndHidden);
const InputChannel* activeButtonChannel = InputChannelRequests::FindInputChannel(GetInputChannelIdForButton(GetParam().m_button));
const auto inactiveButtonChannels = [this]()
{
const auto inactiveButtonChannelIds = GetIdleChannelIdsForButton(GetParam().m_button);
AZStd::array<const InputChannel*, 4> channels{};
AZStd::transform(begin(inactiveButtonChannelIds), end(inactiveButtonChannelIds), begin(channels), [](const InputChannelId& id)
{
return InputChannelRequests::FindInputChannel(id);
});
return channels;
}();
ASSERT_TRUE(activeButtonChannel);
ASSERT_THAT(inactiveButtonChannels, Each(NotNull()));
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Idle));
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
PumpApplication();
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Began));
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
PumpApplication();
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Updated));
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
PumpApplication();
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Ended));
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
PumpApplication();
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Idle));
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
}
INSTANTIATE_TEST_CASE_P(
AllButtons,
XcbInputDeviceMouseButtonTests,
testing::Values(
MouseButtonTestData{ XCB_BUTTON_INDEX_1 },
MouseButtonTestData{ XCB_BUTTON_INDEX_2 },
MouseButtonTestData{ XCB_BUTTON_INDEX_3 }
// XCB_BUTTON_INDEX_4 and XCB_BUTTON_INDEX_5 map to positive and
// negative scroll wheel events, which are handled as motion events
)
);
TEST_F(XcbInputDeviceMouseTests, MovementInputChannelsUpdateStateFromXcbEvents)
{
using testing::Each;
using testing::Eq;
using testing::FloatEq;
using testing::NotNull;
using testing::Property;
using testing::Return;
// Set the expectations for the events that will be generated
// nullptr entries represent when the event queue is empty, and will cause
// PumpSystemEventLoopUntilEmpty to return
//
// Event pointers are freed by the calling code, so these actions
// malloc new copies
//
// The xcb mouse does not react to the `XCB_MOTION_NOTIFY` event, but
// it will still receive it from the X server.
EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_input_raw_motion_event_t{
/*response_type=*/XCB_GE_GENERIC,
/*extension=*/s_xinputMajorOpcode,
/*sequence=*/5,
/*length=*/10,
/*event_type=*/XCB_INPUT_RAW_MOTION,
/*deviceid=*/s_virtualCorePointerId,
/*time=*/0, // use the time value to identify each event
/*detail=*/XCB_MOTION_NORMAL,
/*sourceid=*/s_physicalPointerDeviceId,
/*valuators_len=*/2, // number of axes that have values for this event
/*flags=*/0,
/*pad0[4]=*/{},
/*full_sequence=*/5,
})))
.WillOnce(Return(nullptr))
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_motion_notify_event_t{
/*response_type=*/XCB_MOTION_NOTIFY,
/*detail=*/XCB_MOTION_NORMAL,
/*sequence=*/5,
/*time=*/1, // use the time value to identify each event
/*root=*/s_rootWindow,
/*event=*/127926272,
/*child=*/0,
/*root_x=*/95,
/*root_y=*/1079,
/*event_x=*/95,
/*event_y=*/20,
/*state=*/0,
/*same_screen=*/1,
})))
.WillOnce(Return(nullptr))
;
AZStd::array axisValues
{
xcb_input_fp3232_t{ /*.integral=*/ 1, /*.fraction=*/0 }, // x motion
xcb_input_fp3232_t{ /*.integral=*/ 2, /*.fraction=*/0 } // y motion
};
EXPECT_CALL(m_interface, xcb_input_raw_button_press_axisvalues_length(testing::Field(&xcb_input_raw_button_press_event_t::time, 0)))
.WillRepeatedly(testing::Return(2)); // x and y axis
EXPECT_CALL(m_interface, xcb_input_raw_button_press_axisvalues_raw(testing::Field(&xcb_input_raw_button_press_event_t::time, 0)))
.WillRepeatedly(testing::Return(axisValues.data())); // x and y axis
m_application.Start();
InputSystemCursorRequestBus::Event(
InputDeviceMouse::Id,
&InputSystemCursorRequests::SetSystemCursorState,
SystemCursorState::ConstrainedAndHidden);
const InputChannel* xMotionChannel = InputChannelRequests::FindInputChannel(InputDeviceMouse::Movement::X);
const InputChannel* yMotionChannel = InputChannelRequests::FindInputChannel(InputDeviceMouse::Movement::Y);
ASSERT_TRUE(xMotionChannel);
ASSERT_TRUE(yMotionChannel);
EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Idle));
EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Idle));
EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(0.0f));
EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(0.0f));
PumpApplication();
EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Began));
EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Began));
EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(1.0f));
EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(2.0f));
PumpApplication();
EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Ended));
EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Ended));
EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(0.0f));
EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(0.0f));
}
struct GetCursorPositionParam
{
int16_t m_x;
int16_t m_y;
};
class XcbGetSystemCursorPositionTests
: public XcbInputDeviceMouseTests
, public testing::WithParamInterface<GetCursorPositionParam>
{
};
TEST_P(XcbGetSystemCursorPositionTests, GetSystemCursorPositionNormalizedReturnsCorrectValue)
{
using testing::Eq;
using testing::Field;
using testing::Return;
using testing::_;
xcb_window_t focusWindow = 42;
const xcb_query_pointer_reply_t queryPointerReply{
/*.response_type=*/XCB_QUERY_POINTER,
/*.same_screen=*/1,
/*.sequence=*/0,
/*.length=*/1,
/*.root=*/s_rootWindow,
/*.child=*/focusWindow,
/*.root_x=*/static_cast<int16_t>(GetParam().m_x + s_defaultWindowGeometry.x),
/*.root_y=*/static_cast<int16_t>(GetParam().m_y + s_defaultWindowGeometry.y),
/*.win_x=*/GetParam().m_x,
/*.win_y=*/GetParam().m_y,
/*.mask=*/{},
/*.pad0[2]=*/{},
};
// Querying the root window's pointer gives its absolute value
const xcb_query_pointer_reply_t rootWindowQueryPointerReply{
/*.response_type=*/XCB_QUERY_POINTER,
/*.same_screen=*/1,
/*.sequence=*/0,
/*.length=*/1,
/*.root=*/s_rootWindow,
/*.child=*/s_rootWindow,
/*.root_x=*/static_cast<int16_t>(GetParam().m_x + s_defaultWindowGeometry.x),
/*.root_y=*/static_cast<int16_t>(GetParam().m_y + s_defaultWindowGeometry.y),
/*.win_x=*/static_cast<int16_t>(GetParam().m_x + s_defaultWindowGeometry.x),
/*.win_y=*/static_cast<int16_t>(GetParam().m_y + s_defaultWindowGeometry.y),
/*.mask=*/{},
/*.pad0[2]=*/{},
};
EXPECT_CALL(m_interface, xcb_get_property_value(Field(&xcb_get_property_reply_t::sequence, Eq(s_getActiveWindowPropertySequence))))
.WillRepeatedly(Return(&focusWindow));
EXPECT_CALL(m_interface, xcb_query_pointer(&m_connection, focusWindow))
.WillRepeatedly(Return(xcb_query_pointer_cookie_t{/*.sequence=*/1}));
EXPECT_CALL(m_interface, xcb_query_pointer_reply(&m_connection, Field(&xcb_query_pointer_cookie_t::sequence, 1), _))
.WillRepeatedly(ReturnMalloc<xcb_query_pointer_reply_t>(queryPointerReply));
EXPECT_CALL(m_interface, xcb_query_pointer(&m_connection, s_rootWindow))
.WillRepeatedly(Return(xcb_query_pointer_cookie_t{/*.sequence=*/2}));
EXPECT_CALL(m_interface, xcb_query_pointer_reply(&m_connection, Field(&xcb_query_pointer_cookie_t::sequence, 2), _))
.WillRepeatedly(ReturnMalloc<xcb_query_pointer_reply_t>(rootWindowQueryPointerReply));
m_application.Start();
InputSystemCursorRequestBus::Event(
InputDeviceMouse::Id,
&InputSystemCursorRequests::SetSystemCursorState,
SystemCursorState::ConstrainedAndHidden);
AZ::Vector2 systemCursorPositionNormalized = AZ::Vector2::CreateZero();
InputSystemCursorRequestBus::EventResult(
systemCursorPositionNormalized,
InputDeviceMouse::Id,
&InputSystemCursorRequests::GetSystemCursorPositionNormalized);
EXPECT_THAT(systemCursorPositionNormalized, ::testing::AllOf(
testing::Property(&AZ::Vector2::GetX, testing::FloatEq(static_cast<float>(GetParam().m_x) / s_defaultWindowGeometry.width)),
testing::Property(&AZ::Vector2::GetY, testing::FloatEq(static_cast<float>(GetParam().m_y) / s_defaultWindowGeometry.height))
));
}
INSTANTIATE_TEST_CASE_P(
AllPointerPositions,
XcbGetSystemCursorPositionTests,
testing::Values(
// Default mocked window geometry sets width and height to 100, all
// parameter values should be within [0, 100)
GetCursorPositionParam{ 50, 50 },
GetCursorPositionParam{ 25, 25 },
GetCursorPositionParam{ 0, 100 }
)
);
} // namespace AzFramework
@@ -17,5 +17,6 @@ set(FILES
XcbBaseTestFixture.cpp
XcbBaseTestFixture.h
XcbInputDeviceKeyboardTests.cpp
XcbInputDeviceMouseTests.cpp
XcbTestApplication.h
)
@@ -40,9 +40,9 @@ namespace AzToolsFramework
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QModelIndex parent(const QModelIndex& child) const override;
QModelIndex sibling(int row, int column, const QModelIndex& idx) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
protected:
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role /* = Qt::DisplayRole */) const override;
////////////////////////////////////////////////////////////////////
@@ -55,7 +55,7 @@ namespace AzToolsFramework
private slots:
void SourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
private:
AZ::u64 m_numberOfItemsDisplayed = 0;
AZ::u64 m_numberOfItemsDisplayed = 50;
int m_displayedItemsCounter = 0;
QPointer<AssetBrowserFilterModel> m_filterModel;
QMap<int, QModelIndex> m_indexMap;
@@ -0,0 +1,344 @@
/*
* 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 <AzTest/AzTest.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserComponent.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserFilterModel.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserModel.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserTableModel.h>
#include <AzToolsFramework/AssetBrowser/Entries/AssetBrowserEntry.h>
#include <AzToolsFramework/AssetBrowser/Entries/FolderAssetBrowserEntry.h>
#include <AzToolsFramework/AssetBrowser/Entries/ProductAssetBrowserEntry.h>
#include <AzToolsFramework/AssetBrowser/Entries/RootAssetBrowserEntry.h>
#include <AzToolsFramework/AssetBrowser/Entries/SourceAssetBrowserEntry.h>
#include <AzToolsFramework/AssetBrowser/Search/SearchWidget.h>
#include <AzToolsFramework/AssetDatabase/AssetDatabaseConnection.h>
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <QAbstractItemModelTester>
namespace UnitTest
{
// Test fixture for the AssetBrowser model that uses a QAbstractItemModelTester to validate the state of the model
// when QAbstractItemModel signals fire. Tests will exit with a fatal error if an invalid state is detected.
class AssetBrowserTest
: public ToolsApplicationFixture
, public testing::WithParamInterface<const char*>
{
protected:
enum class AssetEntryType
{
Root,
Folder,
Source,
Product
};
enum class FolderType
{
Root,
File
};
void SetUpEditorFixtureImpl() override;
void TearDownEditorFixtureImpl() override;
//! Creates a Mock Scan Folder
void AddScanFolder(AZ::s64 folderID, AZStd::string folderPath, AZStd::string displayName, FolderType folderType = FolderType::File);
//! Creates a Source entry from a mock file
AZ::Uuid CreateSourceEntry(
AZ::s64 fileID, AZ::s64 parentFolderID, AZStd::string filename, AssetEntryType sourceType = AssetEntryType::Source);
//! Creates a product from a given sourceEntry
void CreateProduct(AZ::s64 productID, AZ::Uuid sourceUuid, AZStd::string productName);
void SetupAssetBrowser();
void PrintModel(const QAbstractItemModel* model, AZStd::function<void(const QString&)> printer);
QModelIndex GetModelIndex(const QAbstractItemModel* model, int targetDepth, int row = 0);
AZStd::shared_ptr<AzToolsFramework::AssetBrowser::RootAssetBrowserEntry> GetRootEntry();
AZStd::vector<QString> GetVectorFromFormattedString(const QString& formattedString);
protected:
QString m_assetBrowserHierarchy = QString();
AZStd::unique_ptr<AzToolsFramework::AssetBrowser::SearchWidget> m_searchWidget;
AZStd::unique_ptr<AzToolsFramework::AssetBrowser::AssetBrowserComponent> m_assetBrowserComponent;
AZStd::unique_ptr<AzToolsFramework::AssetBrowser::AssetBrowserFilterModel> m_filterModel;
AZStd::unique_ptr<AzToolsFramework::AssetBrowser::AssetBrowserTableModel> m_tableModel;
AZStd::unique_ptr<QAbstractItemModelTester> m_modelTesterAssetBrowser;
AZStd::unique_ptr<QAbstractItemModelTester> m_modelTesterFilterModel;
AZStd::unique_ptr<QAbstractItemModelTester> m_modelTesterTableModel;
QVector<int> m_folderIds = { 13, 14, 15 };
QVector<int> m_sourceIDs = { 1, 2, 3, 4, 5 };
QVector<int> m_productIDs = { 1, 2, 3, 4, 5 };
};
void AssetBrowserTest::SetUpEditorFixtureImpl()
{
GetApplication()->RegisterComponentDescriptor(AzToolsFramework::EditorEntityContextComponent::CreateDescriptor());
m_assetBrowserComponent = AZStd::make_unique<AzToolsFramework::AssetBrowser::AssetBrowserComponent>();
m_assetBrowserComponent->Activate();
m_filterModel = AZStd::make_unique<AzToolsFramework::AssetBrowser::AssetBrowserFilterModel>();
m_tableModel = AZStd::make_unique<AzToolsFramework::AssetBrowser::AssetBrowserTableModel>();
m_filterModel->setSourceModel(m_assetBrowserComponent->GetAssetBrowserModel());
m_tableModel->setSourceModel(m_filterModel.get());
m_modelTesterAssetBrowser = AZStd::make_unique<QAbstractItemModelTester>(m_assetBrowserComponent->GetAssetBrowserModel());
m_modelTesterFilterModel = AZStd::make_unique<QAbstractItemModelTester>(m_filterModel.get());
m_modelTesterTableModel = AZStd::make_unique<QAbstractItemModelTester>(m_tableModel.get());
m_searchWidget = AZStd::make_unique<AzToolsFramework::AssetBrowser::SearchWidget>();
// Setup String filters
m_searchWidget->Setup(true, true);
m_filterModel->SetFilter(m_searchWidget->GetFilter());
SetupAssetBrowser();
}
void AssetBrowserTest::TearDownEditorFixtureImpl()
{
m_modelTesterAssetBrowser.reset();
m_modelTesterFilterModel.reset();
m_modelTesterTableModel.reset();
m_tableModel.reset();
m_filterModel.reset();
m_assetBrowserComponent->Deactivate();
m_assetBrowserComponent.reset();
m_searchWidget.reset();
}
void AssetBrowserTest::AddScanFolder(
AZ::s64 folderID, AZStd::string folderPath, AZStd::string displayName, FolderType folderType /*= FolderType::File*/)
{
AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry scanFolder = AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry();
scanFolder.m_scanFolderID = folderID;
scanFolder.m_scanFolder = folderPath;
scanFolder.m_displayName = displayName;
scanFolder.m_isRoot = folderType == FolderType::Root;
GetRootEntry()->AddScanFolder(scanFolder);
}
AZ::Uuid AssetBrowserTest::CreateSourceEntry(
AZ::s64 fileID, AZ::s64 parentFolderID, AZStd::string filename, AssetEntryType sourceType /*= AssetEntryType::Source*/)
{
AzToolsFramework::AssetDatabase::FileDatabaseEntry entry = AzToolsFramework::AssetDatabase::FileDatabaseEntry();
entry.m_scanFolderPK = parentFolderID;
entry.m_fileID = fileID;
entry.m_fileName = filename;
entry.m_isFolder = sourceType == AssetEntryType::Folder;
GetRootEntry()->AddFile(entry);
if (!entry.m_isFolder)
{
AzToolsFramework::AssetBrowser::SourceWithFileID entrySource = AzToolsFramework::AssetBrowser::SourceWithFileID();
entrySource.first = entry.m_fileID;
entrySource.second = AzToolsFramework::AssetDatabase::SourceDatabaseEntry();
entrySource.second.m_scanFolderPK = parentFolderID;
entrySource.second.m_sourceName = filename;
entrySource.second.m_sourceID = fileID;
entrySource.second.m_sourceGuid = AZ::Uuid::CreateRandom();
GetRootEntry()->AddSource(entrySource);
return entrySource.second.m_sourceGuid;
}
return AZ::Uuid::CreateNull();
}
void AssetBrowserTest::CreateProduct(AZ::s64 productID, AZ::Uuid sourceUuid, AZStd::string productName)
{
AzToolsFramework::AssetBrowser::ProductWithUuid product = AzToolsFramework::AssetBrowser::ProductWithUuid();
product.first = sourceUuid;
product.second = AzToolsFramework::AssetDatabase::ProductDatabaseEntry();
product.second.m_productID = productID;
product.second.m_subID = aznumeric_cast<AZ::u32>(productID);
product.second.m_productName = productName;
GetRootEntry()->AddProduct(product);
}
void AssetBrowserTest::SetupAssetBrowser()
{
// RootEntries : 1 | Folders : 4 | SourceEntries : 5 | ProductEntries : 9
m_assetBrowserHierarchy = R"(
D:
\
dev
o3de
GameProject
Assets
Source_1
Product_1_1
Product_1_0
Source_0
Product_0_3
Product_0_2
Product_0_1
Product_0_0
Scripts
Source_3
Source_2
Product_2_2
Product_2_1
Product_2_0
Misc
Source_4
Product_4_2
Product_4_1
Product_4_0 )";
namespace AzAssetBrowser = AzToolsFramework::AssetBrowser;
AddScanFolder(m_folderIds.at(2), "D:/dev/o3de/GameProject/Misc", "Misc");
AZ::Uuid sourceUuid_4 = CreateSourceEntry(m_sourceIDs.at(4), m_folderIds.at(2), "Source_4");
CreateProduct(m_productIDs.at(0), sourceUuid_4, "Product_4_0");
CreateProduct(m_productIDs.at(1), sourceUuid_4, "Product_4_1");
CreateProduct(m_productIDs.at(2), sourceUuid_4, "Product_4_2");
AddScanFolder(m_folderIds.at(1), "D:/dev/o3de/GameProject/Scripts", "Scripts");
AZ::Uuid sourceUuid_2 = CreateSourceEntry(m_sourceIDs.at(2), m_folderIds.at(1), "Source_2");
CreateProduct(m_productIDs.at(0), sourceUuid_2, "Product_2_0");
CreateProduct(m_productIDs.at(1), sourceUuid_2, "Product_2_1");
CreateProduct(m_productIDs.at(2), sourceUuid_2, "Product_2_2");
CreateSourceEntry(m_sourceIDs.at(3), m_folderIds.at(1), "Source_3");
AddScanFolder(m_folderIds.at(0), "D:/dev/o3de/GameProject/Assets", "Assets");
AZ::Uuid sourceUuid_0 = CreateSourceEntry(m_sourceIDs.at(0), m_folderIds.at(0), "Source_0");
CreateProduct(m_productIDs.at(0), sourceUuid_0, "Product_0_0");
CreateProduct(m_productIDs.at(1), sourceUuid_0, "Product_0_1");
CreateProduct(m_productIDs.at(2), sourceUuid_0, "Product_0_2");
CreateProduct(m_productIDs.at(3), sourceUuid_0, "Product_0_3");
AZ::Uuid sourceUuid_1 = CreateSourceEntry(m_sourceIDs.at(1), m_folderIds.at(0), "Source_1");
CreateProduct(m_productIDs.at(0), sourceUuid_1, "Product_1_0");
CreateProduct(m_productIDs.at(1), sourceUuid_1, "Product_1_1");
}
void AssetBrowserTest::PrintModel(const QAbstractItemModel* model, AZStd::function<void(const QString&)> printer)
{
AZStd::deque<AZStd::pair<QModelIndex, int>> indices;
indices.push_back({ model->index(0, 0), 0 });
while (!indices.empty())
{
auto [index, depth] = indices.front();
indices.pop_front();
QString indentString;
for (int i = 0; i < depth; ++i)
{
indentString += " ";
}
const QString message = indentString + index.data(Qt::DisplayRole).toString();
printer(message);
for (int i = 0; i < model->rowCount(index); ++i)
{
indices.emplace_front(model->index(i, 0, index), depth + 1);
}
}
}
QModelIndex AssetBrowserTest::GetModelIndex(const QAbstractItemModel* model, int targetDepth, int row)
{
AZStd::deque<AZStd::pair<QModelIndex, int>> indices;
indices.push_back({ model->index(0, 0), 0 });
while (!indices.empty())
{
auto [index, depth] = indices.front();
indices.pop_front();
for (int i = 0; i < model->rowCount(index); ++i)
{
if (depth + 1 == targetDepth && row == i)
{
return model->index(i, 0, index);
}
indices.emplace_front(model->index(i, 0, index), depth + 1);
}
}
return QModelIndex();
}
AZStd::shared_ptr<AzToolsFramework::AssetBrowser::RootAssetBrowserEntry> AssetBrowserTest::GetRootEntry()
{
return m_assetBrowserComponent->GetAssetBrowserModel()->GetRootEntry();
}
AZStd::vector<QString> AssetBrowserTest::GetVectorFromFormattedString(const QString& formattedString)
{
AZStd::vector<QString> hierarchySections;
QStringList splittedList = formattedString.split('\n', Qt::SkipEmptyParts);
for (auto& str : splittedList)
{
str.replace(" ", "");
hierarchySections.push_back(str);
}
return hierarchySections;
}
TEST_F(AssetBrowserTest, CheckCorrectNumberOfEntriesInTableView)
{
m_filterModel->FilterUpdatedSlotImmediate();
const int tableViewRowcount = m_tableModel->rowCount();
// RowCount should be 17 -> 5 SourceEntries + 12 ProductEntries)
EXPECT_EQ(tableViewRowcount, 17);
}
TEST_F(AssetBrowserTest, CheckCorrectNumberOfEntriesInTableViewAfterStringFilter)
{
/*
*-Source_1
* |
* |-product_1_0
* |-product_1_1
*
*
* Matching entries = 3
*/
// Apply string filter
m_searchWidget->SetTextFilter(QString("source_1"));
m_filterModel->FilterUpdatedSlotImmediate();
const int tableViewRowcount = m_tableModel->rowCount();
EXPECT_EQ(tableViewRowcount, 3);
}
TEST_F(AssetBrowserTest, CheckScanFolderAddition)
{
EXPECT_EQ(m_assetBrowserComponent->GetAssetBrowserModel()->rowCount(), 1);
const int newFolderId = 20;
AddScanFolder(newFolderId, "E:/TestFolder/TestFolder2", "TestFolder");
// Since the folder is empty it shouldn't be added to the model.
EXPECT_EQ(m_assetBrowserComponent->GetAssetBrowserModel()->rowCount(), 1);
CreateSourceEntry(123, newFolderId, "DummyFile");
// When we add a file to the folder it should be added to the model
EXPECT_EQ(m_assetBrowserComponent->GetAssetBrowserModel()->rowCount(), 2);
}
} // namespace UnitTest
@@ -123,6 +123,7 @@ set(FILES
UI/EntityIdQLineEditTests.cpp
UI/EntityOutlinerTests.cpp
UI/EntityPropertyEditorTests.cpp
UI/AssetBrowserTests.cpp
UndoStack.cpp
Viewport/ClusterTests.cpp
Viewport/ViewportEditorModeTests.cpp
@@ -76,6 +76,12 @@ foreach(project_name project_path IN ZIP_LISTS LY_PROJECTS_TARGET_NAME LY_PROJEC
Legacy::CrySystem
)
if(PAL_TRAIT_BUILD_SERVER_SUPPORTED)
set(server_runtime_dependencies
Legacy::CrySystem
)
endif()
endif()
################################################################################
+2 -2
View File
@@ -1121,8 +1121,8 @@ inline ISystem* GetISystem()
// Description:
// This function must be called once by each module at the beginning, to setup global pointers.
extern "C" AZ_DLL_EXPORT void ModuleInitISystem(ISystem* pSystem, const char* moduleName);
extern "C" AZ_DLL_EXPORT void ModuleShutdownISystem(ISystem* pSystem);
void ModuleInitISystem(ISystem* pSystem, const char* moduleName);
void ModuleShutdownISystem(ISystem* pSystem);
extern "C" AZ_DLL_EXPORT void InjectEnvironment(void* env);
extern "C" AZ_DLL_EXPORT void DetachEnvironment();
+2 -2
View File
@@ -74,7 +74,7 @@ void InitCRTHandlers() {}
//////////////////////////////////////////////////////////////////////////
// This is an entry to DLL initialization function that must be called for each loaded module
//////////////////////////////////////////////////////////////////////////
extern "C" AZ_DLL_EXPORT void ModuleInitISystem(ISystem* pSystem, [[maybe_unused]] const char* moduleName)
void ModuleInitISystem(ISystem* pSystem, [[maybe_unused]] const char* moduleName)
{
if (gEnv) // Already registered.
{
@@ -96,7 +96,7 @@ extern "C" AZ_DLL_EXPORT void ModuleInitISystem(ISystem* pSystem, [[maybe_unused
} // if pSystem
}
extern "C" AZ_DLL_EXPORT void ModuleShutdownISystem([[maybe_unused]] ISystem* pSystem)
void ModuleShutdownISystem([[maybe_unused]] ISystem* pSystem)
{
// Unregister with AZ environment.
AZ::Environment::Detach();
-23
View File
@@ -173,8 +173,6 @@ void CryEngineSignalHandler(int signal)
//////////////////////////////////////////////////////////////////////////
#if defined(WIN32) || defined(LINUX) || defined(APPLE)
# define DLL_MODULE_INIT_ISYSTEM "ModuleInitISystem"
# define DLL_MODULE_SHUTDOWN_ISYSTEM "ModuleShutdownISystem"
# define DLL_INITFUNC_RENDERER "PackageRenderConstructor"
# define DLL_INITFUNC_SOUND "CreateSoundSystem"
# define DLL_INITFUNC_FONT "CreateCryFontInterface"
@@ -188,8 +186,6 @@ void CryEngineSignalHandler(int signal)
#if defined(AZ_RESTRICTED_SECTION_IMPLEMENTED)
#undef AZ_RESTRICTED_SECTION_IMPLEMENTED
#else
# define DLL_MODULE_INIT_ISYSTEM (LPCSTR)2
# define DLL_MODULE_SHUTDOWN_ISYSTEM (LPCSTR)3
# define DLL_INITFUNC_RENDERER (LPCSTR)1
# define DLL_INITFUNC_RENDERER (LPCSTR)1
# define DLL_INITFUNC_SOUND (LPCSTR)1
@@ -445,18 +441,6 @@ AZStd::unique_ptr<AZ::DynamicModuleHandle> CSystem::LoadDLL(const char* dllName)
return handle;
}
//////////////////////////////////////////////////////////////////////////
// After loading DLL initialize it by calling ModuleInitISystem
//////////////////////////////////////////////////////////////////////////
AZStd::string moduleName = PathUtil::GetFileName(dllName);
typedef void*(*PtrFunc_ModuleInitISystem)(ISystem* pSystem, const char* moduleName);
PtrFunc_ModuleInitISystem pfnModuleInitISystem = handle->GetFunction<PtrFunc_ModuleInitISystem>(DLL_MODULE_INIT_ISYSTEM);
if (pfnModuleInitISystem)
{
pfnModuleInitISystem(this, moduleName.c_str());
}
return handle;
}
@@ -497,13 +481,6 @@ void CSystem::ShutdownModuleLibraries()
#if !defined(AZ_MONOLITHIC_BUILD)
for (auto iterator = m_moduleDLLHandles.begin(); iterator != m_moduleDLLHandles.end(); ++iterator)
{
typedef void*( * PtrFunc_ModuleShutdownISystem )(ISystem* pSystem);
PtrFunc_ModuleShutdownISystem pfnModuleShutdownISystem = iterator->second->GetFunction<PtrFunc_ModuleShutdownISystem>(DLL_MODULE_SHUTDOWN_ISYSTEM);
if (pfnModuleShutdownISystem)
{
pfnModuleShutdownISystem(this);
}
if (iterator->second->IsLoaded())
{
iterator->second->Unload();
-5
View File
@@ -333,11 +333,6 @@ void CXConsole::Init(ISystem* pSystem)
m_nLoadingBackTexID = -1;
if (gEnv->IsDedicated())
{
m_bConsoleActive = true;
}
REGISTER_COMMAND("ConsoleShow", &ConsoleShow, VF_NULL, "Opens the console");
REGISTER_COMMAND("ConsoleHide", &ConsoleHide, VF_NULL, "Closes the console");
@@ -25,7 +25,7 @@ namespace AssetProcessor
: public ::testing::Test
{
protected:
UnitTestUtils::AssertAbsorber* m_errorAbsorber;
AZStd::unique_ptr<UnitTestUtils::AssertAbsorber> m_errorAbsorber{};
FileStatePassthrough m_fileStateCache;
void SetUp() override
@@ -40,7 +40,7 @@ namespace AssetProcessor
m_ownsSysAllocator = true;
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
}
m_errorAbsorber = new UnitTestUtils::AssertAbsorber();
m_errorAbsorber = AZStd::make_unique<UnitTestUtils::AssertAbsorber>();
m_application = AZStd::make_unique<AzFramework::Application>();
@@ -62,8 +62,8 @@ namespace AssetProcessor
AssetUtilities::ResetAssetRoot();
m_application.reset();
delete m_errorAbsorber;
m_errorAbsorber = nullptr;
m_errorAbsorber.reset();
if (m_ownsSysAllocator)
{
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
@@ -4139,11 +4139,19 @@ struct LockedFileTest
switch (message.GetMessageType())
{
case SourceFileNotificationMessage::MessageType:
if (const auto sourceFileMessage = azrtti_cast<const SourceFileNotificationMessage*>(&message);
sourceFileMessage != nullptr && sourceFileMessage->m_type == SourceFileNotificationMessage::NotificationType::FileRemoved
&& m_callback)
if (const auto sourceFileMessage = azrtti_cast<const SourceFileNotificationMessage*>(&message); sourceFileMessage != nullptr &&
sourceFileMessage->m_type == SourceFileNotificationMessage::NotificationType::FileRemoved)
{
m_callback();
// The File Remove message will occur before an attempt to delete the file
// Wait for more than 1 File Remove message.
// This indicates the AP has attempted to delete the file once, failed to do so and is now retrying
++m_deleteCounter;
if(m_deleteCounter > 1 && m_callback)
{
m_callback();
m_callback = {}; // Unset it to be safe, we only intend to run the callback once
}
}
break;
default:
@@ -4167,6 +4175,7 @@ struct LockedFileTest
ModtimeScanningTest::TearDown();
}
AZStd::atomic_int m_deleteCounter{ 0 };
AZStd::function<void()> m_callback;
};
@@ -4206,6 +4215,10 @@ TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeleteFails)
TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased)
{
// This test is intended to verify the AP will successfully retry deleting a source asset
// when one of its product assets is locked temporarily
// We'll lock the file by holding it open
auto theFile = m_data->m_absolutePath[1].toUtf8();
const char* theFileString = theFile.constData();
auto [sourcePath, productPath] = *m_data->m_productPaths.find(theFileString);
@@ -4218,19 +4231,22 @@ TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased)
ASSERT_GT(m_data->m_productPaths.size(), 0);
QFile product(productPath);
// Open the file and keep it open to lock it
// We'll start a thread later to unlock the file
// This will allow us to test how AP handles trying to delete a locked file
ASSERT_TRUE(product.open(QIODevice::ReadOnly));
// Check if we can delete the file now, if we can't, proceed with the test
// If we can, it means the OS running this test doesn't lock open files so there's nothing to test
if (!AZ::IO::SystemFile::Delete(productPath.toUtf8().constData()))
{
AZStd::thread workerThread;
m_deleteCounter = 0;
m_callback = [&product, &workerThread]() {
workerThread = AZStd::thread([&product]() {
AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(60));
product.close();
});
// Set up a callback which will fire after at least 1 retry
// Unlock the file at that point so AP can successfully delete it
m_callback = [&product]()
{
product.close();
};
QMetaObject::invokeMethod(
@@ -4240,8 +4256,9 @@ TEST_F(LockedFileTest, DeleteFile_LockedProduct_DeletesWhenReleased)
EXPECT_FALSE(QFile::exists(productPath));
EXPECT_EQ(m_data->m_deletedSources.size(), 1);
workerThread.join();
EXPECT_GT(m_deleteCounter, 1); // Make sure the AP tried more than once to delete the file
m_errorAbsorber->ExpectAsserts(0);
}
else
{
@@ -49,6 +49,12 @@ int main(int argc, char* argv[])
AZStd::unique_ptr<AzFramework::ProcessWatcher> shellProcess(AzFramework::ProcessWatcher::LaunchProcess(shellProcessLaunch, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE));
shellProcess->WaitForProcessToExit(120);
shellProcess.reset();
parameters = AZStd::string::format("-c \"%s/scripts/o3de.sh register --this-engine\"", enginePath.c_str());
shellProcessLaunch.m_commandlineParameters = parameters;
shellProcess.reset(AzFramework::ProcessWatcher::LaunchProcess(shellProcessLaunch, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_NONE));
shellProcess->WaitForProcessToExit(120);
shellProcess.reset();
AZ::IO::FixedMaxPath projectManagerPath = installedBinariesFolder/"o3de.app"/"Contents"/"MacOS"/"o3de";
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
@@ -10,6 +10,8 @@
#include <QProcessEnvironment>
#include <QDir>
#include <AzCore/Utils/Utils.h>
namespace O3DE::ProjectManager
{
namespace ProjectUtils
@@ -94,5 +96,10 @@ namespace O3DE::ProjectManager
QProcessEnvironment::systemEnvironment(),
QObject::tr("Running get_python script..."));
}
AZ::IO::FixedMaxPath GetEditorDirectory()
{
return AZ::Utils::GetExecutableDirectory();
}
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -11,6 +11,9 @@
#include <QStandardPaths>
#include <QDir>
#include <AzCore/Utils/Utils.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
namespace O3DE::ProjectManager
{
namespace ProjectUtils
@@ -104,5 +107,35 @@ namespace O3DE::ProjectManager
QProcessEnvironment::systemEnvironment(),
QObject::tr("Running get_python script..."));
}
AZ::IO::FixedMaxPath GetEditorDirectory()
{
AZ::IO::FixedMaxPath executableDirectory = AZ::Utils::GetExecutableDirectory();
AZ::IO::FixedMaxPath editorPath{ executableDirectory };
editorPath /= "../../../Editor.app/Contents/MacOS";
editorPath = editorPath.LexicallyNormal();
if (!AZ::IO::SystemFile::IsDirectory(editorPath.c_str()))
{
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
if (AZ::IO::FixedMaxPath installedBinariesPath;
settingsRegistry->Get(installedBinariesPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_InstalledBinaryFolder))
{
if (AZ::IO::FixedMaxPath engineRootFolder;
settingsRegistry->Get(engineRootFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder))
{
editorPath = engineRootFolder / installedBinariesPath / "Editor.app/Contents/MacOS";
}
}
}
if (!AZ::IO::SystemFile::IsDirectory(editorPath.c_str()))
{
AZ_Error("ProjectManager", false, "Unable to find the Editor app bundle!");
}
}
return editorPath;
}
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -14,6 +14,8 @@
#include <QProcess>
#include <QProcessEnvironment>
#include <AzCore/Utils/Utils.h>
namespace O3DE::ProjectManager
{
namespace ProjectUtils
@@ -139,5 +141,10 @@ namespace O3DE::ProjectManager
QProcessEnvironment::systemEnvironment(),
QObject::tr("Running get_python script..."));
}
AZ::IO::FixedMaxPath GetEditorDirectory()
{
return AZ::Utils::GetExecutableDirectory();
}
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -81,6 +81,8 @@ namespace O3DE::ProjectManager
DownloadStatus m_downloadStatus = UnknownDownloadStatus;
QStringList m_features;
QString m_requirement;
QString m_licenseText;
QString m_licenseLink;
QString m_directoryLink;
QString m_documentationLink;
QString m_version = "Unknown Version";
@@ -52,6 +52,22 @@ namespace O3DE::ProjectManager
Update(selectedIndices[0]);
}
void SetLabelElidedText(QLabel* label, QString text)
{
QFontMetrics nameFontMetrics(label->font());
int labelWidth = label->width();
// Don't elide if the widgets are sized too small (sometimes occurs when loading gem catalog)
if (labelWidth > 100)
{
label->setText(nameFontMetrics.elidedText(text, Qt::ElideRight, labelWidth));
}
else
{
label->setText(text);
}
}
void GemInspector::Update(const QModelIndex& modelIndex)
{
if (!modelIndex.isValid())
@@ -59,38 +75,52 @@ namespace O3DE::ProjectManager
m_mainWidget->hide();
}
m_nameLabel->setText(m_model->GetDisplayName(modelIndex));
m_creatorLabel->setText(m_model->GetCreator(modelIndex));
SetLabelElidedText(m_nameLabel, m_model->GetDisplayName(modelIndex));
SetLabelElidedText(m_creatorLabel, m_model->GetCreator(modelIndex));
m_summaryLabel->setText(m_model->GetSummary(modelIndex));
m_summaryLabel->adjustSize();
m_licenseLinkLabel->setText(m_model->GetLicenseText(modelIndex));
m_licenseLinkLabel->SetUrl(m_model->GetLicenseLink(modelIndex));
m_directoryLinkLabel->SetUrl(m_model->GetDirectoryLink(modelIndex));
m_documentationLinkLabel->SetUrl(m_model->GetDocLink(modelIndex));
if (m_model->HasRequirement(modelIndex))
{
m_reqirementsIconLabel->show();
m_reqirementsTitleLabel->show();
m_reqirementsTextLabel->show();
m_requirementsIconLabel->show();
m_requirementsTitleLabel->show();
m_requirementsTextLabel->show();
m_requirementsMainSpacer->changeSize(0, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
m_reqirementsTitleLabel->setText("Requirement");
m_reqirementsTextLabel->setText(m_model->GetRequirement(modelIndex));
m_requirementsTitleLabel->setText(tr("Requirement"));
m_requirementsTextLabel->setText(m_model->GetRequirement(modelIndex));
}
else
{
m_reqirementsIconLabel->hide();
m_reqirementsTitleLabel->hide();
m_reqirementsTextLabel->hide();
m_requirementsIconLabel->hide();
m_requirementsTitleLabel->hide();
m_requirementsTextLabel->hide();
m_requirementsMainSpacer->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed);
}
// Depending gems
m_dependingGems->Update("Depending Gems", "The following Gems will be automatically enabled with this Gem.", m_model->GetDependingGemNames(modelIndex));
QStringList dependingGems = m_model->GetDependingGemNames(modelIndex);
if (!dependingGems.isEmpty())
{
m_dependingGems->Update(tr("Depending Gems"), tr("The following Gems will be automatically enabled with this Gem."), dependingGems);
m_dependingGems->show();
}
else
{
m_dependingGems->hide();
}
// Additional information
m_versionLabel->setText(QString("Gem Version: %1").arg(m_model->GetVersion(modelIndex)));
m_lastUpdatedLabel->setText(QString("Last Updated: %1").arg(m_model->GetLastUpdated(modelIndex)));
m_binarySizeLabel->setText(QString("Binary Size: %1 KB").arg(m_model->GetBinarySizeInKB(modelIndex)));
m_versionLabel->setText(tr("Gem Version: %1").arg(m_model->GetVersion(modelIndex)));
m_lastUpdatedLabel->setText(tr("Last Updated: %1").arg(m_model->GetLastUpdated(modelIndex)));
m_binarySizeLabel->setText(tr("Binary Size: %1 KB").arg(m_model->GetBinarySizeInKB(modelIndex)));
m_mainWidget->adjustSize();
m_mainWidget->show();
@@ -108,35 +138,51 @@ namespace O3DE::ProjectManager
{
// Gem name, creator and summary
m_nameLabel = CreateStyledLabel(m_mainLayout, 18, s_headerColor);
m_creatorLabel = CreateStyledLabel(m_mainLayout, 12, s_headerColor);
m_creatorLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_headerColor);
m_mainLayout->addSpacing(5);
// TODO: QLabel seems to have issues determining the right sizeHint() for our font with the given font size.
// This results into squeezed elements in the layout in case the text is a little longer than a sentence.
m_summaryLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor);
m_summaryLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_headerColor);
m_mainLayout->addWidget(m_summaryLabel);
m_summaryLabel->setWordWrap(true);
m_summaryLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
m_summaryLabel->setOpenExternalLinks(true);
m_mainLayout->addSpacing(5);
// License
{
QHBoxLayout* licenseHLayout = new QHBoxLayout();
licenseHLayout->setMargin(0);
licenseHLayout->setAlignment(Qt::AlignLeft);
m_mainLayout->addLayout(licenseHLayout);
QLabel* licenseLabel = CreateStyledLabel(licenseHLayout, s_baseFontSize, s_headerColor);
licenseLabel->setText(tr("License: "));
m_licenseLinkLabel = new LinkLabel("", QUrl(), s_baseFontSize);
licenseHLayout->addWidget(m_licenseLinkLabel);
licenseHLayout->addStretch();
m_mainLayout->addSpacing(5);
}
// Directory and documentation links
{
QHBoxLayout* linksHLayout = new QHBoxLayout();
linksHLayout->setMargin(0);
m_mainLayout->addLayout(linksHLayout);
QSpacerItem* spacerLeft = new QSpacerItem(0, 0, QSizePolicy::Expanding);
linksHLayout->addSpacerItem(spacerLeft);
linksHLayout->addStretch();
m_directoryLinkLabel = new LinkLabel("View in Directory");
m_directoryLinkLabel = new LinkLabel(tr("View in Directory"));
linksHLayout->addWidget(m_directoryLinkLabel);
linksHLayout->addWidget(new QLabel("|"));
m_documentationLinkLabel = new LinkLabel("Read Documentation");
m_documentationLinkLabel = new LinkLabel(tr("Read Documentation"));
linksHLayout->addWidget(m_documentationLinkLabel);
QSpacerItem* spacerRight = new QSpacerItem(0, 0, QSizePolicy::Expanding);
linksHLayout->addSpacerItem(spacerRight);
linksHLayout->addStretch();
m_mainLayout->addSpacing(8);
}
@@ -144,34 +190,35 @@ namespace O3DE::ProjectManager
// Separating line
QFrame* hLine = new QFrame();
hLine->setFrameShape(QFrame::HLine);
hLine->setStyleSheet("color: #666666;");
hLine->setObjectName("horizontalSeparatingLine");
m_mainLayout->addWidget(hLine);
m_mainLayout->addSpacing(10);
// Requirements
m_reqirementsTitleLabel = GemInspector::CreateStyledLabel(m_mainLayout, 16, s_headerColor);
m_requirementsTitleLabel = GemInspector::CreateStyledLabel(m_mainLayout, 16, s_headerColor);
QHBoxLayout* requrementsLayout = new QHBoxLayout();
requrementsLayout->setAlignment(Qt::AlignTop);
requrementsLayout->setMargin(0);
requrementsLayout->setSpacing(0);
QHBoxLayout* requirementsLayout = new QHBoxLayout();
requirementsLayout->setAlignment(Qt::AlignTop);
requirementsLayout->setMargin(0);
requirementsLayout->setSpacing(0);
m_reqirementsIconLabel = new QLabel();
m_reqirementsIconLabel->setPixmap(QIcon(":/Warning.svg").pixmap(24, 24));
requrementsLayout->addWidget(m_reqirementsIconLabel);
m_requirementsIconLabel = new QLabel();
m_requirementsIconLabel->setPixmap(QIcon(":/Warning.svg").pixmap(24, 24));
requirementsLayout->addWidget(m_requirementsIconLabel);
m_reqirementsTextLabel = GemInspector::CreateStyledLabel(requrementsLayout, 10, s_textColor);
m_reqirementsTextLabel->setWordWrap(true);
m_reqirementsTextLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
m_reqirementsTextLabel->setOpenExternalLinks(true);
m_requirementsTextLabel = GemInspector::CreateStyledLabel(requirementsLayout, 10, s_textColor);
m_requirementsTextLabel->setWordWrap(true);
m_requirementsTextLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
m_requirementsTextLabel->setOpenExternalLinks(true);
QSpacerItem* reqirementsSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding);
requrementsLayout->addSpacerItem(reqirementsSpacer);
QSpacerItem* requirementsSpacer = new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding);
requirementsLayout->addSpacerItem(requirementsSpacer);
m_mainLayout->addLayout(requrementsLayout);
m_mainLayout->addLayout(requirementsLayout);
m_mainLayout->addSpacing(20);
m_requirementsMainSpacer = new QSpacerItem(0, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
m_mainLayout->addSpacerItem(m_requirementsMainSpacer);
// Depending gems
m_dependingGems = new GemsSubWidget();
@@ -181,10 +228,10 @@ namespace O3DE::ProjectManager
// Additional information
QLabel* additionalInfoLabel = CreateStyledLabel(m_mainLayout, 14, s_headerColor);
additionalInfoLabel->setText("Additional Information");
additionalInfoLabel->setText(tr("Additional Information"));
m_versionLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor);
m_lastUpdatedLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor);
m_binarySizeLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor);
m_versionLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
m_lastUpdatedLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
m_binarySizeLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
}
} // namespace O3DE::ProjectManager
@@ -16,7 +16,7 @@
#include <QItemSelection>
#include <QScrollArea>
#include <QWidget>
#include <QSpacerItem>
#endif
QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
@@ -36,6 +36,9 @@ namespace O3DE::ProjectManager
void Update(const QModelIndex& modelIndex);
static QLabel* CreateStyledLabel(QLayout* layout, int fontSize, const QString& colorCodeString);
// Fonts
inline constexpr static int s_baseFontSize = 12;
// Colors
inline constexpr static const char* s_headerColor = "#FFFFFF";
inline constexpr static const char* s_textColor = "#DDDDDD";
@@ -57,13 +60,15 @@ namespace O3DE::ProjectManager
QLabel* m_nameLabel = nullptr;
QLabel* m_creatorLabel = nullptr;
QLabel* m_summaryLabel = nullptr;
LinkLabel* m_licenseLinkLabel = nullptr;
LinkLabel* m_directoryLinkLabel = nullptr;
LinkLabel* m_documentationLinkLabel = nullptr;
// Requirements
QLabel* m_reqirementsTitleLabel = nullptr;
QLabel* m_reqirementsIconLabel = nullptr;
QLabel* m_reqirementsTextLabel = nullptr;
QLabel* m_requirementsTitleLabel = nullptr;
QLabel* m_requirementsIconLabel = nullptr;
QLabel* m_requirementsTextLabel = nullptr;
QSpacerItem* m_requirementsMainSpacer = nullptr;
// Depending and conflicting gems
GemsSubWidget* m_dependingGems = nullptr;
@@ -58,6 +58,8 @@ namespace O3DE::ProjectManager
item->setData(gemInfo.m_path, RolePath);
item->setData(gemInfo.m_requirement, RoleRequirement);
item->setData(gemInfo.m_downloadStatus, RoleDownloadStatus);
item->setData(gemInfo.m_licenseText, RoleLicenseText);
item->setData(gemInfo.m_licenseLink, RoleLicenseLink);
appendRow(item);
@@ -248,6 +250,16 @@ namespace O3DE::ProjectManager
return modelIndex.data(RoleRequirement).toString();
}
QString GemModel::GetLicenseText(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleLicenseText).toString();
}
QString GemModel::GetLicenseLink(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleLicenseLink).toString();
}
GemModel* GemModel::GetSourceModel(QAbstractItemModel* model)
{
GemSortFilterProxyModel* proxyModel = qobject_cast<GemSortFilterProxyModel*>(model);
@@ -50,6 +50,8 @@ namespace O3DE::ProjectManager
static QStringList GetFeatures(const QModelIndex& modelIndex);
static QString GetPath(const QModelIndex& modelIndex);
static QString GetRequirement(const QModelIndex& modelIndex);
static QString GetLicenseText(const QModelIndex& modelIndex);
static QString GetLicenseLink(const QModelIndex& modelIndex);
static GemModel* GetSourceModel(QAbstractItemModel* model);
static const GemModel* GetSourceModel(const QAbstractItemModel* model);
@@ -107,7 +109,9 @@ namespace O3DE::ProjectManager
RoleTypes,
RolePath,
RoleRequirement,
RoleDownloadStatus
RoleDownloadStatus,
RoleLicenseText,
RoleLicenseLink
};
QHash<QString, QModelIndex> m_nameToIndexMap;
@@ -99,7 +99,7 @@ namespace O3DE::ProjectManager
m_nameLabel->setObjectName("gemRepoInspectorNameLabel");
m_mainLayout->addWidget(m_nameLabel);
m_repoLinkLabel = new LinkLabel(tr("Repo Url"), QUrl(""), 12, this);
m_repoLinkLabel = new LinkLabel(tr("Repo Url"), QUrl(), 12, this);
m_mainLayout->addWidget(m_repoLinkLabel);
m_mainLayout->addSpacing(5);
@@ -14,6 +14,7 @@
#include <QWidget>
#include <QProcessEnvironment>
#include <AzCore/IO/Path/Path_fwd.h>
#include <AzCore/Outcome/Outcome.h>
namespace O3DE::ProjectManager
@@ -67,7 +68,8 @@ namespace O3DE::ProjectManager
AZ::Outcome<QString, QString> GetProjectBuildPath(const QString& projectPath);
AZ::Outcome<void, QString> OpenCMakeGUI(const QString& projectPath);
AZ::Outcome<QString, QString> RunGetPythonScript(const QString& enginePath);
AZ::IO::FixedMaxPath GetEditorDirectory();
} // namespace ProjectUtils
} // namespace O3DE::ProjectManager
@@ -392,11 +392,11 @@ namespace O3DE::ProjectManager
{
if (!WarnIfInBuildQueue(projectPath))
{
AZ::IO::FixedMaxPath executableDirectory = AZ::Utils::GetExecutableDirectory();
AZ::IO::FixedMaxPath executableDirectory = ProjectUtils::GetEditorDirectory();
AZStd::string executableFilename = "Editor";
AZ::IO::FixedMaxPath editorExecutablePath = executableDirectory / (executableFilename + AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
auto cmdPath = AZ::IO::FixedMaxPathString::format(
"%s -regset=\"/Amazon/AzCore/Bootstrap/project_path=%s\"", editorExecutablePath.c_str(),
"%s --regset=\"/Amazon/AzCore/Bootstrap/project_path=%s\"", editorExecutablePath.c_str(),
projectPath.toStdString().c_str());
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
@@ -709,6 +709,8 @@ namespace O3DE::ProjectManager
gemInfo.m_requirement = Py_To_String_Optional(data, "requirements", "");
gemInfo.m_creator = Py_To_String_Optional(data, "origin", "");
gemInfo.m_documentationLink = Py_To_String_Optional(data, "documentation_url", "");
gemInfo.m_licenseText = Py_To_String_Optional(data, "license", "Unspecified License");
gemInfo.m_licenseLink = Py_To_String_Optional(data, "license_url", "");
if (gemInfo.m_creator.contains("Open 3D Engine"))
{
@@ -63,7 +63,7 @@ namespace AZ
}
ProcessingOverlayWidget::ProcessingOverlayWidget(UI::OverlayWidget* overlay, Layout layout, Uuid traceTag)
: QWidget()
: QWidget(nullptr, Qt::Tool | Qt::WindowStaysOnTopHint)
, m_traceTag(traceTag)
, ui(new Ui::ProcessingOverlayWidget())
, m_overlay(overlay)