Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,38 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AzToolsFramework_precompiled.h"
#include "ComponentPaletteModel.hxx"
namespace AzToolsFramework
{
ComponentPaletteModel::ComponentPaletteModel(QObject* parent)
: QStandardItemModel(parent)
{
}
ComponentPaletteModel::~ComponentPaletteModel()
{
}
QVariant ComponentPaletteModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole && section == 0 && orientation == Qt::Horizontal)
{
return tr("Components");
}
return QStandardItemModel::headerData(section, orientation, role);
}
}
#include "UI/ComponentPalette/moc_ComponentPaletteModel.cpp"
@@ -0,0 +1,34 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // class 'QScopedPointer<QStandardItemPrivate,QScopedPointerDeleter<T>>' needs to have dll-interface to be used by clients of class 'QStandardItem'
#include <QStandardItemModel>
AZ_POP_DISABLE_WARNING
#endif
namespace AzToolsFramework
{
class ComponentPaletteModel
: public QStandardItemModel
{
Q_OBJECT
public:
ComponentPaletteModel(QObject* parent = 0);
~ComponentPaletteModel() override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
};
}
@@ -0,0 +1,58 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AzToolsFramework_precompiled.h"
#include "ComponentPaletteModelFilter.hxx"
#include <AzCore/Serialization/SerializeContext.h>
namespace AzToolsFramework
{
ComponentPaletteModelFilter::ComponentPaletteModelFilter(QObject* parent)
: QSortFilterProxyModel(parent)
{
}
bool ComponentPaletteModelFilter::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
if (!index.isValid())
{
return false;
}
if (!filterRegExp().isValid())
{
return true;
}
auto componentClass = reinterpret_cast<const AZ::SerializeContext::ClassData*>(sourceModel()->data(index, Qt::ItemDataRole::UserRole + 1).toULongLong());
if (componentClass)
{
const QString componentName = sourceModel()->data(index, Qt::DisplayRole).toString();
return componentName.contains(filterRegExp());
}
const int childRowCount = sourceModel()->rowCount(index);
for (int childRow = 0; childRow < childRowCount; ++childRow)
{
if (filterAcceptsRow(childRow, index))
{
return true;
}
}
return false;
}
}
#include "UI/ComponentPalette/moc_ComponentPaletteModelFilter.cpp"
@@ -0,0 +1,33 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QSortFilterProxyModel>
#endif
namespace AzToolsFramework
{
class ComponentPaletteModelFilter : public QSortFilterProxyModel
{
Q_OBJECT
public:
ComponentPaletteModelFilter(QObject* parent = nullptr);
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
protected:
QRegExp m_filterRegExp;
};
}
@@ -0,0 +1,254 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AzToolsFramework_precompiled.h"
#include "ComponentPaletteUtil.hxx"
#include <AzCore/Debug/Profiler.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'QLayoutItem::align': class 'QFlags<Qt::AlignmentFlag>' needs to have dll-interface to be used by clients of class 'QLayoutItem'
#include <AzToolsFramework/UI/SearchWidget/SearchCriteriaWidget.hxx>
AZ_POP_DISABLE_WARNING
namespace AzToolsFramework
{
namespace ComponentPaletteUtil
{
bool OffersRequiredServices(
const AZ::SerializeContext::ClassData* componentClass,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
const AZStd::vector<AZ::ComponentServiceType>& incompatibleServiceFilter
)
{
AZ_Assert(componentClass, "Component class must not be null");
if (!componentClass)
{
return false;
}
AZ::ComponentDescriptor* componentDescriptor = nullptr;
EBUS_EVENT_ID_RESULT(componentDescriptor, componentClass->m_typeId, AZ::ComponentDescriptorBus, GetDescriptor);
if (!componentDescriptor)
{
return false;
}
// If no services are provided, this function returns true
if (serviceFilter.empty())
{
return true;
}
AZ::ComponentDescriptor::DependencyArrayType providedServices;
componentDescriptor->GetProvidedServices(providedServices, nullptr);
//reject this component if it does not offer any of the required services
if (AZStd::find_first_of(
providedServices.begin(),
providedServices.end(),
serviceFilter.begin(),
serviceFilter.end()) == providedServices.end())
{
return false;
}
//reject this component if it does offer any of the incompatible services
if (AZStd::find_first_of(
providedServices.begin(),
providedServices.end(),
incompatibleServiceFilter.begin(),
incompatibleServiceFilter.end()) != providedServices.end())
{
return false;
}
return true;
}
bool OffersRequiredServices(
const AZ::SerializeContext::ClassData* componentClass,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter
)
{
const AZStd::vector<AZ::ComponentServiceType> incompatibleServices;
return OffersRequiredServices(componentClass, serviceFilter, incompatibleServices);
}
bool IsAddableByUser(const AZ::SerializeContext::ClassData* componentClass)
{
AZ_Assert(componentClass, "component class must not be null");
if (!componentClass)
{
return false;
}
auto editorDataElement = componentClass->m_editData->FindElementData(AZ::Edit::ClassElements::EditorData);
if (!editorDataElement)
{
return false;
}
auto attribute = editorDataElement->FindAttribute(AZ::Edit::Attributes::AddableByUser);
if (attribute)
{
auto data = azdynamic_cast<AZ::Edit::AttributeData<bool>*>(attribute);
if (data)
{
if (!data->Get(nullptr))
{
return false;
}
}
}
return true;
}
void BuildComponentTables(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
const AZStd::vector<AZ::ComponentServiceType>& incompatibleServiceFilter,
ComponentDataTable &componentDataTable,
ComponentIconTable &componentIconTable)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
serializeContext->EnumerateDerived<AZ::Component>(
[&](const AZ::SerializeContext::ClassData* componentClass, const AZ::Uuid& knownType) -> bool
{
AZ_UNUSED(knownType);
if (componentFilter(*componentClass) && componentClass->m_editData)
{
QString categoryName = QString::fromUtf8("Miscellaneous");
QString componentName = QString::fromUtf8(componentClass->m_editData->m_name);
// If none of the required services are offered by this component, or the component
// can not be added by the user, skip to the next component
if (!OffersRequiredServices(componentClass, serviceFilter, incompatibleServiceFilter) || !IsAddableByUser(componentClass))
{
return true;
}
if (auto editorDataElement = componentClass->m_editData->FindElementData(AZ::Edit::ClassElements::EditorData))
{
if (auto attribute = editorDataElement->FindAttribute(AZ::Edit::Attributes::Category))
{
if (auto data = azdynamic_cast<AZ::Edit::AttributeData<const char*>*>(attribute))
{
categoryName = QString::fromUtf8(data->Get(nullptr));
}
}
AZStd::string componentIconPath;
EBUS_EVENT_RESULT(componentIconPath, AzToolsFramework::EditorRequests::Bus, GetComponentEditorIcon, componentClass->m_typeId, nullptr);
componentIconTable[componentClass] = QString::fromUtf8(componentIconPath.c_str());
}
componentDataTable[categoryName][componentName] = componentClass;
}
return true;
});
}
void BuildComponentTables(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
ComponentDataTable& componentDataTable,
ComponentIconTable& componentIconTable)
{
const AZStd::vector<AZ::ComponentServiceType> incompatibleServices;
BuildComponentTables(serializeContext, componentFilter, serviceFilter, incompatibleServices, componentDataTable, componentIconTable);
}
bool ContainsEditableComponents(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
const AZStd::vector<AZ::ComponentServiceType>& incompatibleServiceFilter
)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
bool containsEditable = false;
serializeContext->EnumerateDerived<AZ::Component>(
[&](const AZ::SerializeContext::ClassData* componentClass, const AZ::Uuid& knownType) -> bool
{
AZ_UNUSED(knownType);
if (componentFilter(*componentClass) && componentClass->m_editData)
{
// If none of the required services are offered by this component, or the component
// can not be added by the user, skip to the next component
if (!OffersRequiredServices(componentClass, serviceFilter, incompatibleServiceFilter) || !IsAddableByUser(componentClass))
{
return true;
}
containsEditable = true;
}
// We can stop enumerating if we've found an editable component
return !containsEditable;
});
return containsEditable;
}
bool ContainsEditableComponents(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter
)
{
const AZStd::vector<AZ::ComponentServiceType> incompatibleServices;
return ContainsEditableComponents(serializeContext, componentFilter, serviceFilter, incompatibleServices);
}
QRegExp BuildFilterRegExp(QStringList& criteriaList, AzToolsFramework::FilterOperatorType filterOperator)
{
// 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);
if (filterOperator == AzToolsFramework::FilterOperatorType::Or)
{
if (filter.isEmpty())
{
filter = text;
}
else
{
filter += "|" + text;
}
}
else if (filterOperator == AzToolsFramework::FilterOperatorType::And)
{
filter += "(?=.*" + text + ")";
}
}
return QRegExp(filter, Qt::CaseInsensitive, QRegExp::RegExp);
}
}
}
@@ -0,0 +1,82 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/std/containers/map.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/UI/SearchWidget/SearchWidgetTypes.hxx>
#include <QString>
namespace AZ
{
class SerializeContext;
};
namespace AzToolsFramework
{
namespace ComponentPaletteUtil
{
using ComponentDataTable = AZStd::map <QString, AZStd::map <QString, const AZ::SerializeContext::ClassData* > >;
using ComponentIconTable = AZStd::map<const AZ::SerializeContext::ClassData*, QString>;
// Returns true if the given component provides at least one of the services specified or no services are provided
bool OffersRequiredServices(
const AZ::SerializeContext::ClassData* componentClass,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
const AZStd::vector<AZ::ComponentServiceType>& incompatibleServiceFilter
);
bool OffersRequiredServices(
const AZ::SerializeContext::ClassData* componentClass,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter
);
// Returns true if the given component is addable by the user
bool IsAddableByUser(const AZ::SerializeContext::ClassData* componentClass);
void BuildComponentTables(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
const AZStd::vector<AZ::ComponentServiceType>& incompatibleServiceFilter,
ComponentDataTable &componentDataTable,
ComponentIconTable &componentIconTable
);
void BuildComponentTables(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
ComponentDataTable &componentDataTable,
ComponentIconTable &componentIconTable
);
// Returns true if any components in the given filter provide any of the services
// specified and are addable/editable by the user
bool ContainsEditableComponents(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
const AZStd::vector<AZ::ComponentServiceType>& incompatibleServiceFilter
);
bool ContainsEditableComponents(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter
);
QRegExp BuildFilterRegExp(QStringList& criteriaList, AzToolsFramework::FilterOperatorType filterOperator);
}
}
@@ -0,0 +1,458 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AzToolsFramework_precompiled.h"
#include "ComponentPaletteModel.hxx"
#include "ComponentPaletteUtil.hxx"
#include "ComponentPaletteWidget.hxx"
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/Debug/Profiler.h>
#include <AzFramework/Components/DeprecatedComponentsBus.h>
#include <AzToolsFramework/API/EntityCompositionRequestBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzQtComponents/Components/Widgets/LineEdit.h>
AZ_PUSH_DISABLE_WARNING(4244 4251, "-Wunknown-warning-option") // 4244: conversion from 'int' to 'float', possible loss of data
// 4251: class '...' needs to have dll-interface to be used by clients of class '...'
#include <QAction>
#include <QAbstractItemView>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLineEdit>
#include <QPushButton>
#include <QScrollBar>
#include <QStandardItem>
#include <QStandardItem>
#include <QTimer>
#include <QTreeView>
#include <QVBoxLayout>
#include <QKeyEvent>
#include <QToolButton>
AZ_POP_DISABLE_WARNING
namespace AzToolsFramework
{
ComponentPaletteWidget::ComponentPaletteWidget(QWidget* parent, bool enableSearch)
: QFrame(parent)
{
setWindowFlags(Qt::FramelessWindowHint | Qt::Popup);
setFrameShadow(QFrame::Shadow::Raised);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setAcceptDrops(false);
auto outerLayout = new QVBoxLayout(this);
outerLayout->setSizeConstraint(QLayout::SetNoConstraint);
setLayout(outerLayout);
// Search filter
m_searchFrame = new QFrame(this);
m_searchFrame->setObjectName("SearchFrame");
m_searchFrame->setVisible(enableSearch);
auto searchLayout = new QHBoxLayout(m_searchFrame);
searchLayout->setSizeConstraint(QLayout::SetMinimumSize);
m_searchText = new QLineEdit(m_searchFrame);
m_searchText->setObjectName("SearchText");
m_searchText->setText("");
m_searchText->setPlaceholderText("Search...");
m_searchText->setClearButtonEnabled(true);
AzQtComponents::LineEdit::applySearchStyle(m_searchText);
m_searchRegExp = QRegExp("", Qt::CaseInsensitive, QRegExp::RegExp);
searchLayout->addWidget(m_searchText);
m_searchFrame->setLayout(searchLayout);
outerLayout->addWidget(m_searchFrame);
m_componentModel = new ComponentPaletteModel(this);
m_componentTree = new QTreeView(this);
m_componentTree->setObjectName("Tree");
m_componentTree->setModel(m_componentModel);
m_componentTree->setEditTriggers(QAbstractItemView::NoEditTriggers);
outerLayout->addWidget(m_componentTree);
//hide header for dropdown-style, single-column, tree
m_componentTree->header()->hide();
connect(m_searchText, &QLineEdit::textChanged, this, &ComponentPaletteWidget::QueueUpdateSearch);
QToolButton* clearButton = AzQtComponents::LineEdit::getClearButton(m_searchText);
assert(clearButton);
connect(clearButton, &QToolButton::clicked, this, &ComponentPaletteWidget::ClearSearch);
connect(m_componentTree, &QTreeView::activated, this, &ComponentPaletteWidget::ActivateSelection);
connect(m_componentTree, &QTreeView::clicked, this, &ComponentPaletteWidget::ActivateSelection);
connect(m_componentTree, &QTreeView::doubleClicked, this, &ComponentPaletteWidget::ActivateSelection);
connect(m_componentTree, &QTreeView::expanded, this, &ComponentPaletteWidget::ExpandCategory);
connect(m_componentTree, &QTreeView::collapsed, this, &ComponentPaletteWidget::CollapseCategory);
auto actionToHideWindow = new QAction(tr("Hide Window"), this);
actionToHideWindow->setShortcut(QKeySequence::Cancel);
actionToHideWindow->setShortcutContext(Qt::WidgetWithChildrenShortcut);
connect(actionToHideWindow, &QAction::triggered, this, &ComponentPaletteWidget::OnAddComponentCancel);
connect(actionToHideWindow, &QAction::triggered, this, &ComponentPaletteWidget::hide);
addAction(actionToHideWindow);
auto actionToFocusSearchBox = new QAction(tr("Focus Search Box"), this);
actionToFocusSearchBox->setShortcut(QKeySequence::Find);
actionToFocusSearchBox->setShortcutContext(Qt::WidgetWithChildrenShortcut);
connect(actionToFocusSearchBox, &QAction::triggered, this, &ComponentPaletteWidget::FocusSearchBox);
addAction(actionToFocusSearchBox);
installEventFilter(this);
}
void ComponentPaletteWidget::Populate(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::EntityIdList& selectedEntityIds,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
const AZStd::vector<AZ::ComponentServiceType>& incompatibleServiceFilter)
{
m_serializeContext = serializeContext;
m_selectedEntityIds = selectedEntityIds;
m_componentFilter = componentFilter;
m_serviceFilter = serviceFilter;
m_incompatibleServiceFilter = incompatibleServiceFilter;
UpdateContent();
Present();
}
void ComponentPaletteWidget::UpdateContent()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
m_componentModel->clear();
bool applyRegExFilter = !m_searchRegExp.isEmpty();
// Gather all components that match our filter and group by category.
ComponentPaletteUtil::ComponentDataTable componentDataTable;
ComponentPaletteUtil::ComponentIconTable componentIconTable;
ComponentPaletteUtil::BuildComponentTables(
m_serializeContext,
m_componentFilter,
m_serviceFilter,
m_incompatibleServiceFilter,
componentDataTable,
componentIconTable);
AzFramework::Components::DeprecatedComponentsList deprecatedList;
AzFramework::Components::DeprecatedComponentsRequestBus::Broadcast(&AzFramework::Components::DeprecatedComponentsRequestBus::Events::EnumerateDeprecatedComponents, deprecatedList);
AZ::Entity::ComponentArrayType componentsOnEntity;
// Get all components on all selected entities so we can display a count of used components by type
AZStd::unordered_set<AZ::Component*> allComponentsOnSelectedEntities;
for (AZ::EntityId entityId : m_selectedEntityIds)
{
componentsOnEntity.clear();
AzToolsFramework::GetAllComponentsForEntity(entityId, componentsOnEntity);
allComponentsOnSelectedEntities.insert(componentsOnEntity.begin(), componentsOnEntity.end());
}
// Populate the context menu.
AZStd::map<QString, QStandardItem*> categoryItemMap;
for (const auto& categoryPair : componentDataTable)
{
//get the full category name/path and split it by separators for iteration
const QString& categoryPath = categoryPair.first;
const QStringList& categoryPathSegments = categoryPath.split('/', Qt::SkipEmptyParts);
QString categoryPathBuilder;
//for every segment of the category path, create an expandable header
auto parentItem = m_componentModel->invisibleRootItem();
for (const QString& categoryName : categoryPathSegments)
{
categoryPathBuilder += categoryName + "/";
QStandardItem* categoryItem = nullptr;
auto categoryItemItr = categoryItemMap.find(categoryPathBuilder);
if (categoryItemItr == categoryItemMap.end())
{
categoryItem = new QStandardItem(categoryName);
categoryItem->setCheckable(false);
categoryItem->setEditable(false);
categoryItem->setSelectable(true);
categoryItem->setData((qulonglong)nullptr, Qt::ItemDataRole::UserRole + 1);
//make groups bold
QFont font = categoryItem->font();
font.setBold(true);
categoryItem->setFont(font);
parentItem->appendRow(categoryItem);
categoryItemMap[categoryPathBuilder] = categoryItem;
}
else
{
categoryItem = categoryItemItr->second;
}
parentItem = categoryItem;
}
}
for (const auto& categoryPair : componentDataTable)
{
auto categoryItemItr = categoryItemMap.find(categoryPair.first + "/");
auto parentItem = categoryItemItr != categoryItemMap.end() ? categoryItemItr->second : m_componentModel->invisibleRootItem();
const auto& componentMap = categoryPair.second;
for (const auto& componentPair : componentMap)
{
auto componentClass = componentPair.second;
const QString& componentName = componentPair.first;
const QString& componentIconName = componentIconTable[componentClass];
auto deprecatedInfo = deprecatedList.find(componentClass->m_typeId);
bool componentIsDeprecated = deprecatedInfo != deprecatedList.end();
if ((!applyRegExFilter || componentName.contains(m_searchRegExp)) && (!componentIsDeprecated || !deprecatedInfo->second.m_hideComponent))
{
//count the number of components on selected entities that match this type
auto componentCount = AZStd::count_if(allComponentsOnSelectedEntities.begin(), allComponentsOnSelectedEntities.end(), [componentClass](const AZ::Component* component) {
return componentClass->m_typeId == component->GetUnderlyingComponentType();
});
//generate the display name for the component
QString displayName = componentName;
if (componentCount) //<append count if count > 0
{
displayName += QObject::tr(" (%1)").arg(componentCount);
}
if (componentIsDeprecated) //< append deprecation strings
{
displayName += deprecatedInfo->second.m_deprecationString.c_str();
}
auto componentItem = new QStandardItem(QIcon(componentIconName), displayName);
componentItem->setToolTip(componentClass->m_editData->m_description);
componentItem->setCheckable(false);
componentItem->setEditable(false);
componentItem->setSelectable(true);
componentItem->setData((qulonglong)componentClass, Qt::ItemDataRole::UserRole + 1);
parentItem->appendRow(componentItem);
}
}
}
// If we have removed component items from the visible tree we need to prune the hanging titles.
// We also need to auto expand all the items that contain children.
for (int i = m_componentModel->rowCount() - 1; i >= 0; --i)
{
if (BranchHasNoChildren(m_componentModel->item(i)))
{
m_componentModel->removeRow(i);
}
else
{
SetExpanded(m_componentModel->index(i, 0));
}
}
m_componentModel->sort(0);
}
bool ComponentPaletteWidget::BranchHasNoChildren(QStandardItem* item)
{
bool returnVal = true;
// We have to check all items to completely remove all non-parent items. Hence no early-exit.
for (int i = item->rowCount() - 1; i >= 0 ; --i)
{
QStandardItem* it = item->child(i);
// Check in reverse so removing a row doesn't affect remaining items.
if (it->data(Qt::ItemDataRole::UserRole + 1).toULongLong())
{
// if this node has data then the parent has children.
returnVal = false;
}
else
{
// If this item has no children, prune it.
if (BranchHasNoChildren(it))
{
item->removeRow(i);
}
else
{
SetExpanded(it->index());
returnVal = false;
}
}
}
return returnVal;
}
void ComponentPaletteWidget::SetExpanded(QModelIndex itemIndex)
{
auto label = m_componentModel->data(itemIndex, Qt::ItemDataRole::DisplayRole).toString();
auto stateItr = m_categoryExpandedState.find(label);
auto expand = stateItr == m_categoryExpandedState.end() || stateItr->second;
m_componentTree->setExpanded(itemIndex, expand);
}
void ComponentPaletteWidget::Present()
{
layout()->setEnabled(true);
layout()->update();
layout()->activate();
raise();
show();
FocusSearchBox();
}
void ComponentPaletteWidget::QueueUpdateSearch()
{
QTimer::singleShot(1, this, &ComponentPaletteWidget::UpdateSearch);
}
void ComponentPaletteWidget::UpdateSearch()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
m_searchRegExp = QRegExp(m_searchText->text(), Qt::CaseInsensitive, QRegExp::RegExp);
m_searchText->setFocus();
UpdateContent();
}
void ComponentPaletteWidget::ClearSearch()
{
m_searchText->setText("");
QueueUpdateSearch();
}
void ComponentPaletteWidget::ActivateSelection(const QModelIndex& index)
{
if (index.isValid())
{
auto componentClass = reinterpret_cast<const AZ::SerializeContext::ClassData*>(m_componentModel->data(index, Qt::ItemDataRole::UserRole + 1).toULongLong());
if (componentClass)
{
emit OnAddComponentBegin();
EntityCompositionRequestBus::Broadcast(&EntityCompositionRequests::AddComponentsToEntities, m_selectedEntityIds, AZ::ComponentTypeList{ componentClass->m_typeId });
emit OnAddComponentEnd();
ClearSearch();
hide();
}
}
}
void ComponentPaletteWidget::ExpandCategory(const QModelIndex& index)
{
if (index.isValid())
{
auto label = m_componentModel->data(index, Qt::ItemDataRole::DisplayRole).toString();
m_categoryExpandedState[label] = true;
}
}
void ComponentPaletteWidget::CollapseCategory(const QModelIndex& index)
{
if (index.isValid())
{
auto label = m_componentModel->data(index, Qt::ItemDataRole::DisplayRole).toString();
m_categoryExpandedState[label] = false;
}
}
void ComponentPaletteWidget::focusOutEvent(QFocusEvent *event)
{
hide();
QFrame::focusOutEvent(event);
}
void ComponentPaletteWidget::FocusSearchBox()
{
if (m_searchFrame->isVisible())
{
if (!m_searchText->hasFocus())
{
m_searchText->setFocus();
}
}
else
{
FocusComponentTree();
}
}
void ComponentPaletteWidget::FocusComponentTree()
{
if (!m_componentTree->hasFocus())
{
m_componentTree->setFocus();
// Focus the first actual component (leaf node)
QModelIndex indexToSelect = m_componentModel->index(0, 0);
while (indexToSelect.isValid() && m_componentModel->rowCount(indexToSelect) > 0)
{
indexToSelect = indexToSelect.model()->index(0, 0, indexToSelect);
}
m_componentTree->setCurrentIndex(indexToSelect);
}
}
//overridden to intercept key events to move between filter and tree
bool ComponentPaletteWidget::eventFilter(QObject* object, QEvent* event)
{
if (event->type() != QEvent::KeyPress)
{
return false;
}
if (object != this &&
object != m_searchText &&
object != m_componentTree)
{
return false;
}
if (!hasFocus() &&
!m_searchText->hasFocus() &&
!m_componentTree->hasFocus())
{
return false;
}
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Down ||
keyEvent->key() == Qt::Key_Return ||
keyEvent->key() == Qt::Key_Enter)
{
FocusComponentTree();
return false;
}
if (keyEvent->key() == Qt::Key_Up)
{
if (!m_componentTree->hasFocus() ||
m_componentTree->selectionModel()->selectedIndexes().empty() ||
m_componentTree->selectionModel()->selectedIndexes().front().row() == 0)
{
FocusSearchBox();
return false;
}
}
return false;
}
}
#include "UI/ComponentPalette/moc_ComponentPaletteWidget.cpp"
@@ -0,0 +1,95 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/std/containers/map.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/UI/SearchWidget/SearchWidgetTypes.hxx>
AZ_PUSH_DISABLE_WARNING(4244 4251, "-Wunknown-warning-option") // 4244: conversion from 'int' to 'float', possible loss of data
// 4251: class '...' needs to have dll-interface to be used by clients of class '...'
#include <QFrame>
#include <QString>
AZ_POP_DISABLE_WARNING
#endif
class QLineEdit;
class QPushButton;
class QSortFilterProxyModel;
class QTreeView;
namespace AZ
{
class SerializeContext;
};
namespace AzToolsFramework
{
class ComponentPaletteModel;
class ComponentPaletteWidget
: public QFrame
{
Q_OBJECT
public:
ComponentPaletteWidget(QWidget* parent, bool enableSearch);
void Populate(
AZ::SerializeContext* serializeContext,
const AzToolsFramework::EntityIdList& selectedEntityIds,
const AzToolsFramework::ComponentFilter& componentFilter,
const AZStd::vector<AZ::ComponentServiceType>& serviceFilter,
const AZStd::vector<AZ::ComponentServiceType>& incompatibleServiceFilter);
void Present();
Q_SIGNALS:
void OnAddComponentBegin();
void OnAddComponentEnd();
void OnAddComponentCancel();
protected:
void focusOutEvent(QFocusEvent *event) override;
private slots:
void UpdateContent();
void QueueUpdateSearch();
void UpdateSearch();
void ClearSearch();
void ActivateSelection(const QModelIndex& index);
void ExpandCategory(const QModelIndex& index);
void CollapseCategory(const QModelIndex& index);
void FocusSearchBox();
void FocusComponentTree();
private:
bool eventFilter(QObject* object, QEvent* event) override;
bool BranchHasNoChildren(QStandardItem* item);
void SetExpanded(QModelIndex itemIndex);
QRegExp m_searchRegExp;
QFrame* m_searchFrame = nullptr;
QLineEdit* m_searchText = nullptr;
QTreeView* m_componentTree = nullptr;
ComponentPaletteModel* m_componentModel = nullptr;
AZ::SerializeContext* m_serializeContext = nullptr;
EntityIdList m_selectedEntityIds;
ComponentFilter m_componentFilter;
AZStd::vector<AZ::ComponentServiceType> m_serviceFilter;
AZStd::vector<AZ::ComponentServiceType> m_incompatibleServiceFilter;
AZStd::map<QString, bool> m_categoryExpandedState;
};
}