Atom Tools: move boilerplate document management code to atom tools framework

• Moved all of the common save and load code to the document base class
• Moved undo and redo support to document base class but will probably extract to its own class or replace with one from AzTF
• Streamlined material editor, shader management console, and other tools with updated document code
• Cleaned up some of shader management console loading code, added support for saving, as well as getting and setting the shader variant list source data structure

Signed-off-by: Guthrie Adams <guthadam@amazon.com>
monroegm-disable-blank-issue-2
Guthrie Adams 4 years ago
parent e1c7dce7a7
commit dc598a8b3c

@ -7,8 +7,9 @@
*/ */
#pragma once #pragma once
#include <AzCore/RTTI/RTTI.h>
#include <AtomToolsFramework/Document/AtomToolsDocumentRequestBus.h> #include <AtomToolsFramework/Document/AtomToolsDocumentRequestBus.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
namespace AtomToolsFramework namespace AtomToolsFramework
{ {
@ -17,6 +18,7 @@ namespace AtomToolsFramework
*/ */
class AtomToolsDocument class AtomToolsDocument
: public AtomToolsDocumentRequestBus::Handler : public AtomToolsDocumentRequestBus::Handler
, private AzToolsFramework::AssetSystemBus::Handler
{ {
public: public:
AZ_RTTI(AtomToolsDocument, "{8992DF74-88EC-438C-B280-6E71D4C0880B}"); AZ_RTTI(AtomToolsDocument, "{8992DF74-88EC-438C-B280-6E71D4C0880B}");
@ -28,10 +30,8 @@ namespace AtomToolsFramework
const AZ::Uuid& GetId() const; const AZ::Uuid& GetId() const;
//////////////////////////////////////////////////////////////////////// // AtomToolsDocumentRequestBus::Handler overrides...
// AtomToolsDocumentRequestBus::Handler implementation
AZStd::string_view GetAbsolutePath() const override; AZStd::string_view GetAbsolutePath() const override;
AZStd::string_view GetRelativePath() const override;
const AZStd::any& GetPropertyValue(const AZ::Name& propertyId) const override; const AZStd::any& GetPropertyValue(const AZ::Name& propertyId) const override;
const AtomToolsFramework::DynamicProperty& GetProperty(const AZ::Name& propertyId) const override; const AtomToolsFramework::DynamicProperty& GetProperty(const AZ::Name& propertyId) const override;
bool IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const override; bool IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const override;
@ -51,21 +51,59 @@ namespace AtomToolsFramework
bool Redo() override; bool Redo() override;
bool BeginEdit() override; bool BeginEdit() override;
bool EndEdit() override; bool EndEdit() override;
////////////////////////////////////////////////////////////////////////
protected: protected:
virtual void Clear();
virtual bool OpenSucceeded();
virtual bool OpenFailed();
virtual bool ReopenRecordState();
virtual bool ReopenRestoreState();
virtual bool SaveSucceeded();
virtual bool SaveFailed();
// Unique id of this document // Unique id of this document
AZ::Uuid m_id = AZ::Uuid::CreateRandom(); AZ::Uuid m_id = AZ::Uuid::CreateRandom();
// Relative path to the material source file
AZStd::string m_relativePath;
// Absolute path to the material source file // Absolute path to the material source file
AZStd::string m_absolutePath; AZStd::string m_absolutePath;
AZStd::string m_savePathNormalized;
AZStd::any m_invalidValue; AZStd::any m_invalidValue;
AtomToolsFramework::DynamicProperty m_invalidProperty; AtomToolsFramework::DynamicProperty m_invalidProperty;
// Set of assets that can trigger a document reload
AZStd::unordered_set<AZStd::string> m_sourceDependencies;
// Track if document saved itself last to skip external modification notification
bool m_saveTriggeredInternally = false;
// Variables needed for tracking the undo and redo state of this document
// Function to be bound for undo and redo
using UndoRedoFunction = AZStd::function<void()>;
// A pair of functions, where first is the undo operation and second is the redo operation
using UndoRedoFunctionPair = AZStd::pair<UndoRedoFunction, UndoRedoFunction>;
// Container for all of the active undo and redo functions and state
using UndoRedoHistory = AZStd::vector<UndoRedoFunctionPair>;
// Container of undo commands
UndoRedoHistory m_undoHistory;
UndoRedoHistory m_undoHistoryBeforeReopen;
// The current position in the undo redo history
int m_undoHistoryIndex = {};
int m_undoHistoryIndexBeforeReopen = {};
void AddUndoRedoHistory(const UndoRedoFunction& undoCommand, const UndoRedoFunction& redoCommand);
// AzToolsFramework::AssetSystemBus::Handler overrides...
void SourceFileChanged(AZStd::string relativePath, AZStd::string scanFolder, AZ::Uuid sourceUUID) override;
}; };
} // namespace AtomToolsFramework } // namespace AtomToolsFramework

@ -25,9 +25,6 @@ namespace AtomToolsFramework
//! Get absolute path of document //! Get absolute path of document
virtual AZStd::string_view GetAbsolutePath() const = 0; virtual AZStd::string_view GetAbsolutePath() const = 0;
//! Get relative path of document
virtual AZStd::string_view GetRelativePath() const = 0;
//! Return property value //! Return property value
//! If the document is not open or the id can't be found, an invalid value is returned instead. //! If the document is not open or the id can't be found, an invalid value is returned instead.
virtual const AZStd::any& GetPropertyValue(const AZ::Name& propertyFullName) const = 0; virtual const AZStd::any& GetPropertyValue(const AZ::Name& propertyFullName) const = 0;

@ -111,7 +111,7 @@ namespace AtomToolsFramework
AZStd::string GetDescription() const; AZStd::string GetDescription() const;
AZStd::vector<AZ::Edit::EnumConstant<uint32_t>> GetEnumValues() const; AZStd::vector<AZ::Edit::EnumConstant<uint32_t>> GetEnumValues() const;
// Handles changes from the ReflectedPropertyEditor and sends notification to the material document. // Handles changes from the ReflectedPropertyEditor and sends notification.
AZ::u32 OnDataChanged() const; AZ::u32 OnDataChanged() const;
template<typename T> template<typename T>

@ -6,8 +6,10 @@
* *
*/ */
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <AtomToolsFramework/Document/AtomToolsDocument.h> #include <AtomToolsFramework/Document/AtomToolsDocument.h>
#include <AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h> #include <AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h>
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
namespace AtomToolsFramework namespace AtomToolsFramework
{ {
@ -19,6 +21,7 @@ namespace AtomToolsFramework
AtomToolsDocument::~AtomToolsDocument() AtomToolsDocument::~AtomToolsDocument()
{ {
AzToolsFramework::AssetSystemBus::Handler::BusDisconnect();
AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsDocumentNotificationBus::Events::OnDocumentDestroyed, m_id); AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsDocumentNotificationBus::Events::OnDocumentDestroyed, m_id);
AtomToolsDocumentRequestBus::Handler::BusDisconnect(); AtomToolsDocumentRequestBus::Handler::BusDisconnect();
} }
@ -33,11 +36,6 @@ namespace AtomToolsFramework
return m_absolutePath; return m_absolutePath;
} }
AZStd::string_view AtomToolsDocument::GetRelativePath() const
{
return m_relativePath;
}
const AZStd::any& AtomToolsDocument::GetPropertyValue([[maybe_unused]] const AZ::Name& propertyId) const const AZStd::any& AtomToolsDocument::GetPropertyValue([[maybe_unused]] const AZ::Name& propertyId) const
{ {
AZ_UNUSED(propertyId); AZ_UNUSED(propertyId);
@ -66,49 +64,140 @@ namespace AtomToolsFramework
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
} }
bool AtomToolsDocument::Open([[maybe_unused]] AZStd::string_view loadPath) bool AtomToolsDocument::Open(AZStd::string_view loadPath)
{ {
AZ_UNUSED(loadPath); Clear();
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
return false; m_absolutePath = loadPath;
if (!AzFramework::StringFunc::Path::Normalize(m_absolutePath))
{
AZ_Error("AtomToolsDocument", false, "Document path could not be normalized: '%s'.", m_absolutePath.c_str());
return OpenFailed();
}
if (AzFramework::StringFunc::Path::IsRelative(m_absolutePath.c_str()))
{
AZ_Error("AtomToolsDocument", false, "Document path must be absolute: '%s'.", m_absolutePath.c_str());
return OpenFailed();
}
return true;
} }
bool AtomToolsDocument::Reopen() bool AtomToolsDocument::Reopen()
{ {
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); if (!ReopenRecordState())
return false; {
return false;
}
const auto loadPath = m_absolutePath;
if (!Open(loadPath))
{
return false;
}
if (!ReopenRestoreState())
{
return false;
}
return true;
} }
bool AtomToolsDocument::Save() bool AtomToolsDocument::Save()
{ {
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); m_savePathNormalized = m_absolutePath;
return false; if (!AzFramework::StringFunc::Path::Normalize(m_savePathNormalized))
{
AZ_Error("AtomToolsDocument", false, "Document save path could not be normalized: '%s'.", m_savePathNormalized.c_str());
return SaveFailed();
}
if (!IsOpen())
{
AZ_Error("AtomToolsDocument", false, "Document is not open to be saved: '%s'.", m_absolutePath.c_str());
return SaveFailed();
}
if (!IsSavable())
{
AZ_Error("AtomToolsDocument", false, "Material types can only be saved as a child: '%s'.", m_absolutePath.c_str());
return SaveFailed();
}
return true;
} }
bool AtomToolsDocument::SaveAsCopy([[maybe_unused]] AZStd::string_view savePath) bool AtomToolsDocument::SaveAsCopy(AZStd::string_view savePath)
{ {
AZ_UNUSED(savePath); m_savePathNormalized = savePath;
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); if (!AzFramework::StringFunc::Path::Normalize(m_savePathNormalized))
return false; {
} AZ_Error("AtomToolsDocument", false, "Document save path could not be normalized: '%s'.", m_savePathNormalized.c_str());
return SaveFailed();
}
if (!IsOpen())
{
AZ_Error("AtomToolsDocument", false, "Document is not open to be saved: '%s'.", m_absolutePath.c_str());
return SaveFailed();
}
if (!IsSavable())
{
AZ_Error("AtomToolsDocument", false, "Material types can only be saved as a child: '%s'.", m_absolutePath.c_str());
return SaveFailed();
}
return true;
}
bool AtomToolsDocument::SaveAsChild([[maybe_unused]] AZStd::string_view savePath) bool AtomToolsDocument::SaveAsChild(AZStd::string_view savePath)
{ {
AZ_UNUSED(savePath); m_savePathNormalized = savePath;
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); if (!AzFramework::StringFunc::Path::Normalize(m_savePathNormalized))
return false; {
AZ_Error("AtomToolsDocument", false, "Document save path could not be normalized: '%s'.", m_savePathNormalized.c_str());
return SaveFailed();
}
if (!IsOpen())
{
AZ_Error("AtomToolsDocument", false, "Document is not open to be saved: '%s'.", m_absolutePath.c_str());
return SaveFailed();
}
if (m_absolutePath == m_savePathNormalized || m_sourceDependencies.find(m_savePathNormalized) != m_sourceDependencies.end())
{
AZ_Error("AtomToolsDocument", false, "Document can't be saved over a dependancy: '%s'.", m_savePathNormalized.c_str());
return SaveFailed();
}
return true;
} }
bool AtomToolsDocument::Close() bool AtomToolsDocument::Close()
{ {
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); if (!IsOpen())
return false; {
AZ_Error("AtomToolsDocument", false, "Document is not open.");
return false;
}
AZ_TracePrintf("AtomToolsDocument", "Document closed: '%s'.\n", m_absolutePath.c_str());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentClosed, m_id);
// Clearing after notification so paths are still available
Clear();
return true;
} }
bool AtomToolsDocument::IsOpen() const bool AtomToolsDocument::IsOpen() const
{ {
return false; return !m_id.IsNull() && !m_absolutePath.empty();
} }
bool AtomToolsDocument::IsModified() const bool AtomToolsDocument::IsModified() const
@ -123,23 +212,41 @@ namespace AtomToolsFramework
bool AtomToolsDocument::CanUndo() const bool AtomToolsDocument::CanUndo() const
{ {
return false; // Undo will only be allowed if something has been recorded and we're not at the beginning of history
return IsOpen() && !m_undoHistory.empty() && m_undoHistoryIndex > 0;
} }
bool AtomToolsDocument::CanRedo() const bool AtomToolsDocument::CanRedo() const
{ {
return false; // Redo will only be allowed if something has been recorded and we're not at the end of history
return IsOpen() && !m_undoHistory.empty() && m_undoHistoryIndex < m_undoHistory.size();
} }
bool AtomToolsDocument::Undo() bool AtomToolsDocument::Undo()
{ {
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); if (CanUndo())
{
// The history index is one beyond the last executed command. Decrement the index then execute undo.
m_undoHistory[--m_undoHistoryIndex].first();
AZ_TracePrintf("AtomToolsDocument", "Document undo: '%s'.\n", m_absolutePath.c_str());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentUndoStateChanged, m_id);
return true;
}
return false; return false;
} }
bool AtomToolsDocument::Redo() bool AtomToolsDocument::Redo()
{ {
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); if (CanRedo())
{
// Execute the current redo command then move the history index to the next position.
m_undoHistory[m_undoHistoryIndex++].second();
AZ_TracePrintf("AtomToolsDocument", "Document redo: '%s'.\n", m_absolutePath.c_str());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentUndoStateChanged, m_id);
return true;
}
return false; return false;
} }
@ -154,4 +261,108 @@ namespace AtomToolsFramework
AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__);
return false; return false;
} }
void AtomToolsDocument::Clear()
{
AzToolsFramework::AssetSystemBus::Handler::BusDisconnect();
m_absolutePath.clear();
m_sourceDependencies.clear();
m_saveTriggeredInternally = {};
m_undoHistory.clear();
m_undoHistoryIndex = {};
}
bool AtomToolsDocument::OpenSucceeded()
{
AZ_TracePrintf("AtomToolsDocument", "Document opened: '%s'.\n", m_absolutePath.c_str());
AzToolsFramework::AssetSystemBus::Handler::BusConnect();
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentOpened, m_id);
return true;
}
bool AtomToolsDocument::OpenFailed()
{
AZ_TracePrintf("AtomToolsDocument", "Document could not opened: '%s'.\n", m_absolutePath.c_str());
Clear();
return false;
}
bool AtomToolsDocument::ReopenRecordState()
{
// Store history and property changes that should be reapplied after reload
m_undoHistoryBeforeReopen = m_undoHistory;
m_undoHistoryIndexBeforeReopen = m_undoHistoryIndex;
return true;
}
bool AtomToolsDocument::ReopenRestoreState()
{
m_undoHistory = m_undoHistoryBeforeReopen;
m_undoHistoryIndex = m_undoHistoryIndexBeforeReopen;
m_undoHistoryBeforeReopen = {};
m_undoHistoryIndexBeforeReopen = {};
return true;
}
bool AtomToolsDocument::SaveSucceeded()
{
m_saveTriggeredInternally = true;
AZ_TracePrintf("AtomToolsDocument", "Document saved: '%s'.\n", m_savePathNormalized.c_str());
// Auto add or checkout saved file
AzToolsFramework::SourceControlCommandBus::Broadcast(
&AzToolsFramework::SourceControlCommandBus::Events::RequestEdit, m_savePathNormalized.c_str(), true,
[](bool, const AzToolsFramework::SourceControlFileInfo&) {});
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentSaved, m_id);
return true;
}
bool AtomToolsDocument::SaveFailed()
{
AZ_TracePrintf("AtomToolsDocument", "Document not saved: '%s'.\n", m_savePathNormalized.c_str());
return false;
}
void AtomToolsDocument::AddUndoRedoHistory(const UndoRedoFunction& undoCommand, const UndoRedoFunction& redoCommand)
{
// Wipe any state beyond the current history index
m_undoHistory.erase(m_undoHistory.begin() + m_undoHistoryIndex, m_undoHistory.end());
// Add undo and redo operations using functions that capture state and restore it when executed
m_undoHistory.emplace_back(undoCommand, redoCommand);
// Assign the index to the end of history
m_undoHistoryIndex = aznumeric_cast<int>(m_undoHistory.size());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentUndoStateChanged, m_id);
}
void AtomToolsDocument::SourceFileChanged(AZStd::string relativePath, AZStd::string scanFolder, [[maybe_unused]] AZ::Uuid sourceUUID)
{
const auto sourcePath = AZ::RPI::AssetUtils::ResolvePathReference(scanFolder, relativePath);
if (m_absolutePath == sourcePath)
{
// ignore notifications caused by saving the open document
if (!m_saveTriggeredInternally)
{
AZ_TracePrintf("AtomToolsDocument", "Document changed externally: '%s'.\n", m_absolutePath.c_str());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentExternallyModified, m_id);
}
m_saveTriggeredInternally = false;
}
else if (m_sourceDependencies.find(sourcePath) != m_sourceDependencies.end())
{
AZ_TracePrintf("AtomToolsDocument", "Document dependency changed: '%s'.\n", m_absolutePath.c_str());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentDependencyModified, m_id);
}
}
} // namespace AtomToolsFramework } // namespace AtomToolsFramework

