merging latest development

Signed-off-by: kberg-amzn <karlberg@amazon.com>
This commit is contained in:
kberg-amzn
2021-09-20 13:28:57 -07:00
257 changed files with 8431 additions and 7127 deletions
@@ -140,9 +140,6 @@
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="showGrid">
<bool>false</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
@@ -201,6 +198,11 @@
<header>AzToolsFramework/AssetBrowser/Search/SearchWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>AzQtComponents::TableView</class>
<extends>QTreeView</extends>
<header>AzQtComponents/Components/Widgets/TableView.h</header>
</customwidget>
<customwidget>
<class>AzToolsFramework::AssetBrowser::AssetBrowserTreeView</class>
<extends>QTreeView</extends>
@@ -214,7 +216,7 @@
</customwidget>
<customwidget>
<class>AzToolsFramework::AssetBrowser::AssetBrowserTableView</class>
<extends>QTableView</extends>
<extends>AzQtComponents::TableView</extends>
<header>AzToolsFramework/AssetBrowser/Views/AssetBrowserTableView.h</header>
</customwidget>
</customwidgets>
+12 -2
View File
@@ -8,11 +8,21 @@
#include "QtEditorApplication.h"
#ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
#include <AzFramework/API/ApplicationAPI_Linux.h>
#endif
namespace Editor
{
bool EditorQtApplication::nativeEventFilter(const QByteArray& , void* , long* )
bool EditorQtApplication::nativeEventFilter([[maybe_unused]] const QByteArray& eventType, void* message, long*)
{
// TODO_KDAB_LINUX
if (GetIEditor()->IsInGameMode())
{
#ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
AzFramework::LinuxXcbEventHandlerBus::Broadcast(&AzFramework::LinuxXcbEventHandler::HandleXcbEvent, static_cast<xcb_generic_event_t*>(message));
#endif
return true;
}
return false;
}
}
+14 -2
View File
@@ -34,10 +34,14 @@ namespace EditorInternal
: ToolsApplication(argc, argv)
{
EditorToolsApplicationRequests::Bus::Handler::BusConnect();
AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusConnect();
AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler::BusConnect();
}
EditorToolsApplication::~EditorToolsApplication()
{
AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler::BusDisconnect();
AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusDisconnect();
EditorToolsApplicationRequests::Bus::Handler::BusDisconnect();
Stop();
}
@@ -48,7 +52,6 @@ namespace EditorInternal
return m_StartupAborted;
}
void EditorToolsApplication::RegisterCoreComponents()
{
AzToolsFramework::ToolsApplication::RegisterCoreComponents();
@@ -274,5 +277,14 @@ namespace EditorInternal
Exit();
}
}
AzToolsFramework::ViewportInteraction::KeyboardModifiers EditorToolsApplication::QueryKeyboardModifiers()
{
return AzToolsFramework::ViewportInteraction::BuildKeyboardModifiers(QGuiApplication::queryKeyboardModifiers());
}
AZStd::chrono::milliseconds EditorToolsApplication::EditorViewportInputTimeNow()
{
const auto now = AZStd::chrono::high_resolution_clock::now();
return AZStd::chrono::time_point_cast<AZStd::chrono::milliseconds>(now).time_since_epoch();
}
} // namespace EditorInternal
+10
View File
@@ -7,7 +7,9 @@
*/
#pragma once
#include <AzToolsFramework/Application/ToolsApplication.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include "Core/EditorMetricsPlainTextNameRegistration.h"
#include "EditorToolsApplicationAPI.h"
@@ -19,6 +21,8 @@ namespace EditorInternal
class EditorToolsApplication
: public AzToolsFramework::ToolsApplication
, public EditorToolsApplicationRequests::Bus::Handler
, public AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler
, public AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler
{
public:
EditorToolsApplication(int* argc, char*** argv);
@@ -44,6 +48,12 @@ namespace EditorInternal
void CreateReflectionManager() override;
void Reflect(AZ::ReflectContext* context) override;
// EditorModifierKeyRequestBus overrides ...
AzToolsFramework::ViewportInteraction::KeyboardModifiers QueryKeyboardModifiers() override;
// EditorViewportInputTimeNowRequestBus overrides ...
AZStd::chrono::milliseconds EditorViewportInputTimeNow() override;
protected:
// From EditorToolsApplicationRequests
bool OpenLevel(AZStd::string_view levelName) override;
+11 -8
View File
@@ -744,11 +744,15 @@ void EditorViewportWidget::RenderAll()
{
namespace AztfVi = AzToolsFramework::ViewportInteraction;
AztfVi::KeyboardModifiers keyboardModifiers;
AztfVi::EditorModifierKeyRequestBus::BroadcastResult(
keyboardModifiers, &AztfVi::EditorModifierKeyRequestBus::Events::QueryKeyboardModifiers);
m_debugDisplay->DepthTestOff();
m_manipulatorManager->DrawManipulators(
*m_debugDisplay, GetCameraState(),
BuildMouseInteractionInternal(
AztfVi::MouseButtons(AztfVi::TranslateMouseButtons(QGuiApplication::mouseButtons())), QueryKeyboardModifiers(),
AztfVi::MouseButtons(AztfVi::TranslateMouseButtons(QGuiApplication::mouseButtons())), keyboardModifiers,
BuildMousePick(WidgetToViewport(mapFromGlobal(QCursor::pos())))));
m_debugDisplay->DepthTestOn();
}
@@ -959,12 +963,13 @@ QWidget* EditorViewportWidget::GetWidgetForViewportContextMenu()
bool EditorViewportWidget::ShowingWorldSpace()
{
return QueryKeyboardModifiers().Shift();
}
namespace AztfVi = AzToolsFramework::ViewportInteraction;
AzToolsFramework::ViewportInteraction::KeyboardModifiers EditorViewportWidget::QueryKeyboardModifiers()
{
return AzToolsFramework::ViewportInteraction::BuildKeyboardModifiers(QGuiApplication::queryKeyboardModifiers());
AztfVi::KeyboardModifiers keyboardModifiers;
AztfVi::EditorModifierKeyRequestBus::BroadcastResult(
keyboardModifiers, &AztfVi::EditorModifierKeyRequestBus::Events::QueryKeyboardModifiers);
return keyboardModifiers.Shift();
}
void EditorViewportWidget::SetViewportId(int id)
@@ -1039,7 +1044,6 @@ void EditorViewportWidget::ConnectViewportInteractionRequestBus()
{
AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler::BusConnect(GetViewportId());
AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler::BusConnect(GetViewportId());
AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusConnect();
m_viewportUi.ConnectViewportUiBus(GetViewportId());
AzFramework::InputSystemCursorConstraintRequestBus::Handler::BusConnect();
@@ -1050,7 +1054,6 @@ void EditorViewportWidget::DisconnectViewportInteractionRequestBus()
AzFramework::InputSystemCursorConstraintRequestBus::Handler::BusDisconnect();
m_viewportUi.DisconnectViewportUiBus();
AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusDisconnect();
AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler::BusDisconnect();
AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler::BusDisconnect();
}
-4
View File
@@ -92,7 +92,6 @@ class SANDBOX_API EditorViewportWidget final
, private AzFramework::InputSystemCursorConstraintRequestBus::Handler
, private AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler
, private AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler
, private AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler
, private AzFramework::AssetCatalogEventBus::Handler
, private AZ::RPI::SceneNotificationBus::Handler
{
@@ -212,9 +211,6 @@ private:
// EditorEntityViewportInteractionRequestBus overrides ...
void FindVisibleEntities(AZStd::vector<AZ::EntityId>& visibleEntities) override;
// EditorModifierKeyRequestBus overrides ...
AzToolsFramework::ViewportInteraction::KeyboardModifiers QueryKeyboardModifiers() override;
// Camera::EditorCameraRequestBus overrides ...
void SetViewFromEntityPerspective(const AZ::EntityId& entityId) override;
void SetViewAndMovementLockFromEntityPerspective(const AZ::EntityId& entityId, bool lockCameraMovement) override;
+4 -2
View File
@@ -14,8 +14,10 @@
#define CRYINCLUDE_EDITOR_INCLUDE_IEDITORCLASSFACTORY_H
#pragma once
#include <CryCommon/platform.h>
#include <vector>
#include <QtCore/QString>
#include <AzCore/Math/Guid.h>
#define DEFINE_UUID(l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
static const GUID uuid() { return { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }; }
@@ -34,7 +36,7 @@ struct IUnknown
#endif
#define __uuidof(T) T::uuid()
#if defined(AZ_PLATFORM_LINUX)
#if defined(AZ_PLATFORM_LINUX) || defined(AZ_PLATFORM_MAC)
# ifndef _REFGUID_DEFINED
# define _REFGUID_DEFINED
@@ -65,7 +67,7 @@ enum
};
#endif
#endif // defined(AZ_PLATFORM_LINUX)
#endif // defined(AZ_PLATFORM_LINUX) || defined(AZ_PLATFORM_MAC)
#include "SandboxAPI.h"
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
+1 -24
View File
@@ -12,28 +12,5 @@ set_target_properties(Editor PROPERTIES
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_LIST_DIR}/gui_info.plist
RESOURCE ${CMAKE_CURRENT_LIST_DIR}/Images.xcassets
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME EditorAppIcon
ENTITLEMENT_FILE_PATH ${CMAKE_CURRENT_LIST_DIR}/EditorEntitlements.plist
)
# We cannot use ly_add_target here because we're already including this file from inside ly_add_target
# So we need to setup target, dependencies and install logic manually.
add_executable(EditorDummy Platform/Mac/main_dummy.cpp)
add_executable(AZ::EditorDummy ALIAS EditorDummy)
ly_target_link_libraries(EditorDummy
PRIVATE
AZ::AzCore
AZ::AzFramework)
ly_add_dependencies(Editor EditorDummy)
# Store the aliased target into a DIRECTORY property
set_property(DIRECTORY APPEND PROPERTY LY_DIRECTORY_TARGETS AZ::EditorDummy)
# Store the directory path in a GLOBAL property so that it can be accessed
# in the layout install logic. Skip if the directory has already been added
get_property(ly_all_target_directories GLOBAL PROPERTY LY_ALL_TARGET_DIRECTORIES)
if(NOT CMAKE_CURRENT_SOURCE_DIR IN_LIST ly_all_target_directories)
set_property(GLOBAL APPEND PROPERTY LY_ALL_TARGET_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR})
endif()
ly_install_add_install_path_setreg(Editor)
+1 -1
View File
@@ -3,7 +3,7 @@
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>EditorDummy</string>
<string>Editor</string>
<key>CFBundleIdentifier</key>
<string>org.O3DE.Editor</string>
<key>CFBundlePackageType</key>
-75
View File
@@ -1,75 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/Process/ProcessWatcher.h>
#include <cstdlib>
int main(int argc, char* argv[])
{
// Create a ComponentApplication to initialize the AZ::SystemAllocator and initialize the SettingsRegistry
AZ::ComponentApplication::Descriptor desc;
AZ::ComponentApplication application;
application.Create(desc);
AZStd::vector<AZStd::string> envVars;
const char* homePath = std::getenv("HOME");
envVars.push_back(AZStd::string::format("HOME=%s", homePath));
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
const char* dyldLibPathOrig = std::getenv("DYLD_LIBRARY_PATH");
AZStd::string dyldSearchPath = AZStd::string::format("DYLD_LIBRARY_PATH=%s", dyldLibPathOrig);
if (AZ::IO::FixedMaxPath projectModulePath;
settingsRegistry->Get(projectModulePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectConfigurationBinPath))
{
dyldSearchPath.append(":");
dyldSearchPath.append(projectModulePath.c_str());
}
if (AZ::IO::FixedMaxPath installedBinariesFolder;
settingsRegistry->Get(installedBinariesFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_InstalledBinaryFolder))
{
if (AZ::IO::FixedMaxPath engineRootFolder;
settingsRegistry->Get(engineRootFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder))
{
installedBinariesFolder = engineRootFolder / installedBinariesFolder;
dyldSearchPath.append(":");
dyldSearchPath.append(installedBinariesFolder.c_str());
}
}
envVars.push_back(dyldSearchPath);
}
AZStd::string commandArgs;
for (int i = 1; i < argc; i++)
{
commandArgs.append(argv[i]);
commandArgs.append(" ");
}
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
AZ::IO::Path processPath{ AZ::IO::PathView(AZ::Utils::GetExecutableDirectory()) };
processPath /= "Editor";
processLaunchInfo.m_processExecutableString = AZStd::move(processPath.Native());
processLaunchInfo.m_commandlineParameters = commandArgs;
processLaunchInfo.m_environmentVariables = &envVars;
processLaunchInfo.m_showWindow = true;
AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
application.Destroy();
return 0;
}
@@ -51,4 +51,5 @@ ly_add_target(
AZ::AzCore
AZ::AzToolsFramework
AZ::AzQtComponents
Legacy::EditorCore
)
@@ -59,6 +59,20 @@ namespace AZStd
namespace AZ::Debug
{
// interface for externally defined profiler systems
class Profiler
{
public:
AZ_RTTI(Profiler, "{3E5D6329-72D1-41BA-9158-68A349D1A4D5}");
Profiler() = default;
virtual ~Profiler() = default;
// support for the extra macro args (e.g. format strings) will come in a later PR
virtual void BeginRegion(const Budget* budget, const char* eventName) = 0;
virtual void EndRegion(const Budget* budget) = 0;
};
class ProfileScope
{
public:
@@ -6,6 +6,8 @@
*
*/
#include <AzCore/Interface/Interface.h>
namespace AZ::Debug
{
template<typename... T>
@@ -22,9 +24,11 @@ namespace AZ::Debug
PIXBeginEvent(PIX_COLOR_INDEX(budget->Crc() & 0xff), eventName, args...);
#endif
budget->BeginProfileRegion();
// TODO: injecting instrumentation for other profilers
// NOTE: external profiler registration won't occur inline in a header necessarily in this manner, but the exact mechanism
// will be introduced in a future PR
if (auto profiler = AZ::Interface<Profiler>::Get(); profiler)
{
profiler->BeginRegion(budget, eventName);
}
#endif
}
@@ -39,6 +43,10 @@ namespace AZ::Debug
#if defined(USE_PIX)
PIXEndEvent();
#endif
if (auto profiler = AZ::Interface<Profiler>::Get(); profiler)
{
profiler->EndRegion(budget);
}
#endif
}
@@ -72,6 +72,7 @@ namespace AZ
{
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
bool fileFound = false;
if (AZ::IO::FixedMaxPath projectModulePath;
settingsRegistry->Get(projectModulePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectConfigurationBinPath))
{
@@ -79,6 +80,23 @@ namespace AZ
if (AZ::IO::SystemFile::Exists(projectModulePath.c_str()))
{
m_fileName.assign(projectModulePath.c_str(), projectModulePath.Native().size());
fileFound = true;
}
}
if (!fileFound)
{
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))
{
installedBinariesPath = engineRootFolder / installedBinariesPath / fullFilePath;
if (AZ::IO::SystemFile::Exists(installedBinariesPath.c_str()))
{
m_fileName.assign(installedBinariesPath.c_str(), installedBinariesPath.Native().size());
}
}
}
}
}
@@ -9,6 +9,7 @@
#include <AzCore/Utils/Utils.h>
#include <cstdlib>
#include <pwd.h>
namespace AZ
{
@@ -39,6 +40,14 @@ namespace AZ
AZ::IO::FixedMaxPath path{homePath};
return path.Native();
}
struct passwd* pass = getpwuid(getuid());
if (pass)
{
AZ::IO::FixedMaxPath path{pass->pw_dir};
return path.Native();
}
return {};
}
@@ -181,7 +181,7 @@ namespace AZ::IO::ArchiveInternal
return 0;
}
nTotal = (AZStd::min)(nTotal, GetFileSize() - m_nCurSeek);
nTotal = AZStd::min<size_t>(nTotal, GetFileSize() - m_nCurSeek);
int64_t nReadBytes = GetFile()->ReadData(pDest, m_nCurSeek, nTotal);
if (nReadBytes == -1)
@@ -9,8 +9,19 @@
#include <AzFramework/Viewport/ClickDetector.h>
#include <AzFramework/Viewport/ScreenGeometry.h>
#include <AzCore/std/chrono/clocks.h>
namespace AzFramework
{
ClickDetector::ClickDetector()
{
m_timeNowFn = []
{
const auto now = AZStd::chrono::high_resolution_clock::now();
return AZStd::chrono::time_point_cast<AZStd::chrono::milliseconds>(now).time_since_epoch();
};
}
ClickDetector::ClickOutcome ClickDetector::DetectClick(const ClickEvent clickEvent, const ScreenVector& cursorDelta)
{
const auto previousDetectionState = m_detectionState;
@@ -26,11 +37,13 @@ namespace AzFramework
if (clickEvent == ClickEvent::Down)
{
const auto now = std::chrono::steady_clock::now();
const auto now = m_timeNowFn();
if (m_tryBeginTime)
{
const std::chrono::duration<float> diff = now - m_tryBeginTime.value();
if (diff.count() < m_doubleClickInterval)
using FloatingPointSeconds = AZStd::chrono::duration<float, AZStd::chrono::seconds::period>;
const auto diff = now - m_tryBeginTime.value();
if (FloatingPointSeconds(diff).count() < m_doubleClickInterval)
{
return ClickOutcome::Nil;
}
@@ -43,7 +56,8 @@ namespace AzFramework
}
else if (clickEvent == ClickEvent::Up)
{
const auto clickOutcome = [detectionState = m_detectionState] {
const auto clickOutcome = [detectionState = m_detectionState]
{
if (detectionState == DetectionState::WaitingForMove)
{
return ClickOutcome::Click;
@@ -66,4 +80,9 @@ namespace AzFramework
return ClickOutcome::Nil;
}
void ClickDetector::OverrideTimeNowFn(AZStd::function<AZStd::chrono::milliseconds()> timeNowFn)
{
m_timeNowFn = AZStd::move(timeNowFn);
}
} // namespace AzFramework
@@ -8,6 +8,7 @@
#pragma once
#include <AzCore/std/functional.h>
#include <AzCore/std/optional.h>
#include <chrono>
@@ -21,10 +22,9 @@ namespace AzFramework
//! (mouse down with movement and then mouse up).
class ClickDetector
{
//! Alias for recording time of mouse down events
using Time = std::chrono::time_point<std::chrono::steady_clock>;
public:
ClickDetector();
//! Internal representation of click event (map from external event for this when
//! calling DetectClick).
enum class ClickEvent
@@ -51,6 +51,10 @@ namespace AzFramework
void SetDoubleClickInterval(float doubleClickInterval);
//! Override the dead zone before a 'move' outcome will be triggered.
void SetDeadZone(float deadZone);
//! Override how the current time is retrieved.
//! This is helpful to override when it comes to simulating different passages of
//! time to avoid double click issues in tests for example.
void OverrideTimeNowFn(AZStd::function<AZStd::chrono::milliseconds()> timeNowFn);
private:
//! Internal state of ClickDetector based on incoming events.
@@ -65,7 +69,9 @@ namespace AzFramework
float m_deadZone = 2.0f; //!< How far to move before a click is cancelled (when Move will fire).
float m_doubleClickInterval = 0.4f; //!< Default double click interval, can be overridden.
DetectionState m_detectionState; //!< Internal state of ClickDetector.
AZStd::optional<Time> m_tryBeginTime; //!< Mouse down time (happens each mouse down, helps with double click handling).
//! Mouse down time (happens each mouse down, helps with double click handling).
AZStd::optional<AZStd::chrono::milliseconds> m_tryBeginTime;
AZStd::function<AZStd::chrono::milliseconds()> m_timeNowFn; //!< Interface to query the current time.
};
inline void ClickDetector::SetDoubleClickInterval(const float doubleClickInterval)
@@ -0,0 +1,293 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/std/typetraits/integral_constant.h>
#include <AzFramework/API/ApplicationAPI_Linux.h>
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
#define explicit ExplicitIsACXXKeyword
#include <xcb/xkb.h>
#undef explicit
#include <xkbcommon/xkbcommon-keysyms.h>
#include <xkbcommon/xkbcommon.h>
#include <xkbcommon/xkbcommon-x11.h>
namespace AzFramework
{
class InputDeviceKeyboardXcb
: public InputDeviceKeyboard::Implementation
, public LinuxXcbEventHandlerBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(InputDeviceKeyboardXcb, AZ::SystemAllocator, 0);
using InputDeviceKeyboard::Implementation::Implementation;
InputDeviceKeyboardXcb(InputDeviceKeyboard& inputDevice)
: InputDeviceKeyboard::Implementation(inputDevice)
{
LinuxXcbEventHandlerBus::Handler::BusConnect();
auto* interface = AzFramework::LinuxXcbConnectionManagerInterface::Get();
if (!interface)
{
AZ_Warning("ApplicationLinux", false, "XCB interface not available");
return;
}
auto* connection = AzFramework::LinuxXcbConnectionManagerInterface::Get()->GetXcbConnection();
if (!connection)
{
AZ_Warning("ApplicationLinux", false, "XCB connection not available");
return;
}
AZStd::unique_ptr<xcb_xkb_use_extension_reply_t, DeleterForFreeFn<::std::free>> xkbUseExtensionReply{
xcb_xkb_use_extension_reply(connection, xcb_xkb_use_extension(connection, 1, 0), nullptr)
};
if (!xkbUseExtensionReply)
{
AZ_Warning("ApplicationLinux", false, "Failed to initialize the xkb extension");
return;
}
if (!xkbUseExtensionReply->supported)
{
AZ_Warning("ApplicationLinux", false, "The X server does not support the xkb extension");
return;
}
m_coreDeviceId = xkb_x11_get_core_keyboard_device_id(connection);
m_xkbContext.reset(xkb_context_new(XKB_CONTEXT_NO_FLAGS));
m_xkbKeymap.reset(xkb_x11_keymap_new_from_device(m_xkbContext.get(), connection, m_coreDeviceId, XKB_KEYMAP_COMPILE_NO_FLAGS));
m_xkbState.reset(xkb_x11_state_new_from_device(m_xkbKeymap.get(), connection, m_coreDeviceId));
m_initialized = true;
}
bool IsConnected() const override
{
return m_initialized;
}
bool HasTextEntryStarted() const override
{
return false;
}
void TextEntryStart(const InputDeviceKeyboard::VirtualKeyboardOptions& options) override
{
}
void TextEntryStop() override
{
}
void TickInputDevice() override
{
ProcessRawEventQueues();
}
void HandleXcbEvent(xcb_generic_event_t* event) override
{
if (!IsConnected())
{
return;
}
switch (event->response_type & ~0x80)
{
case XCB_KEY_PRESS:
{
auto* keyPress = reinterpret_cast<xcb_key_press_event_t*>(event);
const InputChannelId* key = InputChannelFromKeyEvent(keyPress->detail);
if (key)
{
QueueRawKeyEvent(*key, true);
}
break;
}
case XCB_KEY_RELEASE:
{
auto* keyRelease = reinterpret_cast<xcb_key_release_event_t*>(event);
const InputChannelId* key = InputChannelFromKeyEvent(keyRelease->detail);
if (key)
{
QueueRawKeyEvent(*key, false);
}
break;
}
}
}
private:
[[nodiscard]] const InputChannelId* InputChannelFromKeyEvent(xcb_keycode_t code) const
{
const xcb_keysym_t keysym = xkb_state_key_get_one_sym(m_xkbState.get(), code);
switch(keysym)
{
case XKB_KEY_0: return &InputDeviceKeyboard::Key::Alphanumeric0;
case XKB_KEY_1: return &InputDeviceKeyboard::Key::Alphanumeric1;
case XKB_KEY_2: return &InputDeviceKeyboard::Key::Alphanumeric2;
case XKB_KEY_3: return &InputDeviceKeyboard::Key::Alphanumeric3;
case XKB_KEY_4: return &InputDeviceKeyboard::Key::Alphanumeric4;
case XKB_KEY_5: return &InputDeviceKeyboard::Key::Alphanumeric5;
case XKB_KEY_6: return &InputDeviceKeyboard::Key::Alphanumeric6;
case XKB_KEY_7: return &InputDeviceKeyboard::Key::Alphanumeric7;
case XKB_KEY_8: return &InputDeviceKeyboard::Key::Alphanumeric8;
case XKB_KEY_9: return &InputDeviceKeyboard::Key::Alphanumeric9;
case XKB_KEY_A:
case XKB_KEY_a: return &InputDeviceKeyboard::Key::AlphanumericA;
case XKB_KEY_B:
case XKB_KEY_b: return &InputDeviceKeyboard::Key::AlphanumericB;
case XKB_KEY_C:
case XKB_KEY_c: return &InputDeviceKeyboard::Key::AlphanumericC;
case XKB_KEY_D:
case XKB_KEY_d: return &InputDeviceKeyboard::Key::AlphanumericD;
case XKB_KEY_E:
case XKB_KEY_e: return &InputDeviceKeyboard::Key::AlphanumericE;
case XKB_KEY_F:
case XKB_KEY_f: return &InputDeviceKeyboard::Key::AlphanumericF;
case XKB_KEY_G:
case XKB_KEY_g: return &InputDeviceKeyboard::Key::AlphanumericG;
case XKB_KEY_H:
case XKB_KEY_h: return &InputDeviceKeyboard::Key::AlphanumericH;
case XKB_KEY_I:
case XKB_KEY_i: return &InputDeviceKeyboard::Key::AlphanumericI;
case XKB_KEY_J:
case XKB_KEY_j: return &InputDeviceKeyboard::Key::AlphanumericJ;
case XKB_KEY_K:
case XKB_KEY_k: return &InputDeviceKeyboard::Key::AlphanumericK;
case XKB_KEY_L:
case XKB_KEY_l: return &InputDeviceKeyboard::Key::AlphanumericL;
case XKB_KEY_M:
case XKB_KEY_m: return &InputDeviceKeyboard::Key::AlphanumericM;
case XKB_KEY_N:
case XKB_KEY_n: return &InputDeviceKeyboard::Key::AlphanumericN;
case XKB_KEY_O:
case XKB_KEY_o: return &InputDeviceKeyboard::Key::AlphanumericO;
case XKB_KEY_P:
case XKB_KEY_p: return &InputDeviceKeyboard::Key::AlphanumericP;
case XKB_KEY_Q:
case XKB_KEY_q: return &InputDeviceKeyboard::Key::AlphanumericQ;
case XKB_KEY_R:
case XKB_KEY_r: return &InputDeviceKeyboard::Key::AlphanumericR;
case XKB_KEY_S:
case XKB_KEY_s: return &InputDeviceKeyboard::Key::AlphanumericS;
case XKB_KEY_T:
case XKB_KEY_t: return &InputDeviceKeyboard::Key::AlphanumericT;
case XKB_KEY_U:
case XKB_KEY_u: return &InputDeviceKeyboard::Key::AlphanumericU;
case XKB_KEY_V:
case XKB_KEY_v: return &InputDeviceKeyboard::Key::AlphanumericV;
case XKB_KEY_W:
case XKB_KEY_w: return &InputDeviceKeyboard::Key::AlphanumericW;
case XKB_KEY_X:
case XKB_KEY_x: return &InputDeviceKeyboard::Key::AlphanumericX;
case XKB_KEY_Y:
case XKB_KEY_y: return &InputDeviceKeyboard::Key::AlphanumericY;
case XKB_KEY_Z:
case XKB_KEY_z: return &InputDeviceKeyboard::Key::AlphanumericZ;
case XKB_KEY_BackSpace: return &InputDeviceKeyboard::Key::EditBackspace;
case XKB_KEY_Caps_Lock: return &InputDeviceKeyboard::Key::EditCapsLock;
case XKB_KEY_Return: return &InputDeviceKeyboard::Key::EditEnter;
case XKB_KEY_space: return &InputDeviceKeyboard::Key::EditSpace;
case XKB_KEY_Tab: return &InputDeviceKeyboard::Key::EditTab;
case XKB_KEY_Escape: return &InputDeviceKeyboard::Key::Escape;
case XKB_KEY_F1: return &InputDeviceKeyboard::Key::Function01;
case XKB_KEY_F2: return &InputDeviceKeyboard::Key::Function02;
case XKB_KEY_F3: return &InputDeviceKeyboard::Key::Function03;
case XKB_KEY_F4: return &InputDeviceKeyboard::Key::Function04;
case XKB_KEY_F5: return &InputDeviceKeyboard::Key::Function05;
case XKB_KEY_F6: return &InputDeviceKeyboard::Key::Function06;
case XKB_KEY_F7: return &InputDeviceKeyboard::Key::Function07;
case XKB_KEY_F8: return &InputDeviceKeyboard::Key::Function08;
case XKB_KEY_F9: return &InputDeviceKeyboard::Key::Function09;
case XKB_KEY_F10: return &InputDeviceKeyboard::Key::Function10;
case XKB_KEY_F11: return &InputDeviceKeyboard::Key::Function11;
case XKB_KEY_F12: return &InputDeviceKeyboard::Key::Function12;
case XKB_KEY_F13: return &InputDeviceKeyboard::Key::Function13;
case XKB_KEY_F14: return &InputDeviceKeyboard::Key::Function14;
case XKB_KEY_F15: return &InputDeviceKeyboard::Key::Function15;
case XKB_KEY_F16: return &InputDeviceKeyboard::Key::Function16;
case XKB_KEY_F17: return &InputDeviceKeyboard::Key::Function17;
case XKB_KEY_F18: return &InputDeviceKeyboard::Key::Function18;
case XKB_KEY_F19: return &InputDeviceKeyboard::Key::Function19;
case XKB_KEY_F20: return &InputDeviceKeyboard::Key::Function20;
case XKB_KEY_Alt_L: return &InputDeviceKeyboard::Key::ModifierAltL;
case XKB_KEY_Alt_R: return &InputDeviceKeyboard::Key::ModifierAltR;
case XKB_KEY_Control_L: return &InputDeviceKeyboard::Key::ModifierCtrlL;
case XKB_KEY_Control_R: return &InputDeviceKeyboard::Key::ModifierCtrlR;
case XKB_KEY_Shift_L: return &InputDeviceKeyboard::Key::ModifierShiftL;
case XKB_KEY_Shift_R: return &InputDeviceKeyboard::Key::ModifierShiftR;
case XKB_KEY_Super_L: return &InputDeviceKeyboard::Key::ModifierSuperL;
case XKB_KEY_Super_R: return &InputDeviceKeyboard::Key::ModifierSuperR;
case XKB_KEY_Down: return &InputDeviceKeyboard::Key::NavigationArrowDown;
case XKB_KEY_Left: return &InputDeviceKeyboard::Key::NavigationArrowLeft;
case XKB_KEY_Right: return &InputDeviceKeyboard::Key::NavigationArrowRight;
case XKB_KEY_Up: return &InputDeviceKeyboard::Key::NavigationArrowUp;
case XKB_KEY_Delete: return &InputDeviceKeyboard::Key::NavigationDelete;
case XKB_KEY_End: return &InputDeviceKeyboard::Key::NavigationEnd;
case XKB_KEY_Home: return &InputDeviceKeyboard::Key::NavigationHome;
case XKB_KEY_Insert: return &InputDeviceKeyboard::Key::NavigationInsert;
case XKB_KEY_Page_Down: return &InputDeviceKeyboard::Key::NavigationPageDown;
case XKB_KEY_Page_Up: return &InputDeviceKeyboard::Key::NavigationPageUp;
case XKB_KEY_Num_Lock: return &InputDeviceKeyboard::Key::NumLock;
case XKB_KEY_KP_0: return &InputDeviceKeyboard::Key::NumPad0;
case XKB_KEY_KP_1: return &InputDeviceKeyboard::Key::NumPad1;
case XKB_KEY_KP_2: return &InputDeviceKeyboard::Key::NumPad2;
case XKB_KEY_KP_3: return &InputDeviceKeyboard::Key::NumPad3;
case XKB_KEY_KP_4: return &InputDeviceKeyboard::Key::NumPad4;
case XKB_KEY_KP_5: return &InputDeviceKeyboard::Key::NumPad5;
case XKB_KEY_KP_6: return &InputDeviceKeyboard::Key::NumPad6;
case XKB_KEY_KP_7: return &InputDeviceKeyboard::Key::NumPad7;
case XKB_KEY_KP_8: return &InputDeviceKeyboard::Key::NumPad8;
case XKB_KEY_KP_9: return &InputDeviceKeyboard::Key::NumPad9;
case XKB_KEY_KP_Add: return &InputDeviceKeyboard::Key::NumPadAdd;
case XKB_KEY_KP_Decimal: return &InputDeviceKeyboard::Key::NumPadDecimal;
case XKB_KEY_KP_Divide: return &InputDeviceKeyboard::Key::NumPadDivide;
case XKB_KEY_KP_Enter: return &InputDeviceKeyboard::Key::NumPadEnter;
case XKB_KEY_KP_Multiply: return &InputDeviceKeyboard::Key::NumPadMultiply;
case XKB_KEY_KP_Subtract: return &InputDeviceKeyboard::Key::NumPadSubtract;
case XKB_KEY_apostrophe: return &InputDeviceKeyboard::Key::PunctuationApostrophe;
case XKB_KEY_backslash: return &InputDeviceKeyboard::Key::PunctuationBackslash;
case XKB_KEY_bracketleft: return &InputDeviceKeyboard::Key::PunctuationBracketL;
case XKB_KEY_bracketright: return &InputDeviceKeyboard::Key::PunctuationBracketR;
case XKB_KEY_comma: return &InputDeviceKeyboard::Key::PunctuationComma;
case XKB_KEY_equal: return &InputDeviceKeyboard::Key::PunctuationEquals;
case XKB_KEY_hyphen: return &InputDeviceKeyboard::Key::PunctuationHyphen;
case XKB_KEY_period: return &InputDeviceKeyboard::Key::PunctuationPeriod;
case XKB_KEY_semicolon: return &InputDeviceKeyboard::Key::PunctuationSemicolon;
case XKB_KEY_slash: return &InputDeviceKeyboard::Key::PunctuationSlash;
case XKB_KEY_grave:
case XKB_KEY_asciitilde: return &InputDeviceKeyboard::Key::PunctuationTilde;
case XKB_KEY_ISO_Group_Shift: return &InputDeviceKeyboard::Key::SupplementaryISO;
case XKB_KEY_Pause: return &InputDeviceKeyboard::Key::WindowsSystemPause;
case XKB_KEY_Print: return &InputDeviceKeyboard::Key::WindowsSystemPrint;
case XKB_KEY_Scroll_Lock: return &InputDeviceKeyboard::Key::WindowsSystemScrollLock;
default: return nullptr;
}
}
template<auto freeFn>
using DeleterForFreeFn = AZStd::integral_constant<decltype(freeFn), freeFn>;
AZStd::unique_ptr<xkb_context, DeleterForFreeFn<xkb_context_unref>> m_xkbContext;
AZStd::unique_ptr<xkb_keymap, DeleterForFreeFn<xkb_keymap_unref>> m_xkbKeymap;
AZStd::unique_ptr<xkb_state, DeleterForFreeFn<xkb_state_unref>> m_xkbState;
int m_coreDeviceId{-1};
bool m_initialized{false};
};
InputDeviceKeyboard::Implementation* InputDeviceKeyboard::Implementation::Create(InputDeviceKeyboard& inputDevice)
{
return aznew InputDeviceKeyboardXcb(inputDevice);
}
} // namespace AzFramework
@@ -62,8 +62,16 @@ namespace AzFramework
uint32_t eventMask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
const uint32_t interestedEvents =
XCB_EVENT_MASK_STRUCTURE_NOTIFY
| XCB_EVENT_MASK_BUTTON_PRESS
| XCB_EVENT_MASK_BUTTON_RELEASE
| XCB_EVENT_MASK_KEY_PRESS
| XCB_EVENT_MASK_KEY_RELEASE
| XCB_EVENT_MASK_POINTER_MOTION
;
uint32_t valueList[] = { xcbRootScreen->black_pixel,
XCB_EVENT_MASK_STRUCTURE_NOTIFY };
interestedEvents };
xcb_void_cookie_t xcbCheckResult;
@@ -11,10 +11,16 @@
if (${PAL_TRAIT_LINUX_WINDOW_MANAGER} STREQUAL "xcb")
find_library(XCB_LIBRARY xcb)
find_library(XCB_XKB_LIBRARY xcb-xkb)
find_library(XKBCOMMON_LIBRARY xkbcommon)
find_library(XKBCOMMON_X11_LIBRARY xkbcommon-x11)
set(LY_BUILD_DEPENDENCIES
PRIVATE
${XCB_LIBRARY}
${XKBCOMMON_LIBRARY}
${XKBCOMMON_X11_LIBRARY}
${XCB_XKB_LIBRARY}
)
set(LY_COMPILE_DEFINITIONS PUBLIC PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB)
@@ -25,7 +25,7 @@ set(FILES
AzFramework/Windowing/NativeWindow_Linux_xcb.h
AzFramework/Windowing/NativeWindow_Linux_xcb.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad_Unimplemented.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_Unimplemented.cpp
AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_xcb.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Motion/InputDeviceMotion_Unimplemented.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Mouse/InputDeviceMouse_Unimplemented.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Touch/InputDeviceTouch_Unimplemented.cpp
@@ -18,6 +18,7 @@ namespace AzManipulatorTestFramework
class ImmediateModeActionDispatcher
: public ActionDispatcher<ImmediateModeActionDispatcher>
, public AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler
, public AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler
{
using KeyboardModifier = AzToolsFramework::ViewportInteraction::KeyboardModifier;
using KeyboardModifiers = AzToolsFramework::ViewportInteraction::KeyboardModifiers;
@@ -50,6 +51,9 @@ namespace AzManipulatorTestFramework
// EditorModifierKeyRequestBus overrides ...
KeyboardModifiers QueryKeyboardModifiers() override;
// EditorViewportInputTimeNowRequestBus overrides ...
AZStd::chrono::milliseconds EditorViewportInputTimeNow() override;
protected:
// ActionDispatcher ...
void SetSnapToGridImpl(bool enabled) override;
@@ -79,6 +83,9 @@ namespace AzManipulatorTestFramework
mutable AZStd::unique_ptr<MouseInteractionEvent> m_event;
ManipulatorViewportInteraction& m_viewportManipulatorInteraction;
//! Current time that ticks up after each call to EditorViewportInputTimeNow.
AZStd::chrono::milliseconds m_timeNow = AZStd::chrono::milliseconds(0);
};
template<typename ActualT, typename ExpectedT>
@@ -106,4 +113,13 @@ namespace AzManipulatorTestFramework
{
return GetMouseInteractionEvent()->m_mouseInteraction.m_keyboardModifiers;
}
inline AZStd::chrono::milliseconds ImmediateModeActionDispatcher::EditorViewportInputTimeNow()
{
// step the time for each call to be greater than the minimum time required for a double click to register
// note: the time increment is very high to ensure any potential system changes to settings such as double
// click interval will not be impacted
m_timeNow += AZStd::chrono::milliseconds(10000);
return m_timeNow;
}
} // namespace AzManipulatorTestFramework
@@ -33,10 +33,12 @@ namespace AzManipulatorTestFramework
: m_viewportManipulatorInteraction(viewportManipulatorInteraction)
{
AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusConnect();
AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler::BusConnect();
}
ImmediateModeActionDispatcher::~ImmediateModeActionDispatcher()
{
AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler::BusDisconnect();
AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusDisconnect();
}
@@ -0,0 +1,43 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Interface/Interface.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzToolsFramework/API/ViewportEditorModeTrackerNotificationBus.h>
namespace AzToolsFramework
{
//! The AZ::Interface of the central editor mode tracker for all viewports.
class ViewportEditorModeTrackerInterface
{
public:
AZ_RTTI(ViewportEditorModeTrackerInterface, "{7D72A4F7-2147-4ED9-A315-E456A3BE3CF6}");
virtual ~ViewportEditorModeTrackerInterface() = default;
//! Activates the specified editor mode for the specified viewport.
virtual AZ::Outcome<void, AZStd::string> ActivateMode(
const ViewportEditorModeInfo& viewportEditorModeInfo, ViewportEditorMode mode) = 0;
//! Deactivates the specified editor mode for the specified viewport.
virtual AZ::Outcome<void, AZStd::string> DeactivateMode(
const ViewportEditorModeInfo& viewportEditorModeInfo, ViewportEditorMode mode) = 0;
//! Attempts to retrieve the editor mode state for the specified viewport, otherwise returns nullptr.
virtual const ViewportEditorModesInterface* GetViewportEditorModes(const ViewportEditorModeInfo& viewportEditorModeInfo) const = 0;
//! Returns the number of viewports currently being tracked.
virtual size_t GetTrackedViewportCount() const = 0;
//! Returns true if the specified viewport is being tracked, otherwise false.
virtual bool IsViewportModeTracked(const ViewportEditorModeInfo& viewportEditorModeInfo) const = 0;
};
} // namespace AzToolsFramework
@@ -0,0 +1,66 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/EBus/Event.h>
#include <AzFramework/Viewport/ViewportId.h>
#include <AzToolsFramework/ViewportUi/ViewportUiRequestBus.h>
namespace AzToolsFramework
{
//! Enumeration of each viewport editor mode.
enum class ViewportEditorMode : AZ::u8
{
Default,
Component,
Focus,
Pick
};
//! Viewport identifier and other relevant viewport data.
struct ViewportEditorModeInfo
{
using IdType = AzFramework::ViewportId;
IdType m_id = ViewportUi::DefaultViewportId; //!< The unique identifier for a given viewport.
};
//! Interface for the editor modes of a given viewport.
class ViewportEditorModesInterface
{
public:
virtual ~ViewportEditorModesInterface() = default;
//! Returns true if the specified editor mode is active, otherwise false.
virtual bool IsModeActive(ViewportEditorMode mode) const = 0;
};
//! Provides a bus to notify when the different editor modes are entered/exit.
class ViewportEditorModeNotifications
: public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using BusIdType = ViewportEditorModeInfo::IdType;
//////////////////////////////////////////////////////////////////////////
//! Notifies subscribers of the a given viewport to the activation of the specified editor mode.
virtual void OnEditorModeActivated([[maybe_unused]] const ViewportEditorModesInterface& editorModeState, [[maybe_unused]] ViewportEditorMode mode)
{
}
//! Notifies subscribers of the a given viewport to the deactivation of the specified editor mode.
virtual void OnEditorModeDeactivated([[maybe_unused]] const ViewportEditorModesInterface& editorModeState, [[maybe_unused]] ViewportEditorMode mode)
{
}
};
using ViewportEditorModeNotificationsBus = AZ::EBus<ViewportEditorModeNotifications>;
} // namespace AzToolsFramework
@@ -28,16 +28,16 @@ namespace AzToolsFramework
namespace AssetBrowser
{
AssetBrowserTableView::AssetBrowserTableView(QWidget* parent)
: QTableView(parent)
: AzQtComponents::TableView(parent)
, m_delegate(new EntryDelegate(this))
{
setSortingEnabled(true);
setItemDelegate(m_delegate);
verticalHeader()->hide();
setRootIsDecorated(false);
//Styling the header aligning text to the left and using a bold font.
horizontalHeader()->setDefaultAlignment(Qt::AlignLeft);
horizontalHeader()->setStyleSheet("QHeaderView { font-weight: bold; }");
header()->setDefaultAlignment(Qt::AlignLeft);
header()->setStyleSheet("QHeaderView { font-weight: bold; }");
setContextMenuPolicy(Qt::CustomContextMenu);
@@ -45,7 +45,7 @@ namespace AzToolsFramework
setSortingEnabled(false);
setSelectionMode(QAbstractItemView::SingleSelection);
connect(this, &QTableView::customContextMenuRequested, this, &AssetBrowserTableView::OnContextMenu);
connect(this, &AzQtComponents::TableView::customContextMenuRequested, this, &AssetBrowserTableView::OnContextMenu);
AssetBrowserViewRequestBus::Handler::BusConnect();
AssetBrowserComponentNotificationBus::Handler::BusConnect();
@@ -62,11 +62,11 @@ namespace AzToolsFramework
m_tableModel = qobject_cast<AssetBrowserTableModel*>(model);
AZ_Assert(m_tableModel, "Expecting AssetBrowserTableModel");
m_sourceFilterModel = qobject_cast<AssetBrowserFilterModel*>(m_tableModel->sourceModel());
QTableView::setModel(model);
AzQtComponents::TableView::setModel(model);
connect(m_tableModel, &AssetBrowserTableModel::layoutChanged, this, &AssetBrowserTableView::layoutChangedSlot);
horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeMode::Stretch);
horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeMode::Stretch);
header()->setSectionResizeMode(0, QHeaderView::ResizeMode::Stretch);
header()->setSectionResizeMode(1, QHeaderView::ResizeMode::Stretch);
}
void AssetBrowserTableView::SetName(const QString& name)
@@ -98,7 +98,7 @@ namespace AzToolsFramework
void AssetBrowserTableView::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
{
QTableView::selectionChanged(selected, deselected);
AzQtComponents::TableView::selectionChanged(selected, deselected);
Q_EMIT selectionChangedSignal(selected, deselected);
}
@@ -115,7 +115,7 @@ namespace AzToolsFramework
selectionModel()->clear();
}
}
QTableView::rowsAboutToBeRemoved(parent, start, end);
AzQtComponents::TableView::rowsAboutToBeRemoved(parent, start, end);
}
void AssetBrowserTableView::layoutChangedSlot(
@@ -13,9 +13,10 @@
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserTableModel.h>
#include <AzQtComponents/Components/Widgets/TableView.h>
#include <QModelIndex>
#include <QPointer>
#include <QTableView>
#endif
namespace AzToolsFramework
@@ -28,7 +29,7 @@ namespace AzToolsFramework
class EntryDelegate;
class AssetBrowserTableView //! Table view that displays the asset browser entries in a list.
: public QTableView
: public AzQtComponents::TableView
, public AssetBrowserViewRequestBus::Handler
, public AssetBrowserComponentNotificationBus::Handler
{
@@ -282,6 +282,24 @@ namespace AzToolsFramework
return keyboardModifiers;
}
//! An interface to deal with time requests relating to viewports.
//! @note The bus is global and not per viewport.
class EditorViewportInputTimeNowRequests : public AZ::EBusTraits
{
public:
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
//! Returns the current time in seconds.
//! This interface can be overridden for the purposes of testing to simplify viewport input requests.
virtual AZStd::chrono::milliseconds EditorViewportInputTimeNow() = 0;
protected:
~EditorViewportInputTimeNowRequests() = default;
};
using EditorViewportInputTimeNowRequestBus = AZ::EBus<EditorViewportInputTimeNowRequests>;
//! Viewport requests for managing the viewport cursor state.
class ViewportMouseCursorRequests
{
@@ -253,10 +253,10 @@ namespace AzToolsFramework
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl());
}
static bool ManipulatorDitto(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
static bool ManipulatorDitto(
const AzFramework::ClickDetector::ClickOutcome clickOutcome, const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
{
return mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down &&
mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() &&
return clickOutcome == AzFramework::ClickDetector::ClickOutcome::Click &&
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl() &&
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Alt();
}
@@ -1054,6 +1054,17 @@ namespace AzToolsFramework
RegisterActions();
SetupBoxSelect();
RefreshSelectedEntityIdsAndRegenerateManipulators();
// ensure the click detector uses the EditorViewportInputTimeNowRequests interface to retrieve elapsed time
// note: this is to facilitate overriding this functionality for purposes such as testing
m_clickDetector.OverrideTimeNowFn(
[]
{
AZStd::chrono::milliseconds timeNow;
AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::BroadcastResult(
timeNow, &AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Events::EditorViewportInputTimeNow);
return timeNow;
});
}
EditorTransformComponentSelection::~EditorTransformComponentSelection()
@@ -1883,7 +1894,7 @@ namespace AzToolsFramework
}
// set manipulator pivot override translation or orientation (update manipulators)
if (Input::ManipulatorDitto(mouseInteraction))
if (Input::ManipulatorDitto(clickOutcome, mouseInteraction))
{
PerformManipulatorDitto(entityIdUnderCursor);
return false;
@@ -0,0 +1,149 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzToolsFramework/API/ViewportEditorModeTrackerNotificationBus.h>
#include <AzToolsFramework/ViewportSelection/ViewportEditorModeTracker.h>
namespace AzToolsFramework
{
AZ::Outcome<void, AZStd::string> ViewportEditorModes::ActivateMode(ViewportEditorMode mode)
{
if (const AZ::u32 modeIndex = static_cast<AZ::u32>(mode);
modeIndex < NumEditorModes)
{
m_editorModes[modeIndex] = true;
return AZ::Success();
}
else
{
return AZ::Failure(
AZStd::string::format("Cannot activate mode %u, mode is not recognized", modeIndex));
}
}
AZ::Outcome<void, AZStd::string> ViewportEditorModes::DeactivateMode(ViewportEditorMode mode)
{
if (const AZ::u32 modeIndex = static_cast<AZ::u32>(mode); modeIndex < NumEditorModes)
{
m_editorModes[modeIndex] = false;
return AZ::Success();
}
else
{
return AZ::Failure(
AZStd::string::format("Cannot deactivate mode %u, mode is not recognized", modeIndex));
}
}
bool ViewportEditorModes::IsModeActive(ViewportEditorMode mode) const
{
return m_editorModes[static_cast<AZ::u32>(mode)];
}
void ViewportEditorModeTracker::RegisterInterface()
{
if (AZ::Interface<ViewportEditorModeTrackerInterface>::Get() == nullptr)
{
AZ::Interface<ViewportEditorModeTrackerInterface>::Register(this);
}
}
void ViewportEditorModeTracker::UnregisterInterface()
{
if (AZ::Interface<ViewportEditorModeTrackerInterface>::Get() != nullptr)
{
AZ::Interface<ViewportEditorModeTrackerInterface>::Unregister(this);
}
}
AZ::Outcome<void, AZStd::string> ViewportEditorModeTracker::ActivateMode(
const ViewportEditorModeInfo& viewportEditorModeInfo, ViewportEditorMode mode)
{
auto& editorModes = m_viewportEditorModesMap[viewportEditorModeInfo.m_id];
if (editorModes.IsModeActive(mode))
{
return AZ::Failure(AZStd::string::format(
"Duplicate call to ActivateMode for mode '%u' on id '%i'", static_cast<AZ::u32>(mode), viewportEditorModeInfo.m_id));
}
if (const auto result = editorModes.ActivateMode(mode);
!result.IsSuccess())
{
return result;
}
ViewportEditorModeNotificationsBus::Event(
viewportEditorModeInfo.m_id, &ViewportEditorModeNotificationsBus::Events::OnEditorModeActivated, editorModes, mode);
return AZ::Success();
}
AZ::Outcome<void, AZStd::string> ViewportEditorModeTracker::DeactivateMode(
const ViewportEditorModeInfo& viewportEditorModeInfo, ViewportEditorMode mode)
{
ViewportEditorModes* editorModes = nullptr;
bool modeWasActive = true;
if (m_viewportEditorModesMap.count(viewportEditorModeInfo.m_id))
{
editorModes = &m_viewportEditorModesMap.at(viewportEditorModeInfo.m_id);
if (!editorModes->IsModeActive(mode))
{
return AZ::Failure(AZStd::string::format(
"Duplicate call to DeactivateMode for mode '%u' on id '%i'", static_cast<AZ::u32>(mode), viewportEditorModeInfo.m_id));
}
}
else
{
modeWasActive = false;
editorModes = &m_viewportEditorModesMap[viewportEditorModeInfo.m_id];
}
if(const auto result = editorModes->DeactivateMode(mode);
!result.IsSuccess())
{
return result;
}
ViewportEditorModeNotificationsBus::Event(
viewportEditorModeInfo.m_id, &ViewportEditorModeNotificationsBus::Events::OnEditorModeDeactivated, *editorModes, mode);
if (modeWasActive)
{
return AZ::Success();
}
else
{
return AZ::Failure(AZStd::string::format(
"Call to DeactivateMode for mode '%u' on id '%i' without precursor call to ActivateMode", static_cast<AZ::u32>(mode),
viewportEditorModeInfo.m_id));
}
}
const ViewportEditorModesInterface* ViewportEditorModeTracker::GetViewportEditorModes(const ViewportEditorModeInfo& viewportEditorModeInfo) const
{
if (auto editorModes = m_viewportEditorModesMap.find(viewportEditorModeInfo.m_id);
editorModes != m_viewportEditorModesMap.end())
{
return &editorModes->second;
}
else
{
return nullptr;
}
}
size_t ViewportEditorModeTracker::GetTrackedViewportCount() const
{
return m_viewportEditorModesMap.size();
}
bool ViewportEditorModeTracker::IsViewportModeTracked(const ViewportEditorModeInfo& viewportEditorModeInfo) const
{
return m_viewportEditorModesMap.count(viewportEditorModeInfo.m_id) > 0;
}
} // namespace AzToolsFramework
@@ -0,0 +1,61 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/containers/array.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzToolsFramework/API/ViewportEditorModeTrackerNotificationBus.h>
#include <AzToolsFramework/API/ViewportEditorModeTrackerInterface.h>
namespace AzToolsFramework
{
//! The encapsulation of the editor modes for a given viewport.
class ViewportEditorModes
: public ViewportEditorModesInterface
{
public:
//! The number of currently supported viewport editor modes.
static constexpr AZ::u8 NumEditorModes = 4;
//! Sets the specified mode as active.
AZ::Outcome<void, AZStd::string> ActivateMode(ViewportEditorMode mode);
// Sets the specified mode as inactive.
AZ::Outcome<void, AZStd::string> DeactivateMode(ViewportEditorMode mode);
// ViewportEditorModesInterface ...
bool IsModeActive(ViewportEditorMode mode) const override;
private:
AZStd::array<bool, NumEditorModes> m_editorModes{}; //!< State flags to track active/inactive status of viewport editor modes.
};
//! The implementation of the central editor mode state tracker for all viewports.
class ViewportEditorModeTracker
: public ViewportEditorModeTrackerInterface
{
public:
//! Registers this object with the AZ::Interface.
void RegisterInterface();
//! Unregisters this object with the AZ::Interface.
void UnregisterInterface();
// ViewportEditorModeTrackerInterface overrides ...
AZ::Outcome<void, AZStd::string> ActivateMode(const ViewportEditorModeInfo& viewportEditorModeInfo, ViewportEditorMode mode) override;
AZ::Outcome<void, AZStd::string> DeactivateMode(const ViewportEditorModeInfo& viewportEditorModeInfo, ViewportEditorMode mode) override;
const ViewportEditorModesInterface* GetViewportEditorModes(const ViewportEditorModeInfo& viewportEditorModeInfo) const override;
size_t GetTrackedViewportCount() const override;
bool IsViewportModeTracked(const ViewportEditorModeInfo& viewportEditorModeInfo) const override;
private:
using ViewportEditorModesMap = AZStd::unordered_map<typename ViewportEditorModeInfo::IdType, ViewportEditorModes>;
ViewportEditorModesMap m_viewportEditorModesMap; //!< Editor mode state per viewport.
};
} // namespace AzToolsFramework
@@ -34,6 +34,7 @@ set(FILES
API/EditorAnimationSystemRequestBus.h
API/EditorEntityAPI.h
API/EditorLevelNotificationBus.h
API/ViewportEditorModeTrackerNotificationBus.h
API/EditorVegetationRequestsBus.h
API/EditorPythonConsoleBus.h
API/EditorPythonRunnerRequestsBus.h
@@ -44,6 +45,7 @@ set(FILES
API/EntityCompositionNotificationBus.h
API/EditorViewportIconDisplayInterface.h
API/ViewPaneOptions.h
API/ViewportEditorModeTrackerInterface.h
Application/Ticker.h
Application/Ticker.cpp
Application/EditorEntityManager.cpp
@@ -538,6 +540,8 @@ set(FILES
ViewportSelection/EditorTransformComponentSelectionRequestBus.cpp
ViewportSelection/EditorVisibleEntityDataCache.h
ViewportSelection/EditorVisibleEntityDataCache.cpp
ViewportSelection/ViewportEditorModeTracker.cpp
ViewportSelection/ViewportEditorModeTracker.h
ToolsFileUtils/ToolsFileUtils.h
AssetBrowser/AssetBrowserBus.h
AssetBrowser/AssetBrowserSourceDropBus.h
@@ -783,7 +783,8 @@ namespace UnitTest
const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState);
// click the entity in the viewport
m_actionDispatcher->SetStickySelect(true)->CameraState(m_cameraState)
m_actionDispatcher->SetStickySelect(true)
->CameraState(m_cameraState)
->MousePosition(entity2ScreenPosition)
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Control)
->MouseLButtonDown()
@@ -1018,6 +1019,105 @@ namespace UnitTest
EXPECT_THAT(selectedEntitiesAfter, UnorderedElementsAre(m_entityId1));
}
class EditorTransformComponentSelectionViewportPickingManipulatorTestFixtureParam
: public EditorTransformComponentSelectionViewportPickingManipulatorTestFixture
, public ::testing::WithParamInterface<bool>
{
};
TEST_P(
EditorTransformComponentSelectionViewportPickingManipulatorTestFixtureParam,
StickyAndUnstickyDittoManipulatorToOtherEntityChangesManipulatorAndDoesNotChangeSelection)
{
PositionEntities();
PositionCamera(m_cameraState);
AzToolsFramework::SelectEntity(m_entityId1);
// calculate the position in screen space of the second entity
const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState);
// single click select entity2
m_actionDispatcher->SetStickySelect(GetParam())
->CameraState(m_cameraState)
->MousePosition(entity2ScreenPosition)
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Control)
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Alt)
->MouseLButtonDown()
->MouseLButtonUp();
// entity1 is still selected
using ::testing::UnorderedElementsAre;
auto selectedEntitiesAfter = SelectedEntities();
EXPECT_THAT(selectedEntitiesAfter, UnorderedElementsAre(m_entityId1));
AZStd::optional<AZ::Transform> manipulatorTransform;
AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult(
manipulatorTransform, AzToolsFramework::GetEntityContextId(),
&AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform);
EXPECT_THAT(manipulatorTransform->GetTranslation(), IsClose(m_entity2WorldTranslation));
}
TEST_P(
EditorTransformComponentSelectionViewportPickingManipulatorTestFixtureParam,
StickyAndUnstickyDittoManipulatorToOtherEntityChangesManipulatorAndClickOffResetsManipulator)
{
PositionEntities();
PositionCamera(m_cameraState);
AzToolsFramework::SelectEntity(m_entityId1);
// calculate the position in screen space of the second entity
const auto entity2ScreenPosition = AzFramework::WorldToScreen(m_entity2WorldTranslation, m_cameraState);
// position in space above the entities
const auto clickOffPositionWorld = AZ::Vector3(5.0f, 15.0f, 12.0f);
// calculate the screen space position of the click
const auto clickOffPositionScreen = AzFramework::WorldToScreen(clickOffPositionWorld, m_cameraState);
using ::testing::UnorderedElementsAre;
// single click select entity2, then click off
m_actionDispatcher->SetStickySelect(GetParam())
->CameraState(m_cameraState)
->MousePosition(entity2ScreenPosition)
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Control)
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Alt)
->MouseLButtonDown()
->MouseLButtonUp()
->ExecuteBlock(
[this]()
{
auto selectedEntitiesAfter = SelectedEntities();
EXPECT_THAT(selectedEntitiesAfter, UnorderedElementsAre(m_entityId1));
AZStd::optional<AZ::Transform> manipulatorTransform;
AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult(
manipulatorTransform, AzToolsFramework::GetEntityContextId(),
&AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform);
EXPECT_THAT(manipulatorTransform->GetTranslation(), IsClose(m_entity2WorldTranslation));
})
->MousePosition(clickOffPositionScreen)
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Control)
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Alt)
->MouseLButtonDown()
->MouseLButtonUp();
auto selectedEntitiesAfter = SelectedEntities();
EXPECT_THAT(selectedEntitiesAfter, UnorderedElementsAre(m_entityId1));
AZStd::optional<AZ::Transform> manipulatorTransform;
AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult(
manipulatorTransform, AzToolsFramework::GetEntityContextId(),
&AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform);
// manipulator transform is reset
EXPECT_THAT(manipulatorTransform->GetTranslation(), IsClose(m_entity1WorldTranslation));
}
INSTANTIATE_TEST_CASE_P(All, EditorTransformComponentSelectionViewportPickingManipulatorTestFixtureParam, testing::Values(true, false));
using EditorTransformComponentSelectionManipulatorTestFixture =
IndirectCallManipulatorViewportInteractionFixtureMixin<EditorTransformComponentSelectionFixture>;
@@ -0,0 +1,498 @@
/*
* 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/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <AzToolsFramework/ViewportSelection/ViewportEditorModeTracker.h>
namespace UnitTest
{
using ViewportEditorMode = AzToolsFramework::ViewportEditorMode;
using ViewportEditorModes = AzToolsFramework::ViewportEditorModes;
using ViewportEditorModeTracker = AzToolsFramework::ViewportEditorModeTracker;
using ViewportEditorModeInfo = AzToolsFramework::ViewportEditorModeInfo;
using ViewportId = ViewportEditorModeInfo::IdType;
using ViewportEditorModesInterface = AzToolsFramework::ViewportEditorModesInterface;
void ActivateModeAndExpectSuccess(ViewportEditorModes& editorModeState, ViewportEditorMode mode)
{
const auto result = editorModeState.ActivateMode(mode);
EXPECT_TRUE(result.IsSuccess());
}
void DeactivateModeAndExpectSuccess(ViewportEditorModes& editorModeState, ViewportEditorMode mode)
{
const auto result = editorModeState.DeactivateMode(mode);
EXPECT_TRUE(result.IsSuccess());
}
void SetAllModesActive(ViewportEditorModes& editorModeState)
{
for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++)
{
ActivateModeAndExpectSuccess(editorModeState, static_cast<ViewportEditorMode>(mode));
}
}
void SetAllModesInactive(ViewportEditorModes& editorModeState)
{
for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++)
{
DeactivateModeAndExpectSuccess(editorModeState, static_cast<ViewportEditorMode>(mode));
}
}
// Fixture for testing editor mode states
class ViewportEditorModesTestsFixture
: public ::testing::Test
{
public:
ViewportEditorModes m_editorModes;
};
// Fixture for testing editor mode states with parameterized test arguments
class ViewportEditorModesTestsFixtureWithParams
: public ViewportEditorModesTestsFixture
, public ::testing::WithParamInterface<AzToolsFramework::ViewportEditorMode>
{
public:
void SetUp() override
{
m_selectedEditorMode = GetParam();
}
ViewportEditorMode m_selectedEditorMode;
};
// Fixture for testing the viewport editor mode state tracker
class ViewportEditorModeTrackerTestFixture
: public ToolsApplicationFixture
{
public:
ViewportEditorModeTracker m_viewportEditorModeTracker;
};
// Subscriber of viewport editor mode notifications for a single viewport that expects a single mode to be activated/deactivated
class ViewportEditorModeNotificationsBusHandler
: private AzToolsFramework::ViewportEditorModeNotificationsBus::Handler
{
public:
struct ReceivedEvents
{
bool m_onEnter = false;
bool m_onExit = false;
};
using EditModeTracker = AZStd::unordered_map<ViewportEditorMode, ReceivedEvents>;
ViewportEditorModeNotificationsBusHandler(ViewportId viewportId)
: m_viewportSubscription(viewportId)
{
AzToolsFramework::ViewportEditorModeNotificationsBus::Handler::BusConnect(m_viewportSubscription);
}
~ViewportEditorModeNotificationsBusHandler()
{
AzToolsFramework::ViewportEditorModeNotificationsBus::Handler::BusDisconnect();
}
ViewportId GetViewportSubscription() const
{
return m_viewportSubscription;
}
const EditModeTracker& GetEditorModes() const
{
return m_editorModes;
}
void OnEditorModeActivated([[maybe_unused]]const ViewportEditorModesInterface& editorModeState, ViewportEditorMode mode) override
{
m_editorModes[mode].m_onEnter = true;
}
virtual void OnEditorModeDeactivated([[maybe_unused]] const ViewportEditorModesInterface& editorModeState, ViewportEditorMode mode) override
{
m_editorModes[mode].m_onExit = true;
}
private:
ViewportId m_viewportSubscription;
EditModeTracker m_editorModes;
};
// Fixture for testing viewport editor mode notifications publishing
class ViewportEditorModePublisherTestFixture
: public ViewportEditorModeTrackerTestFixture
{
public:
void SetUpEditorFixtureImpl() override
{
for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++)
{
m_editorModeHandlers[mode] = AZStd::make_unique<ViewportEditorModeNotificationsBusHandler>(mode);
}
}
void TearDownEditorFixtureImpl() override
{
for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++)
{
m_editorModeHandlers[mode].reset();
}
}
AZStd::array<AZStd::unique_ptr<ViewportEditorModeNotificationsBusHandler>, ViewportEditorModes::NumEditorModes> m_editorModeHandlers;
};
TEST_F(ViewportEditorModesTestsFixture, NumberOfEditorModesIsEqualTo4)
{
EXPECT_EQ(ViewportEditorModes::NumEditorModes, 4);
}
TEST_F(ViewportEditorModesTestsFixture, InitialEditorModeStateHasAllInactiveModes)
{
for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++)
{
EXPECT_FALSE(m_editorModes.IsModeActive(static_cast<ViewportEditorMode>(mode)));
}
}
TEST_P(ViewportEditorModesTestsFixtureWithParams, SettingModeActiveActivatesOnlyThatMode)
{
ActivateModeAndExpectSuccess(m_editorModes, m_selectedEditorMode);
for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++)
{
const auto editorMode = static_cast<ViewportEditorMode>(mode);
if (editorMode == m_selectedEditorMode)
{
EXPECT_TRUE(m_editorModes.IsModeActive(static_cast<ViewportEditorMode>(editorMode)));
}
else
{
EXPECT_FALSE(m_editorModes.IsModeActive(static_cast<ViewportEditorMode>(editorMode)));
}
}
}
TEST_P(ViewportEditorModesTestsFixtureWithParams, SettingModeInactiveInactivatesOnlyThatMode)
{
SetAllModesActive(m_editorModes);
DeactivateModeAndExpectSuccess(m_editorModes, m_selectedEditorMode);
for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++)
{
const auto editorMode = static_cast<ViewportEditorMode>(mode);
if (editorMode == m_selectedEditorMode)
{
EXPECT_FALSE(m_editorModes.IsModeActive(editorMode));
}
else
{
EXPECT_TRUE(m_editorModes.IsModeActive(editorMode));
}
}
}
TEST_P(ViewportEditorModesTestsFixtureWithParams, SettingMultipleModesActiveActivatesAllThoseModesNonMutuallyExclusively)
{
for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes - 1; mode++)
{
// Given only the selected mode active
SetAllModesInactive(m_editorModes);
{
ActivateModeAndExpectSuccess(m_editorModes, m_selectedEditorMode);
}
const auto editorMode = static_cast<ViewportEditorMode>(mode);
if (editorMode == m_selectedEditorMode)
{
continue;
}
// When other modes are activated
ActivateModeAndExpectSuccess(m_editorModes, editorMode);
for (auto expectedMode = 0; expectedMode < ViewportEditorModes::NumEditorModes; expectedMode++)
{
const auto expectedEditorMode = static_cast<ViewportEditorMode>(expectedMode);
if (expectedEditorMode == editorMode || expectedEditorMode == m_selectedEditorMode)
{
// Expect the activated modes to be active
EXPECT_TRUE(m_editorModes.IsModeActive(expectedEditorMode));
}
else
{
// Expect the modes not active to be inactive
EXPECT_FALSE(m_editorModes.IsModeActive(expectedEditorMode));
}
}
}
}
TEST_P(ViewportEditorModesTestsFixtureWithParams, SettingMultipleModesInactiveInactivatesAllThoseModesNonMutuallyExclusively)
{
for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes - 1; mode++)
{
// Given only the selected mode inactive
SetAllModesActive(m_editorModes);
DeactivateModeAndExpectSuccess(m_editorModes, m_selectedEditorMode);
const auto editorMode = static_cast<ViewportEditorMode>(mode);
if (editorMode == m_selectedEditorMode)
{
continue;
}
// When other modes are deactivated
DeactivateModeAndExpectSuccess(m_editorModes, editorMode);
for (auto expectedMode = 0; expectedMode < ViewportEditorModes::NumEditorModes; expectedMode++)
{
const auto expectedEditorMode = static_cast<ViewportEditorMode>(expectedMode);
if (expectedEditorMode == editorMode || expectedEditorMode == m_selectedEditorMode)
{
// Expect the deactivated modes to be inactive
EXPECT_FALSE(m_editorModes.IsModeActive(expectedEditorMode));
}
else
{
// Expects the modes not deactivated to still be active
EXPECT_TRUE(m_editorModes.IsModeActive(expectedEditorMode));
}
}
}
}
INSTANTIATE_TEST_CASE_P(
AllEditorModes,
ViewportEditorModesTestsFixtureWithParams,
::testing::Values(
AzToolsFramework::ViewportEditorMode::Default,
AzToolsFramework::ViewportEditorMode::Component,
AzToolsFramework::ViewportEditorMode::Focus,
AzToolsFramework::ViewportEditorMode::Pick));
TEST_F(ViewportEditorModesTestsFixture, SettingOutOfBoundsModeActiveReturnsError)
{
const auto result = m_editorModes.ActivateMode(static_cast<ViewportEditorMode>(ViewportEditorModes::NumEditorModes));
EXPECT_FALSE(result.IsSuccess());
}
TEST_F(ViewportEditorModesTestsFixture, SettingOutOfBoundsModeInactiveReturnsError)
{
const auto result = m_editorModes.DeactivateMode(static_cast<ViewportEditorMode>(ViewportEditorModes::NumEditorModes));
EXPECT_FALSE(result.IsSuccess());
}
TEST_F(ViewportEditorModeTrackerTestFixture, InitialCentralStateTrackerHasNoViewportEditorModess)
{
EXPECT_EQ(m_viewportEditorModeTracker.GetTrackedViewportCount(), 0);
}
TEST_F(ViewportEditorModeTrackerTestFixture, RegisteringViewportEditorModeForNonExistentIdCreatesViewportEditorModesForThatId)
{
// Given a viewport not currently being tracked
const ViewportId viewportid = 0;
EXPECT_FALSE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid }));
EXPECT_EQ(m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid }), nullptr);
// When a mode is activated for that viewport
const auto editorMode = ViewportEditorMode::Default;
m_viewportEditorModeTracker.ActivateMode({ viewportid }, editorMode);
const auto* viewportEditorModeState = m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid });
// Expect that viewport to now be tracked
EXPECT_TRUE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid }));
EXPECT_NE(viewportEditorModeState, nullptr);
// Expect the mode for that viewport to be active
EXPECT_TRUE(viewportEditorModeState->IsModeActive(editorMode));
}
TEST_F(ViewportEditorModeTrackerTestFixture, UnregisteringViewportEditorModeForNonExistentIdCreatesViewportEditorModesForThatIdButReturnsError)
{
// Given a viewport not currently being tracked
const ViewportId viewportid = 0;
EXPECT_FALSE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid }));
EXPECT_EQ(m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid }), nullptr);
// When a mode is deactivated for that viewport
const auto editorMode = ViewportEditorMode::Default;
const auto expectedErrorMsg = AZStd::string::format(
"Call to DeactivateMode for mode '%u' on id '%i' without precursor call to ActivateMode", static_cast<AZ::u32>(editorMode), viewportid);
const auto result = m_viewportEditorModeTracker.DeactivateMode({ viewportid }, editorMode);
// Expect an error due to no precursor activation of that mode
EXPECT_FALSE(result.IsSuccess());
EXPECT_EQ(result.GetError(), expectedErrorMsg);
// Expect that viewport to now be tracked
const auto* viewportEditorModeState = m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid });
EXPECT_TRUE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid }));
// Expect the mode for that viewport to be inactive
EXPECT_NE(viewportEditorModeState, nullptr);
EXPECT_FALSE(viewportEditorModeState->IsModeActive(editorMode));
}
TEST_F(ViewportEditorModeTrackerTestFixture, GettingNonExistentViewportEditorModesForIdReturnsNull)
{
const ViewportId viewportid = 0;
EXPECT_FALSE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid }));
EXPECT_EQ(m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid }), nullptr);
}
TEST_F(ViewportEditorModeTrackerTestFixture, RegisteringViewportEditorModesForExistingIdInThatStateReturnsError)
{
// Given a viewport not currently tracked
const ViewportId viewportid = 0;
EXPECT_FALSE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid }));
EXPECT_EQ(m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid }), nullptr);
const auto editorMode = ViewportEditorMode::Default;
{
// When the mode is activated for the viewport
const auto result = m_viewportEditorModeTracker.ActivateMode({ viewportid }, editorMode);
// Expect no error as there is no duplicate activation
EXPECT_TRUE(result.IsSuccess());
// Expect the mode to be active for the viewport
const auto* viewportEditorModeState = m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid });
EXPECT_TRUE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid }));
EXPECT_NE(viewportEditorModeState, nullptr);
EXPECT_TRUE(viewportEditorModeState->IsModeActive(editorMode));
}
{
// When the mode is activated again for the viewport
const auto result = m_viewportEditorModeTracker.ActivateMode({ viewportid }, editorMode);
// Expect an error for the duplicate activation
const auto expectedErrorMsg = AZStd::string::format(
"Duplicate call to ActivateMode for mode '%u' on id '%i'", static_cast<AZ::u32>(editorMode), viewportid);
EXPECT_FALSE(result.IsSuccess());
EXPECT_EQ(result.GetError(), expectedErrorMsg);
// Expect the mode to still be active for the viewport
const auto* viewportEditorModeState = m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid });
EXPECT_TRUE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid }));
EXPECT_NE(viewportEditorModeState, nullptr);
EXPECT_TRUE(viewportEditorModeState->IsModeActive(editorMode));
}
}
TEST_F(ViewportEditorModeTrackerTestFixture, UnregisteringViewportEditorModesForExistingIdNotInThatStateReturnssError)
{
// Given a viewport not currently tracked
const ViewportId viewportid = 0;
EXPECT_FALSE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid }));
EXPECT_EQ(m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid }), nullptr);
const auto editorMode = ViewportEditorMode::Default;
{
// When the mode is activated and then deactivated for the viewport
m_viewportEditorModeTracker.ActivateMode({ viewportid }, editorMode);
const auto result = m_viewportEditorModeTracker.DeactivateMode({ viewportid }, editorMode);
// Expect no error as there is no duplicate deactivation
EXPECT_TRUE(result.IsSuccess());
// Expect the mode to be inctive for the viewport
const auto* viewportEditorModeState = m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid });
EXPECT_TRUE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid }));
EXPECT_NE(viewportEditorModeState, nullptr);
EXPECT_FALSE(viewportEditorModeState->IsModeActive(editorMode));
}
{
// When the mode is deactivated again for the viewport
const auto result = m_viewportEditorModeTracker.DeactivateMode({ viewportid }, editorMode);
// Expect an error for the duplicate deactivation
const auto expectedErrorMsg = AZStd::string::format(
"Duplicate call to DeactivateMode for mode '%u' on id '%i'", static_cast<AZ::u32>(editorMode), viewportid);
EXPECT_FALSE(result.IsSuccess());
EXPECT_EQ(result.GetError(), expectedErrorMsg);
// Expect the mode to still be inactive for the viewport
const auto* viewportEditorModeState = m_viewportEditorModeTracker.GetViewportEditorModes({ viewportid });
EXPECT_TRUE(m_viewportEditorModeTracker.IsViewportModeTracked({ viewportid }));
EXPECT_NE(viewportEditorModeState, nullptr);
EXPECT_FALSE(viewportEditorModeState->IsModeActive(editorMode));
}
}
TEST_F(
ViewportEditorModePublisherTestFixture,
RegisteringViewportEditorModesForExistingIdPublishesOnViewportEditorModeRegisterEventForAllSubscribers)
{
// Given a set of subscribers tracking the editor modes for their exclusive viewport
for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++)
{
// Expect each subscriber to have received no editor mode state changes
EXPECT_EQ(m_editorModeHandlers[mode]->GetEditorModes().size(), 0);
}
// When each editor mode is activated by the state tracker for a specific viewport
for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++)
{
const ViewportId viewportId = mode;
const ViewportEditorMode editorMode = static_cast<ViewportEditorMode>(mode);
m_viewportEditorModeTracker.ActivateMode({ viewportId }, editorMode);
}
for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++)
{
// Expect only the subscribers of each viewport to have received the editor mode activated event
const ViewportEditorMode editorMode = static_cast<ViewportEditorMode>(mode);
const auto& editorModes = m_editorModeHandlers[mode]->GetEditorModes();
EXPECT_EQ(editorModes.size(), 1);
EXPECT_EQ(editorModes.count(editorMode), 1);
const auto& expectedEditorModeSet = editorModes.find(editorMode);
EXPECT_NE(expectedEditorModeSet, editorModes.end());
EXPECT_TRUE(expectedEditorModeSet->second.m_onEnter);
EXPECT_FALSE(expectedEditorModeSet->second.m_onExit);
}
}
TEST_F(
ViewportEditorModePublisherTestFixture,
UnregisteringViewportEditorModesForExistingIdPublishesOnViewportEditorModeUnregisterEventForAllSubscribers)
{
// Given a set of subscribers tracking the editor modes for their exclusive viewport
for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++)
{
EXPECT_EQ(m_editorModeHandlers[mode]->GetEditorModes().size(), 0);
}
// When each editor mode is activated deactivated by the state tracker for a specific viewport
for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++)
{
const ViewportId viewportId = mode;
const ViewportEditorMode editorMode = static_cast<ViewportEditorMode>(mode);
m_viewportEditorModeTracker.ActivateMode({ viewportId }, editorMode);
m_viewportEditorModeTracker.DeactivateMode({ viewportId }, editorMode);
}
for (auto mode = 0; mode < ViewportEditorModes::NumEditorModes; mode++)
{
// Expect only the subscribers of each viewport to have received the editor mode activated and deactivated event
const ViewportEditorMode editorMode = static_cast<ViewportEditorMode>(mode);
const auto& editorModes = m_editorModeHandlers[mode]->GetEditorModes();
EXPECT_EQ(editorModes.size(), 1);
EXPECT_EQ(editorModes.count(editorMode), 1);
const auto& expectedEditorModeSet = editorModes.find(editorMode);
EXPECT_NE(expectedEditorModeSet, editorModes.end());
EXPECT_TRUE(expectedEditorModeSet->second.m_onEnter);
EXPECT_TRUE(expectedEditorModeSet->second.m_onExit);
}
}
} // namespace UnitTest
@@ -110,6 +110,7 @@ set(FILES
UI/EntityPropertyEditorTests.cpp
UndoStack.cpp
Viewport/ClusterTests.cpp
Viewport/ViewportEditorModeTests.cpp
Viewport/ViewportScreenTests.cpp
Viewport/ViewportUiClusterTests.cpp
Viewport/ViewportUiDisplayTests.cpp
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
@@ -12,28 +12,5 @@ set_target_properties(AssetProcessor PROPERTIES
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Platform/Mac/gui_info.plist
RESOURCE ${CMAKE_CURRENT_SOURCE_DIR}/Platform/Mac/Images.xcassets
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME AssetProcessorAppIcon
ENTITLEMENT_FILE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/Platform/Mac/AssetProcessorEntitlements.plist
)
# We cannot use ly_add_target here because we're already including this file from inside ly_add_target
# So we need to setup target, dependencies and install logic manually.
add_executable(AssetProcessorDummy Platform/Mac/main_dummy.cpp)
add_executable(AZ::AssetProcessorDummy ALIAS AssetProcessorDummy)
ly_target_link_libraries(AssetProcessorDummy
PRIVATE
AZ::AzCore
AZ::AzFramework)
ly_add_dependencies(AssetProcessor AssetProcessorDummy)
# Store the aliased target into a DIRECTORY property
set_property(DIRECTORY APPEND PROPERTY LY_DIRECTORY_TARGETS AZ::AssetProcessorDummy)
# Store the directory path in a GLOBAL property so that it can be accessed
# in the layout install logic. Skip if the directory has already been added
get_property(ly_all_target_directories GLOBAL PROPERTY LY_ALL_TARGET_DIRECTORIES)
if(NOT CMAKE_CURRENT_SOURCE_DIR IN_LIST ly_all_target_directories)
set_property(GLOBAL APPEND PROPERTY LY_ALL_TARGET_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR})
endif()
ly_install_add_install_path_setreg(AssetProcessor)
@@ -11,7 +11,7 @@
<key>CFBundleSignature</key>
<string>ASPR</string>
<key>CFBundleExecutable</key>
<string>AssetProcessorDummy</string>
<string>AssetProcessor</string>
<key>CFBundleIdentifier</key>
<string>com.Amazon.AssetProcessor</string>
</dict>
@@ -1,75 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/Process/ProcessWatcher.h>
#include <cstdlib>
int main(int argc, char* argv[])
{
// Create a ComponentApplication to initialize the AZ::SystemAllocator and initialize the SettingsRegistry
AZ::ComponentApplication::Descriptor desc;
AZ::ComponentApplication application;
application.Create(desc);
AZStd::vector<AZStd::string> envVars;
const char* homePath = std::getenv("HOME");
envVars.push_back(AZStd::string::format("HOME=%s", homePath));
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
const char* dyldLibPathOrig = std::getenv("DYLD_LIBRARY_PATH");
AZStd::string dyldSearchPath = AZStd::string::format("DYLD_LIBRARY_PATH=%s", dyldLibPathOrig);
if (AZ::IO::FixedMaxPath projectModulePath;
settingsRegistry->Get(projectModulePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectConfigurationBinPath))
{
dyldSearchPath.append(":");
dyldSearchPath.append(projectModulePath.c_str());
}
if (AZ::IO::FixedMaxPath installedBinariesFolder;
settingsRegistry->Get(installedBinariesFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_InstalledBinaryFolder))
{
if (AZ::IO::FixedMaxPath engineRootFolder;
settingsRegistry->Get(engineRootFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder))
{
installedBinariesFolder = engineRootFolder / installedBinariesFolder;
dyldSearchPath.append(":");
dyldSearchPath.append(installedBinariesFolder.c_str());
}
}
envVars.push_back(dyldSearchPath);
}
AZStd::string commandArgs;
for (int i = 1; i < argc; i++)
{
commandArgs.append(argv[i]);
commandArgs.append(" ");
}
AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
AZ::IO::Path processPath{ AZ::IO::PathView(AZ::Utils::GetExecutableDirectory()) };
processPath /= "AssetProcessor";
processLaunchInfo.m_processExecutableString = AZStd::move(processPath.Native());
processLaunchInfo.m_commandlineParameters = commandArgs;
processLaunchInfo.m_environmentVariables = &envVars;
processLaunchInfo.m_showWindow = true;
AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
application.Destroy();
return 0;
}
+24
View File
@@ -0,0 +1,24 @@
#
# 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
#
#
# This is the launcher that will be used by the O3DE_SDK.app bundle
# generated by the cmake install process for Mac.
if(NOT ${PAL_PLATFORM_NAME} STREQUAL Mac)
return()
endif()
ly_add_target(
NAME O3DE_SDK EXECUTABLE
NAMESPACE AZ
FILES_CMAKE
O3DE_SDK_files.cmake
BUILD_DEPENDENCIES
PRIVATE
AZ::AzCore
AZ::AzFramework
)
@@ -0,0 +1,63 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/Process/ProcessWatcher.h>
#include <cstdlib>
#include <mach-o/dyld.h>
int main(int argc, char* argv[])
{
// We need to pass in the engine path since we won't be able to find it by searching upwards.
// We can't use any containers that use our custom allocator till after the call to ComponentApplication::Create()
AZ::IO::FixedMaxPath processPath = AZ::Utils::GetExecutableDirectory();
AZ::IO::FixedMaxPath enginePath = (processPath / "../Engine").LexicallyNormal();
auto enginePathParam = AZ::SettingsRegistryInterface::FixedValueString::format(R"(--engine-path="%s")", enginePath.c_str());
// Uses the fixed_vector deduction guide to determine the type is AZStd::fixed_vector<char*, 2>
AZStd::fixed_vector commandLineParams{ processPath.Native().data(), enginePathParam.data() };
// Create a ComponentApplication to initialize the AZ::SystemAllocator and initialize the SettingsRegistry
AZ::ComponentApplication application(static_cast<int>(commandLineParams.size()), commandLineParams.data());
application.Create(AZ::ComponentApplication::Descriptor());
AZ::IO::FixedMaxPath installedBinariesFolder;
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
if (settingsRegistry->Get(installedBinariesFolder.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_InstalledBinaryFolder))
{
installedBinariesFolder = enginePath / installedBinariesFolder;
}
}
AZ::IO::FixedMaxPath shellPath = "/bin/sh";
AZStd::string parameters = AZStd::string::format("-c \"export LY_CMAKE_PATH=/usr/local/bin && \"%s/python/get_python.sh\"\"", enginePath.c_str());
AzFramework::ProcessLauncher::ProcessLaunchInfo shellProcessLaunch;
shellProcessLaunch.m_processExecutableString = AZStd::move(shellPath.Native());
shellProcessLaunch.m_commandlineParameters = parameters;
shellProcessLaunch.m_showWindow = true;
shellProcessLaunch.m_workingDirectory = enginePath.String();
AZStd::unique_ptr<AzFramework::ProcessWatcher> shellProcess(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;
processLaunchInfo.m_processExecutableString = AZStd::move(projectManagerPath.Native());
processLaunchInfo.m_showWindow = true;
AzFramework::ProcessLauncher::LaunchUnwatchedProcess(processLaunchInfo);
application.Destroy();
return 0;
}
@@ -0,0 +1,11 @@
#
# 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
#
#
set(FILES
O3DE_SDK_Launcher.cpp
)
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>O3DE_SDK</string>
<key>CFBundleIdentifier</key>
<string>org.O3DE.O3DE_SDK</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright (c) Contributors to the Open 3D Engine Project.</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>
+1
View File
@@ -20,3 +20,4 @@ add_subdirectory(GridHub)
add_subdirectory(Standalone)
add_subdirectory(TestImpactFramework)
add_subdirectory(ProjectManager)
add_subdirectory(BundleLauncher)
@@ -5,3 +5,4 @@
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.33301 6H12.6663V14.6667H3.33301V6ZM5.33301 7.33333H6.66634V13.3333H5.33301V7.33333ZM10.6663 7.33333H9.33301V13.3333H10.6663V7.33333Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.6667 2H5.33333V3.33333H2V4.66667H14V3.33333H10.6667V2Z" fill="#E9E9E9"/>
</svg>

After

Width:  |  Height:  |  Size: 430 B

@@ -0,0 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="2" y="2" width="1.33333" height="12" fill="white"/>
<rect x="2" y="12.6666" width="12" height="1.33333" fill="white"/>
<rect x="5.33301" y="9.20911" width="10.6667" height="2" transform="rotate(-45 5.33301 9.20911)" fill="white"/>
<rect x="2" y="2" width="6.66667" height="1.33333" fill="white"/>
<rect width="1.33333" height="6.66667" transform="matrix(-1 0 0 1 14 7.33337)" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 503 B

@@ -35,5 +35,8 @@
<file>Backgrounds/DefaultBackground.jpg</file>
<file>Backgrounds/FtueBackground.jpg</file>
<file>FeatureTagClose.svg</file>
<file>Refresh.svg</file>
<file>Edit.svg</file>
<file>Delete.svg</file>
</qresource>
</RCC>
@@ -518,3 +518,82 @@ QProgressBar::chunk {
font-size: 12px;
font-weight: 600;
}
/************** Engine **************/
#engineTab::tab-bar {
left: 60px;
}
#engineTabBar::tab {
height: 50px;
background-color: transparent;
font-weight: 400;
font-size: 18px;
min-width: 160px;
}
#engineTabBar::tab:selected {
border-bottom: 3px solid #94D2FF;
color: #94D2FF;
font-weight: 600;
}
#engineTabBar::tab:hover {
color: #94D2FF;
font-weight: 600;
}
#engineTabBar::tab:pressed {
color: #66bcfa;
}
#engineTopFrame {
background-color:#1E252F;
}
/************** Gem Repo **************/
#gemRepoHeaderLabel {
font-size: 12px;
}
#gemRepoHeaderRefreshButton {
background-color: transparent;
qproperty-flat: true;
qproperty-iconSize: 14px;
}
#gemRepoHeaderAddButton {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #888888, stop: 1.0 #555555);
qproperty-flat: true;
margin-right:30px;
min-width:120px;
max-width:120px;
min-height:24px;
max-height:24px;
border-radius: 3px;
text-align:center;
font-size:12px;
font-weight:600;
}
#gemRepoHeaderAddButton:hover {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #999999, stop: 1.0 #666666);
}
#gemRepoHeaderAddButton:pressed {
background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1,
stop: 0 #555555, stop: 1.0 #777777);
}
#gemRepoHeaderTable {
background-color: transparent;
max-height: 30px;
}
#gemRepoListHeader {
background-color: transparent;
}
#gemRepoInspector {
background: #444444;
}
@@ -0,0 +1,4 @@
<svg width="15" height="12" viewBox="0 0 15 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.046 5.85448C13.046 5.869 13.0436 5.66561 13.0436 5.68014H14.5278L12.3341 8.22252L10.1114 5.68014H11.6481C11.6481 5.66561 11.6513 5.869 11.6513 5.85448C11.6513 3.42833 9.77724 1.46707 7.46731 1.46707C6.42131 1.46707 5.46247 1.87385 4.73608 2.54213L3.8063 1.43801C4.79419 0.537286 6.07264 -0.000244141 7.46731 -0.000244141C10.5472 -0.000244141 13.046 2.6293 13.046 5.85448Z" fill="white"/>
<path d="M1.48184 6.14503C1.48184 6.13051 1.48428 6.3339 1.48428 6.31937H0L2.1937 3.777L4.41646 6.31937H2.87975C2.87975 6.3339 2.87651 6.13051 2.87651 6.14503C2.87651 8.57118 4.7506 10.5324 7.06053 10.5324C8.10654 10.5324 9.06537 10.1257 9.79177 9.45738L10.7215 10.5615C9.73366 11.4622 8.4552 11.9998 7.06053 11.9998C3.98063 11.9998 1.48184 9.37022 1.48184 6.14503Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 886 B

