[NUI] Fix RelativeLayout's descendant size and position calculations
authorJaehyun Cho <jae_hyun.cho@samsung.com>
Mon, 7 Jun 2021 13:02:25 +0000 (22:02 +0900)
committerdongsug-song <35130733+dongsug-song@users.noreply.github.com>
Mon, 14 Jun 2021 10:12:19 +0000 (19:12 +0900)
Previously, 2 descendant sizes and positions calculation problems
exitsted on RelativeLayout as follows.

1. MatchParent grand children's sizes and positions were not calculated.
 - RelativeLayout set its children's sizes and positions but it did not
   set its children's MeasuredWidth/Height.
 - If children's MeasuredWidth/Height are not set, then the MatchParent
   grand children's sizes and positions are not calculated.

2. MatchParent children's sizes fill to the RelativeLayout by default.
 - MatchParent children's sizes should not fill to the RelativeLayout by
   default because children's sizes and positions should be calculated
   by RelativeLayout's APIs.

Now, the above problems have been fixed as follows.

1. RelativeLayout sets its children's MeasuredWidth/Height, so its
   MatchParent grand children's sizes and positions are calculated
   correctly.

2. MatchParent children's sizes are calculated by RelativeLayout's APIs.

src/Tizen.NUI/src/public/Layouting/LinearLayout.cs
src/Tizen.NUI/src/public/Layouting/RelativeLayout.cs

index f986536..2b171b9 100755 (executable)
@@ -354,7 +354,12 @@ namespace Tizen.NUI
                     {
                         needToMeasure = true;
                     }
