/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include "LyShine_precompiled.h" #include "UiSerialize.h" #include #include #include #include "UiInteractableComponent.h" #include #include #include #include #include #include #include #include #include #include #include //////////////////////////////////////////////////////////////////////////////////////////////////// // NAMESPACE FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////////////////// namespace UiSerialize { //////////////////////////////////////////////////////////////////////////////////////////////////// class CryStringTCharSerializer : public AZ::SerializeContext::IDataSerializer { /// Return the size of binary buffer necessary to store the value in binary format size_t GetRequiredBinaryBufferSize(const void* classPtr) const { const CryStringT* string = reinterpret_cast*>(classPtr); return string->length() + 1; } /// Store the class data into a stream. size_t Save(const void* classPtr, AZ::IO::GenericStream& stream, bool isDataBigEndian /*= false*/) override { const CryStringT* string = reinterpret_cast*>(classPtr); const char* data = string->c_str(); return static_cast(stream.Write(string->length() + 1, reinterpret_cast(data))); } size_t DataToText(AZ::IO::GenericStream& in, AZ::IO::GenericStream& out, bool isDataBigEndian /*= false*/) override { size_t len = in.GetLength(); char* buffer = static_cast(azmalloc(len)); in.Read(in.GetLength(), reinterpret_cast(buffer)); AZStd::string outText = buffer; azfree(buffer); return static_cast(out.Write(outText.size(), outText.data())); } size_t TextToData(const char* text, unsigned int textVersion, AZ::IO::GenericStream& stream, bool isDataBigEndian /*= false*/) override { (void)textVersion; size_t len = strlen(text) + 1; stream.Seek(0, AZ::IO::GenericStream::ST_SEEK_BEGIN); return static_cast(stream.Write(len, reinterpret_cast(text))); } bool Load(void* classPtr, AZ::IO::GenericStream& stream, unsigned int /*version*/, bool isDataBigEndian /*= false*/) override { CryStringT* string = reinterpret_cast*>(classPtr); size_t len = stream.GetLength(); char* buffer = static_cast(azmalloc(len)); stream.Read(len, reinterpret_cast(buffer)); *string = buffer; azfree(buffer); return true; } bool CompareValueData(const void* lhs, const void* rhs) override { return AZ::SerializeContext::EqualityCompareHelper >::CompareValues(lhs, rhs); } }; ////////////////////////////////////////////////////////////////////////// void UiOffsetsScriptConstructor(UiTransform2dInterface::Offsets* thisPtr, AZ::ScriptDataContext& dc) { int numArgs = dc.GetNumArguments(); const int noArgsGiven = 0; const int allArgsGiven = 4; switch (numArgs) { case noArgsGiven: { *thisPtr = UiTransform2dInterface::Offsets(); } break; case allArgsGiven: { if (dc.IsNumber(0) && dc.IsNumber(1) && dc.IsNumber(2) && dc.IsNumber(3)) { float left = 0; float top = 0; float right = 0; float bottom = 0; dc.ReadArg(0, left); dc.ReadArg(1, top); dc.ReadArg(2, right); dc.ReadArg(3, bottom); *thisPtr = UiTransform2dInterface::Offsets(left, top, right, bottom); } else { dc.GetScriptContext()->Error(AZ::ScriptContext::ErrorType::Error, true, "When providing 4 arguments to UiOffsets(), all must be numbers!"); } } break; default: { dc.GetScriptContext()->Error(AZ::ScriptContext::ErrorType::Error, true, "UiOffsets() accepts only 0 or 4 arguments, not %d!", numArgs); } break; } } ////////////////////////////////////////////////////////////////////////// void UiAnchorsScriptConstructor(UiTransform2dInterface::Anchors* thisPtr, AZ::ScriptDataContext& dc) { int numArgs = dc.GetNumArguments(); const int noArgsGiven = 0; const int allArgsGiven = 4; switch (numArgs) { case noArgsGiven: { *thisPtr = UiTransform2dInterface::Anchors(); } break; case allArgsGiven: { if (dc.IsNumber(0) && dc.IsNumber(1) && dc.IsNumber(2) && dc.IsNumber(3)) { float left = 0; float top = 0; float right = 0; float bottom = 0; dc.ReadArg(0, left); dc.ReadArg(1, top); dc.ReadArg(2, right); dc.ReadArg(3, bottom); *thisPtr = UiTransform2dInterface::Anchors(left, top, right, bottom); } else { dc.GetScriptContext()->Error(AZ::ScriptContext::ErrorType::Error, true, "When providing 4 arguments to UiAnchors(), all must be numbers!"); } } break; default: { dc.GetScriptContext()->Error(AZ::ScriptContext::ErrorType::Error, true, "UiAnchors() accepts only 0 or 4 arguments, not %d!", numArgs); } break; } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SetAnchorLeft(UiTransform2dInterface::Anchors* anchor, float left) { if (anchor) { anchor->m_left = left; } else { AZ_ErrorOnce("Script Canvas", false, "UI Script tried to set left on null anchor.") } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SetAnchorTop(UiTransform2dInterface::Anchors* anchor, float top) { if (anchor) { anchor->m_top = top; } else { AZ_ErrorOnce("Script Canvas", false, "UI Script tried to set top on null anchor.") } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SetAnchorRight(UiTransform2dInterface::Anchors* anchor, float right) { if (anchor) { anchor->m_right = right; } else { AZ_ErrorOnce("Script Canvas", false, "UI Script tried to set right on null anchor.") } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SetAnchorBottom(UiTransform2dInterface::Anchors* anchor, float bottom) { if (anchor) { anchor->m_bottom = bottom; } else { AZ_ErrorOnce("Script Canvas", false, "UI Script tried to set bottom on null anchor.") } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SetAnchors(UiTransform2dInterface::Anchors* anchor, float left, float top, float right, float bottom) { if (anchor) { anchor->m_left = left; anchor->m_top = top; anchor->m_right = right; anchor->m_bottom = bottom; } else { AZ_ErrorOnce("Script Canvas", false, "UI Script tried to set values on null anchor.") } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SetOffsetLeft(UiTransform2dInterface::Offsets* offset, float left) { if (offset) { offset->m_left = left; } else { AZ_ErrorOnce("Script Canvas", false, "UI Script tried to set left on null offset.") } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SetOffsetTop(UiTransform2dInterface::Offsets* offset, float top) { if (offset) { offset->m_top = top; } else { AZ_ErrorOnce("Script Canvas", false, "UI Script tried to set top on null offset.") } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SetOffsetRight(UiTransform2dInterface::Offsets* offset, float right) { if (offset) { offset->m_right = right; } else { AZ_ErrorOnce("Script Canvas", false, "UI Script tried to set right on null offset.") } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SetOffsetBottom(UiTransform2dInterface::Offsets* offset, float bottom) { if (offset) { offset->m_bottom = bottom; } else { AZ_ErrorOnce("Script Canvas", false, "UI Script tried to set bottom on null offset.") } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SetOffsets(UiTransform2dInterface::Offsets* offset, float left, float top, float right, float bottom) { if (offset) { offset->m_left = left; offset->m_top = top; offset->m_right = right; offset->m_bottom = bottom; } else { AZ_ErrorOnce("Script Canvas", false, "UI Script tried to set values on null offset.") } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SetPaddingLeft(UiLayoutInterface::Padding* padding, int left) { if (padding) { padding->m_left = left; } else { AZ_ErrorOnce("Script Canvas", false, "UI Script tried to set left on null padding.") } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SetPaddingTop(UiLayoutInterface::Padding* padding, int top) { if (padding) { padding->m_top = top; } else { AZ_ErrorOnce("Script Canvas", false, "UI Script tried to set top on null padding.") } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SetPaddingRight(UiLayoutInterface::Padding* padding, int right) { if (padding) { padding->m_right = right; } else { AZ_ErrorOnce("Script Canvas", false, "UI Script tried to set right on null padding.") } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SetPaddingBottom(UiLayoutInterface::Padding* padding, int bottom) { if (padding) { padding->m_bottom = bottom; } else { AZ_ErrorOnce("Script Canvas", false, "UI Script tried to set bottom on null padding.") } } //////////////////////////////////////////////////////////////////////////////////////////////////// void SetPadding(UiLayoutInterface::Padding* padding, int left, int top, int right, int bottom) { if (padding) { padding->m_left = left; padding->m_top = top; padding->m_right = right; padding->m_bottom = bottom; } else { AZ_ErrorOnce("Script Canvas", false, "UI Script tried to set values on null padding.") } } //////////////////////////////////////////////////////////////////////////////////////////////////// void ReflectUiTypes(AZ::ReflectContext* context) { AZ::SerializeContext* serializeContext = azrtti_cast(context); AZ::BehaviorContext* behaviorContext = azrtti_cast(context); if (serializeContext) { serializeContext->Class()-> Field("r", &ColorF::r)-> Field("g", &ColorF::g)-> Field("b", &ColorF::b)-> Field("a", &ColorF::a); serializeContext->Class()-> Field("r", &ColorB::r)-> Field("g", &ColorB::g)-> Field("b", &ColorB::b)-> Field("a", &ColorB::a); } // Vec2 (still used in UI Animation sequence splines) { if (serializeContext) { serializeContext->Class()-> Field("x", &Vec2::x)-> Field("y", &Vec2::y); } } // Vec3 (possibly no longer used) { if (serializeContext) { serializeContext->Class()-> Field("x", &Vec3::x)-> Field("y", &Vec3::y)-> Field("z", &Vec3::z); } } // Anchors { if (serializeContext) { serializeContext->Class()-> Field("left", &UiTransform2dInterface::Anchors::m_left)-> Field("top", &UiTransform2dInterface::Anchors::m_top)-> Field("right", &UiTransform2dInterface::Anchors::m_right)-> Field("bottom", &UiTransform2dInterface::Anchors::m_bottom); } if (behaviorContext) { behaviorContext->Class("UiAnchors") ->Constructor<>() ->Constructor() ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) ->Attribute(AZ::Script::Attributes::ConstructorOverride, &UiAnchorsScriptConstructor) ->Property("left", BehaviorValueProperty(&UiTransform2dInterface::Anchors::m_left)) ->Property("top", BehaviorValueProperty(&UiTransform2dInterface::Anchors::m_top)) ->Property("right", BehaviorValueProperty(&UiTransform2dInterface::Anchors::m_right)) ->Property("bottom", BehaviorValueProperty(&UiTransform2dInterface::Anchors::m_bottom)) ->Method("SetLeft", SetAnchorLeft) ->Method("SetTop", SetAnchorTop) ->Method("SetRight", SetAnchorRight) ->Method("SetBottom", SetAnchorBottom) ->Method("SetAnchors", SetAnchors); } } // ParticleColorKeyframe { if (serializeContext) { serializeContext->Class() ->Field("Time", &UiParticleEmitterInterface::ParticleColorKeyframe::time) ->Field("Color", &UiParticleEmitterInterface::ParticleColorKeyframe::color) ->Field("InTangent", &UiParticleEmitterInterface::ParticleColorKeyframe::inTangent) ->Field("OutTangent", &UiParticleEmitterInterface::ParticleColorKeyframe::outTangent); } } // ParticleFloatKeyframe { if (serializeContext) { serializeContext->Class() ->Field("Time", &UiParticleEmitterInterface::ParticleFloatKeyframe::time) ->Field("Multiplier", &UiParticleEmitterInterface::ParticleFloatKeyframe::multiplier) ->Field("InTangent", &UiParticleEmitterInterface::ParticleFloatKeyframe::inTangent) ->Field("OutTangent", &UiParticleEmitterInterface::ParticleFloatKeyframe::outTangent); } } // Offsets { if (serializeContext) { serializeContext->Class()-> Field("left", &UiTransform2dInterface::Offsets::m_left)-> Field("top", &UiTransform2dInterface::Offsets::m_top)-> Field("right", &UiTransform2dInterface::Offsets::m_right)-> Field("bottom", &UiTransform2dInterface::Offsets::m_bottom); } if (behaviorContext) { behaviorContext->Class("UiOffsets") ->Constructor<>() ->Constructor() ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) ->Attribute(AZ::Script::Attributes::ConstructorOverride, &UiOffsetsScriptConstructor) ->Property("left", BehaviorValueProperty(&UiTransform2dInterface::Offsets::m_left)) ->Property("top", BehaviorValueProperty(&UiTransform2dInterface::Offsets::m_top)) ->Property("right", BehaviorValueProperty(&UiTransform2dInterface::Offsets::m_right)) ->Property("bottom", BehaviorValueProperty(&UiTransform2dInterface::Offsets::m_bottom)) ->Method("SetLeft", SetOffsetLeft) ->Method("SetTop", SetOffsetTop) ->Method("SetRight", SetOffsetRight) ->Method("SetBottom", SetOffsetBottom) ->Method("SetOffsets", SetOffsets); } } // Padding { if (serializeContext) { serializeContext->Class()-> Field("left", &UiLayoutInterface::Padding::m_left)-> Field("top", &UiLayoutInterface::Padding::m_top)-> Field("right", &UiLayoutInterface::Padding::m_right)-> Field("bottom", &UiLayoutInterface::Padding::m_bottom); } if (behaviorContext) { behaviorContext->Class("UiPadding") ->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value) ->Property("left", BehaviorValueProperty(&UiLayoutInterface::Padding::m_left)) ->Property("right", BehaviorValueProperty(&UiLayoutInterface::Padding::m_right)) ->Property("top", BehaviorValueProperty(&UiLayoutInterface::Padding::m_top)) ->Property("bottom", BehaviorValueProperty(&UiLayoutInterface::Padding::m_bottom)) ->Method("SetLeft", SetPaddingLeft) ->Method("SetTop", SetPaddingTop) ->Method("SetRight", SetPaddingRight) ->Method("SetBottom", SetPaddingBottom) ->Method("SetPadding", SetPadding); } } // UiLayout enums { if (behaviorContext) { behaviorContext->Enum<(int)UiLayoutInterface::HorizontalOrder::LeftToRight>("eUiHorizontalOrder_LeftToRight") ->Enum<(int)UiLayoutInterface::HorizontalOrder::RightToLeft>("eUiHorizontalOrder_RightToLeft") ->Enum<(int)UiLayoutInterface::VerticalOrder::TopToBottom>("eUiVerticalOrder_TopToBottom") ->Enum<(int)UiLayoutInterface::VerticalOrder::BottomToTop>("eUiVerticalOrder_BottomToTop"); } } // IDraw2d enums { if (behaviorContext) { behaviorContext->Enum<(int)IDraw2d::HAlign::Left>("eUiHAlign_Left") ->Enum<(int)IDraw2d::HAlign::Center>("eUiHAlign_Center") ->Enum<(int)IDraw2d::HAlign::Right>("eUiHAlign_Right") ->Enum<(int)IDraw2d::VAlign::Top>("eUiVAlign_Top") ->Enum<(int)IDraw2d::VAlign::Center>("eUiVAlign_Center") ->Enum<(int)IDraw2d::VAlign::Bottom>("eUiVAlign_Bottom"); } } if (serializeContext) { serializeContext->Class >()-> Serializer(&AZ::Serialize::StaticInstance::s_instance); serializeContext->Class() ->Version(2, &PrefabFileObject::VersionConverter) ->Field("RootEntity", &PrefabFileObject::m_rootEntityId) ->Field("Entities", &PrefabFileObject::m_entities); serializeContext->Class() ->Version(1) ->Field("SerializeString", &AnimationData::m_serializeData); // deprecate old classes that no longer exist serializeContext->ClassDeprecate("UiCanvasEditor", "{65682E87-B573-435B-88CB-B4C12B71EEEE}"); serializeContext->ClassDeprecate("ImageAsset", "{138E471A-F3AE-404A-9075-EDC7488C97FC}"); AzFramework::SimpleAssetReference::Register(*serializeContext); AzFramework::SimpleAssetReference::Register(*serializeContext); UiInteractableComponent::Reflect(serializeContext); } if (behaviorContext) { UiInteractableComponent::Reflect(behaviorContext); behaviorContext->EBus("UiLayoutBus") ->Event("GetHorizontalChildAlignment", &UiLayoutBus::Events::GetHorizontalChildAlignment) ->Event("SetHorizontalChildAlignment", &UiLayoutBus::Events::SetHorizontalChildAlignment) ->Event("GetVerticalChildAlignment", &UiLayoutBus::Events::GetVerticalChildAlignment) ->Event("SetVerticalChildAlignment", &UiLayoutBus::Events::SetVerticalChildAlignment) ->Event("GetIgnoreDefaultLayoutCells", &UiLayoutBus::Events::GetIgnoreDefaultLayoutCells) ->Event("SetIgnoreDefaultLayoutCells", &UiLayoutBus::Events::SetIgnoreDefaultLayoutCells); } } //////////////////////////////////////////////////////////////////////////////////////////////////// bool PrefabFileObject::VersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement) { if (classElement.GetVersion() == 1) { // this is an old UI prefab (prior to UI Slices). We need to move all of the owned child entities into a // separate list and have the references to them be via entity ID // Find the m_rootEntity in the PrefabFileObject, in the old format this is an entity, // we will replace it with an entityId int rootEntityIndex = classElement.FindElement(AZ_CRC("RootEntity", 0x3cead042)); if (rootEntityIndex == -1) { return false; } AZ::SerializeContext::DataElementNode& rootEntityNode = classElement.GetSubElement(rootEntityIndex); // All UI element entities will be copied to this container and then added to the m_childEntities list AZStd::vector copiedEntities; // recursively process the root element and all of its child elements, copying their child entities to the // entities container and replacing them with EntityIds if (!UiElementComponent::MoveEntityAndDescendantsToListAndReplaceWithEntityId(context, rootEntityNode, -1, copiedEntities)) { return false; } // Create the child entities member (which is a generic vector) using entityVector = AZStd::vector; AZ::SerializeContext::ClassData* classData = AZ::SerializeGenericTypeInfo::GetGenericInfo()->GetClassData(); int entitiesIndex = classElement.AddElement(context, "Entities", *classData); if (entitiesIndex == -1) { return false; } AZ::SerializeContext::DataElementNode& entitiesNode = classElement.GetSubElement(entitiesIndex); // now add all of the copied entities to the entities vector node for (AZ::SerializeContext::DataElementNode& entityElement : copiedEntities) { entityElement.SetName("element"); // all elements in the Vector should have this name entitiesNode.AddElement(entityElement); } } return true; } //////////////////////////////////////////////////////////////////////////////////////////////// // Helper function to VersionConverter to move three state actions from the derived interactable // to the interactable base class bool MoveToInteractableStateActions( AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& srcClassElement, const char* stateActionsElementName, const char* colorElementName, const char* alphaElementName, const char* spriteElementName) { // Note, we can assume that srcClassElement will stay in the same place in memory during this function // But the base class (and everything in it) will move around in memory as we remove elements from // srcClassElement. So it is improtant not to hold only any indicies or references to the base class. int interactableBaseClassIndex = srcClassElement.FindElement(AZ_CRC("BaseClass1", 0xd4925735)); // Add a new element for the state actions. int stateActionsIndex = srcClassElement.GetSubElement(interactableBaseClassIndex) .AddElement >(context, stateActionsElementName); if (stateActionsIndex == -1) { // Error adding the new sub element AZ_Error("Serialization", false, "AddElement failed for %s", stateActionsElementName); return false; } { interactableBaseClassIndex = srcClassElement.FindElement(AZ_CRC("BaseClass1", 0xd4925735)); AZ::SerializeContext::DataElementNode& dstClassElement = srcClassElement.GetSubElement(interactableBaseClassIndex); stateActionsIndex = dstClassElement.FindElement(AZ_CRC(stateActionsElementName)); AZ::SerializeContext::DataElementNode& stateActionsNode = dstClassElement.GetSubElement(stateActionsIndex); int colorIndex = stateActionsNode.AddElement(context, "element"); AZ::SerializeContext::DataElementNode& colorNode = stateActionsNode.GetSubElement(colorIndex); if (!LyShine::MoveElement(context, srcClassElement, colorNode, colorElementName, "Color")) { return false; } { // In the latest version of UiInteractableStateColor the color is an AZ::Color but in the // version we are converting from (before UiInteractableStateColor existed) colors were stored // as Vector3. Since the UiInteractableStateColor we just created will be at the latest version // we need to convert the color to an AZ::Color now. // Note that indices will have changed since MoveElement was called. interactableBaseClassIndex = srcClassElement.FindElement(AZ_CRC("BaseClass1", 0xd4925735)); AZ::SerializeContext::DataElementNode& dstBaseClassElement = srcClassElement.GetSubElement(interactableBaseClassIndex); stateActionsIndex = dstBaseClassElement.FindElement(AZ_CRC(stateActionsElementName)); AZ::SerializeContext::DataElementNode& dstStateActionsNode = dstBaseClassElement.GetSubElement(stateActionsIndex); colorIndex = dstStateActionsNode.FindElement(AZ_CRC("element")); AZ::SerializeContext::DataElementNode& dstColorNode = dstStateActionsNode.GetSubElement(colorIndex); if (!LyShine::ConvertSubElementFromVector3ToAzColor(context, dstColorNode, "Color")) { return false; } } } { interactableBaseClassIndex = srcClassElement.FindElement(AZ_CRC("BaseClass1", 0xd4925735)); AZ::SerializeContext::DataElementNode& dstClassElement = srcClassElement.GetSubElement(interactableBaseClassIndex); stateActionsIndex = dstClassElement.FindElement(AZ_CRC(stateActionsElementName)); AZ::SerializeContext::DataElementNode& stateActionsNode = dstClassElement.GetSubElement(stateActionsIndex); int alphaIndex = stateActionsNode.AddElement(context, "element"); AZ::SerializeContext::DataElementNode& alphaNode = stateActionsNode.GetSubElement(alphaIndex); if (!LyShine::MoveElement(context, srcClassElement, alphaNode, alphaElementName, "Alpha")) { return false; } } { interactableBaseClassIndex = srcClassElement.FindElement(AZ_CRC("BaseClass1", 0xd4925735)); AZ::SerializeContext::DataElementNode& dstClassElement = srcClassElement.GetSubElement(interactableBaseClassIndex); stateActionsIndex = dstClassElement.FindElement(AZ_CRC(stateActionsElementName)); AZ::SerializeContext::DataElementNode& stateActionsNode = dstClassElement.GetSubElement(stateActionsIndex); int spriteIndex = stateActionsNode.AddElement(context, "element"); AZ::SerializeContext::DataElementNode& spriteNode = stateActionsNode.GetSubElement(spriteIndex); if (!LyShine::MoveElement(context, srcClassElement, spriteNode, spriteElementName, "Sprite")) { return false; } } // if the field did not exist then we do not report an error return true; } }