git mv Code\Sandbox\Plugins Code/Editor/Plugins

Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com>
This commit is contained in:
Esteban Papp
2021-06-29 12:42:54 -07:00
parent e34e36cb35
commit 1696680240
215 changed files with 0 additions and 0 deletions
@@ -0,0 +1,98 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "ComponentEntityEditorPlugin_precompiled.h"
#include "CategoriesList.h"
ComponentCategoryList::ComponentCategoryList(QWidget* parent /*= nullptr*/)
: QTreeWidget(parent)
{
}
void ComponentCategoryList::Init()
{
setColumnCount(1);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setDragDropMode(QAbstractItemView::DragDropMode::DragOnly);
setDragEnabled(true);
setSelectionMode(QAbstractItemView::ExtendedSelection);
setAllColumnsShowFocus(true);
setStyleSheet("QTreeWidget { selection-background-color: rgba(255,255,255,0.2); }");
QStringList headers;
headers << tr("Categories");
setHeaderLabels(headers);
const QString parentCategoryIconPath = QString("Icons/PropertyEditor/Browse_on.png");
const QString categoryIconPath = QString("Icons/PropertyEditor/Browse.png");
QTreeWidgetItem* allCategory = new QTreeWidgetItem(this);
allCategory->setText(0, "All");
allCategory->setIcon(0, QIcon(categoryIconPath));
// Need this briefly to collect the list of available categories.
ComponentDataModel dataModel(this);
for (const auto& cat : dataModel.GetCategories())
{
QString categoryString = QString(cat.c_str());
QStringList categories = categoryString.split('/', Qt::SkipEmptyParts);
QTreeWidgetItem* parent = nullptr;
QTreeWidgetItem* categoryWidget = nullptr;
for (const auto& categoryName : categories)
{
if (parent)
{
categoryWidget = new QTreeWidgetItem(parent);
categoryWidget->setIcon(0, QIcon(categoryIconPath));
// Store the full category path in a user role because we'll need it to locate the actual category
categoryWidget->setData(0, Qt::UserRole, QVariant::fromValue(categoryString));
}
else
{
auto existingCategory = findItems(categoryName, Qt::MatchExactly);
if (existingCategory.empty())
{
categoryWidget = new QTreeWidgetItem(this);
categoryWidget->setIcon(0, QIcon(parentCategoryIconPath));
}
else
{
categoryWidget = static_cast<QTreeWidgetItem*>(existingCategory.first());
categoryWidget->setIcon(0, QIcon(parentCategoryIconPath));
}
}
parent = categoryWidget;
categoryWidget->setText(0, categoryName);
}
}
expandAll();
connect(this, &QTreeWidget::itemClicked, this, &ComponentCategoryList::OnItemClicked);
}
void ComponentCategoryList::OnItemClicked(QTreeWidgetItem* item, int /*column*/)
{
QVariant userData = item->data(0, Qt::UserRole);
if (userData.isValid())
{
// Send in the full category path, not just the child category name
emit OnCategoryChange(userData.value<QString>().toStdString().c_str());
}
else
{
emit OnCategoryChange(item->text(0).toStdString().c_str());
}
}
#include <UI/ComponentPalette/moc_CategoriesList.cpp>
@@ -0,0 +1,38 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include "ComponentDataModel.h"
#include <QTreeWidget>
#endif
//! ComponentCategoryList
//! Provides a list of all reflected categories that users can select for quick
//! filtering the filtered component list.
class ComponentCategoryList : public QTreeWidget
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(ComponentCategoryList, AZ::SystemAllocator, 0);
explicit ComponentCategoryList(QWidget* parent = nullptr);
void Init();
Q_SIGNALS:
void OnCategoryChange(const char* category);
protected:
// Will emit OnCategoryChange signal
void OnItemClicked(QTreeWidgetItem* item, int column);
};
@@ -0,0 +1,548 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "ComponentEntityEditorPlugin_precompiled.h"
#include "ComponentDataModel.h"
#include "Include/IObjectManager.h"
#include "Objects/SelectionGroup.h"
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/ToolsComponents/ComponentMimeData.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Commands/EntityStateCommand.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/API/EntityCompositionRequestBus.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <Editor/IEditor.h>
#include <Editor/Viewport.h>
#include <Editor/ViewManager.h>
#include <CryCommon/MathConversion.h>
#include <AzQtComponents/DragAndDrop/ViewportDragAndDrop.h>
#include <QMimeData>
namespace
{
// This is a helper function that given an object that derives from QAbstractItemModel,
// it will request the model's "ClassDataRole" class data for an entry and use that
// information to create a new entity with the selected components.
AZ::EntityId CreateEntityFromSelection(const QModelIndexList& selection, QAbstractItemModel* model)
{
AZ::Vector3 position = AZ::Vector3::CreateZero();
CViewport *view = GetIEditor()->GetViewManager()->GetGameViewport();
int width, height;
view->GetDimensions(&width, &height);
position = LYVec3ToAZVec3(view->ViewToWorld(QPoint(width / 2, height / 2)));
AZ::EntityId newEntityId;
EBUS_EVENT_RESULT(newEntityId, AzToolsFramework::EditorRequests::Bus, CreateNewEntityAtPosition, position, AZ::EntityId());
if (newEntityId.IsValid())
{
// Add all the selected components.
AZ::ComponentTypeList componentsToAdd;
for (auto index : selection)
{
// We only need to consider the first column, it's important that the data() function that
// returns ComponentDataModel::ClassDataRole also does so for the first column.
if (index.column() != 0)
{
continue;
}
QVariant classDataVariant = model->data(index, ComponentDataModel::ClassDataRole);
if (classDataVariant.isValid())
{
const AZ::SerializeContext::ClassData* classData = reinterpret_cast<const AZ::SerializeContext::ClassData*>(classDataVariant.value<void*>());
componentsToAdd.push_back(classData->m_typeId);
}
}
AzToolsFramework::EntityCompositionRequestBus::Broadcast(&AzToolsFramework::EntityCompositionRequests::AddComponentsToEntities, AzToolsFramework::EntityIdList{ newEntityId }, componentsToAdd);
return newEntityId;
}
return AZ::EntityId();
}
}
namespace ComponentDataUtilities
{
// This is a helper function to add the specified components to the selected entities, it relies on the provided
// QAbstractItemModel to determine the appropriate ClassData to use to create the components (given that some widgets
// may provide proxy models that alter the order).
void AddComponentsToSelectedEntities(const QModelIndexList& selectedComponents, QAbstractItemModel* model)
{
AzToolsFramework::EntityIdList selectedEntities;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(selectedEntities, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities);
if (selectedEntities.empty())
{
return;
}
// Add all the selected components.
AZ::ComponentTypeList componentsToAdd;
for (auto index : selectedComponents)
{
// We only need to consider the first column, it's important that the data() function that
// returns ComponentDataModel::ClassDataRole also does so for the first column.
if (index.column() != 0)
{
continue;
}
QVariant classDataVariant = model->data(index, ComponentDataModel::ClassDataRole);
if (classDataVariant.isValid())
{
const AZ::SerializeContext::ClassData* classData = reinterpret_cast<const AZ::SerializeContext::ClassData*>(classDataVariant.value<void*>());
componentsToAdd.push_back(classData->m_typeId);
}
}
AzToolsFramework::EntityCompositionRequestBus::Broadcast(&AzToolsFramework::EntityCompositionRequests::AddComponentsToEntities, selectedEntities, componentsToAdd);
}
}
// ComponentDataModel
//////////////////////////////////////////////////////////////////////////
ComponentDataModel::ComponentDataModel(QObject* parent)
: QAbstractTableModel(parent)
{
AZ::SerializeContext* serializeContext = nullptr;
EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(serializeContext, "Failed to acquire application serialize context.");
serializeContext->EnumerateDerived<AZ::Component>([this](const AZ::SerializeContext::ClassData* classData, const AZ::Uuid&) -> bool
{
bool allowed = false;
bool hidden = false;
AZStd::string category = "Miscellaneous";
if (classData->m_editData)
{
for (const AZ::Edit::ElementData& element : classData->m_editData->m_elements)
{
if (element.m_elementId == AZ::Edit::ClassElements::EditorData)
{
AZStd::string iconPath;
EBUS_EVENT_RESULT(iconPath, AzToolsFramework::EditorRequests::Bus, GetComponentEditorIcon, classData->m_typeId, nullptr);
if (!iconPath.empty())
{
m_componentIcons[classData->m_typeId] = QIcon(iconPath.c_str());
}
for (const AZ::Edit::AttributePair& attribPair : element.m_attributes)
{
if (attribPair.first == AZ::Edit::Attributes::AppearsInAddComponentMenu)
{
if (auto data = azdynamic_cast<AZ::Edit::AttributeData<AZ::Crc32>*>(attribPair.second))
{
if (data->Get(nullptr) == AZ_CRC("Game"))
{
allowed = true;
}
}
}
else if (attribPair.first == AZ::Edit::Attributes::AddableByUser)
{
// skip this component if user is not allowed to add it directly
if (auto data = azdynamic_cast<AZ::Edit::AttributeData<bool>*>(attribPair.second))
{
if (!data->Get(nullptr))
{
hidden = true;
}
}
}
else if (attribPair.first == AZ::Edit::Attributes::Category)
{
if (auto data = azdynamic_cast<AZ::Edit::AttributeData<const char*>*>(attribPair.second))
{
category = data->Get(nullptr);
}
}
}
break;
}
}
}
if (allowed && !hidden)
{
m_componentList.push_back(classData);
m_componentMap[category].push_back(classData);
m_categories.insert(category);
}
return true;
});
// we'd like viewport events
AzQtComponents::DragAndDropEventsBus::Handler::BusConnect(AzQtComponents::DragAndDropContexts::EditorViewport);
}
ComponentDataModel::~ComponentDataModel()
{
AzQtComponents::DragAndDropEventsBus::Handler::BusDisconnect();
}
Qt::ItemFlags ComponentDataModel::flags([[maybe_unused]] const QModelIndex &index) const
{
return Qt::ItemFlags(
Qt::ItemIsEnabled |
Qt::ItemIsDragEnabled |
Qt::ItemIsDropEnabled |
Qt::ItemIsSelectable);
}
const AZ::SerializeContext::ClassData* ComponentDataModel::GetClassData(const QModelIndex& index) const
{
int row = index.row();
if (row < 0 || row >= m_componentList.size())
{
return nullptr;
}
return m_componentList[row];
}
const char* ComponentDataModel::GetCategory(const AZ::SerializeContext::ClassData* classData)
{
if (classData)
{
if (auto editorDataElement = classData->m_editData->FindElementData(AZ::Edit::ClassElements::EditorData))
{
if (auto categoryAttribute = editorDataElement->FindAttribute(AZ::Edit::Attributes::Category))
{
if (auto categoryData = azdynamic_cast<AZ::Edit::AttributeData<const char*>*>(categoryAttribute))
{
const char* result = categoryData->Get(nullptr);
if (result)
{
return result;
}
}
}
}
}
return "";
}
QModelIndex ComponentDataModel::index(int row, int column, const QModelIndex &parent /*= QModelIndex()*/) const
{
if (row >= rowCount(parent) || column >= columnCount(parent))
{
return QModelIndex();
}
return createIndex(row, column, (void*)(m_componentList[row]));
}
QModelIndex ComponentDataModel::parent([[maybe_unused]] const QModelIndex &child) const
{
return QModelIndex();
}
int ComponentDataModel::rowCount([[maybe_unused]] const QModelIndex &parent /*= QModelIndex()*/) const
{
return m_componentList.size();
}
int ComponentDataModel::columnCount([[maybe_unused]] const QModelIndex &parent /*= QModelIndex()*/) const
{
return ColumnIndex::Count;
}
QVariant ComponentDataModel::data(const QModelIndex &index, int role /*= Qt::DisplayRole*/) const
{
if (index.isValid())
{
const AZ::SerializeContext::ClassData* classData = m_componentList[index.row()];
if (!classData)
{
return QVariant();
}
switch (role)
{
case ClassDataRole:
if (index.column() == 0) // Only get data for one column
{
return QVariant::fromValue<void*>(reinterpret_cast<void*>(const_cast<AZ::SerializeContext::ClassData*>(classData)));
}
break;
case Qt::DisplayRole:
{
if (index.column() == ColumnIndex::Name)
{
return QVariant(classData->m_editData->m_name);
}
else
if (index.column() == ColumnIndex::Category)
{
if (auto editorDataElement = classData->m_editData->FindElementData(AZ::Edit::ClassElements::EditorData))
{
if (auto categoryAttribute = editorDataElement->FindAttribute(AZ::Edit::Attributes::Category))
{
if (auto categoryData = azdynamic_cast<const AZ::Edit::AttributeData<const char*>*>(categoryAttribute))
{
return QVariant(categoryData->Get(nullptr));
}
}
}
}
}
break;
case Qt::ToolTipRole:
{
return QVariant(classData->m_editData->m_description);
}
case Qt::DecorationRole:
{
if (index.column() == ColumnIndex::Icon)
{
auto iconIterator = m_componentIcons.find(classData->m_typeId);
if (iconIterator != m_componentIcons.end())
{
return iconIterator->second;
}
}
}
break;
default:
break;
}
}
return QVariant();
}
QMimeData* ComponentDataModel::mimeData(const QModelIndexList& indices) const
{
QModelIndexList list;
// Filter out columns we are not interested in.
for (const QModelIndex& index : indices)
{
if (index.column() == 0)
{
list.push_back(index);
}
}
AZStd::vector<const AZ::SerializeContext::ClassData*> sortedList;
for (QModelIndex index : list)
{
QVariant classDataVariant = index.data(ComponentDataModel::ClassDataRole);
if (classDataVariant.isValid())
{
const AZ::SerializeContext::ClassData* classData = reinterpret_cast<const AZ::SerializeContext::ClassData*>(classDataVariant.value<void*>());
sortedList.push_back(classData);
}
}
QMimeData* mimeData = nullptr;
if (!sortedList.empty())
{
mimeData = AzToolsFramework::ComponentTypeMimeData::Create(sortedList).release();
}
return mimeData;
}
bool ComponentDataModel::CanAcceptDragAndDropEvent(QDropEvent* event, AzQtComponents::DragAndDropContextBase& context) const
{
using namespace AzToolsFramework;
using namespace AzQtComponents;
// if a listener with a higher priority already claimed this event, do not touch it.
if ((!event) || (event->isAccepted()) || (!event->mimeData()))
{
return false;
}
ViewportDragContext* contextVP = azrtti_cast<ViewportDragContext*>(&context);
if (!contextVP)
{
// not a viewport event. This is for some other GUI such as the main window itself.
return false;
}
AZStd::vector<const AZ::SerializeContext::ClassData*> componentClassDataList;
return AzToolsFramework::ComponentTypeMimeData::Get(event->mimeData(), componentClassDataList);
}
void ComponentDataModel::DragEnter(QDragEnterEvent* event, AzQtComponents::DragAndDropContextBase& context)
{
if (CanAcceptDragAndDropEvent(event, context))
{
event->setDropAction(Qt::CopyAction);
event->setAccepted(true);
// opportunities to show special highlights, or ghosted entities or previews here.
}
}
void ComponentDataModel::DragMove(QDragMoveEvent* event, AzQtComponents::DragAndDropContextBase& context)
{
if (CanAcceptDragAndDropEvent(event, context))
{
event->setDropAction(Qt::CopyAction);
event->setAccepted(true);
// opportunities to update special highlights, or ghosted entities or previews here.
}
}
void ComponentDataModel::DragLeave(QDragLeaveEvent* /*event*/)
{
// opportunities to remove ghosted entities or previews here.
}
void ComponentDataModel::Drop(QDropEvent* event, AzQtComponents::DragAndDropContextBase& context)
{
using namespace AzToolsFramework;
using namespace AzQtComponents;
// ALWAYS CHECK - you are not the only one connected to this bus, and someone else may have already
// handled the event or accepted the drop - it might not contain types relevant to you.
// you still get informed about the drop event in case you did some stuff in your gui and need to clean it up.
if (!CanAcceptDragAndDropEvent(event, context))
{
return;
}
// note that the above call already checks all the pointers such as event, or whether context is a VP context, mimetype, etc
ViewportDragContext* contextVP = azrtti_cast<ViewportDragContext*>(&context);
// we don't get given this action by Qt unless we already returned accepted from one of the other ones (such as drag move of drag enter)
event->setDropAction(Qt::CopyAction);
event->setAccepted(true);
AzToolsFramework::ScopedUndoBatch undo("Create entity from components");
const AZStd::string name = AZStd::string::format("Entity%d", GetIEditor()->GetObjectManager()->GetObjectCount());
AZ::Entity* newEntity = aznew AZ::Entity(name.c_str());
if (newEntity)
{
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(&AzToolsFramework::EditorEntityContextRequests::AddRequiredComponents, *newEntity);
auto* transformComponent = newEntity->FindComponent<AzToolsFramework::Components::TransformComponent>();
if (transformComponent)
{
transformComponent->SetWorldTM(AZ::Transform::CreateTranslation(contextVP->m_hitLocation));
}
// Add the entity to the editor context, which activates it and creates the sandbox object.
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(&AzToolsFramework::EditorEntityContextRequests::AddEditorEntity, newEntity);
// Prepare undo command last so it captures the final state of the entity.
AzToolsFramework::EntityCreateCommand* command = aznew AzToolsFramework::EntityCreateCommand(static_cast<AZ::u64>(newEntity->GetId()));
command->Capture(newEntity);
command->SetParent(undo.GetUndoBatch());
// Only need to add components to the new entity
AzToolsFramework::EntityIdList entities = { newEntity->GetId() };
AZStd::vector<const AZ::SerializeContext::ClassData*> componentClassDataList;
AzToolsFramework::ComponentTypeMimeData::Get(event->mimeData(), componentClassDataList);
AZ::ComponentTypeList componentsToAdd;
for (auto classData : componentClassDataList)
{
if (!classData)
{
continue;
}
componentsToAdd.push_back(classData->m_typeId);
}
AzToolsFramework::EntityCompositionRequests::AddComponentsOutcome addedComponentsResult = AZ::Failure(AZStd::string("Failed to call AddComponentsToEntities on EntityCompositionRequestBus"));
AzToolsFramework::EntityCompositionRequestBus::BroadcastResult(addedComponentsResult, &AzToolsFramework::EntityCompositionRequests::AddComponentsToEntities, entities, componentsToAdd);
ToolsApplicationRequests::Bus::Broadcast(&ToolsApplicationRequests::AddDirtyEntity, newEntity->GetId());
AzToolsFramework::ToolsApplicationRequestBus::Broadcast(&AzToolsFramework::ToolsApplicationRequests::SetSelectedEntities, entities);
}
}
AZ::EntityId ComponentDataProxyModel::NewEntityFromSelection(const QModelIndexList& selection)
{
return CreateEntityFromSelection(selection, this);
}
AZ::EntityId ComponentDataModel::NewEntityFromSelection(const QModelIndexList& selection)
{
return CreateEntityFromSelection(selection, this);
}
bool ComponentDataProxyModel::filterAcceptsRow(int sourceRow, [[maybe_unused]] const QModelIndex &sourceParent) const
{
if (m_selectedCategory.empty() && !filterRegExp().isValid())
return true;
ComponentDataModel* dataModel = static_cast<ComponentDataModel*>(sourceModel());
if (sourceRow < 0 || sourceRow >= dataModel->GetComponents().size())
{
return false;
}
const AZ::SerializeContext::ClassData* classData = dataModel->GetComponents()[sourceRow];
if (!classData)
{
return false;
}
// Get Category
if (!m_selectedCategory.empty())
{
AZStd::string currentCateogry = ComponentDataModel::GetCategory(classData);
if (AzFramework::StringFunc::Find(currentCateogry.c_str(), m_selectedCategory.c_str()))
{
return false;
}
}
if (filterRegExp().isValid())
{
QString componentName = QString::fromUtf8(classData->m_editData->m_name);
return componentName.contains(filterRegExp());
}
return true;
}
void ComponentDataProxyModel::SetSelectedCategory(const AZStd::string& category)
{
m_selectedCategory = category;
invalidate();
}
void ComponentDataProxyModel::ClearSelectedCategory()
{
m_selectedCategory.clear();
invalidate();
}
#include "UI/ComponentPalette/moc_ComponentDataModel.cpp"
@@ -0,0 +1,127 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QAbstractTableModel>
#include <QSortFilterProxyModel>
#include <AzCore/EBus/EBus.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/std/containers/set.h>
#include <AzCore/std/containers/vector.h>
#include <AzQtComponents/Buses/DragAndDrop.h>
#endif
namespace ComponentDataUtilities
{
// Given a list of selected components, use the provided model to get the components to add to any selected entities.
void AddComponentsToSelectedEntities(const QModelIndexList& selectedComponents, QAbstractItemModel* model);
}
class CViewport;
//! ComponentDataModel
//! Holds the data required to display components in a table, this includes component name, categories, icons.
class ComponentDataModel
: public QAbstractTableModel
, protected AzQtComponents::DragAndDropEventsBus::Handler // its okay if more than one of these is installed, the first one gets it.
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(ComponentDataModel, AZ::SystemAllocator, 0);
using ComponentClassList = AZStd::vector<const AZ::SerializeContext::ClassData*>;
using ComponentCategorySet = AZStd::set<AZStd::string>;
using ComponentClassMap = AZStd::unordered_map<AZStd::string, AZStd::vector<const AZ::SerializeContext::ClassData*>>;
using ComponentIconMap = AZStd::unordered_map<AZ::Uuid, QIcon>;
enum ColumnIndex
{
Icon,
Category,
Name,
Count
};
enum CustomRoles
{
ClassDataRole = Qt::UserRole + 1
};
ComponentDataModel(QObject* parent = nullptr);
~ComponentDataModel() override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &child) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QMimeData* mimeData(const QModelIndexList& indexes) const override;
const AZ::SerializeContext::ClassData* GetClassData(const QModelIndex&) const;
AZ::EntityId NewEntityFromSelection(const QModelIndexList& selection);
static const char* GetCategory(const AZ::SerializeContext::ClassData* classData);
ComponentClassList& GetComponents() { return m_componentList; }
ComponentCategorySet& GetCategories() { return m_categories; }
protected:
//////////////////////////////////////////////////////////////////////////
// AzQtComponents::DragAndDropEventsBus::Handler
//////////////////////////////////////////////////////////////////////////
void DragEnter(QDragEnterEvent* event, AzQtComponents::DragAndDropContextBase& context) override;
void DragMove(QDragMoveEvent* event, AzQtComponents::DragAndDropContextBase& context) override;
void DragLeave(QDragLeaveEvent* event) override;
void Drop(QDropEvent* event, AzQtComponents::DragAndDropContextBase& context) override;
bool CanAcceptDragAndDropEvent(QDropEvent* event, AzQtComponents::DragAndDropContextBase& context) const;
ComponentClassList m_componentList;
ComponentClassMap m_componentMap;
ComponentIconMap m_componentIcons;
ComponentCategorySet m_categories;
};
//! ComponentDataProxyModel
//! FilterProxy for the ComponentDataModel is used along with the search criteria to filter the
//! list of components based on tags and/or selected category.
class ComponentDataProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(ComponentDataProxyModel, AZ::SystemAllocator, 0);
ComponentDataProxyModel(QObject* parent = nullptr)
: QSortFilterProxyModel(parent)
{}
// Creates a new entity and adds the selected components to it.
// It is specialized here to ensure it uses the correct indices according to the sorted data.
AZ::EntityId NewEntityFromSelection(const QModelIndexList& selection);
// Filters rows according to the specifed tags and/or selected category
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
// Set the category to filter by.
void SetSelectedCategory(const AZStd::string& category);
void ClearSelectedCategory();
protected:
AZStd::string m_selectedCategory;
};
@@ -0,0 +1,65 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/base.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/UserSettings/UserSettings.h>
//=============================================================================
class ComponentPaletteSettings
: public AZ::UserSettings
{
public:
AZ_CLASS_ALLOCATOR(ComponentPaletteSettings, AZ::SystemAllocator, 0);
AZ_RTTI(ComponentPaletteSettings, "{BAC3BABA-6DF1-4EEE-AFF1-6A84AD1820A1}", AZ::UserSettings);
AZStd::vector<AZ::Uuid> m_favorites;
void SetFavorites(AZStd::vector<AZ::Uuid>&& componentIds)
{
m_favorites = AZStd::move(componentIds);
}
void RemoveFavorites(const AZStd::vector<AZ::Uuid>& componentIds)
{
for (const AZ::Uuid& componentId : componentIds)
{
auto favoriteIterator = AZStd::find(m_favorites.begin(), m_favorites.end(), componentId);
AZ_Assert(favoriteIterator != m_favorites.end(), "Component Palette Favorite not found.");
if (favoriteIterator != m_favorites.end())
{
m_favorites.erase(favoriteIterator);
}
}
}
static const char* GetSettingsFile()
{
static const char* settingsFile("@user@/editor/componentpalette.usersettings");
return settingsFile;
}
static void Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<ComponentPaletteSettings>()
->Version(1)
->Field("m_favorites", &ComponentPaletteSettings::m_favorites)
;
}
}
};
@@ -0,0 +1,116 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "ComponentEntityEditorPlugin_precompiled.h"
#include "ComponentPaletteWindow.h"
#include "ComponentDataModel.h"
#include "FavoriteComponentList.h"
#include "FilteredComponentList.h"
#include "CategoriesList.h"
#include <LyViewPaneNames.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzToolsFramework/UI/SearchWidget/SearchCriteriaWidget.hxx>
#include <AzToolsFramework/ToolsComponents/ComponentMimeData.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/API/ViewPaneOptions.h>
#include <QLabel>
ComponentPaletteWindow::ComponentPaletteWindow(QWidget* parent)
: QMainWindow(parent)
{
Init();
}
void ComponentPaletteWindow::Init()
{
layout()->setSizeConstraint(QLayout::SetMinimumSize);
QVBoxLayout* layout = new QVBoxLayout();
layout->setSizeConstraint(QLayout::SetMinimumSize);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
QHBoxLayout* gridLayout = new QHBoxLayout(NULL);
gridLayout->setSizeConstraint(QLayout::SetMaximumSize);
gridLayout->setContentsMargins(0, 0, 0, 0);
gridLayout->setSpacing(0);
m_filterWidget = new AzToolsFramework::SearchCriteriaWidget(this);
QStringList tags;
tags << tr("name");
m_filterWidget->SetAcceptedTags(tags, tags[0]);
layout->addLayout(gridLayout, 1);
// Left Panel
QVBoxLayout* leftPaneLayout = new QVBoxLayout(this);
// Favorites
leftPaneLayout->addWidget(new QLabel(tr("Favorites")));
leftPaneLayout->addWidget(new QLabel(tr("Drag components here to add favorites.")));
FavoritesList* favorites = new FavoritesList();
favorites->Init();
leftPaneLayout->addWidget(favorites);
// Categories
m_categoryListWidget = new ComponentCategoryList();
m_categoryListWidget->Init();
leftPaneLayout->addWidget(m_categoryListWidget);
gridLayout->addLayout(leftPaneLayout);
// Right Panel
QVBoxLayout* rightPanelLayout = new QVBoxLayout(this);
gridLayout->addLayout(rightPanelLayout);
// Component list
m_componentListWidget = new FilteredComponentList(this);
m_componentListWidget->Init();
rightPanelLayout->addWidget(new QLabel(tr("Components")));
rightPanelLayout->addWidget(m_filterWidget, 0, Qt::AlignTop);
rightPanelLayout->addWidget(m_componentListWidget);
// The main window
QWidget* window = new QWidget();
window->setLayout(layout);
setCentralWidget(window);
connect(m_categoryListWidget, &ComponentCategoryList::OnCategoryChange, m_componentListWidget, &FilteredComponentList::SetCategory);
connect(m_filterWidget, &AzToolsFramework::SearchCriteriaWidget::SearchCriteriaChanged, m_componentListWidget, &FilteredComponentList::SearchCriteriaChanged);
}
void ComponentPaletteWindow::keyPressEvent(QKeyEvent* event)
{
if (event->modifiers().testFlag(Qt::ControlModifier) && event->key() == Qt::Key_F)
{
m_filterWidget->SelectTextEntryBox();
}
else
{
QMainWindow::keyPressEvent(event);
}
}
void ComponentPaletteWindow::RegisterViewClass()
{
using namespace AzToolsFramework;
ViewPaneOptions options;
options.canHaveMultipleInstances = true;
RegisterViewPane<ComponentPaletteWindow>("Component Palette", LyViewPane::CategoryOther, options);
}
#include <UI/ComponentPalette/moc_ComponentPaletteWindow.cpp>
@@ -0,0 +1,56 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QMainWindow>
#endif
namespace AzToolsFramework
{
class SearchCriteriaWidget;
}
class ComponentCategoryList;
class FilteredComponentList;
class ComponentDataModel;
//! ComponentPaletteWindow
//! Provides a window with controls related to the Component Entity system. It provides an intuitive and organized
//! set of controls to display, sort, filter components. It provides mechanisms for creating entities by dragging
//! and dropping components into the viewport as well as from context menus.
class ComponentPaletteWindow
: public QMainWindow
{
Q_OBJECT
public:
explicit ComponentPaletteWindow(QWidget* parent = 0);
void Init();
static const GUID& GetClassID()
{
// {4236998F-1138-466D-9DF5-6533BFA1DFCA}
static const GUID guid =
{
0x4236998F, 0x1138, 0x466D, { 0x9D, 0xF5, 0x65, 0x33, 0xBF, 0xA1, 0xDF, 0xCA }
};
return guid;
}
static void RegisterViewClass();
protected:
ComponentCategoryList* m_categoryListWidget;
FilteredComponentList* m_componentListWidget;
AzToolsFramework::SearchCriteriaWidget* m_filterWidget;
void keyPressEvent(QKeyEvent* event) override;
};
@@ -0,0 +1,393 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "ComponentEntityEditorPlugin_precompiled.h"
#include "FavoriteComponentList.h"
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <Editor/CryEditDoc.h>
#include <Editor/ViewManager.h>
#include <QHeaderView>
#include <QMimeData>
// FavoritesList
//////////////////////////////////////////////////////////////////////////
FavoritesList::FavoritesList(QWidget* parent /*= nullptr*/)
: FilteredComponentList(parent)
{
}
FavoritesList::~FavoritesList()
{
FavoriteComponentListRequestBus::Handler::BusDisconnect();
}
void FavoritesList::Init()
{
FavoriteComponentListRequestBus::Handler::BusConnect();
FavoritesDataModel* favoritesDataModel = new FavoritesDataModel(this);
setModel(favoritesDataModel);
horizontalHeader()->setSectionResizeMode(ComponentDataModel::ColumnIndex::Name, QHeaderView::Stretch);
setShowGrid(false);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setSelectionMode(QAbstractItemView::SelectionMode::ExtendedSelection);
setStyleSheet("QTableView { selection-background-color: rgba(255,255,255,0.2); }");
setGridStyle(Qt::PenStyle::NoPen);
verticalHeader()->hide();
horizontalHeader()->hide();
setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows);
setShowGrid(false);
setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
setColumnWidth(ComponentDataModel::ColumnIndex::Icon, 32);
hideColumn(ComponentDataModel::ColumnIndex::Category);
setDragDropMode(QAbstractItemView::DragDrop);
setAcceptDrops(true);
horizontalHeader()->setSectionResizeMode(ComponentDataModel::ColumnIndex::Icon, QHeaderView::ResizeToContents);
setColumnWidth(ComponentDataModel::ColumnIndex::Icon, 32);
horizontalHeader()->setSectionResizeMode(ComponentDataModel::ColumnIndex::Category, QHeaderView::Stretch);
setColumnWidth(ComponentDataModel::ColumnIndex::Category, 90);
// Context menu
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &QWidget::customContextMenuRequested, this, &FavoritesList::ShowContextMenu);
}
void FavoritesList::ShowContextMenu(const QPoint& pos)
{
// Only show if a level is loaded
if (!GetIEditor() || GetIEditor()->IsInGameMode())
{
return;
}
if ( model()->rowCount() == 0)
{
return;
}
QMenu contextMenu(tr("Context menu"), this);
QAction actionNewEntity(tr("Make entity with selected favorites"), this);
QAction actionAddToSelection(this);
if (GetIEditor()->GetDocument()->IsDocumentReady())
{
QObject::connect(&actionNewEntity, &QAction::triggered, this, [&] { ContextMenu_NewEntity(); });
contextMenu.addAction(&actionNewEntity);
AzToolsFramework::EntityIdList selectedEntities;
EBUS_EVENT_RESULT(selectedEntities, AzToolsFramework::ToolsApplicationRequests::Bus, GetSelectedEntities);
if (!selectedEntities.empty())
{
QString addToSelection = selectedEntities.size() > 1 ? tr("Add to selected entities") : tr("Add to selected entity");
actionAddToSelection.setText(addToSelection);
QObject::connect(&actionAddToSelection, &QAction::triggered, this, [&] { ContextMenu_AddToSelectedEntities(); });
contextMenu.addAction(&actionAddToSelection);
}
contextMenu.addSeparator();
}
QAction action(tr("Remove"), this);
QObject::connect(&action, &QAction::triggered, this, [&] { ContextMenu_RemoveSelectedFavorites(); });
contextMenu.addAction(&action);
contextMenu.exec(mapToGlobal(pos));
}
void FavoritesList::ContextMenu_RemoveSelectedFavorites()
{
FavoritesDataModel* dataModel = qobject_cast<FavoritesDataModel*>(model());
if (!selectedIndexes().empty())
{
dataModel->Remove(selectedIndexes());
}
}
void FavoritesList::rowsInserted([[maybe_unused]] const QModelIndex& parent, [[maybe_unused]] int start, [[maybe_unused]] int end)
{
resizeRowToContents(0);
}
void FavoritesList::AddFavorites(const AZStd::vector<const AZ::SerializeContext::ClassData*>& classDataContainer)
{
for (const AZ::SerializeContext::ClassData* classData : classDataContainer)
{
if (classData)
{
FavoritesDataModel* dataModel = qobject_cast<FavoritesDataModel*>(model());
dataModel->AddFavorite(classData);
}
}
}
void FavoritesList::dragEnterEvent(QDragEnterEvent* event)
{
if (event->mimeData()->hasFormat(AzToolsFramework::ComponentTypeMimeData::GetMimeType()))
{
event->acceptProposedAction();
}
}
void FavoritesList::dragMoveEvent(QDragMoveEvent* event)
{
if (event->source() == this)
{
event->ignore();
}
else
{
event->accept();
}
}
// FavoritesDataModel
//////////////////////////////////////////////////////////////////////////
int FavoritesDataModel::rowCount([[maybe_unused]] const QModelIndex &parent /*= QModelIndex()*/) const
{
return m_favorites.size();
}
int FavoritesDataModel::columnCount([[maybe_unused]] const QModelIndex &parent /*= QModelIndex()*/) const
{
return ColumnIndex::Count;
}
void FavoritesDataModel::SaveState()
{
AZStd::vector<AZ::Uuid> favorites;
for (const AZ::SerializeContext::ClassData* classData : m_favorites)
{
favorites.push_back(classData->m_typeId);
}
m_settings->SetFavorites(AZStd::move(favorites));
// Write the settings to file...
AZ::SerializeContext* serializeContext = nullptr;
EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(serializeContext, "Serialize Context is null!");
char settingsPath[AZ_MAX_PATH_LEN] = { 0 };
AZ::IO::FileIOBase::GetInstance()->ResolvePath(ComponentPaletteSettings::GetSettingsFile(), settingsPath, AZ_MAX_PATH_LEN);
bool result = m_provider.Save(settingsPath, serializeContext);
(void)result;
AZ_Warning("ComponentPaletteSettings", result, "Failed to Save the Component Palette Settings!");
}
void FavoritesDataModel::LoadState()
{
// It is necessary to Load the settings file *before* you call UserSettings::CreateFind!
AZ::SerializeContext* serializeContext = nullptr;
EBUS_EVENT_RESULT(serializeContext, AZ::ComponentApplicationBus, GetSerializeContext);
AZ_Assert(serializeContext, "Serialize Context is null!");
char settingsPath[AZ_MAX_PATH_LEN] = { 0 };
AZ::IO::FileIOBase::GetInstance()->ResolvePath(ComponentPaletteSettings::GetSettingsFile(), settingsPath, AZ_MAX_PATH_LEN);
bool result = m_provider.Load(settingsPath, serializeContext);
(void)result;
// Create (if no file was found) or find the settings, this will populate the m_settings->m_favorites list.
m_settings = AZ::UserSettings::CreateFind<ComponentPaletteSettings>(AZ_CRC("ComponentPaletteSettings", 0x481d355b), m_providerId);
// Add favorites to the data model from loaded settings
for (const AZ::Uuid& favorite : m_settings->m_favorites)
{
const AZ::SerializeContext::ClassData* classData = serializeContext->FindClassData(favorite);
if (classData)
{
AddFavorite(classData, false);
}
}
}
void FavoritesDataModel::Remove(const QModelIndexList& indices)
{
beginResetModel();
auto newFavorites = m_favorites;
// swap here
for (auto index : indices)
{
// we're only dealing with columns and they're the only thing with class data anyways
if (index.column() == 0)
{
QVariant classDataVariant = index.data(ComponentDataModel::ClassDataRole);
if (classDataVariant.isValid())
{
const AZ::SerializeContext::ClassData* classData = reinterpret_cast<const AZ::SerializeContext::ClassData*>(classDataVariant.value<void*>());
newFavorites.removeAll(classData);
AZ_TracePrintf("Debug", "Removing: %s\n", classData->m_editData->m_name);
}
}
}
m_favorites.swap(newFavorites);
endResetModel();
SaveState();
}
QModelIndex FavoritesDataModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
{
return QModelIndex();
}
if (row >= rowCount(parent) || column >= columnCount(parent))
{
return QModelIndex();
}
return createIndex(row, column, (void*)(m_favorites[row]));
}
QVariant FavoritesDataModel::data(const QModelIndex &index, int role /*= Qt::DisplayRole*/) const
{
if (!index.isValid())
{
return QVariant();
}
const AZ::SerializeContext::ClassData* classData = m_favorites[index.row()];
if (!classData)
{
return QVariant();
}
switch (role)
{
case Qt::DisplayRole:
{
if (index.column() == ComponentDataModel::ColumnIndex::Name)
{
if (m_favorites.empty())
{
return QVariant(tr("You have 0 favorites.\nDrag some components here."));
}
return QVariant(classData->m_editData->m_name);
}
}
break;
case Qt::DecorationRole:
{
if (index.column() == ColumnIndex::Icon)
{
const AZ::SerializeContext::ClassData* iconClassData = m_favorites[index.row()];
auto iconIterator = m_componentIcons.find(iconClassData->m_typeId);
if (iconIterator != m_componentIcons.end())
{
return iconIterator->second;
}
return QVariant();
}
}
break;
case ClassDataRole:
if (index.column() == 0) // Only get data for one column
{
return QVariant::fromValue<void*>(reinterpret_cast<void*>(const_cast<AZ::SerializeContext::ClassData*>(classData)));
}
break;
default:
break;
}
return ComponentDataModel::data(index, role);
}
void FavoritesDataModel::SetSavedStateKey([[maybe_unused]] AZ::u32 key)
{
}
FavoritesDataModel::FavoritesDataModel(QWidget* parent /*= nullptr*/)
: ComponentDataModel(parent)
, m_providerId(AZ_CRC("ComponentPaletteSettingsProviderId"))
{
m_provider.Activate(m_providerId);
LoadState();
}
FavoritesDataModel::~FavoritesDataModel()
{
m_provider.Deactivate();
}
void FavoritesDataModel::AddFavorite(const AZ::SerializeContext::ClassData* classData, bool updateSettings)
{
beginResetModel();
if (m_favorites.indexOf(classData) < 0)
{
m_favorites.push_back(classData);
}
endResetModel();
if (updateSettings)
{
SaveState();
}
}
bool FavoritesDataModel::dropMimeData(const QMimeData *data, Qt::DropAction action, [[maybe_unused]] int row, [[maybe_unused]] int column, [[maybe_unused]] const QModelIndex &parent)
{
if (action == Qt::IgnoreAction)
{
return true;
}
if (data && data->hasFormat(AzToolsFramework::ComponentTypeMimeData::GetMimeType()))
{
AzToolsFramework::ComponentTypeMimeData::ClassDataContainer classDataContainer;
AzToolsFramework::ComponentTypeMimeData::Get(data, classDataContainer);
for (const AZ::SerializeContext::ClassData* classData : classDataContainer)
{
if (classData)
{
AddFavorite(classData);
}
}
return true;
}
return false;
}
#include <UI/ComponentPalette/moc_FavoriteComponentList.cpp>
@@ -0,0 +1,116 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include "ComponentDataModel.h"
#include "FilteredComponentList.h"
#include "ComponentPaletteSettings.h"
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/UserSettings/UserSettingsProvider.h>
#include <AzToolsFramework/ToolsComponents/ComponentMimeData.h>
#include <AzToolsFramework/ToolsComponents/ComponentAssetMimeDataContainer.h>
#endif
//! FavoriteComponentListRequest
//! Bus that provides a way for external features to record favorites
class FavoriteComponentListRequest : public AZ::EBusTraits
{
public:
virtual void AddFavorites(const AZStd::vector<const AZ::SerializeContext::ClassData*>&) = 0;
};
using FavoriteComponentListRequestBus = AZ::EBus<FavoriteComponentListRequest>;
//! FavoritesDataModel
//! Stores the list of component class data to display in the favorites control, offers persistence through user settings.
class FavoritesDataModel
: public ComponentDataModel
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(FavoritesDataModel, AZ::SystemAllocator, 0);
FavoritesDataModel(QWidget* parent = nullptr);
~FavoritesDataModel() override;
//! Add a favorite component
//! \param classData The ClassData information for the component to store as favorite
//! \param updateSettings Optional parameter used to determine if the persistent settings need to be updated.
void AddFavorite(const AZ::SerializeContext::ClassData* classData, bool updateSettings = true);
//! Remove all the specified items from the table
//! \param indices List of indices to remove from favorites
void Remove(const QModelIndexList& indices);
//! Save the list of favorite components to user settings
void SaveState();
//! Load the list of favorite components from user settings
void LoadState();
protected:
void SetSavedStateKey(AZ::u32 key);
// Qt handlers
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
// List of component class data
QList<const AZ::SerializeContext::ClassData*> m_favorites;
// The Palette settings and provider information for saving out the Favorites list
AZStd::intrusive_ptr<ComponentPaletteSettings> m_settings;
AZ::UserSettingsProvider m_provider;
AZ::u32 m_providerId;
};
//! FavoritesList
//! User customized list of favorite components, provides persistence.
class FavoritesList
: public FilteredComponentList
, FavoriteComponentListRequestBus::Handler
{
Q_OBJECT
public:
explicit FavoritesList(QWidget* parent = nullptr);
~FavoritesList() override;
void Init() override;
protected:
//////////////////////////////////////////////////////////////////////////
// FavoriteComponentListRequestBus
void AddFavorites(const AZStd::vector<const AZ::SerializeContext::ClassData*>& classDataContainer) override;
//////////////////////////////////////////////////////////////////////////
void rowsInserted(const QModelIndex& parent, int start, int end);
// Context menu handlers
void ShowContextMenu(const QPoint&);
void ContextMenu_RemoveSelectedFavorites();
// Validate data being dragged in
void dragEnterEvent(QDragEnterEvent * event) override;
void dragMoveEvent(QDragMoveEvent* event) override;
//! Handler used when dropping PaletteItems into the Viewport.
static void DragDropHandler(CViewport* viewport, int ptx, int pty, void* custom);
};
@@ -0,0 +1,265 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "ComponentEntityEditorPlugin_precompiled.h"
#include "ComponentDataModel.h"
#include "FavoriteComponentList.h"
#include "FilteredComponentList.h"
#include "CryCommon/MathConversion.h"
#include "Editor/IEditor.h"
#include "Editor/ViewManager.h"
#include <Editor/CryEditDoc.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <QHeaderView>
void FilteredComponentList::Init()
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setDragDropMode(QAbstractItemView::DragDropMode::DragOnly);
setDragEnabled(true);
setSelectionMode(QAbstractItemView::SelectionMode::ExtendedSelection);
setStyleSheet("QTreeWidget { selection-background-color: rgba(255,255,255,0.2); }");
setGridStyle(Qt::PenStyle::NoPen);
verticalHeader()->hide();
horizontalHeader()->hide();
setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows);
setAcceptDrops(false);
m_componentDataModel = new ComponentDataModel(this);
ComponentDataProxyModel* componentDataProxyModel = new ComponentDataProxyModel(this);
componentDataProxyModel->setSourceModel(m_componentDataModel);
setModel(componentDataProxyModel);
QHeaderView* horizontalHeaderView = horizontalHeader();
horizontalHeaderView->setSectionResizeMode(ComponentDataModel::ColumnIndex::Icon, QHeaderView::ResizeToContents);
horizontalHeaderView->setSectionResizeMode(ComponentDataModel::ColumnIndex::Name, QHeaderView::Stretch);
setColumnWidth(ComponentDataModel::ColumnIndex::Icon, 32);
setShowGrid(false);
setColumnWidth(ComponentDataModel::ColumnIndex::Name, 90);
setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
sortByColumn(ComponentDataModel::ColumnIndex::Name, Qt::AscendingOrder);
hideColumn(ComponentDataModel::ColumnIndex::Category);
connect(model(), &QAbstractItemModel::rowsInserted, this, &FilteredComponentList::rowsInserted);
connect(model(), &QAbstractItemModel::rowsRemoved, this, &FilteredComponentList::rowsAboutToBeRemoved);
connect(model(), SIGNAL(modelReset()), SLOT(modelReset()));
// Context menu
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, &QWidget::customContextMenuRequested, this, &FilteredComponentList::ShowContextMenu);
}
void FilteredComponentList::ContextMenu_NewEntity()
{
AZ::EntityId entityId;
auto proxyDataModel = qobject_cast<ComponentDataProxyModel*>(model());
if (proxyDataModel)
{
entityId = proxyDataModel->NewEntityFromSelection(selectedIndexes());
}
else
{
auto dataModel = qobject_cast<ComponentDataModel*>(model());
if (dataModel)
{
entityId = dataModel->NewEntityFromSelection(selectedIndexes());
}
}
}
void FilteredComponentList::ContextMenu_AddToFavorites()
{
AZStd::vector<const AZ::SerializeContext::ClassData*> componentsToAdd;
for (auto index : selectedIndexes())
{
QVariant classDataVariant = index.data(ComponentDataModel::ClassDataRole);
if (classDataVariant.isValid())
{
auto classData = reinterpret_cast<const AZ::SerializeContext::ClassData*>(classDataVariant.value<void*>());
componentsToAdd.push_back(classData);
}
}
if (!componentsToAdd.empty())
{
EBUS_EVENT(FavoriteComponentListRequestBus, AddFavorites, componentsToAdd);
}
}
void FilteredComponentList::ContextMenu_AddToSelectedEntities()
{
ComponentDataUtilities::AddComponentsToSelectedEntities(selectedIndexes(), model());
}
void FilteredComponentList::ShowContextMenu(const QPoint& pos)
{
QMenu contextMenu(tr("Context menu"), this);
QAction actionNewEntity(tr("Create new entity with selected components"), this);
if (GetIEditor()->GetDocument()->IsDocumentReady())
{
QObject::connect(&actionNewEntity, &QAction::triggered, this, [this] { ContextMenu_NewEntity(); });
contextMenu.addAction(&actionNewEntity);
}
QAction actionAddFavorite(tr("Add to favorites"), this);
QObject::connect(&actionAddFavorite, &QAction::triggered, this, [this] { ContextMenu_AddToFavorites(); });
contextMenu.addAction(&actionAddFavorite);
QAction actionAddToSelection(this);
if (GetIEditor()->GetDocument()->IsDocumentReady())
{
AzToolsFramework::EntityIdList selectedEntities;
EBUS_EVENT_RESULT(selectedEntities, AzToolsFramework::ToolsApplicationRequests::Bus, GetSelectedEntities);
if (!selectedEntities.empty())
{
QString addToSelection = selectedEntities.size() > 1 ? tr("Add to selected entities") : tr("Add to selected entity");
actionAddToSelection.setText(addToSelection);
QObject::connect(&actionAddToSelection, &QAction::triggered, this, [this] { ContextMenu_AddToSelectedEntities(); });
contextMenu.addAction(&actionAddToSelection);
}
}
// TODO: Requires information panel implementation LMBR-28174
//QAction actionHelp(tr("Help"), this);
//QObject::connect(&actionHelp, &QAction::triggered, this, [&] {});
//contextMenu.addAction(&actionHelp);
contextMenu.exec(mapToGlobal(pos));
}
void FilteredComponentList::modelReset()
{
// Ensure that the category column is hidden
hideColumn(ComponentDataModel::ColumnIndex::Category);
}
FilteredComponentList::FilteredComponentList(QWidget* parent /*= nullptr*/)
: QTableView(parent)
{
}
FilteredComponentList::~FilteredComponentList()
{
}
void FilteredComponentList::SearchCriteriaChanged(QStringList& criteriaList, AzToolsFramework::FilterOperatorType filterOperator)
{
setUpdatesEnabled(false);
auto dataModel = qobject_cast<ComponentDataProxyModel*>(model());
if (dataModel)
{
// Go through the list of items and show/hide as needed due to filter.
QString filter;
for (const auto& criteria : criteriaList)
{
QString tag, text;
AzToolsFramework::SearchCriteriaButton::SplitTagAndText(criteria, tag, text);
AppendFilter(filter, text, filterOperator);
}
dataModel->setFilterRegExp(QRegExp(filter, Qt::CaseSensitivity::CaseInsensitive));
}
setUpdatesEnabled(true);
}
void FilteredComponentList::SetCategory(const char* category)
{
auto dataModel = qobject_cast<ComponentDataProxyModel*>(model());
if (dataModel)
{
if (!category || category[0] == 0 || azstricmp(category, "All") == 0)
{
dataModel->ClearSelectedCategory();
}
else
{
dataModel->SetSelectedCategory(category);
}
}
// Note: this ensures the category column remains hidden
hideColumn(ComponentDataModel::ColumnIndex::Category);
}
void FilteredComponentList::BuildFilter(QStringList& criteriaList, AzToolsFramework::FilterOperatorType filterOperator)
{
ClearFilterRegExp();
for (const auto& criteria : criteriaList)
{
QString tag, text;
AzToolsFramework::SearchCriteriaButton::SplitTagAndText(criteria, tag, text);
if (tag.isEmpty())
{
tag = "null";
}
QString filter = m_filtersRegExp[tag.toStdString().c_str()].pattern();
AppendFilter(filter, text, filterOperator);
SetFilterRegExp(tag.toStdString().c_str(), QRegExp(filter, Qt::CaseInsensitive));
}
}
void FilteredComponentList::AppendFilter(QString& filter, const QString& text, AzToolsFramework::FilterOperatorType filterOperator)
{
if (filterOperator == AzToolsFramework::FilterOperatorType::Or)
{
if (filter.isEmpty())
{
filter = text;
}
else
{
filter += "|" + text;
}
}
else if (filterOperator == AzToolsFramework::FilterOperatorType::And)
{
//using lookaheads to produce an "and" effect.
filter += "(?=.*" + text + ")";
}
}
void FilteredComponentList::SetFilterRegExp(const AZStd::string& filterType, const QRegExp& regExp)
{
m_filtersRegExp[filterType] = regExp;
}
void FilteredComponentList::ClearFilterRegExp(const AZStd::string& filterType /*= AZStd::string()*/)
{
if (filterType.empty())
{
for (auto& it : m_filtersRegExp)
{
it.second = QRegExp();
}
}
else
{
m_filtersRegExp[filterType] = QRegExp();
}
}
#include <UI/ComponentPalette/moc_FilteredComponentList.cpp>
@@ -0,0 +1,67 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QTableView>
#include <QWidget>
#include <AzToolsFramework/UI/SearchWidget/SearchCriteriaWidget.hxx>
#include <AzToolsFramework/UI/SearchWidget/SearchWidgetTypes.hxx>
#include <AzCore/Serialization/SerializeContext.h>
#include "ComponentDataModel.h"
#endif
namespace AZ
{
class SerializeContext;
class ClassData;
}
class ComponentDataModel;
//! FilteredComponentList
//! Provides a list of components that can be filtered according to search criteria provided and/or from
//! a category selection control.
class FilteredComponentList
: public QTableView
{
Q_OBJECT
public:
explicit FilteredComponentList(QWidget* parent = nullptr);
~FilteredComponentList() override;
virtual void Init();
void SearchCriteriaChanged(QStringList& criteriaList, AzToolsFramework::FilterOperatorType filterOperator);
void SetCategory(const char* category);
protected:
// Filtering support
void BuildFilter(QStringList& criteriaList, AzToolsFramework::FilterOperatorType filterOperator);
void AppendFilter(QString& filter, const QString& text, AzToolsFramework::FilterOperatorType filterOperator);
void SetFilterRegExp(const AZStd::string& filterType, const QRegExp& regExp);
void ClearFilterRegExp(const AZStd::string& filterType = AZStd::string());
// Context menu handlers
void ShowContextMenu(const QPoint&);
void ContextMenu_NewEntity();
void ContextMenu_AddToFavorites();
void ContextMenu_AddToSelectedEntities();
void modelReset();
AzToolsFramework::FilterByCategoryMap m_filtersRegExp;
ComponentDataModel* m_componentDataModel;
};
@@ -0,0 +1,13 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "ComponentEntityEditorPlugin_precompiled.h"
#include "InformationPanel.h"
// TODO: LMBR-28174
@@ -0,0 +1,10 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
// TODO: LMBR-28174