Merge remote-tracking branch 'upstream/stabilization/2110' into nvsickle/FixEntityOrdering
This commit is contained in:
@@ -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
|
||||
|
||||
+97
-180
@@ -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;
|
||||
|
||||
|
||||
+19
-13
@@ -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
|
||||
)
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user