Browse Source

Integrate DPE and Prefab patch mechanisms to handle property edits in DPE backed inspector (#14988)

* Add support for reverting property override in DPE Entity Inspector

Signed-off-by: Junhao Wang <[email protected]>

* Move flag check to ComponentEditor

Signed-off-by: Junhao Wang <[email protected]>

* Always store RelativePath attribute

Signed-off-by: Junhao Wang <[email protected]>

* Move alias logic to prefab component adapter

Signed-off-by: Junhao Wang <[email protected]>

* Assert empty component alias

Signed-off-by: Junhao Wang <[email protected]>

* More assert on empty component alias

Signed-off-by: Junhao Wang <[email protected]>

* Choose to use PrefabComponentAdapter

Signed-off-by: Junhao Wang <[email protected]>

* Generate prefab patches using DPE patches for component property changes

Signed-off-by: srikappa-amzn <[email protected]>

* Fixed undo classes comments

Signed-off-by: srikappa-amzn <[email protected]>

* Remove pragma optimize and add a couple of asserts

Signed-off-by: srikappa-amzn <[email protected]>

* Fixed incorrect spacing and removed unnecessary explicit namespacing

Signed-off-by: srikappa-amzn <[email protected]>

* Add back deleted HandleMessage function to DPEComponentAdapter to make dpe work for inspector without overrides

Signed-off-by: srikappa-amzn <[email protected]>

* Fix PrefabLoadTemplateTests by removing 'LinkId' from sanitized template DOM'

Signed-off-by: srikappa-amzn <[email protected]>

* Prevent marking entities dirty for propety changes when overrides flag is on

Signed-off-by: srikappa-amzn <[email protected]>

* Validate that the first element in a property row is PrefabOverrideLabel

Signed-off-by: srikappa-amzn <[email protected]>

* Remove unused variables and fix a typo

Signed-off-by: srikappa-amzn <[email protected]>

---------

Signed-off-by: Junhao Wang <[email protected]>
Signed-off-by: srikappa-amzn <[email protected]>
Co-authored-by: Junhao Wang <[email protected]>
srikappa-amzn 2 years ago
parent
commit
a29202f094

+ 6 - 1
Code/Framework/AzFramework/AzFramework/DocumentPropertyEditor/ReflectionAdapter.cpp

@@ -710,6 +710,11 @@ namespace AZ::DocumentPropertyEditor
         adapterBuilder->Label(labelText);
     }
 
+    void ReflectionAdapter::UpdateDomContents(const PropertyChangeInfo& propertyChangeInfo)
+    {
+        NotifyContentsChanged({ Dom::PatchOperation::ReplaceOperation(propertyChangeInfo.path / "Value", propertyChangeInfo.newValue) });
+    }
+
     Dom::Value ReflectionAdapter::GenerateContents()
     {
         m_impl->m_builder.BeginAdapter();
@@ -735,7 +740,7 @@ namespace AZ::DocumentPropertyEditor
             if (changeHandler != nullptr)
             {
                 Dom::Value newValue = (*changeHandler)(valueFromEditor);
-                NotifyContentsChanged({ Dom::PatchOperation::ReplaceOperation(message.m_messageOrigin / "Value", newValue) });
+                UpdateDomContents({ message.m_messageOrigin, newValue, changeType });
                 NotifyPropertyChanged({ message.m_messageOrigin, newValue, changeType });
             }
         };

+ 5 - 0
Code/Framework/AzFramework/AzFramework/DocumentPropertyEditor/ReflectionAdapter.h

@@ -68,6 +68,11 @@ namespace AZ::DocumentPropertyEditor
         //! @param serializedPath The serialized path fetched from AZ::Reflection::DescriptorAttributes.
         virtual void CreateLabel(AdapterBuilder* adapterBuilder, AZStd::string_view labelText, AZStd::string_view serializedPath);
 
+        //! Updates the contents of adapter Dom contents using the property change information provided. Child adapters can override
+        //! this function to make more changes to the Dom in addition to the property changes.
+        //! @param propertyChangeInfo Object containing information about the property change.
+        virtual void UpdateDomContents(const PropertyChangeInfo& propertyChangeInfo);
+
         void* GetInstance() { return m_instance; }
         const void* GetInstance() const { return m_instance; }
         AZ::TypeId GetTypeId() const { return m_typeId; }