-                    else
+                    // RelativeLayout's MatchParent children should not fill to the RelativeLayout.
+                    // Because the children's sizes and positions are calculated by RelativeLayout's APIs.
+                    // Therefore, not to fill the RelativeLayout, the mode is changed from Exactly to AtMost.
+                    //
+                    // Not to print the recursive reference error message for this case, Specification is checked if it is WrapContent.
+                    else if (Owner.HeightSpecification == LayoutParamPolicies.WrapContent)
                     {
                         if (childDesiredHeight == LayoutParamPolicies.MatchParent)
                         {
@@ -378,7 +383,12 @@ namespace Tizen.NUI
                             widthMeasureSpec.SetSize(new LayoutLength((int)(remainingWidth / childrenMatchParentCount) + Padding.Start + Padding.End));
                             needToMeasure = true;
                         }
-                        else
+                        // RelativeLayout's MatchParent children should not fill to the RelativeLayout.
+                        // Because the children's sizes and positions are calculated by RelativeLayout's APIs.
+                        // Therefore, not to fill the RelativeLayout, the mode is changed from Exactly to AtMost.
+                        //
+                        // Not to print the recursive reference error message for this case, Specification is checked if it is WrapContent.
+                        else if (Owner.WidthSpecification == LayoutParamPolicies.WrapContent)
                         {
                             if (childDesiredWidth == LayoutParamPolicies.MatchParent)
                             {
@@ -432,7 +442,12 @@ namespace Tizen.NUI
                                                  widthMeasureSpec, heightMeasureSpec, childState,
                                                  Orientation.Horizontal);
                         }
-                        else
+                        // RelativeLayout's MatchParent children should not fill to the RelativeLayout.
+                        // Because the children's sizes and positions are calculated by RelativeLayout's APIs.
+                        // Therefore, not to fill the RelativeLayout, the mode is changed from Exactly to AtMost.
+                        //
+                        // Not to print the recursive reference error message for this case, Specification is checked if it is WrapContent.
+                        else if (Owner.WidthSpecification == LayoutParamPolicies.WrapContent)
                         {
                             if (childDesiredWidth == LayoutParamPolicies.MatchParent)
                             {
@@ -572,7 +587,12 @@ namespace Tizen.NUI
                     {
                         needToMeasure = true;
                     }
-                    else
+                    // RelativeLayout's MatchParent children should not fill to the RelativeLayout.
+                    // Because the children's sizes and positions are calculated by RelativeLayout's APIs.
+                    // Therefore, not to fill the RelativeLayout, the mode is changed from Exactly to AtMost.
+                    //
+                    // Not to print the recursive reference error message for this case, Specification is checked if it is WrapContent.
+                    else if (Owner.WidthSpecification == LayoutParamPolicies.WrapContent)
                     {
                         if (childDesiredWidth == LayoutParamPolicies.MatchParent)
                         {
@@ -596,7 +616,12 @@ namespace Tizen.NUI
                             heightMeasureSpec.SetSize(new LayoutLength((int)(remainingHeight / childrenMatchParentCount) + Padding.Top + Padding.Bottom));
                             needToMeasure = true;
                         }
-                        else
+                        // RelativeLayout's MatchParent children should not fill to the RelativeLayout.
+                        // Because the children's sizes and positions are calculated by RelativeLayout's APIs.
+                        // Therefore, not to fill the RelativeLayout, the mode is changed from Exactly to AtMost.
+                        //
+                        // Not to print the recursive reference error message for this case, Specification is checked if it is WrapContent.
+                        else if (Owner.HeightSpecification == LayoutParamPolicies.WrapContent)
                         {
                             if (childDesiredHeight == LayoutParamPolicies.MatchParent)
                             {
@@ -650,7 +675,12 @@ namespace Tizen.NUI
                                                  widthMeasureSpec, heightMeasureSpec, childState,
                                                  Orientation.Vertical);
                         }
-                        else
+                        // RelativeLayout's MatchParent children should not fill to the RelativeLayout.
+                        // Because the children's sizes and positions are calculated by RelativeLayout's APIs.
+                        // Therefore, not to fill the RelativeLayout, the mode is changed from Exactly to AtMost.
+                        //
+                        // Not to print the recursive reference error message for this case, Specification is checked if it is WrapContent.
+                        else if (Owner.HeightSpecification == LayoutParamPolicies.WrapContent)
                         {
                             if (childDesiredHeight == LayoutParamPolicies.MatchParent)
                             {
index c78e17f..ed8ea33 100755 (executable)
@@ -348,7 +348,28 @@ namespace Tizen.NUI
                 LayoutItem childLayout = LayoutChildren[i];
                 if (childLayout != null)
                 {
-                    MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0));
+                    var childWidthMeasureSpec = new MeasureSpecification(widthMeasureSpec.Size, widthMeasureSpec.Mode);
+                    var childHeightMeasureSpec = new MeasureSpecification(heightMeasureSpec.Size, heightMeasureSpec.Mode);
+
+                    // RelativeLayout's MatchParent children should not fill to the RelativeLayout.
+                    // Because the children's sizes and positions are calculated by RelativeLayout's APIs.
+                    // Therefore, not to fill the RelativeLayout, the mode is changed from Exactly to AtMost.
+                    if (childLayout.Owner.WidthSpecification == LayoutParamPolicies.MatchParent)
+                    {
+                        childWidthMeasureSpec.SetSize(new LayoutLength(widthMeasureSpec.Size));
+                        childWidthMeasureSpec.SetMode(MeasureSpecification.ModeType.AtMost);
+                    }
+
+                    // RelativeLayout's MatchParent children should not fill to the RelativeLayout.
+                    // Because the children's sizes and positions are calculated by RelativeLayout's APIs.
+                    // Therefore, not to fill the RelativeLayout, the mode is changed from Exactly to AtMost.
+                    if (childLayout.Owner.HeightSpecification == LayoutParamPolicies.MatchParent)
+                    {
+                        childHeightMeasureSpec.SetSize(new LayoutLength(heightMeasureSpec.Size));
+                        childHeightMeasureSpec.SetMode(MeasureSpecification.ModeType.AtMost);
+                    }
+
+                    MeasureChildWithMargins(childLayout, childWidthMeasureSpec, new LayoutLength(0), childHeightMeasureSpec, new LayoutLength(0));
 
                     if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
                     {
@@ -378,6 +399,18 @@ namespace Tizen.NUI
                     Geometry horizontalGeometry = GetHorizontalLayout(childLayout.Owner);
                     Geometry verticalGeometry = GetVerticalLayout(childLayout.Owner);
 
+                    // MeasureChildWithMargins() is called to assign child's MeasuredWidth/Height to calculate grand children's sizes correctly.
+                    // Grand children's positions are calculated correctly only if their sizes are calculated correctly.
+                    // MeasureChildWithMargins() should be called before childLayout.Layout() to use childLayout's MeasuredWidth/Height
+                    // when the grand children's positions are calculated.
+                    //
+                    // FIXME: It would be better if MeasureChildWithMargins() are called in OnMeasure() to separate Measure and Layout calculations.
+                    //        For now, not to call duplicate GetHorizontalLayout() and GetVerticalLayout() in both OnMeasure() and OnLayout(),
+                    //        MeasureChildWithMargins() is called here.
+                    MeasureChildWithMargins(childLayout,
+                                            new MeasureSpecification(new LayoutLength(horizontalGeometry.Size), MeasureSpecification.ModeType.Exactly), new LayoutLength(0),
+                                            new MeasureSpecification(new LayoutLength(verticalGeometry.Size), MeasureSpecification.ModeType.Exactly), new LayoutLength(0));
+
                     LayoutLength childLeft = new LayoutLength(horizontalGeometry.Position + Padding.Start + childLayout.Margin.Start);
                     LayoutLength childRight = new LayoutLength(horizontalGeometry.Position + horizontalGeometry.Size + Padding.Start - childLayout.Margin.End);
                     LayoutLength childTop = new LayoutLength(verticalGeometry.Position + Padding.Top + childLayout.Margin.Top);