Merge branch 'development' into math_string_converters

This commit is contained in:
puvvadar
2022-02-14 10:42:48 -08:00
committed by GitHub
373 changed files with 15071 additions and 5781 deletions
@@ -21,7 +21,6 @@
#include <AzToolsFramework/AssetBrowser/AssetEntryChangeset.h>
#include <AzToolsFramework/AssetBrowser/Entries/AssetBrowserEntryCache.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerBus.h>
#include <AzToolsFramework/Thumbnails/ThumbnailContext.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/FolderThumbnail.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/SourceThumbnail.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/ProductThumbnail.h>
@@ -71,9 +70,9 @@ namespace AzToolsFramework
AssetBrowserInteractionNotificationBus::Handler::BusConnect();
using namespace Thumbnailer;
ThumbnailerRequestBus::Broadcast(&ThumbnailerRequests::RegisterThumbnailProvider, MAKE_TCACHE(FolderThumbnailCache), ThumbnailContext::DefaultContext);
ThumbnailerRequestBus::Broadcast(&ThumbnailerRequests::RegisterThumbnailProvider, MAKE_TCACHE(SourceThumbnailCache), ThumbnailContext::DefaultContext);
ThumbnailerRequestBus::Broadcast(&ThumbnailerRequests::RegisterThumbnailProvider, MAKE_TCACHE(ProductThumbnailCache), ThumbnailContext::DefaultContext);
ThumbnailerRequestBus::Broadcast(&ThumbnailerRequests::RegisterThumbnailProvider, MAKE_TCACHE(FolderThumbnailCache));
ThumbnailerRequestBus::Broadcast(&ThumbnailerRequests::RegisterThumbnailProvider, MAKE_TCACHE(SourceThumbnailCache));
ThumbnailerRequestBus::Broadcast(&ThumbnailerRequests::RegisterThumbnailProvider, MAKE_TCACHE(ProductThumbnailCache));
AzFramework::SocketConnection* socketConn = AzFramework::SocketConnection::GetInstance();
AZ_Assert(socketConn, "AzToolsFramework::AssetBrowser::AssetBrowserComponent requires a valid socket conection!");
@@ -82,7 +82,7 @@ namespace AzToolsFramework
});
connect(m_ui->m_assetBrowserTreeViewWidget, &QAbstractItemView::doubleClicked, this, &AssetPickerDialog::DoubleClickedSlot);
connect(m_ui->m_assetBrowserTreeViewWidget, &AssetBrowserTreeView::selectionChangedSignal, this,
[this](const QItemSelection&, const QItemSelection&){ AssetPickerDialog::SelectionChangedSlot(); });
[this](const QItemSelection&, const QItemSelection&){ SelectionChangedSlot(); });
connect(m_ui->m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(m_ui->m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
@@ -153,7 +153,7 @@ namespace AzToolsFramework
m_ui->m_assetBrowserTableViewWidget, &AssetBrowserTableView::selectionChangedSignal, this,
[this](const QItemSelection&, const QItemSelection&)
{
AssetPickerDialog::SelectionChangedSlot();
SelectionChangedSlot();
});
connect(m_ui->m_assetBrowserTableViewWidget, &QAbstractItemView::doubleClicked, this, &AssetPickerDialog::DoubleClickedSlot);
@@ -174,8 +174,15 @@ namespace AzToolsFramework
m_tableModel->UpdateTableModelMaps();
}
QTimer::singleShot(0, this, &AssetPickerDialog::RestoreState);
SelectionChangedSlot();
QTimer::singleShot(0, this, [this]() {
RestoreState();
// The selection doesn't propagate immediately, so we need to delay
// it as well so that the OK button can be updated appropriately.
// Otherwise, it will always be disabled when you first launch
// the asset picker dialog.
SelectionChangedSlot();
});
}
AssetPickerDialog::~AssetPickerDialog() = default;
@@ -58,16 +58,16 @@ namespace AzToolsFramework
void keyPressEvent(QKeyEvent* e) override;
void resizeEvent(QResizeEvent* resizeEvent) override;
private Q_SLOTS:
protected Q_SLOTS:
void DoubleClickedSlot(const QModelIndex& index);
void SelectionChangedSlot();
void RestoreState();
void OnFilterUpdated();
private:
protected:
//! Evaluate whether current selection is valid.
//! Valid selection requires exactly one item to be selected, must be source or product type, and must match the wildcard filter
bool EvaluateSelection() const;
virtual bool EvaluateSelection() const;
void UpdatePreview() const;
void SaveState();
@@ -75,7 +75,9 @@ namespace AzToolsFramework
bool foundIt = false;
AZStd::string watchFolder;
AZ::Data::AssetInfo assetInfo;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(foundIt, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, iconPath.toUtf8().constData(), assetInfo, watchFolder);
AssetSystemRequestBus::BroadcastResult(
foundIt, &AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, iconPath.toUtf8().constData(), assetInfo,
watchFolder);
if (foundIt)
{
@@ -67,7 +67,8 @@ namespace AzToolsFramework
bool foundIt = false;
AZStd::string watchFolder;
AZ::Data::AssetInfo assetInfo;
AssetSystemRequestBus::BroadcastResult(foundIt, &AssetSystemRequestBus::Events::GetSourceInfoBySourceUUID, sourceKey->GetSourceUuid(), assetInfo, watchFolder);
AssetSystemRequestBus::BroadcastResult(
foundIt, &AssetSystemRequestBus::Events::GetSourceInfoBySourceUUID, sourceKey->GetSourceUuid(), assetInfo, watchFolder);
QString iconPathToUse;
if (foundIt)
@@ -203,11 +203,6 @@ namespace AzToolsFramework
return selectionModel()->selectedIndexes();
}
void AssetBrowserTreeView::SetThumbnailContext(const char* thumbnailContext) const
{
m_delegate->SetThumbnailContext(thumbnailContext);
}
void AssetBrowserTreeView::SetShowSourceControlIcons(bool showSourceControlsIcons)
{
m_delegate->SetShowSourceControlIcons(showSourceControlsIcons);
@@ -73,7 +73,6 @@ namespace AzToolsFramework
void OnAssetBrowserComponentReady() override;
//////////////////////////////////////////////////////////////////////////
void SetThumbnailContext(const char* context) const;
void SetShowSourceControlIcons(bool showSourceControlsIcons);
void UpdateAfterFilter(bool hasFilter, bool selectFirstValidEntry);
@@ -112,11 +112,6 @@ namespace AzToolsFramework
}
}
void EntryDelegate::SetThumbnailContext(const char* thumbnailContext)
{
m_thumbnailContext = thumbnailContext;
}
void EntryDelegate::SetShowSourceControlIcons(bool showSourceControl)
{
m_showSourceControl = showSourceControl;
@@ -125,8 +120,8 @@ namespace AzToolsFramework
int EntryDelegate::DrawThumbnail(QPainter* painter, const QPoint& point, const QSize& size, Thumbnailer::SharedThumbnailKey thumbnailKey) const
{
SharedThumbnail thumbnail;
ThumbnailerRequestsBus::BroadcastResult(thumbnail, &ThumbnailerRequests::GetThumbnail, thumbnailKey, m_thumbnailContext.c_str());
AZ_Assert(thumbnail, "The shared numbernail was not available from the ThumbnailerRequestsBus.");
ThumbnailerRequestBus::BroadcastResult(thumbnail, &ThumbnailerRequests::GetThumbnail, thumbnailKey);
AZ_Assert(thumbnail, "The shared numbernail was not available from the ThumbnailerRequestBus.");
AZ_Assert(painter, "A null QPainter was passed in to DrawThumbnail.");
if (!painter || !thumbnail || thumbnail->GetState() == Thumbnail::State::Failed)
{
@@ -11,7 +11,6 @@
#if !defined(Q_MOC_RUN)
#include <AzCore/std/function/function_fwd.h>
#include <AzToolsFramework/Thumbnails/Thumbnail.h>
#include <AzToolsFramework/Thumbnails/ThumbnailContext.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // 4251: class 'QScopedPointer<QBrushData,QBrushDataPointerDeleter>' needs to have dll-interface to be used by clients of class 'QBrush'
// 4800: 'uint': forcing value to bool 'true' or 'false' (performance warning)
#include <QStyledItemDelegate>
@@ -50,14 +49,11 @@ namespace AzToolsFramework
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
//! Set location where thumbnails are located for this instance of asset browser
void SetThumbnailContext(const char* thumbnailContext);
//! Set whether to show source control icons, this is still temporary mainly to support existing functionality of material browser
void SetShowSourceControlIcons(bool showSourceControl);
protected:
int m_iconSize;
AZStd::string m_thumbnailContext = ThumbnailContext::DefaultContext;
bool m_showSourceControl = false;
//! Draw a thumbnail and return its width
int DrawThumbnail(QPainter* painter, const QPoint& point, const QSize& size, Thumbnailer::SharedThumbnailKey thumbnailKey) const;
@@ -14,6 +14,8 @@
#include <AzToolsFramework/Manipulators/ManipulatorSnapping.h>
#include <AzFramework/Viewport/ViewportColors.h>
#include <AzFramework/Viewport/ViewportConstants.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Component/NonUniformScaleBus.h>
namespace AzToolsFramework
{
@@ -37,20 +39,20 @@ namespace AzToolsFramework
void BoxViewportEdit::UpdateManipulators()
{
AZ::Transform boxWorldFromLocal = AZ::Transform::CreateIdentity();
BoxManipulatorRequestBus::EventResult(
boxWorldFromLocal, m_entityComponentIdPair, &BoxManipulatorRequests::GetCurrentTransform);
AZ::TransformBus::EventResult(
boxWorldFromLocal, m_entityComponentIdPair.GetEntityId(), &AZ::TransformBus::Events::GetWorldTM);
AZ::Vector3 boxScale = AZ::Vector3::CreateOne();
BoxManipulatorRequestBus::EventResult(
boxScale, m_entityComponentIdPair, &BoxManipulatorRequests::GetBoxScale);
AZ::Vector3 nonUniformScale = AZ::Vector3::CreateOne();
AZ::NonUniformScaleRequestBus::EventResult(
nonUniformScale, m_entityComponentIdPair.GetEntityId(), &AZ::NonUniformScaleRequestBus::Events::GetScale);
AZ::Vector3 boxDimensions = AZ::Vector3::CreateZero();
BoxManipulatorRequestBus::EventResult(
boxDimensions, m_entityComponentIdPair, &BoxManipulatorRequests::GetDimensions);
boxDimensions, m_entityComponentIdPair, &BoxManipulatorRequestBus::Events::GetDimensions);
// ensure we apply the entity scale to the box dimensions so
// the manipulators appear in the correct location
boxDimensions *= boxScale;
AZ::Transform boxLocalTransform = AZ::Transform::CreateIdentity();
BoxManipulatorRequestBus::EventResult(
boxLocalTransform, m_entityComponentIdPair, &BoxManipulatorRequestBus::Events::GetCurrentLocalTransform);
for (size_t manipulatorIndex = 0; manipulatorIndex < m_linearManipulators.size(); ++manipulatorIndex)
{
@@ -58,7 +60,8 @@ namespace AzToolsFramework
{
linearManipulator->SetSpace(boxWorldFromLocal);
linearManipulator->SetLocalTransform(
AZ::Transform::CreateTranslation(s_boxAxes[manipulatorIndex] * 0.5f * boxDimensions));
boxLocalTransform * AZ::Transform::CreateTranslation(s_boxAxes[manipulatorIndex] * 0.5f * boxDimensions));
linearManipulator->SetNonUniformScale(nonUniformScale);
linearManipulator->SetBoundsDirty();
}
}
@@ -69,8 +72,8 @@ namespace AzToolsFramework
m_entityComponentIdPair = entityComponentIdPair;
AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity();
BoxManipulatorRequestBus::EventResult(
worldFromLocal, entityComponentIdPair, &BoxManipulatorRequests::GetCurrentTransform);
AZ::TransformBus::EventResult(
worldFromLocal, entityComponentIdPair.GetEntityId(), &AZ::TransformBus::Events::GetWorldTM);
for (size_t manipulatorIndex = 0; manipulatorIndex < m_linearManipulators.size(); ++manipulatorIndex)
{
@@ -85,36 +88,35 @@ namespace AzToolsFramework
ManipulatorViews views;
views.emplace_back(CreateManipulatorViewQuadBillboard(
AzFramework::ViewportColors::DefaultManipulatorHandleColor, AzFramework::ViewportConstants::DefaultManipulatorHandleSize));
AzFramework::ViewportColors::DefaultManipulatorHandleColor,
AzFramework::ViewportConstants::DefaultManipulatorHandleSize));
linearManipulator->SetViews(AZStd::move(views));
linearManipulator->InstallMouseMoveCallback(
[this, entityComponentIdPair](
const LinearManipulator::Action& action)
[this, entityComponentIdPair,
transformScale{ linearManipulator->GetSpace().GetUniformScale() }](const LinearManipulator::Action& action)
{
AZ::Transform boxLocalTransform = AZ::Transform::CreateIdentity();
BoxManipulatorRequestBus::EventResult(
boxLocalTransform, entityComponentIdPair, &BoxManipulatorRequestBus::Events::GetCurrentLocalTransform);
const AZ::Vector3 manipulatorPosition = GetPositionInManipulatorFrame(transformScale, boxLocalTransform, action);
// calculate the amount of displacement along an axis this manipulator has moved
// clamp movement so it cannot go negative based on axis direction
const AZ::Vector3 axisDisplacement =
action.LocalPosition().GetAbs() * 2.0f
* AZ::GetMax<float>(0.0f, action.LocalPosition().GetNormalized().Dot(action.m_fixed.m_axis));
AZ::Vector3 boxScale = AZ::Vector3::CreateOne();
BoxManipulatorRequestBus::EventResult(
boxScale, entityComponentIdPair, &BoxManipulatorRequests::GetBoxScale);
manipulatorPosition.GetAbs() * 2.0f
* AZ::GetMax(0.0f, manipulatorPosition.GetNormalized().Dot(action.m_fixed.m_axis));
AZ::Vector3 boxDimensions = AZ::Vector3::CreateZero();
BoxManipulatorRequestBus::EventResult(
boxDimensions, entityComponentIdPair, &BoxManipulatorRequests::GetDimensions);
// ensure we take into account the entity scale using the axis displacement
const AZ::Vector3 scaledAxisDisplacement =
axisDisplacement / boxScale;
boxDimensions, entityComponentIdPair, &BoxManipulatorRequestBus::Events::GetDimensions);
// update dimensions - preserve dimensions not effected by this
// axis, and update current axis displacement
BoxManipulatorRequestBus::Event(
entityComponentIdPair, &BoxManipulatorRequests::SetDimensions,
(NotAxis(action.m_fixed.m_axis) * boxDimensions).GetMax(scaledAxisDisplacement));
entityComponentIdPair, &BoxManipulatorRequestBus::Events::SetDimensions,
(NotAxis(action.m_fixed.m_axis) * boxDimensions).GetMax(axisDisplacement));
UpdateManipulators();
});
@@ -137,4 +139,12 @@ namespace AzToolsFramework
}
}
}
AZ::Vector3 GetPositionInManipulatorFrame(float worldUniformScale, const AZ::Transform& manipulatorLocalTransform,
const LinearManipulator::Action& action)
{
return manipulatorLocalTransform.GetInverse().TransformPoint(
action.m_start.m_localPosition +
action.m_current.m_localPositionOffset / AZ::GetClamp(worldUniformScale, AZ::MinTransformScale, AZ::MaxTransformScale));
}
} // namespace AzToolsFramework
@@ -31,4 +31,10 @@ namespace AzToolsFramework
using BoxManipulators = AZStd::array<AZStd::shared_ptr<LinearManipulator>, 6>;
BoxManipulators m_linearManipulators; ///< Manipulators for editing box size.
};
/// Calculates the position of the manipulator in its own reference frame.
/// Removes the effects of the manipulator local transform, and accounts for world transform scale in
/// the action local offset.
AZ::Vector3 GetPositionInManipulatorFrame(float worldUniformScale, const AZ::Transform& manipulatorLocalTransform,
const LinearManipulator::Action& action);
} // namespace AzToolsFramework
@@ -54,7 +54,8 @@ namespace AzToolsFramework
virtual ~BaseManipulator();
using EntityComponentIds = AZStd::unordered_set<AZ::EntityComponentIdPair>;
using UniqueEntityIds = AZStd::unordered_set<AZ::EntityId>;
using UniqueEntityComponentIds = AZStd::unordered_set<AZ::EntityComponentIdPair>;
//! Callback for the event when the mouse pointer is over this manipulator and the left mouse button is pressed.
//! @param interaction It contains various mouse states when the event happens, as well as a ray shooting from the viewing camera
@@ -137,7 +138,7 @@ namespace AzToolsFramework
}
//! Returns all EntityComponentIdPairs associated with this manipulator.
const EntityComponentIds& EntityComponentIdPairs() const
const UniqueEntityComponentIds& EntityComponentIdPairs() const
{
return m_entityComponentIdPairs;
}
@@ -147,10 +148,10 @@ namespace AzToolsFramework
//! Remove an entity from being affected by this manipulator.
//! @note All components on this entity registered with the manipulator will be removed.
EntityComponentIds::iterator RemoveEntityId(AZ::EntityId entityId);
UniqueEntityComponentIds::iterator RemoveEntityId(AZ::EntityId entityId);
//! Remove a specific component (via a EntityComponentIdPair) being affected by this manipulator.
EntityComponentIds::iterator RemoveEntityComponentIdPair(const AZ::EntityComponentIdPair& entityComponentIdPair);
UniqueEntityComponentIds::iterator RemoveEntityComponentIdPair(const AZ::EntityComponentIdPair& entityComponentIdPair);
//! Is this entity currently being tracked by this manipulator.
bool HasEntityId(AZ::EntityId entityId) const;
@@ -26,11 +26,21 @@ namespace AzToolsFramework
virtual AZ::Vector3 GetDimensions() = 0;
//! Set the X/Y/Z dimensions of the box shape/collider.
virtual void SetDimensions(const AZ::Vector3& dimensions) = 0;
// O3DE_DEPRECATION_NOTICE(GHI-7572)
//! @deprecated Because non-uniform scale effects can be complex, it is recommended to separately use
//! AZ::TransformBus::Events::GetWorldTM, AZ::NonUniformScaleRequests::GetScale and GetCurrentLocalTransform
//! and combine their effects.
//! Get the transform of the box shape/collider.
//! This is used by \ref BoxComponentMode instead of the \ref \AZ::TransformBus
//! because a collider may have an additional translation/orientation offset from
//! the Entity transform.
virtual AZ::Transform GetCurrentTransform() = 0;
//! Get the transform of the box relative to the entity.
virtual AZ::Transform GetCurrentLocalTransform() = 0;
// O3DE_DEPRECATION_NOTICE(GHI-7572)
//! @deprecated Because non-uniform scale effects can be complex, it is recommended to separately use
//! AZ::TransformBus::Events::GetWorldTM, AZ::NonUniformScaleRequests::GetScale and GetCurrentLocalTransform
//! and combine their effects.
//! Get the scale currently applied to the box.
//! With the Box Shape, the largest x/y/z component is taken
//! so scale is always uniform, with colliders the scale may
@@ -80,23 +80,18 @@ namespace AzToolsFramework
m_onMouseMoveCallback = onMouseMoveCallback;
}
void SurfaceManipulator::InstallEntityIdsToIgnoreFn(EntityIdsToIgnoreFn entityIdsToIgnoreCallback)
{
m_entityIdsToIgnoreFn = AZStd::move(entityIdsToIgnoreCallback);
}
void SurfaceManipulator::OnLeftMouseDownImpl(
const ViewportInteraction::MouseInteraction& interaction, [[maybe_unused]] float rayIntersectionDistance)
{
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace());
const AzFramework::ViewportId viewportId = interaction.m_interactionId.m_viewportId;
const auto& entityComponentIdPairs = EntityComponentIdPairs();
m_rayRequest.m_entityFilter.m_ignoreEntities.clear();
m_rayRequest.m_entityFilter.m_ignoreEntities.reserve(entityComponentIdPairs.size());
AZStd::transform(
entityComponentIdPairs.begin(), entityComponentIdPairs.end(),
AZStd::inserter(m_rayRequest.m_entityFilter.m_ignoreEntities, m_rayRequest.m_entityFilter.m_ignoreEntities.begin()),
[](const AZ::EntityComponentIdPair& entityComponentIdPair)
{
return entityComponentIdPair.GetEntityId();
});
m_rayRequest.m_entityFilter.m_ignoreEntities = m_entityIdsToIgnoreFn(interaction);
// calculate the start and end of the ray
RefreshRayRequest(
@@ -139,6 +134,8 @@ namespace AzToolsFramework
{
const AzFramework::ViewportId viewportId = interaction.m_interactionId.m_viewportId;
m_rayRequest.m_entityFilter.m_ignoreEntities = m_entityIdsToIgnoreFn(interaction);
// update the start and end of the ray
RefreshRayRequest(
m_rayRequest, ViewportInteraction::ViewportScreenToWorldRay(viewportId, interaction.m_mousePick.m_screenCoordinates),
@@ -162,12 +159,12 @@ namespace AzToolsFramework
const ManipulatorManagerState& managerState,
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction)
const ViewportInteraction::MouseInteraction& interaction)
{
m_manipulatorView->Draw(
GetManipulatorManagerId(), managerState, GetManipulatorId(),
ManipulatorState{ TransformUniformScale(GetSpace()), GetNonUniformScale(), GetLocalPosition(), MouseOver() }, debugDisplay,
cameraState, mouseInteraction);
cameraState, interaction);
}
void SurfaceManipulator::InvalidateImpl()
@@ -40,6 +40,9 @@ namespace AzToolsFramework
//! A Manipulator must only be created and managed through a shared_ptr.
static AZStd::shared_ptr<SurfaceManipulator> MakeShared(const AZ::Transform& worldFromLocal);
//! Callback function to determine which EntityIds to ignore when performing the ray intersection.
using EntityIdsToIgnoreFn = AZStd::function<UniqueEntityIds(const ViewportInteraction::MouseInteraction&)>;
//! The state of the manipulator at the start of an interaction.
struct Start
{
@@ -77,11 +80,13 @@ namespace AzToolsFramework
void InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback);
void InstallMouseMoveCallback(const MouseActionCallback& onMouseMoveCallback);
void InstallEntityIdsToIgnoreFn(EntityIdsToIgnoreFn entityIdsToIgnoreFn);
void Draw(
const ManipulatorManagerState& managerState,
AzFramework::DebugDisplayRequests& debugDisplay,
const AzFramework::CameraState& cameraState,
const ViewportInteraction::MouseInteraction& mouseInteraction) override;
const ViewportInteraction::MouseInteraction& interaction) override;
void SetView(AZStd::unique_ptr<ManipulatorView>&& view);
@@ -109,6 +114,9 @@ namespace AzToolsFramework
MouseActionCallback m_onLeftMouseUpCallback = nullptr;
MouseActionCallback m_onMouseMoveCallback = nullptr;
//! Customization point to determine which (if any) EntityIds to ignore while performing the ray intersection.
EntityIdsToIgnoreFn m_entityIdsToIgnoreFn = nullptr;
//! Cached ray request initialized at mouse down and updated during mouse move.
AzFramework::RenderGeometry::RayRequest m_rayRequest;
@@ -145,6 +145,15 @@ namespace AzToolsFramework
}
}
void TranslationManipulators::InstallSurfaceManipulatorEntityIdsToIgnoreFn(
SurfaceManipulator::EntityIdsToIgnoreFn entityIdsToIgnoreFn)
{
if (m_surfaceManipulator)
{
m_surfaceManipulator->InstallEntityIdsToIgnoreFn(AZStd::move(entityIdsToIgnoreFn));
}
}
void TranslationManipulators::SetLocalTransformImpl(const AZ::Transform& localTransform)
{
for (AZStd::shared_ptr<LinearManipulator>& manipulator : m_linearManipulators)
@@ -61,6 +61,8 @@ namespace AzToolsFramework
void InstallSurfaceManipulatorMouseMoveCallback(const SurfaceManipulator::MouseActionCallback& onMouseMoveCallback);
void InstallSurfaceManipulatorMouseUpCallback(const SurfaceManipulator::MouseActionCallback& onMouseUpCallback);
void InstallSurfaceManipulatorEntityIdsToIgnoreFn(SurfaceManipulator::EntityIdsToIgnoreFn entityIdsToIgnoreFn);
void SetSpaceImpl(const AZ::Transform& worldFromLocal) override;
void SetLocalTransformImpl(const AZ::Transform& localTransform) override;
void SetLocalPositionImpl(const AZ::Vector3& localPosition) override;
@@ -23,7 +23,15 @@ namespace AzToolsFramework
//////////////////////////////////////////////////////////////////////////
// ThumbnailKey
//////////////////////////////////////////////////////////////////////////
bool ThumbnailKey::IsReady() const { return m_ready; }
void ThumbnailKey::SetReady(bool ready)
{
m_ready = ready;
}
bool ThumbnailKey::IsReady() const
{
return m_ready;
}
bool ThumbnailKey::UpdateThumbnail()
{
@@ -75,10 +83,8 @@ namespace AzToolsFramework
if (m_state == State::Unloaded)
{
m_state = State::Loading;
QThreadPool* threadPool;
ThumbnailContextRequestBus::BroadcastResult(
threadPool,
&ThumbnailContextRequestBus::Handler::GetThreadPool);
QThreadPool* threadPool = {};
ThumbnailerRequestBus::BroadcastResult(threadPool, &ThumbnailerRequestBus::Handler::GetThreadPool);
QFuture<void> future = QtConcurrent::run(threadPool, [this](){ LoadThread(); });
m_watcher.setFuture(future);
}
@@ -26,13 +26,11 @@ namespace AzToolsFramework
//! ThumbnailKey is used to locate thumbnails in thumbnail cache
/*
ThumbnailKey contains any kind of identifiable information to retrieve thumbnails (e.g. assetId, assetType, filename, etc.)
To use thumbnail system, keep reference to your thumbnail key, and retrieve Thumbnail via ThumbnailerRequestsBus
To use thumbnail system, keep reference to your thumbnail key, and retrieve Thumbnail via ThumbnailerRequestBus
*/
class ThumbnailKey
: public QObject
{
friend class ThumbnailContext;
Q_OBJECT
public:
AZ_RTTI(ThumbnailKey, "{43F20F6B-333D-4226-8E4F-331A62315255}");
@@ -40,6 +38,8 @@ namespace AzToolsFramework
ThumbnailKey() = default;
virtual ~ThumbnailKey() = default;
void SetReady(bool ready);
bool IsReady() const;
virtual bool UpdateThumbnail();
@@ -47,13 +47,13 @@ namespace AzToolsFramework
virtual size_t GetHash() const;
virtual bool Equals(const ThumbnailKey* other) const;
Q_SIGNALS:
//! Updated signal is dispatched whenever thumbnail data was changed. Anyone using this thumbnail should listen to this.
void ThumbnailUpdatedSignal() const;
//! Force update mapped thumbnails
void UpdateThumbnailSignal() const;
private:
bool m_ready = false;
};
@@ -114,11 +114,14 @@ namespace AzToolsFramework
ThumbnailProvider() = default;
virtual ~ThumbnailProvider() = default;
virtual bool GetThumbnail(SharedThumbnailKey key, SharedThumbnail& thumbnail) = 0;
//! Priority identifies ThumbnailProvider order in ThumbnailContext
//! Higher priority means this ThumbnailProvider will take precedence in generating a thumbnail when
//! a supplied ThumbnailKey is supported by multiple providers.
virtual int GetPriority() const { return 0; }
//! A unique ThumbnailProvider name identifying it in a ThumbnailContext
//! Priority identifies ThumbnailProvider order
//! Higher priority means this ThumbnailProvider will take precedence in generating a thumbnail when a supplied ThumbnailKey is
//! supported by multiple providers.
virtual int GetPriority() const
{
return 0;
}
//! A unique ThumbnailProvider name identifyier
virtual const char* GetProviderName() const = 0;
};
@@ -1,132 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzToolsFramework/Thumbnails/ThumbnailContext.h>
#include <AzToolsFramework/Thumbnails/MissingThumbnail.h>
#include <AzToolsFramework/Thumbnails/LoadingThumbnail.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
AZ_PUSH_DISABLE_WARNING(4244 4251, "-Wunknown-warning-option") // 4251: 'QImageIOHandler::d_ptr': class 'QScopedPointer<QImageIOHandlerPrivate,QScopedPointerDeleter<T>>' needs to have dll-interface to be used by clients of class 'QImageIOHandler'
#include <AzQtComponents/Components/StyledBusyLabel.h>
AZ_POP_DISABLE_WARNING
namespace AzToolsFramework
{
namespace Thumbnailer
{
ThumbnailContext::ThumbnailContext()
: m_missingThumbnail(new MissingThumbnail())
, m_loadingThumbnail(new LoadingThumbnail())
, m_threadPool(this)
{
ThumbnailContextRequestBus::Handler::BusConnect();
}
ThumbnailContext::~ThumbnailContext()
{
ThumbnailContextRequestBus::Handler::BusDisconnect();
}
bool ThumbnailContext::IsLoading(SharedThumbnailKey key)
{
SharedThumbnail thumbnail;
for (auto& provider : m_providers)
{
if (provider->GetThumbnail(key, thumbnail))
{
return thumbnail->GetState() == Thumbnail::State::Unloaded ||
thumbnail->GetState() == Thumbnail::State::Loading;
}
}
return false;
}
void ThumbnailContext::RedrawThumbnail()
{
AzToolsFramework::AssetBrowser::AssetBrowserViewRequestBus::Broadcast(&AzToolsFramework::AssetBrowser::AssetBrowserViewRequests::Update);
}
QThreadPool* ThumbnailContext::GetThreadPool()
{
return &m_threadPool;
}
SharedThumbnail ThumbnailContext::GetThumbnail(SharedThumbnailKey key)
{
SharedThumbnail thumbnail;
// find provider who can handle supplied key
for (auto& provider : m_providers)
{
if (provider->GetThumbnail(key, thumbnail))
{
// if thumbnail is ready return it
if (thumbnail->GetState() == Thumbnail::State::Ready)
{
return thumbnail;
}
// if thumbnail is not loaded, start loading it, meanwhile return loading thumbnail
if (thumbnail->GetState() == Thumbnail::State::Unloaded)
{
// listen to the loading signal, so the anyone using it will update loading animation
connect(m_loadingThumbnail.data(), &Thumbnail::Updated, key.data(), &ThumbnailKey::ThumbnailUpdatedSignal);
AzQtComponents::StyledBusyLabel* busyLabel;
AzToolsFramework::AssetBrowser::AssetBrowserComponentRequestBus::BroadcastResult(busyLabel, &AzToolsFramework::AssetBrowser::AssetBrowserComponentRequests::GetStyledBusyLabel);
connect(busyLabel, &AzQtComponents::StyledBusyLabel::repaintNeeded, this, &ThumbnailContext::RedrawThumbnail);
// once the thumbnail is loaded, disconnect it from loading thumbnail
connect(thumbnail.data(), &Thumbnail::Updated, this , [this, key, thumbnail, busyLabel]()
{
disconnect(m_loadingThumbnail.data(), &Thumbnail::Updated, key.data(), &ThumbnailKey::ThumbnailUpdatedSignal);
disconnect(busyLabel, &AzQtComponents::StyledBusyLabel::repaintNeeded, this, &ThumbnailContext::RedrawThumbnail);
thumbnail->disconnect();
connect(thumbnail.data(), &Thumbnail::Updated, key.data(), &ThumbnailKey::ThumbnailUpdatedSignal);
connect(key.data(), &ThumbnailKey::UpdateThumbnailSignal, thumbnail.data(), &Thumbnail::Update);
key->m_ready = true;
Q_EMIT key->ThumbnailUpdatedSignal();
});
thumbnail->Load();
}
if (thumbnail->GetState() == Thumbnail::State::Failed)
{
return m_missingThumbnail;
}
return m_loadingThumbnail;
}
}
return m_missingThumbnail;
}
void ThumbnailContext::RegisterThumbnailProvider(SharedThumbnailProvider providerToAdd)
{
auto it = AZStd::find_if(m_providers.begin(), m_providers.end(), [providerToAdd](const SharedThumbnailProvider& provider)
{
return AZ::StringFunc::Equal(provider->GetProviderName(), providerToAdd->GetProviderName());
});
if (it != m_providers.end())
{
AZ_Error("ThumbnailContext", false, "Provider with name %s is already registered with context.", providerToAdd->GetProviderName());
return;
}
m_providers.insert(providerToAdd);
}
void ThumbnailContext::UnregisterThumbnailProvider(const char* providerName)
{
auto it = AZStd::remove_if(m_providers.begin(), m_providers.end(), [providerName](const SharedThumbnailProvider& provider)
{
return AZ::StringFunc::Equal(provider->GetProviderName(), providerName);
});
m_providers.erase(it, m_providers.end());
}
} // namespace Thumbnailer
} // namespace AzToolsFramework
#include "Thumbnails/moc_ThumbnailContext.cpp"
@@ -1,86 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/containers/set.h>
#include <AzToolsFramework/Thumbnails/Thumbnail.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerBus.h>
#include <QObject>
#include <QList>
#include <QThreadPool>
#endif
class QString;
class QPixmap;
namespace AzToolsFramework
{
namespace Thumbnailer
{
class ThumbnailProvider;
//! ThumbnailContext provides distinct thumbnail location for specific context
/*
There can be any number of contexts for every unique feature that may need different types of thumbnails.
For example 'AssetBrowser' context provides thumbnails specific to Asset Browser
'PreviewContext' may provide thumbnails for Preview Widget
'MaterialBrowser' may provide thumbnails for Material Browser
etc.
*/
class ThumbnailContext
: public QObject
, public ThumbnailContextRequestBus::Handler
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(ThumbnailContext, AZ::SystemAllocator, 0);
ThumbnailContext();
~ThumbnailContext() override;
//! Is the thumbnail currently loading or is about to load.
bool IsLoading(SharedThumbnailKey key);
//! Retrieve thumbnail by key, generate one if needed
SharedThumbnail GetThumbnail(SharedThumbnailKey key);
//! Add new thumbnail cache
void RegisterThumbnailProvider(SharedThumbnailProvider providerToAdd);
//! Remove thumbnail cache by name if found
void UnregisterThumbnailProvider(const char* providerName);
void RedrawThumbnail();
//! Default context used for most thumbnails
static constexpr const char* DefaultContext = "Default";
// ThumbnailContextRequestBus::Handler interface overrides...
QThreadPool* GetThreadPool() override;
private:
struct ProviderCompare {
bool operator() (const SharedThumbnailProvider& lhs, const SharedThumbnailProvider& rhs) const
{
// sorting in reverse, higher priority means the provider should be considered first
return lhs->GetPriority() > rhs->GetPriority();
}
};
//! Collection of thumbnail caches provided by this context
AZStd::multiset<SharedThumbnailProvider, ProviderCompare> m_providers;
//! Default missing thumbnail used when no thumbnail for given key can be found within this context
SharedThumbnail m_missingThumbnail;
//! Default loading thumbnail used when thumbnail is found by is not yet generated
SharedThumbnail m_loadingThumbnail;
//! There is only a limited number of threads on global threadPool, because there can be many thumbnails rendering at once
//! an individual threadPool is needed to avoid deadlocks
QThreadPool m_threadPool;
};
} // namespace Thumbnailer
} // namespace AzToolsFramework
@@ -30,14 +30,13 @@ namespace AzToolsFramework
{
}
void ThumbnailWidget::SetThumbnailKey(SharedThumbnailKey key, const char* contextName)
void ThumbnailWidget::SetThumbnailKey(SharedThumbnailKey key)
{
if (m_key)
{
disconnect(m_key.data(), &ThumbnailKey::ThumbnailUpdatedSignal, this, &ThumbnailWidget::KeyUpdatedSlot);
}
m_key = key;
m_contextName = contextName;
connect(m_key.data(), &ThumbnailKey::ThumbnailUpdatedSignal, this, &ThumbnailWidget::KeyUpdatedSlot);
repaint();
}
@@ -65,7 +64,7 @@ namespace AzToolsFramework
{
// thumbnail instance is not stored locally, but retrieved each paintEvent since thumbnail mapped to a specific key may change
SharedThumbnail thumbnail;
ThumbnailerRequestsBus::BroadcastResult(thumbnail, &ThumbnailerRequests::GetThumbnail, m_key, m_contextName.c_str());
ThumbnailerRequestBus::BroadcastResult(thumbnail, &ThumbnailerRequests::GetThumbnail, m_key);
QPainter painter(this);
// Scaling and centering pixmap within bounds to preserve aspect ratio
@@ -32,7 +32,7 @@ namespace AzToolsFramework
~ThumbnailWidget() override = default;
//! Call this to set what thumbnail widget will display
void SetThumbnailKey(SharedThumbnailKey key, const char* contextName = "Default");
void SetThumbnailKey(SharedThumbnailKey key);
//! Remove current thumbnail
void ClearThumbnail();
@@ -44,7 +44,6 @@ namespace AzToolsFramework
private:
SharedThumbnailKey m_key;
AZStd::string m_contextName;
private Q_SLOTS:
void KeyUpdatedSlot();
@@ -19,48 +19,30 @@ namespace AzToolsFramework
{
namespace Thumbnailer
{
//! Interaction with thumbnail context
class ThumbnailContextRequests
: public AZ::EBusTraits
{
public:
//! Get thread pool for drawing thumbnails
virtual QThreadPool* GetThreadPool() = 0;
};
using ThumbnailContextRequestBus = AZ::EBus<ThumbnailContextRequests>;
//! Interaction with thumbnailer
class ThumbnailerRequests
: public AZ::EBusTraits
{
public:
//! Add thumbnail context
virtual void RegisterContext(const char* contextName) = 0;
//! Add new thumbnail provider
virtual void RegisterThumbnailProvider(SharedThumbnailProvider provider) = 0;
//! Remove thumbnail context and all associated ThumbnailProviders
virtual void UnregisterContext(const char* contextName) = 0;
//! Return whether a given ThumbnailContext has been registered
virtual bool HasContext(const char* contextName) const = 0;
//! Add new thumbnail provider to ThumbnailContext
virtual void RegisterThumbnailProvider(SharedThumbnailProvider provider, const char* contextName) = 0;
//! Remove thumbnail provider from ThumbnailContext
virtual void UnregisterThumbnailProvider(const char* providerName, const char* contextName) = 0;
//! Remove thumbnail provider
virtual void UnregisterThumbnailProvider(const char* providerName) = 0;
//! Retrieve thumbnail by key,
//! if no thumbnail matching found, one of ThumbnailProviders will attempt to create
//! If no compatible providers found, MissingThumbnail will be returned
virtual SharedThumbnail GetThumbnail(SharedThumbnailKey thumbnailKey, const char* contextName) = 0;
virtual SharedThumbnail GetThumbnail(SharedThumbnailKey thumbnailKey) = 0;
//! Return whether the thumbnail is loading.
virtual bool IsLoading(SharedThumbnailKey thumbnailKey, const char* contextName) = 0;
virtual bool IsLoading(SharedThumbnailKey thumbnailKey) = 0;
//! Get thread pool for drawing thumbnails
virtual QThreadPool* GetThreadPool() = 0;
};
using ThumbnailerRequestBus = AZ::EBus<ThumbnailerRequests>;
using ThumbnailerRequestsBus = AZ::EBus<ThumbnailerRequests>; //deprecated
//! Request product thumbnail to be rendered
class ThumbnailerRendererRequests
@@ -6,11 +6,14 @@
*
*/
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzQtComponents/Components/StyledBusyLabel.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/Thumbnails/LoadingThumbnail.h>
#include <AzToolsFramework/Thumbnails/MissingThumbnail.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerComponent.h>
#include <AzToolsFramework/Thumbnails/ThumbnailContext.h>
#include <QApplication>
#include <QStyle>
@@ -19,8 +22,10 @@ namespace AzToolsFramework
{
namespace Thumbnailer
{
ThumbnailerComponent::ThumbnailerComponent()
: m_missingThumbnail(new MissingThumbnail())
, m_loadingThumbnail(new LoadingThumbnail())
, m_threadPool(this)
{
}
@@ -28,14 +33,13 @@ namespace AzToolsFramework
void ThumbnailerComponent::Activate()
{
RegisterContext(ThumbnailContext::DefaultContext);
BusConnect();
ThumbnailerRequestBus::Handler::BusConnect();
}
void ThumbnailerComponent::Deactivate()
{
BusDisconnect();
m_thumbnails.clear();
ThumbnailerRequestBus::Handler::BusDisconnect();
m_providers.clear();
}
void ThumbnailerComponent::Reflect(AZ::ReflectContext* context)
@@ -49,59 +53,114 @@ namespace AzToolsFramework
void ThumbnailerComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("ThumbnailerService", 0x65422b97));
incompatible.push_back(AZ_CRC_CE("ThumbnailerService"));
}
void ThumbnailerComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("ThumbnailerService", 0x65422b97));
provided.push_back(AZ_CRC_CE("ThumbnailerService"));
}
void ThumbnailerComponent::RegisterContext(const char* contextName)
void ThumbnailerComponent::RegisterThumbnailProvider(SharedThumbnailProvider provider)
{
AZ_Assert(m_thumbnails.find(contextName) == m_thumbnails.end(), "Context %s already registered", contextName);
m_thumbnails[contextName] = AZStd::make_shared<ThumbnailContext>();
auto it = AZStd::find_if(m_providers.begin(), m_providers.end(), [provider](const SharedThumbnailProvider& existingProvider)
{
return AZ::StringFunc::Equal(provider->GetProviderName(), existingProvider->GetProviderName());
});
if (it != m_providers.end())
{
AZ_Error("ThumbnailerComponent", false, "Provider with name %s is already registered with context.", provider->GetProviderName());
return;
}
m_providers.insert(provider);
}
void ThumbnailerComponent::UnregisterContext(const char* contextName)
void ThumbnailerComponent::UnregisterThumbnailProvider(const char* providerName)
{
AZ_Assert(m_thumbnails.find(contextName) != m_thumbnails.end(), "Context %s not registered", contextName);
m_thumbnails.erase(contextName);
AZStd::erase_if(
m_providers,
[providerName](const SharedThumbnailProvider& provider)
{
return AZ::StringFunc::Equal(provider->GetProviderName(), providerName);
});
}
bool ThumbnailerComponent::HasContext(const char* contextName) const
SharedThumbnail ThumbnailerComponent::GetThumbnail(SharedThumbnailKey key)
{
return m_thumbnails.find(contextName) != m_thumbnails.end();
// find provider who can handle supplied key
for (auto& provider : m_providers)
{
SharedThumbnail thumbnail;
if (provider->GetThumbnail(key, thumbnail))
{
// if thumbnail is ready return it
if (thumbnail->GetState() == Thumbnail::State::Ready)
{
return thumbnail;
}
// if thumbnail is not loaded, start loading it, meanwhile return loading thumbnail
if (thumbnail->GetState() == Thumbnail::State::Unloaded)
{
// listen to the loading signal, so the anyone using it will update loading animation
AzQtComponents::StyledBusyLabel* busyLabel;
AssetBrowser::AssetBrowserComponentRequestBus::BroadcastResult(busyLabel, &AssetBrowser::AssetBrowserComponentRequests::GetStyledBusyLabel);
QObject::connect(m_loadingThumbnail.data(), &Thumbnail::Updated, key.data(), &ThumbnailKey::ThumbnailUpdatedSignal);
QObject::connect(busyLabel, &AzQtComponents::StyledBusyLabel::repaintNeeded, this, &ThumbnailerComponent::RedrawThumbnail);
// once the thumbnail is loaded, disconnect it from loading thumbnail
QObject::connect(thumbnail.data(), &Thumbnail::Updated, this , [this, key, thumbnail, busyLabel]()
{
QObject::disconnect(m_loadingThumbnail.data(), &Thumbnail::Updated, key.data(), &ThumbnailKey::ThumbnailUpdatedSignal);
QObject::disconnect(busyLabel, &AzQtComponents::StyledBusyLabel::repaintNeeded, this, &ThumbnailerComponent::RedrawThumbnail);
thumbnail->disconnect();
QObject::connect(thumbnail.data(), &Thumbnail::Updated, key.data(), &ThumbnailKey::ThumbnailUpdatedSignal);
QObject::connect(key.data(), &ThumbnailKey::UpdateThumbnailSignal, thumbnail.data(), &Thumbnail::Update);
key->SetReady(true);
Q_EMIT key->ThumbnailUpdatedSignal();
});
thumbnail->Load();
}
if (thumbnail->GetState() == Thumbnail::State::Failed)
{
return m_missingThumbnail;
}
return m_loadingThumbnail;
}
}
return m_missingThumbnail;
}
void ThumbnailerComponent::RegisterThumbnailProvider(SharedThumbnailProvider provider, const char* contextName)
bool ThumbnailerComponent::IsLoading(SharedThumbnailKey key)
{
auto it = m_thumbnails.find(contextName);
AZ_Assert(it != m_thumbnails.end(), "Context %s not registered", contextName);
it->second->RegisterThumbnailProvider(provider);
for (auto& provider : m_providers)
{
SharedThumbnail thumbnail;
if (provider->GetThumbnail(key, thumbnail))
{
return thumbnail->GetState() == Thumbnail::State::Unloaded || thumbnail->GetState() == Thumbnail::State::Loading;
}
}
return false;
}
void ThumbnailerComponent::UnregisterThumbnailProvider(const char* providerName, const char* contextName)
QThreadPool* ThumbnailerComponent::GetThreadPool()
{
auto it = m_thumbnails.find(contextName);
AZ_Assert(it != m_thumbnails.end(), "Context %s not registered", contextName);
it->second->UnregisterThumbnailProvider(providerName);
return &m_threadPool;
}
SharedThumbnail ThumbnailerComponent::GetThumbnail(SharedThumbnailKey key, const char* contextName)
void ThumbnailerComponent::RedrawThumbnail()
{
auto it = m_thumbnails.find(contextName);
AZ_Assert(it != m_thumbnails.end(), "Context %s not registered", contextName);
return it->second->GetThumbnail(key);
AssetBrowser::AssetBrowserViewRequestBus::Broadcast(&AssetBrowser::AssetBrowserViewRequests::Update);
}
bool ThumbnailerComponent::IsLoading(SharedThumbnailKey key, const char* contextName)
{
auto it = m_thumbnails.find(contextName);
AZ_Assert(it != m_thumbnails.end(), "Context %s not registered", contextName);
return it->second->IsLoading(key);
}
} // namespace Thumbnailer
} // namespace AzToolsFramework
@@ -7,19 +7,29 @@
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#if !defined(Q_MOC_RUN)
#include <AzCore/Component/Component.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/containers/set.h>
#include <AzToolsFramework/Thumbnails/Thumbnail.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerBus.h>
#include <QList>
#include <QObject>
#include <QThreadPool>
#endif
class QString;
class QPixmap;
namespace AzToolsFramework
{
namespace Thumbnailer
{
class ThumbnailContext;
class ThumbnailerComponent
: public AZ::Component
, public ThumbnailerRequestsBus::Handler
, public ThumbnailerRequestBus::Handler
, public QObject
{
public:
AZ_COMPONENT(ThumbnailerComponent, "{80090CA5-6A3A-4554-B5FE-A6D74ECB2D84}")
@@ -27,28 +37,41 @@ namespace AzToolsFramework
ThumbnailerComponent();
virtual ~ThumbnailerComponent();
//////////////////////////////////////////////////////////////////////////
// AZ::Component
//////////////////////////////////////////////////////////////////////////
// AZ::Component overrides...
void Activate() override;
void Deactivate() override;
static void Reflect(AZ::ReflectContext* context);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
//////////////////////////////////////////////////////////////////////////
// ThumbnailerRequests
//////////////////////////////////////////////////////////////////////////
void RegisterContext(const char* contextName) override;
void UnregisterContext(const char* contextName) override;
bool HasContext(const char* contextName) const override;
void RegisterThumbnailProvider(SharedThumbnailProvider provider, const char* contextName) override;
void UnregisterThumbnailProvider(const char* providerName, const char* contextName) override;
SharedThumbnail GetThumbnail(SharedThumbnailKey thumbnailKey, const char* contextName) override;
bool IsLoading(SharedThumbnailKey thumbnailKey, const char* contextName) override;
// ThumbnailerRequestBus::Handler interface overrides...
void RegisterThumbnailProvider(SharedThumbnailProvider provider) override;
void UnregisterThumbnailProvider(const char* providerName) override;
SharedThumbnail GetThumbnail(SharedThumbnailKey thumbnailKey) override;
bool IsLoading(SharedThumbnailKey thumbnailKey) override;
QThreadPool* GetThreadPool() override;
void RedrawThumbnail();
private:
AZStd::unordered_map<AZStd::string, AZStd::shared_ptr<ThumbnailContext>> m_thumbnails;
struct ProviderCompare
{
bool operator()(const SharedThumbnailProvider& lhs, const SharedThumbnailProvider& rhs) const
{
// sorting in reverse, higher priority means the provider should be considered first
return lhs->GetPriority() > rhs->GetPriority();
}
};
//! Collection of thumbnail caches provided by this context
AZStd::multiset<SharedThumbnailProvider, ProviderCompare> m_providers;
//! Default missing thumbnail used when no thumbnail for given key can be found within this context
SharedThumbnail m_missingThumbnail;
//! Default loading thumbnail used when thumbnail is found by is not yet generated
SharedThumbnail m_loadingThumbnail;
//! There is only a limited number of threads on global threadPool, because there can be many thumbnails rendering at once
//! an individual threadPool is needed to avoid deadlocks
QThreadPool m_threadPool;
};
} // Thumbnailer
} // namespace AssetBrowser
@@ -9,7 +9,6 @@
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerNullComponent.h>
#include <AzToolsFramework/Thumbnails/ThumbnailContext.h>
#include <AzToolsFramework/Thumbnails/MissingThumbnail.h>
namespace AzToolsFramework
@@ -47,35 +46,27 @@ namespace AzToolsFramework
services.push_back(AZ_CRC("ThumbnailerService", 0x65422b97));
}
void ThumbnailerNullComponent::RegisterContext(const char* /*contextName*/)
void ThumbnailerNullComponent::RegisterThumbnailProvider(AzToolsFramework::Thumbnailer::SharedThumbnailProvider /*provider*/)
{
}
void ThumbnailerNullComponent::UnregisterContext(const char* /*contextName*/)
void ThumbnailerNullComponent::UnregisterThumbnailProvider(const char* /*providerName*/)
{
}
bool ThumbnailerNullComponent::HasContext(const char* /*contextName*/) const
{
return false;
}
void ThumbnailerNullComponent::RegisterThumbnailProvider(AzToolsFramework::Thumbnailer::SharedThumbnailProvider /*provider*/, const char* /*contextName*/)
{
}
void ThumbnailerNullComponent::UnregisterThumbnailProvider(const char* /*providerName*/, const char* /*contextName*/)
{
}
AzToolsFramework::Thumbnailer::SharedThumbnail ThumbnailerNullComponent::GetThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey /*key*/, const char* /*contextName*/)
AzToolsFramework::Thumbnailer::SharedThumbnail ThumbnailerNullComponent::GetThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey /*key*/)
{
return m_nullThumbnail;
}
bool ThumbnailerNullComponent::IsLoading(AzToolsFramework::Thumbnailer::SharedThumbnailKey /*thumbnailKey*/, const char* /*contextName*/)
bool ThumbnailerNullComponent::IsLoading(AzToolsFramework::Thumbnailer::SharedThumbnailKey /*thumbnailKey*/)
{
return false;
}
QThreadPool* ThumbnailerNullComponent::GetThreadPool()
{
return nullptr;
}
} // namespace Thumbnailer
} // namespace AzToolsFramework
@@ -19,11 +19,9 @@ namespace AzToolsFramework
{
namespace Thumbnailer
{
class ThumbnailContext;
class ThumbnailerNullComponent
: public AZ::Component
, public AzToolsFramework::Thumbnailer::ThumbnailerRequestsBus::Handler
, public AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::Handler
{
public:
AZ_COMPONENT(ThumbnailerNullComponent, "{8009D651-3FAA-9815-B99E-AF174A3B29D4}")
@@ -42,13 +40,12 @@ namespace AzToolsFramework
//////////////////////////////////////////////////////////////////////////
// ThumbnailerRequests
//////////////////////////////////////////////////////////////////////////
void RegisterContext(const char* contextName) override;
void UnregisterContext(const char* contextName) override;
bool HasContext(const char* contextName) const override;
void RegisterThumbnailProvider(AzToolsFramework::Thumbnailer::SharedThumbnailProvider provider, const char* contextName) override;
void UnregisterThumbnailProvider(const char* providerName, const char* contextName) override;
AzToolsFramework::Thumbnailer::SharedThumbnail GetThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey thumbnailKey, const char* contextName) override;
bool IsLoading(AzToolsFramework::Thumbnailer::SharedThumbnailKey thumbnailKey, const char* contextName) override;
void RegisterThumbnailProvider(AzToolsFramework::Thumbnailer::SharedThumbnailProvider provider) override;
void UnregisterThumbnailProvider(const char* providerName) override;
AzToolsFramework::Thumbnailer::SharedThumbnail GetThumbnail(AzToolsFramework::Thumbnailer::SharedThumbnailKey thumbnailKey) override;
bool IsLoading(AzToolsFramework::Thumbnailer::SharedThumbnailKey thumbnailKey) override;
QThreadPool* GetThreadPool() override;
private:
AzToolsFramework::Thumbnailer::SharedThumbnail m_nullThumbnail;
};
@@ -7,6 +7,7 @@
*/
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.h>
#include <AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx>
#include <AzCore/Interface/Interface.h>
@@ -117,9 +118,16 @@ namespace AzToolsFramework
{
}
bool EditorEntityUiHandlerBase::OnEntityDoubleClick([[maybe_unused]] AZ::EntityId entityId) const
bool EditorEntityUiHandlerBase::OnOutlinerItemDoubleClick([[maybe_unused]] const QModelIndex& index) const
{
return false;
}
AZ::EntityId EditorEntityUiHandlerBase::GetEntityIdFromIndex(const QModelIndex& index)
{
QModelIndex firstColumnIndex = index.siblingAtColumn(EntityOutlinerListModel::ColumnName);
return AZ::EntityId(firstColumnIndex.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
}
} // namespace AzToolsFramework
@@ -21,7 +21,6 @@ class QTreeView;
namespace AzToolsFramework
{
//! Defines a handler that can customize entity UI appearance and behavior in the Entity Outliner.
//! This class is meant to be abstract, entities do not have a handler by default.
class EditorEntityUiHandlerBase
{
protected:
@@ -33,7 +32,7 @@ namespace AzToolsFramework
public:
EditorEntityUiHandlerId GetHandlerId();
// # Entity Outliner
// # Entity Outliner Item
//! Returns the item info string that is appended to the item name in the Outliner.
virtual QString GenerateItemInfoString(AZ::EntityId entityId) const;
@@ -41,10 +40,12 @@ namespace AzToolsFramework
virtual QString GenerateItemTooltip(AZ::EntityId entityId) const;
//! Returns the item icon pixmap to display in the Outliner.
virtual QIcon GenerateItemIcon(AZ::EntityId entityId) const;
//! Returns whether the element's lock and visibility state should be accessible in the Outliner
virtual bool CanToggleLockVisibility(AZ::EntityId entityId) const;
//! Returns whether the element's name should be editable
virtual bool CanRename(AZ::EntityId entityId) const;
//! Returns whether the element's lock and visibility state should be accessible in the Outliner
virtual bool CanToggleLockVisibility(AZ::EntityId entityId) const;
// Qt-specific painting functions
//! Paints the background of the item in the Outliner.
virtual void PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
@@ -54,24 +55,27 @@ namespace AzToolsFramework
//! Paints the background of the descendant branches of the item in the Outliner.
virtual void PaintDescendantBranchBackground(QPainter* painter, const QTreeView* view, const QRect& rect,
const QModelIndex& index, const QModelIndex& descendantIndex) const;
//! Paints visual elements on the foreground of the item in the Outliner.
virtual void PaintItemForeground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
//! Paints visual elements on the foreground of the descendants of the item in the Outliner.
virtual void PaintDescendantForeground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index,
const QModelIndex& descendantIndex) const;
// Outliner-specific interactions
//! 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 the entity is double-clicked in the Outliner.
//! @return True if the double-click has been handled and should not be propagated, false otherwise.
virtual bool OnOutlinerItemDoubleClick(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;
protected:
static AZ::EntityId GetEntityIdFromIndex(const QModelIndex& index);
private:
EditorEntityUiHandlerId m_handlerId = 0;
@@ -2091,8 +2091,14 @@ namespace AzToolsFramework
customOption.state ^= QStyle::State_HasFocus;
}
// Don't allow to paint on the spacing column
if (index.column() == EntityOutlinerListModel::ColumnSpacing)
{
return;
}
// Retrieve the Entity UI Handler
auto firstColumnIndex = index.siblingAtColumn(0);
auto firstColumnIndex = index.siblingAtColumn(EntityOutlinerListModel::ColumnName);
AZ::EntityId entityId(firstColumnIndex.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
auto entityUiHandler = m_editorEntityFrameworkInterface->GetHandler(entityId);
@@ -69,6 +69,7 @@ namespace AzToolsFramework
ColumnName, //!< Entity name
ColumnVisibilityToggle, //!< Visibility Icons
ColumnLockToggle, //!< Lock Icons
ColumnSpacing, //!< Spacing to allow for drag select
ColumnSortIndex, //!< Index of sort order
ColumnCount //!< Total number of columns
};
@@ -30,7 +30,6 @@ namespace AzToolsFramework
EntityOutlinerTreeView::EntityOutlinerTreeView(QWidget* pParent)
: AzQtComponents::StyledTreeView(pParent)
, m_queuedMouseEvent(nullptr)
, m_draggingUnselectedItem(false)
{
setUniformRowHeights(true);
setHeaderHidden(true);
@@ -65,22 +64,6 @@ namespace AzToolsFramework
m_expandOnlyDelay = delay;
}
void EntityOutlinerTreeView::ClearQueuedMouseEvent()
{
if (m_queuedMouseEvent)
{
delete m_queuedMouseEvent;
m_queuedMouseEvent = nullptr;
}
}
void EntityOutlinerTreeView::leaveEvent([[maybe_unused]] QEvent* event)
{
m_mousePosition = QPoint(-1, -1);
m_currentHoveredIndex = QModelIndex();
update();
}
void EntityOutlinerTreeView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector<int>& roles)
{
AzQtComponents::StyledTreeView::dataChanged(topLeft, bottomRight, roles);
@@ -93,7 +76,7 @@ namespace AzToolsFramework
auto modelRow = topLeft.sibling(i, EntityOutlinerListModel::ColumnName);
if (modelRow.isValid())
{
checkExpandedState(modelRow);
CheckExpandedState(modelRow);
}
}
}
@@ -108,15 +91,15 @@ namespace AzToolsFramework
auto modelRow = model()->index(i, EntityOutlinerListModel::ColumnName, parent);
if (modelRow.isValid())
{
checkExpandedState(modelRow);
recursiveCheckExpandedStates(modelRow);
CheckExpandedState(modelRow);
RecursiveCheckExpandedStates(modelRow);
}
}
}
AzQtComponents::StyledTreeView::rowsInserted(parent, start, end);
}
void EntityOutlinerTreeView::recursiveCheckExpandedStates(const QModelIndex& current)
void EntityOutlinerTreeView::RecursiveCheckExpandedStates(const QModelIndex& current)
{
const int rowCount = model()->rowCount(current);
for (int i = 0; i < rowCount; i++)
@@ -124,13 +107,13 @@ namespace AzToolsFramework
auto modelRow = model()->index(i, EntityOutlinerListModel::ColumnName, current);
if (modelRow.isValid())
{
checkExpandedState(modelRow);
recursiveCheckExpandedStates(modelRow);
CheckExpandedState(modelRow);
RecursiveCheckExpandedStates(modelRow);
}
}
}
void EntityOutlinerTreeView::checkExpandedState(const QModelIndex& current)
void EntityOutlinerTreeView::CheckExpandedState(const QModelIndex& current)
{
const bool expandState = current.data(EntityOutlinerListModel::ExpandedRole).template value<bool>();
setExpanded(current, expandState);
@@ -138,116 +121,102 @@ namespace AzToolsFramework
void EntityOutlinerTreeView::mousePressEvent(QMouseEvent* event)
{
//postponing normal mouse pressed logic until mouse is released or dragged
//this means selection occurs on mouse released now
//this is to support drag/drop of non-selected items
// Postponing normal mouse press logic until mouse is released or dragged.
// This allows drag/drop of non-selected items.
ClearQueuedMouseEvent();
m_queuedMouseEvent = new QMouseEvent(*event);
}
void EntityOutlinerTreeView::mouseMoveEvent(QMouseEvent* event)
{
// Prevent multiple updates throughout the function for changing UIs.
bool forceUpdate = false;
QModelIndex previousHoveredIndex = m_currentHoveredIndex;
m_mousePosition = event->pos();
if (QModelIndex hoveredIndex = indexAt(m_mousePosition);
m_currentHoveredIndex != hoveredIndex)
{
m_currentHoveredIndex = hoveredIndex;
}
if (m_queuedMouseEvent)
{
if (!m_isDragSelectActive)
{
// Determine whether the mouse move should trigger a rect selection or an entity drag.
QModelIndex clickedIndex = indexAt(m_queuedMouseEvent->pos());
// Even though the drag started on an index, we want to trigger a drag select from the last column.
// This is to allow drag selection to be triggered from anywhere in the hierarchy.
if (clickedIndex.isValid() && clickedIndex.column() != EntityOutlinerListModel::ColumnSpacing)
{
HandleDrag();
}
else
{
m_isDragSelectActive = true;
forceUpdate = true;
}
}
else
{
SelectAllEntitiesInSelectionRect();
forceUpdate = true;
}
}
if (previousHoveredIndex != m_currentHoveredIndex)
{
forceUpdate = true;
}
if (forceUpdate)
{
update();
}
}
void EntityOutlinerTreeView::mouseReleaseEvent(QMouseEvent* event)
{
if (m_queuedMouseEvent && !m_draggingUnselectedItem)
if (m_isDragSelectActive)
{
// mouseMoveEvent will set the state to be DraggingState, which will make Qt ignore
// mousePressEvent in QTreeViewPrivate::expandOrCollapseItemAtPos. So we manually
// and temporarily set it to EditingState.
QAbstractItemView::State stateBefore = QAbstractItemView::state();
QAbstractItemView::setState(QAbstractItemView::State::EditingState);
//treat this as a mouse pressed event to process selection etc
processQueuedMousePressedEvent(m_queuedMouseEvent);
QAbstractItemView::setState(stateBefore);
SelectAllEntitiesInSelectionRect();
update();
}
else if (m_queuedMouseEvent)
{
ProcessQueuedMousePressedEvent(m_queuedMouseEvent);
}
ClearQueuedMouseEvent();
m_draggingUnselectedItem = false;
m_isDragSelectActive = false;
QTreeView::mouseReleaseEvent(event);
}
void EntityOutlinerTreeView::mouseDoubleClickEvent(QMouseEvent* event)
{
//cancel pending mouse press
// Cancel pending mouse press.
ClearQueuedMouseEvent();
QTreeView::mouseDoubleClickEvent(event);
}
void EntityOutlinerTreeView::mouseMoveEvent(QMouseEvent* event)
{
if (m_queuedMouseEvent)
{
//disable selection for the pending click if the mouse moved so selection is maintained for dragging
QAbstractItemView::SelectionMode selectionModeBefore = selectionMode();
setSelectionMode(QAbstractItemView::NoSelection);
//treat this as a mouse pressed event to process everything but selection, but use the position data from the mousePress message
processQueuedMousePressedEvent(m_queuedMouseEvent);
//restore selection state
setSelectionMode(selectionModeBefore);
}
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);
}
void EntityOutlinerTreeView::focusInEvent(QFocusEvent* event)
{
//cancel pending mouse press
// Cancel pending mouse press.
ClearQueuedMouseEvent();
QTreeView::focusInEvent(event);
}
void EntityOutlinerTreeView::focusOutEvent(QFocusEvent* event)
{
//cancel pending mouse press
// Cancel pending mouse press.
ClearQueuedMouseEvent();
QTreeView::focusOutEvent(event);
}
void EntityOutlinerTreeView::startDrag(Qt::DropActions supportedActions)
{
QModelIndex index = indexAt(m_queuedMouseEvent->pos());
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
AZ::EntityId parentEntityId;
EditorEntityInfoRequestBus::EventResult(parentEntityId, entityId, &EditorEntityInfoRequestBus::Events::GetParent);
// If the entity is parented to a read-only entity, cancel the drag operation.
if (m_readOnlyEntityPublicInterface->IsReadOnly(parentEntityId))
{
return;
}
//if we are attempting to drag an unselected item then we must special case drag and drop logic
//QAbstractItemView::startDrag only supports selected items
if (m_queuedMouseEvent)
{
if (!index.isValid() || index.column() != 0)
{
return;
}
if (!selectionModel()->isSelected(index))
{
StartCustomDrag({ index }, supportedActions);
return;
}
}
StyledTreeView::startDrag(supportedActions);
}
void EntityOutlinerTreeView::dragMoveEvent(QDragMoveEvent* event)
void EntityOutlinerTreeView::dragMoveEvent([[maybe_unused]] QDragMoveEvent* event)
{
if (m_expandOnlyDelay >= 0)
{
@@ -257,12 +226,139 @@ namespace AzToolsFramework
QTreeView::dragMoveEvent(event);
}
void EntityOutlinerTreeView::dropEvent(QDropEvent* event)
void EntityOutlinerTreeView::dropEvent([[maybe_unused]] QDropEvent* event)
{
emit ItemDropped();
QTreeView::dropEvent(event);
m_draggingUnselectedItem = false;
ClearQueuedMouseEvent();
}
void EntityOutlinerTreeView::HandleDrag()
{
// Retrieve the index at the click position.
QModelIndex indexAtClick = indexAt(m_queuedMouseEvent->pos()).siblingAtColumn(EntityOutlinerListModel::ColumnName);
AZ::EntityId entityId(indexAtClick.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
AZ::EntityId parentEntityId;
EditorEntityInfoRequestBus::EventResult(parentEntityId, entityId, &EditorEntityInfoRequestBus::Events::GetParent);
// If the entity is parented to a read-only entity, cancel the drag operation.
if (m_readOnlyEntityPublicInterface->IsReadOnly(parentEntityId))
{
return;
}
// If the index is selected, we should move the whole selection.
if (selectionModel()->isSelected(indexAtClick))
{
StartCustomDrag(selectionModel()->selectedIndexes(), defaultDropAction());
}
else
{
StartCustomDrag(QModelIndexList{ indexAtClick }, defaultDropAction());
}
}
void EntityOutlinerTreeView::SelectAllEntitiesInSelectionRect()
{
if (!m_queuedMouseEvent)
{
return;
}
// Retrieve the two opposing corners of the rect.
const QPoint point1 = (m_queuedMouseEvent->pos()); // The position the drag operation started at.
const QPoint point2 = (m_mousePosition); // The current mouse position.
// Determine which point's y is the top and which is the bottom.
const int top(AZStd::min(point1.y(), point2.y()));
const int bottom(AZStd::max(point1.y(), point2.y()));
// We don't really need the x values for the rect, just use the center of the viewport.
const int middle(viewport()->rect().center().x());
// Find the extremes of the range of indices that are in the selection rect.
QModelIndex topIndex = indexAt(QPoint(middle, top));
const QModelIndex bottomIndex = indexAt(QPoint(middle, bottom));
// If we have no top index, the mouse may have been dragged above the top item. Let's try to course correct.
const int topDistanceForFirstItem = 10; // A reasonable distance from the top we're sure to encounter the first item.
const QModelIndex firstIndex = indexAt(QPoint(middle, topDistanceForFirstItem));
if (!topIndex.isValid() && top < topDistanceForFirstItem)
{
topIndex = firstIndex;
}
// We can assume that if topIndex is still invalid, it was below the last item in the hierarchy, hence no selection is made.
if (!topIndex.isValid())
{
return;
}
QItemSelection selection;
// Starting from the top index, traverse all visible elements of the list and select them until the bottom index is hit.
// If the bottom index is undefined, just keep going to the end.
QModelIndex iter = topIndex;
selection.select(iter, iter);
while (iter.isValid() && iter != bottomIndex)
{
iter = indexBelow(iter);
selection.select(iter, iter);
}
selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
}
void EntityOutlinerTreeView::ClearQueuedMouseEvent()
{
if (m_queuedMouseEvent)
{
delete m_queuedMouseEvent;
m_queuedMouseEvent = nullptr;
}
}
void EntityOutlinerTreeView::leaveEvent([[maybe_unused]] QEvent* event)
{
ClearQueuedMouseEvent();
// Only clear the mouse position if the last mouse position registered is inside.
// This allows drag to select to work correctly in all situations.
if(this->viewport()->rect().contains(m_mousePosition))
{
m_mousePosition = QPoint(-1, -1);
}
m_currentHoveredIndex = QModelIndex();
update();
}
void EntityOutlinerTreeView::paintEvent(QPaintEvent* event)
{
AzQtComponents::StyledTreeView::paintEvent(event);
// Draw the drag selection rect.
if (m_isDragSelectActive && m_queuedMouseEvent)
{
// Create a painter to draw on the viewport.
QPainter painter(viewport());
// Retrieve the two corners of the rect.
const QPoint point1 = (m_queuedMouseEvent->pos()); // The position the drag operation started at.
const QPoint point2 = (m_mousePosition); // The current mouse position.
// We need the top left and bottom right corners, which may not be the two corners we got above.
// So we composite the corners based on the coordinates of the points.
const QPoint topLeft(AZStd::min(point1.x(), point2.x()), AZStd::min(point1.y(), point2.y()));
const QPoint bottomRight(AZStd::max(point1.x(), point2.x()), AZStd::max(point1.y(), point2.y()));
// Paint the rect.
painter.setBrush(m_dragSelectRectColor);
painter.setPen(m_dragSelectBorderColor);
painter.drawRect(QRect(topLeft, bottomRight));
}
}
void EntityOutlinerTreeView::drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const
@@ -270,7 +366,7 @@ namespace AzToolsFramework
const bool isEnabled = (this->model()->flags(index) & Qt::ItemIsEnabled);
const bool isSelected = selectionModel()->isSelected(index);
const bool isHovered = (index == indexAt(m_mousePosition).siblingAtColumn(0)) && isEnabled;
const bool isHovered = (index == m_currentHoveredIndex.siblingAtColumn(0)) && isEnabled;
// Paint the branch Selection/Hover Rect
PaintBranchSelectionHoverRect(painter, rect, isSelected, isHovered);
@@ -352,25 +448,27 @@ namespace AzToolsFramework
QTreeView::timerEvent(event);
}
void EntityOutlinerTreeView::processQueuedMousePressedEvent(QMouseEvent* event)
void EntityOutlinerTreeView::ProcessQueuedMousePressedEvent(QMouseEvent* event)
{
//interpret the mouse event as a button press
QMouseEvent mousePressedEvent(
QEvent::MouseButtonPress,
event->localPos(),
event->windowPos(),
event->screenPos(),
event->button(),
event->buttons(),
event->modifiers(),
event->source());
QTreeView::mousePressEvent(&mousePressedEvent);
QModelIndex clickedIndex = indexAt(m_queuedMouseEvent->pos());
if (!clickedIndex.isValid() || clickedIndex.column() != EntityOutlinerListModel::ColumnSpacing)
{
//interpret the mouse event as a button press
QMouseEvent mousePressedEvent(
QEvent::MouseButtonPress,
event->localPos(),
event->windowPos(),
event->screenPos(),
event->button(),
event->buttons(),
event->modifiers(),
event->source());
QTreeView::mousePressEvent(&mousePressedEvent);
}
}
void EntityOutlinerTreeView::StartCustomDrag(const QModelIndexList& indexList, Qt::DropActions supportedActions)
{
m_draggingUnselectedItem = true;
//sort by container entity depth and order in hierarchy for proper drag image and drop order
QModelIndexList indexListSorted = indexList;
AZStd::unordered_map<AZ::EntityId, AZStd::list<AZ::u64>> locations;
@@ -63,7 +63,6 @@ namespace AzToolsFramework
void mouseMoveEvent(QMouseEvent* event) override;
void focusInEvent(QFocusEvent* event) override;
void focusOutEvent(QFocusEvent* event) override;
void startDrag(Qt::DropActions supportedActions) override;
void dragMoveEvent(QDragMoveEvent* event) override;
void dropEvent(QDropEvent* event) override;
void leaveEvent(QEvent* event) override;
@@ -71,33 +70,39 @@ namespace AzToolsFramework
// FocusModeNotificationBus overrides ...
void OnEditorFocusChanged(AZ::EntityId previousFocusEntityId, AZ::EntityId newFocusEntityId) override;
void paintEvent(QPaintEvent* event) override;
//! Renders the left side of the item: appropriate background, branch lines, icons.
void drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const override;
void timerEvent(QTimerEvent* event) override;
private:
void ClearQueuedMouseEvent();
void ProcessQueuedMousePressedEvent(QMouseEvent* event);
void processQueuedMousePressedEvent(QMouseEvent* event);
void recursiveCheckExpandedStates(const QModelIndex& parent);
void checkExpandedState(const QModelIndex& current);
void SelectAllEntitiesInSelectionRect();
void HandleDrag();
void StartCustomDrag(const QModelIndexList& indexList, Qt::DropActions supportedActions) override;
void RecursiveCheckExpandedStates(const QModelIndex& parent);
void CheckExpandedState(const QModelIndex& current);
void PaintBranchBackground(QPainter* painter, const QRect& rect, const QModelIndex& index) const;
void PaintBranchSelectionHoverRect(QPainter* painter, const QRect& rect, bool isSelected, bool isHovered) const;
QMouseEvent* m_queuedMouseEvent;
QModelIndex m_currentHoveredIndex;
QPoint m_mousePosition;
bool m_draggingUnselectedItem; // This is set when an item is dragged outside its bounding box.
bool m_isDragSelectActive = false;
int m_expandOnlyDelay = -1;
QBasicTimer m_expandTimer;
const QColor m_selectedColor = QColor(255, 255, 255, 45);
const QColor m_hoverColor = QColor(255, 255, 255, 30);
QModelIndex m_currentHoveredIndex;
const QColor m_dragSelectRectColor = QColor(255, 255, 255, 20);
const QColor m_dragSelectBorderColor = QColor(255, 255, 255);
EditorEntityUiInterface* m_editorEntityFrameworkInterface = nullptr;
ReadOnlyEntityPublicInterface* m_readOnlyEntityPublicInterface = nullptr;
@@ -193,7 +193,6 @@ namespace AzToolsFramework
m_listModel->SetSortMode(m_sortMode);
const int autoExpandDelayMilliseconds = 2500;
m_gui->m_objectTree->setSelectionMode(QAbstractItemView::ExtendedSelection);
SetDefaultTreeViewEditTriggers();
m_gui->m_objectTree->setAutoExpandDelay(autoExpandDelayMilliseconds);
m_gui->m_objectTree->setDragEnabled(true);
@@ -208,6 +207,7 @@ namespace AzToolsFramework
m_gui->m_objectTree->setAutoScrollMargin(20);
m_gui->m_objectTree->setIndentation(24);
m_gui->m_objectTree->setRootIsDecorated(false);
m_gui->m_objectTree->setSelectionMode(QAbstractItemView::ExtendedSelection);
connect(m_gui->m_objectTree, &QTreeView::customContextMenuRequested, this, &EntityOutlinerWidget::OnOpenTreeContextMenu);
// custom item delegate
@@ -260,6 +260,8 @@ namespace AzToolsFramework
m_gui->m_objectTree->header()->resizeSection(EntityOutlinerListModel::ColumnVisibilityToggle, 20);
m_gui->m_objectTree->header()->setSectionResizeMode(EntityOutlinerListModel::ColumnLockToggle, QHeaderView::Fixed);
m_gui->m_objectTree->header()->resizeSection(EntityOutlinerListModel::ColumnLockToggle, 24);
m_gui->m_objectTree->header()->setSectionResizeMode(EntityOutlinerListModel::ColumnSpacing, QHeaderView::Fixed);
m_gui->m_objectTree->header()->resizeSection(EntityOutlinerListModel::ColumnSpacing, 16);
connect(m_gui->m_objectTree->selectionModel(),
&QItemSelectionModel::selectionChanged,
@@ -945,7 +947,7 @@ namespace AzToolsFramework
{
if (AZ::EntityId entityId = GetEntityIdFromIndex(index); auto entityUiHandler = m_editorEntityUiInterface->GetHandler(entityId))
{
entityUiHandler->OnEntityDoubleClick(entityId);
entityUiHandler->OnOutlinerItemDoubleClick(index);
}
}
@@ -33,7 +33,7 @@ namespace AzToolsFramework
}
}
QIcon LevelRootUiHandler::GenerateItemIcon(AZ::EntityId /*entityId*/) const
QIcon LevelRootUiHandler::GenerateItemIcon([[maybe_unused]] AZ::EntityId entityId) const
{
return QIcon(m_levelRootIconPath);
}
@@ -62,17 +62,18 @@ namespace AzToolsFramework
return infoString;
}
bool LevelRootUiHandler::CanToggleLockVisibility(AZ::EntityId /*entityId*/) const
bool LevelRootUiHandler::CanToggleLockVisibility([[maybe_unused]] AZ::EntityId entityId) const
{
return false;
}
bool LevelRootUiHandler::CanRename(AZ::EntityId /*entityId*/) const
bool LevelRootUiHandler::CanRename([[maybe_unused]] AZ::EntityId entityId) const
{
return false;
}
void LevelRootUiHandler::PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& /*index*/) const
void LevelRootUiHandler::PaintItemBackground(
QPainter* painter, const QStyleOptionViewItem& option, [[maybe_unused]] const QModelIndex& index) const
{
if (!painter)
{
@@ -94,8 +95,10 @@ namespace AzToolsFramework
painter->restore();
}
bool LevelRootUiHandler::OnEntityDoubleClick(AZ::EntityId entityId) const
bool LevelRootUiHandler::OnOutlinerItemDoubleClick(const QModelIndex& index) const
{
AZ::EntityId entityId = GetEntityIdFromIndex(index);
if (auto prefabFocusPublicInterface = AZ::Interface<Prefab::PrefabFocusPublicInterface>::Get();
!prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
{
@@ -33,7 +33,7 @@ namespace AzToolsFramework
bool CanToggleLockVisibility(AZ::EntityId entityId) const override;
bool CanRename(AZ::EntityId entityId) const override;
void PaintItemBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
bool OnEntityDoubleClick(AZ::EntityId entityId) const override;
bool OnOutlinerItemDoubleClick(const QModelIndex& index) const override;
private:
Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr;
@@ -99,7 +99,7 @@ namespace AzToolsFramework
return;
}
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
AZ::EntityId entityId = GetEntityIdFromIndex(index);
const bool isFirstColumn = index.column() == EntityOutlinerListModel::ColumnName;
const bool isLastColumn = index.column() == EntityOutlinerListModel::ColumnLockToggle;
QModelIndex firstColumnIndex = index.siblingAtColumn(EntityOutlinerListModel::ColumnName);
@@ -183,7 +183,7 @@ namespace AzToolsFramework
return;
}
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
AZ::EntityId entityId = GetEntityIdFromIndex(index);
const QTreeView* outlinerTreeView(qobject_cast<const QTreeView*>(option.widget));
const int ancestorLeft = outlinerTreeView->visualRect(index).left() + (m_prefabBorderThickness / 2) - 1;
@@ -283,7 +283,7 @@ namespace AzToolsFramework
void PrefabUiHandler::PaintItemForeground(QPainter* painter, const QStyleOptionViewItem& option, [[maybe_unused]] const QModelIndex& index) const
{
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
AZ::EntityId entityId = GetEntityIdFromIndex(index);
const QPoint offset = QPoint(-18, 3);
QModelIndex firstColumnIndex = index.siblingAtColumn(EntityOutlinerListModel::ColumnName);
const int iconSize = 16;
@@ -385,7 +385,7 @@ namespace AzToolsFramework
bool PrefabUiHandler::OnOutlinerItemClick(const QPoint& position, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
AZ::EntityId entityId = GetEntityIdFromIndex(index);
const QPoint offset = QPoint(-18, 3);
if (m_prefabFocusPublicInterface->IsOwningPrefabInFocusHierarchy(entityId))
@@ -411,7 +411,7 @@ namespace AzToolsFramework
void PrefabUiHandler::OnOutlinerItemCollapse(const QModelIndex& index) const
{
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
AZ::EntityId entityId = GetEntityIdFromIndex(index);
if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
{
@@ -420,8 +420,10 @@ namespace AzToolsFramework
}
}
bool PrefabUiHandler::OnEntityDoubleClick(AZ::EntityId entityId) const
bool PrefabUiHandler::OnOutlinerItemDoubleClick(const QModelIndex& index) const
{
AZ::EntityId entityId = GetEntityIdFromIndex(index);
if (!m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
{
// Focus on this prefab
@@ -43,8 +43,8 @@ namespace AzToolsFramework
const QModelIndex& index,
const QModelIndex& descendantIndex) const override;
bool OnOutlinerItemClick(const QPoint& position, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
bool OnOutlinerItemDoubleClick(const QModelIndex& index) const override;
void OnOutlinerItemCollapse(const QModelIndex& index) const override;
bool OnEntityDoubleClick(AZ::EntityId entityId) const override;
protected:
Prefab::PrefabFocusPublicInterface* m_prefabFocusPublicInterface = nullptr;
@@ -52,7 +52,6 @@ AZ_POP_DISABLE_WARNING
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/Thumbnails/ThumbnailWidget.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerBus.h>
#include <AzToolsFramework/Thumbnails/ThumbnailContext.h>
#include <AzToolsFramework/AssetBrowser/Thumbnails/ProductThumbnail.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
@@ -102,7 +101,7 @@ namespace AzToolsFramework
m_editButton->setAutoRaise(true);
m_editButton->setIcon(QIcon(":/stylesheet/img/UI20/open-in-internal-app.svg"));
m_editButton->setToolTip("Edit asset");
m_editButton->setVisible(false);
SetEditButtonVisible(false);
connect(m_editButton, &QToolButton::clicked, this, &PropertyAssetCtrl::OnEditButtonClicked);
@@ -813,7 +812,7 @@ namespace AzToolsFramework
selection.SetDisplayFilter(FilterConstType(compFilter));
}
AssetBrowserComponentRequestBus::Broadcast(&AssetBrowserComponentRequests::PickAssets, selection, parentWidget());
PickAssetSelectionFromDialog(selection, parentWidget());
if (selection.IsValid())
{
const auto product = azrtti_cast<const ProductAssetBrowserEntry*>(selection.GetResult());
@@ -831,6 +830,11 @@ namespace AzToolsFramework
}
}
void PropertyAssetCtrl::PickAssetSelectionFromDialog(AssetSelectionModel& selection, QWidget* parent)
{
AssetBrowserComponentRequestBus::Broadcast(&AssetBrowserComponentRequests::PickAssets, selection, parent);
}
void PropertyAssetCtrl::OnClearButtonClicked()
{
ClearAssetInternal();
@@ -961,12 +965,16 @@ namespace AzToolsFramework
AzFramework::StringFunc::Path::GetFileName(assetPath.c_str(), m_defaultAssetHint);
}
m_browseEdit->setPlaceholderText((m_defaultAssetHint + m_DefaultSuffix).c_str());
UpdateEditButton();
}
void PropertyAssetCtrl::UpdateAssetDisplay()
{
UpdateThumbnail();
UpdateEditButton();
if (m_currentAssetType == AZ::Data::s_invalidAssetType)
{
return;
@@ -1109,7 +1117,9 @@ namespace AzToolsFramework
void PropertyAssetCtrl::SetEditButtonVisible(bool visible)
{
m_editButton->setVisible(visible);
m_showEditButton = visible;
m_editButton->setVisible(m_showEditButton);
UpdateEditButton();
}
void PropertyAssetCtrl::SetEditButtonIcon(const QIcon& icon)
@@ -1195,7 +1205,7 @@ namespace AzToolsFramework
SharedThumbnailKey thumbnailKey = MAKE_TKEY(AzToolsFramework::AssetBrowser::ProductThumbnailKey, assetID);
if (m_showThumbnail)
{
m_thumbnail->SetThumbnailKey(thumbnailKey, Thumbnailer::ThumbnailContext::DefaultContext);
m_thumbnail->SetThumbnailKey(thumbnailKey);
}
return;
}
@@ -1205,6 +1215,15 @@ namespace AzToolsFramework
m_thumbnail->ClearThumbnail();
}
void PropertyAssetCtrl::UpdateEditButton()
{
// if Edit button is in use (shown), enable/disable it depending on the current asset id.
if (m_showEditButton && m_disableEditButtonWhenNoAssetSelected)
{
m_editButton->setEnabled(GetCurrentAssetID().IsValid());
}
}
void PropertyAssetCtrl::SetClearButtonEnabled(bool enable)
{
m_browseEdit->setClearButtonEnabled(enable);
@@ -1236,6 +1255,17 @@ namespace AzToolsFramework
return m_hideProductFilesInAssetPicker;
}
void PropertyAssetCtrl::SetDisableEditButtonWhenNoAssetSelected(bool disableEditButtonWhenNoAssetSelected)
{
m_disableEditButtonWhenNoAssetSelected = disableEditButtonWhenNoAssetSelected;
UpdateEditButton();
}
bool PropertyAssetCtrl::GetDisableEditButtonWhenNoAssetSelected() const
{
return m_disableEditButtonWhenNoAssetSelected;
}
void PropertyAssetCtrl::SetShowThumbnail(bool enable)
{
m_showThumbnail = enable;
@@ -1349,6 +1379,12 @@ namespace AzToolsFramework
GUI->SetEditButtonTooltip(tr(buttonTooltip.c_str()));
}
}
else if (attrib == AZ_CRC_CE("DisableEditButtonWhenNoAssetSelected"))
{
bool disableEditButtonWhenNoAssetSelected = false;
attrValue->Read<bool>(disableEditButtonWhenNoAssetSelected);
GUI->SetDisableEditButtonWhenNoAssetSelected(disableEditButtonWhenNoAssetSelected);
}
else if (attrib == AZ::Edit::Attributes::DefaultAsset)
{
AZ::Data::AssetId assetId;
@@ -1492,7 +1528,7 @@ namespace AzToolsFramework
ConsumeAttributeInternal(GUI, attrib, attrValue, debugName);
}
void AssetPropertyHandlerDefault::WriteGUIValuesIntoProperty(size_t index, PropertyAssetCtrl* GUI, property_t& instance, InstanceDataNode* node)
void AssetPropertyHandlerDefault::WriteGUIValuesIntoPropertyInternal(size_t index, PropertyAssetCtrl* GUI, property_t& instance, InstanceDataNode* node)
{
(void)index;
(void)node;
@@ -1507,7 +1543,12 @@ namespace AzToolsFramework
}
}
bool AssetPropertyHandlerDefault::ReadValuesIntoGUI(size_t index, PropertyAssetCtrl* GUI, const property_t& instance, InstanceDataNode* node)
void AssetPropertyHandlerDefault::WriteGUIValuesIntoProperty(size_t index, PropertyAssetCtrl* GUI, property_t& instance, InstanceDataNode* node)
{
WriteGUIValuesIntoPropertyInternal(index, GUI, instance, node);
}
bool AssetPropertyHandlerDefault::ReadValuesIntoGUIInternal(size_t index, PropertyAssetCtrl* GUI, const property_t& instance, InstanceDataNode* node)
{
(void)index;
(void)node;
@@ -1527,6 +1568,11 @@ namespace AzToolsFramework
return false;
}
bool AssetPropertyHandlerDefault::ReadValuesIntoGUI(size_t index, PropertyAssetCtrl* GUI, const property_t& instance, InstanceDataNode* node)
{
return ReadValuesIntoGUIInternal(index, GUI, instance, node);
}
QWidget* SimpleAssetPropertyHandlerDefault::CreateGUI(QWidget* pParent)
{
PropertyAssetCtrl* newCtrl = aznew PropertyAssetCtrl(pParent);
@@ -1597,6 +1643,12 @@ namespace AzToolsFramework
GUI->SetEditButtonTooltip(tr(buttonTooltip.c_str()));
}
}
else if (attrib == AZ_CRC_CE("DisableEditButtonWhenNoAssetSelected"))
{
bool disableEditButtonWhenNoAssetSelected = false;
attrValue->Read<bool>(disableEditButtonWhenNoAssetSelected);
GUI->SetDisableEditButtonWhenNoAssetSelected(disableEditButtonWhenNoAssetSelected);
}
}
void SimpleAssetPropertyHandlerDefault::WriteGUIValuesIntoProperty(size_t index, PropertyAssetCtrl* GUI, property_t& instance, InstanceDataNode* node)
@@ -159,6 +159,10 @@ namespace AzToolsFramework
//! By default the asset picker shows both on an AZ::Asset<> property. You can hide product assets with this flag.
bool m_hideProductFilesInAssetPicker = false;
//! True to disable the edit button when there is no asset currently selected.
bool m_disableEditButtonWhenNoAssetSelected = false;
bool m_showEditButton = false;
bool m_showThumbnail = false;
bool m_showThumbnailDropDownButton = false;
EditCallbackType* m_thumbnailCallback = nullptr;
@@ -220,6 +224,9 @@ namespace AzToolsFramework
void SetHideProductFilesInAssetPicker(bool hide);
bool GetHideProductFilesInAssetPicker() const;
void SetDisableEditButtonWhenNoAssetSelected(bool disableEditButtonWhenNoAssetSelected);
bool GetDisableEditButtonWhenNoAssetSelected() const;
// Enable and configure a thumbnail widget that displays an asset preview and dropdown arrow for a dropdown menu
void SetShowThumbnail(bool enable);
bool GetShowThumbnail() const;
@@ -237,6 +244,7 @@ namespace AzToolsFramework
void SetCurrentAssetHint(const AZStd::string& hint);
void SetDefaultAssetID(const AZ::Data::AssetId& defaultID);
virtual void PopupAssetPicker();
virtual void PickAssetSelectionFromDialog(AssetSelectionModel& selection, QWidget* parent);
void OnClearButtonClicked();
void UpdateAssetDisplay();
void OnLineEditFocus(bool focus);
@@ -250,6 +258,7 @@ namespace AzToolsFramework
private:
void UpdateThumbnail();
void UpdateEditButton();
};
class AssetPropertyHandlerDefault
@@ -272,7 +281,9 @@ namespace AzToolsFramework
virtual QWidget* CreateGUI(QWidget* pParent) override;
static void ConsumeAttributeInternal(PropertyAssetCtrl* GUI, AZ::u32 attrib, PropertyAttributeReader* attrValue, const char* debugName);
void ConsumeAttribute(PropertyAssetCtrl* GUI, AZ::u32 attrib, PropertyAttributeReader* attrValue, const char* debugName) override;
static void WriteGUIValuesIntoPropertyInternal(size_t index, PropertyAssetCtrl* GUI, property_t& instance, InstanceDataNode* node);
virtual void WriteGUIValuesIntoProperty(size_t index, PropertyAssetCtrl* GUI, property_t& instance, InstanceDataNode* node) override;
static bool ReadValuesIntoGUIInternal(size_t index, PropertyAssetCtrl* GUI, const property_t& instance, InstanceDataNode* node);
virtual bool ReadValuesIntoGUI(size_t index, PropertyAssetCtrl* GUI, const property_t& instance, InstanceDataNode* node) override;
};
@@ -154,6 +154,16 @@ namespace AzToolsFramework
(void)debugName;
}
// provides an option to specify reading parent element attributes.
// This allows parent elements to override attributes of their children if needed.
virtual void ConsumeParentAttribute(WidgetType* widget, AZ::u32 attrib, PropertyAttributeReader* attrValue, const char* debugName)
{
(void)widget;
(void)attrib;
(void)attrValue;
(void)debugName;
}
// override GetFirstInTabOrder, GetLastInTabOrder in your base class to define which widget gets focus first when pressing tab,
// and also what widget is last.
// for example, if your widget is a compound widget and contains, say, 5 buttons
@@ -40,7 +40,14 @@ namespace AzToolsFramework
}
void* classInstance = parent->FirstInstance(); // pointer to the owner class so we can read member variables and functions
auto consumeAttributes = [&](const auto& attributes, const char* name)
void* parentClassInstance = nullptr;
if (InstanceDataNode* parentInstanceDataNode = parent->GetParent())
{
parentClassInstance = parentInstanceDataNode->FirstInstance();
}
auto consumeAttributes = [this, classInstance, wid](const auto& attributes, const char* name)
{
for (size_t i = 0; i < attributes.size(); ++i)
{
@@ -50,25 +57,43 @@ namespace AzToolsFramework
}
};
auto consumeParentAttributes = [this, parentClassInstance, wid](const auto& attributes, const char* name)
{
if (parentClassInstance)
{
for (size_t i = 0; i < attributes.size(); ++i)
{
const auto& attrPair = attributes[i];
PropertyAttributeReader reader(parentClassInstance, &*attrPair.second);
ConsumeParentAttribute(wid, attrPair.first, &reader, name);
}
}
};
const AZ::SerializeContext::ClassElement* element = dataNode->GetElementMetadata();
if (element)
{
consumeAttributes(element->m_attributes, element->m_name);
const AZ::Edit::ElementData* elementEdit = dataNode->GetElementEditMetadata();
if (elementEdit)
if (const AZ::Edit::ElementData* elementEdit = dataNode->GetElementEditMetadata();
elementEdit != nullptr)
{
consumeAttributes(elementEdit->m_attributes, elementEdit->m_name);
}
}
if (dataNode->GetClassMetadata())
{
const AZ::Edit::ClassData* classEditData = dataNode->GetClassMetadata()->m_editData;
if (classEditData)
const AZ::SerializeContext::ClassElement* parentElement = parent != dataNode ?
dataNode->GetElementMetadata() :
nullptr;
if (parentElement != nullptr)
{
for (auto it = classEditData->m_elements.begin(); it != classEditData->m_elements.end(); ++it)
// Reuse the current instance element name for the debug name
consumeParentAttributes(parentElement->m_attributes, element->m_name);
if (const AZ::Edit::ElementData* elementEdit = parent->GetElementEditMetadata();
elementEdit != nullptr)
{
consumeAttributes(it->m_attributes, it->m_name);
consumeParentAttributes(elementEdit->m_attributes, elementEdit->m_name);
}
}
}
@@ -66,6 +66,12 @@ namespace AzToolsFramework
class GenericEnumPropertyComboBoxHandler
: public GenericComboBoxHandler<ValueType>
{
virtual void ConsumeParentAttribute(GenericComboBoxCtrlBase* GUI, AZ::u32 attrib, PropertyAttributeReader* attrValue, const char* debugName) override
{
// Simply re-route to ConsumeAttribute since no special logic is needed.
ConsumeAttribute(GUI, attrib, attrValue, debugName);
}
virtual void ConsumeAttribute(GenericComboBoxCtrlBase* GUI, AZ::u32 attrib, PropertyAttributeReader* attrValue, const char* debugName) override
{
(void)debugName;
@@ -65,11 +65,11 @@ namespace AzToolsFramework
UpdateVisibility();
}
void ThumbnailPropertyCtrl::SetThumbnailKey(Thumbnailer::SharedThumbnailKey key, const char* contextName)
void ThumbnailPropertyCtrl::SetThumbnailKey(Thumbnailer::SharedThumbnailKey key)
{
m_key = key;
m_thumbnail->SetThumbnailKey(m_key, contextName);
m_thumbnailEnlarged->SetThumbnailKey(m_key, contextName);
m_thumbnail->SetThumbnailKey(m_key);
m_thumbnailEnlarged->SetThumbnailKey(m_key);
UpdateVisibility();
}
@@ -34,7 +34,7 @@ namespace AzToolsFramework
explicit ThumbnailPropertyCtrl(QWidget* parent = nullptr);
//! Call this to set what thumbnail widget will display
void SetThumbnailKey(Thumbnailer::SharedThumbnailKey key, const char* contextName = "Default");
void SetThumbnailKey(Thumbnailer::SharedThumbnailKey key);
//! Remove current thumbnail
void ClearThumbnail();
@@ -1360,6 +1360,19 @@ namespace AzToolsFramework
EndRecordManipulatorCommand();
});
translationManipulators->InstallSurfaceManipulatorEntityIdsToIgnoreFn(
[this](const ViewportInteraction::MouseInteraction& interaction)
{
if (interaction.m_keyboardModifiers.Ctrl())
{
return AZStd::unordered_set<AZ::EntityId>();
}
else
{
return m_selectedEntityIds;
}
});
// transfer ownership
m_entityIdManipulators.m_manipulators = AZStd::move(translationManipulators);
}
@@ -86,8 +86,6 @@ set(FILES
Thumbnails/Thumbnail.cpp
Thumbnails/Thumbnail.h
Thumbnails/Thumbnail.inl
Thumbnails/ThumbnailContext.cpp
Thumbnails/ThumbnailContext.h
Thumbnails/ThumbnailerBus.h
Thumbnails/ThumbnailWidget.cpp
Thumbnails/ThumbnailWidget.h
@@ -3276,4 +3276,45 @@ namespace UnitTest
const AZ::Transform finalEntityTransform = AzToolsFramework::GetWorldTransform(m_entityIdBox);
EXPECT_THAT(finalEntityTransform.GetTranslation(), IsCloseTolerance(expectedWorldPosition, 0.01f));
}
TEST_F(
EditorTransformComponentSelectionRenderGeometryIntersectionManipulatorFixture, SurfaceManipulatorSelfIntersectsMeshWhenCtrlIsHeld)
{
// camera (go to position format) - 47.00, -52.00, 20.00, 0.00, -60.00
m_cameraState.m_viewportSize = AZ::Vector2(1280.0f, 720.0f);
// position camera
AzFramework::SetCameraTransform(
m_cameraState,
AZ::Transform::CreateFromMatrix3x3AndTranslation(
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(-60.0f)), AZ::Vector3(47.0f, -52.0f, 20.0f)));
// position box
AzToolsFramework::SetWorldTransform(m_entityIdBox, AZ::Transform::CreateTranslation(AZ::Vector3(50.0f, -50.0f, 20.0f)));
// the initial starting position of the entity
const auto initialTransformWorld = AzToolsFramework::GetWorldTransform(m_entityIdBox);
// where the surface manipulator should end up (surface of the box)
const auto finalTransformWorld = AZ::Transform::CreateTranslation(AZ::Vector3(49.5f, -49.6337357f, 19.5793953f));
// calculate the position in screen space of the initial position of the entity
const auto initialPositionScreen = AzFramework::WorldToScreen(initialTransformWorld.GetTranslation(), m_cameraState);
// calculate the position in screen space of the final position of the entity
const auto finalPositionScreen = AzFramework::WorldToScreen(finalTransformWorld.GetTranslation(), m_cameraState);
// select the entity (this will cause the manipulators to appear in EditorTransformComponentSelection)
AzToolsFramework::SelectEntity(m_entityIdBox);
// press and drag the mouse (starting where the surface manipulator is)
m_actionDispatcher->CameraState(m_cameraState)
->MousePosition(initialPositionScreen)
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Control)
->MouseLButtonDown()
->MousePosition(finalPositionScreen)
->MouseLButtonUp();
// read back the position of the entity now
const AZ::Transform finalManipulatorTransform = GetManipulatorTransform().value_or(AZ::Transform::CreateIdentity());
// ensure final world positions match
EXPECT_THAT(finalManipulatorTransform, IsCloseTolerance(finalTransformWorld, 0.01f));
}
} // namespace UnitTest
@@ -15,7 +15,7 @@ namespace UnitTest
// When no containers are in the way, the function will just return the entityId of the entity that was clicked.
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify the correct entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
@@ -29,7 +29,7 @@ namespace UnitTest
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[StreetEntityName]); // Containers are closed by default
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify the correct entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
@@ -47,7 +47,7 @@ namespace UnitTest
m_containerEntityInterface->SetContainerOpen(m_entityMap[StreetEntityName], true);
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify the correct entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
@@ -65,7 +65,7 @@ namespace UnitTest
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[CityEntityName]);
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify the correct entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
@@ -85,7 +85,7 @@ namespace UnitTest
m_containerEntityInterface->SetContainerOpen(m_entityMap[CityEntityName], true);
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify the correct entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
@@ -8,6 +8,7 @@
#include <Tests/FocusMode/EditorFocusModeFixture.h>
#include <AzCore/Component/TransformBus.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <Tests/BoundsTestComponent.h>
@@ -93,10 +94,13 @@ namespace UnitTest
entity->CreateComponent<UnitTest::BoundsTestComponent>();
entity->Activate();
// Move the CarEntity so it's out of the way.
AZ::TransformBus::Event(m_entityMap[CarEntityName], &AZ::TransformBus::Events::SetWorldTranslation, WorldCarEntityPosition);
// Move the City so that it is in view
AZ::TransformBus::Event(m_entityMap[CityEntityName], &AZ::TransformBus::Events::SetWorldTranslation, s_worldCityEntityPosition);
// Setup the camera so the Car entity is in view.
// Move the CarEntity so that it's not overlapping with the rest
AZ::TransformBus::Event(m_entityMap[CarEntityName], &AZ::TransformBus::Events::SetWorldTranslation, s_worldCarEntityPosition);
// Setup the camera so the entities is in view.
AzFramework::SetCameraTransform(
m_cameraState,
AZ::Transform::CreateFromQuaternionAndTranslation(
@@ -113,4 +117,5 @@ namespace UnitTest
return entity->GetId();
}
} // namespace UnitTest
@@ -8,7 +8,6 @@
#pragma once
#include <AzCore/Component/TransformBus.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
@@ -38,9 +37,6 @@ namespace UnitTest
AzToolsFramework::EntityIdList GetSelectedEntities();
AzFramework::EntityContextId m_editorEntityContextId = AzFramework::EntityContextId::CreateNull();
AzFramework::CameraState m_cameraState;
inline static const AZ::Vector3 CameraPosition = AZ::Vector3(10.0f, 15.0f, 10.0f);
inline static const char* CityEntityName = "City";
inline static const char* StreetEntityName = "Street";
@@ -49,7 +45,11 @@ namespace UnitTest
inline static const char* Passenger1EntityName = "Passenger1";
inline static const char* Passenger2EntityName = "Passenger2";
inline static AZ::Vector3 WorldCarEntityPosition = AZ::Vector3(5.0f, 15.0f, 0.0f);
AzFramework::CameraState m_cameraState;
inline static const AZ::Vector3 CameraPosition = AZ::Vector3(10.0f, 15.0f, 10.0f);
inline static AZ::Vector3 s_worldCityEntityPosition = AZ::Vector3(5.0f, 10.0f, 0.0f);
inline static AZ::Vector3 s_worldCarEntityPosition = AZ::Vector3(5.0f, 15.0f, 0.0f);
};
} // namespace UnitTest
@@ -45,5 +45,20 @@ namespace UnitTest
// Click the entity in the viewport
m_actionDispatcher->CameraState(m_cameraState)->MousePosition(carScreenPosition)->MouseLButtonDown()->MouseLButtonUp();
}
void BoxSelectOnViewport()
{
// Calculate the position in screen space of where to begin and end the box select action
const auto beginningPositionWorldBoxSelect = AzFramework::WorldToScreen(AZ::Vector3(-10.0f, 15.0f, 5.0f), m_cameraState);
const auto endingPositionWorldBoxSelect = AzFramework::WorldToScreen(AZ::Vector3(10.0f, 15.0f, -5.0f), m_cameraState);
// Perform a box select in the viewport
m_actionDispatcher->SetStickySelect(true)
->CameraState(m_cameraState)
->MousePosition(beginningPositionWorldBoxSelect)
->MouseLButtonDown()
->MousePosition(endingPositionWorldBoxSelect)
->MouseLButtonUp();
}
};
} // namespace UnitTest
@@ -13,7 +13,7 @@ namespace UnitTest
TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionSelectEntityWithFocusOnLevel)
{
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
@@ -27,7 +27,7 @@ namespace UnitTest
m_focusModeInterface->SetFocusRoot(m_entityMap[StreetEntityName]);
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
@@ -41,7 +41,7 @@ namespace UnitTest
m_focusModeInterface->SetFocusRoot(m_entityMap[CarEntityName]);
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
@@ -55,7 +55,7 @@ namespace UnitTest
m_focusModeInterface->SetFocusRoot(m_entityMap[SportsCarEntityName]);
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
@@ -68,10 +68,71 @@ namespace UnitTest
m_focusModeInterface->SetFocusRoot(m_entityMap[Passenger1EntityName]);
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
ClickAtWorldPositionOnViewport(s_worldCarEntityPosition);
// Verify entity is selected
auto selectedEntitiesAfter = GetSelectedEntities();
EXPECT_EQ(selectedEntitiesAfter.size(), 0);
}
TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionBoxSelectWithFocusOnLevel)
{
// Do a box select that includes all entities in the fixture
BoxSelectOnViewport();
// Entities are selected
using ::testing::UnorderedElementsAre;
auto selectedEntitiesAfter = GetSelectedEntities();
EXPECT_THAT(selectedEntitiesAfter,
UnorderedElementsAre(
m_entityMap[CityEntityName],
m_entityMap[StreetEntityName],
m_entityMap[CarEntityName],
m_entityMap[Passenger1EntityName],
m_entityMap[SportsCarEntityName],
m_entityMap[Passenger2EntityName]
)
);
}
TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionBoxSelectWithFocusOnChild)
{
// Set the focus on the Passenger1 Entity (child of the entity)
m_focusModeInterface->SetFocusRoot(m_entityMap[StreetEntityName]);
// Do a box select that includes all entities in the fixture
BoxSelectOnViewport();
// Entities are selected
using ::testing::UnorderedElementsAre;
auto selectedEntitiesAfter = GetSelectedEntities();
EXPECT_THAT(selectedEntitiesAfter,
UnorderedElementsAre(
m_entityMap[StreetEntityName],
m_entityMap[CarEntityName],
m_entityMap[Passenger1EntityName],
m_entityMap[SportsCarEntityName],
m_entityMap[Passenger2EntityName]
)
);
}
TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionBoxSelectWithFocusOnLeaf)
{
// Set the focus on the Passenger1 Entity (child of the entity)
m_focusModeInterface->SetFocusRoot(m_entityMap[Passenger1EntityName]);
// Do a box select that includes all entities in the fixture
BoxSelectOnViewport();
// Entities are selected
using ::testing::UnorderedElementsAre;
auto selectedEntitiesAfter = GetSelectedEntities();
EXPECT_THAT(selectedEntitiesAfter,
UnorderedElementsAre(
m_entityMap[Passenger1EntityName]
)
);
}
} // namespace UnitTest
@@ -71,83 +71,4 @@ namespace UnitTest
AZ::Entity* m_testEntity = nullptr;
};
TEST_F(ThumbnailerTests, ThumbnailerComponent_RegisterUnregisterContext)
{
constexpr const char* contextName1 = "Context1";
constexpr const char* contextName2 = "Context2";
auto checkHasContext = [](const char* contextName)
{
bool hasContext = false;
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::BroadcastResult(hasContext, &AzToolsFramework::Thumbnailer::ThumbnailerRequests::HasContext, contextName);
return hasContext;
};
EXPECT_FALSE(checkHasContext(contextName1));
EXPECT_FALSE(checkHasContext(contextName2));
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::Broadcast(&AzToolsFramework::Thumbnailer::ThumbnailerRequests::RegisterContext, contextName1);
EXPECT_TRUE(checkHasContext(contextName1));
EXPECT_FALSE(checkHasContext(contextName2));
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::Broadcast(&AzToolsFramework::Thumbnailer::ThumbnailerRequests::RegisterContext, contextName2);
EXPECT_TRUE(checkHasContext(contextName1));
EXPECT_TRUE(checkHasContext(contextName2));
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::Broadcast(&AzToolsFramework::Thumbnailer::ThumbnailerRequests::UnregisterContext, contextName1);
EXPECT_FALSE(checkHasContext(contextName1));
EXPECT_TRUE(checkHasContext(contextName2));
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::Broadcast(&AzToolsFramework::Thumbnailer::ThumbnailerRequests::UnregisterContext, contextName2);
EXPECT_FALSE(checkHasContext(contextName1));
EXPECT_FALSE(checkHasContext(contextName2));
}
TEST_F(ThumbnailerTests, ThumbnailerComponent_Deactivate_ClearTumbnailContexts)
{
constexpr const char* contextName1 = "Context1";
constexpr const char* contextName2 = "Context2";
auto checkHasContext = [](const char* contextName)
{
bool hasContext = false;
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::BroadcastResult(hasContext, &AzToolsFramework::Thumbnailer::ThumbnailerRequests::HasContext, contextName);
return hasContext;
};
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::Broadcast(&AzToolsFramework::Thumbnailer::ThumbnailerRequests::RegisterContext, contextName1);
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::Broadcast(&AzToolsFramework::Thumbnailer::ThumbnailerRequests::RegisterContext, contextName2);
EXPECT_TRUE(checkHasContext(contextName1));
EXPECT_TRUE(checkHasContext(contextName2));
m_testEntity->Deactivate();
m_testEntity->Activate();
EXPECT_FALSE(checkHasContext(contextName1));
EXPECT_FALSE(checkHasContext(contextName2));
}
TEST_F(ThumbnailerTests, ThumbnailerComponent_RegisterContextTwice_Assert)
{
constexpr const char* contextName1 = "Context1";
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::Broadcast(&AzToolsFramework::Thumbnailer::ThumbnailerRequests::RegisterContext, contextName1);
AZ_TEST_START_TRACE_SUPPRESSION;
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::Broadcast(&AzToolsFramework::Thumbnailer::ThumbnailerRequests::RegisterContext, contextName1);
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
}
TEST_F(ThumbnailerTests, ThumbnailerComponent_UnregisterUnknownContext_Assert)
{
AZ_TEST_START_TRACE_SUPPRESSION;
AzToolsFramework::Thumbnailer::ThumbnailerRequestBus::Broadcast(&AzToolsFramework::Thumbnailer::ThumbnailerRequests::UnregisterContext, "ContextDoesNotExist");
AZ_TEST_STOP_TRACE_SUPPRESSION(1);
}
} // namespace UnitTest