+ 175 - 3
Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/DocumentPropertyEditor/PrefabComponentAdapter.cpp

@@ -6,23 +6,29 @@
  *
  */
 
+#include <AzCore/DOM/Backends/JSON/JsonSerializationUtils.h>
 #include <AzFramework/DocumentPropertyEditor/AdapterBuilder.h>
 #include <AzToolsFramework/Prefab/DocumentPropertyEditor/PrefabComponentAdapter.h>
 #include <AzToolsFramework/Prefab/DocumentPropertyEditor/PrefabOverrideLabelHandler.h>
 #include <AzToolsFramework/Prefab/DocumentPropertyEditor/PrefabPropertyEditorNodes.h>
+#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
 #include <AzToolsFramework/Prefab/Overrides/PrefabOverridePublicInterface.h>
 #include <AzToolsFramework/Prefab/PrefabDomUtils.h>
+#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
 #include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
+#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
+#include <AzToolsFramework/Prefab/Undo/PrefabUndoComponentPropertyEdit.h>
+#include <AzToolsFramework/Prefab/Undo/PrefabUndoComponentPropertyOverride.h>
 
 namespace AzToolsFramework::Prefab
 {
     PrefabComponentAdapter::PrefabComponentAdapter()
         : ComponentAdapter()
     {
-        m_prefabOverridePublicInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabOverridePublicInterface>::Get();
+        m_prefabOverridePublicInterface = AZ::Interface<PrefabOverridePublicInterface>::Get();
         AZ_Assert(m_prefabOverridePublicInterface, "Could not get PrefabOverridePublicInterface on PrefabComponentAdapter construction.");
 
-        m_prefabPublicInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabPublicInterface>::Get();
+        m_prefabPublicInterface = AZ::Interface<PrefabPublicInterface>::Get();
         AZ_Assert(m_prefabPublicInterface, "Could not get PrefabPublicInterface on PrefabComponentAdapter construction.");
     }
 
@@ -32,6 +38,13 @@ namespace AzToolsFramework::Prefab
 
     void PrefabComponentAdapter::SetComponent(AZ::Component* componentInstance)
     {
+        auto owningInstance = AZ::Interface<InstanceEntityMapperInterface>::Get()->FindOwningInstance(
+            componentInstance->GetEntityId());
+        AZ_Assert(owningInstance.has_value(), "Entity owning the component doesn't have an owning prefab instance.");
+        auto entityAlias = owningInstance->get().GetEntityAlias(componentInstance->GetEntityId());
+        AZ_Assert(entityAlias.has_value(), "Owning entity of component doesn't have a valid entity alias in the owning prefab.");
+        m_entityAlias = entityAlias->get();
+
         // Set the component alias before calling SetValue() in base SetComponent().
         // Otherwise, an empty component alias will be used in DOM data.
         m_componentAlias = componentInstance->GetSerializedIdentifier();
@@ -88,7 +101,166 @@ namespace AzToolsFramework::Prefab
 
         message.Match(PrefabPropertyEditorNodes::PrefabOverrideLabel::RevertOverride, handleRevertOverride);
 
-        return ComponentAdapter::HandleMessage(message);
+        return ReflectionAdapter::HandleMessage(message);
     }
 
+    void PrefabComponentAdapter::UpdateDomContents(const PropertyChangeInfo& propertyChangeInfo)
+    {
+        if (propertyChangeInfo.changeType == AZ::DocumentPropertyEditor::Nodes::ValueChangeType::FinishedEdit)
+        {
+            AZ::Dom::Path serializedPath = propertyChangeInfo.path / AZ::Reflection::DescriptorAttributes::SerializedPath;
+
+            AZ::Dom::Path relativePathFromOwningPrefab(PrefabDomUtils::EntitiesName);
+            relativePathFromOwningPrefab /= m_entityAlias;
+            relativePathFromOwningPrefab /= PrefabDomUtils::ComponentsName;
+            relativePathFromOwningPrefab /= m_componentAlias;
+
+            AZ::Dom::Value serializedPathValue = GetContents()[serializedPath];
+            AZ_Assert(serializedPathValue.IsString(), "PrefabComponentAdapter::UpdateDomContents - SerialziedPath attribute value is not a string.");
+
+            relativePathFromOwningPrefab /= AZ::Dom::Path(serializedPathValue.GetString());
+
+
+            auto prefabFocusPublicInterface = AZ::Interface<PrefabFocusPublicInterface>::Get();
+            if (prefabFocusPublicInterface->IsOwningPrefabBeingFocused(m_entityId))
+            {
+                if (CreateAndApplyComponentEditPatch(relativePathFromOwningPrefab.ToString(), propertyChangeInfo))
+                {
+                    NotifyContentsChanged(
+                        { AZ::Dom::PatchOperation::ReplaceOperation(propertyChangeInfo.path / "Value", propertyChangeInfo.newValue) });
+
+                }
+            }
+            else if (prefabFocusPublicInterface->IsOwningPrefabInFocusHierarchy(m_entityId))
+            {
+                if (CreateAndApplyComponentOverridePatch(relativePathFromOwningPrefab, propertyChangeInfo))
+                {
+                    AZ::Dom::Patch patches(
+                        { AZ::Dom::PatchOperation::ReplaceOperation(propertyChangeInfo.path / "Value", propertyChangeInfo.newValue) });
+
+                    AZ::Dom::Path pathToProperty = propertyChangeInfo.path;
+
+                    // Get the path to parent row and its value.
+                    pathToProperty.Pop();
+                    AZ::Dom::Value propertyRowValue = GetContents()[pathToProperty];
+
+                    AZ_Assert(
+                        propertyRowValue.IsNode() &&
+                            propertyRowValue.GetNodeName().GetStringView() == AZ::DocumentPropertyEditor::Nodes::Row::Name,
+                        "PrefabComponentAdapter::UpdateDomContents - Parent path to property doesn't map to a 'Row' node. ");
+
+                    AZ::Dom::Value firstRowElement = propertyRowValue[0];
+                    AZ_Assert(
+                        firstRowElement.IsNode() &&
+                            firstRowElement.GetNodeName().GetStringView() == AZ::DocumentPropertyEditor::Nodes::PropertyEditor::Name &&
+                            firstRowElement["Type"].GetString() == PrefabPropertyEditorNodes::PrefabOverrideLabel::Name,
+                        "PrefabComponentAdapter::UpdateDomContents - First element in the property row is not a 'PrefabOverrideLabel'.");
+
+                    // Patch the first child in the row, which is going to the PrefabOverrideLabel.
+                    patches.PushBack(AZ::Dom::PatchOperation::ReplaceOperation(
+                        pathToProperty / 0 / PrefabPropertyEditorNodes::PrefabOverrideLabel::IsOverridden.GetName(), AZ::Dom::Value(true)));
+                    NotifyContentsChanged(patches);
+                }
+            }
+        }
+    }
+
+    bool PrefabComponentAdapter::CreateAndApplyComponentEditPatch(
+        AZStd::string_view relativePathFromOwningPrefab, const ReflectionAdapter::PropertyChangeInfo& propertyChangeInfo)
+    {
+        if (!propertyChangeInfo.newValue.IsOpaqueValue())
+        {
+            auto convertToRapidJsonOutcome = AZ::Dom::Json::WriteToRapidJsonDocument(
+                [propertyChangeInfo](AZ::Dom::Visitor& visitor)
+                {
+                    const bool copyStrings = false;
+                    return propertyChangeInfo.newValue.Accept(visitor, copyStrings);
+                });
+
+            if (!convertToRapidJsonOutcome.IsSuccess())
+            {
+                AZ_Assert(false, "PrefabDom value converted from AZ::Dom::Value.");
+                return false;
+            }
+            else
+            {
+                auto owningInstance = AZ::Interface<InstanceEntityMapperInterface>::Get()->FindOwningInstance(m_entityId);
+                if (!owningInstance.has_value())
+                {
+                    AZ_Assert(false, "Entity owning the component doesn't have an owning prefab instance.");
+                    return false;
+                }
+
+                auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
+
+                if (!prefabSystemComponentInterface)
+                {
+                    AZ_Assert(false, "PrefabSystemComponentInterface is not found.");
+                    return false;
+                }
+
+                const PrefabDom& templateDom = prefabSystemComponentInterface->FindTemplateDom(owningInstance->get().GetTemplateId());
+                PrefabDomPath prefabDomPathToComponentProperty(relativePathFromOwningPrefab.data());
+                const PrefabDomValue* beforeValueOfComponentProperty = prefabDomPathToComponentProperty.Get(templateDom);
+
+                PrefabDom afterValueOfComponentProperty = convertToRapidJsonOutcome.TakeValue();
+                ScopedUndoBatch undoBatch("Update component in a prefab template");
+                PrefabUndoComponentPropertyEdit* state = aznew PrefabUndoComponentPropertyEdit("Undo Updating Component");
+                state->SetParent(undoBatch.GetUndoBatch());
+                state->Capture(*beforeValueOfComponentProperty, afterValueOfComponentProperty, m_entityId, relativePathFromOwningPrefab);
+                state->Redo();
+
+                return true;
+            }
+        }
+        else
+        {
+            AZ_Assert(
+                false, "Opaque property encountered in PrefabComponentAdapter::GeneratePropertyEditPatch. It should have been a serialized value.");
+            return false;
+        }
+    }
+
+    bool PrefabComponentAdapter::CreateAndApplyComponentOverridePatch(
+        AZ::Dom::Path relativePathFromOwningPrefab, const ReflectionAdapter::PropertyChangeInfo& propertyChangeInfo)
+    {
+        if (!propertyChangeInfo.newValue.IsOpaqueValue())
+        {
+            auto convertToRapidJsonOutcome = AZ::Dom::Json::WriteToRapidJsonDocument(
+                [propertyChangeInfo](AZ::Dom::Visitor& visitor)
+                {
+                    const bool copyStrings = false;
+                    return propertyChangeInfo.newValue.Accept(visitor, copyStrings);
+                });
+
+            if (!convertToRapidJsonOutcome.IsSuccess())
+            {
+                AZ_Assert(false, "PrefabDom value converted from AZ::Dom::Value.");
+                return false;
+            }
+            else
+            {
+                auto owningInstance = AZ::Interface<InstanceEntityMapperInterface>::Get()->FindOwningInstance(m_entityId);
+                if (!owningInstance.has_value())
+                {
+                    AZ_Assert(false, "Entity owning the component doesn't have an owning prefab instance.");
+                    return false;
+                }
+
+                PrefabDom afterValueOfComponentProperty = convertToRapidJsonOutcome.TakeValue();
+                ScopedUndoBatch undoBatch("override a component in a nested prefab template");
+                PrefabUndoComponentPropertyOverride* state = aznew PrefabUndoComponentPropertyOverride("Undo overriding Component");
+                state->SetParent(undoBatch.GetUndoBatch());
+                state->CaptureAndRedo(owningInstance->get(), relativePathFromOwningPrefab, afterValueOfComponentProperty);
+
+                return true;
+            }
+        }
+        else
+        {
+            AZ_Assert(
+                false, "Opaque property encountered in PrefabComponentAdapter::GeneratePropertyEditPatch. It should have been a serialized value.");
+            return false;
+        }
+    }
 } // namespace AzToolsFramework::Prefab

