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.
o3de/Gems/LyShine/Code/Editor/HierarchyMenu.cpp

524 lines
18 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 <AzToolsFramework/Slice/SliceUtilities.h>
#include <AzToolsFramework/ToolsComponents/EditorOnlyEntityComponentBus.h>
#include "SliceMenuHelpers.h"
#include "QtHelpers.h"
// Define for enabling/disabling the UI Slice system
#define ENABLE_UI_SLICE_MENU_ITEMS 1
HierarchyMenu::HierarchyMenu(HierarchyWidget* hierarchy,
size_t showMask,
bool addMenuForNewElement,
const QPoint* optionalPos)
: QMenu()
{
setStyleSheet(UICANVASEDITOR_QMENU_ITEM_DISABLED_STYLESHEET);
QTreeWidgetItemRawPtrQList selectedItems = hierarchy->selectedItems();
if (showMask & (Show::kNew_EmptyElement | Show::kNew_EmptyElementAtRoot))
{
QMenu* menu = (addMenuForNewElement ? addMenu("&New...") : this);
if (showMask & (Show::kNew_EmptyElement | Show::kNew_EmptyElementAtRoot))
{
New_EmptyElement(hierarchy, selectedItems, menu, (showMask & Show::kNew_EmptyElementAtRoot), optionalPos);
}
if (showMask & (Show::kNew_InstantiateSlice | Show::kNew_InstantiateSliceAtRoot))
{
New_ElementFromSlice(hierarchy, selectedItems, menu, (showMask & Show::kNew_InstantiateSliceAtRoot), optionalPos);
}
}
if (showMask & (Show::kNewSlice | Show::kPushToSlice))
{
SliceMenuItems(hierarchy, selectedItems, showMask);
}
addSeparator();
if (showMask & Show::kCutCopyPaste)
{
CutCopyPaste(hierarchy, selectedItems);
}
if (showMask & Show::kDeleteElement)
{
DeleteElement(hierarchy, selectedItems);
}
addSeparator();
if (showMask & Show::kAddComponents)
{
AddComponents(hierarchy, selectedItems);
}
addSeparator();
if (showMask & Show::kFindElements)
{
FindElements(hierarchy, selectedItems);
}
addSeparator();
if (showMask & Show::kEditorOnly)
{
EditorOnly(hierarchy, selectedItems);
}
}
void HierarchyMenu::CutCopyPaste(HierarchyWidget* hierarchy,
QTreeWidgetItemRawPtrQList& selectedItems)
{
QAction* action;
bool itemsAreSelected = (!selectedItems.isEmpty());
// Cut element.
{
action = new QAction("Cut", this);
action->setShortcut(QKeySequence::Cut);
action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
QObject::connect(action,
&QAction::triggered,
hierarchy,
[ hierarchy ]([[maybe_unused]] bool checked)
{
QMetaObject::invokeMethod(hierarchy, "Cut", Qt::QueuedConnection);
});
addAction(action);
if (!itemsAreSelected)
{
// Nothing has been selected.
// We want the menu to be visible, but disabled.
action->setEnabled(false);
}
}
// Copy element.
{
action = new QAction("Copy", this);
action->setShortcut(QKeySequence::Copy);
action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
QObject::connect(action,
&QAction::triggered,
hierarchy,
[ hierarchy ]([[maybe_unused]] bool checked)
{
QMetaObject::invokeMethod(hierarchy, "Copy", Qt::QueuedConnection);
});
addAction(action);
if (!itemsAreSelected)
{
// Nothing has been selected.
// We want the menu to be visible, but disabled.
action->setEnabled(false);
}
}
bool thereIsContentInTheClipboard = ClipboardContainsOurDataType();
// Paste element.
{
action = new QAction(QIcon(":/Icons/Eye_Open.png"), (itemsAreSelected ? "Paste as sibling" : "Paste"), this);
action->setShortcut(QKeySequence::Paste);
action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
QObject::connect(action,
&QAction::triggered,
hierarchy,
[ hierarchy ]([[maybe_unused]] bool checked)
{
QMetaObject::invokeMethod(hierarchy, "PasteAsSibling", Qt::QueuedConnection);
});
addAction(action);
if (!thereIsContentInTheClipboard)
{
// Nothing in the clipboard.
// We want the menu to be visible, but disabled.
action->setEnabled(false);
}
if (itemsAreSelected)
{
action = new QAction(QIcon(":/Icons/Eye_Open.png"), ("Paste as child"), this);
{
action->setShortcuts(QList<QKeySequence>{QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_V),
QKeySequence(Qt::META + Qt::SHIFT + Qt::Key_V)});
action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
}
QObject::connect(action,
&QAction::triggered,
hierarchy,
[hierarchy]([[maybe_unused]] bool checked)
{
QMetaObject::invokeMethod(hierarchy, "PasteAsChild", Qt::QueuedConnection);
});
addAction(action);
if (!thereIsContentInTheClipboard)
{
// Nothing in the clipboard.
// We want the menu to be visible, but disabled.
action->setEnabled(false);
}
}
}
}
void HierarchyMenu::SliceMenuItems(HierarchyWidget* hierarchy,
QTreeWidgetItemRawPtrQList& selectedItems,
size_t showMask)
{
#if ENABLE_UI_SLICE_MENU_ITEMS
// Get the EntityId's of the selected elements
auto selectedEntities = SelectionHelpers::GetSelectedElementIds(hierarchy, selectedItems, false);
// Determine if any of the selected entities are in a slice
AZ::SliceComponent::EntityAncestorList referenceAncestors;
AZStd::vector<AZ::SliceComponent::SliceInstanceAddress> sliceInstances;
for (const AZ::EntityId& entityId : selectedEntities)
{
AZ::SliceComponent::SliceInstanceAddress sliceAddress;
AzFramework::SliceEntityRequestBus::EventResult(sliceAddress, entityId,
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
if (sliceAddress.IsValid())
{
if (sliceInstances.end() == AZStd::find(sliceInstances.begin(), sliceInstances.end(), sliceAddress))
{
if (sliceInstances.empty())
{
sliceAddress.GetReference()->GetInstanceEntityAncestry(entityId, referenceAncestors);
}
sliceInstances.push_back(sliceAddress);
}
}
}
bool sliceSelected = sliceInstances.size() > 0;
if (sliceSelected)
{
if (showMask & Show::kPushToSlice)
{
// Push slice action currently acts on entities and all descendants, so include those as part of the selection
AzToolsFramework::EntityIdSet selectedTransformHierarchyEntities =
hierarchy->GetEditorWindow()->GetSliceManager()->GatherEntitiesAndAllDescendents(selectedEntities);
AzToolsFramework::EntityIdList selectedPushEntities;
selectedPushEntities.insert(selectedPushEntities.begin(), selectedTransformHierarchyEntities.begin(), selectedTransformHierarchyEntities.end());
QAction* action = addAction(QObject::tr("&Push to Slice..."));
QObject::connect(action, &QAction::triggered, hierarchy, [hierarchy, selectedPushEntities]
{
hierarchy->GetEditorWindow()->GetSliceManager()->PushEntitiesModal(selectedPushEntities, nullptr);
}
);
}
if (showMask & Show::kNewSlice)
{
QAction* action = addAction("Make Cascaded Slice from Selected Slices && Entities...");
QObject::connect(action, &QAction::triggered, hierarchy, [hierarchy]
{
hierarchy->GetEditorWindow()->GetSliceManager()->MakeSliceFromSelectedItems(hierarchy, true);
}
);
action = addAction(QObject::tr("Make Detached Slice from Selected Entities..."));
QObject::connect(action, &QAction::triggered, hierarchy, [hierarchy]
{
hierarchy->GetEditorWindow()->GetSliceManager()->MakeSliceFromSelectedItems(hierarchy, false);
}
);
}
if (showMask & Show::kPushToSlice) // use the push to slice flag to show detach since it appears in all the same situations
{
// Detach slice entity
{
// Detach entities action currently acts on entities and all descendants, so include those as part of the selection
AzToolsFramework::EntityIdSet selectedTransformHierarchyEntities =
hierarchy->GetEditorWindow()->GetSliceManager()->GatherEntitiesAndAllDescendents(selectedEntities);
AzToolsFramework::EntityIdList selectedDetachEntities;
selectedDetachEntities.insert(selectedDetachEntities.begin(), selectedTransformHierarchyEntities.begin(), selectedTransformHierarchyEntities.end());
QString detachEntitiesActionText;
if (selectedDetachEntities.size() == 1)
{
detachEntitiesActionText = QObject::tr("Detach slice entity...");
}
else
{
detachEntitiesActionText = QObject::tr("Detach slice entities...");
}
QAction* action = addAction(detachEntitiesActionText);
QObject::connect(action, &QAction::triggered, hierarchy, [hierarchy, selectedDetachEntities]
{
hierarchy->GetEditorWindow()->GetSliceManager()->DetachSliceEntities(selectedDetachEntities);
hierarchy->UpdateSliceInfo();
}
);
}
// Detach slice instance
{
QString detachSlicesActionText;
if (sliceInstances.size() == 1)
{
detachSlicesActionText = QObject::tr("Detach slice instance...");
}
else
{
detachSlicesActionText = QObject::tr("Detach slice instances...");
}
QAction* action = addAction(detachSlicesActionText);
QObject::connect(action, &QAction::triggered, hierarchy, [hierarchy, selectedEntities]
{
hierarchy->GetEditorWindow()->GetSliceManager()->DetachSliceInstances(selectedEntities);
hierarchy->UpdateSliceInfo();
}
);
}
// Edit slice in new tab
{
QMenu* menu = addMenu("Edit slice in new tab");
// Catalog all unique slices to which any of the selected entities are associated (anywhere in their ancestry).
// This is used to make a menu allowing any of them to be edited in a new tab
AZStd::vector<AZ::Data::AssetId> slicesAddedToMenu;
AZ::SliceComponent::EntityAncestorList tempAncestors;
for (AZ::EntityId entityId : selectedEntities)
{
AZ::SliceComponent::SliceInstanceAddress sliceAddress;
AzFramework::SliceEntityRequestBus::EventResult(sliceAddress, entityId,
&AzFramework::SliceEntityRequestBus::Events::GetOwningSlice);
if (sliceAddress.IsValid())
{
tempAncestors.clear();
sliceAddress.GetReference()->GetInstanceEntityAncestry(entityId, tempAncestors);
for (const AZ::SliceComponent::Ancestor& ancestor : tempAncestors)
{
const AZ::Data::Asset<AZ::SliceAsset>& sliceAsset = ancestor.m_sliceAddress.GetReference()->GetSliceAsset();
// If this slice has not already been added to the menu then add it.
if (slicesAddedToMenu.end() == AZStd::find(slicesAddedToMenu.begin(), slicesAddedToMenu.end(), sliceAsset.GetId()))
{
const AZStd::string& assetPath = sliceAsset.GetHint();
slicesAddedToMenu.push_back(sliceAsset.GetId());
QAction* action = menu->addAction(assetPath.c_str());
QObject::connect(action, &QAction::triggered, [hierarchy, sliceAsset]
{
hierarchy->GetEditorWindow()->EditSliceInNewTab(sliceAsset.GetId());
}
);
}
}
}
}
}
}
}
else
{
if (showMask & Show::kNewSlice)
{
QAction* action = addAction(QObject::tr("Make New &Slice from Selection..."));
QObject::connect(action, &QAction::triggered, hierarchy, [hierarchy]
{
hierarchy->GetEditorWindow()->GetSliceManager()->MakeSliceFromSelectedItems(hierarchy, false);
}
);
if (selectedItems.size() == 0)
{
action->setEnabled(false);
}
}
}
#endif
}
void HierarchyMenu::New_EmptyElement(HierarchyWidget* hierarchy,
QTreeWidgetItemRawPtrQList& selectedItems,
QMenu* menu,
bool addAtRoot,
const QPoint* optionalPos)
{
menu->addAction(HierarchyHelpers::CreateAddElementAction(hierarchy,
selectedItems,
addAtRoot,
optionalPos));
}
void HierarchyMenu::New_ElementFromSlice(HierarchyWidget* hierarchy,
QTreeWidgetItemRawPtrQList& selectedItems,
QMenu* menu,
bool addAtRoot,
const QPoint* optionalPos)
{
#if ENABLE_UI_SLICE_MENU_ITEMS
AZ::Vector2 viewportPosition(-1.0f,-1.0f); // indicates no viewport position specified
if (optionalPos)
{
// Convert position to render viewport coords
QPointF scaledPosition = *optionalPos * hierarchy->GetEditorWindow()->GetViewport()->WidgetToViewportFactor();
viewportPosition = QtHelpers::QPointFToVector2(scaledPosition);
}
SliceMenuHelpers::CreateInstantiateSliceMenu(hierarchy,
selectedItems,
menu,
addAtRoot,
viewportPosition);
QAction* action = menu->addAction(QObject::tr("Element from Slice &Browser..."));
QObject::connect(
action,
&QAction::triggered,
hierarchy, [hierarchy, optionalPos]
{
AZ::Vector2 viewportPosition(-1.0f,-1.0f); // indicates no viewport position specified
if (optionalPos)
{
// Convert position to render viewport coords
QPointF scaledPosition = *optionalPos * hierarchy->GetEditorWindow()->GetViewport()->WidgetToViewportFactor();
viewportPosition = QtHelpers::QPointFToVector2(scaledPosition);
}
hierarchy->GetEditorWindow()->GetSliceManager()->InstantiateSliceUsingBrowser(hierarchy, viewportPosition);
}
);
#endif
}
void HierarchyMenu::AddComponents(HierarchyWidget* hierarchy,
QTreeWidgetItemRawPtrQList& selectedItems)
{
addActions(ComponentHelpers::CreateAddComponentActions(hierarchy,
selectedItems,
this));
}
void HierarchyMenu::DeleteElement(HierarchyWidget* hierarchy,
QTreeWidgetItemRawPtrQList& selectedItems)
{
QAction* action;
// Delete element.
{
action = new QAction("Delete", this);
action->setShortcut(QKeySequence::Delete);
action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
QObject::connect(action,
&QAction::triggered,
hierarchy,
[ hierarchy ]([[maybe_unused]] bool checked)
{
QMetaObject::invokeMethod(hierarchy, "DeleteSelectedItems", Qt::QueuedConnection);
});
addAction(action);
if (selectedItems.empty())
{
// Nothing has been selected.
// We want the menu to be visible, but disabled.
action->setEnabled(false);
}
}
}
void HierarchyMenu::FindElements(HierarchyWidget* hierarchy,
[[maybe_unused]] QTreeWidgetItemRawPtrQList& selectedItems)
{
QAction* action;
// Find elements
{
action = new QAction("Find Elements...", this);
action->setShortcut(QKeySequence::Find);
action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
QObject::connect(action,
&QAction::triggered,
hierarchy,
[ hierarchy ]([[maybe_unused]] bool checked)
{
hierarchy->GetEditorWindow()->ShowEntitySearchModal();
});
addAction(action);
}
}
void HierarchyMenu::EditorOnly(HierarchyWidget* hierarchy,
QTreeWidgetItemRawPtrQList& selectedItems)
{
QAction* action;
// Toggle editor only state.
{
action = new QAction("Editor Only", this);
action->setCheckable(true);
if (selectedItems.empty())
{
action->setChecked(false);
action->setEnabled(false);
}
else
{
EntityHelpers::EntityIdList entityIds = SelectionHelpers::GetSelectedElementIds(hierarchy, selectedItems, false);
bool checked = true;
for (auto entityId : entityIds)
{
bool isEditorOnly = false;
AzToolsFramework::EditorOnlyEntityComponentRequestBus::EventResult(isEditorOnly, entityId, &AzToolsFramework::EditorOnlyEntityComponentRequests::IsEditorOnlyEntity);
if (!isEditorOnly)
{
checked = false;
break;
}
}
action->setChecked(checked);
action->setEnabled(true);
}
QObject::connect(action,
&QAction::triggered,
[hierarchy](bool checked)
{
QMetaObject::invokeMethod(hierarchy, "SetEditorOnlyForSelectedItems", Qt::QueuedConnection, Q_ARG(bool, checked));
});
addAction(action);
}
}
#include <moc_HierarchyMenu.cpp>