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,205 @@
/*
* 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 <QLabel>
#include <QPushButton>
#include <SceneWidgets/ui_ManifestWidget.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/Containers/SceneManifest.h>
#include <SceneAPI/SceneCore/Events/ManifestMetaInfoBus.h>
#include <SceneAPI/SceneUI/SceneWidgets/ManifestWidget.h>
#include <SceneAPI/SceneUI/SceneWidgets/ManifestWidgetPage.h>
#include <SceneAPI/SceneUI/CommonWidgets/OverlayWidget.h>
#include <SceneAPI/SceneUI/SceneWidgets/SceneGraphWidget.h>
#include <AzCore/std/sort.h>
namespace AZ
{
namespace SceneAPI
{
namespace UI
{
ManifestWidget::ManifestWidget(SerializeContext* serializeContext, QWidget* parent)
: QWidget(parent)
, ui(new Ui::ManifestWidget())
, m_serializeContext(serializeContext)
{
ui->setupUi(this);
AzQtComponents::TabWidget::applySecondaryStyle(ui->m_tabs, false);
}
ManifestWidget::~ManifestWidget()
{
}
void ManifestWidget::BuildFromScene(const AZStd::shared_ptr<Containers::Scene>& scene)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Editor);
ui->m_tabs->clear();
m_pages.clear();
m_scene = scene;
if (!scene)
{
return;
}
BuildPages();
Containers::SceneManifest& manifest = scene->GetManifest();
for (auto& value : manifest.GetValueStorage())
{
AddObject(value);
}
for (ManifestWidgetPage* page : m_pages)
{
page->RefreshPage();
}
// Make sure to reset the active tab if the active tab is now empty
ManifestWidgetPage* currentPage = qobject_cast<ManifestWidgetPage*>(ui->m_tabs->currentWidget());
if (currentPage == nullptr || currentPage->ObjectCount() == 0)
{
for (ManifestWidgetPage* page : m_pages)
{
if (page->ObjectCount() > 0)
{
ui->m_tabs->setCurrentWidget(page);
break;
}
}
}
}
bool ManifestWidget::AddObject(const AZStd::shared_ptr<DataTypes::IManifestObject>& object)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Editor);
for (ManifestWidgetPage* page : m_pages)
{
if (page->SupportsType(object))
{
return page->AddObject(object);
}
}
return false;
}
bool ManifestWidget::RemoveObject(const AZStd::shared_ptr<DataTypes::IManifestObject>& object)
{
for (ManifestWidgetPage* page : m_pages)
{
if (page->SupportsType(object))
{
return page->RemoveObject(object);
}
}
return false;
}
AZStd::shared_ptr<Containers::Scene> ManifestWidget::GetScene()
{
return m_scene;
}
AZStd::shared_ptr<const Containers::Scene> ManifestWidget::GetScene() const
{
return m_scene;
}
ManifestWidget* ManifestWidget::FindRoot(QWidget* child)
{
while (child != nullptr)
{
ManifestWidget* manifestWidget = qobject_cast<ManifestWidget*>(child);
if (manifestWidget)
{
return manifestWidget;
}
else
{
child = child->parentWidget();
}
}
return nullptr;
}
const ManifestWidget* ManifestWidget::FindRoot(const QWidget* child)
{
while (child != nullptr)
{
const ManifestWidget* manifestWidget = qobject_cast<const ManifestWidget*>(child);
if (manifestWidget)
{
return manifestWidget;
}
else
{
child = child->parentWidget();
}
}
return nullptr;
}
void ManifestWidget::BuildPages()
{
if (!m_scene)
{
return;
}
Events::ManifestMetaInfo::CategoryRegistrationList categories;
EBUS_EVENT(Events::ManifestMetaInfoBus, GetCategoryAssignments, categories, *m_scene);
AZStd::sort(categories.begin(), categories.end(),
[](const Events::ManifestMetaInfo::CategoryRegistration& lhs, const Events::ManifestMetaInfo::CategoryRegistration& rhs)
{
return (rhs.m_preferredOrder - lhs.m_preferredOrder) > 0;
}
);
AZStd::string currentCategory;
AZStd::vector<AZ::Uuid> types;
for (auto& category : categories)
{
if (category.m_categoryName != currentCategory)
{
// Skip first occurrence.
if (!currentCategory.empty())
{
ManifestWidgetPage* page = new ManifestWidgetPage(m_serializeContext, AZStd::move(types));
AddPage(currentCategory.c_str(), page);
}
currentCategory = category.m_categoryName;
AZ_Assert(types.empty(), "Expecting vectors to be empty after being moved.");
}
types.push_back(category.m_categoryTargetGroupId);
}
// Add final page
if (!currentCategory.empty())
{
ManifestWidgetPage* page = new ManifestWidgetPage(m_serializeContext, AZStd::move(types));
AddPage(currentCategory.c_str(), page);
}
}
void ManifestWidget::AddPage(const QString& category, ManifestWidgetPage* page)
{
m_pages.push_back(page);
ui->m_tabs->addTab(page, category);
}
} // namespace UI
} // namespace SceneAPI
} // namespace AZ
#include <SceneWidgets/moc_ManifestWidget.cpp>
@@ -0,0 +1,82 @@
#pragma once
/*
* 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.
*
*/
#if !defined(Q_MOC_RUN)
#include <QWidget>
#include <QTabWidget>
#include <AzCore/std/string/string.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/containers/unordered_map.h>
#include <SceneAPI/SceneUI/SceneUIConfiguration.h>
#endif
namespace AZ
{
class SerializeContext;
namespace SceneAPI
{
namespace Containers
{
class Scene;
}
namespace DataTypes
{
class IManifestObject;
}
namespace UI
{
// QT space
namespace Ui
{
class ManifestWidget;
}
class ManifestWidgetPage;
class SCENE_UI_API ManifestWidget : public QWidget
{
Q_OBJECT
public:
using PageList = AZStd::vector<ManifestWidgetPage*>;
explicit ManifestWidget(SerializeContext* serializeContext, QWidget* parent = nullptr);
~ManifestWidget() override;
void BuildFromScene(const AZStd::shared_ptr<Containers::Scene>& scene);
bool AddObject(const AZStd::shared_ptr<DataTypes::IManifestObject>& object);
bool RemoveObject(const AZStd::shared_ptr<DataTypes::IManifestObject>& object);
AZStd::shared_ptr<Containers::Scene> GetScene();
AZStd::shared_ptr<const Containers::Scene> GetScene() const;
//! Finds this ManifestWidget if the given widget is it's child, otherwise returns null.
static ManifestWidget* FindRoot(QWidget* child);
//! Finds this ManifestWidget if the given widget is it's child, otherwise returns null.
static const ManifestWidget* FindRoot(const QWidget* child);
protected:
void BuildPages();
void AddPage(const QString& category, ManifestWidgetPage* page);
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
PageList m_pages;
QScopedPointer<Ui::ManifestWidget> ui;
AZStd::shared_ptr<Containers::Scene> m_scene;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
SerializeContext* m_serializeContext;
};
} // namespace UI
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AZ::SceneAPI::UI::ManifestWidget</class>
<widget class="QWidget" name="AZ::SceneAPI::UI::ManifestWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QVBoxLayout" name="mainLayout">
<item>
<widget class="AzQtComponents::TabWidget" name="m_tabs"/>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>AzQtComponents::TabWidget</class>
<extends>QTabWidget</extends>
<header>AzQtComponents/Components/Widgets/TabWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,426 @@
/*
* 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 <QMenu>
#include <QTimer>
#include <QScrollArea>
#include <QScrollBar>
#include <QMessageBox>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzCore/std/string/conversions.h>
#include <SceneWidgets/ui_ManifestWidgetPage.h>
#include <SceneAPI/SceneCore/DataTypes/IManifestObject.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/Containers/SceneManifest.h>
#include <SceneAPI/SceneCore/Containers/Views/PairIterator.h>
#include <SceneAPI/SceneCore/Events/ManifestMetaInfoBus.h>
#include <SceneAPI/SceneUI/SceneWidgets/ManifestWidget.h>
#include <SceneAPI/SceneUI/SceneWidgets/ManifestWidgetPage.h>
namespace AZ
{
namespace SceneAPI
{
namespace UI
{
ManifestWidgetPage::ManifestWidgetPage(SerializeContext* context, AZStd::vector<AZ::Uuid>&& classTypeIds)
: m_classTypeIds(AZStd::move(classTypeIds))
, ui(new Ui::ManifestWidgetPage())
, m_propertyEditor(nullptr)
, m_context(context)
, m_capSize(100)
{
ui->setupUi(this);
m_propertyEditor = new AzToolsFramework::ReflectedPropertyEditor(nullptr);
m_propertyEditor->Setup(context, this, true, 250);
ui->m_mainLayout->insertWidget(0, m_propertyEditor);
BuildAndConnectAddButton();
BusConnect();
}
ManifestWidgetPage::~ManifestWidgetPage()
{
BusDisconnect();
}
void ManifestWidgetPage::SetCapSize(size_t size)
{
m_capSize = size;
}
size_t ManifestWidgetPage::GetCapSize() const
{
return m_capSize;
}
bool ManifestWidgetPage::SupportsType(const AZStd::shared_ptr<DataTypes::IManifestObject>& object)
{
for (Uuid& id : m_classTypeIds)
{
if (object->RTTI_IsTypeOf(id))
{
return true;
}
}
return false;
}
bool ManifestWidgetPage::AddObject(const AZStd::shared_ptr<DataTypes::IManifestObject>& object)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Editor);
if (!SupportsType(object))
{
return false;
}
if (!m_propertyEditor->AddInstance(object.get(), object->RTTI_GetType()))
{
AZ_Assert(false, "Failed to add manifest object to Reflected Property Editor.");
return false;
}
// Add new object to the list so it's ready for updating later on.
m_objects.push_back(object);
QTimer::singleShot(0, this,
[this]()
{
ScrollToBottom();
}
);
return true;
}
bool ManifestWidgetPage::RemoveObject(const AZStd::shared_ptr<DataTypes::IManifestObject>& object)
{
if (SupportsType(object))
{
// Explicitly keep a copy of the shared pointer to guarantee that the manifest object isn't
// deleted before it can be queued for the delete deletion.
AZStd::shared_ptr<DataTypes::IManifestObject> temp = object;
(void)temp;
auto it = AZStd::find(m_objects.begin(), m_objects.end(), object);
if (it == m_objects.end())
{
AZ_Assert(false, "Manifest object not part of manifest page.");
return false;
}
m_objects.erase(it);
if (m_objects.size() == 0)
{
// We won't get property modified event if it's the last element removed
EmitObjectChanged();
}
// If the property editor is immediately updated here QT will do some processing in an unexpected order,
// leading to heap corruption. To avoid this, keep a cached version of the deleted object and
// delay the rebuilding of the property editor to the end of the update cycle.
QTimer::singleShot(0, this,
[this, object]()
{
// Explicitly keep a copy of the shared pointer to guarantee that the manifest object isn't
// deleted between updates of QT.
(void)object;
m_propertyEditor->ClearInstances();
for (auto& instance : m_objects)
{
if (!m_propertyEditor->AddInstance(instance.get(), instance->RTTI_GetType()))
{
AZ_Assert(false, "Failed to add manifest object to Reflected Property Editor.");
}
}
RefreshPage();
}
);
return true;
}
else
{
return false;
}
}
size_t ManifestWidgetPage::ObjectCount() const
{
return m_objects.size();
}
void ManifestWidgetPage::Clear()
{
m_objects.clear();
m_propertyEditor->ClearInstances();
}
void ManifestWidgetPage::BeforePropertyModified(AzToolsFramework::InstanceDataNode* /*pNode*/)
{
}
void ManifestWidgetPage::AfterPropertyModified(AzToolsFramework::InstanceDataNode* node)
{
if (node)
{
while (node = node->GetParent())
{
if (const AZ::SerializeContext::ClassData* classData = node->GetClassMetadata(); classData && classData->m_azRtti)
{
if (const DataTypes::IManifestObject* cast = classData->m_azRtti->Cast<DataTypes::IManifestObject>(node->FirstInstance()); cast)
{
AZ_Assert(AZStd::find_if(m_objects.begin(), m_objects.end(),
[cast](const AZStd::shared_ptr<DataTypes::IManifestObject>& object)
{
return object.get() == cast;
}) != m_objects.end(), "ManifestWidgetPage detected an update of a field it doesn't own.");
EmitObjectChanged(cast);
break;
}
}
}
}
}
void ManifestWidgetPage::SetPropertyEditingActive(AzToolsFramework::InstanceDataNode* /*pNode*/)
{
}
void ManifestWidgetPage::SetPropertyEditingComplete(AzToolsFramework::InstanceDataNode* /*pNode*/)
{
}
void ManifestWidgetPage::SealUndoStack()
{
}
void ManifestWidgetPage::ScrollToBottom()
{
QScrollArea* propertyGridScrollArea = m_propertyEditor->findChild<QScrollArea*>();
if (propertyGridScrollArea)
{
propertyGridScrollArea->verticalScrollBar()->setSliderPosition(propertyGridScrollArea->verticalScrollBar()->maximum());
}
}
void ManifestWidgetPage::RefreshPage()
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Editor);
m_propertyEditor->InvalidateAll();
m_propertyEditor->ExpandAll();
}
void ManifestWidgetPage::OnSingleGroupAdd()
{
if (m_classTypeIds.size() > 0)
{
if (m_objects.size() >= m_capSize)
{
QMessageBox::warning(this, "Cap reached", QString("The group container reached its cap of %1 entries.\nPlease remove groups to free up space.").
arg(m_capSize));
return;
}
AddNewObject(m_classTypeIds[0]);
}
}
void ManifestWidgetPage::OnMultiGroupAdd(const Uuid& id)
{
if (m_objects.size() >= m_capSize)
{
QMessageBox::warning(this, "Cap reached", QString("The group container reached its cap of %1 entries.\nPlease remove groups to free up space.").
arg(m_capSize));
return;
}
AddNewObject(id);
}
void ManifestWidgetPage::BuildAndConnectAddButton()
{
if (m_classTypeIds.size() == 0)
{
ui->m_addButton->setText("No types for this group");
}
else if (m_classTypeIds.size() == 1)
{
AZStd::string className = ClassIdToName(m_classTypeIds[0]);
AZStd::to_lower(className.begin(), className.end());
ui->m_addButton->setText(QString::fromLatin1("Add another %1").arg(className.c_str()));
connect(ui->m_addButton, &QPushButton::clicked, this, &ManifestWidgetPage::OnSingleGroupAdd);
}
else
{
QMenu* menu = new QMenu();
AZStd::vector<AZStd::string> classNames;
for (Uuid& id : m_classTypeIds)
{
AZStd::string className = ClassIdToName(id);
menu->addAction(className.c_str(),
[this, id]()
{
OnMultiGroupAdd(id);
}
);
AZStd::to_lower(className.begin(), className.end());
classNames.push_back(className);
}
connect(menu, &QMenu::aboutToShow, this,
[this, menu]()
{
menu->setFixedWidth(ui->m_addButton->width());
}
);
ui->m_addButton->setMenu(menu);
AZStd::string buttonText = "Add another ";
AzFramework::StringFunc::Join(buttonText, classNames.begin(), classNames.end(), " or ");
ui->m_addButton->setText(buttonText.c_str());
}
}
AZStd::string ManifestWidgetPage::ClassIdToName(const Uuid& id) const
{
static const AZStd::string s_groupSuffix = "group";
const SerializeContext::ClassData* classData = m_context->FindClassData(id);
if (!classData)
{
return "<type not registered>";
}
AZStd::string className;
if (classData->m_editData)
{
className = classData->m_editData->m_name;
}
else
{
className = classData->m_name;
}
// Get rid of "Group" suffix and all trailing whitespace (e.g. "mesh group" -> "mesh")
if (className.length() > s_groupSuffix.length())
{
size_t potentialSuffixOffset = className.length() - s_groupSuffix.length();
if (AzFramework::StringFunc::Equal(className.c_str() + potentialSuffixOffset, s_groupSuffix.c_str()))
{
AzFramework::StringFunc::LKeep(className, potentialSuffixOffset - 1);
AzFramework::StringFunc::Strip(className, ' ');
}
}
return className;
}
void ManifestWidgetPage::AddNewObject(const Uuid& id)
{
AZ_TraceContext("Instance id", id);
const SerializeContext::ClassData* classData = m_context->FindClassData(id);
AZ_Assert(classData, "Type not registered.");
if (classData)
{
AZ_TraceContext("Object Type", classData->m_name);
AZ_Assert(classData->m_factory, "Registered type has no factory to create a new instance with.");
if (classData->m_factory)
{
ManifestWidget* parent = ManifestWidget::FindRoot(this);
AZ_Assert(parent, "ManifestWidgetPage isn't docked in a ManifestWidget.");
if (!parent)
{
return;
}
AZStd::shared_ptr<Containers::Scene> scene = parent->GetScene();
if (!scene)
{
return;
}
Containers::SceneManifest& manifest = scene->GetManifest();
void* rawInstance = classData->m_factory->Create(classData->m_name);
AZ_Assert(rawInstance, "Serialization factory failed to construct new instance.");
if (!rawInstance)
{
return;
}
AZStd::shared_ptr<DataTypes::IManifestObject> instance(reinterpret_cast<DataTypes::IManifestObject*>(rawInstance));
EBUS_EVENT(Events::ManifestMetaInfoBus, InitializeObject, *scene, *instance);
if (!manifest.AddEntry(instance))
{
AZ_Assert(false, "Unable to add new object to manifest.");
}
if (!AddObject(instance))
{
AZ_Assert(false, "Unable to add new object to Reflected Property Editor.");
}
// Refresh the page after adding this new object.
RefreshPage();
EmitObjectChanged();
}
}
}
void ManifestWidgetPage::EmitObjectChanged(const DataTypes::IManifestObject* object)
{
ManifestWidget* parent = ManifestWidget::FindRoot(this);
AZ_Assert(parent, "ManifestWidgetPage isn't docked in a ManifestWidget.");
if (!parent)
{
return;
}
AZStd::shared_ptr<Containers::Scene> scene = parent->GetScene();
if (!scene)
{
return;
}
Events::ManifestMetaInfoBus::Broadcast(&Events::ManifestMetaInfoBus::Events::ObjectUpdated, *scene, object, this);
}
void ManifestWidgetPage::ObjectUpdated([[maybe_unused]] const Containers::Scene& scene, const DataTypes::IManifestObject* target, void* sender)
{
if (sender != this && target != nullptr && m_propertyEditor)
{
if (AZStd::find_if(m_objects.begin(), m_objects.end(),
[target](const AZStd::shared_ptr<DataTypes::IManifestObject>& object)
{
return object.get() == target;
}) != m_objects.end())
{
m_propertyEditor->InvalidateAttributesAndValues();
}
}
}
} // namespace UI
} // namespace SceneAPI
} // namespace AZ
#include <SceneWidgets/moc_ManifestWidgetPage.cpp>
@@ -0,0 +1,104 @@
#pragma once
/*
* 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.
*
*/
#if !defined(Q_MOC_RUN)
#include <QWidget>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/Math/Uuid.h>
#include <AzToolsFramework/UI/PropertyEditor/ReflectedPropertyEditor.hxx>
#include <SceneAPI/SceneCore/Events/ManifestMetaInfoBus.h>
#endif
namespace AZ
{
class SerializeContext;
namespace SceneAPI
{
namespace DataTypes
{
class IManifestObject;
}
namespace UI
{
// QT space
namespace Ui
{
class ManifestWidgetPage;
}
class ManifestWidgetPage
: public QWidget
, public AzToolsFramework::IPropertyEditorNotify
, public Events::ManifestMetaInfoBus::Handler
{
Q_OBJECT
public:
ManifestWidgetPage(SerializeContext* context, AZStd::vector<AZ::Uuid>&& classTypeIds);
~ManifestWidgetPage() override;
// Sets the number of entries the user can add through this widget. It doesn't limit
// the amount of entries that can be stored.
virtual void SetCapSize(size_t size);
virtual size_t GetCapSize() const;
virtual bool SupportsType(const AZStd::shared_ptr<DataTypes::IManifestObject>& object);
virtual bool AddObject(const AZStd::shared_ptr<DataTypes::IManifestObject>& object);
virtual bool RemoveObject(const AZStd::shared_ptr<DataTypes::IManifestObject>& object);
virtual size_t ObjectCount() const;
virtual void Clear();
virtual void ScrollToBottom();
void RefreshPage(); // Called when a scene is initially loaded, after all objects are populated.
protected slots:
//! Callback that's triggered when the add button only has 1 entry.
void OnSingleGroupAdd();
protected:
//! Callback that's triggered when the add button has multiple entries.
virtual void OnMultiGroupAdd(const Uuid& id);
virtual void BuildAndConnectAddButton();
virtual AZStd::string ClassIdToName(const Uuid& id) const;
virtual void AddNewObject(const Uuid& id);
//! Report that an object on this page has been updated.
//! @param object Pointer to the changed object. If the manifest itself has been update
//! for instance after adding or removing a group use null to update the entire manifest.
virtual void EmitObjectChanged(const DataTypes::IManifestObject* object = nullptr);
// IPropertyEditorNotify Interface Methods
void BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode) override;
void AfterPropertyModified(AzToolsFramework::InstanceDataNode* pNode) override;
void SetPropertyEditingActive(AzToolsFramework::InstanceDataNode* pNode) override;
void SetPropertyEditingComplete(AzToolsFramework::InstanceDataNode* pNode) override;
void SealUndoStack() override;
// ManifestMetaInfoBus
void ObjectUpdated(const Containers::Scene& scene, const DataTypes::IManifestObject* target, void* sender) override;
AZStd::vector<AZ::Uuid> m_classTypeIds;
AZStd::vector<AZStd::shared_ptr<DataTypes::IManifestObject>> m_objects;
QScopedPointer<Ui::ManifestWidgetPage> ui;
AzToolsFramework::ReflectedPropertyEditor* m_propertyEditor;
SerializeContext* m_context;
size_t m_capSize;
};
} // namespace UI
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AZ::SceneAPI::UI::ManifestWidgetPage</class>
<widget class="QWidget" name="AZ::SceneAPI::UI::ManifestWidgetPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>350</width>
<height>275</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QFrame" name="header">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="m_addButton">
<property name="maximumSize">
<size>
<width>250</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Add another</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="m_mainLayout"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,107 @@
/*
* 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 <QSplitter>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <SceneWidgets/ui_SceneGraphInspectWidget.h>
#include <SceneAPI/SceneCore/DataTypes/IGraphObject.h>
#include <SceneAPI/SceneCore/Events/GraphMetaInfoBus.h>
#include <SceneAPI/SceneUI/SceneWidgets/SceneGraphWidget.h>
#include <SceneAPI/SceneUI/SceneWidgets/SceneGraphInspectWidget.h>
#include <AzToolsFramework/UI/PropertyEditor/ReflectedPropertyEditor.hxx>
namespace AZ
{
namespace SceneAPI
{
namespace UI
{
AZ_CLASS_ALLOCATOR_IMPL(SceneGraphInspectWidget, SystemAllocator, 0);
SceneGraphInspectWidget::SceneGraphInspectWidget(const Containers::Scene& scene, QWidget* parent, SerializeContext* context)
: QWidget(parent)
, ui(new Ui::SceneGraphInspectWidget())
, m_graphView(aznew SceneGraphWidget(scene, this))
, m_propertyEditor(aznew AzToolsFramework::ReflectedPropertyEditor(this))
, m_context(context)
{
ui->setupUi(this);
if (!m_context)
{
ComponentApplicationBus::BroadcastResult(m_context, &ComponentApplicationBus::Events::GetSerializeContext);
}
m_propertyEditor->Setup(m_context, nullptr, true, 100);
m_propertyEditor->setEnabled(false);
m_graphView->Build();
ui->m_splitter->insertWidget(0, m_graphView.data());
ui->m_propertyEditorLayout->addWidget(m_propertyEditor.data());
connect(m_graphView.data(), &SceneGraphWidget::SelectionChanged, this, &SceneGraphInspectWidget::OnSelectionChanged);
}
SceneGraphInspectWidget::~SceneGraphInspectWidget() = default;
void SceneGraphInspectWidget::OnSelectionChanged(AZStd::shared_ptr<const DataTypes::IGraphObject> item)
{
using namespace AZ::SceneAPI::Events;
if (item)
{
if (m_context)
{
// Only try to show if there's a registered editor for the class.
const SerializeContext::ClassData* classData = m_context->FindClassData(item->RTTI_GetType());
if (classData && classData->m_editData)
{
// The reflected property editor is made for editing (as the name suggest) not inspecting,
// therefore it only accepts objects it can modify.
DataTypes::IGraphObject* mutableItem = const_cast<DataTypes::IGraphObject*>(item.get());
m_propertyEditor->ClearInstances();
m_propertyEditor->AddInstance(mutableItem, item->RTTI_GetType());
m_propertyEditor->InvalidateAll();
m_propertyEditor->ExpandAll();
ui->m_infoStack->setCurrentIndex(1);
return;
}
}
AZStd::string description = "<html><head/><body><p>";
if (item->RTTI_GetTypeName())
{
description += "<b>";
description += item->RTTI_GetTypeName();
description += "</b></p><p>";
}
AZStd::string tooltip;
GraphMetaInfoBus::Broadcast(&GraphMetaInfoBus::Events::GetToolTip, tooltip, item.get());
description += tooltip.empty() ? "No information found for this node." : tooltip;
description += "</p></body></html>";
ui->m_noSelectionLabel->setText(description.c_str());
}
else
{
ui->m_noSelectionLabel->setText("Empty node selected.");
}
ui->m_infoStack->setCurrentIndex(0);
}
} // UI
} // SceneAPI
} // AZ
#include <SceneWidgets/moc_SceneGraphInspectWidget.cpp>
@@ -0,0 +1,76 @@
#pragma once
/*
* 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.
*
*/
#if !defined(Q_MOC_RUN)
#include <QWidget>
#include <AzCore/Memory/SystemAllocator.h>
#include <SceneAPI/SceneUI/SceneUIConfiguration.h>
#endif
namespace AzToolsFramework
{
class ReflectedPropertyEditor;
}
namespace AZ
{
class SerializeContext;
namespace SceneAPI
{
namespace Containers
{
class Scene;
}
namespace DataTypes
{
class IGraphObject;
}
namespace UI
{
// QT space
namespace Ui
{
class SceneGraphInspectWidget;
}
class SceneGraphWidget;
class SCENE_UI_API SceneGraphInspectWidget
: public QWidget
{
public:
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR_DECL;
explicit SceneGraphInspectWidget(const Containers::Scene& scene, QWidget* parent = nullptr, SerializeContext* context = nullptr);
~SceneGraphInspectWidget() override;
protected:
void OnSelectionChanged(AZStd::shared_ptr<const DataTypes::IGraphObject> item);
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
QScopedPointer<Ui::SceneGraphInspectWidget> ui;
QScopedPointer<SceneGraphWidget> m_graphView;
QScopedPointer<AzToolsFramework::ReflectedPropertyEditor> m_propertyEditor;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
SerializeContext* m_context;
};
} // UI
} // SceneAPI
} // AZ
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AZ::SceneAPI::UI::SceneGraphInspectWidget</class>
<widget class="QWidget" name="AZ::SceneAPI::UI::SceneGraphInspectWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>314</width>
<height>275</height>
</rect>
</property>
<property name="windowTitle">
<string/>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QSplitter" name="m_splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QStackedWidget" name="m_infoStack">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="m_noSelection">
<layout class="QVBoxLayout" name="noSelectionLayout">
<item>
<widget class="QLabel" name="m_noSelectionLabel">
<property name="text">
<string>Select a node to inspect its properties in a read-only format.</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::Alignment(Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop)</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="m_propertyEditor">
<layout class="QHBoxLayout" name="m_propertyEditorLayout"/>
</widget>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
@@ -0,0 +1,538 @@
/*
* 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 <QStandardItemModel>
#include <SceneWidgets/ui_SceneGraphWidget.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/stack.h>
#include <AzToolsFramework/Debug/TraceContext.h>
#include <SceneAPI/SceneCore/Containers/Scene.h>
#include <SceneAPI/SceneCore/Containers/Views/PairIterator.h>
#include <SceneAPI/SceneCore/Containers/Views/SceneGraphDownwardsIterator.h>
#include <SceneAPI/SceneCore/DataTypes/IGraphObject.h>
#include <SceneAPI/SceneCore/DataTypes/ManifestBase/ISceneNodeSelectionList.h>
#include <SceneAPI/SceneCore/Events/GraphMetaInfoBus.h>
#include <SceneAPI/SceneCore/Utilities/SceneGraphSelector.h>
#include <SceneAPI/SceneUI/SceneWidgets/SceneGraphWidget.h>
namespace AZ
{
namespace SceneAPI
{
namespace UI
{
AZ_CLASS_ALLOCATOR_IMPL(SceneGraphWidget, SystemAllocator, 0)
SceneGraphWidget::SceneGraphWidget(const Containers::Scene& scene, QWidget* parent)
: QWidget(parent)
, ui(new Ui::SceneGraphWidget())
, m_treeModel(new QStandardItemModel())
, m_scene(scene)
, m_targetList(nullptr)
, m_selectedCount(0)
, m_totalCount(0)
, m_endPointOption(EndPointOption::AlwaysShow)
, m_checkableOption(CheckableOption::NoneCheckable)
{
SetupUI();
}
SceneGraphWidget::SceneGraphWidget(const Containers::Scene& scene, const DataTypes::ISceneNodeSelectionList& targetList,
QWidget* parent)
: QWidget(parent)
, ui(new Ui::SceneGraphWidget())
, m_treeModel(new QStandardItemModel())
, m_scene(scene)
, m_targetList(targetList.Copy())
, m_selectedCount(0)
, m_totalCount(0)
, m_endPointOption(EndPointOption::OnlyShowFilterTypes)
, m_checkableOption(CheckableOption::AllCheckable)
{
SetupUI();
}
SceneGraphWidget::~SceneGraphWidget() = default;
AZStd::unique_ptr<DataTypes::ISceneNodeSelectionList>&& SceneGraphWidget::ClaimTargetList()
{
return AZStd::move(m_targetList);
}
void SceneGraphWidget::IncludeEndPoints(EndPointOption option)
{
m_endPointOption = option;
}
void SceneGraphWidget::MakeCheckable(CheckableOption option)
{
m_checkableOption = option;
}
void SceneGraphWidget::AddFilterType(const Uuid& id)
{
if (m_filterTypes.find(id) == m_filterTypes.end())
{
m_filterTypes.insert(id);
}
}
void SceneGraphWidget::AddVirtualFilterType(Crc32 name)
{
if (m_filterVirtualTypes.find(name) == m_filterVirtualTypes.end())
{
m_filterVirtualTypes.insert(name);
}
}
void SceneGraphWidget::SetupUI()
{
ui->setupUi(this);
ui->m_selectionTree->setHeaderHidden(true);
ui->m_selectionTree->setModel(m_treeModel.data());
connect(ui->m_selectAllCheckBox, &QCheckBox::stateChanged, this, &SceneGraphWidget::OnSelectAllCheckboxStateChanged);
connect(m_treeModel.data(), &QStandardItemModel::itemChanged, this, &SceneGraphWidget::OnTreeItemStateChanged);
connect(ui->m_selectionTree->selectionModel(), &QItemSelectionModel::currentChanged, this, &SceneGraphWidget::OnTreeItemChanged);
}
void SceneGraphWidget::Build()
{
setUpdatesEnabled(false);
QSignalBlocker blocker(m_treeModel.data());
const Containers::SceneGraph& graph = m_scene.GetGraph();
m_selectedCount = 0;
m_totalCount = 0;
m_treeModel->clear();
m_treeItems.clear();
m_treeItems = AZStd::vector<QStandardItem*>(graph.GetNodeCount(), nullptr);
if (m_checkableOption == CheckableOption::NoneCheckable)
{
ui->m_selectAllCheckBox->hide();
}
else
{
ui->m_selectAllCheckBox->show();
}
auto sceneGraphView = Containers::Views::MakePairView(graph.GetNameStorage(), graph.GetContentStorage());
auto sceneGraphDownardsIteratorView = Containers::Views::MakeSceneGraphDownwardsView<Containers::Views::BreadthFirst>(
graph, graph.GetRoot(), sceneGraphView.begin(), true);
// Some importer implementations may write an empty node to force collection all items under a common root
// If that is the case, we're going to skip it so we don't show the user an empty node root
auto iterator = sceneGraphDownardsIteratorView.begin();
if (iterator->first.GetPathLength() == 0 && !iterator->second)
{
++iterator;
}
for (; iterator != sceneGraphDownardsIteratorView.end(); ++iterator)
{
Containers::SceneGraph::HierarchyStorageConstIterator hierarchy = iterator.GetHierarchyIterator();
Containers::SceneGraph::NodeIndex currentIndex = graph.ConvertToNodeIndex(hierarchy);
AZ_Assert(currentIndex.IsValid(), "While iterating through the Scene Graph an unexpected invalid entry was found.");
AZStd::shared_ptr<const DataTypes::IGraphObject> currentItem = iterator->second;
if (hierarchy->IsEndPoint())
{
switch (m_endPointOption)
{
case EndPointOption::AlwaysShow:
break;
case EndPointOption::NeverShow:
continue;
case EndPointOption::OnlyShowFilterTypes:
if (IsFilteredType(currentItem, currentIndex))
{
break;
}
else
{
continue;
}
default:
AZ_Assert(false, "Unsupported type %i for end point option.", m_endPointOption);
break;
}
}
bool isCheckable = false;
switch (m_checkableOption)
{
case CheckableOption::AllCheckable:
isCheckable = true;
break;
case CheckableOption::NoneCheckable:
isCheckable = false;
break;
case CheckableOption::OnlyFilterTypesCheckable:
isCheckable = IsFilteredType(currentItem, currentIndex);
break;
default:
AZ_Assert(false, "Unsupported type %i for checkable option.", m_checkableOption);
isCheckable = false;
break;
}
QStandardItem* treeItem = BuildTreeItem(currentItem, iterator->first, isCheckable, hierarchy->IsEndPoint());
if (isCheckable)
{
if (IsSelected(iterator->first, false))
{
treeItem->setCheckState(Qt::CheckState::Checked);
m_selectedCount++;
}
m_totalCount++;
}
m_treeItems[currentIndex.AsNumber()] = treeItem;
Containers::SceneGraph::NodeIndex parentIndex = graph.GetNodeParent(currentIndex);
if (parentIndex.IsValid() && m_treeItems[parentIndex.AsNumber()])
{
m_treeItems[parentIndex.AsNumber()]->appendRow(treeItem);
}
else
{
m_treeModel->appendRow(treeItem);
}
}
ui->m_selectionTree->expandAll();
UpdateSelectAllStatus();
setUpdatesEnabled(true);
}
bool SceneGraphWidget::IsFilteredType(const AZStd::shared_ptr<const DataTypes::IGraphObject>& object,
Containers::SceneGraph::NodeIndex index) const
{
if (!object)
{
return false;
}
for (const Uuid& id : m_filterTypes)
{
if (object->RTTI_IsTypeOf(id))
{
return true;
}
}
if (!m_filterVirtualTypes.empty())
{
AZStd::set<Crc32> virtualTypes;
EBUS_EVENT(Events::GraphMetaInfoBus, GetVirtualTypes, virtualTypes, m_scene, index);
for (Crc32 name : virtualTypes)
{
if (m_filterVirtualTypes.find(name) != m_filterVirtualTypes.end())
{
return true;
}
}
}
return false;
}
QStandardItem* SceneGraphWidget::BuildTreeItem(const AZStd::shared_ptr<const DataTypes::IGraphObject>& object,
const Containers::SceneGraph::Name& name, bool isCheckable, [[maybe_unused]] bool isEndPoint) const
{
QStandardItem* treeItem = new QStandardItem(name.GetName());
treeItem->setData(QString(name.GetPath()));
treeItem->setEditable(false);
treeItem->setCheckable(isCheckable);
if (object)
{
AZStd::string toolTip;
EBUS_EVENT(Events::GraphMetaInfoBus, GetToolTip, toolTip, object.get());
if (toolTip.empty())
{
treeItem->setToolTip(QString::asprintf("%s\n<%s>", name.GetPath(), object->RTTI_GetTypeName()));
}
else
{
treeItem->setToolTip(QString::asprintf("%s\n\n%s", name.GetPath(), toolTip.c_str()));
}
AZStd::string iconPath;
EBUS_EVENT(Events::GraphMetaInfoBus, GetIconPath, iconPath, object.get());
if (!iconPath.empty())
{
treeItem->setIcon(QIcon(iconPath.c_str()));
}
}
return treeItem;
}
void SceneGraphWidget::OnSelectAllCheckboxStateChanged()
{
setUpdatesEnabled(false);
QSignalBlocker blocker(m_treeModel.data());
Qt::CheckState state = ui->m_selectAllCheckBox->checkState();
if (m_targetList)
{
m_targetList->ClearSelectedNodes();
m_targetList->ClearUnselectedNodes();
for (QStandardItem* item : m_treeItems)
{
if (!item || !item->isCheckable())
{
continue;
}
item->setCheckState(state);
QVariant itemData = item->data();
if (itemData.isValid())
{
AZStd::string fullName = itemData.toString().toUtf8().data();
if (state == Qt::CheckState::Unchecked)
{
m_targetList->RemoveSelectedNode(fullName);
}
else
{
m_targetList->AddSelectedNode(AZStd::move(fullName));
}
}
}
}
else
{
for (QStandardItem* item : m_treeItems)
{
if (item && item->isCheckable())
{
item->setCheckState(state);
}
}
}
m_selectedCount = (state == Qt::CheckState::Unchecked) ? 0 : m_totalCount;
UpdateSelectAllStatus();
setUpdatesEnabled(true);
}
void SceneGraphWidget::OnTreeItemStateChanged(QStandardItem* item)
{
setUpdatesEnabled(false);
QSignalBlocker blocker(m_treeModel.data());
Qt::CheckState state = item->checkState();
bool decrement = (state == Qt::CheckState::Unchecked);
if (decrement)
{
if (!RemoveSelection(item))
{
item->setCheckState(Qt::CheckState::Checked);
return;
}
}
else
{
if (!AddSelection(item))
{
item->setCheckState(Qt::CheckState::Unchecked);
return;
}
}
AZStd::stack<QStandardItem*> children;
int rowCount = item->rowCount();
for (int index = 0; index < rowCount; ++index)
{
children.push(item->child(index));
}
while (!children.empty())
{
QStandardItem* current = children.top();
children.pop();
if (decrement)
{
if (current->checkState() != Qt::CheckState::Unchecked && RemoveSelection(current))
{
current->setCheckState(state);
}
}
else
{
if (current->checkState() == Qt::CheckState::Unchecked && AddSelection(current))
{
current->setCheckState(state);
}
}
int rowCount2 = current->rowCount();
for (int index = 0; index < rowCount2; ++index)
{
children.push(current->child(index));
}
}
UpdateSelectAllStatus();
setUpdatesEnabled(true);
}
void SceneGraphWidget::OnTreeItemChanged(const QModelIndex& current, const QModelIndex& /*previous*/)
{
QStandardItem* item = m_treeModel->itemFromIndex(current);
QVariant itemData = item->data();
if (!itemData.isValid() || itemData.type() != QVariant::Type::String)
{
return;
}
AZStd::string fullName = itemData.toString().toUtf8().data();
AZ_TraceContext("Selected item", fullName);
Containers::SceneGraph::NodeIndex nodeIndex = m_scene.GetGraph().Find(fullName);
AZ_Assert(nodeIndex.IsValid(), "Invalid node added to tree.");
if (!nodeIndex.IsValid())
{
return;
}
Q_EMIT SelectionChanged(m_scene.GetGraph().GetNodeContent(nodeIndex));
}
void SceneGraphWidget::UpdateSelectAllStatus()
{
QSignalBlocker blocker(ui->m_selectAllCheckBox);
if (m_selectedCount == m_totalCount)
{
ui->m_selectAllCheckBox->setText("Unselect all");
ui->m_selectAllCheckBox->setCheckState(Qt::CheckState::Checked);
}
else
{
ui->m_selectAllCheckBox->setText("Select all");
ui->m_selectAllCheckBox->setCheckState(Qt::CheckState::Unchecked);
}
}
bool SceneGraphWidget::IsSelected(const Containers::SceneGraph::Name& name, bool updateNodeSelection) const
{
if (!m_targetList)
{
return false;
}
if (updateNodeSelection)
{
// Use a temp list to get a valid state of the UI here based on selected/unselected nodes
// We use the temp list so that the real list actually keeps track of the user's selection
// Since UpdateNodeSelection will modify selected/unselected node lists for us.
AZStd::unique_ptr<DataTypes::ISceneNodeSelectionList> tempList(m_targetList->Copy());
Utilities::SceneGraphSelector::UpdateNodeSelection(m_scene.GetGraph(), *tempList);
return IsSelectedInSelectionList(name, *tempList);
}
else
{
return IsSelectedInSelectionList(name, *m_targetList);
}
}
bool SceneGraphWidget::IsSelectedInSelectionList(const Containers::SceneGraph::Name& name, const DataTypes::ISceneNodeSelectionList& targetList) const
{
size_t count = targetList.GetSelectedNodeCount();
for (size_t selectedNodeIndex = 0; selectedNodeIndex < count; ++selectedNodeIndex)
{
if (targetList.GetSelectedNode(selectedNodeIndex) == name.GetPath())
{
return true;
}
}
return false;
}
bool SceneGraphWidget::AddSelection(const QStandardItem* item)
{
if (!m_targetList)
{
return true;
}
QVariant itemData = item->data();
if (!itemData.isValid() || itemData.type() != QVariant::Type::String)
{
return false;
}
AZStd::string fullName = itemData.toString().toUtf8().data();
AZ_TraceContext("Item for addition", fullName);
Containers::SceneGraph::NodeIndex nodeIndex = m_scene.GetGraph().Find(fullName);
AZ_Assert(nodeIndex.IsValid(), "Invalid node added to tree.");
if (!nodeIndex.IsValid())
{
return false;
}
m_targetList->AddSelectedNode(fullName);
m_selectedCount++;
AZ_Assert(m_selectedCount <= m_totalCount, "Selected node count exceeds available node count.");
return true;
}
bool SceneGraphWidget::RemoveSelection(const QStandardItem* item)
{
if (!m_targetList)
{
return true;
}
QVariant itemData = item->data();
if (!itemData.isValid() || itemData.type() != QVariant::Type::String)
{
return false;
}
AZStd::string fullName = itemData.toString().toUtf8().data();
AZ_TraceContext("Item for removal", fullName);
Containers::SceneGraph::NodeIndex nodeIndex = m_scene.GetGraph().Find(fullName);
AZ_Assert(nodeIndex.IsValid(), "Invalid node removed from tree.");
if (!nodeIndex.IsValid())
{
return false;
}
m_targetList->RemoveSelectedNode(fullName);
AZ_Assert(m_selectedCount > 0, "Selected node count can not be decremented below zero.");
m_selectedCount--;
return true;
}
QCheckBox* SceneGraphWidget::GetQCheckBox()
{
return ui->m_selectAllCheckBox;
}
QTreeView* SceneGraphWidget::GetQTreeView()
{
return ui->m_selectionTree;
}
} // namespace UI
} // namespace SceneAPI
} // namespace AZ
#include <SceneWidgets/moc_SceneGraphWidget.cpp>
@@ -0,0 +1,146 @@
/*
* 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 <string>
#include <QWidget>
#include <AzCore/Math/Uuid.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/containers/set.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <SceneAPI/SceneCore/Containers/SceneGraph.h>
#include <SceneAPI/SceneUI/SceneUIConfiguration.h>
#endif
class QStandardItem;
class QStandardItemModel;
class QCheckBox;
class QTreeView;
namespace AZ
{
namespace SceneAPI
{
namespace Containers
{
class Scene;
}
namespace DataTypes
{
class IGraphObject;
class ISceneNodeSelectionList;
}
namespace UI
{
// QT space
namespace Ui
{
class SceneGraphWidget;
}
class SCENE_UI_API SceneGraphWidget
: public QWidget
{
public:
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR_DECL
// Sets default settings for the widget. Content will not be constructed until "Build" is called.
SceneGraphWidget(const Containers::Scene& scene, QWidget* parent = nullptr);
// Sets default settings for the widget. Content will not be constructed until "Build" is called.
SceneGraphWidget(const Containers::Scene& scene, const DataTypes::ISceneNodeSelectionList& targetList,
QWidget* parent = nullptr);
~SceneGraphWidget() override;
AZStd::unique_ptr<DataTypes::ISceneNodeSelectionList>&& ClaimTargetList();
enum class EndPointOption
{
AlwaysShow, // End points are always shown.
NeverShow, // End points are never shown.
OnlyShowFilterTypes // End points are only shown if its type is in the filter type list.
};
// Updates the tree to include/exclude end points. Call "BuildTree()" to rebuild the tree.
virtual void IncludeEndPoints(EndPointOption option);
enum class CheckableOption
{
AllCheckable, // All nodes in the tree can be checked.
NoneCheckable, // No nodes can be checked.
OnlyFilterTypesCheckable // Only nodes in the filter type list can be checked.
};
// Updates the tree to include/exclude check boxes and the master selection. Call "BuildTree()" to rebuild the tree.
virtual void MakeCheckable(CheckableOption option);
// Add a type to filter for. Filter types are used to determine if a check box is added and/or to be shown if
// the type is an end point. See "IncludeEndPoints" and "MakeCheckable" for more details.
// Call "Build()" to rebuild the tree.
virtual void AddFilterType(const Uuid& id);
// Add a virtual type to filter for. Filter types are used to determine if a check box is added and/or to be shown if
// the type is an end point. See "IncludeEndPoints" and "MakeCheckable" for more details.
// Call "Build()" to rebuild the tree.
virtual void AddVirtualFilterType(Crc32 name);
// Constructs the widget's content. Call this after making one or more changes to the settings.
virtual void Build();
Q_SIGNALS:
void SelectionChanged(AZStd::shared_ptr<const DataTypes::IGraphObject> item);
protected:
virtual void SetupUI();
virtual bool IsFilteredType(const AZStd::shared_ptr<const DataTypes::IGraphObject>& object,
Containers::SceneGraph::NodeIndex index) const;
virtual QStandardItem* BuildTreeItem(const AZStd::shared_ptr<const DataTypes::IGraphObject>& object,
const Containers::SceneGraph::Name& name, bool isCheckable, bool isEndPoint) const;
virtual void OnSelectAllCheckboxStateChanged();
virtual void OnTreeItemStateChanged(QStandardItem* item);
virtual void OnTreeItemChanged(const QModelIndex& current, const QModelIndex& previous);
virtual void UpdateSelectAllStatus();
/// If you are calling this on a lot of elements in quick succession (like in a Build function), set
/// updateNodeSelection to false for increased performance.
virtual bool IsSelected(const Containers::SceneGraph::Name& name, bool updateNodeSelection = true) const;
virtual bool AddSelection(const QStandardItem* item);
virtual bool RemoveSelection(const QStandardItem* item);
QCheckBox* GetQCheckBox();
QTreeView* GetQTreeView();
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
AZStd::vector<QStandardItem*> m_treeItems;
AZStd::set<Uuid> m_filterTypes;
AZStd::set<Crc32> m_filterVirtualTypes;
QScopedPointer<Ui::SceneGraphWidget> ui;
QScopedPointer<QStandardItemModel> m_treeModel;
AZStd::unique_ptr<DataTypes::ISceneNodeSelectionList> m_targetList;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
const Containers::Scene& m_scene;
size_t m_selectedCount;
size_t m_totalCount;
EndPointOption m_endPointOption;
CheckableOption m_checkableOption;
private:
bool IsSelectedInSelectionList(const Containers::SceneGraph::Name& name, const DataTypes::ISceneNodeSelectionList& targetList) const;
};
} // namespace UI
} // namespace SceneAPI
} // namespace AZ
@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AZ::SceneAPI::UI::SceneGraphWidget</class>
<widget class="QWidget" name="AZ::SceneAPI::UI::SceneGraphWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>258</width>
<height>222</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="m_selectAllCheckBox">
<property name="text">
<string>Select all nodes</string>
</property>
</widget>
</item>
<item>
<widget class="QTreeView" name="m_selectionTree"/>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>