@ -77,7 +77,6 @@ namespace AtomToolsFramework
->Attribute(AZ::Script::Attributes::Category, "Editor") ->Attribute(AZ::Script::Attributes::Category, "Editor")
->Attribute(AZ::Script::Attributes::Module, "atomtools") ->Attribute(AZ::Script::Attributes::Module, "atomtools")
->Event("GetAbsolutePath", &AtomToolsDocumentRequestBus::Events::GetAbsolutePath) ->Event("GetAbsolutePath", &AtomToolsDocumentRequestBus::Events::GetAbsolutePath)
->Event("GetRelativePath", &AtomToolsDocumentRequestBus::Events::GetRelativePath)
->Event("GetPropertyValue", &AtomToolsDocumentRequestBus::Events::GetPropertyValue) ->Event("GetPropertyValue", &AtomToolsDocumentRequestBus::Events::GetPropertyValue)
->Event("SetPropertyValue", &AtomToolsDocumentRequestBus::Events::SetPropertyValue) ->Event("SetPropertyValue", &AtomToolsDocumentRequestBus::Events::SetPropertyValue)
->Event("Open", &AtomToolsDocumentRequestBus::Events::Open) ->Event("Open", &AtomToolsDocumentRequestBus::Events::Open)

@ -12,16 +12,11 @@
#include <Atom/RPI.Edit/Material/MaterialPropertyId.h> #include <Atom/RPI.Edit/Material/MaterialPropertyId.h>
#include <Atom/RPI.Edit/Material/MaterialUtils.h> #include <Atom/RPI.Edit/Material/MaterialUtils.h>
#include <Atom/RPI.Public/Material/Material.h> #include <Atom/RPI.Public/Material/Material.h>
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
#include <Atom/RPI.Reflect/Image/Image.h>
#include <Atom/RPI.Reflect/Image/StreamingImageAsset.h>
#include <Atom/RPI.Reflect/Material/MaterialFunctor.h> #include <Atom/RPI.Reflect/Material/MaterialFunctor.h>
#include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h> #include <Atom/RPI.Reflect/Material/MaterialPropertiesLayout.h>
#include <AtomCore/Instance/Instance.h> #include <AtomCore/Instance/Instance.h>
#include <AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h> #include <AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h>
#include <AtomToolsFramework/Util/MaterialPropertyUtil.h> #include <AtomToolsFramework/Util/MaterialPropertyUtil.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
#include <Document/MaterialDocument.h> #include <Document/MaterialDocument.h>
namespace MaterialEditor namespace MaterialEditor
@ -30,14 +25,12 @@ namespace MaterialEditor
: AtomToolsFramework::AtomToolsDocument() : AtomToolsFramework::AtomToolsDocument()
{ {
MaterialDocumentRequestBus::Handler::BusConnect(m_id); MaterialDocumentRequestBus::Handler::BusConnect(m_id);
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentCreated, m_id);
} }
MaterialDocument::~MaterialDocument() MaterialDocument::~MaterialDocument()
{ {
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentDestroyed, m_id);
MaterialDocumentRequestBus::Handler::BusDisconnect(); MaterialDocumentRequestBus::Handler::BusDisconnect();
Clear(); AZ::TickBus::Handler::BusDisconnect();
} }
AZ::Data::Asset<AZ::RPI::MaterialAsset> MaterialDocument::GetAsset() const AZ::Data::Asset<AZ::RPI::MaterialAsset> MaterialDocument::GetAsset() const
@ -62,19 +55,16 @@ namespace MaterialEditor
const AZStd::any& MaterialDocument::GetPropertyValue(const AZ::Name& propertyId) const const AZStd::any& MaterialDocument::GetPropertyValue(const AZ::Name& propertyId) const
{ {
using namespace AZ;
using namespace RPI;
if (!IsOpen()) if (!IsOpen())
{ {
AZ_Error("MaterialDocument", false, "Material document is not open."); AZ_Error("MaterialDocument", false, "Document is not open.");
return m_invalidValue; return m_invalidValue;
} }
const auto it = m_properties.find(propertyId); const auto it = m_properties.find(propertyId);
if (it == m_properties.end()) if (it == m_properties.end())
{ {
AZ_Error("MaterialDocument", false, "Material document property could not be found: '%s'.", propertyId.GetCStr()); AZ_Error("MaterialDocument", false, "Document property could not be found: '%s'.", propertyId.GetCStr());
return m_invalidValue; return m_invalidValue;
} }
@ -86,14 +76,14 @@ namespace MaterialEditor
{ {
if (!IsOpen()) if (!IsOpen())
{ {
AZ_Error("MaterialDocument", false, "Material document is not open."); AZ_Error("MaterialDocument", false, "Document is not open.");
return m_invalidProperty; return m_invalidProperty;
} }
const auto it = m_properties.find(propertyId); const auto it = m_properties.find(propertyId);
if (it == m_properties.end()) if (it == m_properties.end())
{ {
AZ_Error("MaterialDocument", false, "Material document property could not be found: '%s'.", propertyId.GetCStr()); AZ_Error("MaterialDocument", false, "Document property could not be found: '%s'.", propertyId.GetCStr());
return m_invalidProperty; return m_invalidProperty;
} }
@ -105,14 +95,14 @@ namespace MaterialEditor
{ {
if (!IsOpen()) if (!IsOpen())
{ {
AZ_Error("MaterialDocument", false, "Material document is not open."); AZ_Error("MaterialDocument", false, "Document is not open.");
return false; return false;
} }
const auto it = m_propertyGroupVisibility.find(propertyGroupFullName); const auto it = m_propertyGroupVisibility.find(propertyGroupFullName);
if (it == m_propertyGroupVisibility.end()) if (it == m_propertyGroupVisibility.end())
{ {
AZ_Error("MaterialDocument", false, "Material document property group could not be found: '%s'.", propertyGroupFullName.GetCStr()); AZ_Error("MaterialDocument", false, "Document property group could not be found: '%s'.", propertyGroupFullName.GetCStr());
return false; return false;
} }
@ -121,25 +111,21 @@ namespace MaterialEditor
void MaterialDocument::SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value) void MaterialDocument::SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value)
{ {
using namespace AZ;
using namespace RPI;
if (!IsOpen()) if (!IsOpen())
{ {
AZ_Error("MaterialDocument", false, "Material document is not open."); AZ_Error("MaterialDocument", false, "Document is not open.");
return; return;
} }
const auto it = m_properties.find(propertyId); const auto it = m_properties.find(propertyId);
if (it == m_properties.end()) if (it == m_properties.end())
{ {
AZ_Error("MaterialDocument", false, "Material document property could not be found: '%s'.", propertyId.GetCStr()); AZ_Error("MaterialDocument", false, "Document property could not be found: '%s'.", propertyId.GetCStr());
return; return;
} }
// This first converts to an acceptable runtime type in case the value came from script // This first converts to an acceptable runtime type in case the value came from script
const AZ::RPI::MaterialPropertyValue propertyValue = const AZ::RPI::MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(value);
AtomToolsFramework::ConvertToRuntimeType(value);
AtomToolsFramework::DynamicProperty& property = it->second; AtomToolsFramework::DynamicProperty& property = it->second;
property.SetValue(AtomToolsFramework::ConvertToEditableType(propertyValue)); property.SetValue(AtomToolsFramework::ConvertToEditableType(propertyValue));
@ -149,88 +135,41 @@ namespace MaterialEditor
{ {
if (m_materialInstance->SetPropertyValue(propertyIndex, propertyValue)) if (m_materialInstance->SetPropertyValue(propertyIndex, propertyValue))
{ {
MaterialPropertyFlags dirtyFlags = m_materialInstance->GetPropertyDirtyFlags(); AZ::RPI::MaterialPropertyFlags dirtyFlags = m_materialInstance->GetPropertyDirtyFlags();
Recompile(); Recompile();
EditorMaterialFunctorResult result = RunEditorMaterialFunctors(dirtyFlags); EditorMaterialFunctorResult result = RunEditorMaterialFunctors(dirtyFlags);
for (const Name& changedPropertyGroupName : result.m_updatedPropertyGroups) for (const AZ::Name& changedPropertyGroupName : result.m_updatedPropertyGroups)
{ {
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentPropertyGroupVisibilityChanged, m_id, changedPropertyGroupName, IsPropertyGroupVisible(changedPropertyGroupName)); AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentPropertyGroupVisibilityChanged, m_id,
changedPropertyGroupName, IsPropertyGroupVisible(changedPropertyGroupName));
} }
for (const Name& changedPropertyName : result.m_updatedProperties) for (const AZ::Name& changedPropertyName : result.m_updatedProperties)
{ {
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentPropertyConfigModified, m_id, GetProperty(changedPropertyName)); AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentPropertyConfigModified, m_id,
GetProperty(changedPropertyName));
} }
} }
} }
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentPropertyValueModified, m_id, property); AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentModified, m_id); &AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentPropertyValueModified, m_id, property);
} AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentModified, m_id);
bool MaterialDocument::Open(AZStd::string_view loadPath)
{
if (!OpenInternal(loadPath))
{
Clear();
AZ_Error("MaterialDocument", false, "Material document could not be opened: '%s'.", loadPath.data());
return false;
}
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentOpened, m_id);
return true;
}
bool MaterialDocument::Reopen()
{
// Store history and property changes that should be reapplied after reload
auto undoHistoryToRestore = m_undoHistory;
auto undoHistoryIndexToRestore = m_undoHistoryIndex;
PropertyValueMap propertyValuesToRestore;
for (const auto& propertyPair : m_properties)
{
const AtomToolsFramework::DynamicProperty& property = propertyPair.second;
if (!AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_parentValue))
{
propertyValuesToRestore[property.GetId()] = property.GetValue();
}
}
// Reopen the same document
const AZStd::string loadPath = m_absolutePath;
if (!OpenInternal(loadPath))
{
Clear();
return false;
}
RestorePropertyValues(propertyValuesToRestore);
AZStd::swap(undoHistoryToRestore, m_undoHistory);
AZStd::swap(undoHistoryIndexToRestore, m_undoHistoryIndex);
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentOpened, m_id);
return true;
} }
bool MaterialDocument::Save() bool MaterialDocument::Save()
{ {
using namespace AZ; if (!AtomToolsDocument::Save())
using namespace RPI;
if (!IsOpen())
{ {
AZ_Error("MaterialDocument", false, "Material document is not open to be saved: '%s'.", m_absolutePath.c_str());
return false;
}
if (!IsSavable())
{
AZ_Error("MaterialDocument", false, "Material types can only be saved as a child: '%s'.", m_absolutePath.c_str());
return false; return false;
} }
// create source data from properties // create source data from properties
MaterialSourceData sourceData; AZ::RPI::MaterialSourceData sourceData;
sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion(); sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion();
sourceData.m_materialType = AtomToolsFramework::GetExteralReferencePath(m_absolutePath, m_materialSourceData.m_materialType); sourceData.m_materialType = AtomToolsFramework::GetExteralReferencePath(m_absolutePath, m_materialSourceData.m_materialType);
sourceData.m_parentMaterial = AtomToolsFramework::GetExteralReferencePath(m_absolutePath, m_materialSourceData.m_parentMaterial); sourceData.m_parentMaterial = AtomToolsFramework::GetExteralReferencePath(m_absolutePath, m_materialSourceData.m_parentMaterial);
@ -243,14 +182,14 @@ namespace MaterialEditor
if (!savedProperties) if (!savedProperties)
{ {
return false; return SaveFailed();
} }
// write sourceData to .material file // write sourceData to .material file
if (!AZ::RPI::JsonUtils::SaveObjectToFile(m_absolutePath, sourceData)) if (!AZ::RPI::JsonUtils::SaveObjectToFile(m_absolutePath, sourceData))
{ {
AZ_Error("MaterialDocument", false, "Material document could not be saved: '%s'.", m_absolutePath.c_str()); AZ_Error("MaterialDocument", false, "Document could not be saved: '%s'.", m_absolutePath.c_str());
return false; return SaveFailed();
} }
// after saving, reset to a clean state // after saving, reset to a clean state
@ -262,181 +201,97 @@ namespace MaterialEditor
property.SetConfig(propertyConfig); property.SetConfig(propertyConfig);
} }
// Auto add or checkout saved file return SaveSucceeded();
AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::RequestEdit,
m_absolutePath.c_str(), true, [](bool, const AzToolsFramework::SourceControlFileInfo&) {});
AZ_TracePrintf("MaterialDocument", "Material document saved: '%s'.\n", m_absolutePath.data());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentSaved, m_id);
m_saveTriggeredInternally = true;
return true;
} }
bool MaterialDocument::SaveAsCopy(AZStd::string_view savePath) bool MaterialDocument::SaveAsCopy(AZStd::string_view savePath)
{ {
using namespace AZ; if (!AtomToolsDocument::SaveAsCopy(savePath))
using namespace RPI;
if (!IsOpen())
{
AZ_Error("MaterialDocument", false, "Material document is not open to be saved: '%s'.", m_absolutePath.c_str());
return false;
}
if (!IsSavable())
{ {
AZ_Error("MaterialDocument", false, "Material types can only be saved as a child: '%s'.", m_absolutePath.c_str());
return false;
}
AZStd::string normalizedSavePath = savePath;
if (!AzFramework::StringFunc::Path::Normalize(normalizedSavePath))
{
AZ_Error("MaterialDocument", false, "Material document save path could not be normalized: '%s'.", normalizedSavePath.c_str());
return false; return false;
} }
// create source data from properties // create source data from properties
MaterialSourceData sourceData; AZ::RPI::MaterialSourceData sourceData;
sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion(); sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion();
sourceData.m_materialType = AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_materialSourceData.m_materialType); sourceData.m_materialType = AtomToolsFramework::GetExteralReferencePath(m_savePathNormalized, m_materialSourceData.m_materialType);
sourceData.m_parentMaterial = AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_materialSourceData.m_parentMaterial); sourceData.m_parentMaterial = AtomToolsFramework::GetExteralReferencePath(m_savePathNormalized, m_materialSourceData.m_parentMaterial);
// populate sourceData with modified or overwritten properties // populate sourceData with modified or overwritten properties
const bool savedProperties = SavePropertiesToSourceData(normalizedSavePath, sourceData, [](const AtomToolsFramework::DynamicProperty& property) const bool savedProperties = SavePropertiesToSourceData(m_savePathNormalized, sourceData, [](const AtomToolsFramework::DynamicProperty& property)
{ {
return !AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_parentValue); return !AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_parentValue);
}); });
if (!savedProperties) if (!savedProperties)
{ {
return false; return SaveFailed();
} }
// write sourceData to .material file // write sourceData to .material file
if (!AZ::RPI::JsonUtils::SaveObjectToFile(normalizedSavePath, sourceData)) if (!AZ::RPI::JsonUtils::SaveObjectToFile(m_savePathNormalized, sourceData))
{ {
AZ_Error("MaterialDocument", false, "Material document could not be saved: '%s'.", normalizedSavePath.c_str()); AZ_Error("MaterialDocument", false, "Document could not be saved: '%s'.", m_savePathNormalized.c_str());
return false; return SaveFailed();
} }
// Auto add or checkout saved file
AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::RequestEdit,
normalizedSavePath.c_str(), true, [](bool, const AzToolsFramework::SourceControlFileInfo&) {});
AZ_TracePrintf("MaterialDocument", "Material document saved: '%s'.\n", normalizedSavePath.c_str());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentSaved, m_id);
// If the document is saved to a new file we need to reopen the new document to update assets, paths, property deltas. // If the document is saved to a new file we need to reopen the new document to update assets, paths, property deltas.
if (!Open(normalizedSavePath)) if (!Open(m_savePathNormalized))
{ {
return false; return SaveFailed();
} }
// Setting flag after reopening becausse it's cleared on open return SaveSucceeded();
m_saveTriggeredInternally = true;
return true;
} }
bool MaterialDocument::SaveAsChild(AZStd::string_view savePath) bool MaterialDocument::SaveAsChild(AZStd::string_view savePath)
{ {
using namespace AZ; if (!AtomToolsDocument::SaveAsChild(savePath))
using namespace RPI;
if (!IsOpen())
{
AZ_Error("MaterialDocument", false, "Material document is not open to be saved: '%s'.", m_absolutePath.c_str());
return false;
}
AZStd::string normalizedSavePath = savePath;
if (!AzFramework::StringFunc::Path::Normalize(normalizedSavePath))
{ {
AZ_Error("MaterialDocument", false, "Material document save path could not be normalized: '%s'.", normalizedSavePath.c_str());
return false;
}
if (m_absolutePath == normalizedSavePath)
{
// ToDo: this should scan the entire hierarchy so we don't overwrite parent's parent, for example
AZ_Error("MaterialDocument", false, "Can't overwrite parent material with a child that depends on it.");
return false; return false;
} }
// create source data from properties // create source data from properties
MaterialSourceData sourceData; AZ::RPI::MaterialSourceData sourceData;
sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion(); sourceData.m_materialTypeVersion = m_materialAsset->GetMaterialTypeAsset()->GetVersion();
sourceData.m_materialType = AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_materialSourceData.m_materialType); sourceData.m_materialType = AtomToolsFramework::GetExteralReferencePath(m_savePathNormalized, m_materialSourceData.m_materialType);
// Only assign a parent path if the source was a .material // Only assign a parent path if the source was a .material
if (AzFramework::StringFunc::Path::IsExtension(m_relativePath.c_str(), MaterialSourceData::Extension)) if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), AZ::RPI::MaterialSourceData::Extension))
{ {
sourceData.m_parentMaterial = AtomToolsFramework::GetExteralReferencePath(normalizedSavePath, m_absolutePath); sourceData.m_parentMaterial = AtomToolsFramework::GetExteralReferencePath(m_savePathNormalized, m_absolutePath);
} }
// populate sourceData with modified properties // populate sourceData with modified properties
const bool savedProperties = SavePropertiesToSourceData(normalizedSavePath, sourceData, [](const AtomToolsFramework::DynamicProperty& property) const bool savedProperties = SavePropertiesToSourceData(m_savePathNormalized, sourceData, [](const AtomToolsFramework::DynamicProperty& property)
{ {
return !AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_originalValue); return !AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_originalValue);
}); });
if (!savedProperties) if (!savedProperties)
{ {
return false; return SaveFailed();
} }
// write sourceData to .material file // write sourceData to .material file
if (!AZ::RPI::JsonUtils::SaveObjectToFile(normalizedSavePath, sourceData)) if (!AZ::RPI::JsonUtils::SaveObjectToFile(m_savePathNormalized, sourceData))
{ {
AZ_Error("MaterialDocument", false, "Material document could not be saved: '%s'.", normalizedSavePath.c_str()); AZ_Error("MaterialDocument", false, "Document could not be saved: '%s'.", m_savePathNormalized.c_str());
return false; return SaveFailed();
} }
// Auto add or checkout saved file
AzToolsFramework::SourceControlCommandBus::Broadcast(&AzToolsFramework::SourceControlCommandBus::Events::RequestEdit,
normalizedSavePath.c_str(), true, [](bool, const AzToolsFramework::SourceControlFileInfo&) {});
AZ_TracePrintf("MaterialDocument", "Material document saved: '%s'.\n", normalizedSavePath.c_str());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentSaved, m_id);
// If the document is saved to a new file we need to reopen the new document to update assets, paths, property deltas. // If the document is saved to a new file we need to reopen the new document to update assets, paths, property deltas.
if (!Open(normalizedSavePath)) if (!Open(m_savePathNormalized))
{ {
return false; return SaveFailed();
} }
// Setting flag after reopening becausse it's cleared on open return SaveSucceeded();
m_saveTriggeredInternally = true;
return true;
}
bool MaterialDocument::Close()
{
using namespace AZ;
using namespace RPI;
if (!IsOpen())
{
AZ_Error("MaterialDocument", false, "Material document is not open.");
return false;
}
AZ_TracePrintf("MaterialDocument", "Material document closed: '%s'.\n", m_absolutePath.c_str());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentClosed, m_id);
// Clearing after notification so paths are still available
Clear();
return true;
} }
bool MaterialDocument::IsOpen() const bool MaterialDocument::IsOpen() const
{ {
return !m_absolutePath.empty() && !m_relativePath.empty() && m_materialAsset.IsReady() && m_materialInstance; return AtomToolsDocument::IsOpen() && m_materialAsset.IsReady() && m_materialInstance;
} }
bool MaterialDocument::IsModified() const bool MaterialDocument::IsModified() const
@ -454,44 +309,6 @@ namespace MaterialEditor
return AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), AZ::RPI::MaterialSourceData::Extension); return AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), AZ::RPI::MaterialSourceData::Extension);
} }
bool MaterialDocument::CanUndo() const
{
// Undo will only be allowed if something has been recorded and we're not at the beginning of history
return IsOpen() && !m_undoHistory.empty() && m_undoHistoryIndex > 0;
}
bool MaterialDocument::CanRedo() const
{
// Redo will only be allowed if something has been recorded and we're not at the end of history
return IsOpen() && !m_undoHistory.empty() && m_undoHistoryIndex < m_undoHistory.size();
}
bool MaterialDocument::Undo()
{
if (CanUndo())
{
// The history index is one beyond the last executed command. Decrement the index then execute undo.
m_undoHistory[--m_undoHistoryIndex].first();
AZ_TracePrintf("MaterialDocument", "Material document undo: '%s'.\n", m_absolutePath.c_str());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentUndoStateChanged, m_id);
return true;
}
return false;
}
bool MaterialDocument::Redo()
{
if (CanRedo())
{
// Execute the current redo command then move the history index to the next position.
m_undoHistory[m_undoHistoryIndex++].second();
AZ_TracePrintf("MaterialDocument", "Material document redo: '%s'.\n", m_absolutePath.c_str());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentUndoStateChanged, m_id);
return true;
}
return false;
}
bool MaterialDocument::BeginEdit() bool MaterialDocument::BeginEdit()
{ {
// Save the current properties as a momento for undo before any changes are applied // Save the current properties as a momento for undo before any changes are applied
@ -524,17 +341,9 @@ namespace MaterialEditor
if (!propertyValuesForUndo.empty() && !propertyValuesForRedo.empty()) if (!propertyValuesForUndo.empty() && !propertyValuesForRedo.empty())
{ {
// Wipe any state beyond the current history index AddUndoRedoHistory(
m_undoHistory.erase(m_undoHistory.begin() + m_undoHistoryIndex, m_undoHistory.end());
// Add undo and redo operations using lambdas that will capture property state and restore it when executed
m_undoHistory.emplace_back(
[this, propertyValuesForUndo]() { RestorePropertyValues(propertyValuesForUndo); }, [this, propertyValuesForUndo]() { RestorePropertyValues(propertyValuesForUndo); },
[this, propertyValuesForRedo]() { RestorePropertyValues(propertyValuesForRedo); }); [this, propertyValuesForRedo]() { RestorePropertyValues(propertyValuesForRedo); });
// Assign the index to the end of history
m_undoHistoryIndex = aznumeric_cast<int>(m_undoHistory.size());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentUndoStateChanged, m_id);
} }
m_propertyValuesBeforeEdit.clear(); m_propertyValuesBeforeEdit.clear();
@ -553,51 +362,25 @@ namespace MaterialEditor
} }
} }
void MaterialDocument::SourceFileChanged(AZStd::string relativePath, AZStd::string scanFolder, [[maybe_unused]] AZ::Uuid sourceUUID)
{
const auto sourcePath = AZ::RPI::AssetUtils::ResolvePathReference(scanFolder, relativePath);
if (m_absolutePath == sourcePath)
{
// ignore notifications caused by saving the open document
if (!m_saveTriggeredInternally)
{
AZ_TracePrintf("MaterialDocument", "Material document changed externally: '%s'.\n", m_absolutePath.c_str());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentExternallyModified, m_id);
}
m_saveTriggeredInternally = false;
}
else if (m_sourceDependencies.find(sourcePath) != m_sourceDependencies.end())
{
AZ_TracePrintf("MaterialDocument", "Material document dependency changed: '%s'.\n", m_absolutePath.c_str());
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentDependencyModified, m_id);
}
}
bool MaterialDocument::SavePropertiesToSourceData( bool MaterialDocument::SavePropertiesToSourceData(
const AZStd::string& exportPath, AZ::RPI::MaterialSourceData& sourceData, PropertyFilterFunction propertyFilter) const const AZStd::string& exportPath, AZ::RPI::MaterialSourceData& sourceData, PropertyFilterFunction propertyFilter) const
{ {
using namespace AZ;
using namespace RPI;
bool result = true; bool result = true;
// populate sourceData with properties that meet the filter // populate sourceData with properties that meet the filter
m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& propertyIdContext, const auto& propertyDefinition) { m_materialTypeSourceData.EnumerateProperties([&](const AZStd::string& propertyIdContext, const auto& propertyDefinition) {
Name propertyId{propertyIdContext + propertyDefinition->GetName()}; AZ::Name propertyId{propertyIdContext + propertyDefinition->GetName()};
const auto it = m_properties.find(propertyId); const auto it = m_properties.find(propertyId);
if (it != m_properties.end() && propertyFilter(it->second)) if (it != m_properties.end() && propertyFilter(it->second))
{ {
MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(it->second.GetValue()); AZ::RPI::MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(it->second.GetValue());
if (propertyValue.IsValid()) if (propertyValue.IsValid())
{ {
if (!AtomToolsFramework::ConvertToExportFormat(exportPath, propertyId, *propertyDefinition, propertyValue)) if (!AtomToolsFramework::ConvertToExportFormat(exportPath, propertyId, *propertyDefinition, propertyValue))
{ {
AZ_Error("MaterialDocument", false, "Material document property could not be converted: '%s' in '%s'.", propertyId.GetCStr(), m_absolutePath.c_str()); AZ_Error("MaterialDocument", false, "Document property could not be converted: '%s' in '%s'.", propertyId.GetCStr(), m_absolutePath.c_str());
result = false; result = false;
return false; return false;
} }
@ -613,52 +396,21 @@ namespace MaterialEditor
return result; return result;
} }
bool MaterialDocument::OpenInternal(AZStd::string_view loadPath) bool MaterialDocument::Open(AZStd::string_view loadPath)
{ {
using namespace AZ; if (!AtomToolsDocument::Open(loadPath))
using namespace RPI;
Clear();
m_absolutePath = loadPath;
if (!AzFramework::StringFunc::Path::Normalize(m_absolutePath))
{ {
AZ_Error("MaterialDocument", false, "Material document path could not be normalized: '%s'.", m_absolutePath.c_str());
return false;
}
if (AzFramework::StringFunc::Path::IsRelative(m_absolutePath.c_str()))
{
AZ_Error("MaterialDocument", false, "Material document path must be absolute: '%s'.", m_absolutePath.c_str());
return false;
}
bool result = false;
Data::AssetInfo sourceAssetInfo;
AZStd::string watchFolder;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetSourceInfoBySourcePath,
m_absolutePath.c_str(), sourceAssetInfo, watchFolder);
if (!result)
{
AZ_Error("MaterialDocument", false, "Could not find source material: '%s'.", m_absolutePath.c_str());
return false;
}
m_relativePath = sourceAssetInfo.m_relativePath;
if (!AzFramework::StringFunc::Path::Normalize(m_relativePath))
{
AZ_Error("MaterialDocument", false, "Material document path could not be normalized: '%s'.", m_relativePath.c_str());
return false; return false;
} }
// The material document and inspector are constructed from source data // The material document and inspector are constructed from source data
if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), MaterialSourceData::Extension)) if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), AZ::RPI::MaterialSourceData::Extension))
{ {
// Load the material source data so that we can check properties and create a material asset from it // Load the material source data so that we can check properties and create a material asset from it
if (!AZ::RPI::JsonUtils::LoadObjectFromFile(m_absolutePath, m_materialSourceData)) if (!AZ::RPI::JsonUtils::LoadObjectFromFile(m_absolutePath, m_materialSourceData))
{ {
AZ_Error("MaterialDocument", false, "Material source data could not be loaded: '%s'.", m_absolutePath.c_str()); AZ_Error("MaterialDocument", false, "Material source data could not be loaded: '%s'.", m_absolutePath.c_str());
return false; return OpenFailed();
} }
// We always need the absolute path for the material type and parent material to load source data and resolving // We always need the absolute path for the material type and parent material to load source data and resolving
@ -666,33 +418,34 @@ namespace MaterialEditor
if (!m_materialSourceData.m_parentMaterial.empty()) if (!m_materialSourceData.m_parentMaterial.empty())
{ {
m_materialSourceData.m_parentMaterial = m_materialSourceData.m_parentMaterial =
AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_parentMaterial); AZ::RPI::AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_parentMaterial);
} }
if (!m_materialSourceData.m_materialType.empty()) if (!m_materialSourceData.m_materialType.empty())
{ {
m_materialSourceData.m_materialType = AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_materialType); m_materialSourceData.m_materialType =
AZ::RPI::AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_materialType);
} }
// Load the material type source data which provides the layout and default values of all of the properties // Load the material type source data which provides the layout and default values of all of the properties
auto materialTypeOutcome = MaterialUtils::LoadMaterialTypeSourceData(m_materialSourceData.m_materialType); auto materialTypeOutcome = AZ::RPI::MaterialUtils::LoadMaterialTypeSourceData(m_materialSourceData.m_materialType);
if (!materialTypeOutcome.IsSuccess()) if (!materialTypeOutcome.IsSuccess())
{ {
AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_materialSourceData.m_materialType.c_str()); AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_materialSourceData.m_materialType.c_str());
return false; return OpenFailed();
} }
m_materialTypeSourceData = materialTypeOutcome.TakeValue(); m_materialTypeSourceData = materialTypeOutcome.TakeValue();
} }
else if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), MaterialTypeSourceData::Extension)) else if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), AZ::RPI::MaterialTypeSourceData::Extension))
{ {
// A material document can be created or loaded from material or material type source data. If we are attempting to load // A material document can be created or loaded from material or material type source data. If we are attempting to load
// material type source data then the material source data object can be created just by referencing the document path as the // material type source data then the material source data object can be created just by referencing the document path as the
// material type path. // material type path.
auto materialTypeOutcome = MaterialUtils::LoadMaterialTypeSourceData(m_absolutePath); auto materialTypeOutcome = AZ::RPI::MaterialUtils::LoadMaterialTypeSourceData(m_absolutePath);
if (!materialTypeOutcome.IsSuccess()) if (!materialTypeOutcome.IsSuccess())
{ {
AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_absolutePath.c_str()); AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_absolutePath.c_str());
return false; return OpenFailed();
} }
m_materialTypeSourceData = materialTypeOutcome.TakeValue(); m_materialTypeSourceData = materialTypeOutcome.TakeValue();
@ -702,8 +455,8 @@ namespace MaterialEditor
} }
else else
{ {
AZ_Error("MaterialDocument", false, "Material document extension not supported: '%s'.", m_absolutePath.c_str()); AZ_Error("MaterialDocument", false, "Document extension not supported: '%s'.", m_absolutePath.c_str());
return false; return OpenFailed();
} }
const bool elevateWarnings = false; const bool elevateWarnings = false;
@ -714,44 +467,44 @@ namespace MaterialEditor
// we can create the asset dynamically from the source data. // we can create the asset dynamically from the source data.
// Long term, the material document should not be concerned with assets at all. The viewport window should be the // Long term, the material document should not be concerned with assets at all. The viewport window should be the
// only thing concerned with assets or instances. // only thing concerned with assets or instances.
auto materialAssetResult = auto materialAssetResult = m_materialSourceData.CreateMaterialAssetFromSourceData(
m_materialSourceData.CreateMaterialAssetFromSourceData(Uuid::CreateRandom(), m_absolutePath, elevateWarnings, &m_sourceDependencies); AZ::Uuid::CreateRandom(), m_absolutePath, elevateWarnings, &m_sourceDependencies);
if (!materialAssetResult) if (!materialAssetResult)
{ {
AZ_Error("MaterialDocument", false, "Material asset could not be created from source data: '%s'.", m_absolutePath.c_str()); AZ_Error("MaterialDocument", false, "Material asset could not be created from source data: '%s'.", m_absolutePath.c_str());
return false; return OpenFailed();
} }
m_materialAsset = materialAssetResult.GetValue(); m_materialAsset = materialAssetResult.GetValue();
if (!m_materialAsset.IsReady()) if (!m_materialAsset.IsReady())
{ {
AZ_Error("MaterialDocument", false, "Material asset is not ready: '%s'.", m_absolutePath.c_str()); AZ_Error("MaterialDocument", false, "Material asset is not ready: '%s'.", m_absolutePath.c_str());
return false; return OpenFailed();
} }
const auto& materialTypeAsset = m_materialAsset->GetMaterialTypeAsset(); const auto& materialTypeAsset = m_materialAsset->GetMaterialTypeAsset();
if (!materialTypeAsset.IsReady()) if (!materialTypeAsset.IsReady())
{ {
AZ_Error("MaterialDocument", false, "Material type asset is not ready: '%s'.", m_absolutePath.c_str()); AZ_Error("MaterialDocument", false, "Material type asset is not ready: '%s'.", m_absolutePath.c_str());
return false; return OpenFailed();
} }
AZStd::span<const AZ::RPI::MaterialPropertyValue> parentPropertyValues = materialTypeAsset->GetDefaultPropertyValues(); AZStd::span<const AZ::RPI::MaterialPropertyValue> parentPropertyValues = materialTypeAsset->GetDefaultPropertyValues();
AZ::Data::Asset<MaterialAsset> parentMaterialAsset; AZ::Data::Asset<AZ::RPI::MaterialAsset> parentMaterialAsset;
if (!m_materialSourceData.m_parentMaterial.empty()) if (!m_materialSourceData.m_parentMaterial.empty())
{ {
AZ::RPI::MaterialSourceData parentMaterialSourceData; AZ::RPI::MaterialSourceData parentMaterialSourceData;
if (!AZ::RPI::JsonUtils::LoadObjectFromFile(m_materialSourceData.m_parentMaterial, parentMaterialSourceData)) if (!AZ::RPI::JsonUtils::LoadObjectFromFile(m_materialSourceData.m_parentMaterial, parentMaterialSourceData))
{ {
AZ_Error("MaterialDocument", false, "Material parent source data could not be loaded for: '%s'.", m_materialSourceData.m_parentMaterial.c_str()); AZ_Error("MaterialDocument", false, "Material parent source data could not be loaded for: '%s'.", m_materialSourceData.m_parentMaterial.c_str());
return false; return OpenFailed();
} }
const auto parentMaterialAssetIdResult = AssetUtils::MakeAssetId(m_materialSourceData.m_parentMaterial, 0); const auto parentMaterialAssetIdResult = AZ::RPI::AssetUtils::MakeAssetId(m_materialSourceData.m_parentMaterial, 0);
if (!parentMaterialAssetIdResult) if (!parentMaterialAssetIdResult)
{ {
AZ_Error("MaterialDocument", false, "Material parent asset ID could not be created: '%s'.", m_materialSourceData.m_parentMaterial.c_str()); AZ_Error("MaterialDocument", false, "Material parent asset ID could not be created: '%s'.", m_materialSourceData.m_parentMaterial.c_str());
return false; return OpenFailed();
} }
auto parentMaterialAssetResult = parentMaterialSourceData.CreateMaterialAssetFromSourceData( auto parentMaterialAssetResult = parentMaterialSourceData.CreateMaterialAssetFromSourceData(
@ -759,7 +512,7 @@ namespace MaterialEditor
if (!parentMaterialAssetResult) if (!parentMaterialAssetResult)
{ {
AZ_Error("MaterialDocument", false, "Material parent asset could not be created from source data: '%s'.", m_materialSourceData.m_parentMaterial.c_str()); AZ_Error("MaterialDocument", false, "Material parent asset could not be created from source data: '%s'.", m_materialSourceData.m_parentMaterial.c_str());
return false; return OpenFailed();
} }
parentMaterialAsset = parentMaterialAssetResult.GetValue(); parentMaterialAsset = parentMaterialAssetResult.GetValue();
@ -767,11 +520,11 @@ namespace MaterialEditor
} }
// Creating a material from a material asset will fail if a texture is referenced but not loaded // Creating a material from a material asset will fail if a texture is referenced but not loaded
m_materialInstance = Material::Create(m_materialAsset); m_materialInstance = AZ::RPI::Material::Create(m_materialAsset);
if (!m_materialInstance) if (!m_materialInstance)
{ {
AZ_Error("MaterialDocument", false, "Material instance could not be created: '%s'.", m_absolutePath.c_str()); AZ_Error("MaterialDocument", false, "Material instance could not be created: '%s'.", m_absolutePath.c_str());
return false; return OpenFailed();
} }
// Pipeline State Object changes are always allowed in the material editor because it only runs on developer systems // Pipeline State Object changes are always allowed in the material editor because it only runs on developer systems
@ -781,7 +534,7 @@ namespace MaterialEditor
// Populate the property map from a combination of source data and assets // Populate the property map from a combination of source data and assets
// Assets must still be used for now because they contain the final accumulated value after all other materials // Assets must still be used for now because they contain the final accumulated value after all other materials
// in the hierarchy are applied // in the hierarchy are applied
m_materialTypeSourceData.EnumeratePropertyGroups([this, &parentPropertyValues](const AZStd::string& propertyIdContext, const MaterialTypeSourceData::PropertyGroup* propertyGroup) m_materialTypeSourceData.EnumeratePropertyGroups([this, &parentPropertyValues](const AZStd::string& propertyIdContext, const AZ::RPI::MaterialTypeSourceData::PropertyGroup* propertyGroup)
{ {
AtomToolsFramework::DynamicPropertyConfig propertyConfig; AtomToolsFramework::DynamicPropertyConfig propertyConfig;
@ -813,7 +566,7 @@ namespace MaterialEditor
// Populate the property group visibility map // Populate the property group visibility map
// TODO: Support populating the Material Editor with nested property groups, not just the top level. // TODO: Support populating the Material Editor with nested property groups, not just the top level.
for (const AZStd::unique_ptr<MaterialTypeSourceData::PropertyGroup>& propertyGroup : m_materialTypeSourceData.GetPropertyLayout().m_propertyGroups) for (const AZStd::unique_ptr<AZ::RPI::MaterialTypeSourceData::PropertyGroup>& propertyGroup : m_materialTypeSourceData.GetPropertyLayout().m_propertyGroups)
{ {
m_propertyGroupVisibility[AZ::Name{propertyGroup->GetName()}] = true; m_propertyGroupVisibility[AZ::Name{propertyGroup->GetName()}] = true;
} }
@ -856,15 +609,15 @@ namespace MaterialEditor
m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig); m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig);
//Add UV name customization properties //Add UV name customization properties
const RPI::MaterialUvNameMap& uvNameMap = materialTypeAsset->GetUvNameMap(); const AZ::RPI::MaterialUvNameMap& uvNameMap = materialTypeAsset->GetUvNameMap();
for (const RPI::UvNamePair& uvNamePair : uvNameMap) for (const AZ::RPI::UvNamePair& uvNamePair : uvNameMap)
{ {
const AZStd::string shaderInput = uvNamePair.m_shaderInput.ToString(); const AZStd::string shaderInput = uvNamePair.m_shaderInput.ToString();
const AZStd::string uvName = uvNamePair.m_uvName.GetStringView(); const AZStd::string uvName = uvNamePair.m_uvName.GetStringView();
propertyConfig = {}; propertyConfig = {};
propertyConfig.m_dataType = AtomToolsFramework::DynamicPropertyType::String; propertyConfig.m_dataType = AtomToolsFramework::DynamicPropertyType::String;
propertyConfig.m_id = MaterialPropertyId(UvGroupName, shaderInput); propertyConfig.m_id = AZ::RPI::MaterialPropertyId(UvGroupName, shaderInput);
propertyConfig.m_name = shaderInput; propertyConfig.m_name = shaderInput;
propertyConfig.m_displayName = shaderInput; propertyConfig.m_displayName = shaderInput;
propertyConfig.m_groupName = "UV Sets"; propertyConfig.m_groupName = "UV Sets";
@ -878,15 +631,15 @@ namespace MaterialEditor
} }
// Add material functors that are in the top-level functors list. // Add material functors that are in the top-level functors list.
const MaterialFunctorSourceData::EditorContext editorContext = const AZ::RPI::MaterialFunctorSourceData::EditorContext editorContext =
MaterialFunctorSourceData::EditorContext(m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout()); AZ::RPI::MaterialFunctorSourceData::EditorContext(m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout());
for (Ptr<MaterialFunctorSourceDataHolder> functorData : m_materialTypeSourceData.m_materialFunctorSourceData) for (Ptr<AZ::RPI::MaterialFunctorSourceDataHolder> functorData : m_materialTypeSourceData.m_materialFunctorSourceData)
{ {
MaterialFunctorSourceData::FunctorResult result2 = functorData->CreateFunctor(editorContext); AZ::RPI::MaterialFunctorSourceData::FunctorResult result2 = functorData->CreateFunctor(editorContext);
if (result2.IsSuccess()) if (result2.IsSuccess())
{ {
Ptr<MaterialFunctor>& functor = result2.GetValue(); Ptr<AZ::RPI::MaterialFunctor>& functor = result2.GetValue();
if (functor != nullptr) if (functor != nullptr)
{ {
m_editorFunctors.push_back(functor); m_editorFunctors.push_back(functor);
@ -895,24 +648,24 @@ namespace MaterialEditor
else else
{ {
AZ_Error("MaterialDocument", false, "Material functors were not created: '%s'.", m_absolutePath.c_str()); AZ_Error("MaterialDocument", false, "Material functors were not created: '%s'.", m_absolutePath.c_str());
return false; return OpenFailed();
} }
} }
// Add any material functors that are located inside each property group. // Add any material functors that are located inside each property group.
bool enumerateResult = m_materialTypeSourceData.EnumeratePropertyGroups( bool enumerateResult = m_materialTypeSourceData.EnumeratePropertyGroups(
[this](const AZStd::string&, const MaterialTypeSourceData::PropertyGroup* propertyGroup) [this](const AZStd::string&, const AZ::RPI::MaterialTypeSourceData::PropertyGroup* propertyGroup)
{ {
const MaterialFunctorSourceData::EditorContext editorContext = MaterialFunctorSourceData::EditorContext( const AZ::RPI::MaterialFunctorSourceData::EditorContext editorContext = AZ::RPI::MaterialFunctorSourceData::EditorContext(
m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout()); m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout());
for (Ptr<MaterialFunctorSourceDataHolder> functorData : propertyGroup->GetFunctors()) for (Ptr<AZ::RPI::MaterialFunctorSourceDataHolder> functorData : propertyGroup->GetFunctors())
{ {
MaterialFunctorSourceData::FunctorResult result = functorData->CreateFunctor(editorContext); AZ::RPI::MaterialFunctorSourceData::FunctorResult result = functorData->CreateFunctor(editorContext);
if (result.IsSuccess()) if (result.IsSuccess())
{ {
Ptr<MaterialFunctor>& functor = result.GetValue(); Ptr<AZ::RPI::MaterialFunctor>& functor = result.GetValue();
if (functor != nullptr) if (functor != nullptr)
{ {
m_editorFunctors.push_back(functor); m_editorFunctors.push_back(functor);
@ -930,18 +683,35 @@ namespace MaterialEditor
if (!enumerateResult) if (!enumerateResult)
{ {
return false; return OpenFailed();
} }
AZ::RPI::MaterialPropertyFlags dirtyFlags; AZ::RPI::MaterialPropertyFlags dirtyFlags;
dirtyFlags.set(); // Mark all properties as dirty since we just loaded the material and need to initialize property visibility dirtyFlags.set(); // Mark all properties as dirty since we just loaded the material and need to initialize property visibility
RunEditorMaterialFunctors(dirtyFlags); RunEditorMaterialFunctors(dirtyFlags);
// Connecting to bus to monitor external changes return OpenSucceeded();
AzToolsFramework::AssetSystemBus::Handler::BusConnect(); }
AZ_TracePrintf("MaterialDocument", "Material document opened: '%s'.\n", m_absolutePath.c_str()); bool MaterialDocument::ReopenRecordState()
return true; {
m_propertyValuesBeforeReopen.clear();
for (const auto& propertyPair : m_properties)
{
const AtomToolsFramework::DynamicProperty& property = propertyPair.second;
if (!AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_parentValue))
{
m_propertyValuesBeforeReopen[property.GetId()] = property.GetValue();
}
}
return AtomToolsDocument::ReopenRecordState();
}
bool MaterialDocument::ReopenRestoreState()
{
RestorePropertyValues(m_propertyValuesBeforeReopen);
m_propertyValuesBeforeReopen.clear();
return AtomToolsDocument::ReopenRestoreState();
} }
void MaterialDocument::Recompile() void MaterialDocument::Recompile()
@ -955,23 +725,18 @@ namespace MaterialEditor
void MaterialDocument::Clear() void MaterialDocument::Clear()
{ {
AtomToolsFramework::AtomToolsDocument::Clear();
AZ::TickBus::Handler::BusDisconnect(); AZ::TickBus::Handler::BusDisconnect();
AzToolsFramework::AssetSystemBus::Handler::BusDisconnect();
m_materialAsset = {}; m_materialAsset = {};
m_materialInstance = {}; m_materialInstance = {};
m_absolutePath.clear();
m_relativePath.clear();
m_sourceDependencies.clear();
m_saveTriggeredInternally = {};
m_compilePending = {}; m_compilePending = {};
m_properties.clear(); m_properties.clear();
m_editorFunctors.clear(); m_editorFunctors.clear();
m_materialTypeSourceData = AZ::RPI::MaterialTypeSourceData(); m_materialTypeSourceData = AZ::RPI::MaterialTypeSourceData();
m_materialSourceData = AZ::RPI::MaterialSourceData(); m_materialSourceData = AZ::RPI::MaterialSourceData();
m_propertyValuesBeforeEdit.clear(); m_propertyValuesBeforeEdit.clear();
m_undoHistory.clear();
m_undoHistoryIndex = {};
} }
void MaterialDocument::RestorePropertyValues(const PropertyValueMap& propertyValues) void MaterialDocument::RestorePropertyValues(const PropertyValueMap& propertyValues)
@ -1040,5 +805,4 @@ namespace MaterialEditor
return result; return result;
} }
} // namespace MaterialEditor } // namespace MaterialEditor

@ -10,7 +10,6 @@
#include <AzCore/Asset/AssetCommon.h> #include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Component/TickBus.h> #include <AzCore/Component/TickBus.h>
#include <AzCore/RTTI/RTTI.h> #include <AzCore/RTTI/RTTI.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <Atom/RPI.Edit/Material/MaterialSourceData.h> #include <Atom/RPI.Edit/Material/MaterialSourceData.h>
#include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h> #include <Atom/RPI.Edit/Material/MaterialTypeSourceData.h>
@ -28,7 +27,6 @@ namespace MaterialEditor
: public AtomToolsFramework::AtomToolsDocument : public AtomToolsFramework::AtomToolsDocument
, public MaterialDocumentRequestBus::Handler , public MaterialDocumentRequestBus::Handler
, private AZ::TickBus::Handler , private AZ::TickBus::Handler
, private AzToolsFramework::AssetSystemBus::Handler
{ {
public: public:
AZ_RTTI(MaterialDocument, "{DBA269AE-892B-415C-8FA1-166B94B0E045}"); AZ_RTTI(MaterialDocument, "{DBA269AE-892B-415C-8FA1-166B94B0E045}");
@ -38,37 +36,26 @@ namespace MaterialEditor
MaterialDocument(); MaterialDocument();
virtual ~MaterialDocument(); virtual ~MaterialDocument();
//////////////////////////////////////////////////////////////////////// // AtomToolsFramework::AtomToolsDocument overrides...
// AtomToolsFramework::AtomToolsDocument
////////////////////////////////////////////////////////////////////////
const AZStd::any& GetPropertyValue(const AZ::Name& propertyId) const override; const AZStd::any& GetPropertyValue(const AZ::Name& propertyId) const override;
const AtomToolsFramework::DynamicProperty& GetProperty(const AZ::Name& propertyId) const override; const AtomToolsFramework::DynamicProperty& GetProperty(const AZ::Name& propertyId) const override;
bool IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const override; bool IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const override;
void SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value) override; void SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value) override;
bool Open(AZStd::string_view loadPath) override; bool Open(AZStd::string_view loadPath) override;
bool Reopen() override;
bool Save() override; bool Save() override;
bool SaveAsCopy(AZStd::string_view savePath) override; bool SaveAsCopy(AZStd::string_view savePath) override;
bool SaveAsChild(AZStd::string_view savePath) override; bool SaveAsChild(AZStd::string_view savePath) override;
bool Close() override;
bool IsOpen() const override; bool IsOpen() const override;
bool IsModified() const override; bool IsModified() const override;
bool IsSavable() const override; bool IsSavable() const override;
bool CanUndo() const override;
bool CanRedo() const override;
bool Undo() override;
bool Redo() override;
bool BeginEdit() override; bool BeginEdit() override;
bool EndEdit() override; bool EndEdit() override;
////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////// // MaterialDocumentRequestBus::Handler overrides...
// MaterialDocumentRequestBus::Handler implementation
AZ::Data::Asset<AZ::RPI::MaterialAsset> GetAsset() const override; AZ::Data::Asset<AZ::RPI::MaterialAsset> GetAsset() const override;
AZ::Data::Instance<AZ::RPI::Material> GetInstance() const override; AZ::Data::Instance<AZ::RPI::Material> GetInstance() const override;
const AZ::RPI::MaterialSourceData* GetMaterialSourceData() const override; const AZ::RPI::MaterialSourceData* GetMaterialSourceData() const override;
const AZ::RPI::MaterialTypeSourceData* GetMaterialTypeSourceData() const override; const AZ::RPI::MaterialTypeSourceData* GetMaterialTypeSourceData() const override;
////////////////////////////////////////////////////////////////////////
private: private:
@ -84,33 +71,18 @@ namespace MaterialEditor
// Map of document's property group visibility flags // Map of document's property group visibility flags
using PropertyGroupVisibilityMap = AZStd::unordered_map<AZ::Name, bool>; using PropertyGroupVisibilityMap = AZStd::unordered_map<AZ::Name, bool>;
// Function to be bound for undo and redo // AZ::TickBus overrides...
using UndoRedoFunction = AZStd::function<void()>;
// A pair of functions, where first is the undo operation and second is the redo operation
using UndoRedoFunctionPair = AZStd::pair<UndoRedoFunction, UndoRedoFunction>;
// Container for all of the active undo and redo functions and state
using UndoRedoHistory = AZStd::vector<UndoRedoFunctionPair>;
////////////////////////////////////////////////////////////////////////
// AZ::TickBus interface implementation
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// AzToolsFramework::AssetSystemBus::Handler overrides...
void SourceFileChanged(AZStd::string relativePath, AZStd::string scanFolder, AZ::Uuid sourceUUID) override;
//////////////////////////////////////////////////////////////////////////
bool SavePropertiesToSourceData( bool SavePropertiesToSourceData(
const AZStd::string& exportPath, AZ::RPI::MaterialSourceData& sourceData, PropertyFilterFunction propertyFilter) const; const AZStd::string& exportPath, AZ::RPI::MaterialSourceData& sourceData, PropertyFilterFunction propertyFilter) const;
bool OpenInternal(AZStd::string_view loadPath); void Clear() override;
void Recompile(); bool ReopenRecordState() override;
bool ReopenRestoreState() override;
void Clear(); void Recompile();
void RestorePropertyValues(const PropertyValueMap& propertyValues); void RestorePropertyValues(const PropertyValueMap& propertyValues);
@ -131,12 +103,6 @@ namespace MaterialEditor
// Material instance being edited // Material instance being edited
AZ::Data::Instance<AZ::RPI::Material> m_materialInstance; AZ::Data::Instance<AZ::RPI::Material> m_materialInstance;
// Set of assets that can trigger a document reload
AZStd::unordered_set<AZStd::string> m_sourceDependencies;
// Track if document saved itself last to skip external modification notification
bool m_saveTriggeredInternally = false;
// If material instance value(s) were modified, do we need to recompile on next tick? // If material instance value(s) were modified, do we need to recompile on next tick?
bool m_compilePending = false; bool m_compilePending = false;
@ -155,19 +121,10 @@ namespace MaterialEditor
// Source data for material // Source data for material
AZ::RPI::MaterialSourceData m_materialSourceData; AZ::RPI::MaterialSourceData m_materialSourceData;
// Variables needed for tracking the undo and redo state of this document
// State of property values prior to an edit, used for restoration during undo // State of property values prior to an edit, used for restoration during undo
PropertyValueMap m_propertyValuesBeforeEdit; PropertyValueMap m_propertyValuesBeforeEdit;
// Container of undo commands // State of property values prior to reopen
UndoRedoHistory m_undoHistory; PropertyValueMap m_propertyValuesBeforeReopen;
// The current position in the undo redo history
int m_undoHistoryIndex = 0;
AZStd::any m_invalidValue;
AtomToolsFramework::DynamicProperty m_invalidProperty;
}; };
} // namespace MaterialEditor } // namespace MaterialEditor

@ -170,11 +170,14 @@ namespace MaterialEditor
{ {
m_lightingPresetAssets[info.m_assetId] = { info.m_assetId, info.m_assetType }; m_lightingPresetAssets[info.m_assetId] = { info.m_assetId, info.m_assetType };
AZ::Data::AssetBus::MultiHandler::BusConnect(info.m_assetId); AZ::Data::AssetBus::MultiHandler::BusConnect(info.m_assetId);
return;
} }
else if (AZ::StringFunc::EndsWith(info.m_relativePath.c_str(), ".modelpreset.azasset"))
if (AZ::StringFunc::EndsWith(info.m_relativePath.c_str(), ".modelpreset.azasset"))
{ {
m_modelPresetAssets[info.m_assetId] = { info.m_assetId, info.m_assetType }; m_modelPresetAssets[info.m_assetId] = { info.m_assetId, info.m_assetType };
AZ::Data::AssetBus::MultiHandler::BusConnect(info.m_assetId); AZ::Data::AssetBus::MultiHandler::BusConnect(info.m_assetId);
return;
} }
}; };
@ -436,24 +439,20 @@ namespace MaterialEditor
auto ReloadLightingAndModelPresets = [this, &assetId](AZ::Data::AssetCatalogRequests* assetCatalogRequests) auto ReloadLightingAndModelPresets = [this, &assetId](AZ::Data::AssetCatalogRequests* assetCatalogRequests)
{ {
AZ::Data::AssetInfo assetInfo = assetCatalogRequests->GetAssetInfoById(assetId); AZ::Data::AssetInfo assetInfo = assetCatalogRequests->GetAssetInfoById(assetId);
AZ::Data::Asset<AZ::RPI::AnyAsset>* modifiedPresetAsset{};
if (AZ::StringFunc::EndsWith(assetInfo.m_relativePath.c_str(), ".lightingpreset.azasset")) if (AZ::StringFunc::EndsWith(assetInfo.m_relativePath.c_str(), ".lightingpreset.azasset"))
{ {
m_lightingPresetAssets[assetInfo.m_assetId] = { assetInfo.m_assetId, assetInfo.m_assetType }; m_lightingPresetAssets[assetInfo.m_assetId] = { assetInfo.m_assetId, assetInfo.m_assetType };
AZ::Data::AssetBus::MultiHandler::BusConnect(assetInfo.m_assetId); AZ::Data::AssetBus::MultiHandler::BusConnect(assetInfo.m_assetId);
modifiedPresetAsset = &m_lightingPresetAssets[assetInfo.m_assetId]; m_lightingPresetAssets[assetInfo.m_assetId].QueueLoad();
return;
} }
else if (AzFramework::StringFunc::EndsWith(assetInfo.m_relativePath.c_str(), ".modelpreset.azasset"))
if (AzFramework::StringFunc::EndsWith(assetInfo.m_relativePath.c_str(), ".modelpreset.azasset"))
{ {
m_modelPresetAssets[assetInfo.m_assetId] = { assetInfo.m_assetId, assetInfo.m_assetType }; m_modelPresetAssets[assetInfo.m_assetId] = { assetInfo.m_assetId, assetInfo.m_assetType };
AZ::Data::AssetBus::MultiHandler::BusConnect(assetInfo.m_assetId); AZ::Data::AssetBus::MultiHandler::BusConnect(assetInfo.m_assetId);
modifiedPresetAsset = &m_modelPresetAssets[assetInfo.m_assetId]; m_modelPresetAssets[assetInfo.m_assetId].QueueLoad();
} return;
// Queue a load on the changed asset
if (modifiedPresetAsset != nullptr)
{
modifiedPresetAsset->QueueLoad();
} }
}; };
AZ::Data::AssetCatalogRequestBus::Broadcast(AZStd::move(ReloadLightingAndModelPresets)); AZ::Data::AssetCatalogRequestBus::Broadcast(AZStd::move(ReloadLightingAndModelPresets));
@ -470,11 +469,14 @@ namespace MaterialEditor
{ {
AZ::Data::AssetBus::MultiHandler::BusDisconnect(assetInfo.m_assetId); AZ::Data::AssetBus::MultiHandler::BusDisconnect(assetInfo.m_assetId);
m_lightingPresetAssets.erase(assetId); m_lightingPresetAssets.erase(assetId);
return;
} }
if (AZ::StringFunc::EndsWith(assetInfo.m_relativePath.c_str(), ".modelpreset.azasset")) if (AZ::StringFunc::EndsWith(assetInfo.m_relativePath.c_str(), ".modelpreset.azasset"))
{ {
AZ::Data::AssetBus::MultiHandler::BusDisconnect(assetInfo.m_assetId); AZ::Data::AssetBus::MultiHandler::BusDisconnect(assetInfo.m_assetId);
m_modelPresetAssets.erase(assetId); m_modelPresetAssets.erase(assetId);
return;
} }
} }
} }

@ -19,8 +19,7 @@
namespace MaterialEditor namespace MaterialEditor
{ {
//! Provides controls for viewing and editing a material document settings. //! Provides controls for viewing and editing document settings.
//! The settings can be divided into cards, with each one showing a subset of properties.
class MaterialInspector class MaterialInspector
: public AtomToolsFramework::InspectorWidget : public AtomToolsFramework::InspectorWidget
, public AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler , public AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler

@ -21,8 +21,7 @@
namespace MaterialEditor namespace MaterialEditor
{ {
//! Provides controls for viewing and editing a material document settings. //! Provides controls for viewing and editing lighting and model preset settings.
//! The settings can be divided into cards, with each one showing a subset of properties.
class ViewportSettingsInspector class ViewportSettingsInspector
: public AtomToolsFramework::InspectorWidget : public AtomToolsFramework::InspectorWidget
, private AzToolsFramework::IPropertyEditorNotify , private AzToolsFramework::IPropertyEditorNotify

@ -10,8 +10,6 @@
#include <Atom/RPI.Reflect/Asset/AssetUtils.h> #include <Atom/RPI.Reflect/Asset/AssetUtils.h>
#include <AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h> #include <AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h>
#include <AzFramework/StringFunc/StringFunc.h> #include <AzFramework/StringFunc/StringFunc.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
#include <Document/ShaderManagementConsoleDocument.h> #include <Document/ShaderManagementConsoleDocument.h>
namespace ShaderManagementConsole namespace ShaderManagementConsole
@ -20,28 +18,32 @@ namespace ShaderManagementConsole
: AtomToolsFramework::AtomToolsDocument() : AtomToolsFramework::AtomToolsDocument()
{ {
ShaderManagementConsoleDocumentRequestBus::Handler::BusConnect(m_id); ShaderManagementConsoleDocumentRequestBus::Handler::BusConnect(m_id);
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentCreated, m_id);
} }
ShaderManagementConsoleDocument::~ShaderManagementConsoleDocument() ShaderManagementConsoleDocument::~ShaderManagementConsoleDocument()
{ {
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentDestroyed, m_id);
ShaderManagementConsoleDocumentRequestBus::Handler::BusDisconnect(); ShaderManagementConsoleDocumentRequestBus::Handler::BusDisconnect();
Clear();
} }
size_t ShaderManagementConsoleDocument::GetShaderOptionCount() const void ShaderManagementConsoleDocument::SetShaderVariantListSourceData(const AZ::RPI::ShaderVariantListSourceData& sourceData)
{ {
auto layout = m_shaderAsset->GetShaderOptionGroupLayout(); m_shaderVariantListSourceData = sourceData;
auto& shaderOptionDescriptors = layout->GetShaderOptions(); AZStd::string shaderPath = m_shaderVariantListSourceData.m_shaderFilePath;
return shaderOptionDescriptors.size(); AzFramework::StringFunc::Path::ReplaceExtension(shaderPath, AZ::RPI::ShaderAsset::Extension);
m_shaderAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath<AZ::RPI::ShaderAsset>(shaderPath.c_str());
if (!m_shaderAsset)
{
AZ_Error("ShaderManagementConsoleDocument", false, "Could not load shader asset: %s.", shaderPath.c_str());
}
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(
&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentModified, m_id);
} }
const AZ::RPI::ShaderOptionDescriptor& ShaderManagementConsoleDocument::GetShaderOptionDescriptor(size_t index) const const AZ::RPI::ShaderVariantListSourceData& ShaderManagementConsoleDocument::GetShaderVariantListSourceData() const
{ {
auto layout = m_shaderAsset->GetShaderOptionGroupLayout(); return m_shaderVariantListSourceData;
auto& shaderOptionDescriptors = layout->GetShaderOptions();
return shaderOptionDescriptors[index];
} }
size_t ShaderManagementConsoleDocument::GetShaderVariantCount() const size_t ShaderManagementConsoleDocument::GetShaderVariantCount() const
@ -54,92 +56,115 @@ namespace ShaderManagementConsole
return m_shaderVariantListSourceData.m_shaderVariants[index]; return m_shaderVariantListSourceData.m_shaderVariants[index];
} }
bool ShaderManagementConsoleDocument::Open(AZStd::string_view loadPath) size_t ShaderManagementConsoleDocument::GetShaderOptionCount() const
{ {
Clear(); if (IsOpen())
{
const auto& layout = m_shaderAsset->GetShaderOptionGroupLayout();
const auto& shaderOptionDescriptors = layout->GetShaderOptions();
return shaderOptionDescriptors.size();
}
return 0;
}
m_absolutePath = loadPath; const AZ::RPI::ShaderOptionDescriptor& ShaderManagementConsoleDocument::GetShaderOptionDescriptor(size_t index) const
if (!AzFramework::StringFunc::Path::Normalize(m_absolutePath)) {
if (IsOpen())
{ {
AZ_Error("ShaderManagementConsoleDocument", false, "Document path could not be normalized: '%s'.", m_absolutePath.c_str()); const auto& layout = m_shaderAsset->GetShaderOptionGroupLayout();
return false; const auto& shaderOptionDescriptors = layout->GetShaderOptions();
return shaderOptionDescriptors.at(index);
} }
return m_invalidDescriptor;
}
if (AzFramework::StringFunc::Path::IsRelative(m_absolutePath.c_str())) bool ShaderManagementConsoleDocument::Open(AZStd::string_view loadPath)
{
if (!AtomToolsDocument::Open(loadPath))
{ {
AZ_Error("ShaderManagementConsoleDocument", false, "Document path must be absolute: '%s'.", m_absolutePath.c_str());
return false; return false;
} }
if (AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), AZ::RPI::ShaderVariantListSourceData::Extension)) if (!AzFramework::StringFunc::Path::IsExtension(m_absolutePath.c_str(), AZ::RPI::ShaderVariantListSourceData::Extension))
{ {
// Load the shader config data and create a shader config asset from it AZ_Error("ShaderManagementConsoleDocument", false, "Document extension is not supported: '%s.'", m_absolutePath.c_str());
if (!AZ::RPI::JsonUtils::LoadObjectFromFile(m_absolutePath, m_shaderVariantListSourceData)) return OpenFailed();
{
AZ_Error("ShaderManagementConsoleDocument", false, "Failed loading shader variant list data: '%s.'", m_absolutePath.c_str());
return false;
}
} }
bool result = false; // Load the shader config data and create a shader config asset from it
AZ::Data::AssetInfo sourceAssetInfo; AZ::RPI::ShaderVariantListSourceData sourceData;
AZStd::string watchFolder; if (!AZ::RPI::JsonUtils::LoadObjectFromFile(m_absolutePath, sourceData))
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
result, &AzToolsFramework::AssetSystem::AssetSystemRequest::GetSourceInfoBySourcePath, m_absolutePath.c_str(), sourceAssetInfo,
watchFolder);
if (!result)
{ {
AZ_Error("ShaderManagementConsoleDocument", false, "Could not find source data: '%s'.", m_absolutePath.c_str()); AZ_Error("ShaderManagementConsoleDocument", false, "Failed loading shader variant list data: '%s.'", m_absolutePath.c_str());
return false; return OpenFailed();
} }
m_relativePath = m_shaderVariantListSourceData.m_shaderFilePath; SetShaderVariantListSourceData(sourceData);
if (!AzFramework::StringFunc::Path::Normalize(m_relativePath)) return IsOpen() ? OpenSucceeded() : OpenFailed();
}
bool ShaderManagementConsoleDocument::Save()
{
if (!AtomToolsDocument::Save())
{ {
AZ_Error("ShaderManagementConsoleDocument", false, "Shader path could not be normalized: '%s'.", m_relativePath.c_str());
return false; return false;
} }
AZStd::string shaderPath = m_relativePath; return SaveSourceData();
AzFramework::StringFunc::Path::ReplaceExtension(shaderPath, AZ::RPI::ShaderAsset::Extension); }
m_shaderAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath<AZ::RPI::ShaderAsset>(shaderPath.c_str()); bool ShaderManagementConsoleDocument::SaveAsCopy(AZStd::string_view savePath)
if (!m_shaderAsset) {
if (!AtomToolsDocument::SaveAsCopy(savePath))
{ {
AZ_Error("ShaderManagementConsoleDocument", false, "Could not load shader asset: %s.", shaderPath.c_str());
return false; return false;
} }
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentOpened, m_id); return SaveSourceData();
AZ_TracePrintf("ShaderManagementConsoleDocument", "Document opened: '%s'\n", m_absolutePath.c_str());
return true;
} }
bool ShaderManagementConsoleDocument::Close() bool ShaderManagementConsoleDocument::SaveAsChild(AZStd::string_view savePath)
{ {
if (!IsOpen()) if (!AtomToolsDocument::SaveAsChild(savePath))
{ {
AZ_Error("ShaderManagementConsoleDocument", false, "Document is not open");
return false; return false;
} }
Clear(); return SaveSourceData();
AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast(&AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentClosed, m_id);
AZ_TracePrintf("ShaderManagementConsoleDocument", "Document closed\n");
return true;
} }
bool ShaderManagementConsoleDocument::IsOpen() const bool ShaderManagementConsoleDocument::IsOpen() const
{ {
return !m_absolutePath.empty() && !m_relativePath.empty(); return AtomToolsDocument::IsOpen() && m_shaderAsset.IsReady();
}
bool ShaderManagementConsoleDocument::IsModified() const
{
return false;
}
bool ShaderManagementConsoleDocument::IsSavable() const
{
return true;
} }
void ShaderManagementConsoleDocument::Clear() void ShaderManagementConsoleDocument::Clear()
{ {
m_absolutePath.clear(); AtomToolsFramework::AtomToolsDocument::Clear();
m_relativePath.clear();
m_shaderVariantListSourceData = {}; m_shaderVariantListSourceData = {};
m_shaderAsset = {}; m_shaderAsset = {};
} }
bool ShaderManagementConsoleDocument::SaveSourceData()
{
if (!AZ::RPI::JsonUtils::SaveObjectToFile(m_savePathNormalized, m_shaderVariantListSourceData))
{
AZ_Error("ShaderManagementConsoleDocument", false, "Document could not be saved: '%s'.", m_savePathNormalized.c_str());
return SaveFailed();
}
m_absolutePath = m_savePathNormalized;
return SaveSucceeded();
}
} // namespace ShaderManagementConsole } // namespace ShaderManagementConsole