+ 22 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/DocumentPropertyEditor/PrefabComponentAdapter.h

@@ -31,7 +31,29 @@ namespace AzToolsFramework::Prefab
 
         AZ::Dom::Value HandleMessage(const AZ::DocumentPropertyEditor::AdapterMessage& message) override;
 
+        //! Updates the DPE DOM using the property change information provided. If the property is owned by the focused prefab,
+        //! the change is applied as direct template edit. If the property is owned by descendant of the focused prefab, it is
+        //! applied as an override from the focused prefab.
+        //! @param propertyChangeInfo The object containing information about the property change.
+        void UpdateDomContents(const PropertyChangeInfo& propertyChangeInfo) override;
+
     private:
+
+        //! Creates and applies a component edit prefab patch using the property change information provided.
+        //! @param relativePathFromOwningPrefab The path to the property in the prefab from the owning prefab.
+        //! @param propertyChangeInfo The object containing information about the property change.
+        bool CreateAndApplyComponentEditPatch(
+            AZStd::string_view relativePathFromOwningPrefab,
+            const AZ::DocumentPropertyEditor::ReflectionAdapter::PropertyChangeInfo& propertyChangeInfo);
+
+        //! Creates and applies a component override prefab patch using the property change information provided. 
+        //! @param relativePathFromOwningPrefab The path to the property in the prefab from the owning prefab.
+        //! @param propertyChangeInfo The object containing information about the property change.
+        bool CreateAndApplyComponentOverridePatch(
+            AZ::Dom::Path relativePathFromOwningPrefab,
+            const AZ::DocumentPropertyEditor::ReflectionAdapter::PropertyChangeInfo& propertyChangeInfo);
+
+        AZStd::string m_entityAlias;
         AZStd::string m_componentAlias;
 
         PrefabOverridePublicInterface* m_prefabOverridePublicInterface = nullptr;

