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,116 @@
/*
* 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/Asset/AssetCommon.h>
#include <AzCore/UserSettings/UserSettings.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/std/containers/unordered_map.h>
namespace AZ { namespace Data { class AssetData; } }
namespace AZStd
{
template<>
struct hash<AZ::Data::Asset<AZ::Data::AssetData>>
{
size_t operator()(const AZ::Data::Asset<AZ::Data::AssetData>& asset) const
{
return asset.GetId().m_guid.GetHash();
}
};
}
namespace AzToolsFramework
{
class InstanceDataNode;
namespace AssetEditor
{
// This class is used to track all of the asset editors that were open. When the editor launches
// these settings will be used to preemptively register all windows and then open them.
struct AssetEditorWindowSettings : AZ::UserSettings
{
AZ_CLASS_ALLOCATOR(AssetEditorWindowSettings, AZ::SystemAllocator, 0);
AZ_RTTI(AssetEditorWindowSettings, "{981FE4FF-0B56-4115-9F75-79609E3D6337}", AZ::UserSettings);
//! The list of opened assets is used to restore their window state
AZStd::unordered_set<AZ::Data::Asset<AZ::Data::AssetData>> m_openAssets;
static constexpr const char* s_name = "AssetEditorWindowSettings";
static void Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<AssetEditorWindowSettings>()
->Field("m_openAssets", &AssetEditorWindowSettings::m_openAssets)
;
}
}
};
// External interaction with Asset Editor
class AssetEditorRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
//! Open Asset Editor (if it is not already open) and create a new asset of type specified, if possible
virtual void CreateNewAsset(const AZ::Data::AssetType& assetType) = 0;
virtual AZ::Outcome<bool, AZStd::string> IsAssetDataValid() { return AZ::Success(true); }
//! Open Asset Editor (if it is not already open) and load asset by reference
virtual void OpenAssetEditor(const AZ::Data::Asset<AZ::Data::AssetData>& asset) = 0;
//! Open Asset Editor (if it is not already open) and load asset by id
virtual void OpenAssetEditorById(const AZ::Data::AssetId assetId) = 0;
};
using AssetEditorRequestsBus = AZ::EBus<AssetEditorRequests>;
class AssetEditorValidationRequests
: public AZ::EBusTraits
{
public:
using BusIdType = AZ::Data::AssetId;
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
virtual void BeforePropertyEdit(AzToolsFramework::InstanceDataNode* node, AZ::Data::Asset<AZ::Data::AssetData> asset) { AZ_UNUSED(node); AZ_UNUSED(asset); }
virtual void PreAssetSave(AZ::Data::Asset<AZ::Data::AssetData> asset) { AZ_UNUSED(asset); }
virtual AZ::Outcome<bool, AZStd::string> IsAssetDataValid(const AZ::Data::Asset<AZ::Data::AssetData>& asset) { AZ_UNUSED(asset); return AZ::Success(true); }
};
using AssetEditorValidationRequestBus = AZ::EBus<AssetEditorValidationRequests>;
// Internal interaction with existing Asset Editor widget
class AssetEditorWidgetRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
//! Creates a new asset of type provided (if possible) in the currently open Asset Editor window
virtual void CreateAsset(const AZ::Data::AssetType& assetType) = 0;
//! Saves the asset being edited in the currently open Asset Editor window to the path provided
virtual void SaveAssetAs(const AZStd::string_view assetPath) = 0;
//! Opens the asset provided (by reference) in the currently open Asset Editor window
virtual void OpenAsset(const AZ::Data::Asset<AZ::Data::AssetData>& asset) = 0;
//! Opens the asset provided (by id) in the currently open Asset Editor window
virtual void OpenAssetById(const AZ::Data::AssetId assetId) = 0;
};
using AssetEditorWidgetRequestsBus = AZ::EBus<AssetEditorWidgetRequests>;
} // namespace AssetEditor
} // namespace AzToolsFramework
@@ -0,0 +1,101 @@
/*
* 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 "AssetEditorHeader.h"
#include <AzQtComponents/Components/Style.h>
namespace Ui
{
namespace HeaderConstants
{
static const char* BackgroundId = "Background";
static const char* NameId = "Name";
static const char* LocationId = "Location";
static const char* IconId = "Icon";
static const char* AssetEditorIconClassName = "AssetEditorHeaderIcon";
static const int DefaultIconSize = 16;
}
AssetEditorHeader::AssetEditorHeader(QWidget* parent)
: QFrame(parent)
{
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
m_backgroundFrame = new QFrame(this);
m_backgroundFrame->setObjectName(HeaderConstants::BackgroundId);
m_backgroundFrame->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum);
m_backgroundFrame->setAutoFillBackground(true);
m_iconLabel = new QLabel(m_backgroundFrame);
m_iconLabel->setObjectName(HeaderConstants::IconId);
m_iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
AzQtComponents::Style::addClass(m_iconLabel, HeaderConstants::AssetEditorIconClassName);
m_iconLabel->hide();
m_assetName = new AzQtComponents::ElidingLabel(m_backgroundFrame);
m_assetName->setObjectName(HeaderConstants::NameId);
m_assetName->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_assetLocation = new AzQtComponents::ElidingLabel(m_backgroundFrame);
m_assetLocation->setObjectName(HeaderConstants::LocationId);
m_assetLocation->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
m_assetLocation->hide();
m_backgroundLayout = new QHBoxLayout(this);
m_backgroundLayout->setSizeConstraint(QLayout::SetMinimumSize);
m_backgroundLayout->setSpacing(0);
m_backgroundLayout->setContentsMargins(0, 0, 0, 0);
m_backgroundLayout->addWidget(m_iconLabel);
m_backgroundLayout->addWidget(m_assetName);
m_backgroundLayout->addStretch(1);
m_backgroundLayout->addWidget(m_assetLocation);
m_backgroundFrame->setLayout(m_backgroundLayout);
m_mainLayout = new QVBoxLayout(this);
m_mainLayout->setSizeConstraint(QLayout::SetMinimumSize);
m_mainLayout->setSpacing(0);
m_mainLayout->setContentsMargins(0, 0, 0, 0);
m_mainLayout->addWidget(m_backgroundFrame);
m_backgroundFrame->setLayout(m_backgroundLayout);
}
void AssetEditorHeader::setName(const QString& name)
{
m_assetName->setText(name);
}
void AssetEditorHeader::setLocation(const QString& location)
{
m_assetLocation->setText(location);
m_assetLocation->setVisible(!location.isEmpty());
}
void AssetEditorHeader::setIcon(const QIcon& icon)
{
m_icon = icon;
if (!icon.isNull())
{
m_iconLabel->setPixmap(icon.pixmap(HeaderConstants::DefaultIconSize, HeaderConstants::DefaultIconSize));
}
m_iconLabel->setVisible(!icon.isNull());
}
} // namespace Ui
#include <AssetEditor/moc_AssetEditorHeader.cpp>
@@ -0,0 +1,51 @@
/*
* 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 <AzQtComponents/AzQtComponentsAPI.h>
#include <AzQtComponents/Components/Widgets/ElidingLabel.h>
#include <QWidget>
#include <QTimer>
#include <QIcon>
AZ_PUSH_DISABLE_WARNING(4244 4251, "-Wunknown-warning-option") // 4244: conversion from 'int' to 'qint8', possible loss of data
// 4251: 'QLayoutItem::align': class 'QFlags<Qt::AlignmentFlag>' needs to have dll-interface to be used by clients of class 'QLayoutItem'
#include <QHBoxLayout>
AZ_POP_DISABLE_WARNING
namespace Ui
{
class AssetEditorHeader
: public QFrame
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(AssetEditorHeader, AZ::SystemAllocator, 0);
explicit AssetEditorHeader(QWidget* parent = nullptr);
void setName(const QString& name);
void setLocation(const QString& location);
void setIcon(const QIcon& icon);
private:
QFrame* m_backgroundFrame;
QHBoxLayout* m_backgroundLayout;
QVBoxLayout* m_mainLayout;
QLabel* m_iconLabel;
QIcon m_icon;
AzQtComponents::ElidingLabel* m_assetName;
AzQtComponents::ElidingLabel* m_assetLocation;
};
} // namespace Ui
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AssetEditorStatusBar</class>
<widget class="QWidget" name="AssetEditorStatusBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>288</width>
<height>32</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QLabel" name="textEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>20</height>
</size>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="Resources/AssetEditorResources.qrc"/>
</resources>
<connections/>
</ui>
@@ -0,0 +1,136 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AssetEditorToolbar</class>
<widget class="QWidget" name="AssetEditorToolbar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>288</width>
<height>32</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QToolButton" name="newButton">
<property name="toolTip">
<string>Creates a New Asset</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="Resources/AssetEditorResources.qrc">
<normaloff>:/AssetEditorResources/New_Icon.png</normaloff>:/AssetEditorResources/New_Icon.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="openButton">
<property name="toolTip">
<string>Opens an Asset</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="Resources/AssetEditorResources.qrc">
<normaloff>:/AssetEditorResources/Load_Icon.png</normaloff>:/AssetEditorResources/Load_Icon.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="saveButton">
<property name="toolTip">
<string>Saves the Current Asset</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="Resources/AssetEditorResources.qrc">
<normaloff>:/AssetEditorResources/Save_Icon.png</normaloff>:/AssetEditorResources/Save_Icon.png</iconset>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>5</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="expandAllButton">
<property name="toolTip">
<string>Expand All</string>
</property>
<property name="text">
<string>Expand All</string>
</property>
<property name="icon">
<iconset resource="Resources/AssetEditorResources.qrc">
<normaloff>:/AssetEditorResources/ExpandAll_Icon.png</normaloff>:/AssetEditorResources/ExpandAll_Icon.png
</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="collapseAllButton">
<property name="toolTip">
<string>Collapse All</string>
</property>
<property name="text">
<string>Collapse All</string>
</property>
<property name="icon">
<iconset resource="Resources/AssetEditorResources.qrc">
<normaloff>:/AssetEditorResources/CollapseAll_Icon.png</normaloff>:/AssetEditorResources/CollapseAll_Icon.png
</iconset>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="Resources/AssetEditorResources.qrc"/>
</resources>
<connections/>
</ui>
@@ -0,0 +1,36 @@
/*
* 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/Asset/AssetManager.h>
#include <AzToolsFramework/AssetEditor/AssetEditorBus.h>
namespace AzToolsFramework
{
inline void OpenGenericAssetEditor(const AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
AzToolsFramework::AssetEditor::AssetEditorRequestsBus::Broadcast(&AzToolsFramework::AssetEditor::AssetEditorRequests::OpenAssetEditor, asset);
}
inline bool OpenGenericAssetEditor(const AZ::Data::AssetType& assetType, const AZ::Data::AssetId& assetId)
{
AZ::Data::Asset<AZ::Data::AssetData> asset = AZ::Data::AssetManager::Instance().FindOrCreateAsset(assetId, assetType, AZ::Data::AssetLoadBehavior::Default);
if (asset)
{
OpenGenericAssetEditor(asset);
return true;
}
return false;
}
}
@@ -0,0 +1,991 @@
/*
* 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 "AssetEditorWidget.h"
#include <API/ToolsApplicationAPI.h>
#include <API/EditorAssetSystemAPI.h>
#include <AssetEditor/AssetEditorBus.h>
#include <AssetEditor/AssetEditorHeader.h>
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 'QLayoutItem::align': class 'QFlags<Qt::AlignmentFlag>' needs to have dll-interface to be used by clients of class 'QLayoutItem'
#include <AssetEditor/ui_AssetEditorToolbar.h>
AZ_POP_DISABLE_WARNING
#include <AssetEditor/ui_AssetEditorStatusBar.h>
#include <AssetBrowser/AssetSelectionModel.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetManagerBus.h>
#include <AzCore/Asset/AssetTypeInfoBus.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/UserSettings/UserSettings.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/std/sort.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/Asset/GenericAssetHandler.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <SourceControl/SourceControlAPI.h>
#include <UI/PropertyEditor/PropertyRowWidget.hxx>
#include <UI/PropertyEditor/ReflectedPropertyEditor.hxx>
#include <QVBoxLayout>
#include <QMessageBox>
#include <QMenu>
#include <QMenuBar>
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 'QFileInfo::d_ptr': class 'QSharedDataPointer<QFileInfoPrivate>' needs to have dll-interface to be used by clients of class 'QFileInfo'
#include <QFileDialog>
AZ_POP_DISABLE_WARNING
#include <QAction>
namespace AzToolsFramework
{
namespace AssetEditor
{
using AssetCheckoutCallback = AZStd::function<void(bool, const AZStd::string&, const AZStd::string&)>;
void AssetCheckoutCommon(const AZ::Data::AssetId& id, AZ::Data::Asset<AZ::Data::AssetData> asset, AZ::SerializeContext* serializeContext, AssetCheckoutCallback assetCheckoutAndSaveCallback)
{
AZStd::string assetPath;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetPath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, id);
if (assetPath.empty())
{
AZ_Assert(!assetPath.empty(), "A valid path needs to be resolved, if this failed, the provided asset path is not a valid asset.");
return;
}
AZStd::string assetFullPath;
AssetSystemRequestBus::Broadcast(&AssetSystem::AssetSystemRequest::GetFullSourcePathFromRelativeProductPath, assetPath, assetFullPath);
if (!assetFullPath.empty())
{
using SCCommandBus = SourceControlCommandBus;
SCCommandBus::Broadcast(&SCCommandBus::Events::RequestEdit, assetFullPath.c_str(), true,
[id, asset, assetFullPath, serializeContext, assetCheckoutAndSaveCallback](bool /*success*/, const SourceControlFileInfo& info)
{
if (!info.IsReadOnly())
{
AZ::Outcome<bool, AZStd::string> outcome = AZ::Success(true);
AzToolsFramework::AssetEditor::AssetEditorValidationRequestBus::EventResult(outcome, id, &AzToolsFramework::AssetEditor::AssetEditorValidationRequests::IsAssetDataValid, asset);
if (!outcome.IsSuccess())
{
assetCheckoutAndSaveCallback(false, outcome.GetError(), assetFullPath);
}
else
{
AssetEditorValidationRequestBus::Event(id, &AssetEditorValidationRequests::PreAssetSave, asset);
assetCheckoutAndSaveCallback(true, "", assetFullPath);
}
}
else
{
AZStd::string error = AZStd::string::format("Could not check out asset file: %s.", assetFullPath.c_str());
assetCheckoutAndSaveCallback(false, error, assetFullPath);
}
}
);
}
else
{
AZStd::string error = AZStd::string::format("Could not resolve path name for asset {%s}.", id.ToString<AZStd::string>().c_str());
assetCheckoutAndSaveCallback(false, error, nullptr);
}
}
namespace Status
{
static QString assetCreated = QStringLiteral("Asset created!");
static QString assetSaved = QStringLiteral("Asset saved!");
static QString assetSaving = QStringLiteral("Asset saving!");
static QString assetLoaded = QStringLiteral("Asset loaded!");
static QString unableToSave = QStringLiteral("Failed to save asset due to validation error, check the log!");
static QString emptyString = QStringLiteral("");
}
//////////////////////////////////
// AssetEditorWidgetUserSettings
//////////////////////////////////
void AssetEditorWidgetUserSettings::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<AssetEditorWidgetUserSettings>()
->Version(0)
->Field("m_showPreviewMessage", &AssetEditorWidgetUserSettings::m_lastSavePath)
->Field("m_snapDistance", &AssetEditorWidgetUserSettings::m_recentPaths)
;
}
}
AssetEditorWidgetUserSettings::AssetEditorWidgetUserSettings()
{
char assetRoot[AZ_MAX_PATH_LEN] = { 0 };
AZ::IO::FileIOBase::GetInstance()->ResolvePath("@devassets@", assetRoot, AZ_MAX_PATH_LEN);
m_lastSavePath = assetRoot;
}
void AssetEditorWidgetUserSettings::AddRecentPath(const AZStd::string& recentPath)
{
AZStd::string lowerCasePath = recentPath;
AZStd::to_lower(lowerCasePath.begin(), lowerCasePath.end());
auto item = AZStd::find(m_recentPaths.begin(), m_recentPaths.end(), lowerCasePath);
if (item != m_recentPaths.end())
{
m_recentPaths.erase(item);
}
m_recentPaths.emplace(m_recentPaths.begin(), lowerCasePath);
while (m_recentPaths.size() > 10)
{
m_recentPaths.pop_back();
}
}
//////////////////////////////////////////////////////////////////////////
// AssetEditorWidget
//////////////////////////////////////////////////////////////////////////
const AZ::Crc32 k_assetEditorWidgetSettings = AZ_CRC("AssetEditorSettings", 0xfe740dee);
AssetEditorWidget::AssetEditorWidget(QWidget* parent)
: QWidget(parent)
, m_statusBar(new Ui::AssetEditorStatusBar())
, m_dirty(true)
{
AZ::ComponentApplicationBus::BroadcastResult(m_serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
AZ_Assert(m_serializeContext, "Failed to retrieve serialize context.");
setObjectName("AssetEditorWidget");
m_propertyEditor = new ReflectedPropertyEditor(this);
m_propertyEditor->setObjectName("AssetEditorWidgetPropertyEditor");
m_propertyEditor->Setup(m_serializeContext, this, true, 250);
m_propertyEditor->show();
QVBoxLayout* mainLayout = new QVBoxLayout();
mainLayout->setContentsMargins(0, 0, 0, 0);
mainLayout->setSpacing(0);
m_header = new Ui::AssetEditorHeader(this);
mainLayout->addWidget(m_header);
m_header->show();
m_propertyEditor->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
mainLayout->addWidget(m_propertyEditor);
QWidget* statusBarWidget = new QWidget(this);
statusBarWidget->setObjectName("AssetEditorStatusBar");
m_statusBar->setupUi(statusBarWidget);
m_statusBar->textEdit->setText(Status::emptyString);
m_statusBar->textEdit->setTextInteractionFlags(Qt::TextSelectableByMouse);
mainLayout->addWidget(statusBarWidget);
PopulateGenericAssetTypes();
QMenuBar* mainMenu = new QMenuBar();
QMenu* fileMenu = mainMenu->addMenu(tr("&File"));
// Add Create New Asset menu and populate it with all asset types that have GenericAssetHandler
QMenu* newAssetMenu = fileMenu->addMenu(tr("&New"));
for (const auto& assetType : m_genericAssetTypes)
{
QString assetTypeName;
AZ::AssetTypeInfoBus::EventResult(assetTypeName, assetType, &AZ::AssetTypeInfo::GetAssetTypeDisplayName);
if (!assetTypeName.isEmpty())
{
QAction* newAssetAction = newAssetMenu->addAction(assetTypeName);
connect(newAssetAction, &QAction::triggered, this, [assetType, this]() { CreateAssetImpl(assetType); });
}
}
QAction* openAssetAction = fileMenu->addAction("&Open");
connect(openAssetAction, &QAction::triggered, this, &AssetEditorWidget::OpenAssetWithDialog);
m_recentFileMenu = fileMenu->addMenu("Open Recent");
m_saveAssetAction = fileMenu->addAction("&Save");
m_saveAssetAction->setShortcut(QKeySequence::Save);
connect(m_saveAssetAction, &QAction::triggered, this, &AssetEditorWidget::SaveAsset);
m_saveAsAssetAction = fileMenu->addAction("&Save As");
m_saveAsAssetAction->setShortcut(QKeySequence::SaveAs);
connect(m_saveAsAssetAction, &QAction::triggered, this, &AssetEditorWidget::SaveAssetAs);
// "Save" and "Save As..." actions are disabled by default,
// and they are activated when an asset is created/open
m_saveAssetAction->setEnabled(false);
m_saveAsAssetAction->setEnabled(false);
QMenu* viewMenu = mainMenu->addMenu(tr("&View"));
QAction* expandAll = viewMenu->addAction("&Expand All");
connect(expandAll, &QAction::triggered, this, &AssetEditorWidget::ExpandAll);
QAction* collapseAll = viewMenu->addAction("&Collapse All");
connect(collapseAll, &QAction::triggered, this, &AssetEditorWidget::CollapseAll);
mainLayout->setMenuBar(mainMenu);
setLayout(mainLayout);
AzFramework::AssetCatalogEventBus::Handler::BusConnect();
m_userSettings = AZ::UserSettings::CreateFind<AssetEditorWidgetUserSettings>(k_assetEditorWidgetSettings, AZ::UserSettings::CT_LOCAL);
QObject::connect(m_recentFileMenu, &QMenu::aboutToShow, this, &AssetEditorWidget::PopulateRecentMenu);
}
AssetEditorWidget::~AssetEditorWidget()
{
AZ::SystemTickBus::Handler::BusDisconnect();
}
void AssetEditorWidget::CreateAsset(AZ::Data::AssetType assetType)
{
auto typeIter = AZStd::find_if(m_genericAssetTypes.begin(), m_genericAssetTypes.end(), [assetType](const AZ::Data::AssetType& testType) { return assetType == testType; });
if (typeIter != m_genericAssetTypes.end())
{
CreateAssetImpl(assetType);
}
else
{
AZ_Assert(false, "The AssetEditorWidget only supports Generic Asset Types, make sure your type has a handler that implements GenericAssetHandler");
}
}
void AssetEditorWidget::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
m_dirty = false;
AZ::Data::AssetBus::Handler::BusDisconnect(asset.GetId());
// Clone the asset
AZ::Data::AssetId newAssetId = AZ::Data::AssetId(AZ::Uuid::CreateRandom());
m_inMemoryAsset = AZ::Data::AssetManager::Instance().CreateAsset(newAssetId, asset.GetType(), m_inMemoryAsset.GetAutoLoadBehavior());
auto serializeContext = AZ::EntityUtils::GetApplicationSerializeContext();
serializeContext->CloneObjectInplace((*m_inMemoryAsset.GetData()), asset.GetData());
UpdatePropertyEditor(m_inMemoryAsset);
SetupHeader();
if (!m_sourceAssetId.IsValid())
{
SetStatusText(Status::assetCreated);
}
else
{
SetStatusText(Status::assetLoaded);
}
UpdateMenusOnAssetOpen();
}
void AssetEditorWidget::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
// Keep a reference to the reloaded asset data
OnAssetReady(asset);
}
void AssetEditorWidget::UpdatePropertyEditor(AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
m_propertyEditor->ClearInstances();
AZ::Crc32 saveStateKey;
saveStateKey.Add(&asset.GetId(), sizeof(AZ::Data::AssetId));
m_propertyEditor->SetSavedStateKey(saveStateKey);
m_propertyEditor->AddInstance(asset.Get(), asset.GetType(), nullptr);
m_propertyEditor->InvalidateAll();
m_propertyEditor->setEnabled(true);
}
void AssetEditorWidget::OnAssetError(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
m_dirty = false;
m_saveAssetAction->setEnabled(false);
m_propertyEditor->ClearInstances();
if (AZ::Data::AssetBus::Handler::BusIsConnectedId(asset.GetId()))
{
AZ::Data::AssetBus::Handler::BusDisconnect(asset.GetId());
}
QString errString = tr("Failed to load %1!").arg(asset.GetHint().c_str());
AZ_Error("Asset Editor", false, errString.toUtf8());
QMessageBox::warning(this, tr("Error!"), errString);
}
bool AssetEditorWidget::TrySave(const AZStd::function<void()>& savedCallback)
{
if (!m_sourceAssetId.IsValid() || !m_dirty)
{
return false;
}
const int result = QMessageBox::question(this,
tr("Save Changes?"),
tr("Changes have been made to the asset during this session. Would you like to save prior to closing?"),
QMessageBox::Yes, QMessageBox::No, QMessageBox::Cancel);
if (result == QMessageBox::No)
{
return false;
}
if (result == QMessageBox::Yes)
{
if (savedCallback)
{
auto conn = AZStd::make_shared<QMetaObject::Connection>();
*conn = connect(this, &AssetEditorWidget::OnAssetSavedSignal, this, [this, conn, savedCallback]()
{
disconnect(*conn);
savedCallback();
});
}
SaveAsset();
}
return true;
}
bool AssetEditorWidget::WaitingToSave() const
{
return m_waitingToSave;
}
void AssetEditorWidget::SetCloseAfterSave()
{
m_closeAfterSave = true;
}
bool AssetEditorWidget::SaveAsDialog(AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
AZStd::vector<AZStd::string> assetTypeExtensions;
AZ::AssetTypeInfoBus::Event(asset.GetType(), &AZ::AssetTypeInfo::GetAssetTypeExtensions, assetTypeExtensions);
QString filter;
if (assetTypeExtensions.empty())
{
filter = tr("All Files (*.*)");
}
else
{
QString assetTypeName;
AZ::AssetTypeInfoBus::EventResult(assetTypeName, asset.GetType(), &AZ::AssetTypeInfo::GetAssetTypeDisplayName);
filter.append(assetTypeName);
filter.append(" (");
for (size_t i = 0, n = assetTypeExtensions.size(); i < n; ++i)
{
const char* ext = assetTypeExtensions[i].c_str();
if (ext[0] == '.')
{
++ext;
}
filter.append("*.");
filter.append(ext);
if (i < n - 1)
{
filter.append(", ");
}
}
filter.append(")");
}
const QString saveAs = QFileDialog::getSaveFileName(nullptr, tr("Save As..."), m_userSettings->m_lastSavePath.c_str(), filter);
return SaveImpl(asset, saveAs);
}
bool AssetEditorWidget::SaveAssetToPath(AZStd::string_view assetPath)
{
return SaveImpl(m_inMemoryAsset, assetPath.data());
}
bool AssetEditorWidget::SaveImpl(const AZ::Data::Asset<AZ::Data::AssetData>& asset, const QString& saveAsPath)
{
if (!saveAsPath.isEmpty())
{
AZStd::string targetFilePath(saveAsPath.toUtf8().constData());
m_propertyEditor->ForceQueuedInvalidation();
AZStd::vector<char> byteBuffer;
AZ::IO::ByteContainerStream<decltype(byteBuffer)> byteStream(&byteBuffer);
auto assetHandler = const_cast<AZ::Data::AssetHandler*>(AZ::Data::AssetManager::Instance().GetHandler(asset.GetType()));
if (assetHandler->SaveAssetData(asset, &byteStream))
{
AZ::IO::FileIOStream fileStream(targetFilePath.c_str(), AZ::IO::OpenMode::ModeWrite);
if (!fileStream.IsOpen())
{
AZ_Warning("Asset Editor Widget", false, "Could not open file for writing - invalid path (%s).", targetFilePath.c_str());
return false;
}
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePathKeepCase, targetFilePath);
m_expectedAddedAssetPath = targetFilePath;
SourceControlCommandBus::Broadcast(&SourceControlCommandBus::Events::RequestEdit, targetFilePath.c_str(), true, [](bool, const SourceControlFileInfo&) {});
fileStream.Write(byteBuffer.size(), byteBuffer.data());
AZStd::string watchFolder;
AZ::Data::AssetInfo assetInfo;
bool sourceInfoFound{};
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(sourceInfoFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, targetFilePath.c_str(), assetInfo, watchFolder);
if (sourceInfoFound)
{
m_sourceAssetId = assetInfo.m_assetId;
}
AZStd::string fileName = targetFilePath;
if (AzFramework::StringFunc::Path::Normalize(fileName))
{
AzFramework::StringFunc::Path::StripPath(fileName);
m_currentAsset = fileName.c_str();
m_userSettings->m_lastSavePath = targetFilePath;
AZStd::size_t findIndex = m_userSettings->m_lastSavePath.find(fileName.c_str());
if (findIndex != AZStd::string::npos)
{
m_userSettings->m_lastSavePath = m_userSettings->m_lastSavePath.substr(0, findIndex);
}
}
AddRecentPath(targetFilePath);
SetStatusText(Status::assetCreated);
return true;
}
else
{
SetStatusText(Status::unableToSave);
return false;
}
}
return false;
}
void AssetEditorWidget::OpenAsset(const AZ::Data::Asset<AZ::Data::AssetData> asset)
{
// If we're already editing this asset, don't do anything.
if (m_sourceAssetId == asset.GetId())
{
return;
}
// If an unsaved asset is open, ask.
if (TrySave([this]() { OpenAssetWithDialog(); }))
{
return;
}
AZ::Data::AssetInfo typeInfo;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(typeInfo, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetInfoById, asset.GetId());
// Check if there is an asset to open
if (typeInfo.m_relativePath.empty())
{
return;
}
bool hasResult = false;
AZStd::string fullPath;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(hasResult, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetFullSourcePathFromRelativeProductPath, typeInfo.m_relativePath, fullPath);
AZStd::string fileName = typeInfo.m_relativePath;
if (AzFramework::StringFunc::Path::Normalize(fileName))
{
AzFramework::StringFunc::Path::StripPath(fileName);
m_currentAsset = fileName.c_str();
}
AddRecentPath(fullPath.c_str());
m_sourceAssetId = asset.GetId();
LoadAsset(asset.GetId(), asset.GetType());
}
void AssetEditorWidget::OpenAssetWithDialog()
{
// If an unsaved asset is open, ask.
if (TrySave([this]() { OpenAssetWithDialog(); }))
{
return;
}
AssetSelectionModel selection = AssetSelectionModel::AssetTypesSelection(m_genericAssetTypes);
EditorRequests::Bus::Broadcast(&EditorRequests::BrowseForAssets, selection);
if (selection.IsValid())
{
auto product = azrtti_cast<const ProductAssetBrowserEntry*>(selection.GetResult());
m_currentAsset = product->GetName().c_str();
AddRecentPath(product->GetFullPath());
LoadAsset(product->GetAssetId(), product->GetAssetType());
m_sourceAssetId = product->GetAssetId();
}
}
void AssetEditorWidget::OpenAssetFromPath(const AZStd::string& assetPath)
{
bool hasResult = false;
AZ::Data::AssetInfo assetInfo;
AZStd::string watchFolder;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(hasResult, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetSourceInfoBySourcePath, assetPath.c_str(), assetInfo, watchFolder);
if (hasResult)
{
AZStd::string fileName = assetPath;
if (AzFramework::StringFunc::Path::Normalize(fileName))
{
AzFramework::StringFunc::Path::StripPath(fileName);
m_currentAsset = fileName.c_str();
}
AZ::Data::AssetInfo typeInfo;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(typeInfo, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetInfoById, assetInfo.m_assetId);
LoadAsset(assetInfo.m_assetId, typeInfo.m_assetType);
m_sourceAssetId = assetInfo.m_assetId;
}
}
void AssetEditorWidget::LoadAsset(AZ::Data::AssetId assetId, AZ::Data::AssetType assetType)
{
auto asset = AZ::Data::AssetManager::Instance().GetAsset(assetId, assetType, AZ::Data::AssetLoadBehavior::Default);
if (asset.IsReady())
{
OnAssetReady(asset);
}
else
{
if (m_inMemoryAsset)
{
AZ::Data::AssetBus::Handler::BusDisconnect(m_inMemoryAsset.GetId());
}
AZ::Data::AssetBus::Handler::BusConnect(asset.GetId());
// Need to disable editing until OnAssetReady.
m_propertyEditor->setEnabled(false);
}
}
void AssetEditorWidget::GenerateSaveDataSnapshot()
{
//Generate data now so that if there's a wait for the source control system, the data saved will be as it was when the save was called.
AZStd::vector<AZ::u8> newSaveData;
// Make a stream and save a snapshot of the asset to it.
AZ::IO::ByteContainerStream<AZStd::vector<AZ::u8> > dstByteStream(&newSaveData);
AssetEditorValidationRequestBus::Event(m_sourceAssetId, &AssetEditorValidationRequests::PreAssetSave, m_inMemoryAsset);
if (AZ::Utils::SaveObjectToStream(dstByteStream, AZ::DataStream::ST_XML, m_inMemoryAsset.Get(), m_inMemoryAsset.Get()->RTTI_GetType(), m_serializeContext))
{
AZStd::swap(newSaveData, m_saveData);
}
}
void AssetEditorWidget::SaveAsset()
{
if (!m_sourceAssetId.IsValid())
{
SaveAsDialog(m_inMemoryAsset);
}
else if (m_dirty)
{
SetStatusText(Status::assetSaving);
GenerateSaveDataSnapshot();
// Clear the dirty flag now so that attempting to close the window during a save doesn't ask the user to save again,
// unless there are further changes.
m_dirty = false;
m_saveAssetAction->setEnabled(false);
m_saveAsAssetAction->setEnabled(false);
if (WaitingToSave())
{
// Don't need to do the save, as we've just overwritten the data that the previously queued save will write out.
return;
}
m_waitingToSave = true;
AssetCheckoutCommon(m_sourceAssetId, m_inMemoryAsset, m_serializeContext,
[this](bool checkoutSuccess, const AZStd::string& error, const AZStd::string& assetFullPath)
{
AZStd::string saveError = error;
bool saveSuccessful = false;
if (checkoutSuccess)
{
saveError = AZStd::string::format("Could not write asset file: %s.", assetFullPath.c_str());
if (!m_saveData.empty())
{
if (AZ::Utils::SaveStreamToFile(assetFullPath, m_saveData))
{
m_saveData.clear();
saveSuccessful = true;
}
}
}
if (saveSuccessful)
{
Q_EMIT OnAssetSavedSignal();
m_propertyEditor->QueueInvalidation(Refresh_AttributesAndValues);
SetStatusText(Status::assetSaved);
if (m_closeAfterSave)
{
m_closeAfterSave = false;
parentWidget()->parentWidget()->close();
}
}
else
{
// Don't want to dirty the asset here, since we are setting ourselves into a weird state since we were unable to save.
m_dirty = true;
m_saveAssetAction->setEnabled(false);
m_saveAsAssetAction->setEnabled(false);
SetStatusText(Status::unableToSave);
Q_EMIT OnAssetSaveFailedSignal(saveError);
}
m_waitingToSave = false;
}
);
}
}
void AssetEditorWidget::SaveAssetAs()
{
SaveAsDialog(m_inMemoryAsset);
}
void AssetEditorWidget::OnNewAsset()
{
// This only works if we've already made a new asset or opened an asset of a given type
// We use that knowledge to create another asset of the same type.
if (!m_inMemoryAsset.GetType().IsNull())
{
CreateAssetImpl(m_inMemoryAsset.GetType());
}
}
void AssetEditorWidget::OnCatalogAssetAdded(const AZ::Data::AssetId& assetId)
{
AZ::Data::AssetInfo assetInfo;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, assetId);
if (assetInfo.m_assetType == m_inMemoryAsset.GetType()
&& strstr(m_expectedAddedAssetPath.c_str(), assetInfo.m_relativePath.c_str()) != 0)
{
m_expectedAddedAssetPath.clear();
m_recentlyAddedAssetPath = assetInfo.m_relativePath;
LoadAsset(assetId, assetInfo.m_assetType);
m_sourceAssetId = assetId;
}
}
void AssetEditorWidget::OnCatalogAssetRemoved(const AZ::Data::AssetId& /*assetId*/, const AZ::Data::AssetInfo& assetInfo)
{
if (assetInfo.m_assetType == m_inMemoryAsset.GetType()
&& assetInfo.m_relativePath.compare(m_recentlyAddedAssetPath) == 0)
{
m_recentlyAddedAssetPath.clear();
// The file was removed due to errors, but the user needs to be
// given a chance to fix the errors and try again.
m_sourceAssetId.SetInvalid();
m_currentAsset = "New Asset";
m_propertyEditor->setEnabled(true);
DirtyAsset();
}
}
void AssetEditorWidget::PopulateGenericAssetTypes()
{
auto enumerateCallback =
[this](const AZ::SerializeContext::ClassData* classData, const AZ::Uuid& /*typeId*/) -> bool
{
AZ::Data::AssetType assetType = classData->m_typeId;
// make sure this is not base class
if (assetType == AZ::AzTypeInfo<AZ::Data::AssetData>::Uuid()
|| AZ::FindAttribute(AZ::Edit::Attributes::EnableForAssetEditor, classData->m_attributes) == nullptr)
{
return true;
}
// narrow down all reflected AssetTypeInfos to those that have a GenericAssetHandler
AZ::Data::AssetManager& manager = AZ::Data::AssetManager::Instance();
if (const AZ::Data::AssetHandler* assetHandler = manager.GetHandler(classData->m_typeId))
{
if (!azrtti_istypeof<AzFramework::GenericAssetHandlerBase>(assetHandler))
{
// its not the generic asset handler.
return true;
}
}
m_genericAssetTypes.push_back(assetType);
return true;
};
m_serializeContext->EnumerateDerived(
enumerateCallback,
AZ::Uuid::CreateNull(),
AZ::AzTypeInfo<AZ::Data::AssetData>::Uuid()
);
AZStd::sort(m_genericAssetTypes.begin(), m_genericAssetTypes.end(), [&](const AZ::Data::AssetType& lhsAssetType, const AZ::Data::AssetType& rhsAssetType)
{
AZStd::string lhsAssetTypeName = "";
AZ::AssetTypeInfoBus::EventResult(lhsAssetTypeName, lhsAssetType, &AZ::AssetTypeInfo::GetAssetTypeDisplayName);
AZStd::string rhsAssetTypeName = "";
AZ::AssetTypeInfoBus::EventResult(rhsAssetTypeName, rhsAssetType, &AZ::AssetTypeInfo::GetAssetTypeDisplayName);
return lhsAssetTypeName < rhsAssetTypeName;
});
}
void AssetEditorWidget::CreateAssetImpl(AZ::Data::AssetType assetType)
{
// If an unsaved asset is open, ask.
if (TrySave([this, assetType]() { CreateAssetImpl(assetType); }))
{
return;
}
if (m_inMemoryAsset)
{
AZ::Data::AssetBus::Handler::BusDisconnect(m_inMemoryAsset.GetId());
m_inMemoryAsset.Release();
}
m_sourceAssetId.SetInvalid();
AZ::Data::AssetId newAssetId = AZ::Data::AssetId(AZ::Uuid::CreateRandom());
m_inMemoryAsset = AZ::Data::AssetManager::Instance().CreateAsset(newAssetId, assetType, AZ::Data::AssetLoadBehavior::Default);
DirtyAsset();
m_propertyEditor->ClearInstances();
m_currentAsset = "New Asset";
UpdatePropertyEditor(m_inMemoryAsset);
ExpandAll();
SetStatusText(Status::assetCreated);
SetupHeader();
}
void AssetEditorWidget::AfterPropertyModified(InstanceDataNode* /*node*/)
{
DirtyAsset();
}
void AssetEditorWidget::RequestPropertyContextMenu(InstanceDataNode* node, const QPoint& point)
{
if (node && node->IsDifferentVersusComparison())
{
QMenu menu;
QAction* resetAction = menu.addAction(tr("Reset value"));
connect(resetAction, &QAction::triggered, this,
[this, node]()
{
const InstanceDataNode* orig = node->GetComparisonNode();
if (orig)
{
InstanceDataHierarchy::CopyInstanceData(orig, node, m_serializeContext);
PropertyRowWidget* widget = m_propertyEditor->GetWidgetFromNode(node);
if (widget)
{
widget->DoPropertyNotify();
}
m_propertyEditor->QueueInvalidation(Refresh_Values);
}
}
);
menu.exec(point);
}
}
void AssetEditorWidget::BeforePropertyModified(InstanceDataNode* node)
{
AssetEditorValidationRequestBus::Event(m_inMemoryAsset.Get()->GetId(), &AssetEditorValidationRequests::BeforePropertyEdit, node, m_inMemoryAsset);
}
void AssetEditorWidget::ExpandAll()
{
m_propertyEditor->ExpandAll();
}
void AssetEditorWidget::CollapseAll()
{
m_propertyEditor->CollapseAll();
}
void AssetEditorWidget::DirtyAsset()
{
m_dirty = true;
m_saveAssetAction->setEnabled(true);
m_saveAsAssetAction->setEnabled(true);
SetStatusText(Status::emptyString);
}
void AssetEditorWidget::OnSystemTick()
{
if (!m_queuedAssetStatus.isEmpty())
{
ApplyStatusText();
}
}
void AssetEditorWidget::ApplyStatusText()
{
QString statusString;
if (m_dirty)
{
statusString = QString("%1*");
}
else
{
statusString = QString("%1");
}
statusString = statusString.arg(m_currentAsset).arg(m_queuedAssetStatus);
if (!m_queuedAssetStatus.isEmpty())
{
statusString.append(" - ");
statusString.append(m_queuedAssetStatus);
}
m_statusBar->textEdit->setText(statusString);
m_queuedAssetStatus.clear();
AZ::SystemTickBus::Handler::BusDisconnect();
}
void AssetEditorWidget::SetupHeader()
{
QString nameString = QString("%1").arg(m_currentAsset).arg(m_queuedAssetStatus);
m_header->setName(nameString);
AZStd::string assetPath;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetPath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, m_sourceAssetId);
// Add the asset location to the right of the header. Location is relative to the Project directory.
QString pathAndAsset = assetPath.c_str();
int seperatorPos = pathAndAsset.lastIndexOf('/');
if (seperatorPos < 0)
{
m_header->setLocation("");
}
else
{
m_header->setLocation(pathAndAsset.left(seperatorPos));
}
m_header->setIcon(QIcon(QStringLiteral(":/AssetEditor/default_document.svg")));
m_header->show();
}
void AssetEditorWidget::SetStatusText(const QString& assetStatus)
{
m_queuedAssetStatus = assetStatus;
AZ::SystemTickBus::Handler::BusConnect();
}
void AssetEditorWidget::AddRecentPath(const AZStd::string& recentPath)
{
m_userSettings->AddRecentPath(recentPath);
}
void AssetEditorWidget::PopulateRecentMenu()
{
m_recentFileMenu->clear();
if (m_userSettings->m_recentPaths.empty())
{
m_recentFileMenu->setEnabled(false);
}
else
{
m_recentFileMenu->setEnabled(true);
for (const AZStd::string& recentFile : m_userSettings->m_recentPaths)
{
bool hasResult = false;
AZStd::string relativePath;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(hasResult, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetRelativeProductPathFromFullSourceOrProductPath, recentFile, relativePath);
if (hasResult)
{
QAction* action = m_recentFileMenu->addAction(relativePath.c_str());
connect(action, &QAction::triggered, this, [recentFile, this]() { this->OpenAssetFromPath(recentFile); });
}
}
}
}
void AssetEditorWidget::UpdateMenusOnAssetOpen()
{
// Activate "Save" and "Save As..." actions
m_saveAssetAction->setEnabled(true);
m_saveAsAssetAction->setEnabled(true);
}
} // namespace AssetEditor
} // namespace AzToolsFramework
#include "AssetEditor/moc_AssetEditorWidget.cpp"
@@ -0,0 +1,182 @@
/*
* 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/Asset/AssetCommon.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/UserSettings/UserSettings.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI_Internals.h>
#include <QWidget>
#endif
namespace AZ
{
class SerializeContext;
}
namespace Ui
{
class AssetEditorToolbar;
class AssetEditorStatusBar;
class AssetEditorHeader;
}
class QMenu;
namespace AzToolsFramework
{
class ReflectedPropertyEditor;
namespace AssetEditor
{
class AssetEditorWidgetUserSettings
: public AZ::UserSettings
{
public:
AZ_RTTI(AssetEditorWidgetUserSettings, "{382FE424-4541-4D93-9BA4-DE17A6DF8676}", AZ::UserSettings);
AZ_CLASS_ALLOCATOR(AssetEditorWidgetUserSettings, AZ::SystemAllocator, 0);
static void Reflect(AZ::ReflectContext* context);
AssetEditorWidgetUserSettings();
~AssetEditorWidgetUserSettings() override = default;
void AddRecentPath(const AZStd::string& recentPath);
AZStd::string m_lastSavePath;
AZStd::vector< AZStd::string > m_recentPaths;
};
/**
* Provides ability to create, edit, and save reflected assets.
*/
class AssetEditorWidget
: public QWidget
, private AZ::Data::AssetBus::Handler
, private AzFramework::AssetCatalogEventBus::Handler
, private AzToolsFramework::IPropertyEditorNotify
, private AZ::SystemTickBus::Handler
{
Q_OBJECT
public:
AZ_CLASS_ALLOCATOR(AssetEditorWidget, AZ::SystemAllocator, 0);
explicit AssetEditorWidget(QWidget* parent = nullptr);
~AssetEditorWidget() override;
void CreateAsset(AZ::Data::AssetType assetType);
void OpenAsset(const AZ::Data::Asset<AZ::Data::AssetData> asset);
void SetAsset(const AZ::Data::Asset<AZ::Data::AssetData> asset);
void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetError(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
bool IsDirty() const { return m_dirty; }
bool TrySave(const AZStd::function<void()>& savedCallback);
bool WaitingToSave() const;
void SetCloseAfterSave();
public Q_SLOTS:
void OpenAssetWithDialog();
void OpenAssetFromPath(const AZStd::string& fullPath);
void SaveAsset();
void SaveAssetAs();
bool SaveAssetToPath(AZStd::string_view assetPath);
void ExpandAll();
void CollapseAll();
void OnNewAsset();
Q_SIGNALS:
void OnAssetSavedSignal();
void OnAssetSaveFailedSignal(const AZStd::string& error);
void OnAssetRevertedSignal();
void OnAssetRevertFailedSignal(const AZStd::string& error);
void OnAssetOpenedSignal(const AZ::Data::Asset<AZ::Data::AssetData>& asset);
protected: // IPropertyEditorNotify
void AfterPropertyModified(InstanceDataNode* /*node*/) override;
void RequestPropertyContextMenu(InstanceDataNode*, const QPoint&) override;
void BeforePropertyModified(InstanceDataNode* node) override;
void SetPropertyEditingActive(InstanceDataNode* /*node*/) override {}
void SetPropertyEditingComplete(InstanceDataNode* /*node*/) override {}
void SealUndoStack() override {};
void OnCatalogAssetAdded(const AZ::Data::AssetId& assetId) override;
void OnCatalogAssetRemoved(const AZ::Data::AssetId& assetId, const AZ::Data::AssetInfo& assetInfo) override;
private:
void DirtyAsset();
void SetStatusText(const QString& assetStatus);
void ApplyStatusText();
void SetupHeader();
QString m_queuedAssetStatus;
void AddRecentPath(const AZStd::string& recentPath);
void PopulateRecentMenu();
void UpdateMenusOnAssetOpen();
// AZ::SystemTickBus
void OnSystemTick() override;
AZStd::vector<AZ::Data::AssetType> m_genericAssetTypes;
AZ::Data::AssetId m_sourceAssetId;
AZ::Data::Asset<AZ::Data::AssetData> m_inMemoryAsset;
Ui::AssetEditorHeader* m_header;
ReflectedPropertyEditor* m_propertyEditor;
AZ::SerializeContext* m_serializeContext = nullptr;
// Ids can change when an asset goes from in-memory to saved on disk.
// If there is a failure, the asset will be removed from the catalog.
// The only reliable mechanism to be certain the asset being added/removed
// from the catalog is the same one that was added is to compare its file path.
AZStd::string m_expectedAddedAssetPath;
AZStd::string m_recentlyAddedAssetPath;
bool m_dirty = false;
QString m_currentAsset;
QAction* m_saveAssetAction;
QAction* m_saveAsAssetAction;
QMenu* m_recentFileMenu;
AZStd::vector<AZ::u8> m_saveData;
bool m_closeAfterSave = false;
bool m_waitingToSave = false;
AZStd::intrusive_ptr<AssetEditorWidgetUserSettings> m_userSettings;
AZStd::unique_ptr< Ui::AssetEditorStatusBar > m_statusBar;
void PopulateGenericAssetTypes();
void CreateAssetImpl(AZ::Data::AssetType assetType);
bool SaveAsDialog(AZ::Data::Asset<AZ::Data::AssetData>& asset);
bool SaveImpl(const AZ::Data::Asset<AZ::Data::AssetData>& asset, const QString& saveAsPath);
void LoadAsset(AZ::Data::AssetId assetId, AZ::Data::AssetType assetType);
void UpdatePropertyEditor(AZ::Data::Asset<AZ::Data::AssetData>& asset);
void GenerateSaveDataSnapshot();
};
} // namespace AssetEditor
} // namespace AzToolsFramework
@@ -0,0 +1,9 @@
<RCC>
<qresource prefix="/AssetEditorResources">
<file>Load_Icon.png</file>
<file>New_Icon.png</file>
<file>Save_Icon.png</file>
<file>ExpandAll_Icon.png</file>
<file>CollapseAll_Icon.png</file>
</qresource>
</RCC>
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:33dac83dde6c81abd43b0aa29503f44ac424c749b686dec1544a00c65c97a5fe
size 685
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:08c5a0d4e3f0ffa369ad0eef88aa56acbc260ce93ed45afba8b504db52ea8345
size 772
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:93b6823017adca5eda8d872239fe70adcc79e14b893365187755b1aa471f1f3e
size 322
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6361c60d60843a67e6baa491fc90c4c4c36bfc6d91aae05360ebd1924ccb9549
size 371
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:aeed1d5565ff0b260eee925507e035b594962ff104658287a5713a3fd6071d5c
size 302