/* * 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 "UiLayoutColumnComponent.h" #include #include #include #include #include #include #include "UiSerialize.h" //////////////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC MEMBER FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// UiLayoutColumnComponent::UiLayoutColumnComponent() : m_padding(UiLayoutInterface::Padding()) , m_spacing(5) , m_order(UiLayoutInterface::VerticalOrder::TopToBottom) , m_childHAlignment(IDraw2d::HAlign::Left) , m_childVAlignment(IDraw2d::VAlign::Top) , m_ignoreDefaultLayoutCells(true) { } //////////////////////////////////////////////////////////////////////////////////////////////////// UiLayoutColumnComponent::~UiLayoutColumnComponent() { } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiLayoutColumnComponent::ApplyLayoutWidth() { // Get the layout rect inside the padding AZ::Vector2 layoutRectSize(0.0f, 0.0f); UiLayoutHelpers::GetSizeInsidePadding(GetEntityId(), m_padding, layoutRectSize); // First, calculate and set the child elements' widths. // Min/target/extra heights may depend on element widths ApplyLayoutWidth(layoutRectSize.GetX()); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiLayoutColumnComponent::ApplyLayoutHeight() { // Get the layout rect inside the padding AZ::Vector2 layoutRectSize(0.0f, 0.0f); UiLayoutHelpers::GetSizeInsidePadding(GetEntityId(), m_padding, layoutRectSize); // Calculate and set the child elements' heights ApplyLayoutHeight(layoutRectSize.GetY()); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool UiLayoutColumnComponent::IsUsingLayoutCellsToCalculateLayout() { return true; } //////////////////////////////////////////////////////////////////////////////////////////////////// bool UiLayoutColumnComponent::GetIgnoreDefaultLayoutCells() { return m_ignoreDefaultLayoutCells; } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiLayoutColumnComponent::SetIgnoreDefaultLayoutCells(bool ignoreDefaultLayoutCells) { m_ignoreDefaultLayoutCells = ignoreDefaultLayoutCells; InvalidateLayout(); InvalidateParentLayout(); } //////////////////////////////////////////////////////////////////////////////////////////////////// IDraw2d::HAlign UiLayoutColumnComponent::GetHorizontalChildAlignment() { return m_childHAlignment; } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiLayoutColumnComponent::SetHorizontalChildAlignment(IDraw2d::HAlign alignment) { m_childHAlignment = alignment; InvalidateLayout(); } //////////////////////////////////////////////////////////////////////////////////////////////////// IDraw2d::VAlign UiLayoutColumnComponent::GetVerticalChildAlignment() { return m_childVAlignment; } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiLayoutColumnComponent::SetVerticalChildAlignment(IDraw2d::VAlign alignment) { m_childVAlignment = alignment; InvalidateLayout(); } //////////////////////////////////////////////////////////////////////////////////////////////////// bool UiLayoutColumnComponent::IsControllingChild(AZ::EntityId childId) { return UiLayoutHelpers::IsControllingChild(GetEntityId(), childId); } //////////////////////////////////////////////////////////////////////////////////////////////////// AZ::Vector2 UiLayoutColumnComponent::GetSizeToFitChildElements(const AZ::Vector2& childElementSize, int numChildElements) { AZ::Vector2 size(0.0f, 0.0f); if (numChildElements > 0) { size.SetY((childElementSize.GetY() * numChildElements) + (m_spacing * (numChildElements - 1)) + m_padding.m_top + m_padding.m_bottom); } else { size.SetY(0.0f); } // Check if anchors are together UiTransform2dInterface::Anchors anchors; EBUS_EVENT_ID_RESULT(anchors, GetEntityId(), UiTransform2dBus, GetAnchors); if (anchors.m_left == anchors.m_right) { size.SetX(numChildElements > 0 ? childElementSize.GetX() : 0.0f); } else { // Anchors are apart, so width remains untouched AZ::Vector2 curSize(0.0f, 0.0f); EBUS_EVENT_ID_RESULT(curSize, GetEntityId(), UiTransformBus, GetCanvasSpaceSizeNoScaleRotate); size.SetX(curSize.GetX()); } return size; } //////////////////////////////////////////////////////////////////////////////////////////////////// UiLayoutInterface::Padding UiLayoutColumnComponent::GetPadding() { return m_padding; } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiLayoutColumnComponent::SetPadding(UiLayoutInterface::Padding padding) { m_padding = padding; InvalidateLayout(); InvalidateParentLayout(); } //////////////////////////////////////////////////////////////////////////////////////////////////// float UiLayoutColumnComponent::GetSpacing() { return m_spacing; } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiLayoutColumnComponent::SetSpacing(float spacing) { m_spacing = spacing; InvalidateLayout(); InvalidateParentLayout(); } //////////////////////////////////////////////////////////////////////////////////////////////////// UiLayoutInterface::VerticalOrder UiLayoutColumnComponent::GetOrder() { return m_order; } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiLayoutColumnComponent::SetOrder(UiLayoutInterface::VerticalOrder order) { m_order = order; InvalidateLayout(); } //////////////////////////////////////////////////////////////////////////////////////////////////// float UiLayoutColumnComponent::GetMinWidth() { float width = 0.0f; // Minimum layout width is padding + maximum child element min width AZStd::vector minWidths = UiLayoutHelpers::GetLayoutCellMinWidths(GetEntityId(), m_ignoreDefaultLayoutCells); if (minWidths.size() > 0) { width += m_padding.m_left + m_padding.m_right; float maxChildWidth = 0.0f; for (auto minWidth : minWidths) { if (maxChildWidth < minWidth) { maxChildWidth = minWidth; } } width += maxChildWidth; } return width; } //////////////////////////////////////////////////////////////////////////////////////////////////// float UiLayoutColumnComponent::GetMinHeight() { float height = 0.0f; // Minimum layout height is padding + spacing + sum of all child element min heights AZStd::vector minHeights = UiLayoutHelpers::GetLayoutCellMinHeights(GetEntityId(), m_ignoreDefaultLayoutCells); if (minHeights.size() > 0) { height += m_padding.m_top + m_padding.m_bottom + (m_spacing * (minHeights.size() - 1)); for (auto minHeight : minHeights) { height += minHeight; } } return height; } //////////////////////////////////////////////////////////////////////////////////////////////////// float UiLayoutColumnComponent::GetTargetWidth(float /*maxWidth*/) { float width = 0.0f; // Target layout width is padding + maximum child element target width AZStd::vector targetWidths = UiLayoutHelpers::GetLayoutCellTargetWidths(GetEntityId(), m_ignoreDefaultLayoutCells); if (targetWidths.size() > 0) { width += m_padding.m_left + m_padding.m_right; float maxChildWidth = 0.0f; for (auto targetWidth : targetWidths) { if (maxChildWidth < targetWidth) { maxChildWidth = targetWidth; } } width += maxChildWidth; } return width; } //////////////////////////////////////////////////////////////////////////////////////////////////// float UiLayoutColumnComponent::GetTargetHeight(float /*maxHeight*/) { float height = 0.0f; // Target layout height is padding + spacing + sum of all child element target heights AZStd::vector targetHeights = UiLayoutHelpers::GetLayoutCellTargetHeights(GetEntityId(), m_ignoreDefaultLayoutCells); if (targetHeights.size() > 0) { height += m_padding.m_top + m_padding.m_bottom + (m_spacing * (targetHeights.size() - 1)); for (auto targetHeight : targetHeights) { height += targetHeight; } } return height; } //////////////////////////////////////////////////////////////////////////////////////////////////// float UiLayoutColumnComponent::GetExtraWidthRatio() { return 1.0f; } //////////////////////////////////////////////////////////////////////////////////////////////////// float UiLayoutColumnComponent::GetExtraHeightRatio() { return 1.0f; } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiLayoutColumnComponent::OnCanvasSpaceRectChanged(AZ::EntityId entityId, const UiTransformInterface::Rect& oldRect, const UiTransformInterface::Rect& newRect) { // If old rect equals new rect, size changed due to initialization bool sizeChanged = (oldRect == newRect) || (!oldRect.GetSize().IsClose(newRect.GetSize(), 0.05f)); if (sizeChanged) { InvalidateLayout(); } } //////////////////////////////////////////////////////////////////////////////////////////////////// // PUBLIC STATIC MEMBER FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////////////////// void UiLayoutColumnComponent::Reflect(AZ::ReflectContext* context) { AZ::SerializeContext* serializeContext = azrtti_cast(context); if (serializeContext) { serializeContext->Class() ->Version(2, &VersionConverter) ->Field("Padding", &UiLayoutColumnComponent::m_padding) ->Field("Spacing", &UiLayoutColumnComponent::m_spacing) ->Field("Order", &UiLayoutColumnComponent::m_order) ->Field("ChildHAlignment", &UiLayoutColumnComponent::m_childHAlignment) ->Field("ChildVAlignment", &UiLayoutColumnComponent::m_childVAlignment) ->Field("IgnoreDefaultLayoutCells", &UiLayoutColumnComponent::m_ignoreDefaultLayoutCells); AZ::EditContext* ec = serializeContext->GetEditContext(); if (ec) { auto editInfo = ec->Class("LayoutColumn", "A layout component that arranges its children in a column"); editInfo->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "UI") ->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/UiLayoutColumn.png") ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/UiLayoutColumn.png") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("UI", 0x27ff46b0)) ->Attribute(AZ::Edit::Attributes::AutoExpand, true); editInfo->DataElement(AZ::Edit::UIHandlers::LayoutPadding, &UiLayoutColumnComponent::m_padding, "Padding", "The layout padding") ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show) // needed because sub-elements are hidden ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiLayoutColumnComponent::InvalidateLayout) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiLayoutColumnComponent::InvalidateParentLayout) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiLayoutColumnComponent::CheckLayoutFitterAndRefreshEditorTransformProperties); editInfo->DataElement(AZ::Edit::UIHandlers::SpinBox, &UiLayoutColumnComponent::m_spacing, "Spacing", "The spacing between children") ->Attribute(AZ::Edit::Attributes::Min, 0.0f) ->Attribute(AZ::Edit::Attributes::Step, 1.0f) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiLayoutColumnComponent::InvalidateLayout) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiLayoutColumnComponent::InvalidateParentLayout) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiLayoutColumnComponent::CheckLayoutFitterAndRefreshEditorTransformProperties); editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiLayoutColumnComponent::m_order, "Order", "Which direction the column fills") ->EnumAttribute(UiLayoutInterface::VerticalOrder::TopToBottom, "Top to bottom") ->EnumAttribute(UiLayoutInterface::VerticalOrder::BottomToTop, "Bottom to top") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiLayoutColumnComponent::InvalidateLayout); editInfo->DataElement(AZ::Edit::UIHandlers::CheckBox, &UiLayoutColumnComponent::m_ignoreDefaultLayoutCells, "Ignore Default Cells", "When checked, fixed default layout cell values are used for child elements with no LayoutCell\n" "component rather than using defaults calculated by other components on the child.") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiLayoutColumnComponent::InvalidateLayout) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiLayoutColumnComponent::InvalidateParentLayout) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiLayoutColumnComponent::CheckLayoutFitterAndRefreshEditorTransformProperties); // Alignment { editInfo->ClassElement(AZ::Edit::ClassElements::Group, "Child Alignment") ->Attribute(AZ::Edit::Attributes::AutoExpand, true); editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiLayoutColumnComponent::m_childHAlignment, "Horizontal", "How to align the children if they don't take up all the available width") ->EnumAttribute(IDraw2d::HAlign::Left, "Left") ->EnumAttribute(IDraw2d::HAlign::Center, "Center") ->EnumAttribute(IDraw2d::HAlign::Right, "Right") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiLayoutColumnComponent::InvalidateLayout); editInfo->DataElement(AZ::Edit::UIHandlers::ComboBox, &UiLayoutColumnComponent::m_childVAlignment, "Vertical", "How to align the children if they don't take up all the available height") ->EnumAttribute(IDraw2d::VAlign::Top, "Top") ->EnumAttribute(IDraw2d::VAlign::Center, "Center") ->EnumAttribute(IDraw2d::VAlign::Bottom, "Bottom") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &UiLayoutColumnComponent::InvalidateLayout); } } } AZ::BehaviorContext* behaviorContext = azrtti_cast(context); if (behaviorContext) { behaviorContext->EBus("UiLayoutColumnBus") ->Event("GetPadding", &UiLayoutColumnBus::Events::GetPadding) ->Event("SetPadding", &UiLayoutColumnBus::Events::SetPadding) ->Event("GetSpacing", &UiLayoutColumnBus::Events::GetSpacing) ->Event("SetSpacing", &UiLayoutColumnBus::Events::SetSpacing) ->Event("GetOrder", &UiLayoutColumnBus::Events::GetOrder) ->Event("SetOrder", &UiLayoutColumnBus::Events::SetOrder) ->VirtualProperty("Padding", "GetPadding", "SetPadding") ->VirtualProperty("Spacing", "GetSpacing", "SetSpacing"); behaviorContext->Class()->RequestBus("UiLayoutColumnBus"); } } //////////////////////////////////////////////////////////////////////////////////////////////////// // PROTECTED MEMBER FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// void UiLayoutColumnComponent::Activate() { UiLayoutBus::Handler::BusConnect(m_entity->GetId()); UiLayoutControllerBus::Handler::BusConnect(m_entity->GetId()); UiLayoutColumnBus::Handler::BusConnect(m_entity->GetId()); UiLayoutCellDefaultBus::Handler::BusConnect(m_entity->GetId()); UiTransformChangeNotificationBus::Handler::BusConnect(m_entity->GetId()); // If this is the first time the entity has been activated this has no effect since the canvas // is not known. But if a LayoutColumn component has just been pasted onto an existing entity // we need to invalidate the layout in case that affects things. InvalidateLayout(); InvalidateParentLayout(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiLayoutColumnComponent::Deactivate() { UiLayoutBus::Handler::BusDisconnect(); UiLayoutControllerBus::Handler::BusDisconnect(); UiLayoutColumnBus::Handler::BusDisconnect(); UiLayoutCellDefaultBus::Handler::BusDisconnect(); UiTransformChangeNotificationBus::Handler::BusDisconnect(); // We could be about to remove this component and then reactivate the entity // which could affect the layout if there is a parent layout component InvalidateLayout(); InvalidateParentLayout(); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiLayoutColumnComponent::InvalidateLayout() { UiLayoutHelpers::InvalidateLayout(GetEntityId()); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiLayoutColumnComponent::InvalidateParentLayout() { UiLayoutHelpers::InvalidateParentLayout(GetEntityId()); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiLayoutColumnComponent::CheckLayoutFitterAndRefreshEditorTransformProperties() const { UiLayoutHelpers::CheckFitterAndRefreshEditorTransformProperties(GetEntityId()); } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiLayoutColumnComponent::ApplyLayoutWidth(float availableWidth) { // Get the child element cell widths UiLayoutHelpers::LayoutCellSizes layoutCells; UiLayoutHelpers::GetLayoutCellWidths(GetEntityId(), m_ignoreDefaultLayoutCells, layoutCells); int numChildren = layoutCells.size(); if (numChildren > 0) { // Set the child elements' transform properties based on the calculated child widths UiTransform2dInterface::Anchors anchors(0.0f, 0.0f, 0.0f, 0.0f); AZStd::vector childEntityIds; EBUS_EVENT_ID_RESULT(childEntityIds, GetEntityId(), UiElementBus, GetChildEntityIds); int childIndex = 0; for (auto child : childEntityIds) { // Set the anchors EBUS_EVENT_ID(child, UiTransform2dBus, SetAnchors, anchors, false, false); // Calculate occupied width float width = UiLayoutHelpers::CalculateSingleElementSize(layoutCells[childIndex], availableWidth); // Calculate alignment float alignmentOffset = UiLayoutHelpers::GetHorizontalAlignmentOffset(m_childHAlignment, availableWidth, width); // Set the offsets UiTransform2dInterface::Offsets offsets; EBUS_EVENT_ID_RESULT(offsets, child, UiTransform2dBus, GetOffsets); offsets.m_left = m_padding.m_left + alignmentOffset; offsets.m_right = offsets.m_left + width; EBUS_EVENT_ID(child, UiTransform2dBus, SetOffsets, offsets); childIndex++; } } } //////////////////////////////////////////////////////////////////////////////////////////////////// void UiLayoutColumnComponent::ApplyLayoutHeight(float availableHeight) { // Get the child element cell heights UiLayoutHelpers::LayoutCellSizes layoutCells; UiLayoutHelpers::GetLayoutCellHeights(GetEntityId(), m_ignoreDefaultLayoutCells, layoutCells); int numChildren = layoutCells.size(); if (numChildren > 0) { // Calculate child heights AZStd::vector finalHeights(numChildren, 0.0f); UiLayoutHelpers::CalculateElementSizes(layoutCells, availableHeight, m_spacing, finalHeights); // Calculate occupied height float childrenRectHeight = (numChildren - 1) * m_spacing; for (auto height : finalHeights) { childrenRectHeight += height; } // Calculate alignment float alignmentOffset = UiLayoutHelpers::GetVerticalAlignmentOffset(m_childVAlignment, availableHeight, childrenRectHeight); // Set the child elements' transform properties based on the calculated child heights AZStd::vector childEntityIds; EBUS_EVENT_ID_RESULT(childEntityIds, GetEntityId(), UiElementBus, GetChildEntityIds); float curY = alignmentOffset; switch (m_order) { case UiLayoutInterface::VerticalOrder::TopToBottom: curY += m_padding.m_top; break; case UiLayoutInterface::VerticalOrder::BottomToTop: curY += m_padding.m_top + childrenRectHeight; break; default: AZ_Assert(0, "Unrecognized VerticalOrder type in UiLayoutColumnComponent"); break; } int childIndex = 0; for (auto child : childEntityIds) { // Set the offsets UiTransform2dInterface::Offsets offsets; EBUS_EVENT_ID_RESULT(offsets, child, UiTransform2dBus, GetOffsets); switch (m_order) { case UiLayoutInterface::VerticalOrder::TopToBottom: offsets.m_top = curY; curY += finalHeights[childIndex]; offsets.m_bottom = curY; curY += m_spacing; break; case UiLayoutInterface::VerticalOrder::BottomToTop: offsets.m_bottom = curY; curY -= finalHeights[childIndex]; offsets.m_top = curY; curY -= m_spacing; break; default: AZ_Assert(0, "Unrecognized VerticalOrder type in UiLayoutColumnComponent"); break; } EBUS_EVENT_ID(child, UiTransform2dBus, SetOffsets, offsets); childIndex++; } } } //////////////////////////////////////////////////////////////////////////////////////////////////// // PRIVATE STATIC MEMBER FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// bool UiLayoutColumnComponent::VersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement) { // conversion from version 1 to 2: // - Need to convert default m_ignoreDefaultLayoutCells to true if (classElement.GetVersion() < 2) { // Add a flag and set it to a value that's different from the default value for new components const char* subElementName = "IgnoreDefaultLayoutCells"; int newElementIndex = classElement.AddElement(context, subElementName); if (newElementIndex == -1) { // Error adding the new sub element AZ_Error("Serialization", false, "AddElement failed for element %s", subElementName); return false; } classElement.GetSubElement(newElementIndex).SetData(context, true); } return true; }