+ 5 - 3
Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabLoader.cpp

@@ -620,12 +620,14 @@ namespace AzToolsFramework
 
             // Now that the Instance has been created, convert it back into a Prefab DOM.  Because we
             // don't specify to skip default fields in the call to StoreInstanceInPrefabDom,
-            // this new DOM will have all fields from all classes except link ids.
-            if (!PrefabDomUtils::StoreInstanceInPrefabDom(
-                    loadedPrefabInstance, loadedTemplateDomRef, PrefabDomUtils::StoreFlags::StripLinkIds))
+            // this new DOM will have all fields from all classes.
+            if (!PrefabDomUtils::StoreInstanceInPrefabDom(loadedPrefabInstance, loadedTemplateDomRef))
             {
                 return false;
             }
+
+            // Remove 'LinkId' from the top level template DOM as only nested template DOMs should have 'LinkId' in them.
+            loadedTemplateDomRef.RemoveMember(PrefabDomUtils::LinkIdName);
             return true;
         }
 

+ 67 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Undo/PrefabUndoComponentPropertyEdit.cpp

@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
+#include <AzToolsFramework/Prefab/Instance/InstanceToTemplateInterface.h>
+#include <AzToolsFramework/Prefab/Undo/PrefabUndoUtils.h>
+#include <AzToolsFramework/Prefab/Undo/PrefabUndoComponentPropertyEdit.h>
+
+namespace AzToolsFramework::Prefab
+{
+    PrefabUndoComponentPropertyEdit::PrefabUndoComponentPropertyEdit(const AZStd::string& undoOperationName)
+        : PrefabUndoBase(undoOperationName)
+    {
+        m_instanceEntityMapperInterface = AZ::Interface<InstanceEntityMapperInterface>::Get();
+        AZ_Assert(m_instanceEntityMapperInterface, "Failed to grab instance entity mapper interface");
+    }
+
+    void PrefabUndoComponentPropertyEdit::Capture(
+        const PrefabDomValue& initialState,
+        const PrefabDomValue& endState,
+        AZ::EntityId entityId,
+        AZStd::string_view pathToComponentProperty,
+        bool updateCache)
+    {
+        // get the entity alias for future undo/redo
+        auto instanceReference = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
+        AZ_Error(
+            "Prefab", instanceReference, "Failed to find an owning instance for the entity with id %llu.", static_cast<AZ::u64>(entityId));
+        Instance& instance = instanceReference->get();
+        m_templateId = instance.GetTemplateId();
+
+
+        // generate undo/redo patches
+        PrefabUndoUtils::GenerateUpdateEntityPatch(m_redoPatch, initialState, endState, pathToComponentProperty);
+        PrefabUndoUtils::GenerateUpdateEntityPatch(m_undoPatch, endState, initialState, pathToComponentProperty);
+
+        // Preemptively updates the cached DOM to prevent reloading instance DOM.
+        if (updateCache)
+        {
+            PrefabDomReference cachedOwningInstanceDom = instance.GetCachedInstanceDom();
+            if (cachedOwningInstanceDom.has_value())
+            {
+                PrefabUndoUtils::UpdateEntityInInstanceDom(cachedOwningInstanceDom, endState, pathToComponentProperty);
+            }
+        }
+    }
+
+    void PrefabUndoComponentPropertyEdit::Undo()
+    {
+        m_instanceToTemplateInterface->PatchTemplate(m_undoPatch, m_templateId);
+    }
+
+    void PrefabUndoComponentPropertyEdit::Redo()
+    {
+        Redo(AZStd::nullopt);
+    }
+
+    void PrefabUndoComponentPropertyEdit::Redo(InstanceOptionalConstReference instance)
+    {
+        m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId, instance);
+    }
+}

