You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
507 lines
16 KiB
C++
507 lines
16 KiB
C++
/*
|
|
* 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 "EditorCommon.h"
|
|
#include <AzCore/Asset/AssetManager.h>
|
|
#include <AzToolsFramework/ToolsComponents/EditorOnlyEntityComponentBus.h>
|
|
#include <LyShine/UiComponentTypes.h>
|
|
|
|
#define UICANVASEDITOR_HIERARCHY_ICON_OPEN ":/Icons/Eye_Open.tif"
|
|
#define UICANVASEDITOR_HIERARCHY_ICON_OPEN_HIDDEN ":/Icons/Eye_Open_Hidden.tif"
|
|
#define UICANVASEDITOR_HIERARCHY_ICON_OPEN_HOVER ":/Icons/Eye_Open_Hover.tif"
|
|
#define UICANVASEDITOR_HIERARCHY_ICON_PADLOCK_ENABLED_HOVER ":/Icons/Padlock_Enabled_Hover.tif"
|
|
#define UICANVASEDITOR_HIERARCHY_ICON_PADLOCK_ENABLED ":/Icons/Padlock_Enabled.tif"
|
|
|
|
HierarchyItem::HierarchyItem(EditorWindow* editWindow,
|
|
QTreeWidgetItem& parent,
|
|
int childIndex,
|
|
const QString label,
|
|
AZ::Entity* optionalElement)
|
|
: QObject()
|
|
, QTreeWidgetItem(static_cast<QTreeWidgetItem*>(nullptr), QStringList(label), HierarchyItem::RttiType)
|
|
, m_editorWindow(editWindow)
|
|
, m_elementId(optionalElement ? optionalElement->GetId() : AZ::EntityId())
|
|
, m_mark(false)
|
|
, m_preMoveChildRow(-1)
|
|
, m_mouseIsHovering(false)
|
|
, m_nonSnappedOffsets()
|
|
, m_nonSnappedZRotation(0.0f)
|
|
{
|
|
// Add this hierarchy item to its parent at the specified child index
|
|
QTreeWidgetItem* child = static_cast<QTreeWidgetItem*>(this);
|
|
if (childIndex >= 0)
|
|
{
|
|
parent.insertChild(childIndex, child);
|
|
}
|
|
else
|
|
{
|
|
parent.addChild(child);
|
|
}
|
|
|
|
// If an optional existing element is specified, we don't need to create a
|
|
// new element. This occurs when building the tree from an existing canvas
|
|
if (!optionalElement)
|
|
{
|
|
// Create a new element as the last child of the specified parent element
|
|
AZ::Entity* element = nullptr;
|
|
HierarchyItem* parentHierarchyItem = HierarchyItem::RttiCast(&parent);
|
|
if (parentHierarchyItem)
|
|
{
|
|
EBUS_EVENT_ID_RESULT(element, parentHierarchyItem->GetEntityId(), UiElementBus,
|
|
CreateChildElement, label.toStdString().c_str());
|
|
}
|
|
else
|
|
{
|
|
EBUS_EVENT_ID_RESULT(element, editWindow->GetCanvas(), UiCanvasBus,
|
|
CreateChildElement, label.toStdString().c_str());
|
|
}
|
|
|
|
if (element->GetState() == AZ::Entity::State::Active)
|
|
{
|
|
element->Deactivate(); // deactivate so that we can add components
|
|
}
|
|
|
|
// add a transform component to the element - all UI elements have a transform
|
|
element->CreateComponent(LyShine::UiTransform2dComponentUuid);
|
|
|
|
if (element->GetState() == AZ::Entity::State::Constructed)
|
|
{
|
|
element->Init(); // init
|
|
}
|
|
|
|
if (element->GetState() == AZ::Entity::State::Init)
|
|
{
|
|
element->Activate(); // activate
|
|
}
|
|
|
|
m_elementId = element->GetId();
|
|
|
|
// Move the new child element to the desired child index
|
|
if (childIndex >= 0)
|
|
{
|
|
AZ::EntityId parentEntityId;
|
|
if (parentHierarchyItem)
|
|
{
|
|
parentEntityId = parentHierarchyItem->GetEntityId();
|
|
}
|
|
|
|
AZ::EntityId insertBeforeEntityId;
|
|
EBUS_EVENT_ID_RESULT(insertBeforeEntityId, parentEntityId, UiElementBus, GetChildEntityId, childIndex);
|
|
|
|
EBUS_EVENT_ID(m_elementId, UiElementBus, ReparentByEntityId, parentEntityId, insertBeforeEntityId);
|
|
}
|
|
}
|
|
|
|
AZ_Assert(m_elementId.IsValid(), "Invalid element ID");
|
|
|
|
// Connect signals.
|
|
{
|
|
// Register with the entity map for quick lookup.
|
|
|
|
QObject::connect(this,
|
|
SIGNAL(SignalItemAdd(HierarchyItem*)),
|
|
m_editorWindow->GetHierarchy(),
|
|
SLOT(HandleItemAdd(HierarchyItem*)));
|
|
|
|
QObject::connect(this,
|
|
SIGNAL(SignalItemRemove(HierarchyItem*)),
|
|
m_editorWindow->GetHierarchy(),
|
|
SLOT(HandleItemRemove(HierarchyItem*)));
|
|
}
|
|
|
|
// Add to the entity map for quick lookup.
|
|
//
|
|
// IMPORTANT: This MUST be done BEFORE changing the
|
|
// behavior and look of this class.
|
|
SignalItemAdd(this);
|
|
|
|
// Behavior and look.
|
|
//
|
|
// IMPORTANT: This MUST be done AFTER SignalItemAdd().
|
|
{
|
|
setFlags(flags() |
|
|
Qt::ItemIsEditable |
|
|
Qt::ItemIsDragEnabled |
|
|
Qt::ItemIsDropEnabled);
|
|
|
|
UpdateIcon();
|
|
UpdateSliceInfo();
|
|
UpdateEditorOnlyInfo();
|
|
}
|
|
}
|
|
|
|
HierarchyItem::~HierarchyItem()
|
|
{
|
|
DeleteElement();
|
|
|
|
// Remove from quick lookup entity map.
|
|
SignalItemRemove(this);
|
|
}
|
|
|
|
void HierarchyItem::DeleteElement()
|
|
{
|
|
// IMPORTANT: DeleteElement() can be called from ~HierarchyItem().
|
|
// Parent HierarchyItem are destroyed BEFORE their children.
|
|
// When a parent HierarchyItem is destroyed, all its AZ::Entity
|
|
// children are destroyed. Therefore, it's NECESSARY to use
|
|
// SAFE_DELETE(). That's because our AZ::Entity might have already
|
|
// been deleted. In which case GetElement() will return nullptr.
|
|
// ~HierarchyItem() is the ONLY place where GetElement() is allowed
|
|
// return nullptr.
|
|
EBUS_EVENT_ID(m_elementId, UiElementBus, DestroyElement);
|
|
}
|
|
|
|
AZ::Entity* HierarchyItem::GetElement() const
|
|
{
|
|
// IMPORTANT: "element" will NEVER be nullptr, EXCEPT in ~HierarchyItem().
|
|
// In the ~HierarchyItem(), deleting the parent of our element will cause
|
|
// our own element to be destroyed. ~HierarchyItem() is the ONLY place
|
|
// where we CAN'T assume that our m_elementId is always valid.
|
|
return EntityHelpers::GetEntity(m_elementId);
|
|
}
|
|
|
|
AZ::EntityId HierarchyItem::GetEntityId() const
|
|
{
|
|
return m_elementId;
|
|
}
|
|
|
|
void HierarchyItem::ClearEntityId()
|
|
{
|
|
m_elementId.SetInvalid();
|
|
}
|
|
|
|
void HierarchyItem::SetMouseIsHovering(bool isHovering)
|
|
{
|
|
m_mouseIsHovering = isHovering;
|
|
|
|
UpdateIcon();
|
|
}
|
|
|
|
void HierarchyItem::SetIsExpanded(bool isExpanded)
|
|
{
|
|
// Runtime-side.
|
|
EBUS_EVENT_ID(m_elementId, UiEditorBus, SetIsExpanded, isExpanded);
|
|
|
|
// Editor-side.
|
|
setExpanded(isExpanded);
|
|
}
|
|
|
|
void HierarchyItem::ApplyElementIsExpanded()
|
|
{
|
|
bool isExpanded = false;
|
|
EBUS_EVENT_ID_RESULT(isExpanded, m_elementId, UiEditorBus, GetIsExpanded);
|
|
|
|
setExpanded(isExpanded);
|
|
}
|
|
|
|
void HierarchyItem::SetIsSelectable(bool isSelectable)
|
|
{
|
|
// Runtime-side.
|
|
EBUS_EVENT_ID(m_elementId, UiEditorBus, SetIsSelectable, isSelectable);
|
|
|
|
// Editor-side.
|
|
UpdateIcon();
|
|
UpdateChildIcon();
|
|
m_editorWindow->GetViewport()->Refresh();
|
|
}
|
|
|
|
void HierarchyItem::SetIsSelected(bool isSelected)
|
|
{
|
|
// Runtime-side.
|
|
EBUS_EVENT_ID(m_elementId, UiEditorBus, SetIsSelected, isSelected);
|
|
|
|
// Editor-side.
|
|
setSelected(isSelected);
|
|
UpdateIcon();
|
|
m_editorWindow->GetViewport()->Refresh();
|
|
}
|
|
|
|
void HierarchyItem::SetIsVisible(bool isVisible)
|
|
{
|
|
// Runtime-side.
|
|
EBUS_EVENT_ID(m_elementId, UiEditorBus, SetIsVisible, isVisible);
|
|
|
|
// Editor-side.
|
|
UpdateIcon();
|
|
UpdateChildIcon();
|
|
m_editorWindow->GetViewport()->Refresh();
|
|
}
|
|
|
|
void HierarchyItem::UpdateIcon()
|
|
{
|
|
// Eye icon.
|
|
{
|
|
const char* textureName = nullptr;
|
|
|
|
bool isVisible = false;
|
|
EBUS_EVENT_ID_RESULT(isVisible, m_elementId, UiEditorBus, GetIsVisible);
|
|
|
|
if (isVisible)
|
|
{
|
|
// This item is visible.
|
|
|
|
bool areAllAncestorsVisible = true;
|
|
EBUS_EVENT_ID_RESULT(areAllAncestorsVisible, m_elementId, UiEditorBus, AreAllAncestorsVisible);
|
|
|
|
textureName = (m_mouseIsHovering ? UICANVASEDITOR_HIERARCHY_ICON_OPEN_HOVER : (areAllAncestorsVisible ? UICANVASEDITOR_HIERARCHY_ICON_OPEN : UICANVASEDITOR_HIERARCHY_ICON_OPEN_HIDDEN));
|
|
}
|
|
else
|
|
{
|
|
// This item is NOT visible.
|
|
textureName = (m_mouseIsHovering ? UICANVASEDITOR_HIERARCHY_ICON_OPEN_HIDDEN : "");
|
|
}
|
|
|
|
setIcon(kHierarchyColumnIsVisible, QIcon(textureName).pixmap(UICANVASEDITOR_HIERARCHY_HEADER_ICON_SIZE, UICANVASEDITOR_HIERARCHY_HEADER_ICON_SIZE));
|
|
}
|
|
|
|
// Padlock icon.
|
|
{
|
|
const char* textureName = nullptr;
|
|
|
|
bool isSelectable = false;
|
|
EBUS_EVENT_ID_RESULT(isSelectable, m_elementId, UiEditorBus, GetIsSelectable);
|
|
|
|
if (isSelectable)
|
|
{
|
|
// This item is NOT locked.
|
|
textureName = (m_mouseIsHovering ? UICANVASEDITOR_HIERARCHY_ICON_PADLOCK_ENABLED : "");
|
|
}
|
|
else
|
|
{
|
|
// This item is locked.
|
|
textureName = (m_mouseIsHovering ? UICANVASEDITOR_HIERARCHY_ICON_PADLOCK_ENABLED_HOVER : UICANVASEDITOR_HIERARCHY_ICON_PADLOCK_ENABLED);
|
|
}
|
|
|
|
setIcon(kHierarchyColumnIsSelectable, QIcon(textureName).pixmap(UICANVASEDITOR_HIERARCHY_HEADER_ICON_SIZE, UICANVASEDITOR_HIERARCHY_HEADER_ICON_SIZE));
|
|
}
|
|
}
|
|
|
|
void HierarchyItem::UpdateChildIcon()
|
|
{
|
|
// Seed the list.
|
|
HierarchyItemRawPtrList items;
|
|
HierarchyHelpers::AppendAllChildrenToEndOfList(this, items);
|
|
|
|
// Update child icons.
|
|
HierarchyHelpers::TraverseListAndAllChildren(items,
|
|
[](HierarchyItem* childItem)
|
|
{
|
|
childItem->UpdateIcon();
|
|
});
|
|
}
|
|
|
|
HierarchyItem* HierarchyItem::Parent() const
|
|
{
|
|
// It's ok to return a nullptr.
|
|
// nullptr normally happens when we've reached the invisibleRootItem(),
|
|
// We DON'T consider the invisibleRootItem() the parent of a HierarchyItem.
|
|
return HierarchyItem::RttiCast(QTreeWidgetItem::parent());
|
|
}
|
|
|
|
HierarchyItem* HierarchyItem::Child(int i) const
|
|
{
|
|
HierarchyItem* item = HierarchyItem::RttiCast(QTreeWidgetItem::child(i));
|
|
AZ_Assert(item, "There's an item in the Hierarchy that isn't a HierarchyItem.");
|
|
|
|
return item;
|
|
}
|
|
|
|
void HierarchyItem::SetMark(bool m)
|
|
{
|
|
m_mark = m;
|
|
}
|
|
|
|
bool HierarchyItem::GetMark()
|
|
{
|
|
return m_mark;
|
|
}
|
|
|
|
void HierarchyItem::SetPreMove(AZ::EntityId parentId, int childRow)
|
|
{
|
|
m_preMoveParentId = parentId;
|
|
m_preMoveChildRow = childRow;
|
|
}
|
|
|
|
AZ::EntityId HierarchyItem::GetPreMoveParentId()
|
|
{
|
|
return m_preMoveParentId;
|
|
}
|
|
|
|
int HierarchyItem::GetPreMoveChildRow()
|
|
{
|
|
return m_preMoveChildRow;
|
|
}
|
|
|
|
void HierarchyItem::ReplaceElement(const AZStd::string& xml, const AZStd::unordered_set<AZ::Data::AssetId>& referencedSliceAssets)
|
|
{
|
|
AZ_Assert(!xml.empty(), "XML is empty");
|
|
|
|
AZ::Entity* parentEntity = Parent() ? Parent()->GetElement() : nullptr;
|
|
AZ::Entity* replaceEntity = GetElement();
|
|
|
|
// find the element after the one to be replaced
|
|
AZ::Entity* insertBeforeEntity = nullptr;
|
|
{
|
|
LyShine::EntityArray childElements;
|
|
if (parentEntity)
|
|
{
|
|
EBUS_EVENT_ID_RESULT(childElements, parentEntity->GetId(), UiElementBus, GetChildElements);
|
|
}
|
|
else
|
|
{
|
|
EBUS_EVENT_ID_RESULT(childElements, m_editorWindow->GetCanvas(), UiCanvasBus, GetChildElements);
|
|
}
|
|
|
|
// find the enity we are replacing in the list (it must exist)
|
|
auto iter = std::find(childElements.begin(), childElements.end(), replaceEntity);
|
|
AZ_Assert(iter != childElements.end(), "Entity not found");
|
|
|
|
// if there is an element after the replace element then that is the one to insert before
|
|
if (++iter != childElements.end())
|
|
{
|
|
insertBeforeEntity = *iter;
|
|
}
|
|
}
|
|
|
|
// If restoring to a slice, keep a reference to the slice asset so it isn't released when the entity
|
|
// is deleted, only to immediately reload upon restoring.
|
|
AZStd::vector<AZ::Data::Asset<AZ::SliceAsset>> preservedAssetsRefs;
|
|
for (auto assetId : referencedSliceAssets)
|
|
{
|
|
preservedAssetsRefs.push_back(AZ::Data::AssetManager::Instance().FindAsset(assetId, AZ::Data::AssetLoadBehavior::Default));
|
|
}
|
|
|
|
// Discard the old element.
|
|
DeleteElement();
|
|
|
|
// Load the new element.
|
|
SerializeHelpers::RestoreSerializedElements(m_editorWindow->GetCanvas(),
|
|
parentEntity,
|
|
insertBeforeEntity,
|
|
m_editorWindow->GetEntityContext(),
|
|
xml,
|
|
false,
|
|
nullptr);
|
|
|
|
// Update any visual information that may have changed with this element or any of its descendants
|
|
UpdateEditorOnlyInfoRecursive();
|
|
}
|
|
|
|
void HierarchyItem::UpdateSliceInfo()
|
|
{
|
|
// This is deliberately slightly different to the blue color used for hover, so that we can still see a change on hover
|
|
static const QColor sliceForegroundColor(117, 156, 254);
|
|
|
|
//determine if entity belongs to a slice
|
|
AZ::SliceComponent::SliceInstanceAddress sliceAddress;
|
|
AzFramework::SliceEntityRequestBus::EventResult(sliceAddress, m_elementId,
|
|
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
|
|
bool isSliceEntity = sliceAddress.IsValid();
|
|
|
|
AZStd::string sliceAssetName;
|
|
if (isSliceEntity)
|
|
{
|
|
auto sliceReference = sliceAddress.GetReference();
|
|
auto sliceInstance = sliceAddress.GetInstance();
|
|
|
|
// determine slice asset name (for tooltip display)
|
|
AZ::Data::AssetCatalogRequestBus::BroadcastResult(sliceAssetName, &AZ::Data::AssetCatalogRequests::GetAssetPathById, sliceReference->GetSliceAsset().GetId());
|
|
|
|
//determine if entity parent belongs to a slice
|
|
AZ::EntityId parentId;
|
|
EBUS_EVENT_ID_RESULT(parentId, m_elementId, UiElementBus, GetParentEntityId);
|
|
|
|
AZ::SliceComponent::SliceInstanceAddress parentSliceAddress;
|
|
AzFramework::SliceEntityRequestBus::EventResult(parentSliceAddress, parentId,
|
|
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
|
|
|
|
//we're a slice root if our parent doesn't have a slice reference or instance or our parent slice reference or instances don't match ours
|
|
auto parentSliceReference = parentSliceAddress.GetReference();
|
|
auto parentSliceInstance = parentSliceAddress.GetInstance();
|
|
bool isSliceRoot = !parentSliceReference || !parentSliceInstance || (sliceReference != parentSliceReference) || (sliceInstance->GetId() != parentSliceInstance->GetId());
|
|
|
|
// set the text color to the slice color
|
|
setForeground(0, sliceForegroundColor);
|
|
|
|
// use bold or italic to indicate whether this is the root of the slice instance or a child entity within an instance
|
|
auto itemFont = font(0);
|
|
if (isSliceRoot)
|
|
{
|
|
itemFont.setBold(true);
|
|
}
|
|
else
|
|
{
|
|
itemFont.setItalic(true);
|
|
}
|
|
setFont(0, itemFont);
|
|
}
|
|
else
|
|
{
|
|
// get the normal text color from the palette
|
|
auto parentWidgetPtr = static_cast<QWidget*>(m_editorWindow);
|
|
setForeground(0, QBrush(parentWidgetPtr->palette().color(QPalette::Text)));
|
|
|
|
// set to normal font
|
|
auto itemFont = font(0);
|
|
itemFont.setBold(false);
|
|
itemFont.setItalic(false);
|
|
setFont(0, itemFont);
|
|
}
|
|
|
|
// Set tooltip to indicate which slice this is part of (if any)
|
|
QString tooltip = !sliceAssetName.empty() ? QString("Slice asset: %1").arg(sliceAssetName.data()) : QString("Slice asset: This entity is not part of a slice.");
|
|
setToolTip(0, tooltip);
|
|
}
|
|
|
|
void HierarchyItem::UpdateEditorOnlyInfo()
|
|
{
|
|
bool isEditorOnly = false;
|
|
AzToolsFramework::EditorOnlyEntityComponentRequestBus::EventResult(isEditorOnly, m_elementId, &AzToolsFramework::EditorOnlyEntityComponentRequests::IsEditorOnlyEntity);
|
|
|
|
if (isEditorOnly)
|
|
{
|
|
static const QColor editorOnlyBackgroundColor(60, 0, 0);
|
|
setBackground(0, editorOnlyBackgroundColor);
|
|
}
|
|
else
|
|
{
|
|
setBackground(0, Qt::transparent);
|
|
}
|
|
}
|
|
|
|
void HierarchyItem::UpdateEditorOnlyInfoRecursive()
|
|
{
|
|
UpdateEditorOnlyInfo();
|
|
for (int i = 0; i < childCount(); ++i)
|
|
{
|
|
HierarchyItem* item = HierarchyItem::RttiCast(child(i));
|
|
item->UpdateEditorOnlyInfoRecursive();
|
|
}
|
|
}
|
|
|
|
void HierarchyItem::SetNonSnappedOffsets(UiTransform2dInterface::Offsets offsets)
|
|
{
|
|
m_nonSnappedOffsets = offsets;
|
|
}
|
|
|
|
UiTransform2dInterface::Offsets HierarchyItem::GetNonSnappedOffsets()
|
|
{
|
|
return m_nonSnappedOffsets;
|
|
}
|
|
|
|
void HierarchyItem::SetNonSnappedZRotation(float rotation)
|
|
{
|
|
m_nonSnappedZRotation = rotation;
|
|
}
|
|
|
|
float HierarchyItem::GetNonSnappedZRotation()
|
|
{
|
|
return m_nonSnappedZRotation;
|
|
}
|
|
|
|
#include <moc_HierarchyItem.cpp>
|