@@ -0,0 +1,64 @@
/*
* 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 <EngineScreenCtrl.h>
#include <GemRepo/GemRepoScreen.h>
#include <EngineSettingsScreen.h>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QTabWidget>
namespace O3DE::ProjectManager
{
EngineScreenCtrl::EngineScreenCtrl(QWidget* parent)
: ScreenWidget(parent)
{
QVBoxLayout* vLayout = new QVBoxLayout();
vLayout->setContentsMargins(0, 0, 0, 0);
QFrame* topBarFrameWidget = new QFrame(this);
topBarFrameWidget->setObjectName("engineTopFrame");
QHBoxLayout* topBarHLayout = new QHBoxLayout();
topBarHLayout->setContentsMargins(0, 0, 0, 0);
topBarFrameWidget->setLayout(topBarHLayout);
QTabWidget* tabWidget = new QTabWidget();
tabWidget->setObjectName("engineTab");
tabWidget->tabBar()->setObjectName("engineTabBar");
tabWidget->tabBar()->setFocusPolicy(Qt::TabFocus);
m_engineSettingsScreen = new EngineSettingsScreen();
m_gemRepoScreen = new GemRepoScreen();
tabWidget->addTab(m_engineSettingsScreen, tr("General"));
tabWidget->addTab(m_gemRepoScreen, tr("Gem Repositories"));
topBarHLayout->addWidget(tabWidget);
vLayout->addWidget(topBarFrameWidget);
setLayout(vLayout);
}
ProjectManagerScreen EngineScreenCtrl::GetScreenEnum()
{
return ProjectManagerScreen::UpdateProject;
}
QString EngineScreenCtrl::GetTabText()
{
return tr("Engine");
}
bool EngineScreenCtrl::IsTab()
{
return true;
}
} // namespace O3DE::ProjectManager
@@ -0,0 +1,34 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <ScreenWidget.h>
#endif
namespace O3DE::ProjectManager
{
QT_FORWARD_DECLARE_CLASS(EngineSettingsScreen)
QT_FORWARD_DECLARE_CLASS(GemRepoScreen)
class EngineScreenCtrl
: public ScreenWidget
{
public:
explicit EngineScreenCtrl(QWidget* parent = nullptr);
~EngineScreenCtrl() = default;
ProjectManagerScreen GetScreenEnum() override;
QString GetTabText() override;
bool IsTab() override;
EngineSettingsScreen* m_engineSettingsScreen = nullptr;
GemRepoScreen* m_gemRepoScreen = nullptr;
};
} // namespace O3DE::ProjectManager
@@ -7,15 +7,16 @@
*/
#include <EngineSettingsScreen.h>
#include <QVBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
#include <FormLineEditWidget.h>
#include <FormFolderBrowseEditWidget.h>
#include <PythonBindingsInterface.h>
#include <PathValidator.h>
#include <QVBoxLayout>
#include <QLabel>
#include <QLineEdit>
#include <QMessageBox>
namespace O3DE::ProjectManager
{
EngineSettingsScreen::EngineSettingsScreen(QWidget* parent)
@@ -78,16 +79,6 @@ namespace O3DE::ProjectManager
return ProjectManagerScreen::EngineSettings;
}
QString EngineSettingsScreen::GetTabText()
{
return tr("Engine");
}
bool EngineSettingsScreen::IsTab()
{
return true;
}
void EngineSettingsScreen::OnTextChanged()
{
// save engine settings
@@ -24,8 +24,6 @@ namespace O3DE::ProjectManager
~EngineSettingsScreen() = default;
ProjectManagerScreen GetScreenEnum() override;
QString GetTabText() override;
bool IsTab() override;
protected slots:
void OnTextChanged();
@@ -0,0 +1,32 @@
/*
* 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 <GemRepo/GemRepoInfo.h>
namespace O3DE::ProjectManager
{
GemRepoInfo::GemRepoInfo(
const QString& name, const QString& creator, const QString& summary, const QDateTime& lastUpdated, bool isEnabled = true)
: m_name(name)
, m_creator(creator)
, m_summary(summary)
, m_lastUpdated(lastUpdated)
, m_isEnabled(isEnabled)
{
}
bool GemRepoInfo::IsValid() const
{
return !m_name.isEmpty();
}
bool GemRepoInfo::operator<(const GemRepoInfo& gemRepoInfo) const
{
return (m_lastUpdated < gemRepoInfo.m_lastUpdated);
}
} // namespace O3DE::ProjectManager
@@ -0,0 +1,37 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QString>
#include <QDateTime>
#endif
namespace O3DE::ProjectManager
{
class GemRepoInfo
{
public:
GemRepoInfo() = default;
GemRepoInfo(const QString& name, const QString& creator, const QString& summary, const QDateTime& lastUpdated, bool isEnabled);
bool IsValid() const;
bool operator<(const GemRepoInfo& gemRepoInfo) const;
QString m_path;
QString m_name = "Unknown Gem Repo Name";
QString m_creator = "Unknown Creator";
bool m_isEnabled = false; //! Is the repo currently enabled for this engine?
QString m_summary = "No summary provided.";
QString m_directoryLink;
QString m_repoLink;
QDateTime m_lastUpdated;
};
} // namespace O3DE::ProjectManager
@@ -0,0 +1,222 @@
/*
* 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 <GemRepo/GemRepoItemDelegate.h>
#include <GemRepo/GemRepoModel.h>
#include <QEvent>
#include <QPainter>
#include <QMouseEvent>
namespace O3DE::ProjectManager
{
GemRepoItemDelegate::GemRepoItemDelegate(QAbstractItemModel* model, QObject* parent)
: QStyledItemDelegate(parent)
, m_model(model)
{
m_refreshIcon = QIcon(":/Refresh.svg").pixmap(s_refreshIconSize, s_refreshIconSize);
m_editIcon = QIcon(":/Edit.svg").pixmap(s_iconSize, s_iconSize);
m_deleteIcon = QIcon(":/Delete.svg").pixmap(s_iconSize, s_iconSize);
}
void GemRepoItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const
{
if (!modelIndex.isValid())
{
return;
}
QStyleOptionViewItem options(option);
initStyleOption(&options, modelIndex);
painter->setRenderHint(QPainter::Antialiasing);
QRect fullRect, itemRect, contentRect;
CalcRects(options, fullRect, itemRect, contentRect);
QRect buttonRect = CalcButtonRect(contentRect);
QFont standardFont(options.font);
standardFont.setPixelSize(static_cast<int>(s_fontSize));
QFontMetrics standardFontMetrics(standardFont);
painter->save();
painter->setClipping(true);
painter->setClipRect(fullRect);
painter->setFont(standardFont);
painter->setPen(m_textColor);
// Draw background
painter->fillRect(fullRect, m_backgroundColor);
// Draw item background
const QColor itemBackgroundColor = options.state & QStyle::State_MouseOver ? m_itemBackgroundColor.lighter(120) : m_itemBackgroundColor;
painter->fillRect(itemRect, itemBackgroundColor);
// Draw border
if (options.state & QStyle::State_Selected)
{
painter->save();
QPen borderPen(m_borderColor);
borderPen.setWidth(s_borderWidth);
painter->setPen(borderPen);
painter->drawRect(itemRect);
painter->restore();
}
// Repo enabled
DrawButton(painter, buttonRect, modelIndex);
// Repo name
QString repoName = GemRepoModel::GetName(modelIndex);
repoName = QFontMetrics(standardFont).elidedText(repoName, Qt::TextElideMode::ElideRight, s_nameMaxWidth);
QRect repoNameRect = GetTextRect(standardFont, repoName, s_fontSize);
int currentHorizontalOffset = buttonRect.left() + s_buttonWidth + s_buttonSpacing;
repoNameRect.moveTo(currentHorizontalOffset, contentRect.center().y() - repoNameRect.height() / 2);
repoNameRect = painter->boundingRect(repoNameRect, Qt::TextSingleLine, repoName);
painter->drawText(repoNameRect, Qt::TextSingleLine, repoName);
// Rem repo creator
QString repoCreator = GemRepoModel::GetCreator(modelIndex);
repoCreator = standardFontMetrics.elidedText(repoCreator, Qt::TextElideMode::ElideRight, s_creatorMaxWidth);
QRect repoCreatorRect = GetTextRect(standardFont, repoCreator, s_fontSize);
currentHorizontalOffset += s_nameMaxWidth + s_contentSpacing;
repoCreatorRect.moveTo(currentHorizontalOffset, contentRect.center().y() - repoCreatorRect.height() / 2);
repoCreatorRect = painter->boundingRect(repoCreatorRect, Qt::TextSingleLine, repoCreator);
painter->drawText(repoCreatorRect, Qt::TextSingleLine, repoCreator);
// Repo update
QString repoUpdatedDate = GemRepoModel::GetLastUpdated(modelIndex).toString("dd/MM/yyyy hh:mmap");
repoUpdatedDate = standardFontMetrics.elidedText(repoUpdatedDate, Qt::TextElideMode::ElideRight, s_updatedMaxWidth);
QRect repoUpdatedDateRect = GetTextRect(standardFont, repoUpdatedDate, s_fontSize);
currentHorizontalOffset += s_creatorMaxWidth + s_contentSpacing;
repoUpdatedDateRect.moveTo(currentHorizontalOffset, contentRect.center().y() - repoUpdatedDateRect.height() / 2);
repoUpdatedDateRect = painter->boundingRect(repoUpdatedDateRect, Qt::TextSingleLine, repoUpdatedDate);
painter->drawText(repoUpdatedDateRect, Qt::TextSingleLine, repoUpdatedDate);
// Draw refresh button
painter->drawPixmap(
repoUpdatedDateRect.left() + repoUpdatedDateRect.width() + s_refreshIconSpacing,
contentRect.center().y() - s_refreshIconSize / 3, // Dividing size by 3 centers much better
m_refreshIcon);
if (options.state & QStyle::State_MouseOver)
{
DrawEditButtons(painter, contentRect);
}
painter->restore();
}
QSize GemRepoItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const
{
QStyleOptionViewItem options(option);
initStyleOption(&options, modelIndex);
int marginsHorizontal = s_itemMargins.left() + s_itemMargins.right() + s_contentMargins.left() + s_contentMargins.right();
return QSize(marginsHorizontal + s_buttonWidth + s_buttonSpacing + s_nameMaxWidth + s_creatorMaxWidth + s_updatedMaxWidth + s_contentSpacing * 3, s_height);
}
bool GemRepoItemDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex)
{
if (!modelIndex.isValid())
{
return false;
}
if (event->type() == QEvent::KeyPress)
{
auto keyEvent = static_cast<const QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Space)
{
const bool isAdded = GemRepoModel::IsEnabled(modelIndex);
GemRepoModel::SetEnabled(*model, modelIndex, !isAdded);
return true;
}
}
if (event->type() == QEvent::MouseButtonPress)
{
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
QRect fullRect, itemRect, contentRect;
CalcRects(option, fullRect, itemRect, contentRect);
const QRect buttonRect = CalcButtonRect(contentRect);
if (buttonRect.contains(mouseEvent->pos()))
{
const bool isAdded = GemRepoModel::IsEnabled(modelIndex);
GemRepoModel::SetEnabled(*model, modelIndex, !isAdded);
return true;
}
}
return QStyledItemDelegate::editorEvent(event, model, option, modelIndex);
}
void GemRepoItemDelegate::CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const
{
outFullRect = QRect(option.rect);
outItemRect = QRect(outFullRect.adjusted(s_itemMargins.left(), s_itemMargins.top(), -s_itemMargins.right(), -s_itemMargins.bottom()));
outContentRect = QRect(outItemRect.adjusted(s_contentMargins.left(), s_contentMargins.top(), -s_contentMargins.right(), -s_contentMargins.bottom()));
}
QRect GemRepoItemDelegate::GetTextRect(QFont& font, const QString& text, qreal fontSize) const
{
font.setPixelSize(static_cast<int>(fontSize));
return QFontMetrics(font).boundingRect(text);
}
QRect GemRepoItemDelegate::CalcButtonRect(const QRect& contentRect) const
{
const QPoint topLeft = QPoint(contentRect.left(), contentRect.top() + contentRect.height() / 2 - s_buttonHeight / 2);
const QSize size = QSize(s_buttonWidth, s_buttonHeight);
return QRect(topLeft, size);
}
void GemRepoItemDelegate::DrawButton(QPainter* painter, const QRect& buttonRect, const QModelIndex& modelIndex) const
{
painter->save();
QPoint circleCenter;
const bool isEnabled = GemRepoModel::IsEnabled(modelIndex);
if (isEnabled)
{
painter->setBrush(m_buttonEnabledColor);
painter->setPen(m_buttonEnabledColor);
circleCenter = buttonRect.center() + QPoint(buttonRect.width() / 2 - s_buttonBorderRadius + 1, 1);
}
else
{
circleCenter = buttonRect.center() + QPoint(-buttonRect.width() / 2 + s_buttonBorderRadius + 1, 1);
}
// Rounded rect
painter->drawRoundedRect(buttonRect, s_buttonBorderRadius, s_buttonBorderRadius);
// Circle
painter->setBrush(m_textColor);
painter->drawEllipse(circleCenter, s_buttonCircleRadius, s_buttonCircleRadius);
painter->restore();
}
void GemRepoItemDelegate::DrawEditButtons(QPainter* painter, const QRect& contentRect) const
{
painter->drawPixmap(contentRect.right() - s_iconSize * 2 - s_iconSpacing, contentRect.center().y() - s_iconSize / 2, m_editIcon);
painter->drawPixmap(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2, m_deleteIcon);
}
} // namespace O3DE::ProjectManager
@@ -0,0 +1,82 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QStyledItemDelegate>
#include <GemRepo/GemRepoInfo.h>
#endif
QT_FORWARD_DECLARE_CLASS(QAbstractItemModel)
QT_FORWARD_DECLARE_CLASS(QEvent)
namespace O3DE::ProjectManager
{
class GemRepoItemDelegate
: public QStyledItemDelegate
{
Q_OBJECT // AUTOMOC
public:
explicit GemRepoItemDelegate(QAbstractItemModel* model, QObject* parent = nullptr);
~GemRepoItemDelegate() = default;
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override;
bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) override;
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const override;
// Colors
const QColor m_textColor = QColor("#FFFFFF");
const QColor m_backgroundColor = QColor("#333333"); // Outside of the actual repo item
const QColor m_itemBackgroundColor = QColor("#404040"); // Background color of the repo item
const QColor m_borderColor = QColor("#1E70EB");
const QColor m_buttonEnabledColor = QColor("#1E70EB");
// Item
inline constexpr static int s_height = 72; // Repo item total height
inline constexpr static qreal s_fontSize = 12.0;
// Margin and borders
inline constexpr static QMargins s_itemMargins = QMargins(/*left=*/0, /*top=*/8, /*right=*/60, /*bottom=*/8); // Item border distances
inline constexpr static QMargins s_contentMargins = QMargins(/*left=*/20, /*top=*/20, /*right=*/20, /*bottom=*/20); // Distances of the elements within an item to the item borders
inline constexpr static int s_borderWidth = 4;
// Content
inline constexpr static int s_contentSpacing = 5;
inline constexpr static int s_nameMaxWidth = 145;
inline constexpr static int s_creatorMaxWidth = 115;
inline constexpr static int s_updatedMaxWidth = 125;
// Button
inline constexpr static int s_buttonWidth = 32;
inline constexpr static int s_buttonHeight = 16;
inline constexpr static int s_buttonBorderRadius = 8;
inline constexpr static int s_buttonCircleRadius = s_buttonBorderRadius - 2;
inline constexpr static int s_buttonSpacing = 20;
// Icon
inline constexpr static int s_iconSize = 24;
inline constexpr static int s_iconSpacing = 16;
inline constexpr static int s_refreshIconSize = 14;
inline constexpr static int s_refreshIconSpacing = 10;
protected:
void CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const;
QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const;
QRect CalcButtonRect(const QRect& contentRect) const;
void DrawButton(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const;
void DrawEditButtons(QPainter* painter, const QRect& contentRect) const;
QAbstractItemModel* m_model = nullptr;
QPixmap m_refreshIcon;
QPixmap m_editIcon;
QPixmap m_deleteIcon;
};
} // namespace O3DE::ProjectManager
@@ -0,0 +1,23 @@
/*
* 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 <GemRepo/GemRepoListView.h>
#include <GemRepo/GemRepoItemDelegate.h>
namespace O3DE::ProjectManager
{
GemRepoListView::GemRepoListView(QAbstractItemModel* model, QWidget* parent)
: QListView(parent)
{
setObjectName("gemRepoListView");
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
setModel(model);
setItemDelegate(new GemRepoItemDelegate(model, this));
}
} // namespace O3DE::ProjectManager
@@ -0,0 +1,28 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QListView>
#endif
QT_FORWARD_DECLARE_CLASS(QAbstractItemModel)
namespace O3DE::ProjectManager
{
class GemRepoListView
: public QListView
{
Q_OBJECT // AUTOMOC
public:
explicit GemRepoListView(QAbstractItemModel* model, QWidget* parent = nullptr);
~GemRepoListView() = default;
};
} // namespace O3DE::ProjectManager
@@ -0,0 +1,94 @@
/*
* 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 <GemRepo/GemRepoModel.h>
#include <QItemSelectionModel>
namespace O3DE::ProjectManager
{
GemRepoModel::GemRepoModel(QObject* parent)
: QStandardItemModel(parent)
{
m_selectionModel = new QItemSelectionModel(this, parent);
}
QItemSelectionModel* GemRepoModel::GetSelectionModel() const
{
return m_selectionModel;
}
void GemRepoModel::AddGemRepo(const GemRepoInfo& gemRepoInfo)
{
QStandardItem* item = new QStandardItem();
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
item->setData(gemRepoInfo.m_name, RoleName);
item->setData(gemRepoInfo.m_creator, RoleCreator);
item->setData(gemRepoInfo.m_summary, RoleSummary);
item->setData(gemRepoInfo.m_isEnabled, RoleIsEnabled);
item->setData(gemRepoInfo.m_directoryLink, RoleDirectoryLink);
item->setData(gemRepoInfo.m_repoLink, RoleRepoLink);
item->setData(gemRepoInfo.m_lastUpdated, RoleLastUpdated);
item->setData(gemRepoInfo.m_path, RolePath);
appendRow(item);
}
void GemRepoModel::Clear()
{
clear();
}
QString GemRepoModel::GetName(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleName).toString();
}
QString GemRepoModel::GetCreator(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleCreator).toString();
}
QString GemRepoModel::GetSummary(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleSummary).toString();
}
QString GemRepoModel::GetDirectoryLink(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleDirectoryLink).toString();
}
QString GemRepoModel::GetRepoLink(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleRepoLink).toString();
}
QDateTime GemRepoModel::GetLastUpdated(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleLastUpdated).toDateTime();
}
QString GemRepoModel::GetPath(const QModelIndex& modelIndex)
{
return modelIndex.data(RolePath).toString();
}
bool GemRepoModel::IsEnabled(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleIsEnabled).toBool();
}
void GemRepoModel::SetEnabled(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isEnabled)
{
model.setData(modelIndex, isEnabled, RoleIsEnabled);
}
} // namespace O3DE::ProjectManager
@@ -0,0 +1,58 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QStandardItemModel>
#include <GemRepo/GemRepoInfo.h>
#endif
QT_FORWARD_DECLARE_CLASS(QItemSelectionModel)
namespace O3DE::ProjectManager
{
class GemRepoModel
: public QStandardItemModel
{
Q_OBJECT // AUTOMOC
public:
explicit GemRepoModel(QObject* parent = nullptr);
QItemSelectionModel* GetSelectionModel() const;
void AddGemRepo(const GemRepoInfo& gemInfo);
void Clear();
static QString GetName(const QModelIndex& modelIndex);
static QString GetCreator(const QModelIndex& modelIndex);
static QString GetSummary(const QModelIndex& modelIndex);
static QString GetDirectoryLink(const QModelIndex& modelIndex);
static QString GetRepoLink(const QModelIndex& modelIndex);
static QDateTime GetLastUpdated(const QModelIndex& modelIndex);
static QString GetPath(const QModelIndex& modelIndex);
static bool IsEnabled(const QModelIndex& modelIndex);
static void SetEnabled(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isEnabled);
private:
enum UserRole
{
RoleName = Qt::UserRole,
RoleCreator,
RoleSummary,
RoleIsEnabled,
RoleDirectoryLink,
RoleRepoLink,
RoleLastUpdated,
RolePath
};
QItemSelectionModel* m_selectionModel = nullptr;
};
} // namespace O3DE::ProjectManager
@@ -0,0 +1,145 @@
/*
* 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 <GemRepo/GemRepoScreen.h>
#include <GemRepo/GemRepoItemDelegate.h>
#include <GemRepo/GemRepoListView.h>
#include <GemRepo/GemRepoModel.h>
#include <PythonBindingsInterface.h>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QTimer>
#include <QMessageBox>
#include <QLabel>
#include <QHeaderView>
#include <QTableWidget>
namespace O3DE::ProjectManager
{
GemRepoScreen::GemRepoScreen(QWidget* parent)
: ScreenWidget(parent)
{
m_gemRepoModel = new GemRepoModel(this);
QVBoxLayout* vLayout = new QVBoxLayout();
vLayout->setMargin(0);
vLayout->setSpacing(0);
setLayout(vLayout);
QHBoxLayout* hLayout = new QHBoxLayout();
hLayout->setMargin(0);
hLayout->setSpacing(0);
vLayout->addLayout(hLayout);
hLayout->addSpacing(60);
m_gemRepoInspector = new QFrame(this);
m_gemRepoInspector->setObjectName(tr("gemRepoInspector"));
m_gemRepoInspector->setFixedWidth(240);
QVBoxLayout* middleVLayout = new QVBoxLayout();
middleVLayout->setMargin(0);
middleVLayout->setSpacing(0);
middleVLayout->addSpacing(30);
QHBoxLayout* topMiddleHLayout = new QHBoxLayout();
topMiddleHLayout->setMargin(0);
topMiddleHLayout->setSpacing(0);
m_lastAllUpdateLabel = new QLabel(tr("Last Updated: Never"), this);
m_lastAllUpdateLabel->setObjectName("gemRepoHeaderLabel");
topMiddleHLayout->addWidget(m_lastAllUpdateLabel);
topMiddleHLayout->addSpacing(20);
m_AllUpdateButton = new QPushButton(QIcon(":/Refresh.svg"), tr("Update All"), this);
m_AllUpdateButton->setObjectName("gemRepoHeaderRefreshButton");
topMiddleHLayout->addWidget(m_AllUpdateButton);
topMiddleHLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum));
m_AddRepoButton = new QPushButton(tr("Add Repository"), this);
m_AddRepoButton->setObjectName("gemRepoHeaderAddButton");
topMiddleHLayout->addWidget(m_AddRepoButton);
middleVLayout->addLayout(topMiddleHLayout);
middleVLayout->addSpacing(30);
// Create a QTableWidget just for its header
// Using a seperate model allows the setup of a header exactly as needed
m_gemRepoHeaderTable = new QTableWidget(this);
m_gemRepoHeaderTable->setObjectName("gemRepoHeaderTable");
m_gemRepoListHeader = m_gemRepoHeaderTable->horizontalHeader();
m_gemRepoListHeader->setObjectName("gemRepoListHeader");
m_gemRepoListHeader->setSectionResizeMode(QHeaderView::ResizeMode::Fixed);
// Insert columns so the header labels will show up
m_gemRepoHeaderTable->insertColumn(0);
m_gemRepoHeaderTable->insertColumn(1);
m_gemRepoHeaderTable->insertColumn(2);
m_gemRepoHeaderTable->insertColumn(3);
m_gemRepoHeaderTable->setHorizontalHeaderLabels({ tr("Enabled"), tr("Repository Name"), tr("Creator"), tr("Updated") });
const int headerExtraMargin = 10;
m_gemRepoListHeader->resizeSection(0, GemRepoItemDelegate::s_buttonWidth + GemRepoItemDelegate::s_buttonSpacing - 3);
m_gemRepoListHeader->resizeSection(1, GemRepoItemDelegate::s_nameMaxWidth + GemRepoItemDelegate::s_contentSpacing - headerExtraMargin);
m_gemRepoListHeader->resizeSection(2, GemRepoItemDelegate::s_creatorMaxWidth + GemRepoItemDelegate::s_contentSpacing - headerExtraMargin);
m_gemRepoListHeader->resizeSection(3, GemRepoItemDelegate::s_updatedMaxWidth + GemRepoItemDelegate::s_contentSpacing - headerExtraMargin);
// Required to set stylesheet in code as it will not be respected if set in qss
m_gemRepoHeaderTable->horizontalHeader()->setStyleSheet("QHeaderView::section { background-color:transparent; color:white; font-size:12px; text-align:left; border-style:none; }");
middleVLayout->addWidget(m_gemRepoHeaderTable);
m_gemRepoListView = new GemRepoListView(m_gemRepoModel, this);
middleVLayout->addWidget(m_gemRepoListView);
hLayout->addLayout(middleVLayout);
hLayout->addWidget(m_gemRepoInspector);
Reinit();
}
void GemRepoScreen::Reinit()
{
m_gemRepoModel->clear();
FillModel();
// Select the first entry after everything got correctly sized
QTimer::singleShot(200, [=]{
QModelIndex firstModelIndex = m_gemRepoListView->model()->index(0,0);
m_gemRepoListView->selectionModel()->select(firstModelIndex, QItemSelectionModel::ClearAndSelect);
});
}
void GemRepoScreen::FillModel()
{
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> allGemRepoInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoInfos();
if (allGemRepoInfosResult.IsSuccess())
{
// Add all available repos to the model
const QVector<GemRepoInfo> allGemRepoInfos = allGemRepoInfosResult.GetValue();
for (const GemRepoInfo& gemRepoInfo : allGemRepoInfos)
{
m_gemRepoModel->AddGemRepo(gemRepoInfo);
}
}
else
{
QMessageBox::critical(this, tr("Operation failed"), QString("Cannot retrieve gem repos for engine.\n\nError:\n%2").arg(allGemRepoInfosResult.GetError().c_str()));
}
}
ProjectManagerScreen GemRepoScreen::GetScreenEnum()
{
return ProjectManagerScreen::GemRepos;
}
} // namespace O3DE::ProjectManager
@@ -0,0 +1,50 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <ScreenWidget.h>
#endif
QT_FORWARD_DECLARE_CLASS(QLabel)
QT_FORWARD_DECLARE_CLASS(QPushButton)
QT_FORWARD_DECLARE_CLASS(QHeaderView)
QT_FORWARD_DECLARE_CLASS(QTableWidget)
namespace O3DE::ProjectManager
{
QT_FORWARD_DECLARE_CLASS(GemRepoListView)
QT_FORWARD_DECLARE_CLASS(GemRepoModel)
class GemRepoScreen
: public ScreenWidget
{
public:
explicit GemRepoScreen(QWidget* parent = nullptr);
~GemRepoScreen() = default;
ProjectManagerScreen GetScreenEnum() override;
void Reinit();
GemRepoModel* GetGemRepoModel() const { return m_gemRepoModel; }
private:
void FillModel();
QTableWidget* m_gemRepoHeaderTable = nullptr;
QHeaderView* m_gemRepoListHeader = nullptr;
GemRepoListView* m_gemRepoListView = nullptr;
QFrame* m_gemRepoInspector = nullptr;
GemRepoModel* m_gemRepoModel = nullptr;
QLabel* m_lastAllUpdateLabel;
QPushButton* m_AllUpdateButton;
QPushButton* m_AddRepoButton;
};
} // namespace O3DE::ProjectManager
@@ -22,7 +22,7 @@ namespace O3DE::ProjectManager
QVector<ProjectManagerScreen> screenEnums =
{
ProjectManagerScreen::Projects,
ProjectManagerScreen::EngineSettings,
ProjectManagerScreen::Engine,
ProjectManagerScreen::CreateProject,
ProjectManagerScreen::UpdateProject
};
@@ -912,4 +912,45 @@ namespace O3DE::ProjectManager
return AZ::Success(AZStd::move(templates));
}
}
GemRepoInfo PythonBindings::GemRepoInfoFromPath(pybind11::handle path, pybind11::handle pyEnginePath)
{
/* Placeholder Logic */
(void)path;
(void)pyEnginePath;
return GemRepoInfo();
}
//#define MOCK_GEM_REPO_INFO true
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> PythonBindings::GetAllGemRepoInfos()
{
QVector<GemRepoInfo> gemRepos;
#ifndef MOCK_GEM_REPO_INFO
auto result = ExecuteWithLockErrorHandling(
[&]
{
/* Placeholder Logic, o3de scripts need method added
*
for (auto path : m_manifest.attr("get_gem_repos")())
{
gemRepos.push_back(GemRepoInfoFromPath(path, pybind11::none()));
}
*
*/
});
if (!result.IsSuccess())
{
return AZ::Failure<AZStd::string>(result.GetError().c_str());
}
#else
gemRepos.push_back(GemRepoInfo("JohnCreates", "John Smith", "", QDateTime(QDate(2021, 8, 31), QTime(11, 57)), true));
gemRepos.push_back(GemRepoInfo("JanesGems", "Jane Doe", "", QDateTime(QDate(2021, 9, 10), QTime(18, 23)), false));
#endif // MOCK_GEM_REPO_INFO
std::sort(gemRepos.begin(), gemRepos.end());
return AZ::Success(AZStd::move(gemRepos));
}
}
@@ -56,12 +56,16 @@ namespace O3DE::ProjectManager
// ProjectTemplate
AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates(const QString& projectPath = {}) override;
// Gem Repos
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() override;
private:
AZ_DISABLE_COPY_MOVE(PythonBindings);
AZ::Outcome<void, AZStd::string> ExecuteWithLockErrorHandling(AZStd::function<void()> executionCallback);
bool ExecuteWithLock(AZStd::function<void()> executionCallback);
GemInfo GemInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath);
GemRepoInfo GemRepoInfoFromPath(pybind11::handle path, pybind11::handle pyEnginePath);
ProjectInfo ProjectInfoFromPath(pybind11::handle path);
ProjectTemplateInfo ProjectTemplateInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath);
bool RegisterThisEngine();
@@ -17,6 +17,7 @@
#include <GemCatalog/GemInfo.h>
#include <ProjectInfo.h>
#include <ProjectTemplateInfo.h>
#include <GemRepo/GemRepoInfo.h>
namespace O3DE::ProjectManager
{
@@ -56,14 +57,14 @@ namespace O3DE::ProjectManager
/**
* Get info about a Gem
* @param path the absolute path to the Gem
* @param projectPath the absolute path to the Gem
* @return an outcome with GemInfo on success
*/
virtual AZ::Outcome<GemInfo> GetGemInfo(const QString& path, const QString& projectPath = {}) = 0;
/**
* Get all available gem infos. This concatenates gems registered by the engine and the project.
* @param path The absolute path to the project.
* @param projectPath The absolute path to the project.
* @return A list of gem infos.
*/
virtual AZ::Outcome<QVector<GemInfo>, AZStd::string> GetAllGemInfos(const QString& projectPath) = 0;
@@ -155,6 +156,14 @@ namespace O3DE::ProjectManager
* @return an outcome with ProjectTemplateInfos on success
*/
virtual AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates(const QString& projectPath = {}) = 0;
// Gem Repos
/**
* Get all available gem repo infos. Gathers all repos registered with the engine.
* @return A list of gem repo infos.
*/
virtual AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() = 0;
};
using PythonBindingsInterface = AZ::Interface<IPythonBindings>;
@@ -23,7 +23,9 @@ namespace O3DE::ProjectManager
Projects,
UpdateProject,
UpdateProjectSettings,
EngineSettings
Engine,
EngineSettings,
GemRepos
};
static QHash<QString, ProjectManagerScreen> s_ProjectManagerStringNames = {
@@ -34,7 +36,9 @@ namespace O3DE::ProjectManager
{ "Projects", ProjectManagerScreen::Projects},
{ "UpdateProject", ProjectManagerScreen::UpdateProject},
{ "UpdateProjectSettings", ProjectManagerScreen::UpdateProjectSettings},
{ "EngineSettings", ProjectManagerScreen::EngineSettings}
{ "Engine", ProjectManagerScreen::Engine},
{ "EngineSettings", ProjectManagerScreen::EngineSettings},
{ "GemRepos", ProjectManagerScreen::GemRepos}
};
// need to define qHash for ProjectManagerScreen when using scoped enums
@@ -13,7 +13,9 @@
#include <GemCatalog/GemCatalogScreen.h>
#include <ProjectsScreen.h>
#include <UpdateProjectSettingsScreen.h>
#include <EngineScreenCtrl.h>
#include <EngineSettingsScreen.h>
#include <GemRepo/GemRepoScreen.h>
namespace O3DE::ProjectManager
{
@@ -41,9 +43,15 @@ namespace O3DE::ProjectManager
case (ProjectManagerScreen::UpdateProjectSettings):
newScreen = new UpdateProjectSettingsScreen(parent);
break;
case (ProjectManagerScreen::Engine):
newScreen = new EngineScreenCtrl(parent);
break;
case (ProjectManagerScreen::EngineSettings):
newScreen = new EngineSettingsScreen(parent);
break;
case (ProjectManagerScreen::GemRepos):
newScreen = new GemRepoScreen(parent);
break;
case (ProjectManagerScreen::Empty):
default:
newScreen = new ScreenWidget(parent);
@@ -56,6 +56,8 @@ set(FILES
Source/ProjectsScreen.cpp
Source/ProjectSettingsScreen.h
Source/ProjectSettingsScreen.cpp
Source/EngineScreenCtrl.h
Source/EngineScreenCtrl.cpp
Source/EngineSettingsScreen.h
Source/EngineSettingsScreen.cpp
Source/ProjectButtonWidget.h
@@ -98,4 +100,14 @@ set(FILES
Source/GemCatalog/GemRequirementListView.cpp
Source/GemCatalog/GemSortFilterProxyModel.h
Source/GemCatalog/GemSortFilterProxyModel.cpp
Source/GemRepo/GemRepoScreen.h
Source/GemRepo/GemRepoScreen.cpp
Source/GemRepo/GemRepoInfo.h
Source/GemRepo/GemRepoInfo.cpp
Source/GemRepo/GemRepoItemDelegate.h
Source/GemRepo/GemRepoItemDelegate.cpp
Source/GemRepo/GemRepoListView.h
Source/GemRepo/GemRepoListView.cpp
Source/GemRepo/GemRepoModel.h
Source/GemRepo/GemRepoModel.cpp
)