+ 40 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Undo/PrefabUndoComponentPropertyEdit.h

@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AzToolsFramework/Prefab/Undo/PrefabUndoBase.h>
+
+namespace AzToolsFramework::Prefab
+{
+    class InstanceEntityMapperInterface;
+
+    //! Undo class for handling updating component properties of the focused prefab.
+    class PrefabUndoComponentPropertyEdit : public PrefabUndoBase
+    {
+    public:
+        AZ_RTTI(PrefabUndoComponentPropertyEdit, "{2B54AD53-329F-45B7-BAEB-737592D0B726}", PrefabUndoBase);
+        AZ_CLASS_ALLOCATOR(PrefabUndoComponentPropertyEdit, AZ::SystemAllocator);
+
+        explicit PrefabUndoComponentPropertyEdit(const AZStd::string& undoOperationName);
+
+        void Capture(
+            const PrefabDomValue& initialState,
+            const PrefabDomValue& endState,
+            AZ::EntityId entityId,
+            AZStd::string_view pathToComponentProperty,
+            bool updateCache = true);
+
+        void Undo() override;
+        void Redo() override;
+        void Redo(InstanceOptionalConstReference instanceToExclude) override;
+
+    private:
+        InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr;
+    };
+} // namespace AzToolsFramework::Prefab

