Merge branch 'development' of https://github.com/o3de/o3de into Atom/semaster/add_diffuse_probe_grid_p0
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
"gem_name": "PythonCoverage",
|
||||
"display_name": "PythonCoverage",
|
||||
"license": "Apache-2.0 Or MIT",
|
||||
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
|
||||
"origin": "Open 3D Engine - o3de.org",
|
||||
"type": "Tool",
|
||||
"summary": "A tool for generating gem coverage for Python tests.",
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
"gem_name": "AutomatedTesting",
|
||||
"display_name": "AutomatedTesting",
|
||||
"license": "Apache-2.0 Or MIT",
|
||||
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
|
||||
"origin": "Amazon Web Services, Inc.",
|
||||
"type": "Code",
|
||||
"summary": "Project Gem for customizing the AutomatedTesting project functionality.",
|
||||
"canonical_tags": ["Gem"],
|
||||
"canonical_tags": [
|
||||
"Gem"
|
||||
],
|
||||
"user_tags": [],
|
||||
"icon_path": "preview.png",
|
||||
"requirements": ""
|
||||
|
||||
@@ -43,10 +43,11 @@
|
||||
|
||||
// AzToolsFramework
|
||||
#include <AzToolsFramework/API/ComponentEntityObjectBus.h>
|
||||
#include <AzToolsFramework/API/EditorCameraBus.h>
|
||||
#include <AzToolsFramework/API/ViewportEditorModeTrackerInterface.h>
|
||||
#include <AzToolsFramework/Manipulators/ManipulatorManager.h>
|
||||
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h>
|
||||
#include <AzToolsFramework/ViewportSelection/EditorTransformComponentSelectionRequestBus.h>
|
||||
#include <AzToolsFramework/API/EditorCameraBus.h>
|
||||
|
||||
// AtomToolsFramework
|
||||
#include <AtomToolsFramework/Viewport/RenderViewportWidget.h>
|
||||
@@ -1032,6 +1033,7 @@ void EditorViewportWidget::ConnectViewportInteractionRequestBus()
|
||||
AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler::BusConnect(GetViewportId());
|
||||
AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler::BusConnect(GetViewportId());
|
||||
m_viewportUi.ConnectViewportUiBus(GetViewportId());
|
||||
AzFramework::ViewportBorderRequestBus::Handler::BusConnect(GetViewportId());
|
||||
|
||||
AzFramework::InputSystemCursorConstraintRequestBus::Handler::BusConnect();
|
||||
}
|
||||
@@ -1040,6 +1042,7 @@ void EditorViewportWidget::DisconnectViewportInteractionRequestBus()
|
||||
{
|
||||
AzFramework::InputSystemCursorConstraintRequestBus::Handler::BusDisconnect();
|
||||
|
||||
AzFramework::ViewportBorderRequestBus::Handler::BusDisconnect();
|
||||
m_viewportUi.DisconnectViewportUiBus();
|
||||
AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler::BusDisconnect();
|
||||
AzToolsFramework::ViewportInteraction::MainEditorViewportInteractionRequestBus::Handler::BusDisconnect();
|
||||
@@ -1124,7 +1127,9 @@ void EditorViewportWidget::OnTitleMenu(QMenu* menu)
|
||||
action = menu->addAction(tr("Create camera entity from current view"));
|
||||
connect(action, &QAction::triggered, this, &EditorViewportWidget::OnMenuCreateCameraEntityFromCurrentView);
|
||||
|
||||
if (!gameEngine || !gameEngine->IsLevelLoaded())
|
||||
const auto prefabEditorEntityOwnershipInterface = AZ::Interface<AzToolsFramework::PrefabEditorEntityOwnershipInterface>::Get();
|
||||
if (!gameEngine || !gameEngine->IsLevelLoaded() ||
|
||||
(prefabEditorEntityOwnershipInterface && !prefabEditorEntityOwnershipInterface->IsRootPrefabAssigned()))
|
||||
{
|
||||
action->setEnabled(false);
|
||||
action->setToolTip(tr(AZ::ViewportHelpers::TextCantCreateCameraNoLevel));
|
||||
@@ -2636,4 +2641,25 @@ void EditorViewportWidget::StopFullscreenPreview()
|
||||
// Show the main window
|
||||
MainWindow::instance()->show();
|
||||
}
|
||||
|
||||
AZStd::optional<AzFramework::ViewportBorderPadding> EditorViewportWidget::GetViewportBorderPadding() const
|
||||
{
|
||||
if (auto viewportEditorModeTracker = AZ::Interface<AzToolsFramework::ViewportEditorModeTrackerInterface>::Get())
|
||||
{
|
||||
auto viewportEditorModes = viewportEditorModeTracker->GetViewportEditorModes({ AzToolsFramework::GetEntityContextId() });
|
||||
if (viewportEditorModes->IsModeActive(AzToolsFramework::ViewportEditorMode::Focus) ||
|
||||
viewportEditorModes->IsModeActive(AzToolsFramework::ViewportEditorMode::Component))
|
||||
{
|
||||
AzFramework::ViewportBorderPadding viewportBorderPadding = {};
|
||||
viewportBorderPadding.m_top = AzToolsFramework::ViewportUi::ViewportUiTopBorderSize;
|
||||
viewportBorderPadding.m_left = AzToolsFramework::ViewportUi::ViewportUiLeftRightBottomBorderSize;
|
||||
viewportBorderPadding.m_right = AzToolsFramework::ViewportUi::ViewportUiLeftRightBottomBorderSize;
|
||||
viewportBorderPadding.m_bottom = AzToolsFramework::ViewportUi::ViewportUiLeftRightBottomBorderSize;
|
||||
return viewportBorderPadding;
|
||||
}
|
||||
}
|
||||
|
||||
return AZStd::nullopt;
|
||||
}
|
||||
|
||||
#include <moc_EditorViewportWidget.cpp>
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
#include <AzFramework/Windowing/WindowBus.h>
|
||||
#include <AzFramework/Visibility/EntityVisibilityQuery.h>
|
||||
#include <AzFramework/Viewport/ViewportBus.h>
|
||||
|
||||
// forward declarations.
|
||||
class CBaseObject;
|
||||
@@ -86,6 +87,7 @@ AZ_PUSH_DISABLE_DLL_EXPORT_BASECLASS_WARNING
|
||||
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
|
||||
class SANDBOX_API EditorViewportWidget final
|
||||
: public QtViewport
|
||||
, public AzFramework::ViewportBorderRequestBus::Handler
|
||||
, private IEditorNotifyListener
|
||||
, private IUndoManagerListener
|
||||
, private Camera::EditorCameraRequestBus::Handler
|
||||
@@ -120,6 +122,9 @@ public:
|
||||
void SetFOV(float fov) override;
|
||||
float GetFOV() const override;
|
||||
|
||||
// AzFramework::ViewportBorderRequestBus overrides ...
|
||||
AZStd::optional<AzFramework::ViewportBorderPadding> GetViewportBorderPadding() const override;
|
||||
|
||||
private:
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Private types ...
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
#ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
#include <AzFramework/XcbEventHandler.h>
|
||||
#include <AzFramework/XcbConnectionManager.h>
|
||||
#include <qpa/qplatformnativeinterface.h>
|
||||
#endif
|
||||
|
||||
namespace Editor
|
||||
@@ -23,16 +25,34 @@ namespace Editor
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
xcb_connection_t* EditorQtApplicationXcb::GetXcbConnectionFromQt()
|
||||
{
|
||||
QPlatformNativeInterface* native = platformNativeInterface();
|
||||
AZ_Warning("EditorQtApplicationXcb", native, "Unable to retrieve the native platform interface");
|
||||
if (!native)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return reinterpret_cast<xcb_connection_t*>(native->nativeResourceForIntegration(QByteArray("connection")));
|
||||
}
|
||||
|
||||
void EditorQtApplicationXcb::OnStartPlayInEditor()
|
||||
{
|
||||
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
|
||||
interface->SetEnableXInput(GetXcbConnectionFromQt(), true);
|
||||
}
|
||||
|
||||
void EditorQtApplicationXcb::OnStopPlayInEditor()
|
||||
{
|
||||
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
|
||||
interface->SetEnableXInput(GetXcbConnectionFromQt(), false);
|
||||
}
|
||||
|
||||
bool EditorQtApplicationXcb::nativeEventFilter([[maybe_unused]] const QByteArray& eventType, void* message, long*)
|
||||
{
|
||||
if (GetIEditor()->IsInGameMode())
|
||||
{
|
||||
#ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
// We need to handle RAW Input events in a separate loop. This is a workaround to enable XInput2 RAW Inputs using Editor mode.
|
||||
// TODO To have this call here might be not be perfect.
|
||||
AzFramework::XcbEventHandlerBus::Broadcast(&AzFramework::XcbEventHandler::PollSpecialEvents);
|
||||
|
||||
// Now handle the rest of the events.
|
||||
AzFramework::XcbEventHandlerBus::Broadcast(
|
||||
&AzFramework::XcbEventHandler::HandleXcbEvent, static_cast<xcb_generic_event_t*>(message));
|
||||
#endif
|
||||
|
||||
@@ -6,19 +6,35 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <Editor/Core/QtEditorApplication.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
|
||||
#endif
|
||||
|
||||
using xcb_connection_t = struct xcb_connection_t;
|
||||
|
||||
namespace Editor
|
||||
{
|
||||
class EditorQtApplicationXcb : public EditorQtApplication
|
||||
class EditorQtApplicationXcb
|
||||
: public EditorQtApplication
|
||||
, public AzToolsFramework::EditorEntityContextNotificationBus::Handler
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
EditorQtApplicationXcb(int& argc, char** argv)
|
||||
: EditorQtApplication(argc, argv)
|
||||
{
|
||||
// Connect bus to listen for OnStart/StopPlayInEditor events
|
||||
AzToolsFramework::EditorEntityContextNotificationBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
xcb_connection_t* GetXcbConnectionFromQt();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////
|
||||
// AzToolsFramework::EditorEntityContextNotificationBus overrides
|
||||
void OnStartPlayInEditor() override;
|
||||
void OnStopPlayInEditor() override;
|
||||
|
||||
// QAbstractNativeEventFilter:
|
||||
bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override;
|
||||
};
|
||||
|
||||
@@ -214,7 +214,6 @@ namespace AZ
|
||||
// Update old Project path before attempting to merge in new Settings Registry values in order to prevent recursive calls
|
||||
m_oldProjectPath = newProjectPath;
|
||||
|
||||
// Merge the project.json file into settings registry under ProjectSettingsRootKey path.
|
||||
// Update all the runtime file paths based on the new "project_path" value.
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry);
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
|
||||
namespace AZ::Internal
|
||||
{
|
||||
static constexpr const char* ProductCacheDirectoryName = "Cache";
|
||||
|
||||
AZ::SettingsRegistryInterface::FixedValueString GetEngineMonikerForProject(
|
||||
SettingsRegistryInterface& settingsRegistry, const AZ::IO::FixedMaxPath& projectJsonPath)
|
||||
{
|
||||
@@ -228,19 +230,20 @@ namespace AZ::Internal
|
||||
|
||||
namespace AZ::SettingsRegistryMergeUtils
|
||||
{
|
||||
constexpr AZStd::string_view InternalScanUpEngineRootKey{ "/O3DE/Settings/Internal/engine_root_scan_up_path" };
|
||||
constexpr AZStd::string_view InternalScanUpProjectRootKey{ "/O3DE/Settings/Internal/project_root_scan_up_path" };
|
||||
|
||||
AZ::IO::FixedMaxPath FindEngineRoot(SettingsRegistryInterface& settingsRegistry)
|
||||
{
|
||||
static constexpr AZStd::string_view InternalScanUpEngineRootKey{ "/O3DE/Runtime/Internal/engine_root_scan_up_path" };
|
||||
using FixedValueString = SettingsRegistryInterface::FixedValueString;
|
||||
using Type = SettingsRegistryInterface::Type;
|
||||
|
||||
AZ::IO::FixedMaxPath engineRoot;
|
||||
// This is the 'external' engine root key, as in passed from command-line or .setreg files.
|
||||
auto engineRootKey = SettingsRegistryInterface::FixedValueString::format("%s/engine_path", BootstrapSettingsRootKey);
|
||||
constexpr auto engineRootKey = FixedValueString(BootstrapSettingsRootKey) + "/engine_path";
|
||||
|
||||
// Step 1 Run the scan upwards logic once to find the location of the engine.json if it exist
|
||||
// Once this step is run the {InternalScanUpEngineRootKey} is set in the Settings Registry
|
||||
// to have this scan logic only run once InternalScanUpEngineRootKey the supplied registry
|
||||
if (settingsRegistry.GetType(InternalScanUpEngineRootKey) == SettingsRegistryInterface::Type::NoType)
|
||||
if (settingsRegistry.GetType(InternalScanUpEngineRootKey) == Type::NoType)
|
||||
{
|
||||
// We can scan up from exe directory to find engine.json, use that for engine root if it exists.
|
||||
engineRoot = Internal::ScanUpRootLocator("engine.json");
|
||||
@@ -283,14 +286,18 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
|
||||
AZ::IO::FixedMaxPath FindProjectRoot(SettingsRegistryInterface& settingsRegistry)
|
||||
{
|
||||
AZ::IO::FixedMaxPath projectRoot;
|
||||
const auto projectRootKey = SettingsRegistryInterface::FixedValueString::format("%s/project_path", BootstrapSettingsRootKey);
|
||||
static constexpr AZStd::string_view InternalScanUpProjectRootKey{ "/O3DE/Runtime/Internal/project_root_scan_up_path" };
|
||||
using FixedValueString = SettingsRegistryInterface::FixedValueString;
|
||||
using Type = SettingsRegistryInterface::Type;
|
||||
|
||||
// Step 1 Run the scan upwards logic once to find the location of the project.json if it exist
|
||||
AZ::IO::FixedMaxPath projectRoot;
|
||||
constexpr auto projectRootKey = FixedValueString(BootstrapSettingsRootKey) + "/project_path";
|
||||
|
||||
// Step 1 Run the scan upwards logic once to find the location of the closest ancestor project.json
|
||||
// Once this step is run the {InternalScanUpProjectRootKey} is set in the Settings Registry
|
||||
// to have this scan logic only run once for the supplied registry
|
||||
// SettingsRegistryInterface::GetType is used to check if a key is set
|
||||
if (settingsRegistry.GetType(InternalScanUpProjectRootKey) == SettingsRegistryInterface::Type::NoType)
|
||||
if (settingsRegistry.GetType(InternalScanUpProjectRootKey) == Type::NoType)
|
||||
{
|
||||
projectRoot = Internal::ScanUpRootLocator("project.json");
|
||||
// Set the {InternalScanUpProjectRootKey} to make sure this code path isn't called again for this settings registry
|
||||
@@ -305,19 +312,129 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
}
|
||||
|
||||
// Step 2 Check the project-path key
|
||||
// This is the project path root key, as in passed from command-line or .setreg files.
|
||||
if (settingsRegistry.Get(projectRoot.Native(), projectRootKey))
|
||||
// This is the project path root key, as passed from command-line or *.setreg files.
|
||||
settingsRegistry.Get(projectRoot.Native(), projectRootKey);
|
||||
return projectRoot;
|
||||
}
|
||||
|
||||
//! The algorithm that is used to find the project cache is as follows
|
||||
//! 1. The "{BootstrapSettingsRootKey}/project_cache_path" is checked for the path
|
||||
//! 2. Otherwise append the ProductCacheDirectoryName constant to the <project-path>
|
||||
static AZ::IO::FixedMaxPath FindProjectCachePath(SettingsRegistryInterface& settingsRegistry, const AZ::IO::FixedMaxPath& projectPath)
|
||||
{
|
||||
using FixedValueString = SettingsRegistryInterface::FixedValueString;
|
||||
|
||||
constexpr auto projectCachePathKey = FixedValueString(BootstrapSettingsRootKey) + "/project_cache_path";
|
||||
|
||||
// Step 1 Check the project-cache-path key
|
||||
if (AZ::IO::FixedMaxPath projectCachePath; settingsRegistry.Get(projectCachePath.Native(), projectCachePathKey))
|
||||
{
|
||||
return projectRoot;
|
||||
return projectCachePath;
|
||||
}
|
||||
|
||||
// Step 3 Check for a "Cache" directory by scanning upwards from the executable directory
|
||||
if (auto candidateRoot = Internal::ScanUpRootLocator("Cache");
|
||||
!candidateRoot.empty() && AZ::IO::SystemFile::IsDirectory(candidateRoot.c_str()))
|
||||
// Step 2 Append the "Cache" directory to the project-path
|
||||
return projectPath / Internal::ProductCacheDirectoryName;
|
||||
}
|
||||
|
||||
//! Set the user directory with the provided path or using <project-path>/user as default
|
||||
static AZ::IO::FixedMaxPath FindProjectUserPath(SettingsRegistryInterface& settingsRegistry,
|
||||
const AZ::IO::FixedMaxPath& projectPath)
|
||||
{
|
||||
using FixedValueString = SettingsRegistryInterface::FixedValueString;
|
||||
|
||||
// User: root - same as the @user@ alias, this is the starting path for transient data and log files.
|
||||
constexpr auto projectUserPathKey = FixedValueString(BootstrapSettingsRootKey) + "/project_user_path";
|
||||
|
||||
// Step 1 Check the project-user-path key
|
||||
if (AZ::IO::FixedMaxPath projectUserPath; settingsRegistry.Get(projectUserPath.Native(), projectUserPathKey))
|
||||
{
|
||||
projectRoot = AZStd::move(candidateRoot);
|
||||
return projectUserPath;
|
||||
}
|
||||
|
||||
// Step 2 Append the "User" directory to the project-path
|
||||
return projectPath / "user";
|
||||
}
|
||||
|
||||
//! Set the log directory using the settings registry path or using <project-user-path>/log as default
|
||||
static AZ::IO::FixedMaxPath FindProjectLogPath(SettingsRegistryInterface& settingsRegistry,
|
||||
const AZ::IO::FixedMaxPath& projectUserPath)
|
||||
{
|
||||
using FixedValueString = SettingsRegistryInterface::FixedValueString;
|
||||
|
||||
// User: root - same as the @log@ alias, this is the starting path for transient data and log files.
|
||||
constexpr auto projectLogPathKey = FixedValueString(BootstrapSettingsRootKey) + "/project_log_path";
|
||||
|
||||
// Step 1 Check the project-user-path key
|
||||
if (AZ::IO::FixedMaxPath projectLogPath; settingsRegistry.Get(projectLogPath.Native(), projectLogPathKey))
|
||||
{
|
||||
return projectLogPath;
|
||||
}
|
||||
|
||||
// Step 2 Append the "Log" directory to the project-user-path
|
||||
return projectUserPath / "log";
|
||||
}
|
||||
|
||||
// check for a default write storage path, fall back to the <project-user-path> if not
|
||||
static AZ::IO::FixedMaxPath FindDevWriteStoragePath(const AZ::IO::FixedMaxPath& projectUserPath)
|
||||
{
|
||||
AZStd::optional<AZ::IO::FixedMaxPathString> devWriteStorage = Utils::GetDevWriteStoragePath();
|
||||
return devWriteStorage.has_value() ? *devWriteStorage : projectUserPath;
|
||||
}
|
||||
|
||||
// check for the project build path, which is a relative path from the project root
|
||||
// that specifies where the build directory is located
|
||||
static void SetProjectBuildPath(SettingsRegistryInterface& settingsRegistry,
|
||||
const AZ::IO::FixedMaxPath& projectPath)
|
||||
{
|
||||
if (AZ::IO::FixedMaxPath projectBuildPath; settingsRegistry.Get(projectBuildPath.Native(), ProjectBuildPath))
|
||||
{
|
||||
settingsRegistry.Remove(FilePathKey_ProjectBuildPath);
|
||||
settingsRegistry.Remove(FilePathKey_ProjectConfigurationBinPath);
|
||||
AZ::IO::FixedMaxPath buildConfigurationPath = (projectPath / projectBuildPath).LexicallyNormal();
|
||||
if (IO::SystemFile::Exists(buildConfigurationPath.c_str()))
|
||||
{
|
||||
settingsRegistry.Set(FilePathKey_ProjectBuildPath, buildConfigurationPath.Native());
|
||||
}
|
||||
|
||||
// Add the specific build configuration paths to the Settings Registry
|
||||
// First try <project-build-path>/bin/$<CONFIG> and if that path doesn't exist
|
||||
// try <project-build-path>/bin/$<PLATFORM>/$<CONFIG>
|
||||
buildConfigurationPath /= "bin";
|
||||
if (IO::SystemFile::Exists((buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).c_str()))
|
||||
{
|
||||
settingsRegistry.Set(FilePathKey_ProjectConfigurationBinPath,
|
||||
(buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).Native());
|
||||
}
|
||||
else if (IO::SystemFile::Exists((buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).c_str()))
|
||||
{
|
||||
settingsRegistry.Set(FilePathKey_ProjectConfigurationBinPath,
|
||||
(buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).Native());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sets the project name within the Settings Registry by looking up the "project_name"
|
||||
// within the project.json file
|
||||
static void SetProjectName(SettingsRegistryInterface& settingsRegistry,
|
||||
const AZ::IO::FixedMaxPath& projectPath)
|
||||
{
|
||||
using FixedValueString = SettingsRegistryInterface::FixedValueString;
|
||||
// Project name - if it was set via merging project.json use that value, otherwise use the project path's folder name.
|
||||
constexpr auto projectNameKey = FixedValueString(ProjectSettingsRootKey) + "/project_name";
|
||||
|
||||
// Read the project name from the project.json file if it exists
|
||||
if (AZ::IO::FixedMaxPath projectJsonPath = projectPath / "project.json";
|
||||
AZ::IO::SystemFile::Exists(projectJsonPath.c_str()))
|
||||
{
|
||||
settingsRegistry.MergeSettingsFile(projectJsonPath.Native(),
|
||||
AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey);
|
||||
}
|
||||
// If a project name isn't set the default will be set to the final path segment of the project path
|
||||
if (FixedValueString projectName; !settingsRegistry.Get(projectName, projectNameKey))
|
||||
{
|
||||
projectName = projectPath.Filename().Native();
|
||||
settingsRegistry.Set(projectNameKey, projectName);
|
||||
}
|
||||
return projectRoot;
|
||||
}
|
||||
|
||||
AZStd::string_view ConfigParserSettings::DefaultCommentPrefixFilter(AZStd::string_view line)
|
||||
@@ -397,7 +514,7 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
bool MergeSettingsToRegistry_ConfigFile(SettingsRegistryInterface& registry, AZStd::string_view filePath,
|
||||
const ConfigParserSettings& configParserSettings)
|
||||
{
|
||||
auto configPath = FindEngineRoot(registry) / filePath;
|
||||
auto configPath = FindProjectRoot(registry) / filePath;
|
||||
IO::FileReader configFile;
|
||||
bool configFileOpened{};
|
||||
switch (configParserSettings.m_fileReaderClass)
|
||||
@@ -542,19 +659,77 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
void MergeSettingsToRegistry_AddRuntimeFilePaths(SettingsRegistryInterface& registry)
|
||||
{
|
||||
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
|
||||
// Binary folder
|
||||
AZ::IO::FixedMaxPath path = AZ::Utils::GetExecutableDirectory();
|
||||
registry.Set(FilePathKey_BinaryFolder, path.LexicallyNormal().Native());
|
||||
|
||||
// Engine root folder - corresponds to the @engroot@ and @engroot@ aliases
|
||||
AZ::IO::FixedMaxPath engineRoot = FindEngineRoot(registry);
|
||||
registry.Set(FilePathKey_EngineRootFolder, engineRoot.LexicallyNormal().Native());
|
||||
// Binary folder - corresponds to the @exefolder@ alias
|
||||
AZ::IO::FixedMaxPath exePath = AZ::Utils::GetExecutableDirectory();
|
||||
registry.Set(FilePathKey_BinaryFolder, exePath.LexicallyNormal().Native());
|
||||
|
||||
auto projectPathKey = FixedValueString::format("%s/project_path", BootstrapSettingsRootKey);
|
||||
SettingsRegistryInterface::FixedValueString projectPathValue;
|
||||
if (registry.Get(projectPathValue, projectPathKey))
|
||||
// Project path - corresponds to the @projectroot@ alias
|
||||
// NOTE: We make the project-path in the BootstrapSettingsRootKey absolute first
|
||||
|
||||
AZ::IO::FixedMaxPath projectPath = FindProjectRoot(registry);
|
||||
if (constexpr auto projectPathKey = FixedValueString(BootstrapSettingsRootKey) + "/project_path";
|
||||
!projectPath.empty())
|
||||
{
|
||||
// Cache folder
|
||||
if (projectPath.IsRelative())
|
||||
{
|
||||
if (auto projectAbsPath = AZ::Utils::ConvertToAbsolutePath(projectPath.Native());
|
||||
projectAbsPath.has_value())
|
||||
{
|
||||
projectPath = AZStd::move(*projectAbsPath);
|
||||
}
|
||||
}
|
||||
|
||||
projectPath = projectPath.LexicallyNormal();
|
||||
AZ_Warning("SettingsRegistryMergeUtils", AZ::IO::SystemFile::Exists(projectPath.c_str()),
|
||||
R"(Project path "%s" does not exist. Is the "%.*s" registry setting set to a valid absolute path?)"
|
||||
, projectPath.c_str(), AZ_STRING_ARG(projectPathKey));
|
||||
|
||||
registry.Set(FilePathKey_ProjectPath, projectPath.Native());
|
||||
}
|
||||
else
|
||||
{
|
||||
AZ_TracePrintf("SettingsRegistryMergeUtils",
|
||||
R"(Project path isn't set in the Settings Registry at "%.*s".)"
|
||||
" Project-related filepaths will be set relative to the executable directory\n",
|
||||
AZ_STRING_ARG(projectPathKey));
|
||||
registry.Set(FilePathKey_ProjectPath, exePath.Native());
|
||||
}
|
||||
|
||||
// Engine root folder - corresponds to the @engroot@ alias
|
||||
AZ::IO::FixedMaxPath engineRoot = FindEngineRoot(registry);
|
||||
if (!engineRoot.empty())
|
||||
{
|
||||
if (engineRoot.IsRelative())
|
||||
{
|
||||
if (auto engineRootAbsPath = AZ::Utils::ConvertToAbsolutePath(engineRoot.Native());
|
||||
engineRootAbsPath.has_value())
|
||||
{
|
||||
engineRoot = AZStd::move(*engineRootAbsPath);
|
||||
}
|
||||
}
|
||||
|
||||
engineRoot = engineRoot.LexicallyNormal();
|
||||
registry.Set(FilePathKey_EngineRootFolder, engineRoot.Native());
|
||||
}
|
||||
|
||||
// Cache folder
|
||||
AZ::IO::FixedMaxPath projectCachePath = FindProjectCachePath(registry, projectPath).LexicallyNormal();
|
||||
if (!projectCachePath.empty())
|
||||
{
|
||||
if (projectCachePath.IsRelative())
|
||||
{
|
||||
if (auto projectCacheAbsPath = AZ::Utils::ConvertToAbsolutePath(projectCachePath.Native());
|
||||
projectCacheAbsPath.has_value())
|
||||
{
|
||||
projectCachePath = AZStd::move(*projectCacheAbsPath);
|
||||
}
|
||||
}
|
||||
|
||||
projectCachePath = projectCachePath.LexicallyNormal();
|
||||
registry.Set(FilePathKey_CacheProjectRootFolder, projectCachePath.Native());
|
||||
|
||||
// Cache/<asset-platform> folder
|
||||
// Get the name of the asset platform assigned by the bootstrap. First check for platform version such as "windows_assets"
|
||||
// and if that's missing just get "assets".
|
||||
FixedValueString assetPlatform;
|
||||
@@ -570,124 +745,67 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
assetPlatform = AZ::OSPlatformToDefaultAssetPlatform(AZ_TRAIT_OS_PLATFORM_CODENAME);
|
||||
}
|
||||
|
||||
// Project path - corresponds to the @projectroot@ alias
|
||||
// NOTE: Here we append to engineRoot, but if projectPathValue is absolute then engineRoot is discarded.
|
||||
path = engineRoot / projectPathValue;
|
||||
|
||||
AZ_Warning("SettingsRegistryMergeUtils", AZ::IO::SystemFile::Exists(path.c_str()),
|
||||
R"(Project path "%s" does not exist. Is the "%.*s" registry setting set to valid absolute path?)"
|
||||
, path.c_str(), aznumeric_cast<int>(projectPathKey.size()), projectPathKey.data());
|
||||
|
||||
AZ::IO::FixedMaxPath normalizedProjectPath = path.LexicallyNormal();
|
||||
registry.Set(FilePathKey_ProjectPath, normalizedProjectPath.Native());
|
||||
|
||||
// Set the user directory with the provided path or using project/user as default
|
||||
auto projectUserPathKey = FixedValueString::format("%s/project_user_path", BootstrapSettingsRootKey);
|
||||
AZ::IO::FixedMaxPath projectUserPath;
|
||||
if (!registry.Get(projectUserPath.Native(), projectUserPathKey))
|
||||
{
|
||||
projectUserPath = (normalizedProjectPath / "user").LexicallyNormal();
|
||||
}
|
||||
registry.Set(FilePathKey_ProjectUserPath, projectUserPath.Native());
|
||||
|
||||
// Set the log directory with the provided path or using project/user/log as default
|
||||
auto projectLogPathKey = FixedValueString::format("%s/project_log_path", BootstrapSettingsRootKey);
|
||||
AZ::IO::FixedMaxPath projectLogPath;
|
||||
if (!registry.Get(projectLogPath.Native(), projectLogPathKey))
|
||||
{
|
||||
projectLogPath = (projectUserPath / "log").LexicallyNormal();
|
||||
}
|
||||
registry.Set(FilePathKey_ProjectLogPath, projectLogPath.Native());
|
||||
|
||||
// check for a default write storage path, fall back to the project's user/ directory if not
|
||||
AZStd::optional<AZ::IO::FixedMaxPathString> devWriteStorage = Utils::GetDevWriteStoragePath();
|
||||
registry.Set(FilePathKey_DevWriteStorage, devWriteStorage.has_value()
|
||||
? devWriteStorage.value()
|
||||
: projectUserPath.Native());
|
||||
|
||||
// Set the project in-memory build path if the ProjectBuildPath key has been supplied
|
||||
if (AZ::IO::FixedMaxPath projectBuildPath; registry.Get(projectBuildPath.Native(), ProjectBuildPath))
|
||||
{
|
||||
registry.Remove(FilePathKey_ProjectBuildPath);
|
||||
registry.Remove(FilePathKey_ProjectConfigurationBinPath);
|
||||
AZ::IO::FixedMaxPath buildConfigurationPath = normalizedProjectPath / projectBuildPath;
|
||||
if (IO::SystemFile::Exists(buildConfigurationPath.c_str()))
|
||||
{
|
||||
registry.Set(FilePathKey_ProjectBuildPath, buildConfigurationPath.LexicallyNormal().Native());
|
||||
}
|
||||
|
||||
// Add the specific build configuration paths to the Settings Registry
|
||||
// First try <project-build-path>/bin/$<CONFIG> and if that path doesn't exist
|
||||
// try <project-build-path>/bin/$<PLATFORM>/$<CONFIG>
|
||||
buildConfigurationPath /= "bin";
|
||||
if (IO::SystemFile::Exists((buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).c_str()))
|
||||
{
|
||||
registry.Set(FilePathKey_ProjectConfigurationBinPath,
|
||||
(buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE).LexicallyNormal().Native());
|
||||
}
|
||||
else if (IO::SystemFile::Exists((buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).c_str()))
|
||||
{
|
||||
registry.Set(FilePathKey_ProjectConfigurationBinPath,
|
||||
(buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE).LexicallyNormal().Native());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Project name - if it was set via merging project.json use that value, otherwise use the project path's folder name.
|
||||
constexpr auto projectNameKey =
|
||||
FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey)
|
||||
+ "/project_name";
|
||||
|
||||
// Read the project name from the project.json file if it exists
|
||||
if (AZ::IO::FixedMaxPath projectJsonPath = normalizedProjectPath / "project.json";
|
||||
AZ::IO::SystemFile::Exists(projectJsonPath.c_str()))
|
||||
{
|
||||
registry.MergeSettingsFile(projectJsonPath.Native(),
|
||||
AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey);
|
||||
}
|
||||
if (FixedValueString projectName; !registry.Get(projectName, projectNameKey))
|
||||
{
|
||||
projectName = path.Filename().Native();
|
||||
registry.Set(projectNameKey, projectName);
|
||||
}
|
||||
|
||||
// Cache folders - sets up various paths in registry for the cache.
|
||||
// Make sure the asset platform is set before setting these cache paths.
|
||||
// Make sure the asset platform is set before setting cache path for the asset platform.
|
||||
if (!assetPlatform.empty())
|
||||
{
|
||||
// Cache: project root - no corresponding fileIO alias, but this is where the asset database lives.
|
||||
// A registry override is accepted using the "project_cache_path" key.
|
||||
auto projectCacheRootOverrideKey = FixedValueString::format("%s/project_cache_path", BootstrapSettingsRootKey);
|
||||
// Clear path to make sure that the `project_cache_path` value isn't concatenated to the project path
|
||||
path.clear();
|
||||
if (registry.Get(path.Native(), projectCacheRootOverrideKey))
|
||||
{
|
||||
registry.Set(FilePathKey_CacheProjectRootFolder, path.LexicallyNormal().Native());
|
||||
path /= assetPlatform;
|
||||
registry.Set(FilePathKey_CacheRootFolder, path.LexicallyNormal().Native());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cache: root - same as the @products@ alias, this is the starting path for cache files.
|
||||
path = normalizedProjectPath / "Cache";
|
||||
registry.Set(FilePathKey_CacheProjectRootFolder, path.LexicallyNormal().Native());
|
||||
path /= assetPlatform;
|
||||
registry.Set(FilePathKey_CacheRootFolder, path.LexicallyNormal().Native());
|
||||
}
|
||||
registry.Set(FilePathKey_CacheRootFolder, (projectCachePath / assetPlatform).Native());
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
// User folder
|
||||
AZ::IO::FixedMaxPath projectUserPath = FindProjectUserPath(registry, projectPath);
|
||||
if (!projectUserPath.empty())
|
||||
{
|
||||
// Set the default ProjectUserPath to the <engine-root>/user directory
|
||||
registry.Set(FilePathKey_ProjectUserPath, (engineRoot / "user").LexicallyNormal().Native());
|
||||
AZ_TracePrintf("SettingsRegistryMergeUtils",
|
||||
R"(Project path isn't set in the Settings Registry at "%.*s". Project-related filepaths will not be set)" "\n",
|
||||
aznumeric_cast<int>(projectPathKey.size()), projectPathKey.data());
|
||||
if (projectUserPath.IsRelative())
|
||||
{
|
||||
if (auto projectUserAbsPath = AZ::Utils::ConvertToAbsolutePath(projectUserPath.Native());
|
||||
projectUserAbsPath.has_value())
|
||||
{
|
||||
projectUserPath = AZStd::move(*projectUserAbsPath);
|
||||
}
|
||||
}
|
||||
|
||||
projectUserPath = projectUserPath.LexicallyNormal();
|
||||
registry.Set(FilePathKey_ProjectUserPath, projectUserPath.Native());
|
||||
}
|
||||
|
||||
// Log folder
|
||||
if (AZ::IO::FixedMaxPath projectLogPath = FindProjectLogPath(registry, projectUserPath); !projectLogPath.empty())
|
||||
{
|
||||
if (projectLogPath.IsRelative())
|
||||
{
|
||||
if (auto projectLogAbsPath = AZ::Utils::ConvertToAbsolutePath(projectLogPath.Native()))
|
||||
{
|
||||
projectLogPath = AZStd::move(*projectLogAbsPath);
|
||||
}
|
||||
}
|
||||
|
||||
projectLogPath = projectLogPath.LexicallyNormal();
|
||||
registry.Set(FilePathKey_ProjectLogPath, projectLogPath.Native());
|
||||
}
|
||||
|
||||
// Developer Write Storage folder
|
||||
if (AZ::IO::FixedMaxPath devWriteStoragePath = FindDevWriteStoragePath(projectUserPath); !devWriteStoragePath.empty())
|
||||
{
|
||||
if (devWriteStoragePath.IsRelative())
|
||||
{
|
||||
if (auto devWriteStorageAbsPath = AZ::Utils::ConvertToAbsolutePath(devWriteStoragePath.Native()))
|
||||
{
|
||||
devWriteStoragePath = AZStd::move(*devWriteStorageAbsPath);
|
||||
}
|
||||
}
|
||||
|
||||
devWriteStoragePath = devWriteStoragePath.LexicallyNormal();
|
||||
registry.Set(FilePathKey_DevWriteStorage, devWriteStoragePath.Native());
|
||||
}
|
||||
|
||||
// Set the project in-memory build path if the ProjectBuildPath key has been supplied
|
||||
SetProjectBuildPath(registry, projectPath);
|
||||
// Set the project name using the "project_name" key
|
||||
SetProjectName(registry, projectPath);
|
||||
|
||||
#if !AZ_TRAIT_OS_IS_HOST_OS_PLATFORM
|
||||
// Setup the cache, user, and log paths to platform specific locations when running on non-host platforms
|
||||
path = engineRoot;
|
||||
if (AZStd::optional<AZ::IO::FixedMaxPathString> nonHostCacheRoot = Utils::GetDefaultAppRootPath();
|
||||
nonHostCacheRoot)
|
||||
{
|
||||
@@ -696,25 +814,25 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
}
|
||||
else
|
||||
{
|
||||
registry.Set(FilePathKey_CacheProjectRootFolder, path.LexicallyNormal().Native());
|
||||
registry.Set(FilePathKey_CacheRootFolder, path.LexicallyNormal().Native());
|
||||
registry.Set(FilePathKey_CacheProjectRootFolder, projectPath.Native());
|
||||
registry.Set(FilePathKey_CacheRootFolder, projectPath.Native());
|
||||
}
|
||||
if (AZStd::optional<AZ::IO::FixedMaxPathString> devWriteStorage = Utils::GetDevWriteStoragePath();
|
||||
devWriteStorage)
|
||||
{
|
||||
const AZ::IO::FixedMaxPath devWriteStoragePath(*devWriteStorage);
|
||||
registry.Set(FilePathKey_DevWriteStorage, devWriteStoragePath.LexicallyNormal().Native());
|
||||
registry.Set(FilePathKey_ProjectUserPath, (devWriteStoragePath / "user").LexicallyNormal().Native());
|
||||
registry.Set(FilePathKey_ProjectLogPath, (devWriteStoragePath / "user/log").LexicallyNormal().Native());
|
||||
const auto devWriteStoragePath = AZ::IO::PathView(*devWriteStorage).LexicallyNormal();
|
||||
registry.Set(FilePathKey_DevWriteStorage, devWriteStoragePath.Native());
|
||||
registry.Set(FilePathKey_ProjectUserPath, (devWriteStoragePath / "user").Native());
|
||||
registry.Set(FilePathKey_ProjectLogPath, (devWriteStoragePath / "user" / "log").Native());
|
||||
}
|
||||
else
|
||||
{
|
||||
registry.Set(FilePathKey_DevWriteStorage, path.LexicallyNormal().Native());
|
||||
registry.Set(FilePathKey_ProjectUserPath, (path / "user").LexicallyNormal().Native());
|
||||
registry.Set(FilePathKey_ProjectLogPath, (path / "user/log").LexicallyNormal().Native());
|
||||
}
|
||||
#endif // AZ_TRAIT_OS_IS_HOST_OS_PLATFORM
|
||||
registry.Set(FilePathKey_DevWriteStorage, projectPath.Native());
|
||||
registry.Set(FilePathKey_ProjectUserPath, (projectPath / "user").Native());
|
||||
registry.Set(FilePathKey_ProjectLogPath, (projectPath / "user" / "log").Native());
|
||||
}
|
||||
#endif // AZ_TRAIT_OS_IS_HOST_OS_PLATFORM
|
||||
}
|
||||
|
||||
void MergeSettingsToRegistry_TargetBuildDependencyRegistry(SettingsRegistryInterface& registry, const AZStd::string_view platform,
|
||||
const SettingsRegistryInterface::Specializations& specializations, AZStd::vector<char>* scratchBuffer)
|
||||
|
||||
@@ -87,9 +87,9 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
AZ::IO::FixedMaxPath FindEngineRoot(SettingsRegistryInterface& settingsRegistry);
|
||||
|
||||
//! The algorithm that is used to find the project root is as follows
|
||||
//! 1. The first time this function is it performs a upward scan for a project.json file from
|
||||
//! the executable directory and if found stores that path to an internal key.
|
||||
//! In the same step it injects the path into the front of list of command line parameters
|
||||
//! 1. The first time this function runs it performs an upward scan for a "project.json" file from
|
||||
//! the executable directory and stores that path into an internal key.
|
||||
//! In the same step it injects the path into the back of the command line parameters
|
||||
//! using the --regset="{BootstrapSettingsRootKey}/project_path=<path>" value
|
||||
//! 2. Next the "{BootstrapSettingsRootKey}/project_path" is checked to see if it has a project path set
|
||||
//!
|
||||
|
||||
@@ -59,7 +59,7 @@ namespace AZ::Utils
|
||||
{
|
||||
// Fix the size value of the fixed string by calculating the c-string length using char traits
|
||||
absolutePath.resize_no_construct(AZStd::char_traits<char>::length(absolutePath.data()));
|
||||
return srcPath;
|
||||
return absolutePath;
|
||||
}
|
||||
|
||||
return AZStd::nullopt;
|
||||
|
||||
@@ -635,6 +635,7 @@ namespace AZ
|
||||
size_t longestMatch = 0;
|
||||
size_t bufStringLength = inBuffer.size();
|
||||
AZStd::string_view longestAlias;
|
||||
AZStd::string_view longestResolvedAlias;
|
||||
|
||||
for (const auto& [alias, resolvedAlias] : m_aliases)
|
||||
{
|
||||
@@ -653,6 +654,7 @@ namespace AZ
|
||||
{
|
||||
longestMatch = resolvedAlias.size();
|
||||
longestAlias = alias;
|
||||
longestResolvedAlias = resolvedAlias;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -661,7 +663,10 @@ namespace AZ
|
||||
// rearrange the buffer to have
|
||||
// [alias][old path]
|
||||
size_t aliasSize = longestAlias.size();
|
||||
size_t charsToAbsorb = longestMatch;
|
||||
// If the resolved alias ends in a path separator, do not consume it.
|
||||
const bool resolvedAliasEndsInPathSeparator = (longestResolvedAlias.ends_with(AZ::IO::PosixPathSeparator) ||
|
||||
longestResolvedAlias.ends_with(AZ::IO::WindowsPathSeparator));
|
||||
const size_t charsToAbsorb = resolvedAliasEndsInPathSeparator ? longestMatch - 1 : longestMatch;
|
||||
size_t remainingData = bufStringLength - charsToAbsorb;
|
||||
size_t finalStringSize = aliasSize + remainingData;
|
||||
if (finalStringSize >= outBufferLength)
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzFramework/Viewport/ViewportId.h>
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
#include <AzCore/std/optional.h>
|
||||
#include <AzFramework/Viewport/ViewportId.h>
|
||||
|
||||
namespace AZ
|
||||
{
|
||||
@@ -20,18 +21,15 @@ namespace AZ
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class ViewportRequests
|
||||
: public AZ::EBusTraits
|
||||
class ViewportRequests : public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
|
||||
using BusIdType = ViewportId;
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
virtual ~ViewportRequests() {}
|
||||
|
||||
//! Gets the current camera's world to view matrix.
|
||||
virtual const AZ::Matrix4x4& GetCameraViewMatrix() const = 0;
|
||||
//! Sets the current camera's world to view matrix.
|
||||
@@ -44,8 +42,36 @@ namespace AzFramework
|
||||
virtual AZ::Transform GetCameraTransform() const = 0;
|
||||
//! Convenience method, sets the camera's world to view matrix from this AZ::Transform.
|
||||
virtual void SetCameraTransform(const AZ::Transform& transform) = 0;
|
||||
|
||||
protected:
|
||||
~ViewportRequests() = default;
|
||||
};
|
||||
|
||||
using ViewportRequestBus = AZ::EBus<ViewportRequests>;
|
||||
|
||||
} //namespace AzFramework
|
||||
//! The additional padding around the viewport when a viewport border is active.
|
||||
struct ViewportBorderPadding
|
||||
{
|
||||
float m_top;
|
||||
float m_bottom;
|
||||
float m_left;
|
||||
float m_right;
|
||||
};
|
||||
|
||||
//! For performing queries about the state of the viewport border.
|
||||
class ViewportBorderRequests : public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
|
||||
using BusIdType = ViewportId;
|
||||
|
||||
//! Returns if a viewport border is in effect and what the current dimensions (padding) of the border are.
|
||||
virtual AZStd::optional<ViewportBorderPadding> GetViewportBorderPadding() const = 0;
|
||||
|
||||
protected:
|
||||
~ViewportBorderRequests() = default;
|
||||
};
|
||||
|
||||
using ViewportBorderRequestBus = AZ::EBus<ViewportBorderRequests>;
|
||||
} // namespace AzFramework
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include <AzFramework/XcbEventHandler.h>
|
||||
#include <AzFramework/XcbInterface.h>
|
||||
|
||||
#include <xcb/xinput.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -34,6 +36,31 @@ namespace AzFramework
|
||||
return m_xcbConnection.get();
|
||||
}
|
||||
|
||||
void SetEnableXInput(xcb_connection_t* connection, bool enable) override
|
||||
{
|
||||
struct Mask
|
||||
{
|
||||
xcb_input_event_mask_t head;
|
||||
xcb_input_xi_event_mask_t mask;
|
||||
};
|
||||
const Mask mask {
|
||||
/*.head=*/{
|
||||
/*.device_id=*/XCB_INPUT_DEVICE_ALL_MASTER,
|
||||
/*.mask_len=*/1
|
||||
},
|
||||
/*.mask=*/ enable ?
|
||||
(xcb_input_xi_event_mask_t)(XCB_INPUT_XI_EVENT_MASK_RAW_MOTION | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE) :
|
||||
(xcb_input_xi_event_mask_t)XCB_NONE
|
||||
};
|
||||
|
||||
const xcb_setup_t* xcbSetup = xcb_get_setup(connection);
|
||||
const xcb_screen_t* xcbScreen = xcb_setup_roots_iterator(xcbSetup).data;
|
||||
|
||||
xcb_input_xi_select_events(connection, xcbScreen->root, 1, &mask.head);
|
||||
|
||||
xcb_flush(connection);
|
||||
}
|
||||
|
||||
private:
|
||||
XcbUniquePtr<xcb_connection_t, xcb_disconnect> m_xcbConnection = nullptr;
|
||||
};
|
||||
|
||||
@@ -24,6 +24,9 @@ namespace AzFramework
|
||||
virtual ~XcbConnectionManager() = default;
|
||||
|
||||
virtual xcb_connection_t* GetXcbConnection() const = 0;
|
||||
|
||||
//! Enables/Disables XInput Raw Input events.
|
||||
virtual void SetEnableXInput(xcb_connection_t* connection, bool enable) = 0;
|
||||
};
|
||||
|
||||
class XcbConnectionManagerBusTraits
|
||||
|
||||
@@ -23,9 +23,6 @@ namespace AzFramework
|
||||
virtual ~XcbEventHandler() = default;
|
||||
|
||||
virtual void HandleXcbEvent(xcb_generic_event_t* event) = 0;
|
||||
|
||||
// ATTN This is used as a workaround for RAW Input events when using the Editor.
|
||||
virtual void PollSpecialEvents(){};
|
||||
};
|
||||
|
||||
class XcbEventHandlerBusTraits : public AZ::EBusTraits
|
||||
|
||||
+97
-180
@@ -13,21 +13,68 @@
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
xcb_window_t GetSystemCursorFocusWindow()
|
||||
xcb_window_t GetSystemCursorFocusWindow(xcb_connection_t* connection)
|
||||
{
|
||||
void* systemCursorFocusWindow = nullptr;
|
||||
AzFramework::InputSystemCursorConstraintRequestBus::BroadcastResult(
|
||||
systemCursorFocusWindow, &AzFramework::InputSystemCursorConstraintRequests::GetSystemCursorConstraintWindow);
|
||||
|
||||
if (!systemCursorFocusWindow)
|
||||
if (systemCursorFocusWindow)
|
||||
{
|
||||
return XCB_NONE;
|
||||
return static_cast<xcb_window_t>(reinterpret_cast<uint64_t>(systemCursorFocusWindow));
|
||||
}
|
||||
|
||||
// TODO Clang compile error because cast .... loses information. On GNU/Linux HWND is void* and on 64-bit
|
||||
// machines its obviously 64 bit but we receive the window id from m_renderOverlay.winId() which is xcb_window_t 32-bit.
|
||||
// EWMH-compliant window managers set the "_NET_ACTIVE_WINDOW" property
|
||||
// of the X server's root window to the currently active window. This
|
||||
// retrieves value of that property.
|
||||
|
||||
return static_cast<xcb_window_t>(reinterpret_cast<uint64_t>(systemCursorFocusWindow));
|
||||
// Get the atom for the _NET_ACTIVE_WINDOW property
|
||||
constexpr int propertyNameLength = 18;
|
||||
xcb_generic_error_t* error = nullptr;
|
||||
XcbStdFreePtr<xcb_intern_atom_reply_t> activeWindowAtom {xcb_intern_atom_reply(
|
||||
connection,
|
||||
xcb_intern_atom(connection, /*only_if_exists=*/ 1, propertyNameLength, "_NET_ACTIVE_WINDOW"),
|
||||
&error
|
||||
)};
|
||||
if (!activeWindowAtom || error)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "Retrieving _NET_ACTIVE_WINDOW atom failed : Error code %d", error->error_code);
|
||||
free(error);
|
||||
}
|
||||
return XCB_WINDOW_NONE;
|
||||
}
|
||||
|
||||
// Get the root window
|
||||
const xcb_window_t rootWId = xcb_setup_roots_iterator(xcb_get_setup(connection)).data->root;
|
||||
|
||||
// Fetch the value of the root window's _NET_ACTIVE_WINDOW property
|
||||
XcbStdFreePtr<xcb_get_property_reply_t> property {xcb_get_property_reply(
|
||||
connection,
|
||||
xcb_get_property(
|
||||
/*c=*/connection,
|
||||
/*_delete=*/ 0,
|
||||
/*window=*/rootWId,
|
||||
/*property=*/activeWindowAtom->atom,
|
||||
/*type=*/XCB_ATOM_WINDOW,
|
||||
/*long_offset=*/0,
|
||||
/*long_length=*/1
|
||||
),
|
||||
&error
|
||||
)};
|
||||
|
||||
if (!property || error)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "Retrieving _NET_ACTIVE_WINDOW atom failed : Error code %d", error->error_code);
|
||||
free(error);
|
||||
}
|
||||
return XCB_WINDOW_NONE;
|
||||
}
|
||||
|
||||
return *static_cast<xcb_window_t*>(xcb_get_property_value(property.get()));
|
||||
}
|
||||
|
||||
xcb_connection_t* XcbInputDeviceMouse::s_xcbConnection = nullptr;
|
||||
@@ -39,8 +86,7 @@ namespace AzFramework
|
||||
: InputDeviceMouse::Implementation(inputDevice)
|
||||
, m_systemCursorState(SystemCursorState::Unknown)
|
||||
, m_systemCursorPositionNormalized(0.5f, 0.5f)
|
||||
, m_prevConstraintWindow(XCB_NONE)
|
||||
, m_focusWindow(XCB_NONE)
|
||||
, m_focusWindow(XCB_WINDOW_NONE)
|
||||
, m_cursorShown(true)
|
||||
{
|
||||
XcbEventHandlerBus::Handler::BusConnect();
|
||||
@@ -57,14 +103,14 @@ namespace AzFramework
|
||||
|
||||
InputDeviceMouse::Implementation* XcbInputDeviceMouse::Create(InputDeviceMouse& inputDevice)
|
||||
{
|
||||
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
|
||||
const auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
|
||||
if (!interface)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "XCB interface not available");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
s_xcbConnection = AzFramework::XcbConnectionManagerInterface::Get()->GetXcbConnection();
|
||||
s_xcbConnection = interface->GetXcbConnection();
|
||||
if (!s_xcbConnection)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "XCB connection not available");
|
||||
@@ -126,7 +172,7 @@ namespace AzFramework
|
||||
|
||||
// Get window information.
|
||||
const XcbStdFreePtr<xcb_get_geometry_reply_t> xcbGeometryReply{ xcb_get_geometry_reply(
|
||||
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
|
||||
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) };
|
||||
|
||||
if (!xcbGeometryReply)
|
||||
{
|
||||
@@ -137,7 +183,7 @@ namespace AzFramework
|
||||
xcb_translate_coordinates(s_xcbConnection, window, s_xcbScreen->root, 0, 0);
|
||||
|
||||
const XcbStdFreePtr<xcb_translate_coordinates_reply_t> xkbTranslateCoordReply{ xcb_translate_coordinates_reply(
|
||||
s_xcbConnection, translate_coord, NULL) };
|
||||
s_xcbConnection, translate_coord, nullptr) };
|
||||
|
||||
if (!xkbTranslateCoordReply)
|
||||
{
|
||||
@@ -173,11 +219,11 @@ namespace AzFramework
|
||||
for (const auto& barrier : m_activeBarriers)
|
||||
{
|
||||
xcb_void_cookie_t cookie = xcb_xfixes_create_pointer_barrier_checked(
|
||||
s_xcbConnection, barrier.id, window, barrier.x0, barrier.y0, barrier.x1, barrier.y1, barrier.direction, 0, NULL);
|
||||
const XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(s_xcbConnection, cookie) };
|
||||
s_xcbConnection, barrier.id, window, barrier.x0, barrier.y0, barrier.x1, barrier.y1, barrier.direction, 0, nullptr);
|
||||
const XcbStdFreePtr<xcb_generic_error_t> xcbError{ xcb_request_check(s_xcbConnection, cookie) };
|
||||
|
||||
AZ_Warning(
|
||||
"XcbInput", !xkbError, "XFixes, failed to create barrier %d at (%d %d %d %d)", barrier.id, barrier.x0, barrier.y0,
|
||||
"XcbInput", !xcbError, "XFixes, failed to create barrier %d at (%d %d %d %d)", barrier.id, barrier.x0, barrier.y0,
|
||||
barrier.x1, barrier.y1);
|
||||
}
|
||||
}
|
||||
@@ -207,7 +253,7 @@ namespace AzFramework
|
||||
|
||||
const xcb_xfixes_query_version_cookie_t query_cookie = xcb_xfixes_query_version(s_xcbConnection, 5, 0);
|
||||
|
||||
xcb_generic_error_t* error = NULL;
|
||||
xcb_generic_error_t* error = nullptr;
|
||||
const XcbStdFreePtr<xcb_xfixes_query_version_reply_t> xkbQueryRequestReply{ xcb_xfixes_query_version_reply(
|
||||
s_xcbConnection, query_cookie, &error) };
|
||||
|
||||
@@ -244,7 +290,7 @@ namespace AzFramework
|
||||
|
||||
const xcb_input_xi_query_version_cookie_t query_version_cookie = xcb_input_xi_query_version(s_xcbConnection, 2, 2);
|
||||
|
||||
xcb_generic_error_t* error = NULL;
|
||||
xcb_generic_error_t* error = nullptr;
|
||||
const XcbStdFreePtr<xcb_input_xi_query_version_reply_t> xkbQueryRequestReply{ xcb_input_xi_query_version_reply(
|
||||
s_xcbConnection, query_version_cookie, &error) };
|
||||
|
||||
@@ -268,40 +314,13 @@ namespace AzFramework
|
||||
return m_xInputInitialized;
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::SetEnableXInput(bool enable)
|
||||
{
|
||||
struct
|
||||
{
|
||||
xcb_input_event_mask_t head;
|
||||
int mask;
|
||||
} mask;
|
||||
|
||||
mask.head.deviceid = XCB_INPUT_DEVICE_ALL;
|
||||
mask.head.mask_len = 1;
|
||||
|
||||
if (enable)
|
||||
{
|
||||
mask.mask = XCB_INPUT_XI_EVENT_MASK_RAW_MOTION | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS |
|
||||
XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE | XCB_INPUT_XI_EVENT_MASK_MOTION | XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS |
|
||||
XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE;
|
||||
}
|
||||
else
|
||||
{
|
||||
mask.mask = XCB_NONE;
|
||||
}
|
||||
|
||||
xcb_input_xi_select_events(s_xcbConnection, s_xcbScreen->root, 1, &mask.head);
|
||||
|
||||
xcb_flush(s_xcbConnection);
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::SetSystemCursorState(SystemCursorState systemCursorState)
|
||||
{
|
||||
if (systemCursorState != m_systemCursorState)
|
||||
{
|
||||
m_systemCursorState = systemCursorState;
|
||||
|
||||
m_focusWindow = GetSystemCursorFocusWindow();
|
||||
m_focusWindow = GetSystemCursorFocusWindow(s_xcbConnection);
|
||||
|
||||
HandleCursorState(m_focusWindow, systemCursorState);
|
||||
}
|
||||
@@ -309,52 +328,10 @@ namespace AzFramework
|
||||
|
||||
void XcbInputDeviceMouse::HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState)
|
||||
{
|
||||
bool confined = false, cursorShown = true;
|
||||
switch (systemCursorState)
|
||||
{
|
||||
case SystemCursorState::ConstrainedAndHidden:
|
||||
{
|
||||
//!< Constrained to the application's main window and hidden
|
||||
confined = true;
|
||||
cursorShown = false;
|
||||
}
|
||||
break;
|
||||
case SystemCursorState::ConstrainedAndVisible:
|
||||
{
|
||||
//!< Constrained to the application's main window and visible
|
||||
confined = true;
|
||||
}
|
||||
break;
|
||||
case SystemCursorState::UnconstrainedAndHidden:
|
||||
{
|
||||
//!< Free to move outside the main window but hidden while inside
|
||||
cursorShown = false;
|
||||
}
|
||||
break;
|
||||
case SystemCursorState::UnconstrainedAndVisible:
|
||||
{
|
||||
//!< Free to move outside the application's main window and visible
|
||||
}
|
||||
case SystemCursorState::Unknown:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// ATTN GetSystemCursorFocusWindow when getting out of the play in editor will return XCB_NONE
|
||||
// We need however the window id to reset the cursor.
|
||||
if (XCB_NONE == window && (confined || cursorShown))
|
||||
{
|
||||
// Reuse the previous window to reset states.
|
||||
window = m_prevConstraintWindow;
|
||||
m_prevConstraintWindow = XCB_NONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remember the window we used to modify cursor and barrier states.
|
||||
m_prevConstraintWindow = window;
|
||||
}
|
||||
|
||||
SetEnableXInput(!cursorShown);
|
||||
const bool confined = (systemCursorState == SystemCursorState::ConstrainedAndHidden) ||
|
||||
(systemCursorState == SystemCursorState::ConstrainedAndVisible);
|
||||
const bool cursorShown = (systemCursorState == SystemCursorState::ConstrainedAndVisible) ||
|
||||
(systemCursorState == SystemCursorState::UnconstrainedAndVisible);
|
||||
|
||||
CreateBarriers(window, confined);
|
||||
ShowCursor(window, cursorShown);
|
||||
@@ -368,26 +345,26 @@ namespace AzFramework
|
||||
void XcbInputDeviceMouse::SetSystemCursorPositionNormalizedInternal(xcb_window_t window, AZ::Vector2 positionNormalized)
|
||||
{
|
||||
// TODO Basically not done at all. Added only the basic functions needed.
|
||||
const XcbStdFreePtr<xcb_get_geometry_reply_t> xkbGeometryReply{ xcb_get_geometry_reply(
|
||||
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
|
||||
const XcbStdFreePtr<xcb_get_geometry_reply_t> xcbGeometryReply{ xcb_get_geometry_reply(
|
||||
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) };
|
||||
|
||||
if (!xkbGeometryReply)
|
||||
if (!xcbGeometryReply)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int16_t x = static_cast<int16_t>(positionNormalized.GetX() * xkbGeometryReply->width);
|
||||
const int16_t y = static_cast<int16_t>(positionNormalized.GetY() * xkbGeometryReply->height);
|
||||
const int16_t x = static_cast<int16_t>(positionNormalized.GetX() * xcbGeometryReply->width);
|
||||
const int16_t y = static_cast<int16_t>(positionNormalized.GetY() * xcbGeometryReply->height);
|
||||
|
||||
xcb_warp_pointer(s_xcbConnection, XCB_NONE, window, 0, 0, 0, 0, x, y);
|
||||
xcb_warp_pointer(s_xcbConnection, XCB_WINDOW_NONE, window, 0, 0, 0, 0, x, y);
|
||||
|
||||
xcb_flush(s_xcbConnection);
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized)
|
||||
{
|
||||
const xcb_window_t window = GetSystemCursorFocusWindow();
|
||||
if (XCB_NONE == window)
|
||||
const xcb_window_t window = GetSystemCursorFocusWindow(s_xcbConnection);
|
||||
if (XCB_WINDOW_NONE == window)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -401,7 +378,7 @@ namespace AzFramework
|
||||
|
||||
const xcb_query_pointer_cookie_t pointer = xcb_query_pointer(s_xcbConnection, window);
|
||||
|
||||
const XcbStdFreePtr<xcb_query_pointer_reply_t> xkbQueryPointerReply{ xcb_query_pointer_reply(s_xcbConnection, pointer, NULL) };
|
||||
const XcbStdFreePtr<xcb_query_pointer_reply_t> xkbQueryPointerReply{ xcb_query_pointer_reply(s_xcbConnection, pointer, nullptr) };
|
||||
|
||||
if (!xkbQueryPointerReply)
|
||||
{
|
||||
@@ -409,7 +386,7 @@ namespace AzFramework
|
||||
}
|
||||
|
||||
const XcbStdFreePtr<xcb_get_geometry_reply_t> xkbGeometryReply{ xcb_get_geometry_reply(
|
||||
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
|
||||
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), nullptr) };
|
||||
|
||||
if (!xkbGeometryReply)
|
||||
{
|
||||
@@ -429,8 +406,8 @@ namespace AzFramework
|
||||
|
||||
AZ::Vector2 XcbInputDeviceMouse::GetSystemCursorPositionNormalized() const
|
||||
{
|
||||
const xcb_window_t window = GetSystemCursorFocusWindow();
|
||||
if (XCB_NONE == window)
|
||||
const xcb_window_t window = GetSystemCursorFocusWindow(s_xcbConnection);
|
||||
if (XCB_WINDOW_NONE == window)
|
||||
{
|
||||
return AZ::Vector2::CreateZero();
|
||||
}
|
||||
@@ -455,11 +432,11 @@ namespace AzFramework
|
||||
cookie = xcb_xfixes_hide_cursor_checked(s_xcbConnection, window);
|
||||
}
|
||||
|
||||
const XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(s_xcbConnection, cookie) };
|
||||
const XcbStdFreePtr<xcb_generic_error_t> xcbError{ xcb_request_check(s_xcbConnection, cookie) };
|
||||
|
||||
if (xkbError)
|
||||
if (xcbError)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "ShowCursor failed: %d", xkbError->error_code);
|
||||
AZ_Warning("XcbInput", false, "ShowCursor failed: %d", xcbError->error_code);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -500,14 +477,6 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::HandlePointerMotionEvents(const xcb_generic_event_t* event)
|
||||
{
|
||||
const xcb_input_motion_event_t* mouseMotionEvent = reinterpret_cast<const xcb_input_motion_event_t*>(event);
|
||||
|
||||
m_systemCursorPosition[0] = mouseMotionEvent->event_x;
|
||||
m_systemCursorPosition[1] = mouseMotionEvent->event_y;
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::HandleRawInputEvents(const xcb_ge_generic_event_t* event)
|
||||
{
|
||||
const xcb_ge_generic_event_t* genericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(event);
|
||||
@@ -552,78 +521,20 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::PollSpecialEvents()
|
||||
{
|
||||
while (xcb_generic_event_t* genericEvent = xcb_poll_for_queued_event(s_xcbConnection))
|
||||
{
|
||||
// TODO Is the following correct? If we are showing the cursor, don't poll RAW Input events.
|
||||
switch (genericEvent->response_type & ~0x80)
|
||||
{
|
||||
case XCB_GE_GENERIC:
|
||||
{
|
||||
const xcb_ge_generic_event_t* geGenericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(genericEvent);
|
||||
|
||||
// Only handle raw inputs if we have focus.
|
||||
// Handle Raw Input events first.
|
||||
if ((geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) ||
|
||||
(geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) ||
|
||||
(geGenericEvent->event_type == XCB_INPUT_RAW_MOTION))
|
||||
{
|
||||
HandleRawInputEvents(geGenericEvent);
|
||||
|
||||
free(genericEvent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::HandleXcbEvent(xcb_generic_event_t* event)
|
||||
{
|
||||
switch (event->response_type & ~0x80)
|
||||
{
|
||||
// QT5 is using by default XInput which means we do need to check for XCB_GE_GENERIC event to parse all mouse related events.
|
||||
// XInput raw events are sent from the server as a XCB_GE_GENERIC
|
||||
// event. A XCB_GE_GENERIC event is typecast to a
|
||||
// xcb_ge_generic_event_t, which is distinct from a
|
||||
// xcb_generic_event_t, and exists so that X11 extensions can extend
|
||||
// the event emission beyond the size that a normal X11 event could
|
||||
// contain.
|
||||
case XCB_GE_GENERIC:
|
||||
{
|
||||
const xcb_ge_generic_event_t* genericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(event);
|
||||
|
||||
// Handling RAW Inputs here works in GameMode but not in Editor mode because QT is
|
||||
// not handling RAW input events and passing to.
|
||||
if (!m_cursorShown)
|
||||
{
|
||||
// Handle Raw Input events first.
|
||||
if ((genericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) ||
|
||||
(genericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) || (genericEvent->event_type == XCB_INPUT_RAW_MOTION))
|
||||
{
|
||||
HandleRawInputEvents(genericEvent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (genericEvent->event_type)
|
||||
{
|
||||
case XCB_INPUT_BUTTON_PRESS:
|
||||
{
|
||||
const xcb_input_button_press_event_t* mouseButtonEvent =
|
||||
reinterpret_cast<const xcb_input_button_press_event_t*>(genericEvent);
|
||||
HandleButtonPressEvents(mouseButtonEvent->detail, true);
|
||||
}
|
||||
break;
|
||||
case XCB_INPUT_BUTTON_RELEASE:
|
||||
{
|
||||
const xcb_input_button_release_event_t* mouseButtonEvent =
|
||||
reinterpret_cast<const xcb_input_button_release_event_t*>(genericEvent);
|
||||
HandleButtonPressEvents(mouseButtonEvent->detail, false);
|
||||
}
|
||||
break;
|
||||
case XCB_INPUT_MOTION:
|
||||
{
|
||||
HandlePointerMotionEvents(event);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
HandleRawInputEvents(genericEvent);
|
||||
}
|
||||
break;
|
||||
case XCB_FOCUS_IN:
|
||||
@@ -634,6 +545,9 @@ namespace AzFramework
|
||||
m_focusWindow = focusInEvent->event;
|
||||
HandleCursorState(m_focusWindow, m_systemCursorState);
|
||||
}
|
||||
|
||||
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
|
||||
interface->SetEnableXInput(interface->GetXcbConnection(), true);
|
||||
}
|
||||
break;
|
||||
case XCB_FOCUS_OUT:
|
||||
@@ -644,7 +558,10 @@ namespace AzFramework
|
||||
ProcessRawEventQueues();
|
||||
ResetInputChannelStates();
|
||||
|
||||
m_focusWindow = XCB_NONE;
|
||||
m_focusWindow = XCB_WINDOW_NONE;
|
||||
|
||||
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
|
||||
interface->SetEnableXInput(interface->GetXcbConnection(), false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -65,9 +65,6 @@ namespace AzFramework
|
||||
//! \ref AzFramework::InputDeviceMouse::Implementation::TickInputDevice
|
||||
void TickInputDevice() override;
|
||||
|
||||
//! This method is called by the Editor to accommodate some events with the Editor. Never called in Game mode.
|
||||
void PollSpecialEvents() override;
|
||||
|
||||
//! Handle X11 events.
|
||||
void HandleXcbEvent(xcb_generic_event_t* event) override;
|
||||
|
||||
@@ -77,9 +74,6 @@ namespace AzFramework
|
||||
//! Initialize XInput extension. Used for raw input during confinement and showing/hiding the cursor.
|
||||
static bool InitializeXInput();
|
||||
|
||||
//! Enables/Disables XInput Raw Input events.
|
||||
void SetEnableXInput(bool enable);
|
||||
|
||||
//! Create barriers.
|
||||
void CreateBarriers(xcb_window_t window, bool create);
|
||||
|
||||
@@ -98,9 +92,6 @@ namespace AzFramework
|
||||
//! Handle button press/release events.
|
||||
void HandleButtonPressEvents(uint32_t detail, bool pressed);
|
||||
|
||||
//! Handle motion notify events.
|
||||
void HandlePointerMotionEvents(const xcb_generic_event_t* event);
|
||||
|
||||
//! Will set cursor states and confinement modes.
|
||||
void HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState);
|
||||
|
||||
@@ -160,7 +151,6 @@ namespace AzFramework
|
||||
AZ::Vector2 m_cursorHiddenPosition;
|
||||
|
||||
AZ::Vector2 m_systemCursorPositionNormalized;
|
||||
uint32_t m_systemCursorPosition[MAX_XI_RAW_AXIS];
|
||||
|
||||
static xcb_connection_t* s_xcbConnection;
|
||||
static xcb_screen_t* s_xcbScreen;
|
||||
@@ -171,9 +161,6 @@ namespace AzFramework
|
||||
//! Will be true if the xinput2 extension could be initialized.
|
||||
static bool m_xInputInitialized;
|
||||
|
||||
//! The window that had focus
|
||||
xcb_window_t m_prevConstraintWindow;
|
||||
|
||||
//! The current window that has focus
|
||||
xcb_window_t m_focusWindow;
|
||||
|
||||
|
||||
+74
-39
@@ -9,19 +9,71 @@
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/IO/SystemFile.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/Console/IConsole.h>
|
||||
#include <AzCore/std/containers/array.h>
|
||||
#include <AzCore/std/tuple.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <cerrno>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
AZ_CVAR(bool, ap_tether_lifetime, true, nullptr, AZ::ConsoleFunctorFlags::Null,
|
||||
"If enabled, a parent process that launches the AP will terminate the AP on exit");
|
||||
|
||||
namespace AzFramework::AssetSystem::Platform
|
||||
{
|
||||
void AllowAssetProcessorToForeground()
|
||||
{}
|
||||
|
||||
[[noreturn]] static void LaunchAssetProcessorDirectly(const AZ::IO::FixedMaxPath& assetProcessorPath, AZStd::string_view engineRoot, AZStd::string_view projectPath)
|
||||
{
|
||||
AZStd::fixed_vector<const char*, 5> args {
|
||||
assetProcessorPath.c_str(),
|
||||
"--start-hidden",
|
||||
};
|
||||
|
||||
// Add the engine path to the launch command if not empty
|
||||
AZ::IO::FixedMaxPathString engineRootArg;
|
||||
if (!engineRoot.empty())
|
||||
{
|
||||
// No need to quote these paths, this code calls exec directly and
|
||||
// does not go through shell string interpolation
|
||||
engineRootArg = AZ::IO::FixedMaxPathString{"--engine-path="} + AZ::IO::FixedMaxPathString{engineRoot};
|
||||
args.push_back(engineRootArg.data());
|
||||
}
|
||||
|
||||
// Add the active project path to the launch command if not empty
|
||||
AZ::IO::FixedMaxPathString projectPathArg;
|
||||
if (!projectPath.empty())
|
||||
{
|
||||
projectPathArg = AZ::IO::FixedMaxPathString{"--regset=/Amazon/AzCore/Bootstrap/project_path="} + AZ::IO::FixedMaxPathString{projectPath};
|
||||
args.push_back(projectPathArg.data());
|
||||
}
|
||||
|
||||
// Make sure this is at the end
|
||||
args.push_back(nullptr); // argv itself needs to be null-terminated
|
||||
|
||||
execv(args[0], const_cast<char**>(args.data()));
|
||||
|
||||
// exec* family of functions only return on error
|
||||
fprintf(stderr, "Asset Processor failed with error: %s\n", strerror(errno));
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
static pid_t LaunchAssetProcessorDaemonized(const AZ::IO::FixedMaxPath& assetProcessorPath, AZStd::string_view engineRoot, AZStd::string_view projectPath)
|
||||
{
|
||||
// detach the child from parent
|
||||
setsid();
|
||||
const pid_t secondChildPid = fork();
|
||||
if (secondChildPid == 0)
|
||||
{
|
||||
LaunchAssetProcessorDirectly(assetProcessorPath, engineRoot, projectPath);
|
||||
}
|
||||
return secondChildPid;
|
||||
}
|
||||
|
||||
bool LaunchAssetProcessor(AZStd::string_view executableDirectory, AZStd::string_view engineRoot,
|
||||
AZStd::string_view projectPath)
|
||||
{
|
||||
@@ -40,7 +92,8 @@ namespace AzFramework::AssetSystem::Platform
|
||||
}
|
||||
}
|
||||
|
||||
pid_t firstChildPid = fork();
|
||||
const pid_t parentPid = getpid();
|
||||
const pid_t firstChildPid = fork();
|
||||
if (firstChildPid == 0)
|
||||
{
|
||||
// redirect output to dev/null so it doesn't hijack an existing console window
|
||||
@@ -53,51 +106,33 @@ namespace AzFramework::AssetSystem::Platform
|
||||
AZ::IO::FileDescriptorRedirector stderrRedirect(STDERR_FILENO);
|
||||
stderrRedirect.RedirectTo(devNull, mode);
|
||||
|
||||
// detach the child from parent
|
||||
setsid();
|
||||
pid_t secondChildPid = fork();
|
||||
if (secondChildPid == 0)
|
||||
if (ap_tether_lifetime)
|
||||
{
|
||||
AZStd::array args {
|
||||
assetProcessorPath.c_str(), assetProcessorPath.c_str(), "--start-hidden",
|
||||
static_cast<const char*>(nullptr), static_cast<const char*>(nullptr), static_cast<const char*>(nullptr)
|
||||
};
|
||||
int optionalArgPos = 3;
|
||||
|
||||
// Add the engine path to the launch command if not empty
|
||||
AZ::IO::FixedMaxPathString engineRootArg;
|
||||
if (!engineRoot.empty())
|
||||
prctl(PR_SET_PDEATHSIG, SIGTERM);
|
||||
if (getppid() != parentPid)
|
||||
{
|
||||
engineRootArg = AZ::IO::FixedMaxPathString::format(R"(--engine-path="%.*s")",
|
||||
aznumeric_cast<int>(engineRoot.size()), engineRoot.data());
|
||||
args[optionalArgPos++] = engineRootArg.data();
|
||||
_exit(1);
|
||||
}
|
||||
LaunchAssetProcessorDirectly(assetProcessorPath, engineRoot, projectPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
const pid_t secondChildPid = LaunchAssetProcessorDaemonized(assetProcessorPath, engineRoot, projectPath);
|
||||
stdoutRedirect.Reset();
|
||||
stderrRedirect.Reset();
|
||||
|
||||
// Add the active project path to the launch command if not empty
|
||||
AZ::IO::FixedMaxPathString projectPathArg;
|
||||
if (!projectPath.empty())
|
||||
{
|
||||
projectPathArg = AZ::IO::FixedMaxPathString::format(R"(--regset="/Amazon/AzCore/Bootstrap/project_path=%.*s")",
|
||||
aznumeric_cast<int>(projectPath.size()), projectPath.data());
|
||||
args[optionalArgPos++] = projectPathArg.data();
|
||||
}
|
||||
|
||||
AZStd::apply(execl, args);
|
||||
|
||||
// exec* family of functions only exit on error
|
||||
AZ_Error("AssetSystemComponent", false, "Asset Processor failed with error: %s", strerror(errno));
|
||||
_exit(1);
|
||||
// exit the transient child with proper return code
|
||||
int ret = (secondChildPid < 0) ? 1 : 0;
|
||||
_exit(ret);
|
||||
}
|
||||
|
||||
stdoutRedirect.Reset();
|
||||
stderrRedirect.Reset();
|
||||
|
||||
// exit the transient child with proper return code
|
||||
int ret = (secondChildPid < 0) ? 1 : 0;
|
||||
_exit(ret);
|
||||
}
|
||||
else if (firstChildPid > 0)
|
||||
{
|
||||
if (ap_tether_lifetime)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// wait for first child to exit to ensure the second child was started
|
||||
int status = 0;
|
||||
pid_t ret = waitpid(firstChildPid, &status, 0);
|
||||
@@ -106,4 +141,4 @@ namespace AzFramework::AssetSystem::Platform
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} // namespace AzFramework::AssetSystem::Platform
|
||||
|
||||
@@ -41,7 +41,9 @@ namespace UnitTest
|
||||
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::IO::FixedMaxPath enginePath;
|
||||
registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_application->Start({});
|
||||
|
||||
@@ -45,7 +45,9 @@ namespace UnitTest
|
||||
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::IO::FixedMaxPath enginePath;
|
||||
registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_application->Start({});
|
||||
|
||||
@@ -305,7 +305,9 @@ namespace UnitTest
|
||||
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::IO::FixedMaxPath enginePath;
|
||||
registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
AZ::ComponentApplication::StartupParameters startupParameters;
|
||||
|
||||
@@ -11,6 +11,13 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
ACTION_TEMPLATE(ReturnMalloc,
|
||||
HAS_1_TEMPLATE_PARAMS(typename, T),
|
||||
AND_0_VALUE_PARAMS()) {
|
||||
T* value = static_cast<T*>(malloc(sizeof(T)));
|
||||
*value = T{};
|
||||
return value;
|
||||
}
|
||||
ACTION_TEMPLATE(ReturnMalloc,
|
||||
HAS_1_TEMPLATE_PARAMS(typename, T),
|
||||
AND_1_VALUE_PARAMS(p0)) {
|
||||
@@ -25,3 +32,38 @@ ACTION_TEMPLATE(ReturnMalloc,
|
||||
*value = T{ p0, p1 };
|
||||
return value;
|
||||
}
|
||||
ACTION_TEMPLATE(ReturnMalloc,
|
||||
HAS_1_TEMPLATE_PARAMS(typename, T),
|
||||
AND_3_VALUE_PARAMS(p0, p1, p2)) {
|
||||
T* value = static_cast<T*>(malloc(sizeof(T)));
|
||||
*value = T{ p0, p1, p2 };
|
||||
return value;
|
||||
}
|
||||
ACTION_TEMPLATE(ReturnMalloc,
|
||||
HAS_1_TEMPLATE_PARAMS(typename, T),
|
||||
AND_4_VALUE_PARAMS(p0, p1, p2, p3)) {
|
||||
T* value = static_cast<T*>(malloc(sizeof(T)));
|
||||
*value = T{ p0, p1, p2, p3 };
|
||||
return value;
|
||||
}
|
||||
ACTION_TEMPLATE(ReturnMalloc,
|
||||
HAS_1_TEMPLATE_PARAMS(typename, T),
|
||||
AND_5_VALUE_PARAMS(p0, p1, p2, p3, p4)) {
|
||||
T* value = static_cast<T*>(malloc(sizeof(T)));
|
||||
*value = T{ p0, p1, p2, p3, p4 };
|
||||
return value;
|
||||
}
|
||||
ACTION_TEMPLATE(ReturnMalloc,
|
||||
HAS_1_TEMPLATE_PARAMS(typename, T),
|
||||
AND_6_VALUE_PARAMS(p0, p1, p2, p3, p4, p5)) {
|
||||
T* value = static_cast<T*>(malloc(sizeof(T)));
|
||||
*value = T{ p0, p1, p2, p3, p4, p5 };
|
||||
return value;
|
||||
}
|
||||
ACTION_TEMPLATE(ReturnMalloc,
|
||||
HAS_1_TEMPLATE_PARAMS(typename, T),
|
||||
AND_7_VALUE_PARAMS(p0, p1, p2, p3, p4, p5, p6)) {
|
||||
T* value = static_cast<T*>(malloc(sizeof(T)));
|
||||
*value = T{ p0, p1, p2, p3, p4, p5, p6 };
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,82 @@ xcb_generic_error_t* xcb_request_check(xcb_connection_t* c, xcb_void_cookie_t co
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_request_check(c, cookie);
|
||||
}
|
||||
const xcb_setup_t* xcb_get_setup(xcb_connection_t *c)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_get_setup(c);
|
||||
}
|
||||
xcb_screen_iterator_t xcb_setup_roots_iterator(const xcb_setup_t* R)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_setup_roots_iterator(R);
|
||||
}
|
||||
const xcb_query_extension_reply_t* xcb_get_extension_data(xcb_connection_t* c, xcb_extension_t* ext)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_get_extension_data(c, ext);
|
||||
}
|
||||
int xcb_flush(xcb_connection_t *c)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_flush(c);
|
||||
}
|
||||
xcb_query_pointer_cookie_t xcb_query_pointer(xcb_connection_t* c, xcb_window_t window)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_query_pointer(c, window);
|
||||
}
|
||||
xcb_query_pointer_reply_t* xcb_query_pointer_reply(xcb_connection_t* c, xcb_query_pointer_cookie_t cookie, xcb_generic_error_t** e)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_query_pointer_reply(c, cookie, e);
|
||||
}
|
||||
xcb_get_geometry_cookie_t xcb_get_geometry(xcb_connection_t* c, xcb_drawable_t drawable)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_get_geometry(c, drawable);
|
||||
}
|
||||
xcb_get_geometry_reply_t* xcb_get_geometry_reply(xcb_connection_t* c, xcb_get_geometry_cookie_t cookie, xcb_generic_error_t** e)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_get_geometry_reply(c, cookie, e);
|
||||
}
|
||||
xcb_void_cookie_t xcb_warp_pointer(
|
||||
xcb_connection_t* c,
|
||||
xcb_window_t src_window,
|
||||
xcb_window_t dst_window,
|
||||
int16_t src_x,
|
||||
int16_t src_y,
|
||||
uint16_t src_width,
|
||||
uint16_t src_height,
|
||||
int16_t dst_x,
|
||||
int16_t dst_y)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_warp_pointer(c, src_window, dst_window, src_x, src_y, src_width, src_height, dst_x, dst_y);
|
||||
}
|
||||
xcb_intern_atom_cookie_t xcb_intern_atom(xcb_connection_t* c, uint8_t only_if_exists, uint16_t name_len, const char* name)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_intern_atom(c, only_if_exists, name_len, name);
|
||||
}
|
||||
xcb_intern_atom_reply_t* xcb_intern_atom_reply(xcb_connection_t* c, xcb_intern_atom_cookie_t cookie, xcb_generic_error_t** e)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_intern_atom_reply(c, cookie, e);
|
||||
}
|
||||
xcb_get_property_cookie_t xcb_get_property(
|
||||
xcb_connection_t* c,
|
||||
uint8_t _delete,
|
||||
xcb_window_t window,
|
||||
xcb_atom_t property,
|
||||
xcb_atom_t type,
|
||||
uint32_t long_offset,
|
||||
uint32_t long_length)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_get_property(c, _delete, window, property, type, long_offset, long_length);
|
||||
}
|
||||
xcb_get_property_reply_t* xcb_get_property_reply(xcb_connection_t* c, xcb_get_property_cookie_t cookie, xcb_generic_error_t** e)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_get_property_reply(c, cookie, e);
|
||||
}
|
||||
void* xcb_get_property_value(const xcb_get_property_reply_t* R)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_get_property_value(R);
|
||||
}
|
||||
uint32_t xcb_generate_id(xcb_connection_t *c)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_generate_id(c);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// xcb-xkb
|
||||
@@ -116,4 +192,76 @@ xkb_state_component xkb_state_update_mask(
|
||||
state, depressed_mods, latched_mods, locked_mods, depressed_layout, latched_layout, locked_layout);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// xcb-xfixes
|
||||
xcb_xfixes_query_version_cookie_t xcb_xfixes_query_version(
|
||||
xcb_connection_t* c, uint32_t client_major_version, uint32_t client_minor_version)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_xfixes_query_version(c, client_major_version, client_minor_version);
|
||||
}
|
||||
xcb_xfixes_query_version_reply_t* xcb_xfixes_query_version_reply(
|
||||
xcb_connection_t* c, xcb_xfixes_query_version_cookie_t cookie, xcb_generic_error_t** e)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_xfixes_query_version_reply(c, cookie, e);
|
||||
}
|
||||
xcb_void_cookie_t xcb_xfixes_show_cursor_checked(xcb_connection_t* c, xcb_window_t window)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_xfixes_show_cursor_checked(c, window);
|
||||
}
|
||||
xcb_void_cookie_t xcb_xfixes_hide_cursor_checked(xcb_connection_t* c, xcb_window_t window)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_xfixes_hide_cursor_checked(c, window);
|
||||
}
|
||||
xcb_void_cookie_t xcb_xfixes_delete_pointer_barrier_checked(xcb_connection_t* c, xcb_xfixes_barrier_t barrier)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_xfixes_delete_pointer_barrier_checked(c, barrier);
|
||||
}
|
||||
xcb_translate_coordinates_cookie_t xcb_translate_coordinates(xcb_connection_t* c, xcb_window_t src_window, xcb_window_t dst_window, int16_t src_x, int16_t src_y)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_translate_coordinates(c, src_window, dst_window, src_x, src_y);
|
||||
}
|
||||
xcb_translate_coordinates_reply_t* xcb_translate_coordinates_reply(xcb_connection_t* c, xcb_translate_coordinates_cookie_t cookie, xcb_generic_error_t** e)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_translate_coordinates_reply(c, cookie, e);
|
||||
}
|
||||
xcb_void_cookie_t xcb_xfixes_create_pointer_barrier_checked(
|
||||
xcb_connection_t* c,
|
||||
xcb_xfixes_barrier_t barrier,
|
||||
xcb_window_t window,
|
||||
uint16_t x1,
|
||||
uint16_t y1,
|
||||
uint16_t x2,
|
||||
uint16_t y2,
|
||||
uint32_t directions,
|
||||
uint16_t num_devices,
|
||||
const uint16_t* devices)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_xfixes_create_pointer_barrier_checked(c, barrier, window, x1, y1, x2, y2, directions, num_devices, devices);
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// xcb-xinput
|
||||
xcb_input_xi_query_version_cookie_t xcb_input_xi_query_version(xcb_connection_t* c, uint16_t major_version, uint16_t minor_version)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_input_xi_query_version(c, major_version, minor_version);
|
||||
}
|
||||
xcb_input_xi_query_version_reply_t* xcb_input_xi_query_version_reply(
|
||||
xcb_connection_t* c, xcb_input_xi_query_version_cookie_t cookie, xcb_generic_error_t** e)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_input_xi_query_version_reply(c, cookie, e);
|
||||
}
|
||||
xcb_void_cookie_t xcb_input_xi_select_events(
|
||||
xcb_connection_t* c, xcb_window_t window, uint16_t num_mask, const xcb_input_event_mask_t* masks)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_input_xi_select_events(c, window, num_mask, masks);
|
||||
}
|
||||
int xcb_input_raw_button_press_axisvalues_length (const xcb_input_raw_button_press_event_t *R)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_input_raw_button_press_axisvalues_length(R);
|
||||
}
|
||||
xcb_input_fp3232_t* xcb_input_raw_button_press_axisvalues_raw(const xcb_input_raw_button_press_event_t* R)
|
||||
{
|
||||
return MockXcbInterface::Instance()->xcb_input_raw_button_press_axisvalues_raw(R);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#undef explicit
|
||||
#include <xkbcommon/xkbcommon.h>
|
||||
#include <xkbcommon/xkbcommon-x11.h>
|
||||
#include <xcb/xfixes.h>
|
||||
#include <xcb/xinput.h>
|
||||
|
||||
#include "Printers.h"
|
||||
|
||||
@@ -62,6 +64,37 @@ public:
|
||||
MOCK_CONST_METHOD1(xcb_disconnect, void(xcb_connection_t* c));
|
||||
MOCK_CONST_METHOD1(xcb_poll_for_event, xcb_generic_event_t*(xcb_connection_t* c));
|
||||
MOCK_CONST_METHOD2(xcb_request_check, xcb_generic_error_t*(xcb_connection_t* c, xcb_void_cookie_t cookie));
|
||||
MOCK_CONST_METHOD1(xcb_get_setup, const xcb_setup_t*(xcb_connection_t *c));
|
||||
MOCK_CONST_METHOD1(xcb_setup_roots_iterator, xcb_screen_iterator_t(const xcb_setup_t* R));
|
||||
MOCK_CONST_METHOD2(xcb_get_extension_data, const xcb_query_extension_reply_t*(xcb_connection_t* c, xcb_extension_t* ext));
|
||||
MOCK_CONST_METHOD1(xcb_flush, int(xcb_connection_t *c));
|
||||
MOCK_CONST_METHOD2(xcb_query_pointer, xcb_query_pointer_cookie_t(xcb_connection_t* c, xcb_window_t window));
|
||||
MOCK_CONST_METHOD3(xcb_query_pointer_reply, xcb_query_pointer_reply_t*(xcb_connection_t* c, xcb_query_pointer_cookie_t cookie, xcb_generic_error_t** e));
|
||||
MOCK_CONST_METHOD2(xcb_get_geometry, xcb_get_geometry_cookie_t(xcb_connection_t* c, xcb_drawable_t drawable));
|
||||
MOCK_CONST_METHOD3(xcb_get_geometry_reply, xcb_get_geometry_reply_t*(xcb_connection_t* c, xcb_get_geometry_cookie_t cookie, xcb_generic_error_t** e));
|
||||
MOCK_CONST_METHOD9(xcb_warp_pointer, xcb_void_cookie_t(
|
||||
xcb_connection_t* c,
|
||||
xcb_window_t src_window,
|
||||
xcb_window_t dst_window,
|
||||
int16_t src_x,
|
||||
int16_t src_y,
|
||||
uint16_t src_width,
|
||||
uint16_t src_height,
|
||||
int16_t dst_x,
|
||||
int16_t dst_y));
|
||||
MOCK_CONST_METHOD4(xcb_intern_atom, xcb_intern_atom_cookie_t(xcb_connection_t* c, uint8_t only_if_exists, uint16_t name_len, const char* name));
|
||||
MOCK_CONST_METHOD3(xcb_intern_atom_reply, xcb_intern_atom_reply_t*(xcb_connection_t* c, xcb_intern_atom_cookie_t cookie, xcb_generic_error_t** e));
|
||||
MOCK_CONST_METHOD7(xcb_get_property, xcb_get_property_cookie_t(
|
||||
xcb_connection_t* c,
|
||||
uint8_t _delete,
|
||||
xcb_window_t window,
|
||||
xcb_atom_t property,
|
||||
xcb_atom_t type,
|
||||
uint32_t long_offset,
|
||||
uint32_t long_length));
|
||||
MOCK_CONST_METHOD3(xcb_get_property_reply, xcb_get_property_reply_t*(xcb_connection_t* c, xcb_get_property_cookie_t cookie, xcb_generic_error_t** e));
|
||||
MOCK_CONST_METHOD1(xcb_get_property_value, void*(const xcb_get_property_reply_t* R));
|
||||
MOCK_CONST_METHOD1(xcb_generate_id, uint32_t(xcb_connection_t *c));
|
||||
|
||||
// xcb-xkb
|
||||
MOCK_CONST_METHOD3(xcb_xkb_use_extension, xcb_xkb_use_extension_cookie_t(xcb_connection_t* c, uint16_t wantedMajor, uint16_t wantedMinor));
|
||||
@@ -83,6 +116,33 @@ public:
|
||||
MOCK_CONST_METHOD4(xkb_state_key_get_utf8, int(xkb_state* state, xkb_keycode_t key, char* buffer, size_t size));
|
||||
MOCK_CONST_METHOD7(xkb_state_update_mask, xkb_state_component(xkb_state* state, xkb_mod_mask_t depressed_mods, xkb_mod_mask_t latched_mods, xkb_mod_mask_t locked_mods, xkb_layout_index_t depressed_layout, xkb_layout_index_t latched_layout, xkb_layout_index_t locked_layout));
|
||||
|
||||
// xcb-xfixes
|
||||
MOCK_CONST_METHOD3(xcb_xfixes_query_version, xcb_xfixes_query_version_cookie_t(xcb_connection_t* c, uint32_t client_major_version, uint32_t client_minor_version));
|
||||
MOCK_CONST_METHOD3(xcb_xfixes_query_version_reply, xcb_xfixes_query_version_reply_t*(xcb_connection_t* c, xcb_xfixes_query_version_cookie_t cookie, xcb_generic_error_t** e));
|
||||
MOCK_CONST_METHOD2(xcb_xfixes_show_cursor_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window));
|
||||
MOCK_CONST_METHOD2(xcb_xfixes_hide_cursor_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window));
|
||||
MOCK_CONST_METHOD2(xcb_xfixes_delete_pointer_barrier_checked, xcb_void_cookie_t(xcb_connection_t* c, xcb_xfixes_barrier_t barrier));
|
||||
MOCK_CONST_METHOD5(xcb_translate_coordinates, xcb_translate_coordinates_cookie_t(xcb_connection_t* c, xcb_window_t src_window, xcb_window_t dst_window, int16_t src_x, int16_t src_y));
|
||||
MOCK_CONST_METHOD3(xcb_translate_coordinates_reply, xcb_translate_coordinates_reply_t*(xcb_connection_t* c, xcb_translate_coordinates_cookie_t cookie, xcb_generic_error_t** e));
|
||||
MOCK_CONST_METHOD10(xcb_xfixes_create_pointer_barrier_checked, xcb_void_cookie_t(
|
||||
xcb_connection_t* c,
|
||||
xcb_xfixes_barrier_t barrier,
|
||||
xcb_window_t window,
|
||||
uint16_t x1,
|
||||
uint16_t y1,
|
||||
uint16_t x2,
|
||||
uint16_t y2,
|
||||
uint32_t directions,
|
||||
uint16_t num_devices,
|
||||
const uint16_t* devices));
|
||||
|
||||
// xcb-xinput
|
||||
MOCK_CONST_METHOD3(xcb_input_xi_query_version, xcb_input_xi_query_version_cookie_t(xcb_connection_t* c, uint16_t major_version, uint16_t minor_version));
|
||||
MOCK_CONST_METHOD3(xcb_input_xi_query_version_reply, xcb_input_xi_query_version_reply_t*(xcb_connection_t* c, xcb_input_xi_query_version_cookie_t cookie, xcb_generic_error_t** e));
|
||||
MOCK_CONST_METHOD4(xcb_input_xi_select_events, xcb_void_cookie_t(xcb_connection_t* c, xcb_window_t window, uint16_t num_mask, const xcb_input_event_mask_t* masks));
|
||||
MOCK_CONST_METHOD1(xcb_input_raw_button_press_axisvalues_length, int(const xcb_input_raw_button_press_event_t* R));
|
||||
MOCK_CONST_METHOD1(xcb_input_raw_button_press_axisvalues_raw, xcb_input_fp3232_t*(const xcb_input_raw_button_press_event_t* R));
|
||||
|
||||
private:
|
||||
static inline MockXcbInterface* self = nullptr;
|
||||
};
|
||||
|
||||
@@ -22,6 +22,12 @@ namespace AzFramework
|
||||
public:
|
||||
void SetUp() override;
|
||||
|
||||
template<typename T>
|
||||
static xcb_generic_event_t MakeEvent(T event)
|
||||
{
|
||||
return *reinterpret_cast<xcb_generic_event_t*>(&event);
|
||||
}
|
||||
|
||||
protected:
|
||||
testing::NiceMock<MockXcbInterface> m_interface;
|
||||
xcb_connection_t m_connection{};
|
||||
|
||||
@@ -21,12 +21,6 @@
|
||||
#include "XcbBaseTestFixture.h"
|
||||
#include "XcbTestApplication.h"
|
||||
|
||||
template<typename T>
|
||||
xcb_generic_event_t MakeEvent(T event)
|
||||
{
|
||||
return *reinterpret_cast<xcb_generic_event_t*>(&event);
|
||||
}
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
// Sets up default behavior for mock keyboard responses to xcb methods
|
||||
|
||||
@@ -0,0 +1,545 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include <xcb/xcb.h>
|
||||
|
||||
#include <AzFramework/Input/Buses/Requests/InputSystemCursorRequestBus.h>
|
||||
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
|
||||
|
||||
#include "XcbBaseTestFixture.h"
|
||||
#include "XcbTestApplication.h"
|
||||
#include "Matchers.h"
|
||||
#include "Actions.h"
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
// Sets up default behavior for mock keyboard responses to xcb methods
|
||||
class XcbInputDeviceMouseTests
|
||||
: public XcbBaseTestFixture
|
||||
{
|
||||
public:
|
||||
void SetUp() override
|
||||
{
|
||||
using testing::Eq;
|
||||
using testing::Field;
|
||||
using testing::Return;
|
||||
using testing::StrEq;
|
||||
using testing::_;
|
||||
|
||||
XcbBaseTestFixture::SetUp();
|
||||
|
||||
ON_CALL(m_interface, xcb_get_setup(&m_connection))
|
||||
.WillByDefault(Return(&s_xcbSetup));
|
||||
ON_CALL(m_interface, xcb_setup_roots_iterator(&s_xcbSetup))
|
||||
.WillByDefault(Return(xcb_screen_iterator_t{&s_xcbScreen}));
|
||||
|
||||
ON_CALL(m_interface, xcb_get_extension_data(&m_connection, &xcb_xfixes_id))
|
||||
.WillByDefault(Return(&s_xfixesExtensionReply));
|
||||
ON_CALL(m_interface, xcb_xfixes_query_version_reply(&m_connection, _, _))
|
||||
.WillByDefault(ReturnMalloc<xcb_xfixes_query_version_reply_t>(
|
||||
/*response_type=*/(uint8_t)XCB_XFIXES_QUERY_VERSION,
|
||||
/*pad0=*/(uint8_t)0,
|
||||
/*sequence=*/(uint16_t)1,
|
||||
/*length=*/0u,
|
||||
/*major_version=*/5u,
|
||||
/*minor_version=*/0u
|
||||
));
|
||||
|
||||
ON_CALL(m_interface, xcb_get_extension_data(&m_connection, &xcb_input_id))
|
||||
.WillByDefault(Return(&s_xfixesExtensionReply));
|
||||
ON_CALL(m_interface, xcb_input_xi_query_version_reply(&m_connection, _, _))
|
||||
.WillByDefault(ReturnMalloc<xcb_input_xi_query_version_reply_t>(
|
||||
/*response_type=*/(uint8_t)XCB_INPUT_XI_QUERY_VERSION,
|
||||
/*pad0=*/(uint8_t)0,
|
||||
/*sequence=*/(uint16_t)1,
|
||||
/*length=*/0u,
|
||||
/*major_version=*/(uint16_t)2,
|
||||
/*minor_version=*/(uint16_t)2
|
||||
));
|
||||
|
||||
// Set the default focus window
|
||||
EXPECT_CALL(m_interface, xcb_intern_atom(&m_connection, 1, 18, StrEq("_NET_ACTIVE_WINDOW")))
|
||||
.WillRepeatedly(Return(xcb_intern_atom_cookie_t{/*.sequence=*/ 1}));
|
||||
ON_CALL(m_interface, xcb_intern_atom_reply(&m_connection, Field(&xcb_intern_atom_cookie_t::sequence, Eq(1)), _))
|
||||
.WillByDefault(ReturnMalloc<xcb_intern_atom_reply_t>(
|
||||
/*response_type=*/(uint8_t)XCB_INTERN_ATOM,
|
||||
/*pad0=*/(uint8_t)0,
|
||||
/*sequence=*/(uint16_t)1,
|
||||
/*length=*/0u,
|
||||
/*xcb_atom_t=*/s_netActiveWindowAtom
|
||||
));
|
||||
ON_CALL(m_interface, xcb_get_property(&m_connection, 0, s_rootWindow, s_netActiveWindowAtom, XCB_ATOM_WINDOW, 0, 1))
|
||||
.WillByDefault(Return(xcb_get_property_cookie_t{/*.sequence=*/ s_getActiveWindowPropertySequence}));
|
||||
ON_CALL(m_interface, xcb_get_property_reply(&m_connection, Field(&xcb_get_property_cookie_t::sequence, Eq(s_getActiveWindowPropertySequence)), _))
|
||||
.WillByDefault(ReturnMalloc<xcb_get_property_reply_t>(
|
||||
/*response_type=*/(uint8_t)XCB_GET_PROPERTY,
|
||||
/*format=*/(uint8_t)0,
|
||||
/*sequence=*/(uint16_t)s_getActiveWindowPropertySequence,
|
||||
/*length=*/0u,
|
||||
/*type=*/XCB_ATOM_WINDOW,
|
||||
/*bytes_after=*/0u,
|
||||
/*value_len=*/1u
|
||||
));
|
||||
ON_CALL(m_interface, xcb_get_property_value(Field(&xcb_get_property_reply_t::sequence, Eq(s_getActiveWindowPropertySequence))))
|
||||
.WillByDefault(Return(const_cast<xcb_window_t*>(&s_nullWindow)));
|
||||
|
||||
ON_CALL(m_interface, xcb_get_geometry(&m_connection, _))
|
||||
.WillByDefault(Return(xcb_get_geometry_cookie_t{/*.sequence=*/1}));
|
||||
ON_CALL(m_interface, xcb_get_geometry_reply(&m_connection, Field(&xcb_get_geometry_cookie_t::sequence, Eq(1)), _))
|
||||
.WillByDefault(ReturnMalloc<xcb_get_geometry_reply_t>(s_defaultWindowGeometry));
|
||||
}
|
||||
|
||||
void PumpApplication()
|
||||
{
|
||||
m_application.PumpSystemEventLoopUntilEmpty();
|
||||
m_application.TickSystem();
|
||||
m_application.Tick();
|
||||
}
|
||||
|
||||
protected:
|
||||
static constexpr inline uint8_t s_xinputMajorOpcode = 131;
|
||||
static constexpr inline xcb_window_t s_rootWindow = 1;
|
||||
static constexpr inline xcb_window_t s_nullWindow = XCB_WINDOW_NONE;
|
||||
static constexpr inline xcb_input_device_id_t s_virtualCorePointerId = 2;
|
||||
static constexpr inline xcb_input_device_id_t s_physicalPointerDeviceId = 3;
|
||||
static constexpr inline uint16_t s_screenWidthInPixels = 3840;
|
||||
static constexpr inline uint16_t s_screenHeightInPixels = 2160;
|
||||
static constexpr inline uint16_t s_getActiveWindowPropertySequence = 2160;
|
||||
static constexpr inline xcb_atom_t s_netActiveWindowAtom = 1;
|
||||
static constexpr inline xcb_setup_t s_xcbSetup{
|
||||
/*.status=*/1,
|
||||
/*.pad0=*/0,
|
||||
/*.protocol_major_version=*/11,
|
||||
/*.protocol_minor_version=*/0,
|
||||
};
|
||||
static inline xcb_screen_t s_xcbScreen{
|
||||
/*.root=*/s_rootWindow,
|
||||
/*.default_colormap=*/32,
|
||||
/*.white_pixel=*/16777215,
|
||||
/*.black_pixel=*/0,
|
||||
/*.current_input_masks=*/0,
|
||||
/*.width_in_pixels=*/s_screenWidthInPixels,
|
||||
/*.height_in_pixels=*/s_screenHeightInPixels,
|
||||
/*.width_in_millimeters=*/602,
|
||||
/*.height_in_millimeters=*/341,
|
||||
};
|
||||
static constexpr inline xcb_query_extension_reply_t s_xfixesExtensionReply{
|
||||
/*.response_type=*/XCB_QUERY_EXTENSION,
|
||||
/*.pad0=*/0,
|
||||
/*.sequence=*/1,
|
||||
/*.length=*/0,
|
||||
/*.present=*/1,
|
||||
};
|
||||
static constexpr inline xcb_query_extension_reply_t s_xinputExtensionReply{
|
||||
/*.response_type=*/XCB_QUERY_EXTENSION,
|
||||
/*.pad0=*/0,
|
||||
/*.sequence=*/1,
|
||||
/*.length=*/0,
|
||||
/*.present=*/1,
|
||||
/*.major_opcode=*/s_xinputMajorOpcode,
|
||||
};
|
||||
static constexpr inline xcb_get_geometry_reply_t s_defaultWindowGeometry{
|
||||
/*.response_type=*/XCB_GET_GEOMETRY,
|
||||
/*.depth=*/0,
|
||||
/*.sequence=*/1,
|
||||
/*.length=*/0,
|
||||
/*.root=*/s_rootWindow,
|
||||
/*.x=*/100,
|
||||
/*.y=*/100,
|
||||
/*.width=*/100,
|
||||
/*.height=*/100,
|
||||
/*.border_width=*/3,
|
||||
/*.pad0[2]=*/{},
|
||||
};
|
||||
XcbTestApplication m_application{
|
||||
/*enabledGamepadsCount=*/0,
|
||||
/*keyboardEnabled=*/false,
|
||||
/*motionEnabled=*/false,
|
||||
/*mouseEnabled=*/true,
|
||||
/*touchEnabled=*/false,
|
||||
/*virtualKeyboardEnabled=*/false
|
||||
};
|
||||
};
|
||||
|
||||
struct MouseButtonTestData
|
||||
{
|
||||
xcb_button_index_t m_button;
|
||||
};
|
||||
|
||||
class XcbInputDeviceMouseButtonTests
|
||||
: public XcbInputDeviceMouseTests
|
||||
, public testing::WithParamInterface<MouseButtonTestData>
|
||||
{
|
||||
public:
|
||||
static InputChannelId GetInputChannelIdForButton(const xcb_button_index_t button)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case XCB_BUTTON_INDEX_1:
|
||||
return InputDeviceMouse::Button::Left;
|
||||
case XCB_BUTTON_INDEX_2:
|
||||
return InputDeviceMouse::Button::Right;
|
||||
case XCB_BUTTON_INDEX_3:
|
||||
return InputDeviceMouse::Button::Middle;
|
||||
}
|
||||
return InputChannelId{};
|
||||
}
|
||||
|
||||
AZStd::array<InputChannelId, 4> GetIdleChannelIdsForButton(const xcb_button_index_t button)
|
||||
{
|
||||
switch (button)
|
||||
{
|
||||
case XCB_BUTTON_INDEX_1:
|
||||
return { InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 };
|
||||
case XCB_BUTTON_INDEX_2:
|
||||
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 };
|
||||
case XCB_BUTTON_INDEX_3:
|
||||
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Other1, InputDeviceMouse::Button::Other2 };
|
||||
case XCB_BUTTON_INDEX_4:
|
||||
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other2 };
|
||||
case XCB_BUTTON_INDEX_5:
|
||||
return { InputDeviceMouse::Button::Left, InputDeviceMouse::Button::Right, InputDeviceMouse::Button::Middle, InputDeviceMouse::Button::Other1 };
|
||||
}
|
||||
return AZStd::array<InputChannelId, 4>();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(XcbInputDeviceMouseButtonTests, ButtonInputChannelsUpdateStateFromXcbEvents)
|
||||
{
|
||||
using testing::Each;
|
||||
using testing::Eq;
|
||||
using testing::NotNull;
|
||||
using testing::Property;
|
||||
using testing::Return;
|
||||
|
||||
// Set the expectations for the events that will be generated
|
||||
// nullptr entries represent when the event queue is empty, and will cause
|
||||
// PumpSystemEventLoopUntilEmpty to return
|
||||
//
|
||||
// Event pointers are freed by the calling code, so these actions
|
||||
// malloc new copies
|
||||
//
|
||||
// The xcb mouse does not react to the `XCB_BUTTON_PRESS` /
|
||||
// `XCB_BUTTON_RELEASE` events, but it will still receive those events
|
||||
// from the X server.
|
||||
EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_input_raw_button_press_event_t{
|
||||
/*response_type=*/XCB_GE_GENERIC,
|
||||
/*extension=*/s_xinputMajorOpcode,
|
||||
/*sequence=*/4,
|
||||
/*length=*/2,
|
||||
/*event_type=*/XCB_INPUT_RAW_BUTTON_PRESS,
|
||||
/*deviceid=*/s_virtualCorePointerId,
|
||||
/*time=*/3984920,
|
||||
/*detail=*/GetParam().m_button,
|
||||
/*sourceid=*/s_physicalPointerDeviceId,
|
||||
/*valuators_len=*/2,
|
||||
/*flags=*/0,
|
||||
/*pad0[4]=*/{},
|
||||
/*full_sequence=*/4
|
||||
})))
|
||||
.WillOnce(Return(nullptr))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_button_press_event_t{
|
||||
/*response_type=*/XCB_BUTTON_PRESS,
|
||||
/*detail=*/static_cast<xcb_button_t>(GetParam().m_button),
|
||||
/*sequence=*/4,
|
||||
/*time=*/3984920,
|
||||
/*root=*/s_rootWindow,
|
||||
/*event=*/119537664,
|
||||
/*child=*/0,
|
||||
/*root_x=*/55,
|
||||
/*root_y=*/1099,
|
||||
/*event_x=*/55,
|
||||
/*event_y=*/55,
|
||||
/*state=*/0,
|
||||
/*same_screen=*/1
|
||||
})))
|
||||
.WillOnce(Return(nullptr))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_input_raw_button_release_event_t{
|
||||
/*response_type=*/XCB_GE_GENERIC,
|
||||
/*extension=*/s_xinputMajorOpcode,
|
||||
/*sequence=*/4,
|
||||
/*length=*/2,
|
||||
/*event_type=*/XCB_INPUT_RAW_BUTTON_RELEASE,
|
||||
/*deviceid=*/s_virtualCorePointerId,
|
||||
/*time=*/3984964,
|
||||
/*detail=*/GetParam().m_button,
|
||||
/*sourceid=*/s_physicalPointerDeviceId,
|
||||
/*valuators_len=*/2,
|
||||
/*flags=*/0,
|
||||
/*pad0[4]=*/{},
|
||||
/*full_sequence=*/4
|
||||
})))
|
||||
.WillOnce(Return(nullptr))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_button_release_event_t{
|
||||
/*response_type=*/XCB_BUTTON_RELEASE,
|
||||
/*detail=*/static_cast<xcb_button_t>(GetParam().m_button),
|
||||
/*sequence=*/4,
|
||||
/*time=*/3984964,
|
||||
/*root=*/s_rootWindow,
|
||||
/*event=*/119537664,
|
||||
/*child=*/0,
|
||||
/*root_x=*/55,
|
||||
/*root_y=*/1099,
|
||||
/*event_x=*/55,
|
||||
/*event_y=*/55,
|
||||
/*state=*/XCB_KEY_BUT_MASK_BUTTON_1,
|
||||
/*same_screen=*/1
|
||||
})))
|
||||
.WillOnce(Return(nullptr))
|
||||
;
|
||||
|
||||
m_application.Start();
|
||||
InputSystemCursorRequestBus::Event(
|
||||
InputDeviceMouse::Id,
|
||||
&InputSystemCursorRequests::SetSystemCursorState,
|
||||
SystemCursorState::ConstrainedAndHidden);
|
||||
|
||||
const InputChannel* activeButtonChannel = InputChannelRequests::FindInputChannel(GetInputChannelIdForButton(GetParam().m_button));
|
||||
const auto inactiveButtonChannels = [this]()
|
||||
{
|
||||
const auto inactiveButtonChannelIds = GetIdleChannelIdsForButton(GetParam().m_button);
|
||||
AZStd::array<const InputChannel*, 4> channels{};
|
||||
AZStd::transform(begin(inactiveButtonChannelIds), end(inactiveButtonChannelIds), begin(channels), [](const InputChannelId& id)
|
||||
{
|
||||
return InputChannelRequests::FindInputChannel(id);
|
||||
});
|
||||
return channels;
|
||||
}();
|
||||
|
||||
ASSERT_TRUE(activeButtonChannel);
|
||||
ASSERT_THAT(inactiveButtonChannels, Each(NotNull()));
|
||||
|
||||
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Idle));
|
||||
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
|
||||
|
||||
PumpApplication();
|
||||
|
||||
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Began));
|
||||
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
|
||||
|
||||
PumpApplication();
|
||||
|
||||
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Updated));
|
||||
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
|
||||
|
||||
PumpApplication();
|
||||
|
||||
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Ended));
|
||||
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
|
||||
|
||||
PumpApplication();
|
||||
|
||||
EXPECT_THAT(activeButtonChannel->GetState(), Eq(InputChannel::State::Idle));
|
||||
EXPECT_THAT(inactiveButtonChannels, Each(Property(&InputChannel::GetState, Eq(InputChannel::State::Idle))));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
AllButtons,
|
||||
XcbInputDeviceMouseButtonTests,
|
||||
testing::Values(
|
||||
MouseButtonTestData{ XCB_BUTTON_INDEX_1 },
|
||||
MouseButtonTestData{ XCB_BUTTON_INDEX_2 },
|
||||
MouseButtonTestData{ XCB_BUTTON_INDEX_3 }
|
||||
// XCB_BUTTON_INDEX_4 and XCB_BUTTON_INDEX_5 map to positive and
|
||||
// negative scroll wheel events, which are handled as motion events
|
||||
)
|
||||
);
|
||||
|
||||
TEST_F(XcbInputDeviceMouseTests, MovementInputChannelsUpdateStateFromXcbEvents)
|
||||
{
|
||||
using testing::Each;
|
||||
using testing::Eq;
|
||||
using testing::FloatEq;
|
||||
using testing::NotNull;
|
||||
using testing::Property;
|
||||
using testing::Return;
|
||||
|
||||
// Set the expectations for the events that will be generated
|
||||
// nullptr entries represent when the event queue is empty, and will cause
|
||||
// PumpSystemEventLoopUntilEmpty to return
|
||||
//
|
||||
// Event pointers are freed by the calling code, so these actions
|
||||
// malloc new copies
|
||||
//
|
||||
// The xcb mouse does not react to the `XCB_MOTION_NOTIFY` event, but
|
||||
// it will still receive it from the X server.
|
||||
EXPECT_CALL(m_interface, xcb_poll_for_event(&m_connection))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_input_raw_motion_event_t{
|
||||
/*response_type=*/XCB_GE_GENERIC,
|
||||
/*extension=*/s_xinputMajorOpcode,
|
||||
/*sequence=*/5,
|
||||
/*length=*/10,
|
||||
/*event_type=*/XCB_INPUT_RAW_MOTION,
|
||||
/*deviceid=*/s_virtualCorePointerId,
|
||||
/*time=*/0, // use the time value to identify each event
|
||||
/*detail=*/XCB_MOTION_NORMAL,
|
||||
/*sourceid=*/s_physicalPointerDeviceId,
|
||||
/*valuators_len=*/2, // number of axes that have values for this event
|
||||
/*flags=*/0,
|
||||
/*pad0[4]=*/{},
|
||||
/*full_sequence=*/5,
|
||||
})))
|
||||
.WillOnce(Return(nullptr))
|
||||
.WillOnce(ReturnMalloc<xcb_generic_event_t>(MakeEvent(xcb_motion_notify_event_t{
|
||||
/*response_type=*/XCB_MOTION_NOTIFY,
|
||||
/*detail=*/XCB_MOTION_NORMAL,
|
||||
/*sequence=*/5,
|
||||
/*time=*/1, // use the time value to identify each event
|
||||
/*root=*/s_rootWindow,
|
||||
/*event=*/127926272,
|
||||
/*child=*/0,
|
||||
/*root_x=*/95,
|
||||
/*root_y=*/1079,
|
||||
/*event_x=*/95,
|
||||
/*event_y=*/20,
|
||||
/*state=*/0,
|
||||
/*same_screen=*/1,
|
||||
})))
|
||||
.WillOnce(Return(nullptr))
|
||||
;
|
||||
|
||||
AZStd::array axisValues
|
||||
{
|
||||
xcb_input_fp3232_t{ /*.integral=*/ 1, /*.fraction=*/0 }, // x motion
|
||||
xcb_input_fp3232_t{ /*.integral=*/ 2, /*.fraction=*/0 } // y motion
|
||||
};
|
||||
|
||||
EXPECT_CALL(m_interface, xcb_input_raw_button_press_axisvalues_length(testing::Field(&xcb_input_raw_button_press_event_t::time, 0)))
|
||||
.WillRepeatedly(testing::Return(2)); // x and y axis
|
||||
EXPECT_CALL(m_interface, xcb_input_raw_button_press_axisvalues_raw(testing::Field(&xcb_input_raw_button_press_event_t::time, 0)))
|
||||
.WillRepeatedly(testing::Return(axisValues.data())); // x and y axis
|
||||
|
||||
m_application.Start();
|
||||
InputSystemCursorRequestBus::Event(
|
||||
InputDeviceMouse::Id,
|
||||
&InputSystemCursorRequests::SetSystemCursorState,
|
||||
SystemCursorState::ConstrainedAndHidden);
|
||||
|
||||
const InputChannel* xMotionChannel = InputChannelRequests::FindInputChannel(InputDeviceMouse::Movement::X);
|
||||
const InputChannel* yMotionChannel = InputChannelRequests::FindInputChannel(InputDeviceMouse::Movement::Y);
|
||||
ASSERT_TRUE(xMotionChannel);
|
||||
ASSERT_TRUE(yMotionChannel);
|
||||
|
||||
EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Idle));
|
||||
EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Idle));
|
||||
EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(0.0f));
|
||||
EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(0.0f));
|
||||
|
||||
PumpApplication();
|
||||
|
||||
EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Began));
|
||||
EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Began));
|
||||
EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(1.0f));
|
||||
EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(2.0f));
|
||||
|
||||
PumpApplication();
|
||||
|
||||
EXPECT_THAT(xMotionChannel->GetState(), Eq(InputChannel::State::Ended));
|
||||
EXPECT_THAT(yMotionChannel->GetState(), Eq(InputChannel::State::Ended));
|
||||
EXPECT_THAT(xMotionChannel->GetValue(), FloatEq(0.0f));
|
||||
EXPECT_THAT(yMotionChannel->GetValue(), FloatEq(0.0f));
|
||||
}
|
||||
|
||||
struct GetCursorPositionParam
|
||||
{
|
||||
int16_t m_x;
|
||||
int16_t m_y;
|
||||
};
|
||||
|
||||
class XcbGetSystemCursorPositionTests
|
||||
: public XcbInputDeviceMouseTests
|
||||
, public testing::WithParamInterface<GetCursorPositionParam>
|
||||
{
|
||||
};
|
||||
|
||||
TEST_P(XcbGetSystemCursorPositionTests, GetSystemCursorPositionNormalizedReturnsCorrectValue)
|
||||
{
|
||||
using testing::Eq;
|
||||
using testing::Field;
|
||||
using testing::Return;
|
||||
using testing::_;
|
||||
|
||||
xcb_window_t focusWindow = 42;
|
||||
const xcb_query_pointer_reply_t queryPointerReply{
|
||||
/*.response_type=*/XCB_QUERY_POINTER,
|
||||
/*.same_screen=*/1,
|
||||
/*.sequence=*/0,
|
||||
/*.length=*/1,
|
||||
/*.root=*/s_rootWindow,
|
||||
/*.child=*/focusWindow,
|
||||
/*.root_x=*/static_cast<int16_t>(GetParam().m_x + s_defaultWindowGeometry.x),
|
||||
/*.root_y=*/static_cast<int16_t>(GetParam().m_y + s_defaultWindowGeometry.y),
|
||||
/*.win_x=*/GetParam().m_x,
|
||||
/*.win_y=*/GetParam().m_y,
|
||||
/*.mask=*/{},
|
||||
/*.pad0[2]=*/{},
|
||||
};
|
||||
|
||||
// Querying the root window's pointer gives its absolute value
|
||||
const xcb_query_pointer_reply_t rootWindowQueryPointerReply{
|
||||
/*.response_type=*/XCB_QUERY_POINTER,
|
||||
/*.same_screen=*/1,
|
||||
/*.sequence=*/0,
|
||||
/*.length=*/1,
|
||||
/*.root=*/s_rootWindow,
|
||||
/*.child=*/s_rootWindow,
|
||||
/*.root_x=*/static_cast<int16_t>(GetParam().m_x + s_defaultWindowGeometry.x),
|
||||
/*.root_y=*/static_cast<int16_t>(GetParam().m_y + s_defaultWindowGeometry.y),
|
||||
/*.win_x=*/static_cast<int16_t>(GetParam().m_x + s_defaultWindowGeometry.x),
|
||||
/*.win_y=*/static_cast<int16_t>(GetParam().m_y + s_defaultWindowGeometry.y),
|
||||
/*.mask=*/{},
|
||||
/*.pad0[2]=*/{},
|
||||
};
|
||||
|
||||
EXPECT_CALL(m_interface, xcb_get_property_value(Field(&xcb_get_property_reply_t::sequence, Eq(s_getActiveWindowPropertySequence))))
|
||||
.WillRepeatedly(Return(&focusWindow));
|
||||
|
||||
EXPECT_CALL(m_interface, xcb_query_pointer(&m_connection, focusWindow))
|
||||
.WillRepeatedly(Return(xcb_query_pointer_cookie_t{/*.sequence=*/1}));
|
||||
EXPECT_CALL(m_interface, xcb_query_pointer_reply(&m_connection, Field(&xcb_query_pointer_cookie_t::sequence, 1), _))
|
||||
.WillRepeatedly(ReturnMalloc<xcb_query_pointer_reply_t>(queryPointerReply));
|
||||
|
||||
EXPECT_CALL(m_interface, xcb_query_pointer(&m_connection, s_rootWindow))
|
||||
.WillRepeatedly(Return(xcb_query_pointer_cookie_t{/*.sequence=*/2}));
|
||||
EXPECT_CALL(m_interface, xcb_query_pointer_reply(&m_connection, Field(&xcb_query_pointer_cookie_t::sequence, 2), _))
|
||||
.WillRepeatedly(ReturnMalloc<xcb_query_pointer_reply_t>(rootWindowQueryPointerReply));
|
||||
|
||||
m_application.Start();
|
||||
InputSystemCursorRequestBus::Event(
|
||||
InputDeviceMouse::Id,
|
||||
&InputSystemCursorRequests::SetSystemCursorState,
|
||||
SystemCursorState::ConstrainedAndHidden);
|
||||
|
||||
AZ::Vector2 systemCursorPositionNormalized = AZ::Vector2::CreateZero();
|
||||
InputSystemCursorRequestBus::EventResult(
|
||||
systemCursorPositionNormalized,
|
||||
InputDeviceMouse::Id,
|
||||
&InputSystemCursorRequests::GetSystemCursorPositionNormalized);
|
||||
|
||||
EXPECT_THAT(systemCursorPositionNormalized, ::testing::AllOf(
|
||||
testing::Property(&AZ::Vector2::GetX, testing::FloatEq(static_cast<float>(GetParam().m_x) / s_defaultWindowGeometry.width)),
|
||||
testing::Property(&AZ::Vector2::GetY, testing::FloatEq(static_cast<float>(GetParam().m_y) / s_defaultWindowGeometry.height))
|
||||
));
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
AllPointerPositions,
|
||||
XcbGetSystemCursorPositionTests,
|
||||
testing::Values(
|
||||
// Default mocked window geometry sets width and height to 100, all
|
||||
// parameter values should be within [0, 100)
|
||||
GetCursorPositionParam{ 50, 50 },
|
||||
GetCursorPositionParam{ 25, 25 },
|
||||
GetCursorPositionParam{ 0, 100 }
|
||||
)
|
||||
);
|
||||
} // namespace AzFramework
|
||||
@@ -17,5 +17,6 @@ set(FILES
|
||||
XcbBaseTestFixture.cpp
|
||||
XcbBaseTestFixture.h
|
||||
XcbInputDeviceKeyboardTests.cpp
|
||||
XcbInputDeviceMouseTests.cpp
|
||||
XcbTestApplication.h
|
||||
)
|
||||
|
||||
@@ -87,7 +87,7 @@ namespace AzGameFramework
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
|
||||
#endif
|
||||
|
||||
// Used the lowercase the platform name since the bootstrap.game.<config>.<platform>.setreg is being loaded
|
||||
// Used the lowercase the platform name since the bootstrap.game.<config>.setreg is being loaded
|
||||
// from the asset cache root where all the files are in lowercased from regardless of the filesystem case-sensitivity
|
||||
static constexpr char filename[] = "bootstrap.game." AZ_BUILD_CONFIGURATION_TYPE ".setreg";
|
||||
|
||||
|
||||
+4
@@ -58,6 +58,10 @@ namespace AzToolsFramework
|
||||
//! @return The highest closed entity container id if any, or entityId otherwise.
|
||||
virtual AZ::EntityId FindHighestSelectableEntity(AZ::EntityId entityId) const = 0;
|
||||
|
||||
//! Triggers the OnContainerEntityStatusChanged notifications for all registered containers,
|
||||
//! allowing listeners to update correctly.
|
||||
virtual void RefreshAllContainerEntities(AzFramework::EntityContextId entityContextId) const = 0;
|
||||
|
||||
//! Clears all open state information for Container Entities for the EntityContextId provided.
|
||||
//! Used when context is switched, for example in the case of a new root prefab being loaded
|
||||
//! in place of an old one.
|
||||
|
||||
+9
@@ -142,6 +142,15 @@ namespace AzToolsFramework
|
||||
Clear(editorEntityContextId);
|
||||
}
|
||||
|
||||
void ContainerEntitySystemComponent::RefreshAllContainerEntities([[maybe_unused]] AzFramework::EntityContextId entityContextId) const
|
||||
{
|
||||
for (AZ::EntityId containerEntityId : m_containers)
|
||||
{
|
||||
ContainerEntityNotificationBus::Broadcast(
|
||||
&ContainerEntityNotificationBus::Events::OnContainerEntityStatusChanged, containerEntityId, m_openContainers.contains(containerEntityId));
|
||||
}
|
||||
}
|
||||
|
||||
ContainerEntityOperationResult ContainerEntitySystemComponent::Clear(AzFramework::EntityContextId entityContextId)
|
||||
{
|
||||
// We don't yet support multiple entity contexts, so only clear the default.
|
||||
|
||||
+1
@@ -47,6 +47,7 @@ namespace AzToolsFramework
|
||||
ContainerEntityOperationResult SetContainerOpen(AZ::EntityId entityId, bool open) override;
|
||||
bool IsContainerOpen(AZ::EntityId entityId) const override;
|
||||
AZ::EntityId FindHighestSelectableEntity(AZ::EntityId entityId) const override;
|
||||
void RefreshAllContainerEntities(AzFramework::EntityContextId entityContextId) const override;
|
||||
ContainerEntityOperationResult Clear(AzFramework::EntityContextId entityContextId) override;
|
||||
bool IsUnderClosedContainerEntity(AZ::EntityId entityId) const override;
|
||||
|
||||
|
||||
+1
-2
@@ -46,11 +46,10 @@ namespace AzToolsFramework
|
||||
//! Updates the template links (updating instances) for the given template and triggers propagation on its instances.
|
||||
//! @param providedPatch The patch to apply to the template.
|
||||
//! @param templateId The id of the template to update.
|
||||
//! @param immediate An optional flag whether to apply the patch immediately (needed for Undo/Redos) or wait until next system tick.
|
||||
//! @param instanceToExclude An optional reference to an instance of the template being updated that should not be refreshes as part of propagation.
|
||||
//! Defaults to nullopt, which means that all instances will be refreshed.
|
||||
//! @return True if the template was patched correctly, false if the operation failed.
|
||||
virtual bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
|
||||
virtual bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
|
||||
|
||||
virtual void ApplyPatchesToInstance(const AZ::EntityId& entityId, PrefabDom& patches, const Instance& instanceToAddPatches) = 0;
|
||||
|
||||
|
||||
+2
-2
@@ -156,7 +156,7 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
bool InstanceToTemplatePropagator::PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, bool immediate, InstanceOptionalReference instanceToExclude)
|
||||
bool InstanceToTemplatePropagator::PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalReference instanceToExclude)
|
||||
{
|
||||
PrefabDom& templateDomReference = m_prefabSystemComponentInterface->FindTemplateDom(templateId);
|
||||
|
||||
@@ -178,7 +178,7 @@ namespace AzToolsFramework
|
||||
(result.GetOutcome() != AZ::JsonSerializationResult::Outcomes::PartialSkip),
|
||||
"Some of the patches were not successfully applied.");
|
||||
m_prefabSystemComponentInterface->SetTemplateDirtyFlag(templateId, true);
|
||||
m_prefabSystemComponentInterface->PropagateTemplateChanges(templateId, immediate, instanceToExclude);
|
||||
m_prefabSystemComponentInterface->PropagateTemplateChanges(templateId, instanceToExclude);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ namespace AzToolsFramework
|
||||
|
||||
InstanceOptionalReference GetTopMostInstanceInHierarchy(AZ::EntityId entityId) override;
|
||||
|
||||
bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
|
||||
bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
|
||||
|
||||
void ApplyPatchesToInstance(const AZ::EntityId& entityId, PrefabDom& patches, const Instance& instanceToAddPatches) override;
|
||||
|
||||
|
||||
+1
-6
@@ -52,7 +52,7 @@ namespace AzToolsFramework
|
||||
AZ::Interface<InstanceUpdateExecutorInterface>::Unregister(this);
|
||||
}
|
||||
|
||||
void InstanceUpdateExecutor::AddTemplateInstancesToQueue(TemplateId instanceTemplateId, bool immediate, InstanceOptionalReference instanceToExclude)
|
||||
void InstanceUpdateExecutor::AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalReference instanceToExclude)
|
||||
{
|
||||
auto findInstancesResult =
|
||||
m_templateInstanceMapperInterface->FindInstancesOwnedByTemplate(instanceTemplateId);
|
||||
@@ -79,11 +79,6 @@ namespace AzToolsFramework
|
||||
m_instancesUpdateQueue.emplace_back(instance);
|
||||
}
|
||||
}
|
||||
|
||||
if (immediate)
|
||||
{
|
||||
UpdateTemplateInstancesInQueue();
|
||||
}
|
||||
}
|
||||
|
||||
void InstanceUpdateExecutor::RemoveTemplateInstanceFromQueue(const Instance* instance)
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ namespace AzToolsFramework
|
||||
|
||||
explicit InstanceUpdateExecutor(int instanceCountToUpdateInBatch = 0);
|
||||
|
||||
void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
|
||||
void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
|
||||
bool UpdateTemplateInstancesInQueue() override;
|
||||
virtual void RemoveTemplateInstanceFromQueue(const Instance* instance) override;
|
||||
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ namespace AzToolsFramework
|
||||
virtual ~InstanceUpdateExecutorInterface() = default;
|
||||
|
||||
// Add all Instances of Template with given Id into a queue for updating them later.
|
||||
virtual void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
|
||||
virtual void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
|
||||
|
||||
// Update Instances in the waiting queue.
|
||||
virtual bool UpdateTemplateInstancesInQueue() = 0;
|
||||
|
||||
@@ -86,6 +86,45 @@ namespace AzToolsFramework::Prefab
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
PrefabFocusOperationResult PrefabFocusHandler::FocusOnParentOfFocusedPrefab(
|
||||
[[maybe_unused]] AzFramework::EntityContextId entityContextId)
|
||||
{
|
||||
// If only one instance is in the hierarchy, this operation is invalid
|
||||
size_t hierarchySize = m_instanceFocusHierarchy.size();
|
||||
if (hierarchySize <= 1)
|
||||
{
|
||||
return AZ::Failure(
|
||||
AZStd::string("Prefab Focus Handler: Could not complete FocusOnParentOfFocusedPrefab operation while focusing on the root."));
|
||||
}
|
||||
|
||||
// Retrieve parent of currently focused prefab.
|
||||
InstanceOptionalReference parentInstance = m_instanceFocusHierarchy[hierarchySize - 2];
|
||||
|
||||
// Use container entity of parent Instance for focus operations.
|
||||
AZ::EntityId entityId = parentInstance->get().GetContainerEntityId();
|
||||
|
||||
// Initialize Undo Batch object
|
||||
ScopedUndoBatch undoBatch("Edit Prefab");
|
||||
|
||||
// Clear selection
|
||||
{
|
||||
const EntityIdList selectedEntities = EntityIdList{};
|
||||
auto selectionUndo = aznew SelectionCommand(selectedEntities, "Clear Selection");
|
||||
selectionUndo->SetParent(undoBatch.GetUndoBatch());
|
||||
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, selectedEntities);
|
||||
}
|
||||
|
||||
// Edit Prefab
|
||||
{
|
||||
auto editUndo = aznew PrefabFocusUndo("Edit Prefab");
|
||||
editUndo->Capture(entityId);
|
||||
editUndo->SetParent(undoBatch.GetUndoBatch());
|
||||
FocusOnPrefabInstanceOwningEntityId(entityId);
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
|
||||
PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index)
|
||||
{
|
||||
if (index < 0 || index >= m_instanceFocusHierarchy.size())
|
||||
|
||||
@@ -50,6 +50,7 @@ namespace AzToolsFramework::Prefab
|
||||
|
||||
// PrefabFocusPublicInterface overrides ...
|
||||
PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) override;
|
||||
PrefabFocusOperationResult FocusOnParentOfFocusedPrefab(AzFramework::EntityContextId entityContextId) override;
|
||||
PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) override;
|
||||
AZ::EntityId GetFocusedPrefabContainerEntityId(AzFramework::EntityContextId entityContextId) const override;
|
||||
bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const override;
|
||||
|
||||
@@ -30,6 +30,9 @@ namespace AzToolsFramework::Prefab
|
||||
//! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on.
|
||||
virtual PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) = 0;
|
||||
|
||||
//! Set the focused prefab instance to the parent of the currently focused prefab instance. Supports undo/redo.
|
||||
virtual PrefabFocusOperationResult FocusOnParentOfFocusedPrefab(AzFramework::EntityContextId entityContextId) = 0;
|
||||
|
||||
//! Set the focused prefab instance to the instance at position index of the current path. Supports undo/redo.
|
||||
//! @param index The index of the instance in the current path that we want the prefab system to focus on.
|
||||
virtual PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) = 0;
|
||||
|
||||
@@ -1061,7 +1061,7 @@ namespace AzToolsFramework
|
||||
DuplicateNestedEntitiesInInstance(commonOwningInstance->get(),
|
||||
entities, instanceDomAfter, duplicatedEntityAndInstanceIds, duplicateEntityAliasMap);
|
||||
|
||||
PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity/Instance duplication", false);
|
||||
PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity/Instance duplication");
|
||||
command->SetParent(undoBatch.GetUndoBatch());
|
||||
command->Capture(instanceDomBefore, instanceDomAfter, commonOwningInstance->get().GetTemplateId());
|
||||
command->Redo();
|
||||
@@ -1333,7 +1333,7 @@ namespace AzToolsFramework
|
||||
Prefab::PrefabDom instanceDomAfter;
|
||||
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, parentInstance);
|
||||
|
||||
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment", false);
|
||||
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment");
|
||||
command->Capture(instanceDomBefore, instanceDomAfter, parentTemplateId);
|
||||
command->SetParent(undoBatch.GetUndoBatch());
|
||||
{
|
||||
|
||||
@@ -159,10 +159,10 @@ namespace AzToolsFramework
|
||||
newInstance->SetTemplateId(newTemplateId);
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, bool immediate, InstanceOptionalReference instanceToExclude)
|
||||
|
||||
void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, InstanceOptionalReference instanceToExclude)
|
||||
{
|
||||
UpdatePrefabInstances(templateId, immediate, instanceToExclude);
|
||||
UpdatePrefabInstances(templateId, instanceToExclude);
|
||||
|
||||
auto templateIdToLinkIdsIterator = m_templateToLinkIdsMap.find(templateId);
|
||||
if (templateIdToLinkIdsIterator != m_templateToLinkIdsMap.end())
|
||||
@@ -191,9 +191,9 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabSystemComponent::UpdatePrefabInstances(TemplateId templateId, bool immediate, InstanceOptionalReference instanceToExclude)
|
||||
void PrefabSystemComponent::UpdatePrefabInstances(TemplateId templateId, InstanceOptionalReference instanceToExclude)
|
||||
{
|
||||
m_instanceUpdateExecutor.AddTemplateInstancesToQueue(templateId, immediate, instanceToExclude);
|
||||
m_instanceUpdateExecutor.AddTemplateInstancesToQueue(templateId, instanceToExclude);
|
||||
}
|
||||
|
||||
void PrefabSystemComponent::UpdateLinkedInstances(AZStd::queue<LinkIds>& linkIdsQueue)
|
||||
|
||||
@@ -231,17 +231,16 @@ namespace AzToolsFramework
|
||||
*/
|
||||
void UpdatePrefabTemplate(TemplateId templateId, const PrefabDom& updatedDom) override;
|
||||
|
||||
void PropagateTemplateChanges(TemplateId templateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
|
||||
void PropagateTemplateChanges(TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
|
||||
|
||||
/**
|
||||
* Updates all Instances owned by a Template.
|
||||
*
|
||||
* @param templateId The id of the Template owning Instances to update.
|
||||
* @param immediate An optional flag whether to apply the patch immediately (needed for Undo/Redos) or wait until next system tick.
|
||||
* @param instanceToExclude An optional reference to an instance of the template being updated that should not be refreshes as part of propagation.
|
||||
* Defaults to nullopt, which means that all instances will be refreshed.
|
||||
* @param instanceToExclude An optional reference to an instance of the template being updated that should not be refreshed
|
||||
* as part of propagation.Defaults to nullopt, which means that all instances will be refreshed.
|
||||
*/
|
||||
void UpdatePrefabInstances(TemplateId templateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt);
|
||||
void UpdatePrefabInstances(TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt);
|
||||
|
||||
private:
|
||||
AZ_DISABLE_COPY_MOVE(PrefabSystemComponent);
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ namespace AzToolsFramework
|
||||
|
||||
virtual PrefabDom& FindTemplateDom(TemplateId templateId) = 0;
|
||||
virtual void UpdatePrefabTemplate(TemplateId templateId, const PrefabDom& updatedDom) = 0;
|
||||
virtual void PropagateTemplateChanges(TemplateId templateId, bool immediate = false, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
|
||||
virtual void PropagateTemplateChanges(TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
|
||||
|
||||
virtual AZStd::unique_ptr<Instance> InstantiatePrefab(
|
||||
AZ::IO::PathView filePath, InstanceOptionalReference parent = AZStd::nullopt) = 0;
|
||||
|
||||
@@ -17,16 +17,17 @@ namespace AzToolsFramework
|
||||
{
|
||||
PrefabUndoBase::PrefabUndoBase(const AZStd::string& undoOperationName)
|
||||
: UndoSystem::URSequencePoint(undoOperationName)
|
||||
, m_changed(true)
|
||||
, m_templateId(InvalidTemplateId)
|
||||
{
|
||||
m_instanceToTemplateInterface = AZ::Interface<InstanceToTemplateInterface>::Get();
|
||||
AZ_Assert(m_instanceToTemplateInterface, "Failed to grab instance to template interface");
|
||||
}
|
||||
|
||||
//PrefabInstanceUndo
|
||||
PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName, bool useImmediatePropagation)
|
||||
PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName)
|
||||
: PrefabUndoBase(undoOperationName)
|
||||
{
|
||||
m_useImmediatePropagation = useImmediatePropagation;
|
||||
}
|
||||
|
||||
void PrefabUndoInstance::Capture(
|
||||
@@ -42,12 +43,12 @@ namespace AzToolsFramework
|
||||
|
||||
void PrefabUndoInstance::Undo()
|
||||
{
|
||||
m_instanceToTemplateInterface->PatchTemplate(m_undoPatch, m_templateId, m_useImmediatePropagation);
|
||||
m_instanceToTemplateInterface->PatchTemplate(m_undoPatch, m_templateId);
|
||||
}
|
||||
|
||||
void PrefabUndoInstance::Redo()
|
||||
{
|
||||
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId, m_useImmediatePropagation);
|
||||
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId);
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +91,7 @@ namespace AzToolsFramework
|
||||
void PrefabUndoEntityUpdate::Undo()
|
||||
{
|
||||
[[maybe_unused]] bool isPatchApplicationSuccessful =
|
||||
m_instanceToTemplateInterface->PatchTemplate(m_undoPatch, m_templateId, true);
|
||||
m_instanceToTemplateInterface->PatchTemplate(m_undoPatch, m_templateId);
|
||||
|
||||
AZ_Error(
|
||||
"Prefab", isPatchApplicationSuccessful,
|
||||
@@ -101,7 +102,7 @@ namespace AzToolsFramework
|
||||
void PrefabUndoEntityUpdate::Redo()
|
||||
{
|
||||
[[maybe_unused]] bool isPatchApplicationSuccessful =
|
||||
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId, true);
|
||||
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId);
|
||||
|
||||
AZ_Error(
|
||||
"Prefab", isPatchApplicationSuccessful,
|
||||
@@ -112,7 +113,7 @@ namespace AzToolsFramework
|
||||
void PrefabUndoEntityUpdate::Redo(InstanceOptionalReference instanceToExclude)
|
||||
{
|
||||
[[maybe_unused]] bool isPatchApplicationSuccessful =
|
||||
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId, false, instanceToExclude);
|
||||
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId, instanceToExclude);
|
||||
|
||||
AZ_Error(
|
||||
"Prefab", isPatchApplicationSuccessful,
|
||||
@@ -328,7 +329,7 @@ namespace AzToolsFramework
|
||||
|
||||
//propagate the link changes
|
||||
link->get().UpdateTarget();
|
||||
m_prefabSystemComponentInterface->PropagateTemplateChanges(link->get().GetTargetTemplateId(), false, instanceToExclude);
|
||||
m_prefabSystemComponentInterface->PropagateTemplateChanges(link->get().GetTargetTemplateId(), instanceToExclude);
|
||||
|
||||
//mark as dirty
|
||||
m_prefabSystemComponentInterface->SetTemplateDirtyFlag(link->get().GetTargetTemplateId(), true);
|
||||
|
||||
@@ -29,15 +29,14 @@ namespace AzToolsFramework
|
||||
bool Changed() const override { return m_changed; }
|
||||
|
||||
protected:
|
||||
TemplateId m_templateId = InvalidTemplateId;
|
||||
TemplateId m_templateId;
|
||||
|
||||
PrefabDom m_redoPatch;
|
||||
PrefabDom m_undoPatch;
|
||||
|
||||
InstanceToTemplateInterface* m_instanceToTemplateInterface = nullptr;
|
||||
|
||||
bool m_changed = true;
|
||||
bool m_useImmediatePropagation = true;
|
||||
bool m_changed;
|
||||
};
|
||||
|
||||
//! handles the addition and removal of entities from instances
|
||||
@@ -45,7 +44,7 @@ namespace AzToolsFramework
|
||||
: public PrefabUndoBase
|
||||
{
|
||||
public:
|
||||
explicit PrefabUndoInstance(const AZStd::string& undoOperationName, bool useImmediatePropagation = true);
|
||||
explicit PrefabUndoInstance(const AZStd::string& undoOperationName);
|
||||
|
||||
void Capture(
|
||||
const PrefabDom& initialState,
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace AzToolsFramework
|
||||
PrefabDom instanceDomAfterUpdate;
|
||||
PrefabDomUtils::StoreInstanceInPrefabDom(instance, instanceDomAfterUpdate);
|
||||
|
||||
PrefabUndoInstance* state = aznew Prefab::PrefabUndoInstance(undoMessage, false);
|
||||
PrefabUndoInstance* state = aznew Prefab::PrefabUndoInstance(undoMessage);
|
||||
state->Capture(instanceDomBeforeUpdate, instanceDomAfterUpdate, instance.GetTemplateId());
|
||||
state->SetParent(undoBatch);
|
||||
state->Redo();
|
||||
|
||||
+1
-12
@@ -2180,20 +2180,9 @@ namespace AzToolsFramework
|
||||
|
||||
void EntityOutlinerItemDelegate::PaintAncestorForegrounds(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
|
||||
{
|
||||
// Go through ancestors and add them to the stack
|
||||
AZStd::stack<QModelIndex> handlerStack;
|
||||
|
||||
// Ancestor foregrounds are painted on top of the childrens'.
|
||||
for (QModelIndex ancestorIndex = index.parent(); ancestorIndex.isValid(); ancestorIndex = ancestorIndex.parent())
|
||||
{
|
||||
handlerStack.push(ancestorIndex);
|
||||
}
|
||||
|
||||
// Apply the ancestor overrides from top to bottom
|
||||
while (!handlerStack.empty())
|
||||
{
|
||||
QModelIndex ancestorIndex = handlerStack.top();
|
||||
handlerStack.pop();
|
||||
|
||||
AZ::EntityId ancestorEntityId(ancestorIndex.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
|
||||
auto ancestorUiHandler = m_editorEntityFrameworkInterface->GetHandler(ancestorEntityId);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <AzToolsFramework/UI/Prefab/LevelRootUiHandler.h>
|
||||
|
||||
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
|
||||
#include <AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx>
|
||||
|
||||
@@ -92,4 +93,16 @@ namespace AzToolsFramework
|
||||
painter->drawLine(rect.bottomLeft(), rect.bottomRight());
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
bool LevelRootUiHandler::OnEntityDoubleClick(AZ::EntityId entityId) const
|
||||
{
|
||||
if (auto prefabFocusPublicInterface = AZ::Interface<Prefab::PrefabFocusPublicInterface>::Get();
|
||||
!prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
|
||||
{
|
||||
prefabFocusPublicInterface->FocusOnOwningPrefab(entityId);
|
||||
}
|
||||
|
||||
// Don't propagate event.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace AzToolsFramework
|
||||
bool CanToggleLockVisibility(AZ::EntityId entityId) const override;
|
||||
bool CanRename(AZ::EntityId entityId) const override;
|
||||
void PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
|
||||
bool OnEntityDoubleClick(AZ::EntityId entityId) const override;
|
||||
|
||||
private:
|
||||
Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr;
|
||||
|
||||
+127
-15
@@ -35,6 +35,7 @@
|
||||
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiInterface.h>
|
||||
#include <AzToolsFramework/UI/Prefab/PrefabIntegrationInterface.h>
|
||||
#include <AzToolsFramework/UI/UICore/WidgetHelpers.h>
|
||||
#include <AzToolsFramework/Viewport/ActionBus.h>
|
||||
|
||||
#include <AzQtComponents/Components/Widgets/CheckBox.h>
|
||||
#include <AzQtComponents/Components/FlowLayout.h>
|
||||
@@ -54,6 +55,7 @@
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QScrollArea>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
|
||||
@@ -61,6 +63,8 @@ namespace AzToolsFramework
|
||||
{
|
||||
namespace Prefab
|
||||
{
|
||||
AzFramework::EntityContextId PrefabIntegrationManager::s_editorEntityContextId = AzFramework::EntityContextId::CreateNull();
|
||||
|
||||
ContainerEntityInterface* PrefabIntegrationManager::s_containerEntityInterface = nullptr;
|
||||
EditorEntityUiInterface* PrefabIntegrationManager::s_editorEntityUiInterface = nullptr;
|
||||
PrefabFocusPublicInterface* PrefabIntegrationManager::s_prefabFocusPublicInterface = nullptr;
|
||||
@@ -136,6 +140,9 @@ namespace AzToolsFramework
|
||||
return;
|
||||
}
|
||||
|
||||
// Get EditorEntityContextId
|
||||
EditorEntityContextRequestBus::BroadcastResult(s_editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
|
||||
|
||||
// Initialize Editor functionality for the Prefab Focus Handler
|
||||
auto prefabFocusInterface = AZ::Interface<PrefabFocusInterface>::Get();
|
||||
prefabFocusInterface->InitializeEditorInterfaces();
|
||||
@@ -145,10 +152,16 @@ namespace AzToolsFramework
|
||||
PrefabInstanceContainerNotificationBus::Handler::BusConnect();
|
||||
AZ::Interface<PrefabIntegrationInterface>::Register(this);
|
||||
AssetBrowser::AssetBrowserSourceDropBus::Handler::BusConnect(s_prefabFileExtension);
|
||||
EditorEntityContextNotificationBus::Handler::BusConnect();
|
||||
|
||||
InitializeShortcuts();
|
||||
}
|
||||
|
||||
PrefabIntegrationManager::~PrefabIntegrationManager()
|
||||
{
|
||||
UninitializeShortcuts();
|
||||
|
||||
EditorEntityContextNotificationBus::Handler::BusDisconnect();
|
||||
AssetBrowser::AssetBrowserSourceDropBus::Handler::BusDisconnect();
|
||||
AZ::Interface<PrefabIntegrationInterface>::Unregister(this);
|
||||
PrefabInstanceContainerNotificationBus::Handler::BusDisconnect();
|
||||
@@ -161,6 +174,74 @@ namespace AzToolsFramework
|
||||
PrefabUserSettings::Reflect(context);
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::InitializeShortcuts()
|
||||
{
|
||||
// Open/Edit Prefab (+)
|
||||
// We also support = to enable easier editing on compact US keyboards.
|
||||
{
|
||||
m_actions.emplace_back(AZStd::make_unique<QAction>(nullptr));
|
||||
|
||||
m_actions.back()->setShortcuts({ QKeySequence(Qt::Key_Plus), QKeySequence(Qt::Key_Equal) });
|
||||
m_actions.back()->setText("Open/Edit Prefab");
|
||||
m_actions.back()->setStatusTip("Edit the prefab in focus mode.");
|
||||
|
||||
QObject::connect(
|
||||
m_actions.back().get(), &QAction::triggered, m_actions.back().get(),
|
||||
[]
|
||||
{
|
||||
AzToolsFramework::EntityIdList selectedEntities;
|
||||
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
|
||||
selectedEntities, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities);
|
||||
|
||||
if (selectedEntities.size() != 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
AZ::EntityId selectedEntity = selectedEntities[0];
|
||||
|
||||
if (!s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity))
|
||||
{
|
||||
ContextMenu_EditPrefab(selectedEntity);
|
||||
}
|
||||
});
|
||||
|
||||
EditorActionRequestBus::Broadcast(
|
||||
&EditorActionRequests::AddActionViaBusCrc, AZ_CRC_CE("com.o3de.action.editortransform.prefabopen"),
|
||||
m_actions.back().get());
|
||||
}
|
||||
|
||||
// Close Prefab (-)
|
||||
{
|
||||
m_actions.emplace_back(AZStd::make_unique<QAction>(nullptr));
|
||||
|
||||
m_actions.back()->setShortcuts({ QKeySequence(Qt::Key_Minus) });
|
||||
m_actions.back()->setText("Close Prefab");
|
||||
m_actions.back()->setStatusTip("Close focus mode for this prefab and move one level up.");
|
||||
|
||||
QObject::connect(
|
||||
m_actions.back().get(), &QAction::triggered, m_actions.back().get(),
|
||||
[]
|
||||
{
|
||||
ContextMenu_ClosePrefab();
|
||||
});
|
||||
|
||||
EditorActionRequestBus::Broadcast(
|
||||
&EditorActionRequests::AddActionViaBusCrc, AZ_CRC_CE("com.o3de.action.editortransform.prefabclose"),
|
||||
m_actions.back().get());
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::UninitializeShortcuts()
|
||||
{
|
||||
m_actions.clear();
|
||||
}
|
||||
|
||||
int PrefabIntegrationManager::GetMenuPosition() const
|
||||
{
|
||||
return aznumeric_cast<int>(EditorContextMenuOrdering::MIDDLE);
|
||||
@@ -181,16 +262,13 @@ namespace AzToolsFramework
|
||||
AzFramework::ApplicationRequests::Bus::BroadcastResult(
|
||||
prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
|
||||
|
||||
auto editorEntityContextId = AzFramework::EntityContextId::CreateNull();
|
||||
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
|
||||
|
||||
// Create Prefab
|
||||
{
|
||||
if (!selectedEntities.empty())
|
||||
{
|
||||
// Hide if the only selected entity is the Focused Instance Container
|
||||
if (selectedEntities.size() > 1 ||
|
||||
selectedEntities[0] != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId))
|
||||
selectedEntities[0] != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(s_editorEntityContextId))
|
||||
{
|
||||
bool layerInSelection = false;
|
||||
|
||||
@@ -254,17 +332,30 @@ namespace AzToolsFramework
|
||||
|
||||
if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity))
|
||||
{
|
||||
// Edit Prefab
|
||||
if (!s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity))
|
||||
{
|
||||
QAction* editAction = menu->addAction(QObject::tr("Edit Prefab"));
|
||||
// Edit Prefab
|
||||
QAction* editAction = menu->addAction(QObject::tr("Open/Edit Prefab"));
|
||||
editAction->setShortcut(QKeySequence(Qt::Key_Plus));
|
||||
editAction->setToolTip(QObject::tr("Edit the prefab in focus mode."));
|
||||
|
||||
QObject::connect(editAction, &QAction::triggered, editAction, [selectedEntity] {
|
||||
ContextMenu_EditPrefab(selectedEntity);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Close Prefab
|
||||
QAction* closeAction = menu->addAction(QObject::tr("Close Prefab"));
|
||||
closeAction->setShortcut(QKeySequence(Qt::Key_Minus));
|
||||
closeAction->setToolTip(QObject::tr("Close focus mode for this prefab and move one level up."));
|
||||
|
||||
itemWasShown = true;
|
||||
QObject::connect(
|
||||
closeAction, &QAction::triggered, closeAction,
|
||||
[]
|
||||
{
|
||||
ContextMenu_ClosePrefab();
|
||||
});
|
||||
}
|
||||
|
||||
// Save Prefab
|
||||
@@ -279,9 +370,9 @@ namespace AzToolsFramework
|
||||
QObject::connect(saveAction, &QAction::triggered, saveAction, [selectedEntity] {
|
||||
ContextMenu_SavePrefab(selectedEntity);
|
||||
});
|
||||
|
||||
itemWasShown = true;
|
||||
}
|
||||
|
||||
itemWasShown = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,7 +386,8 @@ namespace AzToolsFramework
|
||||
QObject::connect(deleteAction, &QAction::triggered, deleteAction, [] { ContextMenu_DeleteSelected(); });
|
||||
|
||||
if (selectedEntities.empty() ||
|
||||
(selectedEntities.size() == 1 && selectedEntities[0] == s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId)))
|
||||
(selectedEntities.size() == 1 &&
|
||||
selectedEntities[0] == s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(s_editorEntityContextId)))
|
||||
{
|
||||
deleteAction->setDisabled(true);
|
||||
}
|
||||
@@ -306,7 +398,7 @@ namespace AzToolsFramework
|
||||
AZ::EntityId selectedEntityId = selectedEntities[0];
|
||||
|
||||
if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntityId) &&
|
||||
selectedEntityId != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId))
|
||||
selectedEntityId != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(s_editorEntityContextId))
|
||||
{
|
||||
QAction* detachPrefabAction = menu->addAction(QObject::tr("Detach Prefab..."));
|
||||
QObject::connect(
|
||||
@@ -334,6 +426,24 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::OnStartPlayInEditorBegin()
|
||||
{
|
||||
// Focus on the root prefab (AZ::EntityId() will default to it)
|
||||
s_prefabFocusPublicInterface->FocusOnOwningPrefab(AZ::EntityId());
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::OnStopPlayInEditor()
|
||||
{
|
||||
// Refresh all containers when leaving Game Mode to ensure everything is synced.
|
||||
QTimer::singleShot(
|
||||
0,
|
||||
[&]()
|
||||
{
|
||||
s_containerEntityInterface->RefreshAllContainerEntities(s_editorEntityContextId);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::ContextMenu_CreatePrefab(AzToolsFramework::EntityIdList selectedEntities)
|
||||
{
|
||||
// Save a reference to our currently active window since it will be
|
||||
@@ -343,12 +453,9 @@ namespace AzToolsFramework
|
||||
const AZStd::string prefabFilesPath = "@projectroot@/Prefabs";
|
||||
|
||||
// Remove focused instance container entity if it's part of the list
|
||||
auto editorEntityContextId = AzFramework::EntityContextId::CreateNull();
|
||||
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
|
||||
|
||||
auto focusedContainerIter = AZStd::find(
|
||||
selectedEntities.begin(), selectedEntities.end(),
|
||||
s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId));
|
||||
s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(s_editorEntityContextId));
|
||||
if (focusedContainerIter != selectedEntities.end())
|
||||
{
|
||||
selectedEntities.erase(focusedContainerIter);
|
||||
@@ -500,6 +607,11 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::ContextMenu_ClosePrefab()
|
||||
{
|
||||
s_prefabFocusPublicInterface->FocusOnParentOfFocusedPrefab(s_editorEntityContextId);
|
||||
}
|
||||
|
||||
void PrefabIntegrationManager::ContextMenu_EditPrefab(AZ::EntityId containerEntity)
|
||||
{
|
||||
s_prefabFocusPublicInterface->FocusOnOwningPrefab(containerEntity);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
|
||||
#include <AzToolsFramework/AssetBrowser/AssetBrowserSourceDropBus.h>
|
||||
#include <AzToolsFramework/Editor/EditorContextMenuBus.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
|
||||
#include <AzToolsFramework/UI/Prefab/LevelRootUiHandler.h>
|
||||
@@ -56,6 +57,7 @@ namespace AzToolsFramework
|
||||
, public PrefabInstanceContainerNotificationBus::Handler
|
||||
, public PrefabIntegrationInterface
|
||||
, public QObject
|
||||
, private EditorEntityContextNotificationBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(PrefabIntegrationManager, AZ::SystemAllocator, 0);
|
||||
@@ -76,6 +78,10 @@ namespace AzToolsFramework
|
||||
// EntityOutlinerSourceDropHandlingBus overrides ...
|
||||
void HandleSourceFileType(AZStd::string_view sourceFilePath, AZ::EntityId parentId, AZ::Vector3 position) const override;
|
||||
|
||||
// EditorEntityContextNotificationBus overrides ...
|
||||
void OnStartPlayInEditorBegin() override;
|
||||
void OnStopPlayInEditor() override;
|
||||
|
||||
// PrefabInstanceContainerNotificationBus overrides ...
|
||||
void OnPrefabComponentActivate(AZ::EntityId entityId) override;
|
||||
void OnPrefabComponentDeactivate(AZ::EntityId entityId) override;
|
||||
@@ -96,11 +102,16 @@ namespace AzToolsFramework
|
||||
static void ContextMenu_CreatePrefab(AzToolsFramework::EntityIdList selectedEntities);
|
||||
static void ContextMenu_InstantiatePrefab();
|
||||
static void ContextMenu_InstantiateProceduralPrefab();
|
||||
static void ContextMenu_ClosePrefab();
|
||||
static void ContextMenu_EditPrefab(AZ::EntityId containerEntity);
|
||||
static void ContextMenu_SavePrefab(AZ::EntityId containerEntity);
|
||||
static void ContextMenu_DeleteSelected();
|
||||
static void ContextMenu_DetachPrefab(AZ::EntityId containerEntity);
|
||||
|
||||
// Shortcut setup handlers
|
||||
void InitializeShortcuts();
|
||||
void UninitializeShortcuts();
|
||||
|
||||
// Prompt and resolve dialogs
|
||||
static bool QueryUserForPrefabSaveLocation(
|
||||
const AZStd::string& suggestedName, const char* initialTargetDirectory, AZ::u32 prefabUserSettingsId, QWidget* activeWindow,
|
||||
@@ -140,7 +151,10 @@ namespace AzToolsFramework
|
||||
AZStd::unique_ptr<QDialog> ConstructSavePrefabDialog(TemplateId templateId, bool useSaveAllPrefabsPreference);
|
||||
void SavePrefabsInDialog(QDialog* unsavedPrefabsDialog);
|
||||
|
||||
AZStd::vector<AZStd::unique_ptr<QAction>> m_actions;
|
||||
|
||||
static const AZStd::string s_prefabFileExtension;
|
||||
static AzFramework::EntityContextId s_editorEntityContextId;
|
||||
|
||||
static ContainerEntityInterface* s_containerEntityInterface;
|
||||
static EditorEntityUiInterface* s_editorEntityUiInterface;
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
AzFramework::EntityContextId PrefabUiHandler::s_editorEntityContextId = AzFramework::EntityContextId::CreateNull();
|
||||
|
||||
const QColor PrefabUiHandler::m_backgroundColor = QColor("#444444");
|
||||
const QColor PrefabUiHandler::m_backgroundHoverColor = QColor("#5A5A5A");
|
||||
const QColor PrefabUiHandler::m_backgroundSelectedColor = QColor("#656565");
|
||||
@@ -47,6 +49,9 @@ namespace AzToolsFramework
|
||||
AZ_Assert(false, "PrefabUiHandler - could not get PrefabFocusPublicInterface on PrefabUiHandler construction.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get EditorEntityContextId
|
||||
EditorEntityContextRequestBus::BroadcastResult(s_editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
|
||||
}
|
||||
|
||||
QString PrefabUiHandler::GenerateItemInfoString(AZ::EntityId entityId) const
|
||||
@@ -180,7 +185,7 @@ namespace AzToolsFramework
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
void PrefabUiHandler::PaintDescendantBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index,
|
||||
void PrefabUiHandler::PaintDescendantForeground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index,
|
||||
const QModelIndex& descendantIndex) const
|
||||
{
|
||||
if (!painter)
|
||||
@@ -425,19 +430,23 @@ namespace AzToolsFramework
|
||||
|
||||
if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
|
||||
{
|
||||
auto editorEntityContextId = AzFramework::EntityContextId::CreateNull();
|
||||
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
|
||||
|
||||
// Go one level up.
|
||||
int length = m_prefabFocusPublicInterface->GetPrefabFocusPathLength(editorEntityContextId);
|
||||
m_prefabFocusPublicInterface->FocusOnPathIndex(editorEntityContextId, length - 2);
|
||||
// Close this prefab and focus on the parent
|
||||
m_prefabFocusPublicInterface->FocusOnParentOfFocusedPrefab(s_editorEntityContextId);
|
||||
}
|
||||
}
|
||||
|
||||
bool PrefabUiHandler::OnEntityDoubleClick(AZ::EntityId entityId) const
|
||||
{
|
||||
// Focus on this prefab
|
||||
m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId);
|
||||
if (!m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
|
||||
{
|
||||
// Focus on this prefab
|
||||
m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Close this prefab and focus on the parent
|
||||
m_prefabFocusPublicInterface->FocusOnParentOfFocusedPrefab(s_editorEntityContextId);
|
||||
}
|
||||
|
||||
// Don't propagate event.
|
||||
return true;
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.h>
|
||||
|
||||
#include <AzFramework/Entity/EntityContextBus.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
|
||||
@@ -34,9 +36,12 @@ namespace AzToolsFramework
|
||||
QString GenerateItemTooltip(AZ::EntityId entityId) const override;
|
||||
QIcon GenerateItemIcon(AZ::EntityId entityId) const override;
|
||||
void PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
|
||||
void PaintDescendantBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index,
|
||||
const QModelIndex& descendantIndex) const override;
|
||||
void PaintItemForeground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
|
||||
void PaintDescendantForeground(
|
||||
QPainter* painter,
|
||||
const QStyleOptionViewItem& option,
|
||||
const QModelIndex& index,
|
||||
const QModelIndex& descendantIndex) const override;
|
||||
bool OnOutlinerItemClick(const QPoint& position, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
|
||||
void OnOutlinerItemCollapse(const QModelIndex& index) const override;
|
||||
bool OnEntityDoubleClick(AZ::EntityId entityId) const override;
|
||||
@@ -49,6 +54,8 @@ namespace AzToolsFramework
|
||||
static QModelIndex GetLastVisibleChild(const QModelIndex& parent);
|
||||
static QModelIndex Internal_GetLastVisibleChild(const QAbstractItemModel* model, const QModelIndex& index);
|
||||
|
||||
static AzFramework::EntityContextId s_editorEntityContextId;
|
||||
|
||||
static constexpr int m_prefabCapsuleRadius = 6;
|
||||
static constexpr int m_prefabBorderThickness = 2;
|
||||
static const QColor m_backgroundColor;
|
||||
|
||||
+3
-4
@@ -59,12 +59,11 @@ namespace AzToolsFramework::Prefab
|
||||
connect(m_backButton, &QToolButton::clicked, this,
|
||||
[&]()
|
||||
{
|
||||
if (int length = m_prefabFocusPublicInterface->GetPrefabFocusPathLength(m_editorEntityContextId); length > 1)
|
||||
{
|
||||
m_prefabFocusPublicInterface->FocusOnPathIndex(m_editorEntityContextId, length - 2);
|
||||
}
|
||||
m_prefabFocusPublicInterface->FocusOnParentOfFocusedPrefab(m_editorEntityContextId);
|
||||
}
|
||||
);
|
||||
|
||||
m_backButton->setToolTip("Up one level (-)");
|
||||
}
|
||||
|
||||
void PrefabViewportFocusPathHandler::OnPrefabFocusChanged()
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
|
||||
#include <AzToolsFramework/ViewportUi/ViewportUiCluster.h>
|
||||
#include <AzToolsFramework/ViewportUi/ViewportUiDisplay.h>
|
||||
#include <AzToolsFramework/ViewportUi/ViewportUiDisplayLayout.h>
|
||||
#include <AzToolsFramework/ViewportUi/ViewportUiSwitcher.h>
|
||||
#include <AzToolsFramework/ViewportUi/ViewportUiTextField.h>
|
||||
#include <QWidget>
|
||||
@@ -19,7 +20,6 @@
|
||||
namespace AzToolsFramework::ViewportUi::Internal
|
||||
{
|
||||
const static int HighlightBorderSize = 5;
|
||||
const static int TopHighlightBorderSize = 25;
|
||||
const static char* HighlightBorderColor = "#4A90E2";
|
||||
|
||||
static void UnparentWidgets(ViewportUiElementIdInfoLookup& viewportUiElementIdInfoLookup)
|
||||
@@ -61,7 +61,7 @@ namespace AzToolsFramework::ViewportUi::Internal
|
||||
, m_uiOverlay(parent)
|
||||
, m_fullScreenLayout(&m_uiOverlay)
|
||||
, m_uiOverlayLayout()
|
||||
, m_componentModeBorderText(&m_uiOverlay)
|
||||
, m_viewportBorderText(&m_uiOverlay)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -221,11 +221,11 @@ namespace AzToolsFramework::ViewportUi::Internal
|
||||
|
||||
AZStd::shared_ptr<QWidget> ViewportUiDisplay::GetViewportUiElement(ViewportUiElementId elementId)
|
||||
{
|
||||
auto element = m_viewportUiElements.find(elementId);
|
||||
if (element != m_viewportUiElements.end())
|
||||
if (auto element = m_viewportUiElements.find(elementId); element != m_viewportUiElements.end())
|
||||
{
|
||||
return element->second.m_widget;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -287,28 +287,31 @@ namespace AzToolsFramework::ViewportUi::Internal
|
||||
{
|
||||
return element.IsValid() && element.m_widget->isVisible();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ViewportUiDisplay::CreateViewportBorder(const AZStd::string& borderTitle)
|
||||
{
|
||||
const AZStd::string styleSheet = AZStd::string::format(
|
||||
"border: %dpx solid %s; border-top: %dpx solid %s;", HighlightBorderSize, HighlightBorderColor, TopHighlightBorderSize,
|
||||
"border: %dpx solid %s; border-top: %dpx solid %s;", HighlightBorderSize, HighlightBorderColor, ViewportUiTopBorderSize,
|
||||
HighlightBorderColor);
|
||||
m_uiOverlay.setStyleSheet(styleSheet.c_str());
|
||||
m_uiOverlayLayout.setContentsMargins(
|
||||
HighlightBorderSize + ViewportUiOverlayMargin, TopHighlightBorderSize + ViewportUiOverlayMargin,
|
||||
HighlightBorderSize + ViewportUiOverlayMargin, ViewportUiTopBorderSize + ViewportUiOverlayMargin,
|
||||
HighlightBorderSize + ViewportUiOverlayMargin, HighlightBorderSize + ViewportUiOverlayMargin);
|
||||
m_componentModeBorderText.setVisible(true);
|
||||
m_componentModeBorderText.setText(borderTitle.c_str());
|
||||
m_viewportBorderText.setVisible(true);
|
||||
m_viewportBorderText.setText(borderTitle.c_str());
|
||||
UpdateUiOverlayGeometry();
|
||||
}
|
||||
|
||||
void ViewportUiDisplay::RemoveViewportBorder()
|
||||
{
|
||||
m_componentModeBorderText.setVisible(false);
|
||||
m_viewportBorderText.setVisible(false);
|
||||
m_uiOverlay.setStyleSheet("border: none;");
|
||||
m_uiOverlayLayout.setMargin(ViewportUiOverlayMargin);
|
||||
m_uiOverlayLayout.setContentsMargins(
|
||||
ViewportUiOverlayMargin, ViewportUiOverlayMargin + ViewportUiOverlayTopMarginPadding, ViewportUiOverlayMargin,
|
||||
ViewportUiOverlayMargin);
|
||||
}
|
||||
|
||||
void ViewportUiDisplay::PositionViewportUiElementFromWorldSpace(ViewportUiElementId elementId, const AZ::Vector3& pos)
|
||||
@@ -360,10 +363,10 @@ namespace AzToolsFramework::ViewportUi::Internal
|
||||
|
||||
// format the label which will appear on top of the highlight border
|
||||
AZStd::string styleSheet = AZStd::string::format("background-color: %s; border: none;", HighlightBorderColor);
|
||||
m_componentModeBorderText.setStyleSheet(styleSheet.c_str());
|
||||
m_componentModeBorderText.setFixedHeight(TopHighlightBorderSize);
|
||||
m_componentModeBorderText.setVisible(false);
|
||||
m_fullScreenLayout.addWidget(&m_componentModeBorderText, 0, 0, Qt::AlignTop | Qt::AlignHCenter);
|
||||
m_viewportBorderText.setStyleSheet(styleSheet.c_str());
|
||||
m_viewportBorderText.setFixedHeight(ViewportUiTopBorderSize);
|
||||
m_viewportBorderText.setVisible(false);
|
||||
m_fullScreenLayout.addWidget(&m_viewportBorderText, 0, 0, Qt::AlignTop | Qt::AlignHCenter);
|
||||
}
|
||||
|
||||
void ViewportUiDisplay::PrepareWidgetForViewportUi(QPointer<QWidget> widget)
|
||||
@@ -396,14 +399,14 @@ namespace AzToolsFramework::ViewportUi::Internal
|
||||
|
||||
void ViewportUiDisplay::UpdateUiOverlayGeometry()
|
||||
{
|
||||
// add the component mode border region if visible
|
||||
// add the viewport border region if visible
|
||||
QRegion region;
|
||||
if (m_componentModeBorderText.isVisible())
|
||||
if (m_viewportBorderText.isVisible())
|
||||
{
|
||||
// get the border region by taking the entire region and subtracting the non-border area
|
||||
region += m_uiOverlay.rect();
|
||||
region -= QRect(
|
||||
QPoint(m_uiOverlay.rect().left() + HighlightBorderSize, m_uiOverlay.rect().top() + TopHighlightBorderSize),
|
||||
QPoint(m_uiOverlay.rect().left() + HighlightBorderSize, m_uiOverlay.rect().top() + ViewportUiTopBorderSize),
|
||||
QPoint(m_uiOverlay.rect().right() - HighlightBorderSize, m_uiOverlay.rect().bottom() - HighlightBorderSize));
|
||||
}
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ namespace AzToolsFramework::ViewportUi::Internal
|
||||
QWidget m_uiOverlay; //!< The UI Overlay which displays Viewport UI Elements.
|
||||
QGridLayout m_fullScreenLayout; //!< The layout which extends across the full screen.
|
||||
ViewportUiDisplayLayout m_uiOverlayLayout; //!< The layout used for optionally anchoring Viewport UI Elements.
|
||||
QLabel m_componentModeBorderText; //!< The text used for the Component Mode border.
|
||||
QLabel m_viewportBorderText; //!< The text used for the viewport border.
|
||||
|
||||
QWidget* m_renderOverlay;
|
||||
QPointer<QWidget> m_fullScreenWidget; //!< Reference to the widget attached to m_fullScreenLayout if any.
|
||||
|
||||
+12
-5
@@ -14,13 +14,20 @@
|
||||
#include <QGridLayout>
|
||||
#include <QPointer>
|
||||
|
||||
namespace AzToolsFramework::ViewportUi
|
||||
{
|
||||
//! Margin for the Viewport UI Overlay (in pixels)
|
||||
constexpr int ViewportUiOverlayMargin = 5;
|
||||
//! Padding to make space for ImGui (in pixels)
|
||||
constexpr int ViewportUiOverlayTopMarginPadding = 20;
|
||||
//! Size of the top viewport border (in pixels)
|
||||
constexpr int ViewportUiTopBorderSize = 25;
|
||||
//! Size of the left, right and bottom viewport border (in pixels)
|
||||
constexpr int ViewportUiLeftRightBottomBorderSize = 5;
|
||||
} // namespace AzToolsFramework::ViewportUi
|
||||
|
||||
namespace AzToolsFramework::ViewportUi::Internal
|
||||
{
|
||||
// margin for the Viewport UI Overlay in pixels
|
||||
constexpr int ViewportUiOverlayMargin = 5;
|
||||
// padding to make space for ImGui
|
||||
constexpr int ViewportUiOverlayTopMarginPadding = 20;
|
||||
|
||||
//! QGridLayout implementation that uses a grid of QVBox/QHBoxLayouts internally to stack widgets.
|
||||
class ViewportUiDisplayLayout : public QGridLayout
|
||||
{
|
||||
|
||||
@@ -57,9 +57,7 @@ namespace UnitTest
|
||||
ArgumentContainer argContainer{ {} };
|
||||
|
||||
// Append Command Line override for the Project Cache Path
|
||||
auto projectCachePathOverride = FixedValueString::format(R"(--project-cache-path="%s")", m_tempDir.GetDirectory());
|
||||
auto projectPathOverride = FixedValueString{ R"(--project-path=AutomatedTesting)" };
|
||||
argContainer.push_back(projectCachePathOverride.data());
|
||||
auto projectPathOverride = FixedValueString::format(R"(--project-path="%s")", m_tempDir.GetDirectory());
|
||||
argContainer.push_back(projectPathOverride.data());
|
||||
m_application = new ToolsTestApplication("AssetFileInfoListComparisonTest", aznumeric_caster(argContainer.size()), argContainer.data());
|
||||
AzToolsFramework::AssetSeedManager assetSeedManager;
|
||||
@@ -100,7 +98,7 @@ namespace UnitTest
|
||||
m_application->Start(AzFramework::Application::Descriptor());
|
||||
|
||||
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
|
||||
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
|
||||
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
|
||||
// in the unit tests.
|
||||
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
|
||||
|
||||
@@ -223,7 +221,7 @@ namespace UnitTest
|
||||
|
||||
// AssetFileInfo should contain {2*, 4*, 5}
|
||||
AzToolsFramework::AssetFileInfoList assetFileInfoList;
|
||||
|
||||
|
||||
ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::ResultAssetFileInfoList], assetFileInfoList)) << "Unable to read the asset file info list.\n";
|
||||
|
||||
EXPECT_EQ(assetFileInfoList.m_fileInfoList.size(), 3);
|
||||
@@ -256,7 +254,7 @@ namespace UnitTest
|
||||
}
|
||||
}
|
||||
|
||||
// Verifying that correct assetId are present in the assetFileInfo list
|
||||
// Verifying that correct assetId are present in the assetFileInfo list
|
||||
AZStd::unordered_set<AZ::Data::AssetId> expectedAssetIds{ m_assets[2], m_assets[4], m_assets[5] };
|
||||
|
||||
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
|
||||
@@ -298,7 +296,7 @@ namespace UnitTest
|
||||
{
|
||||
firstAssetIdToAssetFileInfoMap[assetFileInfo.m_assetId] = AZStd::move(assetFileInfo);
|
||||
}
|
||||
|
||||
|
||||
AzToolsFramework::AssetFileInfoList secondAssetFileInfoList;
|
||||
ASSERT_TRUE(AZ::Utils::LoadObjectFromFileInPlace(TempFiles[FileIndex::SecondAssetFileInfoList], secondAssetFileInfoList)) << "Unable to read the asset file info list.\n";
|
||||
|
||||
@@ -315,7 +313,7 @@ namespace UnitTest
|
||||
auto foundSecond = secondAssetIdToAssetFileInfoMap.find(assetFileInfo.m_assetId);
|
||||
if (foundSecond != secondAssetIdToAssetFileInfoMap.end())
|
||||
{
|
||||
// Even if the asset Id is present in both the AssetFileInfo List, it should match the file hash from the second AssetFileInfo list
|
||||
// Even if the asset Id is present in both the AssetFileInfo List, it should match the file hash from the second AssetFileInfo list
|
||||
for (int idx = 0; idx < AzToolsFramework::AssetFileInfo::s_arraySize; idx++)
|
||||
{
|
||||
if (foundSecond->second.m_hash[idx] != assetFileInfo.m_hash[idx])
|
||||
@@ -343,7 +341,7 @@ namespace UnitTest
|
||||
}
|
||||
}
|
||||
|
||||
// Verifying that correct assetId are present in the assetFileInfo list
|
||||
// Verifying that correct assetId are present in the assetFileInfo list
|
||||
AZStd::unordered_set<AZ::Data::AssetId> expectedAssetIds{ m_assets[0], m_assets[1], m_assets[2], m_assets[3], m_assets[4], m_assets[5] };
|
||||
|
||||
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
|
||||
@@ -403,7 +401,7 @@ namespace UnitTest
|
||||
}
|
||||
}
|
||||
|
||||
// Verifying that correct assetId are present in the assetFileInfo list
|
||||
// Verifying that correct assetId are present in the assetFileInfo list
|
||||
AZStd::unordered_set<AZ::Data::AssetId> expectedAssetIds{ m_assets[1], m_assets[2], m_assets[3], m_assets[4] };
|
||||
|
||||
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
|
||||
@@ -462,7 +460,7 @@ namespace UnitTest
|
||||
}
|
||||
}
|
||||
|
||||
// Verifying that correct assetId are present in the assetFileInfo list
|
||||
// Verifying that correct assetId are present in the assetFileInfo list
|
||||
AZStd::unordered_set<AZ::Data::AssetId> expectedAssetIds{ m_assets[5] };
|
||||
|
||||
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
|
||||
@@ -493,7 +491,7 @@ namespace UnitTest
|
||||
|
||||
EXPECT_EQ(assetFileInfoList.m_fileInfoList.size(), 5);
|
||||
|
||||
// Verifying that correct assetId are present in the assetFileInfo list
|
||||
// Verifying that correct assetId are present in the assetFileInfo list
|
||||
AZStd::unordered_set<AZ::Data::AssetId> expectedAssetIds{ m_assets[0], m_assets[1], m_assets[2], m_assets[3], m_assets[4] };
|
||||
|
||||
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
|
||||
@@ -601,7 +599,7 @@ namespace UnitTest
|
||||
}
|
||||
}
|
||||
|
||||
// Verifying that correct assetId are present in the assetFileInfo list
|
||||
// Verifying that correct assetId are present in the assetFileInfo list
|
||||
AZStd::unordered_set<AZ::Data::AssetId> expectedAssetIds{ m_assets[2] };
|
||||
|
||||
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
|
||||
@@ -625,12 +623,12 @@ namespace UnitTest
|
||||
AssetFileInfoListComparison::ComparisonData filePatternComparisonData(AssetFileInfoListComparison::ComparisonType::FilePattern,"$1", "Asset[0-3].txt", AssetFileInfoListComparison::FilePatternType::Regex);
|
||||
filePatternComparisonData.m_firstInput = TempFiles[FileIndex::FirstAssetFileInfoList];
|
||||
assetFileInfoListComparison.AddComparisonStep(filePatternComparisonData);
|
||||
|
||||
|
||||
AzToolsFramework::AssetFileInfoListComparison::ComparisonData deltaComparisonData(AzToolsFramework::AssetFileInfoListComparison::ComparisonType::Delta, TempFiles[FileIndex::ResultAssetFileInfoList]);
|
||||
deltaComparisonData.m_firstInput = "$1";
|
||||
deltaComparisonData.m_secondInput = TempFiles[FileIndex::SecondAssetFileInfoList];
|
||||
assetFileInfoListComparison.AddComparisonStep(deltaComparisonData);
|
||||
|
||||
|
||||
ASSERT_TRUE(assetFileInfoListComparison.CompareAndSaveResults().IsSuccess()) << "Multiple Comparison Operation( FilePattern + Delta ) failed.\n";
|
||||
// Output of the FilePattern Operation should be {0,1,2,3}
|
||||
// Output of the Delta Operation should be {2*,4*,5}
|
||||
@@ -666,7 +664,7 @@ namespace UnitTest
|
||||
}
|
||||
}
|
||||
|
||||
// Verifying that correct assetId are present in the assetFileInfo list
|
||||
// Verifying that correct assetId are present in the assetFileInfo list
|
||||
AZStd::unordered_set<AZ::Data::AssetId> expectedAssetIds{ m_assets[2], m_assets[4], m_assets[5] };
|
||||
|
||||
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
|
||||
@@ -738,7 +736,7 @@ namespace UnitTest
|
||||
}
|
||||
}
|
||||
|
||||
// Verifying that correct assetId are present in the assetFileInfo list
|
||||
// Verifying that correct assetId are present in the assetFileInfo list
|
||||
AZStd::unordered_set<AZ::Data::AssetId> expectedAssetIds{ m_assets[4], m_assets[5] };
|
||||
|
||||
for (const AzToolsFramework::AssetFileInfo& assetFileInfo : assetFileInfoList.m_fileInfoList)
|
||||
|
||||
@@ -63,10 +63,8 @@ namespace UnitTest
|
||||
ArgumentContainer argContainer{ {} };
|
||||
|
||||
// Append Command Line override for the Project Cache Path
|
||||
AZ::IO::Path cacheProjectRootFolder{ m_tempDir.GetDirectory() };
|
||||
auto projectCachePathOverride = FixedValueString::format(R"(--project-cache-path="%s")", cacheProjectRootFolder.c_str());
|
||||
auto projectPathOverride = FixedValueString{ R"(--project-path=AutomatedTesting)" };
|
||||
argContainer.push_back(projectCachePathOverride.data());
|
||||
auto cacheProjectRootFolder = AZ::IO::Path{ m_tempDir.GetDirectory() } / "Cache";
|
||||
auto projectPathOverride = FixedValueString::format(R"(--project-path="%s")", m_tempDir.GetDirectory());
|
||||
argContainer.push_back(projectPathOverride.data());
|
||||
m_application = new ToolsTestApplication("AssetSeedManagerTest", aznumeric_caster(argContainer.size()), argContainer.data());
|
||||
m_assetSeedManager = new AzToolsFramework::AssetSeedManager();
|
||||
|
||||
@@ -572,7 +572,9 @@ namespace UnitTest
|
||||
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::IO::FixedMaxPath enginePath;
|
||||
registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
AzFramework::Application::Descriptor descriptor;
|
||||
|
||||
@@ -59,7 +59,9 @@ protected:
|
||||
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::IO::FixedMaxPath enginePath;
|
||||
registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_app.Start(AZ::ComponentApplication::Descriptor());
|
||||
@@ -184,7 +186,9 @@ public:
|
||||
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::IO::FixedMaxPath enginePath;
|
||||
registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_app.Start(AzFramework::Application::Descriptor());
|
||||
|
||||
@@ -44,10 +44,8 @@ namespace UnitTest
|
||||
ArgumentContainer argContainer{ {} };
|
||||
|
||||
// Append Command Line override for the Project Cache Path
|
||||
AZ::IO::Path cacheProjectRootFolder{ m_tempDir.GetDirectory() };
|
||||
auto projectCachePathOverride = FixedValueString::format(R"(--project-cache-path="%s")", cacheProjectRootFolder.c_str());
|
||||
auto projectPathOverride = FixedValueString{ R"(--project-path=AutomatedTesting)" };
|
||||
argContainer.push_back(projectCachePathOverride.data());
|
||||
auto cacheProjectRootFolder = AZ::IO::Path{ m_tempDir.GetDirectory() } / "Cache";
|
||||
auto projectPathOverride = FixedValueString::format(R"(--project-path="%s")", m_tempDir.GetDirectory());
|
||||
argContainer.push_back(projectPathOverride.data());
|
||||
m_application = new ToolsTestApplication("AddressedAssetCatalogManager", aznumeric_caster(argContainer.size()), argContainer.data());
|
||||
|
||||
@@ -195,10 +193,7 @@ namespace UnitTest
|
||||
ArgumentContainer argContainer{ {} };
|
||||
|
||||
// Append Command Line override for the Project Cache Path
|
||||
AZ::IO::Path cacheProjectRootFolder{ m_tempDir.GetDirectory() };
|
||||
auto projectCachePathOverride = FixedValueString::format(R"(--project-cache-path="%s")", cacheProjectRootFolder.c_str());
|
||||
auto projectPathOverride = FixedValueString{ R"(--project-path=AutomatedTesting)" };
|
||||
argContainer.push_back(projectCachePathOverride.data());
|
||||
auto projectPathOverride = FixedValueString::format(R"(--project-path="%s")", m_tempDir.GetDirectory());
|
||||
argContainer.push_back(projectPathOverride.data());
|
||||
m_application = new ToolsTestApplication("MessageTest", aznumeric_caster(argContainer.size()), argContainer.data());
|
||||
|
||||
|
||||
@@ -1059,7 +1059,9 @@ namespace UnitTest
|
||||
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::IO::FixedMaxPath enginePath;
|
||||
registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_app.Start(AzFramework::Application::Descriptor());
|
||||
|
||||
@@ -66,7 +66,9 @@ namespace AssetBundler
|
||||
}
|
||||
auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)
|
||||
+ "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::IO::FixedMaxPath enginePath;
|
||||
registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
|
||||
|
||||
@@ -106,7 +106,9 @@ namespace AssetBundler
|
||||
}
|
||||
auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)
|
||||
+ "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::IO::FixedMaxPath enginePath;
|
||||
registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
AZ::IO::FixedMaxPath engineRoot = AZ::Utils::GetEnginePath();
|
||||
|
||||
@@ -431,8 +431,9 @@ namespace AssetProcessor
|
||||
}
|
||||
file.Close();
|
||||
|
||||
AZ::u32 hashedSpecialization = static_cast<AZ::u32>(AZStd::hash<AZStd::string_view>{}(specializationString));
|
||||
AZ_Assert(hashedSpecialization != 0, "Product ID generation failed for specialization %.*s. This can result in a product ID collision with other builders for this asset.",
|
||||
const AZ::u32 hashedSpecialization = static_cast<AZ::u32>(AZStd::hash<AZStd::string_view>{}(specializationString));
|
||||
AZ_Assert(hashedSpecialization != 0, "Product ID generation failed for specialization %.*s."
|
||||
" This can result in a product ID collision with other builders for this asset.",
|
||||
AZ_STRING_ARG(specializationString));
|
||||
response.m_outputProducts.emplace_back(outputPath, m_assetType, hashedSpecialization);
|
||||
response.m_outputProducts.back().m_dependenciesHandled = true;
|
||||
|
||||
@@ -128,7 +128,9 @@ namespace AssetProcessor
|
||||
settingsRegistry->Set(cacheRootKey, m_data->m_temporarySourceDir.absoluteFilePath("Cache").toUtf8().constData());
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
settingsRegistry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::IO::FixedMaxPath enginePath;
|
||||
settingsRegistry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
settingsRegistry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*settingsRegistry);
|
||||
AssetUtilities::ComputeProjectCacheRoot(m_data->m_cacheRootDir);
|
||||
QString normalizedCacheRoot = AssetUtilities::NormalizeDirectoryPath(m_data->m_cacheRootDir.absolutePath());
|
||||
|
||||
@@ -107,12 +107,13 @@ namespace AssetProcessorMessagesTests
|
||||
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey
|
||||
};
|
||||
constexpr AZ::SettingsRegistryInterface::FixedValueString projectPathKey{ bootstrapKey + "/project_path" };
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::IO::FixedMaxPath enginePath;
|
||||
registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
// Force the branch token into settings registry before starting the application manager.
|
||||
// This avoids writing the asset_processor.setreg file which can cause fileIO errors.
|
||||
const AZ::IO::FixedMaxPathString enginePath = AZ::Utils::GetEnginePath();
|
||||
constexpr AZ::SettingsRegistryInterface::FixedValueString branchTokenKey{ bootstrapKey + "/assetProcessor_branch_token" };
|
||||
AZStd::string token;
|
||||
AZ::StringFunc::AssetPath::CalculateBranchToken(enginePath.c_str(), token);
|
||||
|
||||
@@ -71,12 +71,13 @@ namespace AssetProcessor
|
||||
auto registry = AZ::SettingsRegistry::Get();
|
||||
auto bootstrapKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey);
|
||||
auto projectPathKey = bootstrapKey + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::IO::FixedMaxPath enginePath;
|
||||
registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
// Forcing the branch token into settings registry before starting the application manager.
|
||||
// This avoids writing the asset_processor.setreg file which can cause fileIO errors.
|
||||
AZ::IO::FixedMaxPathString enginePath = AZ::Utils::GetEnginePath();
|
||||
auto branchTokenKey = bootstrapKey + "/assetProcessor_branch_token";
|
||||
AZStd::string token;
|
||||
AzFramework::StringFunc::AssetPath::CalculateBranchToken(enginePath.c_str(), token);
|
||||
|
||||
@@ -50,7 +50,9 @@ namespace AssetProcessor
|
||||
+ "/project_path";
|
||||
if(auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
|
||||
{
|
||||
settingsRegistry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::IO::FixedMaxPath enginePath;
|
||||
settingsRegistry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
settingsRegistry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*settingsRegistry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +193,9 @@ void AssetProcessorManagerTest::SetUp()
|
||||
registry->Set(cacheRootKey, tempPath.absoluteFilePath("Cache").toUtf8().constData());
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::IO::FixedMaxPath enginePath;
|
||||
registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
m_data->m_databaseLocationListener.BusConnect();
|
||||
|
||||
@@ -45,7 +45,9 @@ protected:
|
||||
AZ::SettingsRegistryInterface* registry = AZ::SettingsRegistry::Get();
|
||||
auto projectPathKey =
|
||||
AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
|
||||
registry->Set(projectPathKey, "AutomatedTesting");
|
||||
AZ::IO::FixedMaxPath enginePath;
|
||||
registry->Get(enginePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
|
||||
registry->Set(projectPathKey, (enginePath / "AutomatedTesting").Native());
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
|
||||
|
||||
AZ::ComponentApplication::Descriptor desc;
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace O3DE::ProjectManager
|
||||
: ScreenWidget(parent)
|
||||
{
|
||||
m_gemModel = new GemModel(this);
|
||||
m_proxModel = new GemSortFilterProxyModel(m_gemModel, this);
|
||||
m_proxyModel = new GemSortFilterProxyModel(m_gemModel, this);
|
||||
|
||||
QVBoxLayout* vLayout = new QVBoxLayout();
|
||||
vLayout->setMargin(0);
|
||||
@@ -39,7 +39,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
m_downloadController = new DownloadController();
|
||||
|
||||
m_headerWidget = new GemCatalogHeaderWidget(m_gemModel, m_proxModel, m_downloadController);
|
||||
m_headerWidget = new GemCatalogHeaderWidget(m_gemModel, m_proxyModel, m_downloadController);
|
||||
vLayout->addWidget(m_headerWidget);
|
||||
|
||||
connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged);
|
||||
@@ -50,10 +50,12 @@ namespace O3DE::ProjectManager
|
||||
hLayout->setMargin(0);
|
||||
vLayout->addLayout(hLayout);
|
||||
|
||||
m_gemListView = new GemListView(m_proxModel, m_proxModel->GetSelectionModel(), this);
|
||||
m_gemListView = new GemListView(m_proxyModel, m_proxyModel->GetSelectionModel(), this);
|
||||
m_gemInspector = new GemInspector(m_gemModel, this);
|
||||
m_gemInspector->setFixedWidth(240);
|
||||
|
||||
connect(m_gemInspector, &GemInspector::TagClicked, this, &GemCatalogScreen::SelectGem);
|
||||
|
||||
QWidget* filterWidget = new QWidget(this);
|
||||
filterWidget->setFixedWidth(240);
|
||||
m_filterWidgetLayout = new QVBoxLayout();
|
||||
@@ -61,7 +63,7 @@ namespace O3DE::ProjectManager
|
||||
m_filterWidgetLayout->setSpacing(0);
|
||||
filterWidget->setLayout(m_filterWidgetLayout);
|
||||
|
||||
GemListHeaderWidget* listHeaderWidget = new GemListHeaderWidget(m_proxModel);
|
||||
GemListHeaderWidget* listHeaderWidget = new GemListHeaderWidget(m_proxyModel);
|
||||
|
||||
QVBoxLayout* middleVLayout = new QVBoxLayout();
|
||||
middleVLayout->setMargin(0);
|
||||
@@ -84,15 +86,17 @@ namespace O3DE::ProjectManager
|
||||
m_gemsToRegisterWithProject.clear();
|
||||
FillModel(projectPath);
|
||||
|
||||
m_proxyModel->ResetFilters();
|
||||
|
||||
if (m_filterWidget)
|
||||
{
|
||||
m_filterWidget->hide();
|
||||
m_filterWidget->deleteLater();
|
||||
m_filterWidget->ResetAllFilters();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_filterWidget = new GemFilterWidget(m_proxyModel);
|
||||
m_filterWidgetLayout->addWidget(m_filterWidget);
|
||||
}
|
||||
|
||||
m_proxModel->ResetFilters();
|
||||
m_filterWidget = new GemFilterWidget(m_proxModel);
|
||||
m_filterWidgetLayout->addWidget(m_filterWidget);
|
||||
|
||||
m_headerWidget->ReinitForProject();
|
||||
|
||||
@@ -192,6 +196,20 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
void GemCatalogScreen::SelectGem(const QString& gemName)
|
||||
{
|
||||
QModelIndex modelIndex = m_gemModel->FindIndexByNameString(gemName);
|
||||
if (!m_proxyModel->filterAcceptsRow(modelIndex.row(), QModelIndex()))
|
||||
{
|
||||
m_proxyModel->ResetFilters();
|
||||
m_filterWidget->ResetAllFilters();
|
||||
}
|
||||
|
||||
QModelIndex proxyIndex = m_proxyModel->mapFromSource(modelIndex);
|
||||
m_proxyModel->GetSelectionModel()->select(proxyIndex, QItemSelectionModel::ClearAndSelect);
|
||||
m_gemListView->scrollTo(proxyIndex);
|
||||
}
|
||||
|
||||
void GemCatalogScreen::hideEvent(QHideEvent* event)
|
||||
{
|
||||
ScreenWidget::hideEvent(event);
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace O3DE::ProjectManager
|
||||
public slots:
|
||||
void OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies);
|
||||
void OnAddGemClicked();
|
||||
void SelectGem(const QString& gemName);
|
||||
|
||||
protected:
|
||||
void hideEvent(QHideEvent* event) override;
|
||||
@@ -68,7 +69,7 @@ namespace O3DE::ProjectManager
|
||||
GemInspector* m_gemInspector = nullptr;
|
||||
GemModel* m_gemModel = nullptr;
|
||||
GemCatalogHeaderWidget* m_headerWidget = nullptr;
|
||||
GemSortFilterProxyModel* m_proxModel = nullptr;
|
||||
GemSortFilterProxyModel* m_proxyModel = nullptr;
|
||||
QVBoxLayout* m_filterWidgetLayout = nullptr;
|
||||
GemFilterWidget* m_filterWidget = nullptr;
|
||||
DownloadController* m_downloadController = nullptr;
|
||||
|
||||
@@ -213,11 +213,99 @@ namespace O3DE::ProjectManager
|
||||
m_filterLayout->setContentsMargins(0, 0, 0, 0);
|
||||
filterSection->setLayout(m_filterLayout);
|
||||
|
||||
ResetAllFilters();
|
||||
}
|
||||
|
||||
void GemFilterWidget::ResetAllFilters()
|
||||
{
|
||||
ResetGemStatusFilter();
|
||||
AddGemOriginFilter();
|
||||
AddTypeFilter();
|
||||
AddPlatformFilter();
|
||||
AddFeatureFilter();
|
||||
ResetGemOriginFilter();
|
||||
ResetTypeFilter();
|
||||
ResetPlatformFilter();
|
||||
ResetFeatureFilter();
|
||||
}
|
||||
|
||||
void GemFilterWidget::ResetFilterWidget(
|
||||
FilterCategoryWidget*& filterPtr,
|
||||
const QString& filterName,
|
||||
const QVector<QString>& elementNames,
|
||||
const QVector<int>& elementCounts,
|
||||
int defaultShowCount)
|
||||
{
|
||||
bool wasCollapsed = false;
|
||||
if (filterPtr)
|
||||
{
|
||||
wasCollapsed = filterPtr->IsCollapsed();
|
||||
}
|
||||
|
||||
FilterCategoryWidget* filterWidget = new FilterCategoryWidget(
|
||||
filterName, elementNames, elementCounts, /*showAllLessButton=*/defaultShowCount != 4, /*collapsed*/ wasCollapsed,
|
||||
/*defaultShowCount=*/defaultShowCount);
|
||||
if (filterPtr)
|
||||
{
|
||||
m_filterLayout->replaceWidget(filterPtr, filterWidget);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_filterLayout->addWidget(filterWidget);
|
||||
}
|
||||
|
||||
filterPtr->deleteLater();
|
||||
filterPtr = filterWidget;
|
||||
}
|
||||
|
||||
template<typename filterType, typename filterFlagsType>
|
||||
void GemFilterWidget::ResetSimpleOrFilter(
|
||||
FilterCategoryWidget*& filterPtr,
|
||||
const QString& filterName,
|
||||
int numFilterElements,
|
||||
bool (*filterMatcher)(GemModel*, filterType, int),
|
||||
QString (*typeStringGetter)(filterType),
|
||||
filterFlagsType (GemSortFilterProxyModel::*filterFlagsGetter)() const,
|
||||
void (GemSortFilterProxyModel::*filterFlagsSetter)(const filterFlagsType&))
|
||||
{
|
||||
QVector<QString> elementNames;
|
||||
QVector<int> elementCounts;
|
||||
const int numGems = m_gemModel->rowCount();
|
||||
for (int filterIndex = 0; filterIndex < numFilterElements; ++filterIndex)
|
||||
{
|
||||
const filterType gemFilterToBeCounted = static_cast<filterType>(1 << filterIndex);
|
||||
|
||||
int gemFilterCount = 0;
|
||||
for (int gemIndex = 0; gemIndex < numGems; ++gemIndex)
|
||||
{
|
||||
// If filter matches increment filter count
|
||||
gemFilterCount += filterMatcher(m_gemModel, gemFilterToBeCounted, gemIndex);
|
||||
}
|
||||
elementNames.push_back(typeStringGetter(gemFilterToBeCounted));
|
||||
elementCounts.push_back(gemFilterCount);
|
||||
}
|
||||
|
||||
// Replace existing filter and delete old one
|
||||
ResetFilterWidget(filterPtr, filterName, elementNames, elementCounts);
|
||||
|
||||
const QList<QAbstractButton*> buttons = filterPtr->GetButtonGroup()->buttons();
|
||||
for (int i = 0; i < buttons.size(); ++i)
|
||||
{
|
||||
const filterType gemFilter = static_cast<filterType>(1 << i);
|
||||
QAbstractButton* button = buttons[i];
|
||||
|
||||
connect(
|
||||
button, &QAbstractButton::toggled, this,
|
||||
[=](bool checked)
|
||||
{
|
||||
filterFlagsType gemFilters = (m_filterProxyModel->*filterFlagsGetter)();
|
||||
if (checked)
|
||||
{
|
||||
gemFilters |= gemFilter;
|
||||
}
|
||||
else
|
||||
{
|
||||
gemFilters &= ~gemFilter;
|
||||
}
|
||||
(m_filterProxyModel->*filterFlagsSetter)(gemFilters);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void GemFilterWidget::ResetGemStatusFilter()
|
||||
@@ -241,25 +329,7 @@ namespace O3DE::ProjectManager
|
||||
elementNames.push_back(GemSortFilterProxyModel::GetGemActiveString(GemSortFilterProxyModel::GemActive::Inactive));
|
||||
elementCounts.push_back(totalGems - enabledGemTotal);
|
||||
|
||||
bool wasCollapsed = false;
|
||||
if (m_statusFilter)
|
||||
{
|
||||
wasCollapsed = m_statusFilter->IsCollapsed();
|
||||
}
|
||||
|
||||
FilterCategoryWidget* filterWidget =
|
||||
new FilterCategoryWidget("Status", elementNames, elementCounts, /*showAllLessButton=*/false, /*collapsed*/wasCollapsed);
|
||||
if (m_statusFilter)
|
||||
{
|
||||
m_filterLayout->replaceWidget(m_statusFilter, filterWidget);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_filterLayout->addWidget(filterWidget);
|
||||
}
|
||||
|
||||
m_statusFilter->deleteLater();
|
||||
m_statusFilter = filterWidget;
|
||||
ResetFilterWidget(m_statusFilter, "Status", elementNames, elementCounts);
|
||||
|
||||
const QList<QAbstractButton*> buttons = m_statusFilter->GetButtonGroup()->buttons();
|
||||
|
||||
@@ -317,157 +387,42 @@ namespace O3DE::ProjectManager
|
||||
connect(activeButton, &QAbstractButton::toggled, this, updateGemActive);
|
||||
}
|
||||
|
||||
void GemFilterWidget::AddGemOriginFilter()
|
||||
void GemFilterWidget::ResetGemOriginFilter()
|
||||
{
|
||||
QVector<QString> elementNames;
|
||||
QVector<int> elementCounts;
|
||||
const int numGems = m_gemModel->rowCount();
|
||||
for (int originIndex = 0; originIndex < GemInfo::NumGemOrigins; ++originIndex)
|
||||
{
|
||||
const GemInfo::GemOrigin gemOriginToBeCounted = static_cast<GemInfo::GemOrigin>(1 << originIndex);
|
||||
|
||||
int gemOriginCount = 0;
|
||||
for (int gemIndex = 0; gemIndex < numGems; ++gemIndex)
|
||||
ResetSimpleOrFilter<GemInfo::GemOrigin, GemInfo::GemOrigins>
|
||||
(
|
||||
m_originFilter, "Provider", GemInfo::NumGemOrigins,
|
||||
[](GemModel* gemModel, GemInfo::GemOrigin origin, int gemIndex)
|
||||
{
|
||||
const GemInfo::GemOrigin gemOrigin = m_gemModel->GetGemOrigin(m_gemModel->index(gemIndex, 0));
|
||||
|
||||
// Is the gem of the given origin?
|
||||
if (gemOriginToBeCounted == gemOrigin)
|
||||
{
|
||||
gemOriginCount++;
|
||||
}
|
||||
}
|
||||
|
||||
elementNames.push_back(GemInfo::GetGemOriginString(gemOriginToBeCounted));
|
||||
elementCounts.push_back(gemOriginCount);
|
||||
}
|
||||
|
||||
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Provider", elementNames, elementCounts, /*showAllLessButton=*/false);
|
||||
m_filterLayout->addWidget(filterWidget);
|
||||
|
||||
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
|
||||
for (int i = 0; i < buttons.size(); ++i)
|
||||
{
|
||||
const GemInfo::GemOrigin gemOrigin = static_cast<GemInfo::GemOrigin>(1 << i);
|
||||
QAbstractButton* button = buttons[i];
|
||||
|
||||
connect(button, &QAbstractButton::toggled, this, [=](bool checked)
|
||||
{
|
||||
GemInfo::GemOrigins gemOrigins = m_filterProxyModel->GetGemOrigins();
|
||||
if (checked)
|
||||
{
|
||||
gemOrigins |= gemOrigin;
|
||||
}
|
||||
else
|
||||
{
|
||||
gemOrigins &= ~gemOrigin;
|
||||
}
|
||||
m_filterProxyModel->SetGemOrigins(gemOrigins);
|
||||
});
|
||||
}
|
||||
return origin == gemModel->GetGemOrigin(gemModel->index(gemIndex, 0));
|
||||
},
|
||||
&GemInfo::GetGemOriginString, &GemSortFilterProxyModel::GetGemOrigins, &GemSortFilterProxyModel::SetGemOrigins
|
||||
);
|
||||
}
|
||||
|
||||
void GemFilterWidget::AddTypeFilter()
|
||||
void GemFilterWidget::ResetTypeFilter()
|
||||
{
|
||||
QVector<QString> elementNames;
|
||||
QVector<int> elementCounts;
|
||||
const int numGems = m_gemModel->rowCount();
|
||||
for (int typeIndex = 0; typeIndex < GemInfo::NumTypes; ++typeIndex)
|
||||
{
|
||||
const GemInfo::Type type = static_cast<GemInfo::Type>(1 << typeIndex);
|
||||
|
||||
int typeGemCount = 0;
|
||||
for (int gemIndex = 0; gemIndex < numGems; ++gemIndex)
|
||||
ResetSimpleOrFilter<GemInfo::Type, GemInfo::Types>(
|
||||
m_typeFilter, "Type", GemInfo::NumTypes,
|
||||
[](GemModel* gemModel, GemInfo::Type type, int gemIndex)
|
||||
{
|
||||
const GemInfo::Types types = m_gemModel->GetTypes(m_gemModel->index(gemIndex, 0));
|
||||
|
||||
// Is type (Asset, Code, Tool) part of the gem?
|
||||
if (types & type)
|
||||
{
|
||||
typeGemCount++;
|
||||
}
|
||||
}
|
||||
|
||||
elementNames.push_back(GemInfo::GetTypeString(type));
|
||||
elementCounts.push_back(typeGemCount);
|
||||
}
|
||||
|
||||
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Type", elementNames, elementCounts, /*showAllLessButton=*/false);
|
||||
m_filterLayout->addWidget(filterWidget);
|
||||
|
||||
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
|
||||
for (int i = 0; i < buttons.size(); ++i)
|
||||
{
|
||||
const GemInfo::Type type = static_cast<GemInfo::Type>(1 << i);
|
||||
QAbstractButton* button = buttons[i];
|
||||
|
||||
connect(button, &QAbstractButton::toggled, this, [=](bool checked)
|
||||
{
|
||||
GemInfo::Types types = m_filterProxyModel->GetTypes();
|
||||
if (checked)
|
||||
{
|
||||
types |= type;
|
||||
}
|
||||
else
|
||||
{
|
||||
types &= ~type;
|
||||
}
|
||||
m_filterProxyModel->SetTypes(types);
|
||||
});
|
||||
}
|
||||
return static_cast<bool>(type & gemModel->GetTypes(gemModel->index(gemIndex, 0)));
|
||||
},
|
||||
&GemInfo::GetTypeString, &GemSortFilterProxyModel::GetTypes, &GemSortFilterProxyModel::SetTypes);
|
||||
}
|
||||
|
||||
void GemFilterWidget::AddPlatformFilter()
|
||||
void GemFilterWidget::ResetPlatformFilter()
|
||||
{
|
||||
QVector<QString> elementNames;
|
||||
QVector<int> elementCounts;
|
||||
const int numGems = m_gemModel->rowCount();
|
||||
for (int platformIndex = 0; platformIndex < GemInfo::NumPlatforms; ++platformIndex)
|
||||
{
|
||||
const GemInfo::Platform platform = static_cast<GemInfo::Platform>(1 << platformIndex);
|
||||
|
||||
int platformGemCount = 0;
|
||||
for (int gemIndex = 0; gemIndex < numGems; ++gemIndex)
|
||||
ResetSimpleOrFilter<GemInfo::Platform, GemInfo::Platforms>(
|
||||
m_platformFilter, "Supported Platforms", GemInfo::NumPlatforms,
|
||||
[](GemModel* gemModel, GemInfo::Platform platform, int gemIndex)
|
||||
{
|
||||
const GemInfo::Platforms platforms = m_gemModel->GetPlatforms(m_gemModel->index(gemIndex, 0));
|
||||
|
||||
// Is platform supported?
|
||||
if (platforms & platform)
|
||||
{
|
||||
platformGemCount++;
|
||||
}
|
||||
}
|
||||
|
||||
elementNames.push_back(GemInfo::GetPlatformString(platform));
|
||||
elementCounts.push_back(platformGemCount);
|
||||
}
|
||||
|
||||
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Supported Platforms", elementNames, elementCounts, /*showAllLessButton=*/false);
|
||||
m_filterLayout->addWidget(filterWidget);
|
||||
|
||||
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
|
||||
for (int i = 0; i < buttons.size(); ++i)
|
||||
{
|
||||
const GemInfo::Platform platform = static_cast<GemInfo::Platform>(1 << i);
|
||||
QAbstractButton* button = buttons[i];
|
||||
|
||||
connect(button, &QAbstractButton::toggled, this, [=](bool checked)
|
||||
{
|
||||
GemInfo::Platforms platforms = m_filterProxyModel->GetPlatforms();
|
||||
if (checked)
|
||||
{
|
||||
platforms |= platform;
|
||||
}
|
||||
else
|
||||
{
|
||||
platforms &= ~platform;
|
||||
}
|
||||
m_filterProxyModel->SetPlatforms(platforms);
|
||||
});
|
||||
}
|
||||
return static_cast<bool>(platform & gemModel->GetPlatforms(gemModel->index(gemIndex, 0)));
|
||||
},
|
||||
&GemInfo::GetPlatformString, &GemSortFilterProxyModel::GetPlatforms, &GemSortFilterProxyModel::SetPlatforms);
|
||||
}
|
||||
|
||||
void GemFilterWidget::AddFeatureFilter()
|
||||
void GemFilterWidget::ResetFeatureFilter()
|
||||
{
|
||||
// Alphabetically sorted, unique features and their number of occurrences in the gem database.
|
||||
QMap<QString, int> uniqueFeatureCounts;
|
||||
@@ -497,11 +452,15 @@ namespace O3DE::ProjectManager
|
||||
elementCounts.push_back(iterator.value());
|
||||
}
|
||||
|
||||
FilterCategoryWidget* filterWidget = new FilterCategoryWidget("Features", elementNames, elementCounts,
|
||||
/*showAllLessButton=*/true, false, /*defaultShowCount=*/5);
|
||||
m_filterLayout->addWidget(filterWidget);
|
||||
ResetFilterWidget(m_featureFilter, "Features", elementNames, elementCounts, /*defaultShowCount=*/5);
|
||||
|
||||
const QList<QAbstractButton*> buttons = filterWidget->GetButtonGroup()->buttons();
|
||||
for (QMetaObject::Connection& connection : m_featureTagConnections)
|
||||
{
|
||||
disconnect(connection);
|
||||
}
|
||||
m_featureTagConnections.clear();
|
||||
|
||||
const QList<QAbstractButton*> buttons = m_featureFilter->GetButtonGroup()->buttons();
|
||||
for (int i = 0; i < buttons.size(); ++i)
|
||||
{
|
||||
const QString& feature = elementNames[i];
|
||||
@@ -523,13 +482,13 @@ namespace O3DE::ProjectManager
|
||||
});
|
||||
|
||||
// Sync the UI state with the proxy model filtering.
|
||||
connect(m_filterProxyModel, &GemSortFilterProxyModel::OnInvalidated, this, [=]
|
||||
m_featureTagConnections.push_back(connect(m_filterProxyModel, &GemSortFilterProxyModel::OnInvalidated, this, [=]
|
||||
{
|
||||
const QSet<QString>& filteredFeatureTags = m_filterProxyModel->GetFeatures();
|
||||
const bool isChecked = filteredFeatureTags.contains(button->text());
|
||||
QSignalBlocker signalsBlocker(button);
|
||||
button->setChecked(isChecked);
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -66,17 +66,41 @@ namespace O3DE::ProjectManager
|
||||
~GemFilterWidget() = default;
|
||||
|
||||
public slots:
|
||||
void ResetAllFilters();
|
||||
void ResetGemStatusFilter();
|
||||
|
||||
private:
|
||||
void AddGemOriginFilter();
|
||||
void AddTypeFilter();
|
||||
void AddPlatformFilter();
|
||||
void AddFeatureFilter();
|
||||
void ResetGemOriginFilter();
|
||||
void ResetTypeFilter();
|
||||
void ResetPlatformFilter();
|
||||
void ResetFeatureFilter();
|
||||
|
||||
void ResetFilterWidget(
|
||||
FilterCategoryWidget*& filterPtr,
|
||||
const QString& filterName,
|
||||
const QVector<QString>& elementNames,
|
||||
const QVector<int>& elementCounts,
|
||||
int defaultShowCount = 4);
|
||||
|
||||
template<typename filterType, typename filterFlagsType>
|
||||
void ResetSimpleOrFilter(
|
||||
FilterCategoryWidget*& filterPtr,
|
||||
const QString& filterName,
|
||||
int numFilterElements,
|
||||
bool (*filterMatcher)(GemModel*, filterType, int),
|
||||
QString (*typeStringGetter)(filterType),
|
||||
filterFlagsType (GemSortFilterProxyModel::*filterFlagsGetter)() const,
|
||||
void (GemSortFilterProxyModel::*filterFlagsSetter)(const filterFlagsType&));
|
||||
|
||||
QVBoxLayout* m_filterLayout = nullptr;
|
||||
GemModel* m_gemModel = nullptr;
|
||||
GemSortFilterProxyModel* m_filterProxyModel = nullptr;
|
||||
FilterCategoryWidget* m_statusFilter = nullptr;
|
||||
FilterCategoryWidget* m_originFilter = nullptr;
|
||||
FilterCategoryWidget* m_typeFilter = nullptr;
|
||||
FilterCategoryWidget* m_platformFilter = nullptr;
|
||||
FilterCategoryWidget* m_featureFilter = nullptr;
|
||||
|
||||
QVector<QMetaObject::Connection> m_featureTagConnections;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -81,6 +81,8 @@ namespace O3DE::ProjectManager
|
||||
DownloadStatus m_downloadStatus = UnknownDownloadStatus;
|
||||
QStringList m_features;
|
||||
QString m_requirement;
|
||||
QString m_licenseText;
|
||||
QString m_licenseLink;
|
||||
QString m_directoryLink;
|
||||
QString m_documentationLink;
|
||||
QString m_version = "Unknown Version";
|
||||
|
||||
@@ -52,6 +52,22 @@ namespace O3DE::ProjectManager
|
||||
Update(selectedIndices[0]);
|
||||
}
|
||||
|
||||
void SetLabelElidedText(QLabel* label, QString text)
|
||||
{
|
||||
QFontMetrics nameFontMetrics(label->font());
|
||||
int labelWidth = label->width();
|
||||
|
||||
// Don't elide if the widgets are sized too small (sometimes occurs when loading gem catalog)
|
||||
if (labelWidth > 100)
|
||||
{
|
||||
label->setText(nameFontMetrics.elidedText(text, Qt::ElideRight, labelWidth));
|
||||
}
|
||||
else
|
||||
{
|
||||
label->setText(text);
|
||||
}
|
||||
}
|
||||
|
||||
void GemInspector::Update(const QModelIndex& modelIndex)
|
||||
{
|
||||
if (!modelIndex.isValid())
|
||||
@@ -59,38 +75,52 @@ namespace O3DE::ProjectManager
|
||||
m_mainWidget->hide();
|
||||
}
|
||||
|
||||
m_nameLabel->setText(m_model->GetDisplayName(modelIndex));
|
||||
m_creatorLabel->setText(m_model->GetCreator(modelIndex));
|
||||
SetLabelElidedText(m_nameLabel, m_model->GetDisplayName(modelIndex));
|
||||
SetLabelElidedText(m_creatorLabel, m_model->GetCreator(modelIndex));
|
||||
|
||||
m_summaryLabel->setText(m_model->GetSummary(modelIndex));
|
||||
m_summaryLabel->adjustSize();
|
||||
|
||||
m_licenseLinkLabel->setText(m_model->GetLicenseText(modelIndex));
|
||||
m_licenseLinkLabel->SetUrl(m_model->GetLicenseLink(modelIndex));
|
||||
|
||||
m_directoryLinkLabel->SetUrl(m_model->GetDirectoryLink(modelIndex));
|
||||
m_documentationLinkLabel->SetUrl(m_model->GetDocLink(modelIndex));
|
||||
|
||||
if (m_model->HasRequirement(modelIndex))
|
||||
{
|
||||
m_reqirementsIconLabel->show();
|
||||
m_reqirementsTitleLabel->show();
|
||||
m_reqirementsTextLabel->show();
|
||||
m_requirementsIconLabel->show();
|
||||
m_requirementsTitleLabel->show();
|
||||
m_requirementsTextLabel->show();
|
||||
m_requirementsMainSpacer->changeSize(0, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
|
||||
m_reqirementsTitleLabel->setText("Requirement");
|
||||
m_reqirementsTextLabel->setText(m_model->GetRequirement(modelIndex));
|
||||
m_requirementsTitleLabel->setText(tr("Requirement"));
|
||||
m_requirementsTextLabel->setText(m_model->GetRequirement(modelIndex));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_reqirementsIconLabel->hide();
|
||||
m_reqirementsTitleLabel->hide();
|
||||
m_reqirementsTextLabel->hide();
|
||||
m_requirementsIconLabel->hide();
|
||||
m_requirementsTitleLabel->hide();
|
||||
m_requirementsTextLabel->hide();
|
||||
m_requirementsMainSpacer->changeSize(0, 0, QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
}
|
||||
|
||||
// Depending gems
|
||||
m_dependingGems->Update("Depending Gems", "The following Gems will be automatically enabled with this Gem.", m_model->GetDependingGemNames(modelIndex));
|
||||
QStringList dependingGems = m_model->GetDependingGemNames(modelIndex);
|
||||
if (!dependingGems.isEmpty())
|
||||
{
|
||||
m_dependingGems->Update(tr("Depending Gems"), tr("The following Gems will be automatically enabled with this Gem."), dependingGems);
|
||||
m_dependingGems->show();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dependingGems->hide();
|
||||
}
|
||||
|
||||
// Additional information
|
||||
m_versionLabel->setText(QString("Gem Version: %1").arg(m_model->GetVersion(modelIndex)));
|
||||
m_lastUpdatedLabel->setText(QString("Last Updated: %1").arg(m_model->GetLastUpdated(modelIndex)));
|
||||
m_binarySizeLabel->setText(QString("Binary Size: %1 KB").arg(m_model->GetBinarySizeInKB(modelIndex)));
|
||||
m_versionLabel->setText(tr("Gem Version: %1").arg(m_model->GetVersion(modelIndex)));
|
||||
m_lastUpdatedLabel->setText(tr("Last Updated: %1").arg(m_model->GetLastUpdated(modelIndex)));
|
||||
m_binarySizeLabel->setText(tr("Binary Size: %1 KB").arg(m_model->GetBinarySizeInKB(modelIndex)));
|
||||
|
||||
m_mainWidget->adjustSize();
|
||||
m_mainWidget->show();
|
||||
@@ -108,35 +138,51 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
// Gem name, creator and summary
|
||||
m_nameLabel = CreateStyledLabel(m_mainLayout, 18, s_headerColor);
|
||||
m_creatorLabel = CreateStyledLabel(m_mainLayout, 12, s_headerColor);
|
||||
m_creatorLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_headerColor);
|
||||
m_mainLayout->addSpacing(5);
|
||||
|
||||
// TODO: QLabel seems to have issues determining the right sizeHint() for our font with the given font size.
|
||||
// This results into squeezed elements in the layout in case the text is a little longer than a sentence.
|
||||
m_summaryLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor);
|
||||
m_summaryLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_headerColor);
|
||||
m_mainLayout->addWidget(m_summaryLabel);
|
||||
m_summaryLabel->setWordWrap(true);
|
||||
m_summaryLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
m_summaryLabel->setOpenExternalLinks(true);
|
||||
m_mainLayout->addSpacing(5);
|
||||
|
||||
// License
|
||||
{
|
||||
QHBoxLayout* licenseHLayout = new QHBoxLayout();
|
||||
licenseHLayout->setMargin(0);
|
||||
licenseHLayout->setAlignment(Qt::AlignLeft);
|
||||
m_mainLayout->addLayout(licenseHLayout);
|
||||
|
||||
QLabel* licenseLabel = CreateStyledLabel(licenseHLayout, s_baseFontSize, s_headerColor);
|
||||
licenseLabel->setText(tr("License: "));
|
||||
|
||||
m_licenseLinkLabel = new LinkLabel("", QUrl(), s_baseFontSize);
|
||||
licenseHLayout->addWidget(m_licenseLinkLabel);
|
||||
|
||||
licenseHLayout->addStretch();
|
||||
|
||||
m_mainLayout->addSpacing(5);
|
||||
}
|
||||
|
||||
// Directory and documentation links
|
||||
{
|
||||
QHBoxLayout* linksHLayout = new QHBoxLayout();
|
||||
linksHLayout->setMargin(0);
|
||||
m_mainLayout->addLayout(linksHLayout);
|
||||
|
||||
QSpacerItem* spacerLeft = new QSpacerItem(0, 0, QSizePolicy::Expanding);
|
||||
linksHLayout->addSpacerItem(spacerLeft);
|
||||
linksHLayout->addStretch();
|
||||
|
||||
m_directoryLinkLabel = new LinkLabel("View in Directory");
|
||||
m_directoryLinkLabel = new LinkLabel(tr("View in Directory"));
|
||||
linksHLayout->addWidget(m_directoryLinkLabel);
|
||||
linksHLayout->addWidget(new QLabel("|"));
|
||||
m_documentationLinkLabel = new LinkLabel("Read Documentation");
|
||||
m_documentationLinkLabel = new LinkLabel(tr("Read Documentation"));
|
||||
linksHLayout->addWidget(m_documentationLinkLabel);
|
||||
|
||||
QSpacerItem* spacerRight = new QSpacerItem(0, 0, QSizePolicy::Expanding);
|
||||
linksHLayout->addSpacerItem(spacerRight);
|
||||
linksHLayout->addStretch();
|
||||
|
||||
m_mainLayout->addSpacing(8);
|
||||
}
|
||||
@@ -144,46 +190,48 @@ namespace O3DE::ProjectManager
|
||||
// Separating line
|
||||
QFrame* hLine = new QFrame();
|
||||
hLine->setFrameShape(QFrame::HLine);
|
||||
hLine->setStyleSheet("color: #666666;");
|
||||
hLine->setObjectName("horizontalSeparatingLine");
|
||||
m_mainLayout->addWidget(hLine);
|
||||
|
||||
m_mainLayout->addSpacing(10);
|
||||
|
||||
// Requirements
|
||||
m_reqirementsTitleLabel = GemInspector::CreateStyledLabel(m_mainLayout, 16, s_headerColor);
|
||||
m_requirementsTitleLabel = GemInspector::CreateStyledLabel(m_mainLayout, 16, s_headerColor);
|
||||
|
||||
QHBoxLayout* requrementsLayout = new QHBoxLayout();
|
||||
requrementsLayout->setAlignment(Qt::AlignTop);
|
||||
requrementsLayout->setMargin(0);
|
||||
requrementsLayout->setSpacing(0);
|
||||
QHBoxLayout* requirementsLayout = new QHBoxLayout();
|
||||
requirementsLayout->setAlignment(Qt::AlignTop);
|
||||
requirementsLayout->setMargin(0);
|
||||
requirementsLayout->setSpacing(0);
|
||||
|
||||
m_reqirementsIconLabel = new QLabel();
|
||||
m_reqirementsIconLabel->setPixmap(QIcon(":/Warning.svg").pixmap(24, 24));
|
||||
requrementsLayout->addWidget(m_reqirementsIconLabel);
|
||||
m_requirementsIconLabel = new QLabel();
|
||||
m_requirementsIconLabel->setPixmap(QIcon(":/Warning.svg").pixmap(24, 24));
|
||||
requirementsLayout->addWidget(m_requirementsIconLabel);
|
||||
|
||||
m_reqirementsTextLabel = GemInspector::CreateStyledLabel(requrementsLayout, 10, s_textColor);
|
||||
m_reqirementsTextLabel->setWordWrap(true);
|
||||
m_reqirementsTextLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
m_reqirementsTextLabel->setOpenExternalLinks(true);
|
||||
m_requirementsTextLabel = GemInspector::CreateStyledLabel(requirementsLayout, 10, s_textColor);
|
||||
m_requirementsTextLabel->setWordWrap(true);
|
||||
m_requirementsTextLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
|
||||
m_requirementsTextLabel->setOpenExternalLinks(true);
|
||||
|
||||
QSpacerItem* reqirementsSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding);
|
||||
requrementsLayout->addSpacerItem(reqirementsSpacer);
|
||||
QSpacerItem* requirementsSpacer = new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding);
|
||||
requirementsLayout->addSpacerItem(requirementsSpacer);
|
||||
|
||||
m_mainLayout->addLayout(requrementsLayout);
|
||||
m_mainLayout->addLayout(requirementsLayout);
|
||||
|
||||
m_mainLayout->addSpacing(20);
|
||||
m_requirementsMainSpacer = new QSpacerItem(0, 20, QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
m_mainLayout->addSpacerItem(m_requirementsMainSpacer);
|
||||
|
||||
// Depending gems
|
||||
m_dependingGems = new GemsSubWidget();
|
||||
connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [=](const QString& tag){ emit TagClicked(tag); });
|
||||
m_mainLayout->addWidget(m_dependingGems);
|
||||
m_mainLayout->addSpacing(20);
|
||||
|
||||
// Additional information
|
||||
QLabel* additionalInfoLabel = CreateStyledLabel(m_mainLayout, 14, s_headerColor);
|
||||
additionalInfoLabel->setText("Additional Information");
|
||||
additionalInfoLabel->setText(tr("Additional Information"));
|
||||
|
||||
m_versionLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor);
|
||||
m_lastUpdatedLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor);
|
||||
m_binarySizeLabel = CreateStyledLabel(m_mainLayout, 12, s_textColor);
|
||||
m_versionLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
|
||||
m_lastUpdatedLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
|
||||
m_binarySizeLabel = CreateStyledLabel(m_mainLayout, s_baseFontSize, s_textColor);
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
#include <QItemSelection>
|
||||
#include <QScrollArea>
|
||||
#include <QWidget>
|
||||
#include <QSpacerItem>
|
||||
#endif
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
|
||||
@@ -36,10 +36,16 @@ namespace O3DE::ProjectManager
|
||||
void Update(const QModelIndex& modelIndex);
|
||||
static QLabel* CreateStyledLabel(QLayout* layout, int fontSize, const QString& colorCodeString);
|
||||
|
||||
// Fonts
|
||||
inline constexpr static int s_baseFontSize = 12;
|
||||
|
||||
// Colors
|
||||
inline constexpr static const char* s_headerColor = "#FFFFFF";
|
||||
inline constexpr static const char* s_textColor = "#DDDDDD";
|
||||
|
||||
signals:
|
||||
void TagClicked(const QString& tag);
|
||||
|
||||
private slots:
|
||||
void OnSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
|
||||
|
||||
@@ -54,13 +60,15 @@ namespace O3DE::ProjectManager
|
||||
QLabel* m_nameLabel = nullptr;
|
||||
QLabel* m_creatorLabel = nullptr;
|
||||
QLabel* m_summaryLabel = nullptr;
|
||||
LinkLabel* m_licenseLinkLabel = nullptr;
|
||||
LinkLabel* m_directoryLinkLabel = nullptr;
|
||||
LinkLabel* m_documentationLinkLabel = nullptr;
|
||||
|
||||
// Requirements
|
||||
QLabel* m_reqirementsTitleLabel = nullptr;
|
||||
QLabel* m_reqirementsIconLabel = nullptr;
|
||||
QLabel* m_reqirementsTextLabel = nullptr;
|
||||
QLabel* m_requirementsTitleLabel = nullptr;
|
||||
QLabel* m_requirementsIconLabel = nullptr;
|
||||
QLabel* m_requirementsTextLabel = nullptr;
|
||||
QSpacerItem* m_requirementsMainSpacer = nullptr;
|
||||
|
||||
// Depending and conflicting gems
|
||||
GemsSubWidget* m_dependingGems = nullptr;
|
||||
|
||||
@@ -58,10 +58,13 @@ namespace O3DE::ProjectManager
|
||||
item->setData(gemInfo.m_path, RolePath);
|
||||
item->setData(gemInfo.m_requirement, RoleRequirement);
|
||||
item->setData(gemInfo.m_downloadStatus, RoleDownloadStatus);
|
||||
item->setData(gemInfo.m_licenseText, RoleLicenseText);
|
||||
item->setData(gemInfo.m_licenseLink, RoleLicenseLink);
|
||||
|
||||
appendRow(item);
|
||||
|
||||
const QModelIndex modelIndex = index(rowCount()-1, 0);
|
||||
m_nameToIndexMap[gemInfo.m_displayName] = modelIndex;
|
||||
m_nameToIndexMap[gemInfo.m_name] = modelIndex;
|
||||
}
|
||||
|
||||
@@ -247,6 +250,16 @@ namespace O3DE::ProjectManager
|
||||
return modelIndex.data(RoleRequirement).toString();
|
||||
}
|
||||
|
||||
QString GemModel::GetLicenseText(const QModelIndex& modelIndex)
|
||||
{
|
||||
return modelIndex.data(RoleLicenseText).toString();
|
||||
}
|
||||
|
||||
QString GemModel::GetLicenseLink(const QModelIndex& modelIndex)
|
||||
{
|
||||
return modelIndex.data(RoleLicenseLink).toString();
|
||||
}
|
||||
|
||||
GemModel* GemModel::GetSourceModel(QAbstractItemModel* model)
|
||||
{
|
||||
GemSortFilterProxyModel* proxyModel = qobject_cast<GemSortFilterProxyModel*>(model);
|
||||
|
||||
@@ -50,6 +50,8 @@ namespace O3DE::ProjectManager
|
||||
static QStringList GetFeatures(const QModelIndex& modelIndex);
|
||||
static QString GetPath(const QModelIndex& modelIndex);
|
||||
static QString GetRequirement(const QModelIndex& modelIndex);
|
||||
static QString GetLicenseText(const QModelIndex& modelIndex);
|
||||
static QString GetLicenseLink(const QModelIndex& modelIndex);
|
||||
static GemModel* GetSourceModel(QAbstractItemModel* model);
|
||||
static const GemModel* GetSourceModel(const QAbstractItemModel* model);
|
||||
|
||||
@@ -107,7 +109,9 @@ namespace O3DE::ProjectManager
|
||||
RoleTypes,
|
||||
RolePath,
|
||||
RoleRequirement,
|
||||
RoleDownloadStatus
|
||||
RoleDownloadStatus,
|
||||
RoleLicenseText,
|
||||
RoleLicenseLink
|
||||
};
|
||||
|
||||
QHash<QString, QModelIndex> m_nameToIndexMap;
|
||||
|
||||
@@ -207,6 +207,8 @@ namespace O3DE::ProjectManager
|
||||
void GemSortFilterProxyModel::ResetFilters()
|
||||
{
|
||||
m_searchString.clear();
|
||||
m_gemSelectedFilter = GemSelected::NoFilter;
|
||||
m_gemActiveFilter = GemActive::NoFilter;
|
||||
m_gemOriginFilter = {};
|
||||
m_platformFilter = {};
|
||||
m_typeFilter = {};
|
||||
|
||||
@@ -99,7 +99,7 @@ namespace O3DE::ProjectManager
|
||||
m_nameLabel->setObjectName("gemRepoInspectorNameLabel");
|
||||
m_mainLayout->addWidget(m_nameLabel);
|
||||
|
||||
m_repoLinkLabel = new LinkLabel(tr("Repo Url"), QUrl(""), 12, this);
|
||||
m_repoLinkLabel = new LinkLabel(tr("Repo Url"), QUrl(), 12, this);
|
||||
m_mainLayout->addWidget(m_repoLinkLabel);
|
||||
m_mainLayout->addSpacing(5);
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ namespace O3DE::ProjectManager
|
||||
m_layout->addWidget(m_textLabel);
|
||||
|
||||
m_tagWidget = new TagContainerWidget();
|
||||
connect(m_tagWidget, &TagContainerWidget::TagClicked, this, [=](const QString& tag){ emit TagClicked(tag); });
|
||||
m_layout->addWidget(m_tagWidget);
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,15 @@ namespace O3DE::ProjectManager
|
||||
class GemsSubWidget
|
||||
: public QWidget
|
||||
{
|
||||
Q_OBJECT // AUTOMOC
|
||||
|
||||
public:
|
||||
GemsSubWidget(QWidget* parent = nullptr);
|
||||
void Update(const QString& title, const QString& text, const QStringList& gemNames);
|
||||
|
||||
signals:
|
||||
void TagClicked(const QString& tag);
|
||||
|
||||
private:
|
||||
QLabel* m_titleLabel = nullptr;
|
||||
QLabel* m_textLabel = nullptr;
|
||||
|
||||
@@ -709,6 +709,8 @@ namespace O3DE::ProjectManager
|
||||
gemInfo.m_requirement = Py_To_String_Optional(data, "requirements", "");
|
||||
gemInfo.m_creator = Py_To_String_Optional(data, "origin", "");
|
||||
gemInfo.m_documentationLink = Py_To_String_Optional(data, "documentation_url", "");
|
||||
gemInfo.m_licenseText = Py_To_String_Optional(data, "license", "Unspecified License");
|
||||
gemInfo.m_licenseLink = Py_To_String_Optional(data, "license_url", "");
|
||||
|
||||
if (gemInfo.m_creator.contains("Open 3D Engine"))
|
||||
{
|
||||
|
||||
@@ -18,6 +18,11 @@ namespace O3DE::ProjectManager
|
||||
setObjectName("TagWidget");
|
||||
}
|
||||
|
||||
void TagWidget::mousePressEvent([[maybe_unused]] QMouseEvent* event)
|
||||
{
|
||||
emit(TagClicked(text()));
|
||||
}
|
||||
|
||||
TagContainerWidget::TagContainerWidget(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
@@ -45,7 +50,9 @@ namespace O3DE::ProjectManager
|
||||
|
||||
foreach (const QString& tag, tags)
|
||||
{
|
||||
flowLayout->addWidget(new TagWidget(tag));
|
||||
TagWidget* tagWidget = new TagWidget(tag);
|
||||
connect(tagWidget, &TagWidget::TagClicked, this, [=](const QString& tag){ emit TagClicked(tag); });
|
||||
flowLayout->addWidget(tagWidget);
|
||||
}
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -25,6 +25,12 @@ namespace O3DE::ProjectManager
|
||||
public:
|
||||
explicit TagWidget(const QString& text, QWidget* parent = nullptr);
|
||||
~TagWidget() = default;
|
||||
|
||||
signals:
|
||||
void TagClicked(const QString& tag);
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
};
|
||||
|
||||
// Widget containing multiple tags, automatically wrapping based on the size
|
||||
@@ -38,5 +44,8 @@ namespace O3DE::ProjectManager
|
||||
~TagContainerWidget() = default;
|
||||
|
||||
void Update(const QStringList& tags);
|
||||
|
||||
signals:
|
||||
void TagClicked(const QString& tag);
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"gem_name": "AWSClientAuth",
|
||||
"display_name": "AWS Client Authorization",
|
||||
"license": "Apache-2.0 Or MIT",
|
||||
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
|
||||
"origin": "Amazon Web Services, Inc.",
|
||||
"type": "Code",
|
||||
"summary": "AWS Client Auth provides client authentication and AWS authorization solution.",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"gem_name": "AWSCore",
|
||||
"display_name": "AWS Core",
|
||||
"license": "Apache-2.0 Or MIT",
|
||||
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
|
||||
"origin": "Amazon Web Services, Inc.",
|
||||
"type": "Code",
|
||||
"summary": "The AWS Core Gem provides basic shared AWS functionality such as AWS SDK initialization and client configuration, and is automatically added when selecting any AWS feature Gem.",
|
||||
|
||||
@@ -44,7 +44,7 @@ FLEET_CONFIGURATIONS = [
|
||||
'build_path': '<build path>',
|
||||
# (Conditional) The operating system that the game server binaries are built to run on.
|
||||
# This parameter is required if the parameter build_path is defined.
|
||||
# Choose from AMAZON_LINUX, AMAZON_LINUX or WINDOWS_2012.
|
||||
# Choose from AMAZON_LINUX or WINDOWS_2012.
|
||||
'operating_system': 'WINDOWS_2012'
|
||||
},
|
||||
# (Optional) Information about the use of a TLS/SSL certificate for a fleet.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"gem_name": "AWSGameLift",
|
||||
"display_name": "AWS GameLift",
|
||||
"license": "Apache-2.0 Or MIT",
|
||||
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
|
||||
"origin": "Amazon Web Services, Inc.",
|
||||
"type": "Code",
|
||||
"summary": "The AWS GameLift Gem provides a framework to extend O3DE networking layer to work with GameLift resources via GameLift server and client SDK.",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"gem_name": "AWSMetrics",
|
||||
"display_name": "AWS Metrics",
|
||||
"license": "Apache-2.0 Or MIT",
|
||||
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
|
||||
"origin": "Amazon Web Services, Inc.",
|
||||
"type": "Code",
|
||||
"summary": "The AWS Metrics Gem provides a solution for AWS metrics submission and analytics.",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"gem_name": "Achievements",
|
||||
"display_name": "Achievements",
|
||||
"license": "Apache-2.0 Or MIT",
|
||||
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
|
||||
"origin": "Open 3D Engine - o3de.org",
|
||||
"type": "Code",
|
||||
"summary": "The Achievements Gem provides a target platform agnostic interface for retrieving achievement details and unlocking achievements.",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"gem_name": "AssetMemoryAnalyzer",
|
||||
"display_name": "Asset Memory Analyzer",
|
||||
"license": "Apache-2.0 Or MIT",
|
||||
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
|
||||
"origin": "Open 3D Engine - o3de.org",
|
||||
"type": "Code",
|
||||
"summary": "The Asset Memory Analyzer Gem provides tools to profile asset memory usage in Open 3D Engine through ImGUI (Immediate Mode Graphical User Interface).",
|
||||
|
||||
@@ -150,7 +150,7 @@ struct AssetValidationTest
|
||||
|
||||
auto projectPathKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)
|
||||
+ "/project_path";
|
||||
m_registry.Set(projectPathKey, "AutomatedTesting");
|
||||
m_registry.Set(projectPathKey, (AZ::IO::FixedMaxPath(GetEngineRoot()) / "AutomatedTesting").Native());
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry);
|
||||
|
||||
// Set the engine root to the temporary directory and re-update the runtime file paths
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"gem_name": "AssetValidation",
|
||||
"display_name": "Asset Validation",
|
||||
"license": "Apache-2.0 Or MIT",
|
||||
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
|
||||
"origin": "Open 3D Engine - o3de.org",
|
||||
"type": "Code",
|
||||
"summary": "The Asset Validation Gem provides seed-related commands to ensure assets have valid seeds for asset bundling.",
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"gem_name": "ImageProcessingAtom",
|
||||
"display_name": "Atom Image Processing",
|
||||
"license": "Apache-2.0 Or MIT",
|
||||
"license_url": "https://github.com/o3de/o3de/blob/development/LICENSE.txt",
|
||||
"origin": "Open 3D Engine - o3de.org",
|
||||
"type": "Code",
|
||||
"summary": "",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user