From c9c172794ef08aa007f60f5ff80acdbd7666ec58 Mon Sep 17 00:00:00 2001 From: Guthrie Adams Date: Thu, 3 Feb 2022 20:15:40 -0600 Subject: [PATCH] =?UTF-8?q?Atom=20Tools:=20Document=20system=20exposes=20r?= =?UTF-8?q?eflected=20object=20data=20=E2=80=A2=20This=20removes=20a=20dir?= =?UTF-8?q?ect=20dependency=20on=20dynamic=20property=20groups=20and=20dat?= =?UTF-8?q?a=20from=20the=20document=20system.=20=E2=80=A2=20Added=20suppo?= =?UTF-8?q?rt=20for=20names,=20descriptions,=20and=20nesting=20to=20dynami?= =?UTF-8?q?c=20property=20groups.=20=E2=80=A2=20Moved=20property=20related?= =?UTF-8?q?=20functions=20from=20base=20document=20classes=20into=20materi?= =?UTF-8?q?al=20editor=20document=20classes=20because=20dynamic=20property?= =?UTF-8?q?=20groups=20are=20an=20implementation=20detail=20of=20the=20mat?= =?UTF-8?q?erial=20editor=20document=20to=20support=20material=20type=20fl?= =?UTF-8?q?exible=20data=20format.=20=E2=80=A2=20Change=20material=20docum?= =?UTF-8?q?ent=20to=20use=20a=20table=20of=20dynamic=20property=20groups?= =?UTF-8?q?=20instead=20of=20a=20map=20of=20properties.=20=E2=80=A2=20Adde?= =?UTF-8?q?d=20functions=20to=20traverse=20groups=20and=20properties.=20?= =?UTF-8?q?=E2=80=A2=20This=20keeps=20groups=20and=20properties=20organize?= =?UTF-8?q?d=20consistently=20with=20the=20material=20type=20file=20as=20w?= =?UTF-8?q?ell=20as=20what=E2=80=99s=20expected=20in=20the=20UI.=20?= =?UTF-8?q?=E2=80=A2=20Document=20data=20can=20now=20be=20maps=20directly?= =?UTF-8?q?=20to=20the=20inspector=20reflective=20property=20editors=20ins?= =?UTF-8?q?tead=20of=20copying=20one=20property=20at=20a=20time=20out=20of?= =?UTF-8?q?=20the=20document=20and=20keeping=20those=20synchronized.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Guthrie Adams --- .../Atom/atom_utils/material_editor_utils.py | 4 +- .../Document/AtomToolsDocument.h | 9 +- .../AtomToolsDocumentNotificationBus.h | 23 +- .../Document/AtomToolsDocumentRequestBus.h | 30 +- .../AtomToolsDocumentSystemRequestBus.h | 4 +- .../DynamicProperty/DynamicProperty.h | 4 +- .../DynamicProperty/DynamicPropertyGroup.h | 6 + .../Source/Document/AtomToolsDocument.cpp | 32 +- .../AtomToolsDocumentSystemComponent.cpp | 2 - .../DynamicProperty/DynamicProperty.cpp | 10 +- .../DynamicProperty/DynamicPropertyGroup.cpp | 8 + .../Code/Source/Document/MaterialDocument.cpp | 656 +++++++++++------- .../Code/Source/Document/MaterialDocument.h | 69 +- .../Document/MaterialDocumentRequestBus.h | 9 +- .../Code/Source/MaterialEditorApplication.cpp | 3 +- .../MaterialInspector/MaterialInspector.cpp | 227 ++---- .../MaterialInspector/MaterialInspector.h | 18 +- .../ShaderManagementConsoleDocument.cpp | 22 + .../ShaderManagementConsoleDocument.h | 1 + 19 files changed, 565 insertions(+), 572 deletions(-) diff --git a/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/material_editor_utils.py b/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/material_editor_utils.py index 96eeb4279e..c3c1c76611 100644 --- a/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/material_editor_utils.py +++ b/AutomatedTesting/Gem/PythonTests/Atom/atom_utils/material_editor_utils.py @@ -114,11 +114,11 @@ def get_property(document_id, property_name): """ :return: property value or invalid value if the document is not open or the property_name can't be found """ - return azlmbr.atomtools.AtomToolsDocumentRequestBus(bus.Event, "GetPropertyValue", document_id, property_name) + return azlmbr.materialeditor.MaterialDocumentRequestBus(bus.Event, "GetPropertyValue", document_id, property_name) def set_property(document_id, property_name, value): - azlmbr.atomtools.AtomToolsDocumentRequestBus(bus.Event, "SetPropertyValue", document_id, property_name, value) + azlmbr.materialeditor.MaterialDocumentRequestBus(bus.Event, "SetPropertyValue", document_id, property_name, value) def is_pane_visible(pane_name): diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Document/AtomToolsDocument.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Document/AtomToolsDocument.h index cb64d23cd0..2c9497df8e 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Document/AtomToolsDocument.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Document/AtomToolsDocument.h @@ -32,10 +32,7 @@ namespace AtomToolsFramework // AtomToolsDocumentRequestBus::Handler overrides... AZStd::string_view GetAbsolutePath() const override; - const AZStd::any& GetPropertyValue(const AZ::Name& propertyId) const override; - const AtomToolsFramework::DynamicProperty& GetProperty(const AZ::Name& propertyId) const override; - bool IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const override; - void SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value) override; + AZStd::vector GetObjectInfo() const override; bool Open(AZStd::string_view loadPath) override; bool Reopen() override; bool Save() override; @@ -78,10 +75,6 @@ namespace AtomToolsFramework //! The normalized, absolute path where the document will be saved. AZStd::string m_savePathNormalized; - AZStd::any m_invalidValue; - - AtomToolsFramework::DynamicProperty m_invalidProperty; - //! This contains absolute paths of other source files that affect this document. //! If any of the source files in this container are modified, the document system is notified to reload this document. AZStd::unordered_set m_sourceDependencies; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h index 9c3a536333..b9af219b9e 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h @@ -10,11 +10,10 @@ #include #include -#include -#include - namespace AtomToolsFramework { + struct DocumentObjectInfo; + class AtomToolsDocumentNotifications : public AZ::EBusTraits { @@ -61,22 +60,12 @@ namespace AtomToolsFramework //! Signal that a document undo state was updated //! @param documentId unique id of document for which the notification is sent virtual void OnDocumentUndoStateChanged([[maybe_unused]] const AZ::Uuid& documentId) {} - - //! Signal that a property changed - //! @param documentId unique id of document for which the notification is sent - //! @param property object containing the property value and configuration that was modified - virtual void OnDocumentPropertyValueModified([[maybe_unused]] const AZ::Uuid& documentId, [[maybe_unused]] const AtomToolsFramework::DynamicProperty& property) {} - - //! Signal that the property configuration has been changed. - //! @param documentId unique id of document for which the notification is sent - //! @param property object containing the property value and configuration that was modified - virtual void OnDocumentPropertyConfigModified([[maybe_unused]] const AZ::Uuid& documentId, [[maybe_unused]] const AtomToolsFramework::DynamicProperty& property) {} - //! Signal that the property group visibility has been changed. + //! Signal that the group has been changed. //! @param documentId unique id of document for which the notification is sent - //! @param groupId id of the group that changed - //! @param visible whether the property group is visible - virtual void OnDocumentPropertyGroupVisibilityChanged([[maybe_unused]] const AZ::Uuid& documentId, [[maybe_unused]] const AZ::Name& groupId, [[maybe_unused]] bool visible) {} + //! @param objectInfo description of the reflected object that's been modified + //! @param rebuilt signifies if it was a structural change that might require ui to be rebuilt + virtual void OnDocumentObjectInfoChanged([[maybe_unused]] const AZ::Uuid& documentId, [[maybe_unused]] const DocumentObjectInfo& objectInfo, [[maybe_unused]] bool rebuilt) {} }; using AtomToolsDocumentNotificationBus = AZ::EBus; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Document/AtomToolsDocumentRequestBus.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Document/AtomToolsDocumentRequestBus.h index b6b3f65110..29cd8bede7 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Document/AtomToolsDocumentRequestBus.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Document/AtomToolsDocumentRequestBus.h @@ -14,6 +14,20 @@ namespace AtomToolsFramework { + //! Structure used as an opaque description of objects reflected inside of a document. + //! An example use would be semi automatic handling of serialization or reflection to a property editor. + struct DocumentObjectInfo + { + bool m_visible = true; + AZStd::string m_name; + AZStd::string m_displayName; + AZStd::string m_description; + AZ::Uuid m_objectType = AZ::Uuid::CreateNull(); + void* m_objectPtr = {}; + }; + + //! This bus provides the most basic interface for implementing a document that works with the document system. + //! Any extensions or application specific functionality should be added using a domain specific buses. class AtomToolsDocumentRequests : public AZ::EBusTraits { @@ -25,20 +39,8 @@ namespace AtomToolsFramework //! Get absolute path of document virtual AZStd::string_view GetAbsolutePath() const = 0; - //! Return property value - //! 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; - - //! Returns a property object - //! If the document is not open or the id can't be found, an invalid property is returned. - virtual const AtomToolsFramework::DynamicProperty& GetProperty(const AZ::Name& propertyFullName) const = 0; - - //! Returns whether a property group is visible - //! If the document is not open or the id can't be found, returns false. - virtual bool IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const = 0; - - //! Modify document property value - virtual void SetPropertyValue(const AZ::Name& propertyFullName, const AZStd::any& value) = 0; + //! Returns a container describing all reflected objects contained in a document + virtual AZStd::vector GetObjectInfo() const = 0; //! Load document and related data //! @param loadPath absolute path of document to load diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Document/AtomToolsDocumentSystemRequestBus.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Document/AtomToolsDocumentSystemRequestBus.h index b12cf5e580..88b5acd3be 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Document/AtomToolsDocumentSystemRequestBus.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/Document/AtomToolsDocumentSystemRequestBus.h @@ -25,11 +25,11 @@ namespace AtomToolsFramework //! Register a document factory function used to create specific document types virtual void RegisterDocumentType(AZStd::function documentCreator) = 0; - //! Create a document object + //! Create a document //! @return Uuid of new document, or null Uuid if failed virtual AZ::Uuid CreateDocument() = 0; - //! Destroy a document object with the specified id + //! Destroy a document with the specified id //! @return true if Uuid was found and removed, otherwise false virtual bool DestroyDocument(const AZ::Uuid& documentId) = 0; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/DynamicProperty/DynamicProperty.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/DynamicProperty/DynamicProperty.h index 5af0fcc845..2612b3be1b 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/DynamicProperty/DynamicProperty.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/DynamicProperty/DynamicProperty.h @@ -46,6 +46,7 @@ namespace AtomToolsFramework AZStd::string m_name; AZStd::string m_displayName; AZStd::string m_groupName; + AZStd::string m_groupDisplayName; AZStd::string m_description; AZStd::any m_defaultValue; AZStd::any m_parentValue; @@ -60,6 +61,7 @@ namespace AtomToolsFramework bool m_visible = true; bool m_readOnly = false; bool m_showThumbnail = false; + AZStd::function m_dataChangeCallback; }; //! Wraps an AZStd::any value and configuration so that it can be displayed and edited in a ReflectedPropertyEditor. @@ -106,7 +108,7 @@ namespace AtomToolsFramework private: // Functions used to configure edit data attributes. AZStd::string GetDisplayName() const; - AZStd::string GetGroupName() const; + AZStd::string GetGroupDisplayName() const; AZStd::string GetAssetPickerTitle() const; AZStd::string GetDescription() const; AZStd::vector> GetEnumValues() const; diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/DynamicProperty/DynamicPropertyGroup.h b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/DynamicProperty/DynamicPropertyGroup.h index 91705f3428..7d5e25b95f 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/DynamicProperty/DynamicPropertyGroup.h +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Include/AtomToolsFramework/DynamicProperty/DynamicPropertyGroup.h @@ -9,6 +9,7 @@ #pragma once #include +#include #include namespace AtomToolsFramework @@ -22,6 +23,11 @@ namespace AtomToolsFramework static void Reflect(AZ::ReflectContext* context); + bool m_visible = true; + AZStd::string m_name; + AZStd::string m_displayName; + AZStd::string m_description; AZStd::vector m_properties; + AZStd::vector> m_groups; }; } // namespace AtomToolsFramework diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocument.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocument.cpp index 84638eefc4..2660a1e8fb 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocument.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocument.cpp @@ -36,32 +36,10 @@ namespace AtomToolsFramework return m_absolutePath; } - const AZStd::any& AtomToolsDocument::GetPropertyValue([[maybe_unused]] const AZ::Name& propertyId) const + AZStd::vector AtomToolsDocument::GetObjectInfo() const { - AZ_UNUSED(propertyId); - AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); - return m_invalidValue; - } - - const AtomToolsFramework::DynamicProperty& AtomToolsDocument::GetProperty([[maybe_unused]] const AZ::Name& propertyId) const - { - AZ_UNUSED(propertyId); - AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); - return m_invalidProperty; - } - - bool AtomToolsDocument::IsPropertyGroupVisible([[maybe_unused]] const AZ::Name& propertyGroupFullName) const - { - AZ_UNUSED(propertyGroupFullName); - AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); - return false; - } - - void AtomToolsDocument::SetPropertyValue([[maybe_unused]] const AZ::Name& propertyId, [[maybe_unused]] const AZStd::any& value) - { - AZ_UNUSED(propertyId); - AZ_UNUSED(value); - AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); + AZ_Warning("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); + return AZStd::vector(); } bool AtomToolsDocument::Open(AZStd::string_view loadPath) @@ -256,13 +234,13 @@ namespace AtomToolsFramework bool AtomToolsDocument::BeginEdit() { - AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); + AZ_Warning("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); return false; } bool AtomToolsDocument::EndEdit() { - AZ_Error("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); + AZ_Warning("AtomToolsDocument", false, "%s not implemented.", __FUNCTION__); return false; } diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocumentSystemComponent.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocumentSystemComponent.cpp index b5a3f5322c..c05bb56391 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocumentSystemComponent.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/Document/AtomToolsDocumentSystemComponent.cpp @@ -77,8 +77,6 @@ namespace AtomToolsFramework ->Attribute(AZ::Script::Attributes::Category, "Editor") ->Attribute(AZ::Script::Attributes::Module, "atomtools") ->Event("GetAbsolutePath", &AtomToolsDocumentRequestBus::Events::GetAbsolutePath) - ->Event("GetPropertyValue", &AtomToolsDocumentRequestBus::Events::GetPropertyValue) - ->Event("SetPropertyValue", &AtomToolsDocumentRequestBus::Events::SetPropertyValue) ->Event("Open", &AtomToolsDocumentRequestBus::Events::Open) ->Event("Reopen", &AtomToolsDocumentRequestBus::Events::Reopen) ->Event("Close", &AtomToolsDocumentRequestBus::Events::Close) diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/DynamicProperty/DynamicProperty.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/DynamicProperty/DynamicProperty.cpp index 830f71933c..bd83fef320 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/DynamicProperty/DynamicProperty.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/DynamicProperty/DynamicProperty.cpp @@ -195,14 +195,14 @@ namespace AtomToolsFramework return !m_config.m_displayName.empty() ? m_config.m_displayName : m_config.m_name; } - AZStd::string DynamicProperty::GetGroupName() const + AZStd::string DynamicProperty::GetGroupDisplayName() const { - return m_config.m_groupName; + return m_config.m_groupDisplayName; } AZStd::string DynamicProperty::GetAssetPickerTitle() const { - return GetGroupName().empty() ? GetDisplayName() : GetGroupName() + " " + GetDisplayName(); + return GetGroupDisplayName().empty() ? GetDisplayName() : GetGroupDisplayName() + " " + GetDisplayName(); } AZStd::string DynamicProperty::GetDescription() const @@ -235,6 +235,10 @@ namespace AtomToolsFramework AZ::u32 DynamicProperty::OnDataChanged() const { + if (m_config.m_dataChangeCallback) + { + return m_config.m_dataChangeCallback(GetValue()); + } return AZ::Edit::PropertyRefreshLevels::AttributesAndValues; } diff --git a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/DynamicProperty/DynamicPropertyGroup.cpp b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/DynamicProperty/DynamicPropertyGroup.cpp index 41e96976e6..2647eb180a 100644 --- a/Gems/Atom/Tools/AtomToolsFramework/Code/Source/DynamicProperty/DynamicPropertyGroup.cpp +++ b/Gems/Atom/Tools/AtomToolsFramework/Code/Source/DynamicProperty/DynamicPropertyGroup.cpp @@ -17,7 +17,12 @@ namespace AtomToolsFramework if (auto serializeContext = azrtti_cast(context)) { serializeContext->Class() + ->Field("visible", &DynamicPropertyGroup::m_visible) + ->Field("name", &DynamicPropertyGroup::m_name) + ->Field("displayName", &DynamicPropertyGroup::m_displayName) + ->Field("description", &DynamicPropertyGroup::m_description) ->Field("properties", &DynamicPropertyGroup::m_properties) + ->Field("groups", &DynamicPropertyGroup::m_groups) ; if (auto editContext = serializeContext->GetEditContext()) @@ -28,6 +33,9 @@ namespace AtomToolsFramework ->DataElement(AZ::Edit::UIHandlers::Default, &DynamicPropertyGroup::m_properties, "properties", "") ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) // hides the m_properties row ->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false) // probably not necessary since Visibility is children-only + ->DataElement(AZ::Edit::UIHandlers::Default, &DynamicPropertyGroup::m_groups, "groups", "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) // hides the m_groups row + ->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false) // probably not necessary since Visibility is children-only ; } } diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp index 283b7a8a7f..2c1ff5cad3 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.cpp @@ -53,112 +53,93 @@ namespace MaterialEditor return &m_materialTypeSourceData; } - const AZStd::any& MaterialDocument::GetPropertyValue(const AZ::Name& propertyId) const + void MaterialDocument::SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value) { if (!IsOpen()) { AZ_Error("MaterialDocument", false, "Document is not open."); - return m_invalidValue; + return; } - const auto it = m_properties.find(propertyId); - if (it == m_properties.end()) - { - AZ_Error("MaterialDocument", false, "Document property could not be found: '%s'.", propertyId.GetCStr()); - return m_invalidValue; - } + AtomToolsFramework::DynamicProperty* foundProperty = {}; + TraverseGroups(m_groups, [&, this](auto& group) { + for (auto& property : group->m_properties) + { + if (property.GetId() == propertyId) + { + foundProperty = &property; - const AtomToolsFramework::DynamicProperty& property = it->second; - return property.GetValue(); - } + // This first converts to an acceptable runtime type in case the value came from script + const AZ::RPI::MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(value); - const AtomToolsFramework::DynamicProperty& MaterialDocument::GetProperty(const AZ::Name& propertyId) const - { - if (!IsOpen()) - { - AZ_Error("MaterialDocument", false, "Document is not open."); - return m_invalidProperty; - } + property.SetValue(AtomToolsFramework::ConvertToEditableType(propertyValue)); + + const auto propertyIndex = m_materialInstance->FindPropertyIndex(propertyId); + if (!propertyIndex.IsNull()) + { + if (m_materialInstance->SetPropertyValue(propertyIndex, propertyValue)) + { + AZ::RPI::MaterialPropertyFlags dirtyFlags = m_materialInstance->GetPropertyDirtyFlags(); - const auto it = m_properties.find(propertyId); - if (it == m_properties.end()) + Recompile(); + RunEditorMaterialFunctors(dirtyFlags); + } + } + + AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast( + &AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentObjectInfoChanged, m_id, + GetObjectInfoFromDynamicPropertyGroup(group.get()), false); + + AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast( + &AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentModified, m_id); + return false; + } + } + return true; + }); + + if (!foundProperty) { AZ_Error("MaterialDocument", false, "Document property could not be found: '%s'.", propertyId.GetCStr()); - return m_invalidProperty; } - - const AtomToolsFramework::DynamicProperty& property = it->second; - return property; } - - bool MaterialDocument::IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const + + const AZStd::any& MaterialDocument::GetPropertyValue(const AZ::Name& propertyId) const { if (!IsOpen()) { AZ_Error("MaterialDocument", false, "Document is not open."); - return false; + return m_invalidValue; } - const auto it = m_propertyGroupVisibility.find(propertyGroupFullName); - if (it == m_propertyGroupVisibility.end()) + auto property = FindProperty(propertyId); + if (!property) { - AZ_Error("MaterialDocument", false, "Document property group could not be found: '%s'.", propertyGroupFullName.GetCStr()); - return false; + AZ_Error("MaterialDocument", false, "Document property could not be found: '%s'.", propertyId.GetCStr()); + return m_invalidValue; } - return it->second; + return property->GetValue(); } - void MaterialDocument::SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value) + AZStd::vector MaterialDocument::GetObjectInfo() const { if (!IsOpen()) { AZ_Error("MaterialDocument", false, "Document is not open."); - return; + return {}; } - const auto it = m_properties.find(propertyId); - if (it == m_properties.end()) - { - AZ_Error("MaterialDocument", false, "Document property could not be found: '%s'.", propertyId.GetCStr()); - return; - } - - // This first converts to an acceptable runtime type in case the value came from script - const AZ::RPI::MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(value); - - AtomToolsFramework::DynamicProperty& property = it->second; - property.SetValue(AtomToolsFramework::ConvertToEditableType(propertyValue)); + AZStd::vector objects; + objects.reserve(m_groups.size()); - const auto propertyIndex = m_materialInstance->FindPropertyIndex(propertyId); - if (!propertyIndex.IsNull()) + AtomToolsFramework::DocumentObjectInfo objectInfo; + for (const auto& group : m_groups) { - if (m_materialInstance->SetPropertyValue(propertyIndex, propertyValue)) - { - AZ::RPI::MaterialPropertyFlags dirtyFlags = m_materialInstance->GetPropertyDirtyFlags(); - - Recompile(); - - EditorMaterialFunctorResult result = RunEditorMaterialFunctors(dirtyFlags); - for (const AZ::Name& changedPropertyGroupName : result.m_updatedPropertyGroups) - { - AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast( - &AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentPropertyGroupVisibilityChanged, m_id, - changedPropertyGroupName, IsPropertyGroupVisible(changedPropertyGroupName)); - } - for (const AZ::Name& changedPropertyName : result.m_updatedProperties) - { - AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast( - &AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentPropertyConfigModified, m_id, - GetProperty(changedPropertyName)); - } - } + objects.push_back(GetObjectInfoFromDynamicPropertyGroup(group.get())); } - AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast( - &AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentPropertyValueModified, m_id, property); - AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast( - &AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentModified, m_id); + return objects; } bool MaterialDocument::Save() @@ -185,14 +166,15 @@ namespace MaterialEditor } // after saving, reset to a clean state - for (auto& propertyPair : m_properties) - { - AtomToolsFramework::DynamicProperty& property = propertyPair.second; - auto propertyConfig = property.GetConfig(); - propertyConfig.m_originalValue = property.GetValue(); - property.SetConfig(propertyConfig); - } - + TraverseGroups(m_groups, [&](auto& group) { + for (auto& property : group->m_properties) + { + auto propertyConfig = property.GetConfig(); + propertyConfig.m_originalValue = property.GetValue(); + property.SetConfig(propertyConfig); + } + return true; + }); return SaveSucceeded(); } @@ -273,12 +255,19 @@ namespace MaterialEditor bool MaterialDocument::IsModified() const { - return AZStd::any_of(m_properties.begin(), m_properties.end(), - [](const auto& propertyPair) - { - const AtomToolsFramework::DynamicProperty& property = propertyPair.second; - return !AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_originalValue); + bool result = false; + TraverseGroups(m_groups, [&](auto& group) { + for (auto& property : group->m_properties) + { + if (!AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_originalValue)) + { + result = true; + return false; + } + } + return true; }); + return result; } bool MaterialDocument::IsSavable() const @@ -290,11 +279,13 @@ namespace MaterialEditor { // Save the current properties as a momento for undo before any changes are applied m_propertyValuesBeforeEdit.clear(); - for (const auto& propertyPair : m_properties) - { - const AtomToolsFramework::DynamicProperty& property = propertyPair.second; - m_propertyValuesBeforeEdit[property.GetId()] = property.GetValue(); - } + TraverseGroups(m_groups, [this](auto& group) { + for (auto& property : group->m_properties) + { + m_propertyValuesBeforeEdit[property.GetId()] = property.GetValue(); + } + return true; + }); return true; } @@ -348,10 +339,10 @@ namespace MaterialEditor AZ::Name propertyId{propertyIdContext + propertyDefinition->GetName()}; - const auto it = m_properties.find(propertyId); - if (it != m_properties.end() && propertyFilter(it->second)) + const auto property = FindProperty(propertyId); + if (property && propertyFilter(*property)) { - AZ::RPI::MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(it->second.GetValue()); + AZ::RPI::MaterialPropertyValue propertyValue = AtomToolsFramework::ConvertToRuntimeType(property->GetValue()); if (propertyValue.IsValid()) { if (!AtomToolsFramework::ConvertToExportFormat(m_savePathNormalized, propertyId, *propertyDefinition, propertyValue)) @@ -394,52 +385,17 @@ namespace MaterialEditor // The material document and inspector are constructed from source data 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 - if (!AZ::RPI::JsonUtils::LoadObjectFromFile(m_absolutePath, m_materialSourceData)) + if (!LoadMaterialSourceData()) { - AZ_Error("MaterialDocument", false, "Material source data could not be loaded: '%s'.", m_absolutePath.c_str()); return OpenFailed(); } - - // We always need the absolute path for the material type and parent material to load source data and resolving - // relative paths when saving. This will convert and store them as absolute paths for use within the document. - if (!m_materialSourceData.m_parentMaterial.empty()) - { - m_materialSourceData.m_parentMaterial = - AZ::RPI::AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_parentMaterial); - } - - if (!m_materialSourceData.m_materialType.empty()) - { - 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 - auto materialTypeOutcome = AZ::RPI::MaterialUtils::LoadMaterialTypeSourceData(m_materialSourceData.m_materialType); - if (!materialTypeOutcome.IsSuccess()) - { - AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_materialSourceData.m_materialType.c_str()); - return OpenFailed(); - } - m_materialTypeSourceData = materialTypeOutcome.TakeValue(); } 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 - // material type source data then the material source data object can be created just by referencing the document path as the - // material type path. - auto materialTypeOutcome = AZ::RPI::MaterialUtils::LoadMaterialTypeSourceData(m_absolutePath); - if (!materialTypeOutcome.IsSuccess()) + if (!LoadMaterialTypeSourceData()) { - AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_absolutePath.c_str()); return OpenFailed(); } - m_materialTypeSourceData = materialTypeOutcome.TakeValue(); - - // We are storing absolute paths in the loaded version of the source data so that the files can be resolved at all times. - m_materialSourceData.m_materialType = m_absolutePath; - m_materialSourceData.m_parentMaterial.clear(); } else { @@ -519,58 +475,21 @@ namespace MaterialEditor // where such changes are supported at runtime. m_materialInstance->SetPsoHandlingOverride(AZ::RPI::MaterialPropertyPsoHandling::Allowed); - // 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 - // in the hierarchy are applied - m_materialTypeSourceData.EnumeratePropertyGroups([this, &parentPropertyValues](const AZStd::string& propertyIdContext, const AZ::RPI::MaterialTypeSourceData::PropertyGroup* propertyGroup) - { - AtomToolsFramework::DynamicPropertyConfig propertyConfig; + // Adding properties for material type and parent as part of making dynamic properties and the inspector more general purpose. This + // allows the read only properties to appear in the inspector like any other property. This may change or be removed once support + // for changing the material parent is implemented. + m_groups.emplace_back(aznew AtomToolsFramework::DynamicPropertyGroup); + m_groups.back()->m_name = "overview"; + m_groups.back()->m_displayName = "Overview"; + m_groups.back()->m_description = m_materialSourceData.m_description; - for (const auto& propertyDefinition : propertyGroup->GetProperties()) - { - // Assign id before conversion so it can be used in dynamic description - propertyConfig.m_id = propertyIdContext + propertyGroup->GetName() + "." + propertyDefinition->GetName(); - - const auto& propertyIndex = m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id); - const bool propertyIndexInBounds = propertyIndex.IsValid() && propertyIndex.GetIndex() < m_materialAsset->GetPropertyValues().size(); - AZ_Warning("MaterialDocument", propertyIndexInBounds, "Failed to add material property '%s' to document '%s'.", propertyConfig.m_id.GetCStr(), m_absolutePath.c_str()); - - if (propertyIndexInBounds) - { - AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, *propertyDefinition); - propertyConfig.m_showThumbnail = true; - propertyConfig.m_originalValue = AtomToolsFramework::ConvertToEditableType(m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]); - propertyConfig.m_parentValue = AtomToolsFramework::ConvertToEditableType(parentPropertyValues[propertyIndex.GetIndex()]); - - // TODO: Support populating the Material Editor with nested property groups, not just the top level. - // (Does DynamicPropertyConfig really even need m_groupName?) - propertyConfig.m_groupName = propertyGroup->GetDisplayName(); - m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig); - } - } - - return true; - }); - - // Populate the property group visibility map - // TODO: Support populating the Material Editor with nested property groups, not just the top level. - for (const AZStd::unique_ptr& propertyGroup : m_materialTypeSourceData.GetPropertyLayout().m_propertyGroups) - { - m_propertyGroupVisibility[AZ::Name{propertyGroup->GetName()}] = true; - } - - // Adding properties for material type and parent as part of making dynamic - // properties and the inspector more general purpose. - // This allows the read only properties to appear in the inspector like any - // other property. - // This may change or be removed once support for changing the material parent - // is implemented. AtomToolsFramework::DynamicPropertyConfig propertyConfig; propertyConfig.m_dataType = AtomToolsFramework::DynamicPropertyType::Asset; propertyConfig.m_id = "overview.materialType"; propertyConfig.m_name = "materialType"; propertyConfig.m_displayName = "Material Type"; - propertyConfig.m_groupName = "Overview"; + propertyConfig.m_groupName = "overview"; + propertyConfig.m_groupDisplayName = "Overview"; propertyConfig.m_description = "The material type defines the layout, properties, default values, shader connections, and other " "data needed to create and edit a derived material."; propertyConfig.m_defaultValue = AZStd::any(materialTypeAsset); @@ -578,14 +497,15 @@ namespace MaterialEditor propertyConfig.m_parentValue = propertyConfig.m_defaultValue; propertyConfig.m_readOnly = true; - m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig); + m_groups.back()->m_properties.push_back(AtomToolsFramework::DynamicProperty(propertyConfig)); propertyConfig = {}; propertyConfig.m_dataType = AtomToolsFramework::DynamicPropertyType::Asset; propertyConfig.m_id = "overview.parentMaterial"; propertyConfig.m_name = "parentMaterial"; propertyConfig.m_displayName = "Parent Material"; - propertyConfig.m_groupName = "Overview"; + propertyConfig.m_groupName = "overview"; + propertyConfig.m_groupDisplayName = "Overview"; propertyConfig.m_description = "The parent material provides an initial configuration whose properties are inherited and overriden by a derived material."; propertyConfig.m_defaultValue = AZStd::any(parentMaterialAsset); @@ -594,9 +514,14 @@ namespace MaterialEditor propertyConfig.m_readOnly = true; propertyConfig.m_showThumbnail = true; - m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig); + m_groups.back()->m_properties.push_back(AtomToolsFramework::DynamicProperty(propertyConfig)); + + m_groups.emplace_back(aznew AtomToolsFramework::DynamicPropertyGroup); + m_groups.back()->m_name = UvGroupName; + m_groups.back()->m_displayName = "UV Sets"; + m_groups.back()->m_description = "UV set names in this material, which can be renamed to match those in the model."; - //Add UV name customization properties + // Add UV name customization properties const AZ::RPI::MaterialUvNameMap& uvNameMap = materialTypeAsset->GetUvNameMap(); for (const AZ::RPI::UvNamePair& uvNamePair : uvNameMap) { @@ -608,61 +533,69 @@ namespace MaterialEditor propertyConfig.m_id = AZ::RPI::MaterialPropertyId(UvGroupName, shaderInput); propertyConfig.m_name = shaderInput; propertyConfig.m_displayName = shaderInput; - propertyConfig.m_groupName = "UV Sets"; + propertyConfig.m_groupName = UvGroupName; + propertyConfig.m_groupDisplayName = "UV Sets"; propertyConfig.m_description = shaderInput; propertyConfig.m_defaultValue = uvName; propertyConfig.m_originalValue = uvName; propertyConfig.m_parentValue = uvName; propertyConfig.m_readOnly = true; - m_properties[propertyConfig.m_id] = AtomToolsFramework::DynamicProperty(propertyConfig); + m_groups.back()->m_properties.push_back(AtomToolsFramework::DynamicProperty(propertyConfig)); } - // Add material functors that are in the top-level functors list. - const AZ::RPI::MaterialFunctorSourceData::EditorContext editorContext = - AZ::RPI::MaterialFunctorSourceData::EditorContext(m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout()); - for (AZ::RPI::Ptr functorData : m_materialTypeSourceData.m_materialFunctorSourceData) - { - AZ::RPI::MaterialFunctorSourceData::FunctorResult result2 = functorData->CreateFunctor(editorContext); - - if (result2.IsSuccess()) + // 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 + // in the hierarchy are applied + bool enumerateResult = m_materialTypeSourceData.EnumeratePropertyGroups( + [this, &parentPropertyValues]( + const AZStd::string& propertyIdContext, const AZ::RPI::MaterialTypeSourceData::PropertyGroup* propertyGroup) { - AZ::RPI::Ptr& functor = result2.GetValue(); - if (functor != nullptr) + // Add any material functors that are located inside each property group. + if (!AddEditorMaterialFunctors(propertyGroup->GetFunctors())) { - m_editorFunctors.push_back(functor); + return false; } - } - else - { - AZ_Error("MaterialDocument", false, "Material functors were not created: '%s'.", m_absolutePath.c_str()); - return OpenFailed(); - } - } - - // Add any material functors that are located inside each property group. - bool enumerateResult = m_materialTypeSourceData.EnumeratePropertyGroups( - [this](const AZStd::string&, const AZ::RPI::MaterialTypeSourceData::PropertyGroup* propertyGroup) - { - const AZ::RPI::MaterialFunctorSourceData::EditorContext editorContext = AZ::RPI::MaterialFunctorSourceData::EditorContext( - m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout()); - for (AZ::RPI::Ptr functorData : propertyGroup->GetFunctors()) + m_groups.emplace_back(aznew AtomToolsFramework::DynamicPropertyGroup); + m_groups.back()->m_name = propertyIdContext + propertyGroup->GetName(); + m_groups.back()->m_displayName = propertyGroup->GetDisplayName(); + m_groups.back()->m_description = propertyGroup->GetDescription(); + + for (const auto& propertyDefinition : propertyGroup->GetProperties()) { - AZ::RPI::MaterialFunctorSourceData::FunctorResult result = functorData->CreateFunctor(editorContext); + // Assign id before conversion so it can be used in dynamic description + AtomToolsFramework::DynamicPropertyConfig propertyConfig; + propertyConfig.m_id = m_groups.back()->m_name + "." + propertyDefinition->GetName(); + + const auto& propertyIndex = m_materialAsset->GetMaterialPropertiesLayout()->FindPropertyIndex(propertyConfig.m_id); + const bool propertyIndexInBounds = + propertyIndex.IsValid() && propertyIndex.GetIndex() < m_materialAsset->GetPropertyValues().size(); + AZ_Warning( + "MaterialDocument", propertyIndexInBounds, "Failed to add material property '%s' to document '%s'.", + propertyConfig.m_id.GetCStr(), m_absolutePath.c_str()); - if (result.IsSuccess()) + if (propertyIndexInBounds) { - AZ::RPI::Ptr& functor = result.GetValue(); - if (functor != nullptr) + AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, *propertyDefinition); + + // TODO: Support populating the Material Editor with nested property groups, not just the top level. + // (Does DynamicPropertyConfig really even need m_groupDisplayName?) + propertyConfig.m_groupName = m_groups.back()->m_name; + propertyConfig.m_groupDisplayName = m_groups.back()->m_displayName; + propertyConfig.m_showThumbnail = true; + propertyConfig.m_originalValue = + AtomToolsFramework::ConvertToEditableType(m_materialAsset->GetPropertyValues()[propertyIndex.GetIndex()]); + propertyConfig.m_parentValue = + AtomToolsFramework::ConvertToEditableType(parentPropertyValues[propertyIndex.GetIndex()]); + propertyConfig.m_dataChangeCallback = [documentId = m_id, propertyId = propertyConfig.m_id](const AZStd::any& value) { - m_editorFunctors.push_back(functor); - } - } - else - { - AZ_Error("MaterialDocument", false, "Material functors were not created: '%s'.", m_absolutePath.c_str()); - return false; + MaterialDocumentRequestBus::Event( + documentId, &MaterialDocumentRequestBus::Events::SetPropertyValue, propertyId, value); + return AZ::Edit::PropertyRefreshLevels::AttributesAndValues; + }; + + m_groups.back()->m_properties.push_back(AtomToolsFramework::DynamicProperty(propertyConfig)); } } @@ -674,6 +607,12 @@ namespace MaterialEditor return OpenFailed(); } + // Add material functors that are in the top-level functors list. + if (!AddEditorMaterialFunctors(m_materialTypeSourceData.m_materialFunctorSourceData)) + { + return OpenFailed(); + } + AZ::RPI::MaterialPropertyFlags dirtyFlags; dirtyFlags.set(); // Mark all properties as dirty since we just loaded the material and need to initialize property visibility RunEditorMaterialFunctors(dirtyFlags); @@ -681,17 +620,35 @@ namespace MaterialEditor return OpenSucceeded(); } + void MaterialDocument::Clear() + { + AtomToolsFramework::AtomToolsDocument::Clear(); + + AZ::TickBus::Handler::BusDisconnect(); + + m_materialAsset = {}; + m_materialInstance = {}; + m_compilePending = {}; + m_groups.clear(); + m_editorFunctors.clear(); + m_materialTypeSourceData = AZ::RPI::MaterialTypeSourceData(); + m_materialSourceData = AZ::RPI::MaterialSourceData(); + m_propertyValuesBeforeEdit.clear(); + } + bool MaterialDocument::ReopenRecordState() { m_propertyValuesBeforeReopen.clear(); - for (const auto& propertyPair : m_properties) - { - const AtomToolsFramework::DynamicProperty& property = propertyPair.second; - if (!AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_parentValue)) + TraverseGroups(m_groups, [this](auto& group) { + for (auto& property : group->m_properties) { - m_propertyValuesBeforeReopen[property.GetId()] = property.GetValue(); + if (!AtomToolsFramework::ArePropertyValuesEqual(property.GetValue(), property.GetConfig().m_parentValue)) + { + m_propertyValuesBeforeReopen[property.GetId()] = property.GetValue(); + } } - } + return true; + }); return AtomToolsDocument::ReopenRecordState(); } @@ -711,20 +668,59 @@ namespace MaterialEditor } } - void MaterialDocument::Clear() + bool MaterialDocument::LoadMaterialSourceData() { - AtomToolsFramework::AtomToolsDocument::Clear(); + // 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)) + { + AZ_Error("MaterialDocument", false, "Material source data could not be loaded: '%s'.", m_absolutePath.c_str()); + return false; + } - AZ::TickBus::Handler::BusDisconnect(); + // We always need the absolute path for the material type and parent material to load source data and resolving + // relative paths when saving. This will convert and store them as absolute paths for use within the document. + if (!m_materialSourceData.m_parentMaterial.empty()) + { + m_materialSourceData.m_parentMaterial = + AZ::RPI::AssetUtils::ResolvePathReference(m_absolutePath, m_materialSourceData.m_parentMaterial); + } - m_materialAsset = {}; - m_materialInstance = {}; - m_compilePending = {}; - m_properties.clear(); - m_editorFunctors.clear(); - m_materialTypeSourceData = AZ::RPI::MaterialTypeSourceData(); - m_materialSourceData = AZ::RPI::MaterialSourceData(); - m_propertyValuesBeforeEdit.clear(); + if (!m_materialSourceData.m_materialType.empty()) + { + 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 + auto materialTypeOutcome = AZ::RPI::MaterialUtils::LoadMaterialTypeSourceData(m_materialSourceData.m_materialType); + if (!materialTypeOutcome.IsSuccess()) + { + AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_materialSourceData.m_materialType.c_str()); + return false; + } + + m_materialTypeSourceData = materialTypeOutcome.TakeValue(); + return true; + } + + bool MaterialDocument::LoadMaterialTypeSourceData() + { + // 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 path. + auto materialTypeOutcome = AZ::RPI::MaterialUtils::LoadMaterialTypeSourceData(m_absolutePath); + if (!materialTypeOutcome.IsSuccess()) + { + AZ_Error("MaterialDocument", false, "Material type source data could not be loaded: '%s'.", m_absolutePath.c_str()); + return false; + } + + m_materialTypeSourceData = materialTypeOutcome.TakeValue(); + + // We are storing absolute paths in the loaded version of the source data so that the files can be resolved at all times. + m_materialSourceData.m_materialType = m_absolutePath; + m_materialSourceData.m_parentMaterial.clear(); + return true; } void MaterialDocument::RestorePropertyValues(const PropertyValueMap& propertyValues) @@ -737,60 +733,186 @@ namespace MaterialEditor } } - MaterialDocument::EditorMaterialFunctorResult MaterialDocument::RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags) + bool MaterialDocument::AddEditorMaterialFunctors( + const AZStd::vector>& functorSourceDataHolders) { - EditorMaterialFunctorResult result; + const AZ::RPI::MaterialFunctorSourceData::EditorContext editorContext = AZ::RPI::MaterialFunctorSourceData::EditorContext( + m_materialSourceData.m_materialType, m_materialAsset->GetMaterialPropertiesLayout()); - AZStd::unordered_map propertyDynamicMetadata; - AZStd::unordered_map propertyGroupDynamicMetadata; - for (auto& propertyPair : m_properties) - { - AtomToolsFramework::DynamicProperty& property = propertyPair.second; - AtomToolsFramework::ConvertToPropertyMetaData(propertyDynamicMetadata[property.GetId()], property.GetConfig()); - } - for (auto& groupPair : m_propertyGroupVisibility) + for (AZ::RPI::Ptr functorData : functorSourceDataHolders) { - AZ::RPI::MaterialPropertyGroupDynamicMetadata& metadata = propertyGroupDynamicMetadata[AZ::Name{groupPair.first}]; + AZ::RPI::MaterialFunctorSourceData::FunctorResult result = functorData->CreateFunctor(editorContext); - bool visible = groupPair.second; - metadata.m_visibility = visible ? - AZ::RPI::MaterialPropertyGroupVisibility::Enabled : AZ::RPI::MaterialPropertyGroupVisibility::Hidden; + if (result.IsSuccess()) + { + AZ::RPI::Ptr& functor = result.GetValue(); + if (functor != nullptr) + { + m_editorFunctors.push_back(functor); + } + } + else + { + AZ_Error("MaterialDocument", false, "Material functors were not created: '%s'.", m_absolutePath.c_str()); + return false; + } } - + + return true; + } + + void MaterialDocument::RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags) + { + AZStd::unordered_map propertyDynamicMetadata; + AZStd::unordered_map propertyGroupDynamicMetadata; + + TraverseGroups(m_groups, [&](auto& group) { + AZ::RPI::MaterialPropertyGroupDynamicMetadata& metadata = propertyGroupDynamicMetadata[AZ::Name{ group->m_name }]; + metadata.m_visibility = group->m_visible ? AZ::RPI::MaterialPropertyGroupVisibility::Enabled : AZ::RPI::MaterialPropertyGroupVisibility::Hidden; + + for (auto& property : group->m_properties) + { + AtomToolsFramework::ConvertToPropertyMetaData(propertyDynamicMetadata[property.GetId()], property.GetConfig()); + } + return true; + }); + + AZStd::unordered_set updatedProperties; + AZStd::unordered_set updatedPropertyGroups; + for (AZ::RPI::Ptr& functor : m_editorFunctors) { const AZ::RPI::MaterialPropertyFlags& materialPropertyDependencies = functor->GetMaterialPropertyDependencies(); + // None also covers case that the client code doesn't register material properties to dependencies, // which will later get caught in Process() when trying to access a property. if (materialPropertyDependencies.none() || functor->NeedsProcess(dirtyFlags)) { AZ::RPI::MaterialFunctor::EditorContext context = AZ::RPI::MaterialFunctor::EditorContext( - m_materialInstance->GetPropertyValues(), - m_materialInstance->GetMaterialPropertiesLayout(), - propertyDynamicMetadata, - propertyGroupDynamicMetadata, - result.m_updatedProperties, - result.m_updatedPropertyGroups, - &materialPropertyDependencies - ); + m_materialInstance->GetPropertyValues(), m_materialInstance->GetMaterialPropertiesLayout(), propertyDynamicMetadata, + propertyGroupDynamicMetadata, updatedProperties, updatedPropertyGroups, + &materialPropertyDependencies); functor->Process(context); } } - for (auto& propertyPair : m_properties) + TraverseGroups(m_groups, [&](auto& group) { + bool groupChange = false; + bool groupRebuilt = false; + if (updatedPropertyGroups.find(AZ::Name(group->m_name)) != updatedPropertyGroups.end()) + { + AZ::RPI::MaterialPropertyGroupDynamicMetadata& metadata = propertyGroupDynamicMetadata[AZ::Name{ group->m_name }]; + group->m_visible = metadata.m_visibility != AZ::RPI::MaterialPropertyGroupVisibility::Hidden; + groupChange = true; + } + + for (auto& property : group->m_properties) + { + if (updatedProperties.find(AZ::Name(property.GetId())) != updatedProperties.end()) + { + const bool visibleBefore = property.GetConfig().m_visible; + AtomToolsFramework::DynamicPropertyConfig propertyConfig = property.GetConfig(); + AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, propertyDynamicMetadata[property.GetId()]); + property.SetConfig(propertyConfig); + groupChange = true; + groupRebuilt |= visibleBefore != property.GetConfig().m_visible; + } + } + + if (groupChange || groupRebuilt) + { + AtomToolsFramework::AtomToolsDocumentNotificationBus::Broadcast( + &AtomToolsFramework::AtomToolsDocumentNotificationBus::Events::OnDocumentObjectInfoChanged, m_id, + GetObjectInfoFromDynamicPropertyGroup(group.get()), groupRebuilt); + } + return true; + }); + } + + AtomToolsFramework::DocumentObjectInfo MaterialDocument::GetObjectInfoFromDynamicPropertyGroup( + const AtomToolsFramework::DynamicPropertyGroup* group) const + { + AtomToolsFramework::DocumentObjectInfo objectInfo; + objectInfo.m_visible = group->m_visible; + objectInfo.m_name = group->m_name; + objectInfo.m_displayName = group->m_displayName; + objectInfo.m_description = group->m_description; + objectInfo.m_objectType = azrtti_typeid(); + objectInfo.m_objectPtr = const_cast(group); + return objectInfo; + } + + bool MaterialDocument::TraverseGroups( + AZStd::vector>& groups, + AZStd::function&)> callback) + { + if (!callback) + { + return false; + } + + for (auto& group : groups) + { + if (!callback(group) || !TraverseGroups(group->m_groups, callback)) + { + return false; + } + } + + return true; + } + + bool MaterialDocument::TraverseGroups( + const AZStd::vector>& groups, + AZStd::function&)> callback) const + { + if (!callback) { - AtomToolsFramework::DynamicProperty& property = propertyPair.second; - AtomToolsFramework::DynamicPropertyConfig propertyConfig = property.GetConfig(); - AtomToolsFramework::ConvertToPropertyConfig(propertyConfig, propertyDynamicMetadata[property.GetId()]); - property.SetConfig(propertyConfig); + return false; } - for (auto& updatedPropertyGroup : result.m_updatedPropertyGroups) + for (auto& group : groups) { - bool visible = propertyGroupDynamicMetadata[updatedPropertyGroup].m_visibility == AZ::RPI::MaterialPropertyGroupVisibility::Enabled; - m_propertyGroupVisibility[updatedPropertyGroup] = visible; + if (!callback(group) || !TraverseGroups(group->m_groups, callback)) + { + return false; + } } + return true; + } + + AtomToolsFramework::DynamicProperty* MaterialDocument::FindProperty(const AZ::Name& propertyId) + { + AtomToolsFramework::DynamicProperty* result = nullptr; + TraverseGroups(m_groups, [&](auto& group) { + for (auto& property : group->m_properties) + { + if (property.GetId() == propertyId) + { + result = &property; + return false; + } + } + return true; + }); + return result; + } + + const AtomToolsFramework::DynamicProperty* MaterialDocument::FindProperty(const AZ::Name& propertyId) const + { + AtomToolsFramework::DynamicProperty* result = nullptr; + TraverseGroups(m_groups, [&](auto& group) { + for (auto& property : group->m_properties) + { + if (property.GetId() == propertyId) + { + result = &property; + return false; + } + } + return true; + }); return result; } } // namespace MaterialEditor diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h index b03d0943fa..37b3e1922f 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocument.h @@ -37,10 +37,7 @@ namespace MaterialEditor virtual ~MaterialDocument(); // AtomToolsFramework::AtomToolsDocument overrides... - const AZStd::any& GetPropertyValue(const AZ::Name& propertyId) const override; - const AtomToolsFramework::DynamicProperty& GetProperty(const AZ::Name& propertyId) const override; - bool IsPropertyGroupVisible(const AZ::Name& propertyGroupFullName) const override; - void SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value) override; + AZStd::vector GetObjectInfo() const override; bool Open(AZStd::string_view loadPath) override; bool Save() override; bool SaveAsCopy(AZStd::string_view savePath) override; @@ -56,20 +53,16 @@ namespace MaterialEditor AZ::Data::Instance GetInstance() const override; const AZ::RPI::MaterialSourceData* GetMaterialSourceData() const override; const AZ::RPI::MaterialTypeSourceData* GetMaterialTypeSourceData() const override; + void SetPropertyValue(const AZ::Name& propertyId, const AZStd::any& value) override; + const AZStd::any& GetPropertyValue(const AZ::Name& propertyId) const override; private: // Predicate for evaluating properties using PropertyFilterFunction = AZStd::function; - // Map of document's properties - using PropertyMap = AZStd::unordered_map; - // Map of raw property values for undo/redo comparison and storage using PropertyValueMap = AZStd::unordered_map; - - // Map of document's property group visibility flags - using PropertyGroupVisibilityMap = AZStd::unordered_map; // AZ::TickBus overrides... void OnTick(float deltaTime, AZ::ScriptTimePoint time) override; @@ -78,47 +71,60 @@ namespace MaterialEditor // AtomToolsFramework::AtomToolsDocument overrides... void Clear() override; - bool ReopenRecordState() override; bool ReopenRestoreState() override; void Recompile(); + bool LoadMaterialSourceData(); + bool LoadMaterialTypeSourceData(); + void RestorePropertyValues(const PropertyValueMap& propertyValues); - struct EditorMaterialFunctorResult - { - AZStd::unordered_set m_updatedProperties; - AZStd::unordered_set m_updatedPropertyGroups; - }; + bool AddEditorMaterialFunctors( + const AZStd::vector>& functorSourceDataHolders); // Run editor material functor to update editor metadata. // @param dirtyFlags indicates which properties have changed, and thus which MaterialFunctors need to be run. - // @return names for the set of properties and groups that have been changed or need update. - EditorMaterialFunctorResult RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags); + void RunEditorMaterialFunctors(AZ::RPI::MaterialPropertyFlags dirtyFlags); + + // Convert a dynamic property group pointer into generic document object info used to populate the inspector + AtomToolsFramework::DocumentObjectInfo GetObjectInfoFromDynamicPropertyGroup( + const AtomToolsFramework::DynamicPropertyGroup* group) const; - // Underlying material asset + // In order traversal of dynamic property groups + bool TraverseGroups( + AZStd::vector>& groups, + AZStd::function&)> callback); + + // In order traversal of dynamic property groups + bool TraverseGroups( + const AZStd::vector>& groups, + AZStd::function&)> callback) const; + + // Traverses dynamic property groups to find a property with a specific ID + AtomToolsFramework::DynamicProperty* FindProperty(const AZ::Name& propertyId); + + // Traverses dynamic property groups to find a property with a specific ID + const AtomToolsFramework::DynamicProperty* FindProperty(const AZ::Name& propertyId) const; + + // Material asset generated from source data, used to get the final values for properties to be assigned to the document AZ::Data::Asset m_materialAsset; - // Material instance being edited + // Material instance is only needed to run editor functors and is assigned directly to the viewport model to reflect real time + // changes to material property values AZ::Data::Instance m_materialInstance; // If material instance value(s) were modified, do we need to recompile on next tick? bool m_compilePending = false; - // Collection of all material's properties - PropertyMap m_properties; - - // Collection of all material's property groups - PropertyGroupVisibilityMap m_propertyGroupVisibility; - // Material functors that run in editor. See MaterialFunctor.h for details. AZStd::vector> m_editorFunctors; - // Source data for material type + // Material type source data used to enumerate all properties and populate the document AZ::RPI::MaterialTypeSourceData m_materialTypeSourceData; - // Source data for material + // Material source data with property values that override the material type AZ::RPI::MaterialSourceData m_materialSourceData; // State of property values prior to an edit, used for restoration during undo @@ -126,5 +132,12 @@ namespace MaterialEditor // State of property values prior to reopen PropertyValueMap m_propertyValuesBeforeReopen; + + // A container of root level dynamic property groups that represents the reflected, editable data within the document. + // These groups will be mapped to document object info so they can populate and be edited directly in the inspector. + AZStd::vector> m_groups; + + // Dummy default value returned whenever a property cannot be located + AZStd::any m_invalidValue; }; } // namespace MaterialEditor diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocumentRequestBus.h b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocumentRequestBus.h index c95aa4f215..da47ace48e 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocumentRequestBus.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Document/MaterialDocumentRequestBus.h @@ -45,7 +45,14 @@ namespace MaterialEditor //! Get the internal material type source data virtual const AZ::RPI::MaterialTypeSourceData* GetMaterialTypeSourceData() const = 0; - }; + + //! Modify property value + virtual void SetPropertyValue(const AZ::Name& propertyFullName, const AZStd::any& value) = 0; + + //! Return property value + //! 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; + }; using MaterialDocumentRequestBus = AZ::EBus; } // namespace MaterialEditor diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.cpp index 2e64740057..686715017c 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/MaterialEditorApplication.cpp @@ -65,7 +65,8 @@ namespace MaterialEditor ->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common) ->Attribute(AZ::Script::Attributes::Category, "Editor") ->Attribute(AZ::Script::Attributes::Module, "materialeditor") - ; + ->Event("SetPropertyValue", &MaterialDocumentRequestBus::Events::SetPropertyValue) + ->Event("GetPropertyValue", &MaterialDocumentRequestBus::Events::GetPropertyValue); } } diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp index 1402ad9f24..e6e768a7fd 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.cpp @@ -7,14 +7,10 @@ */ #include -#include -#include -#include #include #include #include #include -#include #include namespace MaterialEditor @@ -38,7 +34,7 @@ namespace MaterialEditor { m_documentPath.clear(); m_documentId = AZ::Uuid::CreateNull(); - m_groups = {}; + m_activeProperty = {}; AtomToolsFramework::InspectorRequestBus::Handler::BusDisconnect(); AtomToolsFramework::InspectorWidget::Reset(); @@ -67,18 +63,31 @@ namespace MaterialEditor m_documentId = documentId; bool isOpen = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(isOpen, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::IsOpen); + AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult( + isOpen, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::IsOpen); - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult(m_documentPath, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetAbsolutePath); + AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult( + m_documentPath, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetAbsolutePath); if (!m_documentId.IsNull() && isOpen) { - // Create the top group for displaying overview info about the material - AddOverviewGroup(); - // Create groups for displaying editable UV names - AddUvNamesGroup(); - // Create groups for displaying editable properties - AddPropertiesGroup(); + // This will automatically expose all document contents to an inspector with a collapsible group per object. In the case of the + // material editor, this will be one inspector group per property group. + AZStd::vector objects; + AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult( + objects, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetObjectInfo); + + for (auto& objectInfo : objects) + { + // Passing in same main and comparison instance to enable custom value comparison for highlighting modified properties + auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget( + objectInfo.m_objectPtr, objectInfo.m_objectPtr, objectInfo.m_objectType, this, this, + GetGroupSaveStateKey(objectInfo.m_name), {}, + [this](const auto node) { return GetInstanceNodePropertyIndicator(node); }, 0); + + AddGroup(objectInfo.m_name, objectInfo.m_displayName, objectInfo.m_description, propertyGroupWidget); + SetGroupVisible(objectInfo.m_name, objectInfo.m_visible); + } AtomToolsFramework::InspectorRequestBus::Handler::BusConnect(m_documentId); } @@ -106,198 +115,42 @@ namespace MaterialEditor return ":/Icons/blank.png"; } - void MaterialInspector::AddOverviewGroup() - { - const AZ::RPI::MaterialTypeSourceData* materialTypeSourceData = nullptr; - MaterialDocumentRequestBus::EventResult( - materialTypeSourceData, m_documentId, &MaterialDocumentRequestBus::Events::GetMaterialTypeSourceData); - - const AZStd::string groupName = "overview"; - const AZStd::string groupDisplayName = "Overview"; - const AZStd::string groupDescription = materialTypeSourceData->m_description; - auto& group = m_groups[groupName]; - - AtomToolsFramework::DynamicProperty property; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult( - property, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetProperty, AZ::Name("overview.materialType")); - group.m_properties.push_back(property); - - property = {}; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult( - property, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetProperty, AZ::Name("overview.parentMaterial")); - group.m_properties.push_back(property); - - // Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties - auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget( - &group, &group, group.TYPEINFO_Uuid(), this, this, GetGroupSaveStateKey(groupName), {}, - [this](const auto node) { return GetInstanceNodePropertyIndicator(node); }, 0); - AddGroup(groupName, groupDisplayName, groupDescription, propertyGroupWidget); - } - - void MaterialInspector::AddUvNamesGroup() - { - AZ::Data::Asset materialAsset; - MaterialDocumentRequestBus::EventResult(materialAsset, m_documentId, &MaterialDocumentRequestBus::Events::GetAsset); - - const AZStd::string groupName = UvGroupName; - const AZStd::string groupDisplayName = "UV Sets"; - const AZStd::string groupDescription = "UV set names in this material, which can be renamed to match those in the model."; - auto& group = m_groups[groupName]; - - const auto& uvNameMap = materialAsset->GetMaterialTypeAsset()->GetUvNameMap(); - group.m_properties.reserve(uvNameMap.size()); - - for (const auto& uvNamePair : uvNameMap) - { - AtomToolsFramework::DynamicProperty property; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult( - property, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetProperty, - AZ::RPI::MaterialPropertyId(groupName, uvNamePair.m_shaderInput.ToString())); - group.m_properties.push_back(property); - - property.SetValue(property.GetConfig().m_parentValue); - } - - // Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties - auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget( - &group, &group, group.TYPEINFO_Uuid(), this, this, GetGroupSaveStateKey(groupName), {}, - [this](const auto node) { return GetInstanceNodePropertyIndicator(node); }, 0); - AddGroup(groupName, groupDisplayName, groupDescription, propertyGroupWidget); - } - - void MaterialInspector::AddPropertiesGroup() - { - const AZ::RPI::MaterialTypeSourceData* materialTypeSourceData = nullptr; - MaterialDocumentRequestBus::EventResult( - materialTypeSourceData, m_documentId, &MaterialDocumentRequestBus::Events::GetMaterialTypeSourceData); - - // TODO: Support populating the Material Editor with nested property groups, not just the top level. - for (const AZStd::unique_ptr& propertyGroup : materialTypeSourceData->GetPropertyLayout().m_propertyGroups) - { - const AZStd::string& groupName = propertyGroup->GetName(); - const AZStd::string& groupDisplayName = !propertyGroup->GetDisplayName().empty() ? propertyGroup->GetDisplayName() : groupName; - const AZStd::string& groupDescription = !propertyGroup->GetDescription().empty() ? propertyGroup->GetDescription() : groupDisplayName; - auto& group = m_groups[groupName]; - - group.m_properties.reserve(propertyGroup->GetProperties().size()); - for (const auto& propertyDefinition : propertyGroup->GetProperties()) - { - AtomToolsFramework::DynamicProperty property; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult( - property, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::GetProperty, - AZ::RPI::MaterialPropertyId(groupName, propertyDefinition->GetName())); - group.m_properties.push_back(property); - } - - // Passing in same group as main and comparison instance to enable custom value comparison for highlighting modified properties - auto propertyGroupWidget = new AtomToolsFramework::InspectorPropertyGroupWidget( - &group, &group, group.TYPEINFO_Uuid(), this, this, GetGroupSaveStateKey(groupName), {}, - [this](const auto node) { return GetInstanceNodePropertyIndicator(node); }, 0); - AddGroup(groupName, groupDisplayName, groupDescription, propertyGroupWidget); - - bool isGroupVisible = false; - AtomToolsFramework::AtomToolsDocumentRequestBus::EventResult( - isGroupVisible, m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::IsPropertyGroupVisible, AZ::Name{groupName}); - SetGroupVisible(groupName, isGroupVisible); - } - } - - void MaterialInspector::OnDocumentPropertyValueModified(const AZ::Uuid& documentId, const AtomToolsFramework::DynamicProperty& property) + void MaterialInspector::OnDocumentObjectInfoChanged( + [[maybe_unused]] const AZ::Uuid& documentId, const AtomToolsFramework::DocumentObjectInfo& objectInfo, bool rebuilt) { - for (auto& groupPair : m_groups) + SetGroupVisible(objectInfo.m_name, objectInfo.m_visible); + if (rebuilt) { - for (auto& reflectedProperty : groupPair.second.m_properties) - { - if (reflectedProperty.GetId() == property.GetId()) - { - if (!AtomToolsFramework::ArePropertyValuesEqual(reflectedProperty.GetValue(), property.GetValue())) - { - reflectedProperty.SetValue(property.GetValue()); - AtomToolsFramework::InspectorRequestBus::Event( - documentId, &AtomToolsFramework::InspectorRequestBus::Events::RefreshGroup, groupPair.first); - } - return; - } - } + RebuildGroup(objectInfo.m_name); } - } - - void MaterialInspector::OnDocumentPropertyConfigModified(const AZ::Uuid&, const AtomToolsFramework::DynamicProperty& property) - { - for (auto& groupPair : m_groups) + else { - for (auto& reflectedProperty : groupPair.second.m_properties) - { - if (reflectedProperty.GetId() == property.GetId()) - { - // Visibility changes require the entire reflected property editor tree for this group to be rebuilt - if (reflectedProperty.GetVisibility() != property.GetVisibility()) - { - reflectedProperty.SetConfig(property.GetConfig()); - RebuildGroup(groupPair.first); - } - else - { - reflectedProperty.SetConfig(property.GetConfig()); - RefreshGroup(groupPair.first); - } - return; - } - } + RefreshGroup(objectInfo.m_name); } } - - void MaterialInspector::OnDocumentPropertyGroupVisibilityChanged(const AZ::Uuid&, const AZ::Name& groupId, bool visible) - { - SetGroupVisible(groupId.GetStringView(), visible); - } void MaterialInspector::BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode) { - // For some reason the reflected property editor notifications are not symmetrical - // This function is called continuously anytime a property changes until the edit has completed - // Because of that, we have to track whether or not we are continuing to edit the same property to know when editing has started and - // ended - const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(pNode); - if (property) - { - if (m_activeProperty != property) - { - m_activeProperty = property; - AtomToolsFramework::AtomToolsDocumentRequestBus::Event(m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::BeginEdit); - } - } - } - - void MaterialInspector::AfterPropertyModified(AzToolsFramework::InstanceDataNode* pNode) - { + // This function is called before every single property change whether it's a button click or dragging a slider. We only want to + // begin tracking undo state for the first change in the sequence, when the user begins to drag the slider. const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(pNode); - if (property) + if (!m_activeProperty && property) { - if (m_activeProperty == property) - { - AtomToolsFramework::AtomToolsDocumentRequestBus::Event( - m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::SetPropertyValue, property->GetId(), property->GetValue()); - } + m_activeProperty = property; + AtomToolsFramework::AtomToolsDocumentRequestBus::Event( + m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::BeginEdit); } } void MaterialInspector::SetPropertyEditingComplete(AzToolsFramework::InstanceDataNode* pNode) { - // As above, there are symmetrical functions on the notification interface for when editing begins and ends and has been completed - // but they are not being called following that pattern. when this function executes the changes to the property are ready to be - // committed or reverted + // If tracking has started and editing has completed then we can stop tracking undue state for this sequence of changes. const AtomToolsFramework::DynamicProperty* property = AtomToolsFramework::FindDynamicPropertyForInstanceDataNode(pNode); - if (property) + if (m_activeProperty && m_activeProperty == property) { - if (m_activeProperty == property) - { - AtomToolsFramework::AtomToolsDocumentRequestBus::Event( - m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::SetPropertyValue, property->GetId(), property->GetValue()); - - AtomToolsFramework::AtomToolsDocumentRequestBus::Event(m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::EndEdit); - m_activeProperty = nullptr; - } + AtomToolsFramework::AtomToolsDocumentRequestBus::Event( + m_documentId, &AtomToolsFramework::AtomToolsDocumentRequestBus::Events::EndEdit); + m_activeProperty = {}; } } } // namespace MaterialEditor diff --git a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h index 690e8add86..fe688e3ad5 100644 --- a/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h +++ b/Gems/Atom/Tools/MaterialEditor/Code/Source/Window/MaterialInspector/MaterialInspector.h @@ -45,31 +45,25 @@ namespace MaterialEditor bool IsInstanceNodePropertyModifed(const AzToolsFramework::InstanceDataNode* node) const; const char* GetInstanceNodePropertyIndicator(const AzToolsFramework::InstanceDataNode* node) const; - void AddOverviewGroup(); - void AddUvNamesGroup(); - void AddPropertiesGroup(); - // AtomToolsDocumentNotificationBus::Handler implementation void OnDocumentOpened(const AZ::Uuid& documentId) override; - void OnDocumentPropertyValueModified(const AZ::Uuid& documentId, const AtomToolsFramework::DynamicProperty& property) override; - void OnDocumentPropertyConfigModified(const AZ::Uuid& documentId, const AtomToolsFramework::DynamicProperty& property) override; - void OnDocumentPropertyGroupVisibilityChanged(const AZ::Uuid& documentId, const AZ::Name& groupId, bool visible) override; + void OnDocumentObjectInfoChanged( + const AZ::Uuid& documentId, const AtomToolsFramework::DocumentObjectInfo& objectInfo, bool rebuilt) override; // AzToolsFramework::IPropertyEditorNotify overrides... void BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode) override; - void AfterPropertyModified(AzToolsFramework::InstanceDataNode* pNode) override; + void AfterPropertyModified([[maybe_unused]] AzToolsFramework::InstanceDataNode* pNode) override {} void SetPropertyEditingActive([[maybe_unused]] AzToolsFramework::InstanceDataNode* pNode) override {} void SetPropertyEditingComplete(AzToolsFramework::InstanceDataNode* pNode) override; void SealUndoStack() override {} - void RequestPropertyContextMenu(AzToolsFramework::InstanceDataNode*, const QPoint&) override {} - void PropertySelectionChanged(AzToolsFramework::InstanceDataNode*, bool) override {} + void RequestPropertyContextMenu([[maybe_unused]] AzToolsFramework::InstanceDataNode* pNode, const QPoint&) override {} + void PropertySelectionChanged([[maybe_unused]] AzToolsFramework::InstanceDataNode* pNode, bool) override {} // Tracking the property that is activiley being edited in the inspector - const AtomToolsFramework::DynamicProperty* m_activeProperty = nullptr; + const AtomToolsFramework::DynamicProperty* m_activeProperty = {}; AZ::Uuid m_documentId = AZ::Uuid::CreateNull(); AZStd::string m_documentPath; - AZStd::unordered_map m_groups; AZStd::intrusive_ptr m_windowSettings; }; } // namespace MaterialEditor diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Document/ShaderManagementConsoleDocument.cpp b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Document/ShaderManagementConsoleDocument.cpp index 99a3aea347..5698e33cec 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Document/ShaderManagementConsoleDocument.cpp +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Document/ShaderManagementConsoleDocument.cpp @@ -78,6 +78,28 @@ namespace ShaderManagementConsole return m_invalidDescriptor; } + AZStd::vector ShaderManagementConsoleDocument::GetObjectInfo() const + { + if (!IsOpen()) + { + AZ_Error("ShaderManagementConsoleDocument", false, "Document is not open."); + return {}; + } + + AZStd::vector objects; + + AtomToolsFramework::DocumentObjectInfo objectInfo; + objectInfo.m_visible = true; + objectInfo.m_name = "Shader Variant List"; + objectInfo.m_displayName = "Shader Variant List"; + objectInfo.m_description = "Shader Variant List"; + objectInfo.m_objectType = azrtti_typeid(); + objectInfo.m_objectPtr = const_cast(&m_shaderVariantListSourceData); + objects.push_back(AZStd::move(objectInfo)); + + return objects; + } + bool ShaderManagementConsoleDocument::Open(AZStd::string_view loadPath) { if (!AtomToolsDocument::Open(loadPath)) diff --git a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Document/ShaderManagementConsoleDocument.h b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Document/ShaderManagementConsoleDocument.h index 4ad951e463..5c3d66b0a9 100644 --- a/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Document/ShaderManagementConsoleDocument.h +++ b/Gems/Atom/Tools/ShaderManagementConsole/Code/Source/Document/ShaderManagementConsoleDocument.h @@ -32,6 +32,7 @@ namespace ShaderManagementConsole ~ShaderManagementConsoleDocument(); // AtomToolsFramework::AtomToolsDocument overrides... + AZStd::vector GetObjectInfo() const override; bool Open(AZStd::string_view loadPath) override; bool Save() override; bool SaveAsCopy(AZStd::string_view savePath) override;