+ 164 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Undo/PrefabUndoComponentPropertyOverride.cpp

@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#include <AzToolsFramework/Prefab/Instance/InstanceToTemplateInterface.h>
+#include <AzToolsFramework/Prefab/Link/Link.h>
+#include <AzToolsFramework/Prefab/Overrides/PrefabOverridePublicInterface.h>
+#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
+#include <AzToolsFramework/Prefab/PrefabInstanceUtils.h>
+#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
+#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
+#include <AzToolsFramework/Prefab/Undo/PrefabUndoComponentPropertyOverride.h>
+#include <AzToolsFramework/Prefab/Undo/PrefabUndoUtils.h>
+
+namespace AzToolsFramework
+{
+    namespace Prefab
+    {
+        PrefabUndoComponentPropertyOverride::PrefabUndoComponentPropertyOverride(const AZStd::string& undoOperationName)
+            : UndoSystem::URSequencePoint(undoOperationName)
+            , m_linkId(InvalidLinkId)
+        {
+            m_prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
+            AZ_Assert(m_prefabSystemComponentInterface, "PrefabUndoComponentPropertyOverride - PrefabSystemComponentInterface not found.");
+
+            m_instanceToTemplateInterface = AZ::Interface<InstanceToTemplateInterface>::Get();
+            AZ_Assert(m_instanceToTemplateInterface, "PrefabUndoComponentPropertyOverride - InstanceToTemplateInterface not found.");
+
+            m_prefabOverridePublicInterface = AZ::Interface<PrefabOverridePublicInterface>::Get();
+            AZ_Assert(m_prefabOverridePublicInterface, "PrefabUndoComponentPropertyOverride - PrefabOverridePublicInterface not found.");
+
+            m_prefabFocusInterface = AZ::Interface<Prefab::PrefabFocusInterface>::Get();
+            AZ_Assert(
+                m_prefabFocusInterface != nullptr,
+                "PrefabUndoComponentPropertyOverride - PrefabFocusInterface not found.");
+        }
+
+        bool PrefabUndoComponentPropertyOverride::Changed() const
+        {
+            return true;
+        }
+
+        void PrefabUndoComponentPropertyOverride::CaptureAndRedo(
+            Instance& owningInstance,
+            AZ::Dom::Path relativePathFromOwningPrefab,
+            const PrefabDomValue& afterStateOfComponentProperty)
+        {
+            InstanceOptionalReference focusedInstance =
+                m_prefabFocusInterface->GetFocusedPrefabInstance(AzFramework::EntityContextId::CreateNull());
+
+            AZ_Assert(focusedInstance.has_value(), "PrefabUndoComponentPropertyOverride::CaptureAndRedo - Focused instance not found.");
+
+            InstanceClimbUpResult climbUpResult = PrefabInstanceUtils::ClimbUpToTargetOrRootInstance(owningInstance, &focusedInstance->get());
+            AZ_Assert(
+                climbUpResult.m_isTargetInstanceReached,
+                "PrefabUndoComponentPropertyOverride::CaptureAndRedo - "
+                "Owning prefab instance should be a descendant of the focused prefab instance.");
+
+            m_linkId = climbUpResult.m_climbedInstances.back()->GetLinkId();
+            AZ_Assert(
+                m_linkId != InvalidLinkId,
+                "PrefabUndoComponentPropertyOverride::CaptureAndRedo - "
+                "Could not get the link id between focused instance and top instance.");
+
+            LinkReference link = m_prefabSystemComponentInterface->FindLink(m_linkId);
+            if (!link.has_value())
+            {
+                AZ_Error("Prefab", false, "PrefabUndoComponentPropertyOverride::CaptureAndRedo - Could not get the link for override editing.");
+                return;
+            }
+
+            m_overriddenPropertyPathFromFocusedPrefab =
+                AZ::Dom::Path(PrefabInstanceUtils::GetRelativePathFromClimbedInstances(climbUpResult.m_climbedInstances, true));
+
+            m_overriddenPropertyPathFromFocusedPrefab /= relativePathFromOwningPrefab;
+
+            const TemplateId topTemplateId = link->get().GetTargetTemplateId();
+            const PrefabDom& topTemplateDom = m_prefabSystemComponentInterface->FindTemplateDom(topTemplateId);
+
+            {
+                AZ::Dom::Path pathFromFocusedPrefab(PrefabDomUtils::InstancesName);
+                pathFromFocusedPrefab /= climbUpResult.m_climbedInstances.back()->GetInstanceAlias();
+                pathFromFocusedPrefab /= m_overriddenPropertyPathFromFocusedPrefab;
+
+                // This scope is added to limit their usage and ensure DOM is not modified when it is being used.
+                // DOM value pointers can't be relied upon if the original DOM gets modified after pointer creation.
+                PrefabDomPath overriddenPropertyDomPath(pathFromFocusedPrefab.ToString().c_str());
+                const PrefabDomValue* overriddenPropertyDomInTopTemplate = overriddenPropertyDomPath.Get(topTemplateDom);
+
+                
+                PrefabDom overridePatches;
+                if (overriddenPropertyDomInTopTemplate)
+                {
+                    PrefabUndoUtils::GenerateUpdateEntityPatch(
+                        overridePatches,
+                        *overriddenPropertyDomInTopTemplate,
+                        afterStateOfComponentProperty,
+                        m_overriddenPropertyPathFromFocusedPrefab.ToString());
+
+                    // Remove the subtree and cache the subtree for undo.
+                    m_componentPropertyOverrideSubTree =
+                        AZStd::move(link->get().RemoveOverrides(m_overriddenPropertyPathFromFocusedPrefab));
+
+                    // Redo - Add the override patches to the tree.
+                    link->get().AddOverrides(overridePatches);
+
+                    PrefabDomReference cachedFocusedInstanceDom = focusedInstance->get().GetCachedInstanceDom();
+
+                    // Preemptively updates the cached DOM to prevent reloading instance DOM.
+                    if (cachedFocusedInstanceDom.has_value())
+                    {
+                        PrefabUndoUtils::UpdateEntityInInstanceDom(
+                            cachedFocusedInstanceDom, afterStateOfComponentProperty, pathFromFocusedPrefab.ToString());
+                    }
+
+                    // Redo - Update target template of the link.
+                    link->get().UpdateTarget();
+                    m_prefabSystemComponentInterface->SetTemplateDirtyFlag(link->get().GetTargetTemplateId(), true);
+                    m_prefabSystemComponentInterface->PropagateTemplateChanges(link->get().GetTargetTemplateId());
+                }
+                else
+                {
+                    AZ_Warning(
+                        "Prefab",
+                        false,
+                        "PrefabUndoComponentPropertyOverride::CaptureAndRedo - Cannot reach overridden property value from the DOM of the prefab being edited.");
+                }
+            }
+        }
+
+        void PrefabUndoComponentPropertyOverride::Undo()
+        {
+            UpdateLink();
+        }
+
+        void PrefabUndoComponentPropertyOverride::Redo()
+        {
+            UpdateLink();
+        }
+
+        void PrefabUndoComponentPropertyOverride::UpdateLink()
+        {
+            LinkReference link = m_prefabSystemComponentInterface->FindLink(m_linkId);
+            if (link.has_value())
+            {
+                // In redo, after-state subtrees in map will be moved to the link tree.
+                // In undo, before-state subtrees in map will be moved to the link tree.
+                // The previous states of subtrees in link are moved back to the map for next undo/redo if any.
+                PrefabOverridePrefixTree subtreeInLink =
+                    AZStd::move(link->get().RemoveOverrides(m_overriddenPropertyPathFromFocusedPrefab));
+                link->get().AddOverrides(m_overriddenPropertyPathFromFocusedPrefab, AZStd::move(m_componentPropertyOverrideSubTree));
+                m_componentPropertyOverrideSubTree = AZStd::move(subtreeInLink);
+
+                link->get().UpdateTarget();
+                m_prefabSystemComponentInterface->SetTemplateDirtyFlag(link->get().GetTargetTemplateId(), true);
+                m_prefabSystemComponentInterface->PropagateTemplateChanges(link->get().GetTargetTemplateId());
+            }
+        }
+    } // namespace Prefab
+} // namespace AzToolsFramework

