Merge branch 'development' of https://github.com/o3de/o3de into bitset_serialization_includes
This commit is contained in:
@@ -120,7 +120,7 @@ namespace AZ::Utils
|
||||
AZ::Outcome<void, AZStd::string> WriteFile(AZStd::string_view content, AZStd::string_view filePath)
|
||||
{
|
||||
AZ::IO::FixedMaxPath filePathFixed = filePath; // Because FileIOStream requires a null-terminated string
|
||||
AZ::IO::FileIOStream stream(filePathFixed.c_str(), AZ::IO::OpenMode::ModeWrite);
|
||||
AZ::IO::FileIOStream stream(filePathFixed.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath);
|
||||
|
||||
bool success = false;
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Math/Vector3.h>
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
#include <AzCore/Component/ComponentBus.h>
|
||||
#include <AzCore/Math/Aabb.h>
|
||||
#include <AzFramework/Physics/Material.h>
|
||||
|
||||
namespace Physics
|
||||
{
|
||||
//! The QuadMeshType specifies the property of the heightfield quad.
|
||||
enum class QuadMeshType : uint8_t
|
||||
{
|
||||
SubdivideUpperLeftToBottomRight, //!< Subdivide the quad, from upper left to bottom right |\|, into two triangles.
|
||||
SubdivideBottomLeftToUpperRight, //!< Subdivide the quad, from bottom left to upper right |/|, into two triangles.
|
||||
Hole //!< The quad should be treated as a hole in the heightfield.
|
||||
};
|
||||
|
||||
struct HeightMaterialPoint
|
||||
{
|
||||
float m_height{ 0.0f }; //!< Holds the height of this point in the heightfield relative to the heightfield entity location.
|
||||
QuadMeshType m_quadMeshType{ QuadMeshType::SubdivideUpperLeftToBottomRight }; //!< By default, create two triangles like this |\|, where this point is in the upper left corner.
|
||||
uint8_t m_materialIndex{ 0 }; //!< The surface material index for the upper left corner of this quad.
|
||||
uint16_t m_padding{ 0 }; //!< available for future use.
|
||||
};
|
||||
|
||||
//! An interface to provide heightfield values.
|
||||
class HeightfieldProviderRequests
|
||||
: public AZ::ComponentBus
|
||||
{
|
||||
public:
|
||||
//! Returns the distance between each height in the map.
|
||||
//! @return Vector containing Column Spacing, Rows Spacing.
|
||||
virtual AZ::Vector2 GetHeightfieldGridSpacing() const = 0;
|
||||
|
||||
//! Returns the height field gridsize.
|
||||
//! @param numColumns contains the size of the grid in the x direction.
|
||||
//! @param numRows contains the size of the grid in the y direction.
|
||||
virtual void GetHeightfieldGridSize(int32_t& numColumns, int32_t& numRows) const = 0;
|
||||
|
||||
//! Returns the height field min and max height bounds.
|
||||
//! @param minHeightBounds contains the minimum height that the heightfield can contain.
|
||||
//! @param maxHeightBounds contains the maximum height that the heightfield can contain.
|
||||
virtual void GetHeightfieldHeightBounds(float& minHeightBounds, float& maxHeightBounds) const = 0;
|
||||
|
||||
//! Returns the AABB of the heightfield.
|
||||
//! This is provided separately from the shape AABB because the heightfield might choose to modify the AABB bounds.
|
||||
//! @return AABB of the heightfield.
|
||||
virtual AZ::Aabb GetHeightfieldAabb() const = 0;
|
||||
|
||||
//! Returns the world transform for the heightfield.
|
||||
//! This is provided separately from the entity transform because the heightfield might want to clear out the rotation or scale.
|
||||
//! @return world transform that should be used with the heightfield data.
|
||||
virtual AZ::Transform GetHeightfieldTransform() const = 0;
|
||||
|
||||
//! Returns the list of materials used by the height field.
|
||||
//! @return returns a vector of all materials.
|
||||
virtual AZStd::vector<MaterialId> GetMaterialList() const = 0;
|
||||
|
||||
//! Returns the list of heights used by the height field.
|
||||
//! @return the rows*columns vector of the heights.
|
||||
virtual AZStd::vector<float> GetHeights() const = 0;
|
||||
|
||||
//! Returns the list of heights and materials used by the height field.
|
||||
//! @return the rows*columns vector of the heights and materials.
|
||||
virtual AZStd::vector<Physics::HeightMaterialPoint> GetHeightsAndMaterials() const = 0;
|
||||
};
|
||||
|
||||
using HeightfieldProviderRequestsBus = AZ::EBus<HeightfieldProviderRequests>;
|
||||
|
||||
//! Broadcasts notifications when heightfield data changes - heightfield providers implement HeightfieldRequests bus.
|
||||
class HeightfieldProviderNotifications
|
||||
: public AZ::ComponentBus
|
||||
{
|
||||
public:
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
|
||||
|
||||
//! Called whenever the heightfield data changes.
|
||||
//! @param the AABB of the area of data that changed.
|
||||
virtual void OnHeightfieldDataChanged([[maybe_unused]] const AZ::Aabb& dirtyRegion)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
~HeightfieldProviderNotifications() = default;
|
||||
};
|
||||
|
||||
using HeightfieldProviderNotificationBus = AZ::EBus<HeightfieldProviderNotifications>;
|
||||
} // namespace Physics
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
|
||||
#include <AzFramework/Physics/HeightfieldProviderBus.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class MockHeightfieldProviderNotificationBusListener
|
||||
: private Physics::HeightfieldProviderNotificationBus::Handler
|
||||
{
|
||||
public:
|
||||
MockHeightfieldProviderNotificationBusListener(AZ::EntityId entityid)
|
||||
{
|
||||
Physics::HeightfieldProviderNotificationBus::Handler::BusConnect(entityid);
|
||||
}
|
||||
|
||||
~MockHeightfieldProviderNotificationBusListener()
|
||||
{
|
||||
Physics::HeightfieldProviderNotificationBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
MOCK_METHOD1(OnHeightfieldDataChanged, void(const AZ::Aabb&));
|
||||
};
|
||||
} // namespace UnitTest
|
||||
@@ -37,6 +37,7 @@ namespace Physics
|
||||
REFLECT_SHAPETYPE_ENUM_VALUE(Sphere);
|
||||
REFLECT_SHAPETYPE_ENUM_VALUE(Cylinder);
|
||||
REFLECT_SHAPETYPE_ENUM_VALUE(PhysicsAsset);
|
||||
REFLECT_SHAPETYPE_ENUM_VALUE(Heightfield);
|
||||
|
||||
#undef REFLECT_SHAPETYPE_ENUM_VALUE
|
||||
}
|
||||
@@ -305,4 +306,125 @@ namespace Physics
|
||||
m_cachedNativeMesh = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void HeightfieldShapeConfiguration::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext
|
||||
->RegisterGenericType<AZStd::shared_ptr<HeightfieldShapeConfiguration>>();
|
||||
|
||||
serializeContext->Class<HeightfieldShapeConfiguration, ShapeConfiguration>()
|
||||
->Version(1);
|
||||
}
|
||||
}
|
||||
|
||||
HeightfieldShapeConfiguration::~HeightfieldShapeConfiguration()
|
||||
{
|
||||
SetCachedNativeHeightfield(nullptr);
|
||||
}
|
||||
|
||||
HeightfieldShapeConfiguration::HeightfieldShapeConfiguration(const HeightfieldShapeConfiguration& other)
|
||||
: ShapeConfiguration(other)
|
||||
, m_gridResolution(other.m_gridResolution)
|
||||
, m_numColumns(other.m_numColumns)
|
||||
, m_numRows(other.m_numRows)
|
||||
, m_samples(other.m_samples)
|
||||
, m_minHeightBounds(other.m_minHeightBounds)
|
||||
, m_maxHeightBounds(other.m_maxHeightBounds)
|
||||
, m_cachedNativeHeightfield(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
HeightfieldShapeConfiguration& HeightfieldShapeConfiguration::operator=(const HeightfieldShapeConfiguration& other)
|
||||
{
|
||||
ShapeConfiguration::operator=(other);
|
||||
|
||||
m_gridResolution = other.m_gridResolution;
|
||||
m_numColumns = other.m_numColumns;
|
||||
m_numRows = other.m_numRows;
|
||||
m_samples = other.m_samples;
|
||||
m_minHeightBounds = other.m_minHeightBounds;
|
||||
m_maxHeightBounds = other.m_maxHeightBounds;
|
||||
|
||||
// Prevent raw pointer from being copied
|
||||
m_cachedNativeHeightfield = nullptr;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void* HeightfieldShapeConfiguration::GetCachedNativeHeightfield() const
|
||||
{
|
||||
return m_cachedNativeHeightfield;
|
||||
}
|
||||
|
||||
void HeightfieldShapeConfiguration::SetCachedNativeHeightfield(void* cachedNativeHeightfield) const
|
||||
{
|
||||
if (m_cachedNativeHeightfield)
|
||||
{
|
||||
Physics::SystemRequestBus::Broadcast(&Physics::SystemRequests::ReleaseNativeHeightfieldObject, m_cachedNativeHeightfield);
|
||||
}
|
||||
|
||||
m_cachedNativeHeightfield = cachedNativeHeightfield;
|
||||
}
|
||||
|
||||
AZ::Vector2 HeightfieldShapeConfiguration::GetGridResolution() const
|
||||
{
|
||||
return m_gridResolution;
|
||||
}
|
||||
|
||||
void HeightfieldShapeConfiguration::SetGridResolution(const AZ::Vector2& gridResolution)
|
||||
{
|
||||
m_gridResolution = gridResolution;
|
||||
}
|
||||
|
||||
int32_t HeightfieldShapeConfiguration::GetNumColumns() const
|
||||
{
|
||||
return m_numColumns;
|
||||
}
|
||||
|
||||
void HeightfieldShapeConfiguration::SetNumColumns(int32_t numColumns)
|
||||
{
|
||||
m_numColumns = numColumns;
|
||||
}
|
||||
|
||||
int32_t HeightfieldShapeConfiguration::GetNumRows() const
|
||||
{
|
||||
return m_numRows;
|
||||
}
|
||||
|
||||
void HeightfieldShapeConfiguration::SetNumRows(int32_t numRows)
|
||||
{
|
||||
m_numRows = numRows;
|
||||
}
|
||||
|
||||
const AZStd::vector<Physics::HeightMaterialPoint>& HeightfieldShapeConfiguration::GetSamples() const
|
||||
{
|
||||
return m_samples;
|
||||
}
|
||||
|
||||
void HeightfieldShapeConfiguration::SetSamples(const AZStd::vector<Physics::HeightMaterialPoint>& samples)
|
||||
{
|
||||
m_samples = samples;
|
||||
}
|
||||
|
||||
float HeightfieldShapeConfiguration::GetMinHeightBounds() const
|
||||
{
|
||||
return m_minHeightBounds;
|
||||
}
|
||||
|
||||
void HeightfieldShapeConfiguration::SetMinHeightBounds(float minBounds)
|
||||
{
|
||||
m_minHeightBounds = minBounds;
|
||||
}
|
||||
|
||||
float HeightfieldShapeConfiguration::GetMaxHeightBounds() const
|
||||
{
|
||||
return m_maxHeightBounds;
|
||||
}
|
||||
|
||||
void HeightfieldShapeConfiguration::SetMaxHeightBounds(float maxBounds)
|
||||
{
|
||||
m_maxHeightBounds = maxBounds;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Math/Vector3.h>
|
||||
#include <AzCore/Math/Vector2.h>
|
||||
#include <AzCore/Math/Quaternion.h>
|
||||
#include <AzCore/Asset/AssetCommon.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
|
||||
#include <AzFramework/Physics/HeightfieldProviderBus.h>
|
||||
|
||||
namespace Physics
|
||||
{
|
||||
/// Used to identify shape configuration type from base class.
|
||||
@@ -27,6 +30,7 @@ namespace Physics
|
||||
Native, ///< Native shape configuration if user wishes to bypass generic shape configurations.
|
||||
PhysicsAsset, ///< Shapes configured in the asset.
|
||||
CookedMesh, ///< Stores a blob of mesh data cooked for the specific engine.
|
||||
Heightfield ///< Interacts with the physics system heightfield
|
||||
};
|
||||
|
||||
class ShapeConfiguration
|
||||
@@ -196,4 +200,52 @@ namespace Physics
|
||||
mutable void* m_cachedNativeMesh = nullptr;
|
||||
};
|
||||
|
||||
class HeightfieldShapeConfiguration
|
||||
: public ShapeConfiguration
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(HeightfieldShapeConfiguration, AZ::SystemAllocator, 0);
|
||||
AZ_RTTI(HeightfieldShapeConfiguration, "{8DF47C83-D2A9-4E7C-8620-5E173E43C0B3}", ShapeConfiguration);
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
HeightfieldShapeConfiguration() = default;
|
||||
HeightfieldShapeConfiguration(const HeightfieldShapeConfiguration&);
|
||||
HeightfieldShapeConfiguration& operator=(const HeightfieldShapeConfiguration&);
|
||||
~HeightfieldShapeConfiguration();
|
||||
|
||||
ShapeType GetShapeType() const override
|
||||
{
|
||||
return ShapeType::Heightfield;
|
||||
}
|
||||
|
||||
void* GetCachedNativeHeightfield() const;
|
||||
void SetCachedNativeHeightfield(void* cachedNativeHeightfield) const;
|
||||
AZ::Vector2 GetGridResolution() const;
|
||||
void SetGridResolution(const AZ::Vector2& gridSpacing);
|
||||
int32_t GetNumColumns() const;
|
||||
void SetNumColumns(int32_t numColumns);
|
||||
int32_t GetNumRows() const;
|
||||
void SetNumRows(int32_t numRows);
|
||||
const AZStd::vector<Physics::HeightMaterialPoint>& GetSamples() const;
|
||||
void SetSamples(const AZStd::vector<Physics::HeightMaterialPoint>& samples);
|
||||
float GetMinHeightBounds() const;
|
||||
void SetMinHeightBounds(float minBounds);
|
||||
float GetMaxHeightBounds() const;
|
||||
void SetMaxHeightBounds(float maxBounds);
|
||||
|
||||
private:
|
||||
//! The number of meters between each heightfield sample.
|
||||
AZ::Vector2 m_gridResolution{ 1.0f };
|
||||
//! The number of columns in the heightfield sample grid.
|
||||
int32_t m_numColumns{ 0 };
|
||||
//! The number of rows in the heightfield sample grid.
|
||||
int32_t m_numRows{ 0 };
|
||||
//! The minimum and maximum heights that can be used by this heightfield.
|
||||
//! This can be used by the physics system to choose a more optimal heightfield data type internally (ex: int16, uint8)
|
||||
float m_minHeightBounds{AZStd::numeric_limits<float>::lowest()};
|
||||
float m_maxHeightBounds{AZStd::numeric_limits<float>::max()};
|
||||
//! The grid of sample points for the heightfield.
|
||||
AZStd::vector<Physics::HeightMaterialPoint> m_samples;
|
||||
//! An optional storage pointer for the physics system to cache its native heightfield representation.
|
||||
mutable void* m_cachedNativeHeightfield{ nullptr };
|
||||
};
|
||||
} // namespace Physics
|
||||
|
||||
@@ -132,6 +132,10 @@ namespace Physics
|
||||
|
||||
virtual AZStd::shared_ptr<Material> CreateMaterial(const Physics::MaterialConfiguration& materialConfiguration) = 0;
|
||||
|
||||
/// Releases the height field object created by the physics backend.
|
||||
/// @param nativeHeightfieldObject Pointer to the height field object.
|
||||
virtual void ReleaseNativeHeightfieldObject(void* nativeHeightfieldObject) = 0;
|
||||
|
||||
/// Releases the mesh object created by the physics backend.
|
||||
/// @param nativeMeshObject Pointer to the mesh object.
|
||||
virtual void ReleaseNativeMeshObject(void* nativeMeshObject) = 0;
|
||||
|
||||
@@ -107,6 +107,7 @@ namespace Physics
|
||||
PhysicsAssetShapeConfiguration::Reflect(context);
|
||||
NativeShapeConfiguration::Reflect(context);
|
||||
CookedMeshShapeConfiguration::Reflect(context);
|
||||
HeightfieldShapeConfiguration::Reflect(context);
|
||||
AzPhysics::SystemInterface::Reflect(context);
|
||||
AzPhysics::Scene::Reflect(context);
|
||||
AzPhysics::CollisionLayer::Reflect(context);
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
|
||||
set(FILES
|
||||
Mocks/MockHeightfieldProviderBus.h
|
||||
)
|
||||
@@ -14,9 +14,8 @@ namespace AzFramework
|
||||
{
|
||||
AZ_CVAR(bool, bg_octreeUseQuadtree, false, nullptr, AZ::ConsoleFunctorFlags::ReadOnly, "If set to true, the visibility octrees will degenerate to a quadtree split along the X/Y plane");
|
||||
AZ_CVAR(float, bg_octreeMaxWorldExtents, 16384.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum supported world size by the world octreeSystemComponent");
|
||||
AZ_CVAR(uint32_t, bg_octreeNodeMaxEntries, 64, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum number of entries to allow in any node before forcing a split");
|
||||
AZ_CVAR(uint32_t, bg_octreeNodeMinEntries, 32, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum number of entries to allow in a node resulting from a merge operation");
|
||||
|
||||
AZ_CVAR(uint32_t, bg_octreeNodeMaxEntries, 64, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum number of entries to allow in any node before forcing a split");
|
||||
AZ_CVAR(uint32_t, bg_octreeNodeMinEntries, 32, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum number of entries to allow in a node resulting from a merge operation");
|
||||
|
||||
static uint32_t GetChildNodeCount()
|
||||
{
|
||||
@@ -25,14 +24,12 @@ namespace AzFramework
|
||||
return (bg_octreeUseQuadtree) ? QuadtreeNodeChildCount : OctreeNodeChildCount;
|
||||
}
|
||||
|
||||
|
||||
OctreeNode::OctreeNode(const AZ::Aabb& bounds)
|
||||
: m_bounds(bounds)
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
OctreeNode::OctreeNode(OctreeNode&& rhs)
|
||||
: m_bounds(rhs.m_bounds)
|
||||
, m_parent(rhs.m_parent)
|
||||
@@ -46,7 +43,6 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
OctreeNode& OctreeNode::operator=(OctreeNode&& rhs)
|
||||
{
|
||||
m_bounds = rhs.m_bounds;
|
||||
@@ -63,7 +59,6 @@ namespace AzFramework
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
void OctreeNode::Insert(OctreeScene& octreeScene, VisibilityEntry* entry)
|
||||
{
|
||||
AZ_Assert(entry->m_internalNode == nullptr, "Double-insertion: Insert invoked for an entry already bound to the OctreeScene");
|
||||
@@ -98,7 +93,6 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OctreeNode::Update(OctreeScene& octreeScene, VisibilityEntry* entry)
|
||||
{
|
||||
AZ_Assert(entry->m_internalNode == this, "Update invoked for an entry bound to a different OctreeNode");
|
||||
@@ -129,7 +123,6 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OctreeNode::Remove(OctreeScene& octreeScene, VisibilityEntry* entry)
|
||||
{
|
||||
AZ_Assert(entry->m_internalNode == this, "Remove invoked for an entry bound to a different OctreeNode");
|
||||
@@ -152,25 +145,30 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OctreeNode::Enumerate(const AZ::Aabb& aabb, const IVisibilityScene::EnumerateCallback& callback) const
|
||||
{
|
||||
EnumerateHelper(aabb, callback);
|
||||
if (AZ::ShapeIntersection::Overlaps(aabb, m_bounds))
|
||||
{
|
||||
EnumerateHelper(aabb, callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OctreeNode::Enumerate(const AZ::Sphere& sphere, const IVisibilityScene::EnumerateCallback& callback) const
|
||||
{
|
||||
EnumerateHelper(sphere, callback);
|
||||
if (AZ::ShapeIntersection::Overlaps(sphere, m_bounds))
|
||||
{
|
||||
EnumerateHelper(sphere, callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OctreeNode::Enumerate(const AZ::Frustum& frustum, const IVisibilityScene::EnumerateCallback& callback) const
|
||||
{
|
||||
EnumerateHelper(frustum, callback);
|
||||
if (AZ::ShapeIntersection::Overlaps(frustum, m_bounds))
|
||||
{
|
||||
EnumerateHelper(frustum, callback);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OctreeNode::EnumerateNoCull(const IVisibilityScene::EnumerateCallback& callback) const
|
||||
{
|
||||
// Invoke the callback for the current node
|
||||
@@ -190,25 +188,21 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const AZStd::vector<VisibilityEntry*>& OctreeNode::GetEntries() const
|
||||
{
|
||||
return m_entries;
|
||||
}
|
||||
|
||||
|
||||
OctreeNode* OctreeNode::GetChildren() const
|
||||
{
|
||||
return m_children;
|
||||
}
|
||||
|
||||
|
||||
bool OctreeNode::IsLeaf() const
|
||||
{
|
||||
return m_children == nullptr;
|
||||
}
|
||||
|
||||
|
||||
void OctreeNode::TryMerge(OctreeScene& octreeScene)
|
||||
{
|
||||
if (IsLeaf())
|
||||
@@ -236,7 +230,6 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
void OctreeNode::EnumerateHelper(const T& boundingVolume, const IVisibilityScene::EnumerateCallback& callback) const
|
||||
{
|
||||
@@ -262,7 +255,6 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OctreeNode::Split(OctreeScene& octreeScene)
|
||||
{
|
||||
AZ_Assert(m_children == nullptr, "Split invoked on an octreeScene node that has already been split");
|
||||
@@ -312,7 +304,6 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OctreeNode::Merge(OctreeScene& octreeScene)
|
||||
{
|
||||
AZ_Assert(m_children != nullptr, "Merge invoked on an octreeScene node that does not have children");
|
||||
@@ -371,7 +362,6 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OctreeScene::RemoveEntry(VisibilityEntry& entry)
|
||||
{
|
||||
AZStd::lock_guard<AZStd::shared_mutex> lock(m_sharedMutex);
|
||||
@@ -382,35 +372,30 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OctreeScene::Enumerate(const AZ::Aabb& aabb, const IVisibilityScene::EnumerateCallback& callback) const
|
||||
{
|
||||
AZStd::shared_lock<AZStd::shared_mutex> lock(m_sharedMutex);
|
||||
m_root.Enumerate(aabb, callback);
|
||||
}
|
||||
|
||||
|
||||
void OctreeScene::Enumerate(const AZ::Sphere& sphere, const IVisibilityScene::EnumerateCallback& callback) const
|
||||
{
|
||||
AZStd::shared_lock<AZStd::shared_mutex> lock(m_sharedMutex);
|
||||
m_root.Enumerate(sphere, callback);
|
||||
}
|
||||
|
||||
|
||||
void OctreeScene::Enumerate(const AZ::Frustum& frustum, const IVisibilityScene::EnumerateCallback& callback) const
|
||||
{
|
||||
AZStd::shared_lock<AZStd::shared_mutex> lock(m_sharedMutex);
|
||||
m_root.Enumerate(frustum, callback);
|
||||
}
|
||||
|
||||
|
||||
void OctreeScene::EnumerateNoCull(const IVisibilityScene::EnumerateCallback& callback) const
|
||||
{
|
||||
AZStd::shared_lock<AZStd::shared_mutex> lock(m_sharedMutex);
|
||||
m_root.EnumerateNoCull(callback);
|
||||
}
|
||||
|
||||
|
||||
uint32_t OctreeScene::GetEntryCount() const
|
||||
{
|
||||
return m_entryCount;
|
||||
@@ -421,26 +406,22 @@ namespace AzFramework
|
||||
return m_nodeCount;
|
||||
}
|
||||
|
||||
|
||||
uint32_t OctreeScene::GetFreeNodeCount() const
|
||||
{
|
||||
// Each entry represents GetChildNodeCount() nodes
|
||||
return aznumeric_cast<uint32_t>(m_freeOctreeNodes.size() * GetChildNodeCount());
|
||||
}
|
||||
|
||||
|
||||
uint32_t OctreeScene::GetPageCount() const
|
||||
{
|
||||
return aznumeric_cast<uint32_t>(m_nodeCache.size());
|
||||
}
|
||||
|
||||
|
||||
uint32_t OctreeScene::GetChildNodeCount() const
|
||||
{
|
||||
return AzFramework::GetChildNodeCount();
|
||||
}
|
||||
|
||||
|
||||
void OctreeScene::DumpStats()
|
||||
{
|
||||
AZ_TracePrintf("Console", "OctreeScene[\"%s\"]::EntryCount = %u", GetName().GetCStr(), GetEntryCount());
|
||||
@@ -450,21 +431,18 @@ namespace AzFramework
|
||||
AZ_TracePrintf("Console", "OctreeScene[\"%s\"]::ChildNodeCount = %u", GetName().GetCStr(), GetChildNodeCount());
|
||||
}
|
||||
|
||||
|
||||
static inline uint32_t CreateNodeIndex(uint32_t page, uint32_t offset)
|
||||
{
|
||||
AZ_Assert(page <= 0xFFFF && offset <= 0xFFFF, "Out of range values passed to CreateNodeIndex");
|
||||
return (page << 16) | offset;
|
||||
}
|
||||
|
||||
|
||||
static inline void ExtractPageAndOffsetFromIndex(uint32_t index, uint32_t& page, uint32_t& offset)
|
||||
{
|
||||
offset = index & 0x0000FFFF;
|
||||
page = index >> 16;
|
||||
}
|
||||
|
||||
|
||||
uint32_t OctreeScene::AllocateChildNodes()
|
||||
{
|
||||
const uint32_t childCount = GetChildNodeCount();
|
||||
@@ -508,14 +486,12 @@ namespace AzFramework
|
||||
return CreateNodeIndex(nextChildPage, nextChildOffset);
|
||||
}
|
||||
|
||||
|
||||
void OctreeScene::ReleaseChildNodes(uint32_t nodeIndex)
|
||||
{
|
||||
m_nodeCount -= GetChildNodeCount();
|
||||
m_freeOctreeNodes.push(nodeIndex);
|
||||
}
|
||||
|
||||
|
||||
OctreeNode* OctreeScene::GetChildNodesAtIndex(uint32_t nodeIndex) const
|
||||
{
|
||||
uint32_t childPage;
|
||||
@@ -524,7 +500,6 @@ namespace AzFramework
|
||||
return &(*m_nodeCache[childPage])[childOffset];
|
||||
}
|
||||
|
||||
|
||||
void OctreeSystemComponent::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
@@ -534,19 +509,16 @@ namespace AzFramework
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OctreeSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
|
||||
{
|
||||
provided.push_back(AZ_CRC("OctreeService"));
|
||||
}
|
||||
|
||||
|
||||
void OctreeSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
|
||||
{
|
||||
incompatible.push_back(AZ_CRC("OctreeService"));
|
||||
}
|
||||
|
||||
|
||||
OctreeSystemComponent::OctreeSystemComponent()
|
||||
{
|
||||
AZ::Interface<IVisibilitySystem>::Register(this);
|
||||
@@ -555,7 +527,6 @@ namespace AzFramework
|
||||
m_defaultScene = aznew OctreeScene(AZ::Name("DefaultVisibilityScene"));
|
||||
}
|
||||
|
||||
|
||||
OctreeSystemComponent::~OctreeSystemComponent()
|
||||
{
|
||||
AZ_Assert(m_scenes.empty(), "All IVisibilityScenes must be destroyed before shutdown");
|
||||
@@ -566,13 +537,11 @@ namespace AzFramework
|
||||
AZ::Interface<IVisibilitySystem>::Unregister(this);
|
||||
}
|
||||
|
||||
|
||||
void OctreeSystemComponent::Activate()
|
||||
{
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
void OctreeSystemComponent::Deactivate()
|
||||
{
|
||||
;
|
||||
@@ -591,7 +560,6 @@ namespace AzFramework
|
||||
return newScene;
|
||||
}
|
||||
|
||||
|
||||
void OctreeSystemComponent::DestroyVisibilityScene(IVisibilityScene* visScene)
|
||||
{
|
||||
for (auto iter = m_scenes.begin(); iter != m_scenes.end(); ++iter)
|
||||
@@ -606,7 +574,6 @@ namespace AzFramework
|
||||
AZ_Assert(false, "visScene[\"%s\"] not found in the OctreeSystemComponent", visScene->GetName().GetCStr());
|
||||
}
|
||||
|
||||
|
||||
IVisibilityScene* OctreeSystemComponent::FindVisibilityScene(const AZ::Name& sceneName)
|
||||
{
|
||||
for (OctreeScene* scene : m_scenes)
|
||||
@@ -619,7 +586,6 @@ namespace AzFramework
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
void OctreeSystemComponent::DumpStats([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments)
|
||||
{
|
||||
for (OctreeScene* scene : m_scenes)
|
||||
|
||||
@@ -228,6 +228,7 @@ set(FILES
|
||||
Physics/Configuration/SimulatedBodyConfiguration.cpp
|
||||
Physics/Configuration/SystemConfiguration.h
|
||||
Physics/Configuration/SystemConfiguration.cpp
|
||||
Physics/HeightfieldProviderBus.h
|
||||
Physics/SimulatedBodies/RigidBody.h
|
||||
Physics/SimulatedBodies/RigidBody.cpp
|
||||
Physics/SimulatedBodies/StaticRigidBody.h
|
||||
@@ -251,6 +252,7 @@ set(FILES
|
||||
Physics/Shape.h
|
||||
Physics/ShapeConfiguration.h
|
||||
Physics/ShapeConfiguration.cpp
|
||||
Physics/HeightfieldProviderBus.h
|
||||
Physics/SystemBus.h
|
||||
Physics/ColliderComponentBus.h
|
||||
Physics/RagdollPhysicsBus.h
|
||||
|
||||
@@ -42,6 +42,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
NAMESPACE AZ
|
||||
FILES_CMAKE
|
||||
Tests/framework_shared_tests_files.cmake
|
||||
AzFramework/Physics/physics_mock_files.cmake
|
||||
INCLUDE_DIRECTORIES
|
||||
PUBLIC
|
||||
Tests
|
||||
@@ -53,7 +54,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
|
||||
AZ::AzTest
|
||||
AZ::AzTestShared
|
||||
)
|
||||
|
||||
|
||||
if(PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
|
||||
ly_add_target(
|
||||
|
||||
+18
@@ -7,17 +7,35 @@
|
||||
*/
|
||||
|
||||
#include <AzFramework/Application/Application.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
#include <AzFramework/XcbApplication.h>
|
||||
#endif
|
||||
|
||||
constexpr rlim_t g_minimumOpenFileHandles = 65536L;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
namespace AzFramework
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Application::Implementation* Application::Implementation::Create()
|
||||
{
|
||||
// The default open file limit for processes may not be enough for O3DE applications.
|
||||
// We will need to increase to the recommended value if the current open file limit
|
||||
// is not sufficient.
|
||||
rlimit currentLimit;
|
||||
int get_limit_result = getrlimit(RLIMIT_NOFILE, ¤tLimit);
|
||||
AZ_Warning("Application", get_limit_result == 0, "Unable to read current ulimit open file limits");
|
||||
if ((get_limit_result == 0) && (currentLimit.rlim_cur < g_minimumOpenFileHandles || currentLimit.rlim_max < g_minimumOpenFileHandles))
|
||||
{
|
||||
rlimit newLimit;
|
||||
newLimit.rlim_cur = g_minimumOpenFileHandles; // Soft Limit
|
||||
newLimit.rlim_max = g_minimumOpenFileHandles; // Hard Limit
|
||||
[[maybe_unused]] int set_limit_result = setrlimit(RLIMIT_NOFILE, &newLimit);
|
||||
AZ_Assert(set_limit_result == 0, "Unable to update open file limits");
|
||||
}
|
||||
|
||||
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
return aznew XcbApplication();
|
||||
#elif PAL_TRAIT_LINUX_WINDOW_MANAGER_WAYLAND
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace AzNetworking
|
||||
m_timeoutItemMap.erase(timeoutId);
|
||||
}
|
||||
|
||||
void TimeoutQueue::UpdateTimeouts(ITimeoutHandler& timeoutHandler, int32_t maxTimeouts)
|
||||
void TimeoutQueue::UpdateTimeouts(const TimeoutHandler& timeoutHandler, int32_t maxTimeouts)
|
||||
{
|
||||
int32_t numTimeouts = 0;
|
||||
if (maxTimeouts < 0)
|
||||
@@ -103,7 +103,7 @@ namespace AzNetworking
|
||||
|
||||
// By this point, the item is definitely timed out
|
||||
// Invoke the timeout function to see how to proceed
|
||||
const TimeoutResult result = timeoutHandler.HandleTimeout(mapItem);
|
||||
const TimeoutResult result = timeoutHandler(mapItem);
|
||||
|
||||
if (result == TimeoutResult::Refresh)
|
||||
{
|
||||
@@ -122,4 +122,10 @@ namespace AzNetworking
|
||||
m_timeoutItemMap.erase(itemTimeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
void TimeoutQueue::UpdateTimeouts(ITimeoutHandler& timeoutHandler, int32_t maxTimeouts)
|
||||
{
|
||||
TimeoutHandler handler([&timeoutHandler](TimeoutQueue::TimeoutItem& item) { return timeoutHandler.HandleTimeout(item); });
|
||||
UpdateTimeouts(handler, maxTimeouts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,12 @@ namespace AzNetworking
|
||||
//! @param timeoutId the identifier of the item to remove
|
||||
void RemoveItem(TimeoutId timeoutId);
|
||||
|
||||
//! Updates timeouts for all items, invokes the provided timeout functor if required.
|
||||
//! @param timeoutHandler lambda to invoke for all timeouts
|
||||
//! @param maxTimeouts the maximum number of timeouts to process before breaking iteration
|
||||
using TimeoutHandler = AZStd::function<TimeoutResult(TimeoutQueue::TimeoutItem&)>;
|
||||
void UpdateTimeouts(const TimeoutHandler& timeoutHandler, int32_t maxTimeouts = -1);
|
||||
|
||||
//! Updates timeouts for all items, invokes timeout handlers if required.
|
||||
//! @param timeoutHandler listener instance to call back on for timeouts
|
||||
//! @param maxTimeouts the maximum number of timeouts to process before breaking iteration
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <QIcon>
|
||||
#include <QToolButton>
|
||||
#include <QPropertyAnimation>
|
||||
#include <QPainter>
|
||||
|
||||
namespace AzQtComponents
|
||||
{
|
||||
@@ -27,6 +28,13 @@ namespace AzQtComponents
|
||||
setAttribute(Qt::WA_ShowWithoutActivating);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
m_borderRadius = toastConfiguration.m_borderRadius;
|
||||
if (m_borderRadius > 0)
|
||||
{
|
||||
setWindowFlags(Qt::FramelessWindowHint | Qt::Dialog);
|
||||
setAttribute(Qt::WA_TranslucentBackground);
|
||||
}
|
||||
|
||||
m_ui->setupUi(this);
|
||||
|
||||
QIcon toastIcon;
|
||||
@@ -53,6 +61,13 @@ namespace AzQtComponents
|
||||
m_ui->titleLabel->setText(toastConfiguration.m_title);
|
||||
m_ui->mainLabel->setText(toastConfiguration.m_description);
|
||||
|
||||
// hide the optional description if none is provided so the title is centered vertically
|
||||
if (toastConfiguration.m_description.isEmpty())
|
||||
{
|
||||
m_ui->mainLabel->setVisible(false);
|
||||
m_ui->verticalLayout->removeWidget(m_ui->mainLabel);
|
||||
}
|
||||
|
||||
m_lifeSpan.setInterval(aznumeric_cast<int>(toastConfiguration.m_duration.count()));
|
||||
m_closeOnClick = toastConfiguration.m_closeOnClick;
|
||||
|
||||
@@ -68,6 +83,24 @@ namespace AzQtComponents
|
||||
{
|
||||
}
|
||||
|
||||
void ToastNotification::paintEvent(QPaintEvent* event)
|
||||
{
|
||||
if (m_borderRadius > 0)
|
||||
{
|
||||
QPainter p(this);
|
||||
p.setPen(Qt::transparent);
|
||||
QColor painterColor;
|
||||
painterColor.setRgbF(0, 0, 0, 255);
|
||||
p.setBrush(painterColor);
|
||||
p.setRenderHint(QPainter::Antialiasing);
|
||||
p.drawRoundedRect(rect(), m_borderRadius, m_borderRadius);
|
||||
}
|
||||
else
|
||||
{
|
||||
QDialog::paintEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void ToastNotification::ShowToastAtCursor()
|
||||
{
|
||||
QPoint globalCursorPos = QCursor::pos();
|
||||
|
||||
@@ -52,6 +52,8 @@ namespace AzQtComponents
|
||||
void mousePressEvent(QMouseEvent* mouseEvent) override;
|
||||
bool eventFilter(QObject* object, QEvent* event) override;
|
||||
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
|
||||
public slots:
|
||||
void StartTimer();
|
||||
void FadeOut();
|
||||
@@ -65,6 +67,7 @@ namespace AzQtComponents
|
||||
|
||||
bool m_closeOnClick;
|
||||
QTimer m_lifeSpan;
|
||||
uint32_t m_borderRadius = 0;
|
||||
|
||||
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
|
||||
AZStd::chrono::milliseconds m_fadeDuration;
|
||||
|
||||
@@ -191,7 +191,7 @@
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
|
||||
+1
@@ -37,6 +37,7 @@ namespace AzQtComponents
|
||||
QString m_title;
|
||||
QString m_description;
|
||||
QString m_customIconImage;
|
||||
uint32_t m_borderRadius = 0;
|
||||
|
||||
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
|
||||
AZStd::chrono::milliseconds m_duration = AZStd::chrono::milliseconds(5000);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.38947 2H2.97474C1.61046 2 0.5 3.09717 0.5 4.44876V13.0353C0.5 14.4028 1.61046 15.5 2.97474 15.5H11.5253C12.8895 15.5 14 14.4028 14 13.0353V9.61053C13.5767 9.88652 13.1131 10.1058 12.6199 10.2576V13.0353H12.5881C12.5881 13.6396 12.0964 14.1007 11.5094 14.1007H2.97474C2.37192 14.1007 1.896 13.6237 1.896 13.0353V4.44876C1.896 3.86042 2.38778 3.38339 2.97474 3.38339H5.74142C5.89324 2.88897 6.11287 2.42422 6.38947 2Z" fill="white"/>
|
||||
<path d="M11 0.5C8.51446 0.5 6.5 2.51471 6.5 5C6.5 7.48529 8.51446 9.5 11 9.5C13.485 9.5 15.5 7.48529 15.5 5C15.5 2.51471 13.485 0.5 11 0.5ZM13.8633 6.39526C13.9155 6.43923 13.8975 6.54774 13.8221 6.63723L13.1024 7.49454C13.0276 7.58429 12.9237 7.62106 12.8715 7.57708L11.0003 6.00697L9.12903 7.57683C9.07683 7.62106 8.97346 7.58403 8.89811 7.49454L8.17837 6.63723C8.10354 6.54749 8.08503 6.43897 8.13723 6.39526L9.80017 4.99974L8.13723 3.60449C8.08503 3.56026 8.10303 3.452 8.17837 3.36251L8.8976 2.5052C8.97294 2.4152 9.07631 2.37869 9.12903 2.42266L11.0003 3.99277L12.8715 2.42266C12.9242 2.37869 13.0276 2.41546 13.1029 2.5052L13.8221 3.36251C13.8975 3.452 13.9155 3.56051 13.8633 3.60449L12.2003 5L13.8633 6.39526Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12.5881 13.0353C12.5881 13.6396 12.0964 14.1007 11.5094 14.1007H2.97474C2.37192 14.1007 1.896 13.6237 1.896 13.0353V4.44876C1.896 3.86042 2.38778 3.38339 2.97474 3.38339H7.33725L8.71739 2H2.97474C1.61046 2 0.5 3.09717 0.5 4.44876V13.0353C0.5 14.4028 1.61046 15.5 2.97474 15.5H11.5253C12.8895 15.5 14 14.4028 14 13.0353V7.26325L12.6199 8.64664V13.0353H12.5881Z" fill="white"/>
|
||||
<path d="M15.1805 2.87326L13.1392 0.850975C12.9217 0.633705 12.6205 0.5 12.3193 0.5C12.0014 0.5 11.717 0.616992 11.4995 0.834262L3.83621 8.41708C3.63543 8.61764 3.5183 8.88505 3.50157 9.16917L3.50157 11.2632C3.48484 11.5975 3.60196 11.9318 3.83621 12.1657C4.05373 12.383 4.3549 12.5 4.65608 12.5C4.67281 12.5 4.70628 12.5 4.72301 12.5H6.69739C6.98183 12.4833 7.24954 12.3663 7.45033 12.1657L15.1638 4.52786C15.3813 4.31059 15.4984 4.00975 15.4984 3.70891C15.5152 3.39137 15.398 3.09053 15.1805 2.87326ZM10.3784 4.02646L12.0014 5.64763L8.23673 9.39136L6.61373 7.77019L10.3784 4.02646ZM6.69739 10.929L4.97399 11.0292L5.07438 9.3078L5.55961 8.82312L7.18261 10.4443L6.69739 10.929ZM13.0221 4.59471L11.4158 2.99025L12.3193 2.08774L13.9423 3.70891L13.0221 4.59471Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -6,6 +6,8 @@
|
||||
<file alias="layer.svg">Entity/layer.svg</file>
|
||||
<file alias="prefab.svg">Entity/prefab.svg</file>
|
||||
<file alias="prefab_edit.svg">Entity/prefab_edit.svg</file>
|
||||
<file alias="prefab_edit_open.svg">Entity/prefab_edit_open.svg</file>
|
||||
<file alias="prefab_edit_close.svg">Entity/prefab_edit_close.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/Level">
|
||||
<file alias="level.svg">Level/level.svg</file>
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
|
||||
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/Math/Uuid.h>
|
||||
#include <AzCore/RTTI/BehaviorContext.h>
|
||||
#include <AzCore/Serialization/EditContext.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
|
||||
@@ -38,6 +40,13 @@ namespace AzToolsFramework
|
||||
AZ::Edit::SliceFlags::DontGatherReference);
|
||||
}
|
||||
}
|
||||
|
||||
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->ConstantProperty(
|
||||
"EditorPrefabComponentTypeId", BehaviorConstant(AZ::Uuid(EditorPrefabComponent::EditorPrefabComponentTypeId)))
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorPrefabComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
|
||||
|
||||
@@ -16,7 +16,9 @@ namespace AzToolsFramework
|
||||
class EditorPrefabComponent : public AzToolsFramework::Components::EditorComponentBase
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(EditorPrefabComponent, "{756E5F9C-3E08-4F8D-855C-A5AEEFB6FCDD}", EditorComponentBase);
|
||||
static constexpr const char* const EditorPrefabComponentTypeId = "{756E5F9C-3E08-4F8D-855C-A5AEEFB6FCDD}";
|
||||
|
||||
AZ_COMPONENT(EditorPrefabComponent, EditorPrefabComponentTypeId, EditorComponentBase);
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services);
|
||||
|
||||
@@ -206,6 +206,34 @@ namespace AzToolsFramework::Prefab
|
||||
return instance.has_value() && (&instance->get() == &m_focusedInstance->get());
|
||||
}
|
||||
|
||||
bool PrefabFocusHandler::IsOwningPrefabInFocusHierarchy(AZ::EntityId entityId) const
|
||||
{
|
||||
if (!m_focusedInstance.has_value())
|
||||
{
|
||||
// PrefabFocusHandler has not been initialized yet.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!entityId.IsValid())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
InstanceOptionalReference instance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
|
||||
|
||||
while (instance.has_value())
|
||||
{
|
||||
if (&instance->get() == &m_focusedInstance->get())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
instance = instance->get().GetParentInstance();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const AZ::IO::Path& PrefabFocusHandler::GetPrefabFocusPath([[maybe_unused]] AzFramework::EntityContextId entityContextId) const
|
||||
{
|
||||
return m_instanceFocusPath;
|
||||
|
||||
@@ -53,6 +53,7 @@ namespace AzToolsFramework::Prefab
|
||||
PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) override;
|
||||
AZ::EntityId GetFocusedPrefabContainerEntityId(AzFramework::EntityContextId entityContextId) const override;
|
||||
bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const override;
|
||||
bool IsOwningPrefabInFocusHierarchy(AZ::EntityId entityId) const override;
|
||||
const AZ::IO::Path& GetPrefabFocusPath(AzFramework::EntityContextId entityContextId) const override;
|
||||
const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const override;
|
||||
|
||||
|
||||
+6
-1
@@ -37,10 +37,15 @@ namespace AzToolsFramework::Prefab
|
||||
//! Returns the entity id of the container entity for the instance the prefab system is focusing on.
|
||||
virtual AZ::EntityId GetFocusedPrefabContainerEntityId(AzFramework::EntityContextId entityContextId) const = 0;
|
||||
|
||||
//! Returns whether the entity belongs to the instance that is being focused on.
|
||||
//! @param entityId The entityId of the queried entity.
|
||||
//! @return true if the entity belongs to the focused instance, false otherwise.
|
||||
virtual bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const = 0;
|
||||
|
||||
//! Returns whether the entity belongs to the instance that is being focused on, or one of its descendants.
|
||||
//! @param entityId The entityId of the queried entity.
|
||||
//! @return true if the entity belongs to the focused instance or one of its descendants, false otherwise.
|
||||
virtual bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const = 0;
|
||||
virtual bool IsOwningPrefabInFocusHierarchy(AZ::EntityId entityId) const = 0;
|
||||
|
||||
//! Returns the path from the root instance to the currently focused instance.
|
||||
//! @return A path composed from the names of the container entities for the instance path.
|
||||
|
||||
@@ -974,7 +974,7 @@ namespace AzToolsFramework
|
||||
return DeleteFromInstance(entityIds, true);
|
||||
}
|
||||
|
||||
PrefabOperationResult PrefabPublicHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds)
|
||||
DuplicatePrefabResult PrefabPublicHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds)
|
||||
{
|
||||
if (entityIds.empty())
|
||||
{
|
||||
@@ -1021,6 +1021,7 @@ namespace AzToolsFramework
|
||||
|
||||
ScopedUndoBatch undoBatch("Duplicate Entities");
|
||||
|
||||
EntityIdList duplicatedEntityAndInstanceIds;
|
||||
{
|
||||
AZ_PROFILE_SCOPE(AzToolsFramework, "DuplicateEntitiesInInstance::UndoCaptureAndDuplicateEntities");
|
||||
|
||||
@@ -1033,7 +1034,7 @@ namespace AzToolsFramework
|
||||
|
||||
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
|
||||
{
|
||||
return AZStd::move(retrieveEntitiesAndInstancesOutcome);
|
||||
return AZ::Failure(retrieveEntitiesAndInstancesOutcome.TakeError());
|
||||
}
|
||||
|
||||
// Take a snapshot of the instance DOM before we manipulate it
|
||||
@@ -1044,8 +1045,6 @@ namespace AzToolsFramework
|
||||
PrefabDom instanceDomAfter;
|
||||
instanceDomAfter.CopyFrom(instanceDomBefore, instanceDomAfter.GetAllocator());
|
||||
|
||||
EntityIdList duplicatedEntityAndInstanceIds;
|
||||
|
||||
// Duplicate any nested entities and instances as requested
|
||||
AZStd::unordered_map<InstanceAlias, Instance*> newInstanceAliasToOldInstanceMap;
|
||||
AZStd::unordered_map<EntityAlias, EntityAlias> duplicateEntityAliasMap;
|
||||
@@ -1114,7 +1113,7 @@ namespace AzToolsFramework
|
||||
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, duplicatedEntityAndInstanceIds);
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
return AZ::Success(AZStd::move(duplicatedEntityAndInstanceIds));
|
||||
}
|
||||
|
||||
PrefabOperationResult PrefabPublicHandler::DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants)
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace AzToolsFramework
|
||||
|
||||
PrefabOperationResult DeleteEntitiesInInstance(const EntityIdList& entityIds) override;
|
||||
PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override;
|
||||
PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override;
|
||||
DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override;
|
||||
|
||||
PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) override;
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace AzToolsFramework
|
||||
{
|
||||
typedef AZ::Outcome<AZ::EntityId, AZStd::string> CreatePrefabResult;
|
||||
typedef AZ::Outcome<AZ::EntityId, AZStd::string> InstantiatePrefabResult;
|
||||
typedef AZ::Outcome<EntityIdList, AZStd::string> DuplicatePrefabResult;
|
||||
typedef AZ::Outcome<void, AZStd::string> PrefabOperationResult;
|
||||
typedef AZ::Outcome<bool, AZStd::string> PrefabRequestResult;
|
||||
typedef AZ::Outcome<AZ::EntityId, AZStd::string> PrefabEntityResult;
|
||||
@@ -160,14 +161,15 @@ namespace AzToolsFramework
|
||||
/**
|
||||
* Duplicates all entities in the owning instance. Bails if the entities don't all belong to the same instance.
|
||||
* @param entities The entities to duplicate.
|
||||
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
|
||||
* @return An outcome object with a list of ids of target entities' duplicates if duplication succeeded;
|
||||
* on failure, it comes with an error message detailing the cause of the error.
|
||||
*/
|
||||
virtual PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0;
|
||||
virtual DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0;
|
||||
|
||||
/**
|
||||
* If the entity id is a container entity id, detaches the prefab instance corresponding to it. This includes converting
|
||||
* the container entity into a regular entity and putting it under the parent prefab, removing the link between this
|
||||
* instance and the parent, removing links between this instance and it's nested instances, adding entities directly
|
||||
* instance and the parent, removing links between this instance and its nested instances, and adding entities directly
|
||||
* owned by this instance under the parent instance.
|
||||
* Bails if the entity is not a container entity or belongs to the level prefab instance.
|
||||
* @param containerEntityId The container entity id of the instance to detach.
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace AzToolsFramework
|
||||
{
|
||||
using CreatePrefabResult = AZ::Outcome<AZ::EntityId, AZStd::string>;
|
||||
using InstantiatePrefabResult = AZ::Outcome<AZ::EntityId, AZStd::string>;
|
||||
using DuplicatePrefabResult = AZ::Outcome<EntityIdList, AZStd::string>;
|
||||
using PrefabOperationResult = AZ::Outcome<void, AZStd::string>;
|
||||
|
||||
/**
|
||||
@@ -69,6 +70,29 @@ namespace AzToolsFramework
|
||||
* Return an outcome object; on failure, it comes with an error message detailing the cause of the error.
|
||||
*/
|
||||
virtual PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) = 0;
|
||||
|
||||
/**
|
||||
* If the entity id is a container entity id, detaches the prefab instance corresponding to it. This includes converting
|
||||
* the container entity into a regular entity and putting it under the parent prefab, removing the link between this
|
||||
* instance and the parent, removing links between this instance and its nested instances, and adding entities directly
|
||||
* owned by this instance under the parent instance.
|
||||
* Bails if the entity is not a container entity or belongs to the level prefab instance.
|
||||
* Return an outcome object; on failure, it comes with an error message detailing the cause of the error.
|
||||
*/
|
||||
virtual PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) = 0;
|
||||
|
||||
/**
|
||||
* Duplicates all entities in the owning instance. Bails if the entities don't all belong to the same instance.
|
||||
* Return an outcome object with a list of ids of given entities' duplicates if duplication succeeded;
|
||||
* on failure, it comes with an error message detailing the cause of the error.
|
||||
*/
|
||||
virtual DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0;
|
||||
|
||||
/**
|
||||
* Get the file path to the prefab file for the prefab instance owning the entity provided.
|
||||
* Returns the path to the prefab, or an empty path if the entity is owned by the level.
|
||||
*/
|
||||
virtual AZStd::string GetOwningInstancePrefabPath(AZ::EntityId entityId) const = 0;
|
||||
};
|
||||
|
||||
using PrefabPublicRequestBus = AZ::EBus<PrefabPublicRequests>;
|
||||
|
||||
+17
@@ -28,6 +28,9 @@ namespace AzToolsFramework
|
||||
->Event("CreatePrefabInMemory", &PrefabPublicRequests::CreatePrefabInMemory)
|
||||
->Event("InstantiatePrefab", &PrefabPublicRequests::InstantiatePrefab)
|
||||
->Event("DeleteEntitiesAndAllDescendantsInInstance", &PrefabPublicRequests::DeleteEntitiesAndAllDescendantsInInstance)
|
||||
->Event("DetachPrefab", &PrefabPublicRequests::DetachPrefab)
|
||||
->Event("DuplicateEntitiesInInstance", &PrefabPublicRequests::DuplicateEntitiesInInstance)
|
||||
->Event("GetOwningInstancePrefabPath", &PrefabPublicRequests::GetOwningInstancePrefabPath)
|
||||
;
|
||||
}
|
||||
}
|
||||
@@ -62,5 +65,19 @@ namespace AzToolsFramework
|
||||
return m_prefabPublicInterface->DeleteEntitiesAndAllDescendantsInInstance(entityIds);
|
||||
}
|
||||
|
||||
PrefabOperationResult PrefabPublicRequestHandler::DetachPrefab(const AZ::EntityId& containerEntityId)
|
||||
{
|
||||
return m_prefabPublicInterface->DetachPrefab(containerEntityId);
|
||||
}
|
||||
|
||||
DuplicatePrefabResult PrefabPublicRequestHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds)
|
||||
{
|
||||
return m_prefabPublicInterface->DuplicateEntitiesInInstance(entityIds);
|
||||
}
|
||||
|
||||
AZStd::string PrefabPublicRequestHandler::GetOwningInstancePrefabPath(AZ::EntityId entityId) const
|
||||
{
|
||||
return m_prefabPublicInterface->GetOwningInstancePrefabPath(entityId).Native();
|
||||
}
|
||||
} // namespace Prefab
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
@@ -34,6 +34,9 @@ namespace AzToolsFramework
|
||||
CreatePrefabResult CreatePrefabInMemory(const EntityIdList& entityIds, AZStd::string_view filePath) override;
|
||||
InstantiatePrefabResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) override;
|
||||
PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override;
|
||||
PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) override;
|
||||
DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override;
|
||||
AZStd::string GetOwningInstancePrefabPath(AZ::EntityId entityId) const override;
|
||||
|
||||
private:
|
||||
PrefabPublicInterface* m_prefabPublicInterface = nullptr;
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace AzToolsFramework
|
||||
}
|
||||
|
||||
//PrefabInstanceUndo
|
||||
PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName, const bool useImmediatePropagation)
|
||||
PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName, bool useImmediatePropagation)
|
||||
: PrefabUndoBase(undoOperationName)
|
||||
{
|
||||
m_useImmediatePropagation = useImmediatePropagation;
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace AzToolsFramework
|
||||
: public PrefabUndoBase
|
||||
{
|
||||
public:
|
||||
explicit PrefabUndoInstance(const AZStd::string& undoOperationName, const bool useImmediatePropagation = true);
|
||||
explicit PrefabUndoInstance(const AZStd::string& undoOperationName, bool useImmediatePropagation = true);
|
||||
|
||||
void Capture(
|
||||
const PrefabDom& initialState,
|
||||
|
||||
@@ -2648,16 +2648,15 @@ namespace AzToolsFramework
|
||||
}
|
||||
else
|
||||
{
|
||||
QString cleanSaveAs(QDir::cleanPath(slicePath));
|
||||
AZ::IO::FixedMaxPath lexicallyNormalPath = AZ::IO::PathView(slicePath.toUtf8().constData()).LexicallyNormal();
|
||||
|
||||
bool isPathSafeForAssets = false;
|
||||
for (AZStd::string assetSafeFolder : assetSafeFolders)
|
||||
for (const AZStd::string& assetSafeFolder : assetSafeFolders)
|
||||
{
|
||||
QString cleanAssetSafeFolder(QDir::cleanPath(assetSafeFolder.c_str()));
|
||||
// Compare using clean paths so slash direction does not matter.
|
||||
// Note that this comparison is case sensitive because some file systems
|
||||
// Open 3D Engine supports are case sensitive.
|
||||
if (cleanSaveAs.startsWith(cleanAssetSafeFolder))
|
||||
AZ::IO::PathView assetSafeFolderView(assetSafeFolder);
|
||||
// Check if the slice path is relative to the safe asset directory.
|
||||
// The Path classes are being used to make this check case insensitive.
|
||||
if (lexicallyNormalPath.IsRelativeTo(assetSafeFolderView))
|
||||
{
|
||||
isPathSafeForAssets = true;
|
||||
break;
|
||||
|
||||
+5
-15
@@ -80,14 +80,7 @@ namespace AzToolsFramework
|
||||
|
||||
// set up signals before we start thread.
|
||||
m_shutdownThreadSignal = false;
|
||||
|
||||
// Check to see if the 'p4' command is available at the command line
|
||||
int p4VersionExitCode = QProcess::execute("p4", QStringList{ "-V" });
|
||||
m_p4ApplicationDetected = (p4VersionExitCode == 0);
|
||||
if (m_p4ApplicationDetected)
|
||||
{
|
||||
m_WorkerThread = AZStd::thread(AZStd::bind(&PerforceComponent::ThreadWorker, this));
|
||||
}
|
||||
m_WorkerThread = AZStd::thread(AZStd::bind(&PerforceComponent::ThreadWorker, this));
|
||||
|
||||
SourceControlConnectionRequestBus::Handler::BusConnect();
|
||||
SourceControlCommandBus::Handler::BusConnect();
|
||||
@@ -98,13 +91,10 @@ namespace AzToolsFramework
|
||||
SourceControlCommandBus::Handler::BusDisconnect();
|
||||
SourceControlConnectionRequestBus::Handler::BusDisconnect();
|
||||
|
||||
if (m_p4ApplicationDetected)
|
||||
{
|
||||
m_shutdownThreadSignal = true; // tell the thread to die.
|
||||
m_WorkerSemaphore.release(1); // wake up the thread so that it sees the signal
|
||||
m_WorkerThread.join(); // wait for the thread to finish.
|
||||
m_WorkerThread = AZStd::thread();
|
||||
}
|
||||
m_shutdownThreadSignal = true; // tell the thread to die.
|
||||
m_WorkerSemaphore.release(1); // wake up the thread so that it sees the signal
|
||||
m_WorkerThread.join(); // wait for the thread to finish.
|
||||
m_WorkerThread = AZStd::thread();
|
||||
|
||||
SetConnection(nullptr);
|
||||
}
|
||||
|
||||
@@ -260,7 +260,5 @@ namespace AzToolsFramework
|
||||
AZStd::atomic_bool m_validConnection;
|
||||
|
||||
SourceControlState m_connectionState;
|
||||
|
||||
bool m_p4ApplicationDetected { false };
|
||||
};
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+18
-1
@@ -101,8 +101,25 @@ namespace AzToolsFramework
|
||||
{
|
||||
}
|
||||
|
||||
void EditorEntityUiHandlerBase::OnDoubleClick([[maybe_unused]] AZ::EntityId entityId) const
|
||||
bool EditorEntityUiHandlerBase::OnOutlinerItemClick(
|
||||
[[maybe_unused]] const QPoint& position,
|
||||
[[maybe_unused]] const QStyleOptionViewItem& option,
|
||||
[[maybe_unused]] const QModelIndex& index) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void EditorEntityUiHandlerBase::OnOutlinerItemExpand([[maybe_unused]] const QModelIndex& index) const
|
||||
{
|
||||
}
|
||||
|
||||
void EditorEntityUiHandlerBase::OnOutlinerItemCollapse([[maybe_unused]] const QModelIndex& index) const
|
||||
{
|
||||
}
|
||||
|
||||
bool EditorEntityUiHandlerBase::OnEntityDoubleClick([[maybe_unused]] AZ::EntityId entityId) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
+11
-2
@@ -61,8 +61,17 @@ namespace AzToolsFramework
|
||||
virtual void PaintDescendantForeground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index,
|
||||
const QModelIndex& descendantIndex) const;
|
||||
|
||||
//! Triggered when the entity is double clicked in the Outliner.
|
||||
virtual void OnDoubleClick(AZ::EntityId entityId) const;
|
||||
//! Triggered when the entity is clicked in the Outliner.
|
||||
//! @return True if the click has been handled and should not be propagated, false otherwise.
|
||||
virtual bool OnOutlinerItemClick(const QPoint& position, const QStyleOptionViewItem& option, const QModelIndex& index) const;
|
||||
//! Triggered when an entity's children are expanded in the Outliner.
|
||||
virtual void OnOutlinerItemExpand(const QModelIndex& index) const;
|
||||
//! Triggered when an entity's children are collapsed in the Outliner.
|
||||
virtual void OnOutlinerItemCollapse(const QModelIndex& index) const;
|
||||
|
||||
//! Triggered when the entity is double clicked in the Outliner or in the Viewport.
|
||||
//! @return True if the double click has been handled and should not be propagated, false otherwise.
|
||||
virtual bool OnEntityDoubleClick(AZ::EntityId entityId) const;
|
||||
|
||||
private:
|
||||
EditorEntityUiHandlerId m_handlerId = 0;
|
||||
|
||||
+10
@@ -177,4 +177,14 @@ namespace AzToolsFramework
|
||||
DisplayQueuedNotification();
|
||||
}
|
||||
}
|
||||
|
||||
void ToastNotificationsView::SetOffset(const QPoint& offset)
|
||||
{
|
||||
m_offset = offset;
|
||||
}
|
||||
|
||||
void ToastNotificationsView::SetAnchorPoint(const QPointF& anchorPoint)
|
||||
{
|
||||
m_anchorPoint = anchorPoint;
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -50,6 +50,9 @@ namespace AzToolsFramework
|
||||
void OnShow();
|
||||
void UpdateToastPosition();
|
||||
|
||||
void SetOffset(const QPoint& offset);
|
||||
void SetAnchorPoint(const QPointF& anchorPoint);
|
||||
|
||||
private:
|
||||
ToastId CreateToastNotification(const AzQtComponents::ToastConfiguration& toastConfiguration);
|
||||
void DisplayQueuedNotification();
|
||||
|
||||
+27
-1
@@ -11,10 +11,12 @@
|
||||
#include <QApplication>
|
||||
#include <QBitmap>
|
||||
#include <QCheckBox>
|
||||
#include <QEvent>
|
||||
#include <QFontMetrics>
|
||||
#include <QGuiApplication>
|
||||
#include <QMessageBox>
|
||||
#include <QMimeData>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QStyle>
|
||||
@@ -2287,7 +2289,14 @@ namespace AzToolsFramework
|
||||
// Now we setup a Text Document so it can draw the rich text
|
||||
QTextDocument textDoc;
|
||||
textDoc.setDefaultFont(optionV4.font);
|
||||
textDoc.setDefaultStyleSheet("body {color: white}");
|
||||
if (option.state & QStyle::State_Enabled)
|
||||
{
|
||||
textDoc.setDefaultStyleSheet("body {color: white}");
|
||||
}
|
||||
else
|
||||
{
|
||||
textDoc.setDefaultStyleSheet("body {color: #7C7C7C}");
|
||||
}
|
||||
textDoc.setHtml("<body>" + entityNameRichText + "</body>");
|
||||
painter->translate(textRect.topLeft());
|
||||
textDoc.setTextWidth(textRect.width());
|
||||
@@ -2326,6 +2335,23 @@ namespace AzToolsFramework
|
||||
return true;
|
||||
}
|
||||
|
||||
if (event->type() == QEvent::MouseButtonPress)
|
||||
{
|
||||
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
|
||||
|
||||
if (auto editorEntityUiInterface = AZ::Interface<EditorEntityUiInterface>::Get(); editorEntityUiInterface != nullptr)
|
||||
{
|
||||
auto mouseEvent = static_cast<QMouseEvent*>(event);
|
||||
|
||||
auto entityUiHandler = editorEntityUiInterface->GetHandler(entityId);
|
||||
|
||||
if (entityUiHandler && entityUiHandler->OnOutlinerItemClick(mouseEvent->pos(), option, index))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return QStyledItemDelegate::editorEvent(event, model, option, index);
|
||||
}
|
||||
|
||||
|
||||
+7
@@ -73,6 +73,8 @@ namespace AzToolsFramework
|
||||
void EntityOutlinerTreeView::leaveEvent([[maybe_unused]] QEvent* event)
|
||||
{
|
||||
m_mousePosition = QPoint();
|
||||
m_currentHoveredIndex = QModelIndex();
|
||||
update();
|
||||
}
|
||||
|
||||
void EntityOutlinerTreeView::mousePressEvent(QMouseEvent* event)
|
||||
@@ -129,6 +131,11 @@ namespace AzToolsFramework
|
||||
}
|
||||
|
||||
m_mousePosition = event->pos();
|
||||
if (QModelIndex hoveredIndex = indexAt(m_mousePosition); m_currentHoveredIndex != indexAt(m_mousePosition))
|
||||
{
|
||||
m_currentHoveredIndex = hoveredIndex;
|
||||
update();
|
||||
}
|
||||
|
||||
//process mouse movement as normal, potentially triggering drag and drop
|
||||
QTreeView::mouseMoveEvent(event);
|
||||
|
||||
+2
@@ -90,6 +90,8 @@ namespace AzToolsFramework
|
||||
const QColor m_selectedColor = QColor(255, 255, 255, 45);
|
||||
const QColor m_hoverColor = QColor(255, 255, 255, 30);
|
||||
|
||||
QModelIndex m_currentHoveredIndex;
|
||||
|
||||
EditorEntityUiInterface* m_editorEntityFrameworkInterface;
|
||||
};
|
||||
|
||||
|
||||
+17
-4
@@ -902,6 +902,7 @@ namespace AzToolsFramework
|
||||
|
||||
EditorPickModeRequestBus::Broadcast(
|
||||
&EditorPickModeRequests::StopEntityPickMode);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (index.column())
|
||||
@@ -918,18 +919,30 @@ namespace AzToolsFramework
|
||||
{
|
||||
if (AZ::EntityId entityId = GetEntityIdFromIndex(index); auto entityUiHandler = m_editorEntityUiInterface->GetHandler(entityId))
|
||||
{
|
||||
entityUiHandler->OnDoubleClick(entityId);
|
||||
entityUiHandler->OnEntityDoubleClick(entityId);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityOutlinerWidget::OnTreeItemExpanded(const QModelIndex& index)
|
||||
{
|
||||
m_listModel->OnEntityExpanded(GetEntityIdFromIndex(index));
|
||||
AZ::EntityId entityId = GetEntityIdFromIndex(index);
|
||||
if (auto entityUiHandler = m_editorEntityUiInterface->GetHandler(entityId))
|
||||
{
|
||||
entityUiHandler->OnOutlinerItemExpand(index);
|
||||
}
|
||||
|
||||
m_listModel->OnEntityExpanded(entityId);
|
||||
}
|
||||
|
||||
void EntityOutlinerWidget::OnTreeItemCollapsed(const QModelIndex& index)
|
||||
{
|
||||
m_listModel->OnEntityCollapsed(GetEntityIdFromIndex(index));
|
||||
AZ::EntityId entityId = GetEntityIdFromIndex(index);
|
||||
if (auto entityUiHandler = m_editorEntityUiInterface->GetHandler(entityId))
|
||||
{
|
||||
entityUiHandler->OnOutlinerItemCollapse(index);
|
||||
}
|
||||
|
||||
m_listModel->OnEntityCollapsed(entityId);
|
||||
}
|
||||
|
||||
void EntityOutlinerWidget::OnExpandEntity(const AZ::EntityId& entityId, bool expand)
|
||||
@@ -1163,7 +1176,7 @@ namespace AzToolsFramework
|
||||
{
|
||||
QTimer::singleShot(1, this, [this]() {
|
||||
m_gui->m_objectTree->setUpdatesEnabled(true);
|
||||
m_gui->m_objectTree->expandToDepth(0);
|
||||
m_gui->m_objectTree->expand(m_proxyModel->index(0,0));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+6
-7
@@ -805,16 +805,15 @@ namespace AzToolsFramework
|
||||
}
|
||||
else
|
||||
{
|
||||
QString cleanSaveAs(QDir::cleanPath(prefabPath));
|
||||
AZ::IO::FixedMaxPath lexicallyNormalPath = AZ::IO::PathView(prefabPath.toUtf8().constData()).LexicallyNormal();
|
||||
|
||||
bool isPathSafeForAssets = false;
|
||||
for (AZStd::string assetSafeFolder : assetSafeFolders)
|
||||
for (const AZStd::string& assetSafeFolder : assetSafeFolders)
|
||||
{
|
||||
QString cleanAssetSafeFolder(QDir::cleanPath(assetSafeFolder.c_str()));
|
||||
// Compare using clean paths so slash direction does not matter.
|
||||
// Note that this comparison is case sensitive because some file systems
|
||||
// Open 3D Engine supports are case sensitive.
|
||||
if (cleanSaveAs.startsWith(cleanAssetSafeFolder))
|
||||
AZ::IO::PathView assetSafeFolderView(assetSafeFolder);
|
||||
// Check if the prefabPath is relative to the safe asset directory.
|
||||
// The Path classes are being used to make this check case insensitive.
|
||||
if (lexicallyNormalPath.IsRelativeTo(assetSafeFolderView))
|
||||
{
|
||||
isPathSafeForAssets = true;
|
||||
break;
|
||||
|
||||
@@ -21,10 +21,16 @@
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
const QColor PrefabUiHandler::m_backgroundColor = QColor("#444444");
|
||||
const QColor PrefabUiHandler::m_backgroundHoverColor = QColor("#5A5A5A");
|
||||
const QColor PrefabUiHandler::m_backgroundSelectedColor = QColor("#656565");
|
||||
const QColor PrefabUiHandler::m_prefabCapsuleColor = QColor("#1E252F");
|
||||
const QColor PrefabUiHandler::m_prefabCapsuleDisabledColor = QColor("#35383C");
|
||||
const QColor PrefabUiHandler::m_prefabCapsuleEditColor = QColor("#4A90E2");
|
||||
const QString PrefabUiHandler::m_prefabIconPath = QString(":/Entity/prefab.svg");
|
||||
const QString PrefabUiHandler::m_prefabEditIconPath = QString(":/Entity/prefab_edit.svg");
|
||||
const QString PrefabUiHandler::m_prefabEditOpenIconPath = QString(":/Entity/prefab_edit_open.svg");
|
||||
const QString PrefabUiHandler::m_prefabEditCloseIconPath = QString(":/Entity/prefab_edit_close.svg");
|
||||
|
||||
PrefabUiHandler::PrefabUiHandler()
|
||||
{
|
||||
@@ -75,7 +81,7 @@ namespace AzToolsFramework
|
||||
|
||||
if (!path.empty())
|
||||
{
|
||||
tooltip = QObject::tr("%1").arg(path.Native().data());
|
||||
tooltip = QObject::tr("Double click to edit.\n%1").arg(path.Native().data());
|
||||
}
|
||||
|
||||
return tooltip;
|
||||
@@ -102,13 +108,20 @@ namespace AzToolsFramework
|
||||
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
|
||||
const bool isFirstColumn = index.column() == EntityOutlinerListModel::ColumnName;
|
||||
const bool isLastColumn = index.column() == EntityOutlinerListModel::ColumnLockToggle;
|
||||
const bool hasVisibleChildren = index.data(EntityOutlinerListModel::ExpandedRole).value<bool>() && index.model()->hasChildren(index);
|
||||
QModelIndex firstColumnIndex = index.siblingAtColumn(EntityOutlinerListModel::ColumnName);
|
||||
const bool hasVisibleChildren =
|
||||
firstColumnIndex.data(EntityOutlinerListModel::ExpandedRole).value<bool>() &&
|
||||
firstColumnIndex.model()->hasChildren(firstColumnIndex);
|
||||
|
||||
QColor backgroundColor = m_prefabCapsuleColor;
|
||||
if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
|
||||
{
|
||||
backgroundColor = m_prefabCapsuleEditColor;
|
||||
}
|
||||
else if (!(option.state & QStyle::State_Enabled))
|
||||
{
|
||||
backgroundColor = m_prefabCapsuleDisabledColor;
|
||||
}
|
||||
|
||||
QPainterPath backgroundPath;
|
||||
backgroundPath.setFillRule(Qt::WindingFill);
|
||||
@@ -184,7 +197,8 @@ namespace AzToolsFramework
|
||||
const bool isFirstColumn = descendantIndex.column() == EntityOutlinerListModel::ColumnName;
|
||||
const bool isLastColumn = descendantIndex.column() == EntityOutlinerListModel::ColumnLockToggle;
|
||||
|
||||
QColor borderColor = m_prefabCapsuleColor;
|
||||
// There is no legal way of opening prefabs in their default state, so default to disabled.
|
||||
QColor borderColor = m_prefabCapsuleDisabledColor;
|
||||
if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
|
||||
{
|
||||
borderColor = m_prefabCapsuleEditColor;
|
||||
@@ -273,6 +287,71 @@ namespace AzToolsFramework
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
void PrefabUiHandler::PaintItemForeground(QPainter* painter, const QStyleOptionViewItem& option, [[maybe_unused]] const QModelIndex& index) const
|
||||
{
|
||||
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
|
||||
const QPoint offset = QPoint(-18, 3);
|
||||
QModelIndex firstColumnIndex = index.siblingAtColumn(EntityOutlinerListModel::ColumnName);
|
||||
const int iconSize = 16;
|
||||
const bool isHovered = (option.state & QStyle::State_MouseOver);
|
||||
const bool isSelected = index.data(EntityOutlinerListModel::SelectedRole).template value<bool>();
|
||||
const bool isFirstColumn = index.column() == EntityOutlinerListModel::ColumnName;
|
||||
const bool isExpanded =
|
||||
firstColumnIndex.data(EntityOutlinerListModel::ExpandedRole).value<bool>() &&
|
||||
firstColumnIndex.model()->hasChildren(firstColumnIndex);
|
||||
|
||||
if (!isFirstColumn || !(option.state & QStyle::State_Enabled))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
painter->save();
|
||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
|
||||
{
|
||||
// Only show the close icon if the prefab is expanded.
|
||||
// This allows the prefab container to be opened if it was collapsed during propagation.
|
||||
if (!isExpanded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the same color as the background.
|
||||
QColor backgroundColor = m_backgroundColor;
|
||||
if (isSelected)
|
||||
{
|
||||
backgroundColor = m_backgroundSelectedColor;
|
||||
}
|
||||
else if (isHovered)
|
||||
{
|
||||
backgroundColor = m_backgroundHoverColor;
|
||||
}
|
||||
|
||||
// Paint a rect to cover up the expander.
|
||||
QRect rect = QRect(0, 0, 16, 16);
|
||||
rect.translate(option.rect.topLeft() + offset);
|
||||
painter->fillRect(rect, backgroundColor);
|
||||
|
||||
// Paint the icon.
|
||||
QIcon closeIcon = QIcon(m_prefabEditCloseIconPath);
|
||||
painter->drawPixmap(option.rect.topLeft() + offset, closeIcon.pixmap(iconSize));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only show the edit icon on hover.
|
||||
if (!isHovered)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QIcon openIcon = QIcon(m_prefabEditOpenIconPath);
|
||||
painter->drawPixmap(option.rect.topLeft() + offset, openIcon.pixmap(iconSize));
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
|
||||
bool PrefabUiHandler::IsLastVisibleChild(const QModelIndex& parent, const QModelIndex& child)
|
||||
{
|
||||
QModelIndex lastVisibleItemIndex = GetLastVisibleChild(parent);
|
||||
@@ -314,9 +393,53 @@ namespace AzToolsFramework
|
||||
return Internal_GetLastVisibleChild(model, lastChild);
|
||||
}
|
||||
|
||||
void PrefabUiHandler::OnDoubleClick(AZ::EntityId entityId) const
|
||||
bool PrefabUiHandler::OnOutlinerItemClick(const QPoint& position, const QStyleOptionViewItem& option, const QModelIndex& index) const
|
||||
{
|
||||
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
|
||||
const QPoint offset = QPoint(-18, 3);
|
||||
|
||||
if (m_prefabFocusPublicInterface->IsOwningPrefabInFocusHierarchy(entityId))
|
||||
{
|
||||
QRect iconRect = QRect(0, 0, 16, 16);
|
||||
iconRect.translate(option.rect.topLeft() + offset);
|
||||
|
||||
if (iconRect.contains(position))
|
||||
{
|
||||
if (!m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
|
||||
{
|
||||
// Focus on this prefab.
|
||||
m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId);
|
||||
}
|
||||
|
||||
// Don't propagate event.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void PrefabUiHandler::OnOutlinerItemCollapse(const QModelIndex& index) const
|
||||
{
|
||||
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
bool PrefabUiHandler::OnEntityDoubleClick(AZ::EntityId entityId) const
|
||||
{
|
||||
// Focus on this prefab
|
||||
m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId);
|
||||
|
||||
// Don't propagate event.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,10 @@ namespace AzToolsFramework
|
||||
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 OnDoubleClick(AZ::EntityId entityId) const override;
|
||||
void PaintItemForeground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) 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;
|
||||
|
||||
private:
|
||||
Prefab::PrefabFocusPublicInterface* m_prefabFocusPublicInterface = nullptr;
|
||||
@@ -48,9 +51,15 @@ namespace AzToolsFramework
|
||||
|
||||
static constexpr int m_prefabCapsuleRadius = 6;
|
||||
static constexpr int m_prefabBorderThickness = 2;
|
||||
static const QColor m_backgroundColor;
|
||||
static const QColor m_backgroundHoverColor;
|
||||
static const QColor m_backgroundSelectedColor;
|
||||
static const QColor m_prefabCapsuleColor;
|
||||
static const QColor m_prefabCapsuleDisabledColor;
|
||||
static const QColor m_prefabCapsuleEditColor;
|
||||
static const QString m_prefabIconPath;
|
||||
static const QString m_prefabEditIconPath;
|
||||
static const QString m_prefabEditOpenIconPath;
|
||||
static const QString m_prefabEditCloseIconPath;
|
||||
};
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace AzToolsFramework::ViewportUi::Internal
|
||||
{
|
||||
const static int HighlightBorderSize = 5;
|
||||
const static int TopHighlightBorderSize = 25;
|
||||
const static char* HighlightBorderColor = "#44B2F8";
|
||||
const static char* HighlightBorderColor = "#4A90E2";
|
||||
|
||||
static void UnparentWidgets(ViewportUiElementIdInfoLookup& viewportUiElementIdInfoLookup)
|
||||
{
|
||||
|
||||
@@ -43,7 +43,7 @@ ly_add_target(
|
||||
3rdParty::pybind11
|
||||
AZ::AzCore
|
||||
AZ::AzFramework
|
||||
AZ::AzQtComponents
|
||||
AZ::AzToolsFramework
|
||||
)
|
||||
|
||||
ly_add_target(
|
||||
|
||||
@@ -40,5 +40,6 @@
|
||||
<file>Delete.svg</file>
|
||||
<file>Download.svg</file>
|
||||
<file>in_progress.gif</file>
|
||||
<file>gem.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -61,6 +61,24 @@ QTabBar::tab:focus {
|
||||
color: #4082eb;
|
||||
}
|
||||
|
||||
#ToastNotification {
|
||||
background-color: black;
|
||||
border-radius: 20px;
|
||||
border:1px solid #dddddd;
|
||||
qproperty-minimumSize: 100px 50px;
|
||||
}
|
||||
|
||||
#ToastNotification #icon_frame {
|
||||
border-radius: 4px;
|
||||
qproperty-minimumSize: 44px 20px;
|
||||
}
|
||||
|
||||
#ToastNotification #iconLabel {
|
||||
qproperty-minimumSize: 30px 20px;
|
||||
qproperty-maximumSize: 30px 20px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
/************** General (Forms) **************/
|
||||
|
||||
#formLineEditWidget,
|
||||
@@ -218,6 +236,10 @@ QTabBar::tab:focus {
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
#verticalSeparatingLine {
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
/************** Project Settings **************/
|
||||
#projectSettings {
|
||||
margin-top:42px;
|
||||
@@ -481,6 +503,26 @@ QProgressBar::chunk {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#gemCatalogMenuButton {
|
||||
qproperty-flat: true;
|
||||
max-width:36px;
|
||||
min-width:36px;
|
||||
max-height:24px;
|
||||
min-height:24px;
|
||||
}
|
||||
|
||||
#GemCatalogCartOverlayGemDownloadHeader {
|
||||
margin:0;
|
||||
padding: 0px;
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
#GemCatalogCartOverlayGemDownloadBG {
|
||||
margin:0;
|
||||
padding: 0px;
|
||||
background-color: #444444;
|
||||
}
|
||||
|
||||
#GemCatalogHeaderLabel {
|
||||
font-size: 12px;
|
||||
color: #FFFFFF;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="22" height="18" viewBox="0 0 22 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M21.3771 6.26808L18.0075 0.389195C17.9407 0.271396 17.844 0.173353 17.7271 0.105004C17.6101 0.0366557 17.4772 0.000430184 17.3418 0H4.15017C4.01474 0.000430184 3.88184 0.0366557 3.76492 0.105004C3.64801 0.173353 3.55125 0.271396 3.48444 0.389195L0.10459 6.26808C0.0213476 6.41083 -0.0136462 6.57661 0.00480427 6.74082C0.0232547 6.90502 0.0941655 7.05891 0.20701 7.17962L10.1724 17.7596C10.2442 17.8355 10.3308 17.896 10.4267 17.9373C10.5227 17.9787 10.6261 18 10.7306 18C10.8351 18 10.9385 17.9787 11.0345 17.9373C11.1305 17.896 11.217 17.8355 11.2888 17.7596L21.2542 7.17962C21.3703 7.06129 21.4451 6.90857 21.4672 6.74428C21.4894 6.57999 21.4578 6.41294 21.3771 6.26808ZM12.5998 7.17962L10.7562 13.7345L8.88195 7.17962H12.5998ZM8.42107 5.64332L7.25348 1.54654H14.218L13.0504 5.64332H8.42107ZM9.44526 14.687L2.31685 7.17962H7.24324L9.44526 14.687ZM14.2385 7.17962H19.1546L11.9853 14.7382L14.2385 7.17962ZM19.2878 5.64332H14.6789L15.8465 1.54654H16.9014L19.2878 5.64332ZM4.64178 1.54654H5.66598L6.83356 5.64332H2.23492L4.64178 1.54654Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -49,6 +49,8 @@ namespace O3DE::ProjectManager
|
||||
m_stack->addWidget(m_gemCatalogScreen);
|
||||
vLayout->addWidget(m_stack);
|
||||
|
||||
connect(m_gemCatalogScreen, &ScreenWidget::ChangeScreenRequest, this, &CreateProjectCtrl::OnChangeScreenRequest);
|
||||
|
||||
// When there are multiple project templates present, we re-gather the gems when changing the selected the project template.
|
||||
connect(m_newProjectSettingsScreen, &NewProjectSettingsScreen::OnTemplateSelectionChanged, this, [=](int oldIndex, [[maybe_unused]] int newIndex)
|
||||
{
|
||||
@@ -133,7 +135,7 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
else
|
||||
{
|
||||
emit GotoPreviousScreenRequest();
|
||||
emit GoToPreviousScreenRequest();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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 <DownloadController.h>
|
||||
#include <DownloadWorker.h>
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
DownloadController::DownloadController(QWidget* parent)
|
||||
: QObject()
|
||||
, m_lastProgress(0)
|
||||
, m_parent(parent)
|
||||
{
|
||||
m_worker = new DownloadWorker();
|
||||
m_worker->moveToThread(&m_workerThread);
|
||||
|
||||
connect(&m_workerThread, &QThread::started, m_worker, &DownloadWorker::StartDownload);
|
||||
connect(m_worker, &DownloadWorker::Done, this, &DownloadController::HandleResults);
|
||||
connect(m_worker, &DownloadWorker::UpdateProgress, this, &DownloadController::UpdateUIProgress);
|
||||
connect(this, &DownloadController::StartGemDownload, m_worker, &DownloadWorker::StartDownload);
|
||||
}
|
||||
|
||||
DownloadController::~DownloadController()
|
||||
{
|
||||
connect(&m_workerThread, &QThread::finished, m_worker, &DownloadController::deleteLater);
|
||||
m_workerThread.requestInterruption();
|
||||
m_workerThread.quit();
|
||||
m_workerThread.wait();
|
||||
}
|
||||
|
||||
void DownloadController::AddGemDownload(const QString& gemName)
|
||||
{
|
||||
m_gemNames.push_back(gemName);
|
||||
if (m_gemNames.size() == 1)
|
||||
{
|
||||
m_worker->SetGemToDownload(m_gemNames[0], false);
|
||||
m_workerThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadController::UpdateUIProgress(int progress)
|
||||
{
|
||||
m_lastProgress = progress;
|
||||
emit GemDownloadProgress(progress);
|
||||
}
|
||||
|
||||
void DownloadController::HandleResults(const QString& result)
|
||||
{
|
||||
bool succeeded = true;
|
||||
|
||||
if (!result.isEmpty())
|
||||
{
|
||||
QMessageBox::critical(nullptr, tr("Gem download"), result);
|
||||
succeeded = false;
|
||||
}
|
||||
|
||||
m_gemNames.erase(m_gemNames.begin());
|
||||
emit Done(succeeded);
|
||||
|
||||
if (!m_gemNames.empty())
|
||||
{
|
||||
emit StartGemDownload(m_gemNames[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_workerThread.quit();
|
||||
m_workerThread.wait();
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadController::HandleCancel()
|
||||
{
|
||||
m_workerThread.quit();
|
||||
emit Done(false);
|
||||
}
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <QString>
|
||||
#include <QThread>
|
||||
#include <AzCore/std/containers/vector.h>
|
||||
#endif
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QProcess)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
QT_FORWARD_DECLARE_CLASS(DownloadWorker)
|
||||
|
||||
class DownloadController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DownloadController(QWidget* parent = nullptr);
|
||||
~DownloadController();
|
||||
|
||||
void AddGemDownload(const QString& m_gemName);
|
||||
|
||||
bool IsDownloadQueueEmpty()
|
||||
{
|
||||
return m_gemNames.empty();
|
||||
}
|
||||
|
||||
const AZStd::vector<QString>& GetDownloadQueue() const
|
||||
{
|
||||
return m_gemNames;
|
||||
}
|
||||
|
||||
const QString& GetCurrentDownloadingGem() const
|
||||
{
|
||||
if (!m_gemNames.empty())
|
||||
{
|
||||
return m_gemNames[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
static const QString emptyString;
|
||||
return emptyString;
|
||||
}
|
||||
}
|
||||
public slots:
|
||||
void UpdateUIProgress(int progress);
|
||||
void HandleResults(const QString& result);
|
||||
void HandleCancel();
|
||||
|
||||
signals:
|
||||
void StartGemDownload(const QString& gemName);
|
||||
void Done(bool success = true);
|
||||
void GemDownloadProgress(int percentage);
|
||||
|
||||
private:
|
||||
DownloadWorker* m_worker;
|
||||
QThread m_workerThread;
|
||||
QWidget* m_parent;
|
||||
AZStd::vector<QString> m_gemNames;
|
||||
|
||||
int m_lastProgress;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 <DownloadController.h>
|
||||
#include <DownloadWorker.h>
|
||||
#include <PythonBindings.h>
|
||||
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
DownloadWorker::DownloadWorker()
|
||||
: QObject()
|
||||
{
|
||||
}
|
||||
|
||||
void DownloadWorker::StartDownload()
|
||||
{
|
||||
auto gemDownloadProgress = [=](int downloadProgress)
|
||||
{
|
||||
m_downloadProgress = downloadProgress;
|
||||
emit UpdateProgress(downloadProgress);
|
||||
};
|
||||
AZ::Outcome<void, AZStd::string> gemInfoResult = PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress);
|
||||
if (gemInfoResult.IsSuccess())
|
||||
{
|
||||
emit Done("");
|
||||
}
|
||||
else
|
||||
{
|
||||
emit Done(tr("Gem download failed"));
|
||||
}
|
||||
}
|
||||
|
||||
void DownloadWorker::SetGemToDownload(const QString& gemName, bool downloadNow)
|
||||
{
|
||||
m_gemName = gemName;
|
||||
if (downloadNow)
|
||||
{
|
||||
StartDownload();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <AzCore/Outcome/Outcome.h>
|
||||
#endif
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QProcess)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
class DownloadWorker : public QObject
|
||||
{
|
||||
// Download was cancelled
|
||||
inline static const QString DownloadCancelled = QObject::tr("Download Cancelled.");
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DownloadWorker();
|
||||
~DownloadWorker() = default;
|
||||
|
||||
public slots:
|
||||
void StartDownload();
|
||||
void SetGemToDownload(const QString& gemName, bool downloadNow = true);
|
||||
|
||||
signals:
|
||||
void UpdateProgress(int progress);
|
||||
void Done(QString result = "");
|
||||
|
||||
private:
|
||||
|
||||
QString m_gemName;
|
||||
int m_downloadProgress;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
@@ -29,17 +29,17 @@ namespace O3DE::ProjectManager
|
||||
|
||||
topBarFrameWidget->setLayout(topBarHLayout);
|
||||
|
||||
QTabWidget* tabWidget = new QTabWidget();
|
||||
tabWidget->setObjectName("engineTab");
|
||||
tabWidget->tabBar()->setObjectName("engineTabBar");
|
||||
tabWidget->tabBar()->setFocusPolicy(Qt::TabFocus);
|
||||
m_tabWidget = new QTabWidget();
|
||||
m_tabWidget->setObjectName("engineTab");
|
||||
m_tabWidget->tabBar()->setObjectName("engineTabBar");
|
||||
m_tabWidget->tabBar()->setFocusPolicy(Qt::TabFocus);
|
||||
|
||||
m_engineSettingsScreen = new EngineSettingsScreen();
|
||||
m_gemRepoScreen = new GemRepoScreen();
|
||||
|
||||
tabWidget->addTab(m_engineSettingsScreen, tr("General"));
|
||||
tabWidget->addTab(m_gemRepoScreen, tr("Gem Repositories"));
|
||||
topBarHLayout->addWidget(tabWidget);
|
||||
m_tabWidget->addTab(m_engineSettingsScreen, tr("General"));
|
||||
m_tabWidget->addTab(m_gemRepoScreen, tr("Gem Repositories"));
|
||||
topBarHLayout->addWidget(m_tabWidget);
|
||||
|
||||
vLayout->addWidget(topBarFrameWidget);
|
||||
|
||||
@@ -61,4 +61,28 @@ namespace O3DE::ProjectManager
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EngineScreenCtrl::ContainsScreen(ProjectManagerScreen screen)
|
||||
{
|
||||
if (screen == m_engineSettingsScreen->GetScreenEnum() || screen == m_gemRepoScreen->GetScreenEnum())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void EngineScreenCtrl::GoToScreen(ProjectManagerScreen screen)
|
||||
{
|
||||
if (screen == m_engineSettingsScreen->GetScreenEnum())
|
||||
{
|
||||
m_tabWidget->setCurrentWidget(m_engineSettingsScreen);
|
||||
m_engineSettingsScreen->NotifyCurrentScreen();
|
||||
}
|
||||
else if (screen == m_gemRepoScreen->GetScreenEnum())
|
||||
{
|
||||
m_tabWidget->setCurrentWidget(m_gemRepoScreen);
|
||||
m_gemRepoScreen->NotifyCurrentScreen();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include <ScreenWidget.h>
|
||||
#endif
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QTabWidget)
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
QT_FORWARD_DECLARE_CLASS(EngineSettingsScreen)
|
||||
@@ -26,7 +28,10 @@ namespace O3DE::ProjectManager
|
||||
|
||||
QString GetTabText() override;
|
||||
bool IsTab() override;
|
||||
bool ContainsScreen(ProjectManagerScreen screen) override;
|
||||
void GoToScreen(ProjectManagerScreen screen) override;
|
||||
|
||||
QTabWidget* m_tabWidget = nullptr;
|
||||
EngineSettingsScreen* m_engineSettingsScreen = nullptr;
|
||||
GemRepoScreen* m_gemRepoScreen = nullptr;
|
||||
};
|
||||
|
||||
@@ -8,17 +8,21 @@
|
||||
|
||||
#include <GemCatalog/GemCatalogHeaderWidget.h>
|
||||
#include <AzCore/std/functional.h>
|
||||
#include <TagWidget.h>
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QMouseEvent>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <TagWidget.h>
|
||||
#include <QMenu>
|
||||
#include <QProgressBar>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
CartOverlayWidget::CartOverlayWidget(GemModel* gemModel, QWidget* parent)
|
||||
CartOverlayWidget::CartOverlayWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_gemModel(gemModel)
|
||||
, m_downloadController(downloadController)
|
||||
{
|
||||
setObjectName("GemCatalogCart");
|
||||
|
||||
@@ -42,6 +46,9 @@ namespace O3DE::ProjectManager
|
||||
hLayout->addWidget(closeButton);
|
||||
m_layout->addLayout(hLayout);
|
||||
|
||||
// downloading gems
|
||||
CreateDownloadSection();
|
||||
|
||||
// added
|
||||
CreateGemSection( tr("Gem to be activated"), tr("Gems to be activated"), [=]
|
||||
{
|
||||
@@ -149,6 +156,109 @@ namespace O3DE::ProjectManager
|
||||
update();
|
||||
}
|
||||
|
||||
void CartOverlayWidget::CreateDownloadSection()
|
||||
{
|
||||
QWidget* widget = new QWidget();
|
||||
widget->setFixedWidth(s_width);
|
||||
m_layout->addWidget(widget);
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
layout->setAlignment(Qt::AlignTop);
|
||||
widget->setLayout(layout);
|
||||
|
||||
QLabel* titleLabel = new QLabel();
|
||||
titleLabel->setObjectName("GemCatalogCartOverlaySectionLabel");
|
||||
layout->addWidget(titleLabel);
|
||||
|
||||
titleLabel->setText(tr("Gems to be installed"));
|
||||
|
||||
// Create header section
|
||||
QWidget* downloadingGemsWidget = new QWidget();
|
||||
downloadingGemsWidget->setObjectName("GemCatalogCartOverlayGemDownloadHeader");
|
||||
layout->addWidget(downloadingGemsWidget);
|
||||
QVBoxLayout* gemDownloadLayout = new QVBoxLayout();
|
||||
gemDownloadLayout->setMargin(0);
|
||||
gemDownloadLayout->setAlignment(Qt::AlignTop);
|
||||
downloadingGemsWidget->setLayout(gemDownloadLayout);
|
||||
QLabel* processingQueueLabel = new QLabel("Processing Queue");
|
||||
gemDownloadLayout->addWidget(processingQueueLabel);
|
||||
|
||||
QWidget* downloadingItemWidget = new QWidget();
|
||||
downloadingItemWidget->setObjectName("GemCatalogCartOverlayGemDownloadBG");
|
||||
gemDownloadLayout->addWidget(downloadingItemWidget);
|
||||
QVBoxLayout* downloadingItemLayout = new QVBoxLayout();
|
||||
downloadingItemLayout->setAlignment(Qt::AlignTop);
|
||||
downloadingItemWidget->setLayout(downloadingItemLayout);
|
||||
|
||||
auto update = [=](int downloadProgress)
|
||||
{
|
||||
if (m_downloadController->IsDownloadQueueEmpty())
|
||||
{
|
||||
widget->hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
widget->setUpdatesEnabled(false);
|
||||
// remove items
|
||||
QLayoutItem* layoutItem = nullptr;
|
||||
while ((layoutItem = downloadingItemLayout->takeAt(0)) != nullptr)
|
||||
{
|
||||
if (layoutItem->layout())
|
||||
{
|
||||
// Gem info row
|
||||
QLayoutItem* rowLayoutItem = nullptr;
|
||||
while ((rowLayoutItem = layoutItem->layout()->takeAt(0)) != nullptr)
|
||||
{
|
||||
rowLayoutItem->widget()->deleteLater();
|
||||
}
|
||||
layoutItem->layout()->deleteLater();
|
||||
}
|
||||
if (layoutItem->widget())
|
||||
{
|
||||
layoutItem->widget()->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
// Setup gem download rows
|
||||
const AZStd::vector<QString>& downloadQueue = m_downloadController->GetDownloadQueue();
|
||||
|
||||
QLabel* downloadsInProgessLabel = new QLabel("");
|
||||
downloadsInProgessLabel->setText(
|
||||
QString("%1 %2").arg(downloadQueue.size()).arg(downloadQueue.size() == 1 ? tr("download in progress...") : tr("downloads in progress...")));
|
||||
downloadingItemLayout->addWidget(downloadsInProgessLabel);
|
||||
|
||||
for (int downloadingGemNumber = 0; downloadingGemNumber < downloadQueue.size(); ++downloadingGemNumber)
|
||||
{
|
||||
QHBoxLayout* nameProgressLayout = new QHBoxLayout();
|
||||
TagWidget* newTag = new TagWidget(downloadQueue[downloadingGemNumber]);
|
||||
nameProgressLayout->addWidget(newTag);
|
||||
QLabel* progress = new QLabel(downloadingGemNumber == 0? QString("%1%").arg(downloadProgress) : tr("Queued"));
|
||||
nameProgressLayout->addWidget(progress);
|
||||
QSpacerItem* spacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
nameProgressLayout->addSpacerItem(spacer);
|
||||
QLabel* cancelText = new QLabel(tr("Cancel"));
|
||||
nameProgressLayout->addWidget(cancelText);
|
||||
downloadingItemLayout->addLayout(nameProgressLayout);
|
||||
QProgressBar* downloadProgessBar = new QProgressBar();
|
||||
downloadingItemLayout->addWidget(downloadProgessBar);
|
||||
downloadProgessBar->setValue(downloadingGemNumber == 0 ? downloadProgress : 0);
|
||||
}
|
||||
|
||||
widget->setUpdatesEnabled(true);
|
||||
widget->show();
|
||||
}
|
||||
};
|
||||
|
||||
auto downloadEnded = [=](bool /*success*/)
|
||||
{
|
||||
update(0); // update the list to remove the gem that has finished
|
||||
};
|
||||
// connect to download controller data changed
|
||||
connect(m_downloadController, &DownloadController::GemDownloadProgress, this, update);
|
||||
connect(m_downloadController, &DownloadController::Done, this, downloadEnded);
|
||||
update(0);
|
||||
}
|
||||
|
||||
QStringList CartOverlayWidget::ConvertFromModelIndices(const QVector<QModelIndex>& gems) const
|
||||
{
|
||||
QStringList gemNames;
|
||||
@@ -160,9 +270,10 @@ namespace O3DE::ProjectManager
|
||||
return gemNames;
|
||||
}
|
||||
|
||||
CartButton::CartButton(GemModel* gemModel, QWidget* parent)
|
||||
CartButton::CartButton(GemModel* gemModel, DownloadController* downloadController, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_gemModel(gemModel)
|
||||
, m_downloadController(downloadController)
|
||||
{
|
||||
m_layout = new QHBoxLayout();
|
||||
m_layout->setMargin(0);
|
||||
@@ -239,7 +350,7 @@ namespace O3DE::ProjectManager
|
||||
delete m_cartOverlay;
|
||||
}
|
||||
|
||||
m_cartOverlay = new CartOverlayWidget(m_gemModel, this);
|
||||
m_cartOverlay = new CartOverlayWidget(m_gemModel, m_downloadController, this);
|
||||
connect(m_cartOverlay, &QWidget::destroyed, this, [=]
|
||||
{
|
||||
// Reset the overlay pointer on destruction to prevent dangling pointers.
|
||||
@@ -265,7 +376,7 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
GemCatalogHeaderWidget::GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, QWidget* parent)
|
||||
GemCatalogHeaderWidget::GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, DownloadController* downloadController, QWidget* parent)
|
||||
: QFrame(parent)
|
||||
{
|
||||
QHBoxLayout* hLayout = new QHBoxLayout();
|
||||
@@ -293,8 +404,30 @@ namespace O3DE::ProjectManager
|
||||
hLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding));
|
||||
hLayout->addSpacerItem(new QSpacerItem(75, 0, QSizePolicy::Fixed));
|
||||
|
||||
CartButton* cartButton = new CartButton(gemModel);
|
||||
CartButton* cartButton = new CartButton(gemModel, downloadController);
|
||||
hLayout->addWidget(cartButton);
|
||||
|
||||
hLayout->addSpacing(16);
|
||||
|
||||
// Separating line
|
||||
QFrame* vLine = new QFrame();
|
||||
vLine->setFrameShape(QFrame::VLine);
|
||||
vLine->setObjectName("verticalSeparatingLine");
|
||||
hLayout->addWidget(vLine);
|
||||
|
||||
hLayout->addSpacing(16);
|
||||
|
||||
QMenu* gemMenu = new QMenu(this);
|
||||
m_openGemReposAction = gemMenu->addAction(tr("Show Gem Repos"));
|
||||
|
||||
connect(m_openGemReposAction, &QAction::triggered, this,[this](){ emit OpenGemsRepo(); });
|
||||
|
||||
QPushButton* gemMenuButton = new QPushButton(this);
|
||||
gemMenuButton->setObjectName("gemCatalogMenuButton");
|
||||
gemMenuButton->setMenu(gemMenu);
|
||||
gemMenuButton->setIcon(QIcon(":/menu.svg"));
|
||||
gemMenuButton->setIconSize(QSize(36, 24));
|
||||
hLayout->addWidget(gemMenuButton);
|
||||
}
|
||||
|
||||
void GemCatalogHeaderWidget::ReinitForProject()
|
||||
|
||||
@@ -15,12 +15,15 @@
|
||||
#include <GemCatalog/GemModel.h>
|
||||
#include <GemCatalog/GemSortFilterProxyModel.h>
|
||||
#include <TagWidget.h>
|
||||
#include <DownloadController.h>
|
||||
|
||||
#include <QFrame>
|
||||
#include <QLabel>
|
||||
#include <QDialog>
|
||||
#include <QMoveEvent>
|
||||
#include <QHideEvent>
|
||||
#include <QVBoxLayout>
|
||||
#include <QAction>
|
||||
#endif
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
@@ -31,16 +34,18 @@ namespace O3DE::ProjectManager
|
||||
Q_OBJECT // AUTOMOC
|
||||
|
||||
public:
|
||||
CartOverlayWidget(GemModel* gemModel, QWidget* parent = nullptr);
|
||||
CartOverlayWidget(GemModel* gemModel, DownloadController* downloadController, QWidget* parent = nullptr);
|
||||
|
||||
private:
|
||||
QStringList ConvertFromModelIndices(const QVector<QModelIndex>& gems) const;
|
||||
|
||||
using GetTagIndicesCallback = AZStd::function<QVector<QModelIndex>()>;
|
||||
void CreateGemSection(const QString& singularTitle, const QString& pluralTitle, GetTagIndicesCallback getTagIndices);
|
||||
void CreateDownloadSection();
|
||||
|
||||
QVBoxLayout* m_layout = nullptr;
|
||||
GemModel* m_gemModel = nullptr;
|
||||
DownloadController* m_downloadController = nullptr;
|
||||
|
||||
inline constexpr static int s_width = 240;
|
||||
};
|
||||
@@ -51,7 +56,7 @@ namespace O3DE::ProjectManager
|
||||
Q_OBJECT // AUTOMOC
|
||||
|
||||
public:
|
||||
CartButton(GemModel* gemModel, QWidget* parent = nullptr);
|
||||
CartButton(GemModel* gemModel, DownloadController* downloadController, QWidget* parent = nullptr);
|
||||
~CartButton();
|
||||
void ShowOverlay();
|
||||
|
||||
@@ -64,6 +69,7 @@ namespace O3DE::ProjectManager
|
||||
QLabel* m_countLabel = nullptr;
|
||||
QPushButton* m_dropDownButton = nullptr;
|
||||
CartOverlayWidget* m_cartOverlay = nullptr;
|
||||
DownloadController* m_downloadController = nullptr;
|
||||
|
||||
inline constexpr static int s_iconSize = 24;
|
||||
inline constexpr static int s_arrowDownIconSize = 8;
|
||||
@@ -75,13 +81,18 @@ namespace O3DE::ProjectManager
|
||||
Q_OBJECT // AUTOMOC
|
||||
|
||||
public:
|
||||
explicit GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, QWidget* parent = nullptr);
|
||||
explicit GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, DownloadController* downloadController, QWidget* parent = nullptr);
|
||||
~GemCatalogHeaderWidget() = default;
|
||||
|
||||
void ReinitForProject();
|
||||
|
||||
signals:
|
||||
void OpenGemsRepo();
|
||||
|
||||
private:
|
||||
AzQtComponents::SearchLineEdit* m_filterLineEdit = nullptr;
|
||||
inline constexpr static int s_height = 60;
|
||||
|
||||
QAction* m_openGemReposAction = nullptr;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <GemCatalog/GemSortFilterProxyModel.h>
|
||||
#include <GemCatalog/GemRequirementDialog.h>
|
||||
#include <GemCatalog/GemDependenciesDialog.h>
|
||||
#include <DownloadController.h>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
@@ -32,9 +33,13 @@ namespace O3DE::ProjectManager
|
||||
vLayout->setSpacing(0);
|
||||
setLayout(vLayout);
|
||||
|
||||
m_headerWidget = new GemCatalogHeaderWidget(m_gemModel, m_proxModel);
|
||||
m_downloadController = new DownloadController();
|
||||
|
||||
m_headerWidget = new GemCatalogHeaderWidget(m_gemModel, m_proxModel, m_downloadController);
|
||||
vLayout->addWidget(m_headerWidget);
|
||||
|
||||
connect(m_headerWidget, &GemCatalogHeaderWidget::OpenGemsRepo, this, &GemCatalogScreen::HandleOpenGemRepo);
|
||||
|
||||
QHBoxLayout* hLayout = new QHBoxLayout();
|
||||
hLayout->setMargin(0);
|
||||
vLayout->addLayout(hLayout);
|
||||
@@ -61,6 +66,9 @@ namespace O3DE::ProjectManager
|
||||
hLayout->addWidget(filterWidget);
|
||||
hLayout->addLayout(middleVLayout);
|
||||
hLayout->addWidget(m_gemInspector);
|
||||
|
||||
m_notificationsView = AZStd::make_unique<AzToolsFramework::ToastNotificationsView>(this, AZ_CRC("GemCatalogNotificationsView"));
|
||||
m_notificationsView->SetOffset(QPoint(10, 70));
|
||||
}
|
||||
|
||||
void GemCatalogScreen::ReinitForProject(const QString& projectPath)
|
||||
@@ -81,6 +89,7 @@ namespace O3DE::ProjectManager
|
||||
m_headerWidget->ReinitForProject();
|
||||
|
||||
connect(m_gemModel, &GemModel::dataChanged, m_filterWidget, &GemFilterWidget::ResetGemStatusFilter);
|
||||
connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged);
|
||||
|
||||
// Select the first entry after everything got correctly sized
|
||||
QTimer::singleShot(200, [=]{
|
||||
@@ -89,6 +98,72 @@ namespace O3DE::ProjectManager
|
||||
});
|
||||
}
|
||||
|
||||
void GemCatalogScreen::OnGemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies)
|
||||
{
|
||||
if (m_notificationsEnabled)
|
||||
{
|
||||
bool added = GemModel::IsAdded(modelIndex);
|
||||
bool dependency = GemModel::IsAddedDependency(modelIndex);
|
||||
|
||||
bool gemStateChanged = (added && !dependency) || (!added && !dependency);
|
||||
if (!gemStateChanged && !numChangedDependencies)
|
||||
{
|
||||
// no actual changes made
|
||||
return;
|
||||
}
|
||||
|
||||
QString notification;
|
||||
if (gemStateChanged)
|
||||
{
|
||||
notification = GemModel::GetDisplayName(modelIndex);
|
||||
if (numChangedDependencies > 0)
|
||||
{
|
||||
notification += " " + tr("and") + " ";
|
||||
}
|
||||
}
|
||||
|
||||
if (numChangedDependencies == 1 )
|
||||
{
|
||||
notification += "1 Gem " + tr("dependency");
|
||||
}
|
||||
else if (numChangedDependencies > 1)
|
||||
{
|
||||
notification += QString("%d Gem ").arg(numChangedDependencies) + tr("dependencies");
|
||||
}
|
||||
notification += " " + (added ? tr("activated") : tr("deactivated"));
|
||||
|
||||
AzQtComponents::ToastConfiguration toastConfiguration(AzQtComponents::ToastType::Custom, notification, "");
|
||||
toastConfiguration.m_customIconImage = ":/gem.svg";
|
||||
toastConfiguration.m_borderRadius = 4;
|
||||
toastConfiguration.m_duration = AZStd::chrono::milliseconds(3000);
|
||||
m_notificationsView->ShowToastNotification(toastConfiguration);
|
||||
}
|
||||
}
|
||||
|
||||
void GemCatalogScreen::hideEvent(QHideEvent* event)
|
||||
{
|
||||
ScreenWidget::hideEvent(event);
|
||||
m_notificationsView->OnHide();
|
||||
}
|
||||
|
||||
void GemCatalogScreen::showEvent(QShowEvent* event)
|
||||
{
|
||||
ScreenWidget::showEvent(event);
|
||||
m_notificationsView->OnShow();
|
||||
}
|
||||
|
||||
void GemCatalogScreen::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
ScreenWidget::resizeEvent(event);
|
||||
m_notificationsView->UpdateToastPosition();
|
||||
}
|
||||
|
||||
void GemCatalogScreen::moveEvent(QMoveEvent* event)
|
||||
{
|
||||
ScreenWidget::moveEvent(event);
|
||||
m_notificationsView->UpdateToastPosition();
|
||||
}
|
||||
|
||||
void GemCatalogScreen::FillModel(const QString& projectPath)
|
||||
{
|
||||
AZ::Outcome<QVector<GemInfo>, AZStd::string> allGemInfosResult = PythonBindingsInterface::Get()->GetAllGemInfos(projectPath);
|
||||
@@ -102,6 +177,7 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
|
||||
m_gemModel->UpdateGemDependencies();
|
||||
m_notificationsEnabled = false;
|
||||
|
||||
// Gather enabled gems for the given project.
|
||||
auto enabledGemNamesResult = PythonBindingsInterface::Get()->GetEnabledGemNames(projectPath);
|
||||
@@ -128,6 +204,8 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
QMessageBox::critical(nullptr, tr("Operation failed"), QString("Cannot retrieve enabled gems for project %1.\n\nError:\n%2").arg(projectPath, enabledGemNamesResult.GetError().c_str()));
|
||||
}
|
||||
|
||||
m_notificationsEnabled = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -191,6 +269,27 @@ namespace O3DE::ProjectManager
|
||||
return EnableDisableGemsResult::Success;
|
||||
}
|
||||
|
||||
void GemCatalogScreen::HandleOpenGemRepo()
|
||||
{
|
||||
QVector<QModelIndex> gemsToBeAdded = m_gemModel->GatherGemsToBeAdded(true);
|
||||
QVector<QModelIndex> gemsToBeRemoved = m_gemModel->GatherGemsToBeRemoved(true);
|
||||
|
||||
if (!gemsToBeAdded.empty() || !gemsToBeRemoved.empty())
|
||||
{
|
||||
QMessageBox::StandardButton warningResult = QMessageBox::warning(
|
||||
nullptr, "Pending Changes",
|
||||
"There are some unsaved changes to the gem selection,<br> they will be lost if you change screens.<br> Are you sure?",
|
||||
QMessageBox::No | QMessageBox::Yes);
|
||||
|
||||
if (warningResult != QMessageBox::Yes)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
emit ChangeScreenRequest(ProjectManagerScreen::GemRepos);
|
||||
}
|
||||
|
||||
ProjectManagerScreen GemCatalogScreen::GetScreenEnum()
|
||||
{
|
||||
return ProjectManagerScreen::GemCatalog;
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
#if !defined(Q_MOC_RUN)
|
||||
#include <ScreenWidget.h>
|
||||
#include <AzCore/std/smart_ptr/unique_ptr.h>
|
||||
#include <AzToolsFramework/UI/Notifications/ToastNotificationsView.h>
|
||||
#include <GemCatalog/GemCatalogHeaderWidget.h>
|
||||
#include <GemCatalog/GemFilterWidget.h>
|
||||
#include <GemCatalog/GemListView.h>
|
||||
@@ -39,10 +41,26 @@ namespace O3DE::ProjectManager
|
||||
EnableDisableGemsResult EnableDisableGemsForProject(const QString& projectPath);
|
||||
|
||||
GemModel* GetGemModel() const { return m_gemModel; }
|
||||
DownloadController* GetDownloadController() const { return m_downloadController; }
|
||||
|
||||
public slots:
|
||||
void OnGemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies);
|
||||
|
||||
protected:
|
||||
void hideEvent(QHideEvent* event) override;
|
||||
void showEvent(QShowEvent* event) override;
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
void moveEvent(QMoveEvent* event) override;
|
||||
|
||||
private slots:
|
||||
void HandleOpenGemRepo();
|
||||
|
||||
|
||||
private:
|
||||
void FillModel(const QString& projectPath);
|
||||
|
||||
AZStd::unique_ptr<AzToolsFramework::ToastNotificationsView> m_notificationsView;
|
||||
|
||||
GemListView* m_gemListView = nullptr;
|
||||
GemInspector* m_gemInspector = nullptr;
|
||||
GemModel* m_gemModel = nullptr;
|
||||
@@ -50,5 +68,7 @@ namespace O3DE::ProjectManager
|
||||
GemSortFilterProxyModel* m_proxModel = nullptr;
|
||||
QVBoxLayout* m_filterWidgetLayout = nullptr;
|
||||
GemFilterWidget* m_filterWidget = nullptr;
|
||||
DownloadController* m_downloadController = nullptr;
|
||||
bool m_notificationsEnabled = true;
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <GemCatalog/GemModel.h>
|
||||
#include <GemCatalog/GemSortFilterProxyModel.h>
|
||||
#include <AzCore/Casting/numeric_cast.h>
|
||||
#include <AzToolsFramework/UI/Notifications/ToastBus.h>
|
||||
|
||||
namespace O3DE::ProjectManager
|
||||
{
|
||||
@@ -299,23 +300,50 @@ namespace O3DE::ProjectManager
|
||||
AZ_Assert(gemModel, "Failed to obtain GemModel");
|
||||
|
||||
QVector<QModelIndex> dependencies = gemModel->GatherGemDependencies(modelIndex);
|
||||
uint32_t numChangedDependencies = 0;
|
||||
|
||||
if (IsAdded(modelIndex))
|
||||
{
|
||||
for (const QModelIndex& dependency : dependencies)
|
||||
{
|
||||
SetIsAddedDependency(*gemModel, dependency, true);
|
||||
if (!IsAddedDependency(dependency))
|
||||
{
|
||||
SetIsAddedDependency(*gemModel, dependency, true);
|
||||
|
||||
// if the gem was already added then the state didn't really change
|
||||
if (!IsAdded(dependency))
|
||||
{
|
||||
numChangedDependencies++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// still a dependency if some added gem depends on this one
|
||||
SetIsAddedDependency(model, modelIndex, gemModel->HasDependentGems(modelIndex));
|
||||
bool hasDependentGems = gemModel->HasDependentGems(modelIndex);
|
||||
if (IsAddedDependency(modelIndex) != hasDependentGems)
|
||||
{
|
||||
SetIsAddedDependency(model, modelIndex, hasDependentGems);
|
||||
}
|
||||
|
||||
for (const QModelIndex& dependency : dependencies)
|
||||
{
|
||||
SetIsAddedDependency(*gemModel, dependency, gemModel->HasDependentGems(dependency));
|
||||
hasDependentGems = gemModel->HasDependentGems(dependency);
|
||||
if (IsAddedDependency(dependency) != hasDependentGems)
|
||||
{
|
||||
SetIsAddedDependency(*gemModel, dependency, hasDependentGems);
|
||||
|
||||
// if the gem was already added then the state didn't really change
|
||||
if (!IsAdded(dependency))
|
||||
{
|
||||
numChangedDependencies++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
gemModel->emit gemStatusChanged(modelIndex, numChangedDependencies);
|
||||
}
|
||||
|
||||
void GemModel::SetIsAddedDependency(QAbstractItemModel& model, const QModelIndex& modelIndex, bool isAdded)
|
||||
@@ -488,5 +516,4 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -77,6 +77,9 @@ namespace O3DE::ProjectManager
|
||||
|
||||
int TotalAddedGems(bool includeDependencies = false) const;
|
||||
|
||||
signals:
|
||||
void gemStatusChanged(const QModelIndex& modelIndex, uint32_t numChangedDependencies);
|
||||
|
||||
private:
|
||||
void FindGemDisplayNamesByNameStrings(QStringList& inOutGemNames);
|
||||
void GetAllDependingGems(const QModelIndex& modelIndex, QSet<QModelIndex>& inOutGems);
|
||||
|
||||
@@ -301,6 +301,7 @@ namespace O3DE::ProjectManager
|
||||
m_enableGemProject = pybind11::module::import("o3de.enable_gem");
|
||||
m_disableGemProject = pybind11::module::import("o3de.disable_gem");
|
||||
m_editProjectProperties = pybind11::module::import("o3de.project_properties");
|
||||
m_download = pybind11::module::import("o3de.download");
|
||||
m_pathlib = pybind11::module::import("pathlib");
|
||||
|
||||
// make sure the engine is registered
|
||||
@@ -1075,4 +1076,30 @@ namespace O3DE::ProjectManager
|
||||
std::sort(gemRepos.begin(), gemRepos.end());
|
||||
return AZ::Success(AZStd::move(gemRepos));
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::DownloadGem(const QString& gemName, std::function<void(int)> gemProgressCallback)
|
||||
{
|
||||
bool downloadSucceeded = false;
|
||||
auto result = ExecuteWithLockErrorHandling(
|
||||
[&]
|
||||
{
|
||||
auto downloadResult = m_download.attr("download_gem")(
|
||||
QString_To_Py_String(gemName), // gem name
|
||||
pybind11::none(), // destination path
|
||||
false// skip auto register
|
||||
);
|
||||
downloadSucceeded = (downloadResult.cast<int>() == 0);
|
||||
});
|
||||
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else if (!downloadSucceeded)
|
||||
{
|
||||
return AZ::Failure<AZStd::string>("Failed to download gem.");
|
||||
}
|
||||
|
||||
return AZ::Success();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ namespace O3DE::ProjectManager
|
||||
bool AddGemRepo(const QString& repoUri) override;
|
||||
bool RemoveGemRepo(const QString& repoUri) override;
|
||||
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() override;
|
||||
AZ::Outcome<void, AZStd::string> DownloadGem(const QString& gemName, std::function<void(int)> gemProgressCallback) override;
|
||||
|
||||
private:
|
||||
AZ_DISABLE_COPY_MOVE(PythonBindings);
|
||||
@@ -87,6 +88,7 @@ namespace O3DE::ProjectManager
|
||||
pybind11::handle m_enableGemProject;
|
||||
pybind11::handle m_disableGemProject;
|
||||
pybind11::handle m_editProjectProperties;
|
||||
pybind11::handle m_download;
|
||||
pybind11::handle m_pathlib;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -187,6 +187,8 @@ namespace O3DE::ProjectManager
|
||||
* @return A list of gem repo infos.
|
||||
*/
|
||||
virtual AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() = 0;
|
||||
|
||||
virtual AZ::Outcome<void, AZStd::string> DownloadGem(const QString& gemName, std::function<void(int)> gemProgressCallback) = 0;
|
||||
};
|
||||
|
||||
using PythonBindingsInterface = AZ::Interface<IPythonBindings>;
|
||||
|
||||
@@ -47,6 +47,14 @@ namespace O3DE::ProjectManager
|
||||
return tr("Missing");
|
||||
}
|
||||
|
||||
virtual bool ContainsScreen([[maybe_unused]] ProjectManagerScreen screen)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
virtual void GoToScreen([[maybe_unused]] ProjectManagerScreen screen)
|
||||
{
|
||||
}
|
||||
|
||||
//! Notify this screen it is the current screen
|
||||
virtual void NotifyCurrentScreen()
|
||||
{
|
||||
@@ -55,7 +63,7 @@ namespace O3DE::ProjectManager
|
||||
|
||||
signals:
|
||||
void ChangeScreenRequest(ProjectManagerScreen screen);
|
||||
void GotoPreviousScreenRequest();
|
||||
void GoToPreviousScreenRequest();
|
||||
void ResetScreenRequest(ProjectManagerScreen screen);
|
||||
void NotifyCurrentProject(const QString& projectPath);
|
||||
void NotifyBuildProject(const ProjectInfo& projectInfo);
|
||||
|
||||
@@ -83,11 +83,28 @@ namespace O3DE::ProjectManager
|
||||
|
||||
bool ScreensCtrl::ForceChangeToScreen(ProjectManagerScreen screen, bool addVisit)
|
||||
{
|
||||
ScreenWidget* newScreen = nullptr;
|
||||
|
||||
const auto iterator = m_screenMap.find(screen);
|
||||
if (iterator != m_screenMap.end())
|
||||
{
|
||||
newScreen = iterator.value();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Check if screen is contained by another screen
|
||||
for (ScreenWidget* checkingScreen : m_screenMap)
|
||||
{
|
||||
if (checkingScreen->ContainsScreen(screen))
|
||||
{
|
||||
newScreen = checkingScreen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (newScreen)
|
||||
{
|
||||
ScreenWidget* currentScreen = GetCurrentScreen();
|
||||
ScreenWidget* newScreen = iterator.value();
|
||||
|
||||
if (currentScreen != newScreen)
|
||||
{
|
||||
@@ -109,6 +126,11 @@ namespace O3DE::ProjectManager
|
||||
|
||||
newScreen->NotifyCurrentScreen();
|
||||
|
||||
if (iterator == m_screenMap.end())
|
||||
{
|
||||
newScreen->GoToScreen(screen);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -116,7 +138,7 @@ namespace O3DE::ProjectManager
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ScreensCtrl::GotoPreviousScreen()
|
||||
bool ScreensCtrl::GoToPreviousScreen()
|
||||
{
|
||||
if (!m_screenVisitOrder.isEmpty())
|
||||
{
|
||||
@@ -171,7 +193,7 @@ namespace O3DE::ProjectManager
|
||||
m_screenMap.insert(screen, newScreen);
|
||||
|
||||
connect(newScreen, &ScreenWidget::ChangeScreenRequest, this, &ScreensCtrl::ChangeToScreen);
|
||||
connect(newScreen, &ScreenWidget::GotoPreviousScreenRequest, this, &ScreensCtrl::GotoPreviousScreen);
|
||||
connect(newScreen, &ScreenWidget::GoToPreviousScreenRequest, this, &ScreensCtrl::GoToPreviousScreen);
|
||||
connect(newScreen, &ScreenWidget::ResetScreenRequest, this, &ScreensCtrl::ResetScreen);
|
||||
connect(newScreen, &ScreenWidget::NotifyCurrentProject, this, &ScreensCtrl::NotifyCurrentProject);
|
||||
connect(newScreen, &ScreenWidget::NotifyBuildProject, this, &ScreensCtrl::NotifyBuildProject);
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace O3DE::ProjectManager
|
||||
public slots:
|
||||
bool ChangeToScreen(ProjectManagerScreen screen);
|
||||
bool ForceChangeToScreen(ProjectManagerScreen screen, bool addVisit = true);
|
||||
bool GotoPreviousScreen();
|
||||
bool GoToPreviousScreen();
|
||||
void ResetScreen(ProjectManagerScreen screen);
|
||||
void ResetAllScreens();
|
||||
void DeleteScreen(ProjectManagerScreen screen);
|
||||
|
||||
@@ -40,6 +40,10 @@ namespace O3DE::ProjectManager
|
||||
m_updateSettingsScreen = new UpdateProjectSettingsScreen();
|
||||
m_gemCatalogScreen = new GemCatalogScreen();
|
||||
|
||||
connect(m_gemCatalogScreen, &ScreenWidget::ChangeScreenRequest, this, [this](ProjectManagerScreen screen){
|
||||
emit ChangeScreenRequest(screen);
|
||||
});
|
||||
|
||||
m_stack = new QStackedWidget(this);
|
||||
m_stack->setObjectName("body");
|
||||
m_stack->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding));
|
||||
@@ -118,7 +122,7 @@ namespace O3DE::ProjectManager
|
||||
{
|
||||
if (UpdateProjectSettings(true))
|
||||
{
|
||||
emit GotoPreviousScreenRequest();
|
||||
emit GoToPreviousScreenRequest();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,6 +140,11 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
else if (m_stack->currentIndex() == ScreenOrder::Gems && m_gemCatalogScreen)
|
||||
{
|
||||
if (!m_gemCatalogScreen->GetDownloadController()->IsDownloadQueueEmpty())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Gems downloading"), tr("You must wait for gems to finish downloading before continuing."));
|
||||
return;
|
||||
}
|
||||
// Enable or disable the gems that got adjusted in the gem catalog and apply them to the given project.
|
||||
const GemCatalogScreen::EnableDisableGemsResult result = m_gemCatalogScreen->EnableDisableGemsForProject(m_projectInfo.m_path);
|
||||
if (result == GemCatalogScreen::EnableDisableGemsResult::Failed)
|
||||
|
||||
@@ -29,6 +29,10 @@ set(FILES
|
||||
Source/FormImageBrowseEditWidget.cpp
|
||||
Source/GemsSubWidget.h
|
||||
Source/GemsSubWidget.cpp
|
||||
Source/DownloadController.h
|
||||
Source/DownloadController.cpp
|
||||
Source/DownloadWorker.h
|
||||
Source/DownloadWorker.cpp
|
||||
Source/PathValidator.h
|
||||
Source/PathValidator.cpp
|
||||
Source/ProjectManagerWindow.h
|
||||
|
||||
Reference in New Issue
Block a user