@ -29,40 +29,35 @@ namespace ShaderManagementConsole
AZ_DISABLE_COPY(ShaderManagementConsoleDocument); AZ_DISABLE_COPY(ShaderManagementConsoleDocument);
ShaderManagementConsoleDocument(); ShaderManagementConsoleDocument();
virtual ~ShaderManagementConsoleDocument(); ~ShaderManagementConsoleDocument();
//////////////////////////////////////////////////////////////////////// // AtomToolsFramework::AtomToolsDocument overrides...
// AtomToolsFramework::AtomToolsDocument
////////////////////////////////////////////////////////////////////////
bool Open(AZStd::string_view loadPath) override; bool Open(AZStd::string_view loadPath) override;
bool Close() override; bool Save() override;
bool SaveAsCopy(AZStd::string_view savePath) override;
bool SaveAsChild(AZStd::string_view savePath) override;
bool IsOpen() const override; bool IsOpen() const override;
//////////////////////////////////////////////////////////////////////// bool IsModified() const override;
bool IsSavable() const override;
//////////////////////////////////////////////////////////////////////// // ShaderManagementConsoleDocumentRequestBus::Handler overridfes...
// ShaderManagementConsoleDocumentRequestBus::Handler implementation void SetShaderVariantListSourceData(const AZ::RPI::ShaderVariantListSourceData& sourceData) override;
size_t GetShaderOptionCount() const override; const AZ::RPI::ShaderVariantListSourceData& GetShaderVariantListSourceData() const override;
const AZ::RPI::ShaderOptionDescriptor& GetShaderOptionDescriptor(size_t index) const override;
size_t GetShaderVariantCount() const override; size_t GetShaderVariantCount() const override;
const AZ::RPI::ShaderVariantListSourceData::VariantInfo& GetShaderVariantInfo(size_t index) const override; const AZ::RPI::ShaderVariantListSourceData::VariantInfo& GetShaderVariantInfo(size_t index) const override;
//////////////////////////////////////////////////////////////////////// size_t GetShaderOptionCount() const override;
const AZ::RPI::ShaderOptionDescriptor& GetShaderOptionDescriptor(size_t index) const override;
private: private:
// Function to be bound for undo and redo void Clear() override;
using UndoRedoFunction = AZStd::function<void()>; bool SaveSourceData();
// A pair of functions, where first is the undo operation and second is the redo operation
using UndoRedoFunctionPair = AZStd::pair<UndoRedoFunction, UndoRedoFunction>;
// Container for all of the active undo and redo functions and state
using UndoRedoHistory = AZStd::vector<UndoRedoFunctionPair>;
void Clear();
// Source data for shader variant list // Source data for shader variant list
AZ::RPI::ShaderVariantListSourceData m_shaderVariantListSourceData; AZ::RPI::ShaderVariantListSourceData m_shaderVariantListSourceData;
// Shader asset for the corresponding shader variant list // Shader asset for the corresponding shader variant list
AZ::Data::Asset<AZ::RPI::ShaderAsset> m_shaderAsset; AZ::Data::Asset<AZ::RPI::ShaderAsset> m_shaderAsset;
const AZ::RPI::ShaderOptionDescriptor m_invalidDescriptor;
}; };
} // namespace ShaderManagementConsole } // namespace ShaderManagementConsole