+ 55 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Undo/PrefabUndoComponentPropertyOverride.h

@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AzToolsFramework/Undo/UndoSystem.h>
+
+namespace AzToolsFramework::Prefab
+{
+    
+    class InstanceToTemplateInterface;
+    class PrefabFocusInterface;
+    class PrefabOverridePublicInterface;
+    class PrefabSystemComponentInterface;
+
+    //! Undo class for handling updating component properties of a prefab as overrides from focused prefab.
+    class PrefabUndoComponentPropertyOverride : public UndoSystem::URSequencePoint
+    {
+    public:
+        AZ_RTTI(PrefabUndoComponentPropertyOverride, "{DF46772A-4D01-4267-A218-778758804C66}", UndoSystem::URSequencePoint);
+        AZ_CLASS_ALLOCATOR(PrefabUndoComponentPropertyOverride, AZ::SystemAllocator);
+
+        explicit PrefabUndoComponentPropertyOverride(const AZStd::string& undoOperationName);
+
+        bool Changed() const override;
+        void Undo() override;
+        void Redo() override;
+
+        // The function to generate override subtrees for updating the provided entity as overrides.
+        // Redo should not be called after. It does redo in capture because adding override patches here allows us
+        // to generate patches with correct indices and add them to tree in one place. Two operations won't be disconnected.
+        void CaptureAndRedo(Instance& owningInstance, AZ::Dom::Path relativePathFromOwningPrefab, const PrefabDomValue& afterStateOfComponentProperty);
+
+    private:
+        // The function to update link during undo and redo.
+        void UpdateLink();
+
+        PrefabOverridePrefixTree m_componentPropertyOverrideSubTree;
+
+        // Link that connects the linked instance and the focused instance.
+        LinkId m_linkId;
+
+        AZ::Dom::Path m_overriddenPropertyPathFromFocusedPrefab;
+
+        PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr;
+        InstanceToTemplateInterface* m_instanceToTemplateInterface = nullptr;
+        PrefabOverridePublicInterface* m_prefabOverridePublicInterface = nullptr;
+        Prefab::PrefabFocusInterface* m_prefabFocusInterface = nullptr;
+    };
+} // namespace AzToolsFramework::Prefab

+ 4 - 0
Code/Framework/AzToolsFramework/AzToolsFramework/aztoolsframework_files.cmake

@@ -909,6 +909,10 @@ set(FILES
     Prefab/Undo/PrefabUndoAddEntity.cpp
     Prefab/Undo/PrefabUndoAddEntityAsOverride.h
     Prefab/Undo/PrefabUndoAddEntityAsOverride.cpp
+    Prefab/Undo/PrefabUndoComponentPropertyEdit.h
+    Prefab/Undo/PrefabUndoComponentPropertyEdit.cpp
+    Prefab/Undo/PrefabUndoComponentPropertyOverride.h
+    Prefab/Undo/PrefabUndoComponentPropertyOverride.cpp
     Prefab/Undo/PrefabUndoDelete.h
     Prefab/Undo/PrefabUndoDelete.cpp
     Prefab/Undo/PrefabUndoDeleteAsOverride.h