@ -23,17 +23,23 @@ namespace ShaderManagementConsole
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
typedef AZ::Uuid BusIdType; typedef AZ::Uuid BusIdType;
//! Get the number of options //! Set the shader variant list
virtual size_t GetShaderOptionCount() const = 0; virtual void SetShaderVariantListSourceData(const AZ::RPI::ShaderVariantListSourceData& sourceData) = 0;
//! Get the descriptor for the shader option at the specified index //! Get the shader variant list
virtual const AZ::RPI::ShaderOptionDescriptor& GetShaderOptionDescriptor(size_t index) const = 0; virtual const AZ::RPI::ShaderVariantListSourceData& GetShaderVariantListSourceData() const = 0;
//! Get the number of shader variants //! Get the number of shader variants
virtual size_t GetShaderVariantCount() const = 0; virtual size_t GetShaderVariantCount() const = 0;
//! Get the information for the shader variant at the specified index //! Get the information for the shader variant at the specified index
virtual const AZ::RPI::ShaderVariantListSourceData::VariantInfo& GetShaderVariantInfo(size_t index) const = 0; virtual const AZ::RPI::ShaderVariantListSourceData::VariantInfo& GetShaderVariantInfo(size_t index) const = 0;
//! Get the number of options
virtual size_t GetShaderOptionCount() const = 0;
//! Get the descriptor for the shader option at the specified index
virtual const AZ::RPI::ShaderOptionDescriptor& GetShaderOptionDescriptor(size_t index) const = 0;
}; };
using ShaderManagementConsoleDocumentRequestBus = AZ::EBus<ShaderManagementConsoleDocumentRequests>; using ShaderManagementConsoleDocumentRequestBus = AZ::EBus<ShaderManagementConsoleDocumentRequests>;

@ -84,6 +84,8 @@ namespace ShaderManagementConsole
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Category, "Editor") ->Attribute(AZ::Script::Attributes::Category, "Editor")
->Attribute(AZ::Script::Attributes::Module, "shadermanagementconsole") ->Attribute(AZ::Script::Attributes::Module, "shadermanagementconsole")
->Event("SetShaderVariantListSourceData", &ShaderManagementConsoleDocumentRequestBus::Events::SetShaderVariantListSourceData)
->Event("GetShaderVariantListSourceData", &ShaderManagementConsoleDocumentRequestBus::Events::GetShaderVariantListSourceData)
->Event("GetShaderOptionCount", &ShaderManagementConsoleDocumentRequestBus::Events::GetShaderOptionCount) ->Event("GetShaderOptionCount", &ShaderManagementConsoleDocumentRequestBus::Events::GetShaderOptionCount)
->Event("GetShaderOptionDescriptor", &ShaderManagementConsoleDocumentRequestBus::Events::GetShaderOptionDescriptor) ->Event("GetShaderOptionDescriptor", &ShaderManagementConsoleDocumentRequestBus::Events::GetShaderOptionDescriptor)
->Event("GetShaderVariantCount", &ShaderManagementConsoleDocumentRequestBus::Events::GetShaderVariantCount) ->Event("GetShaderVariantCount", &ShaderManagementConsoleDocumentRequestBus::Events::GetShaderVariantCount)

@ -61,8 +61,6 @@ namespace ShaderManagementConsole
m_actionNew->setEnabled(false); m_actionNew->setEnabled(false);
m_actionSaveAsChild->setVisible(false); m_actionSaveAsChild->setVisible(false);
m_actionSaveAsChild->setEnabled(false); m_actionSaveAsChild->setEnabled(false);
m_actionSaveAll->setVisible(false);
m_actionSaveAll->setEnabled(false);
OnDocumentOpened(AZ::Uuid::CreateNull()); OnDocumentOpened(AZ::Uuid::CreateNull());
} }

Loading…
Cancel
Save