[NUI] Fix LinearLayout to update its child's MeasuredSizeHeight 0 correctly
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / Layouting / LinearLayout.cs
1 /* Copyright (c) 2020 Samsung Electronics Co., Ltd.
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  *
15  */
16
17 using System;
18 using System.ComponentModel;
19 using Tizen.NUI.BaseComponents;
20 using System.Collections.Generic;
21 using System.Linq;
22
23 namespace Tizen.NUI
24 {
25     /// <summary>
26     /// [Draft] This class implements a linear box layout, automatically handling right to left or left to right direction change.
27     /// </summary>
28     public class LinearLayout : LayoutGroup
29     {
30         private Alignment linearAlignment = Alignment.Top;
31
32         /// <summary>
33         /// [Draft] Enumeration for the direction in which the content is laid out
34         /// </summary>
35         /// <since_tizen> 6 </since_tizen>
36         public enum Orientation
37         {
38             /// <summary>
39             /// Horizontal (row)
40             /// </summary>
41             Horizontal,
42             /// <summary>
43             /// Vertical (column)
44             /// </summary>
45             Vertical
46         }
47
48         /// <summary>
49         /// [Draft] Enumeration for the alignment of the linear layout items
50         /// </summary>
51         /// <since_tizen> 6 </since_tizen>
52         [Obsolete("This has been deprecated in API9 and will be removed in API11. Use HorizontalAlignment and VerticalAlignment instead.")]
53         public enum Alignment
54         {
55             /// <summary>
56             /// At the left/right edge of the container (maps to LTR/RTL direction for horizontal orientation)
57             /// </summary>
58             Begin = 0x1,
59             /// <summary>
60             /// At the right/left edge of the container (maps to LTR/RTL direction for horizontal orientation)
61             /// </summary>
62             End = 0x2,
63             /// <summary>
64             /// At the horizontal center of the container
65             /// </summary>
66             CenterHorizontal = 0x4,
67             /// <summary>
68             /// At the top edge of the container
69             /// </summary>
70             Top = 0x8,
71             /// <summary>
72             /// At the bottom edge of the container
73             /// </summary>
74             Bottom = 0x10,
75             /// <summary>
76             /// At the vertical center of the container
77             /// </summary>
78             CenterVertical = 0x20,
79             /// <summary>
80             /// At the vertical and horizontal center of the container
81             /// </summary>
82             Center = 0x40
83         }
84
85         struct HeightAndWidthState
86         {
87             public MeasuredSize.StateType widthState;
88             public MeasuredSize.StateType heightState;
89
90             public HeightAndWidthState(MeasuredSize.StateType width, MeasuredSize.StateType height)
91             {
92                 widthState = width;
93                 heightState = height;
94             }
95         }
96
97         /// <summary>
98         /// [Draft] Get/Set the orientation in the layout
99         /// </summary>
100         /// <since_tizen> 6 </since_tizen>
101         public LinearLayout.Orientation LinearOrientation
102         {
103             get
104             {
105                 return linearOrientation;
106             }
107             set
108             {
109                 linearOrientation = value;
110                 RequestLayout();
111             }
112         }
113
114         /// <summary>
115         /// [Draft] Get/Set the padding between cells in the layout
116         /// </summary>
117         /// <since_tizen> 6 </since_tizen>
118         public Size2D CellPadding
119         {
120             get
121             {
122                 if (cellPadding == null)
123                 {
124                     cellPadding = new Size2D(0, 0);
125                 }
126
127                 return cellPadding;
128             }
129             set
130             {
131                 cellPadding = value;
132                 RequestLayout();
133             }
134         }
135
136
137         /// <summary>
138         /// [Draft] Get/Set the alignment in the layout
139         /// </summary>
140         /// <since_tizen> 6 </since_tizen>
141         [Obsolete("This has been deprecated in API9 and will be removed in API11. Use HorizontalAlignment and VerticalAlignment properties instead.")]
142         public LinearLayout.Alignment LinearAlignment
143         {
144             get
145             {
146                 return linearAlignment;
147             }
148
149             set
150             {
151                 if (linearAlignment == value)
152                 {
153                     return;
154                 }
155
156                 linearAlignment = value;
157
158                 switch (linearAlignment)
159                 {
160                     case Alignment.Begin:
161                         HorizontalAlignment = HorizontalAlignment.Begin;
162                         break;
163                     case Alignment.End:
164                         HorizontalAlignment = HorizontalAlignment.End;
165                         break;
166                     case Alignment.CenterHorizontal:
167                         HorizontalAlignment = HorizontalAlignment.Center;
168                         break;
169                     case Alignment.Top:
170                         VerticalAlignment = VerticalAlignment.Top;
171                         break;
172                     case Alignment.Bottom:
173                         VerticalAlignment = VerticalAlignment.Bottom;
174                         break;
175                     case Alignment.CenterVertical:
176                         VerticalAlignment = VerticalAlignment.Center;
177                         break;
178                     case Alignment.Center:
179                         HorizontalAlignment = HorizontalAlignment.Center;
180                         VerticalAlignment = VerticalAlignment.Center;
181                         break;
182                     default:
183                         break;
184                 }
185             }
186          }
187
188         /// <summary>
189         /// Get/Set the horizontal alignment in the layout
190         /// </summary>
191         /// <since_tizen> 9 </since_tizen>
192         public HorizontalAlignment HorizontalAlignment { get; set; } = HorizontalAlignment.Begin;
193
194         /// <summary>
195         /// Get/Set the vertical alignment in the layout
196         /// </summary>
197         /// <since_tizen> 9 </since_tizen>
198         public VerticalAlignment VerticalAlignment { get; set; } = VerticalAlignment.Top;
199
200         private float totalLength = 0.0f;
201         private Size2D cellPadding = new Size2D(0, 0);
202         private Orientation linearOrientation = Orientation.Horizontal;
203
204         /// <summary>
205         /// [Draft] Constructor
206         /// </summary>
207         /// <since_tizen> 6 </since_tizen>
208         public LinearLayout()
209         {
210         }
211
212         /// <summary>
213         /// Measure the layout and its content to determine the measured width and the measured height.
214         /// </summary>
215         /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
216         /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
217         /// <since_tizen> 6 </since_tizen>
218         protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
219         {
220             if (linearOrientation == Orientation.Horizontal)
221             {
222                 MeasureHorizontal(widthMeasureSpec, heightMeasureSpec);
223             }
224             else
225             {
226                 MeasureVertical(widthMeasureSpec, heightMeasureSpec);
227             }
228         }
229
230         /// <summary>
231         /// Layout should assign a size and position to each of its children.<br />
232         /// </summary>
233         /// <param name="changed">This is a new size or position for this layout.</param>
234         /// <param name="left">Left position, relative to parent.</param>
235         /// <param name="top"> Top position, relative to parent.</param>
236         /// <param name="right">Right position, relative to parent.</param>
237         /// <param name="bottom">Bottom position, relative to parent.</param>
238         /// <since_tizen> 6 </since_tizen>
239         protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
240         {
241             if (linearOrientation == Orientation.Horizontal)
242             {
243                 LayoutHorizontal(left, top, right, bottom);
244             }
245             else
246             {
247                 LayoutVertical(left, top, right, bottom);
248             }
249         }
250
251
252         private void MeasureWeightedChild(LayoutItem childLayout, float totalWeightLength, float totalWeight, float childWeight,
253                                            MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec,
254                                            HeightAndWidthState childState, Orientation orientation)
255         {
256             bool horizontal = false;
257             if (orientation == Orientation.Horizontal)
258             {
259                 horizontal = true;
260             }
261
262             float childsShare = totalWeightLength * (childWeight / totalWeight);
263             float desiredWidth = childLayout.Owner.WidthSpecification;
264             float desiredHeight = childLayout.Owner.HeightSpecification;
265
266             MeasureSpecification childWidthMeasureSpec;
267             MeasureSpecification childHeightMeasureSpec;
268
269             if (horizontal)
270             {
271                 childWidthMeasureSpec = new MeasureSpecification(new LayoutLength(childsShare - (childLayout.Margin.Start + childLayout.Margin.End)), MeasureSpecification.ModeType.Exactly);
272
273                 childHeightMeasureSpec = GetChildMeasureSpecification(
274                                             new MeasureSpecification(
275                                                 new LayoutLength(heightMeasureSpec.Size - (childLayout.Margin.Top + childLayout.Margin.Bottom)),
276                                                 heightMeasureSpec.Mode),
277                                             new LayoutLength(Padding.Top + Padding.Bottom),
278                                             new LayoutLength(desiredHeight));
279             }
280             else // vertical
281             {
282                 childWidthMeasureSpec = GetChildMeasureSpecification(
283                                             new MeasureSpecification(
284                                                 new LayoutLength(widthMeasureSpec.Size - (childLayout.Margin.Start + childLayout.Margin.End)),
285                                                 widthMeasureSpec.Mode),
286                                             new LayoutLength(Padding.Start + Padding.End),
287                                             new LayoutLength(desiredWidth));
288
289                 childHeightMeasureSpec = new MeasureSpecification(new LayoutLength(childsShare - (childLayout.Margin.Top + childLayout.Margin.Bottom)), MeasureSpecification.ModeType.Exactly);
290             }
291
292             childLayout.Measure(childWidthMeasureSpec, childHeightMeasureSpec);
293
294             // Child may now not fit in horizontal dimension.
295             if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
296             {
297                 childState.widthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
298             }
299
300             // Child may now not fit in vertical dimension.
301             if (childLayout.MeasuredHeight.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
302             {
303                 childState.heightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
304             }
305         }
306
307         private void MeasureHorizontal(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
308         {
309             var widthMode = widthMeasureSpec.Mode;
310             var heightMode = heightMeasureSpec.Mode;
311             bool isWidthExactly = (widthMode == MeasureSpecification.ModeType.Exactly);
312             bool isHeightExactly = (heightMode == MeasureSpecification.ModeType.Exactly);
313             float maxHeight = 0.0f;
314             float totalWeight = 0.0f;
315             int childrenCount = IterateLayoutChildren().Count();
316
317             // Child layout, which wants to match its width to its parent's remaining width, is either following 1 or 2.
318             // 1. Child layout whose Owner.WidthSpecification is LayoutParamPolicies.MatchParent.
319             // 2. Child layout whose Owner.WidthSpecification is 0 and Owner.Weight is greater than 0.
320             // The number of child layout which wants to match its width to its parent's remaining width.
321             int childrenMatchParentCount = 0;
322
323             // Reset measure variable
324             totalLength = 0.0f;
325
326             HeightAndWidthState childState = new HeightAndWidthState(MeasuredSize.StateType.MeasuredSizeOK,
327                                                                      MeasuredSize.StateType.MeasuredSizeOK);
328
329             // 1ST PHASE:
330             //
331             // We measure all children whose width specification policy is WrapContent without weight.
332             // After 1st phase, remaining width of parent is accumulated to calculate width of children
333             // whose width specification policy is MatchParent.
334             foreach (var childLayout in LayoutChildren)
335             {
336                 if (!childLayout.SetPositionByLayout)
337                 {
338                     continue;
339                 }
340                 int childDesiredWidth = childLayout.Owner.WidthSpecification;
341                 int childDesiredHeight = childLayout.Owner.HeightSpecification;
342                 float childWeight = childLayout.Owner.Weight;
343                 Extents childMargin = childLayout.Margin;
344                 float childMarginWidth = childMargin.Start + childMargin.End;
345                 bool useRemainingWidth = (childDesiredWidth == 0) && (childWeight > 0);
346
347                 if ((childDesiredWidth == LayoutParamPolicies.MatchParent) || (useRemainingWidth))
348                 {
349                     totalWeight += childWeight;
350                     childrenMatchParentCount++;
351                 }
352
353                 // MatchParent child layout's margin is not added to totalLength.
354                 // Consequently, MatchParent child layout's margin is added to remaining size,
355                 // so the margin is not shared with other MatchParent child layouts.
356                 // e.g.
357                 // LinearLayout has size 100.
358                 // Child layout1 is MatchParent and its margin is 20. (This margin is not ad
359                 // Child layout2 is MatchParent and its margin is 0.
360                 // Then, child layout1's size is 30 and child layout2's size is 50.
361                 if ((childDesiredWidth == LayoutParamPolicies.WrapContent) || ((childDesiredWidth >= 0) && (!useRemainingWidth)))
362                 {
363                     MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0));
364
365                     float childMeasuredWidth = childLayout.MeasuredWidth.Size.AsDecimal();
366
367                     if (childMeasuredWidth < 0)
368                     {
369                         totalLength = Math.Max(totalLength, totalLength + childMarginWidth);
370                     }
371                     else
372                     {
373                         totalLength = Math.Max(totalLength, totalLength + childMeasuredWidth + childMarginWidth);
374                     }
375                 }
376
377                 if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
378                 {
379                     childState.widthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
380                 }
381                 if (childLayout.MeasuredHeight.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
382                 {
383                     childState.heightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
384                 }
385             } // 1ST PHASE foreach
386
387             totalLength = Math.Max(totalLength, totalLength + CellPadding.Width * (childrenCount - 1) + Padding.Start + Padding.End);
388             float widthSize = Math.Max(totalLength, SuggestedMinimumWidth.AsDecimal());
389             MeasuredSize widthSizeAndState = ResolveSizeAndState(new LayoutLength(widthSize), widthMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
390             widthSize = widthSizeAndState.Size.AsDecimal();
391
392             float remainingWidth = widthSize - totalLength;
393             float totalWeightLength = 0.0f;
394
395             // Up to now, only WrapContent children's sizes are added to the totalLength.
396             // Since the totalLength is used in OnLayout as the sum of all children's sizes,
397             // the layout size is assigned to the totalLength if MatchParent child exists.
398             if (childrenMatchParentCount > 0)
399             {
400                 totalLength = widthSize;
401             }
402
403             // 2ND PHASE:
404             //
405             // We measure all children whose width specification policy is MatchParent without weight.
406             // After 2nd phase, all children's widths are calculated without considering weight.
407             // And the widths of all weighted children are accumulated to calculate weighted width.
408             foreach (var childLayout in LayoutChildren)
409             {
410                 if (!childLayout.SetPositionByLayout)
411                 {
412                     continue;
413                 }
414                 int childDesiredWidth = childLayout.Owner.WidthSpecification;
415                 int childDesiredHeight = childLayout.Owner.HeightSpecification;
416                 float childWeight = childLayout.Owner.Weight;
417                 Extents childMargin = childLayout.Margin;
418                 bool useRemainingWidth = (childDesiredWidth == 0) && (childWeight > 0);
419                 bool needToMeasure = false;
420
421                 if ((childDesiredHeight == LayoutParamPolicies.MatchParent) || (useRemainingWidth))
422                 {
423                     if (isHeightExactly)
424                     {
425                         needToMeasure = true;
426                     }
427                     // RelativeLayout's MatchParent children should not fill to the RelativeLayout.
428                     // Because the children's sizes and positions are calculated by RelativeLayout's APIs.
429                     // Therefore, not to fill the RelativeLayout, the mode is changed from Exactly to AtMost.
430                     //
431                     // Not to print the recursive reference error message for this case, Specification is checked if it is WrapContent.
432                     else if (Owner.HeightSpecification == LayoutParamPolicies.WrapContent)
433                     {
434                         if (childDesiredHeight == LayoutParamPolicies.MatchParent)
435                         {
436                             Tizen.Log.Error("NUI", "There is a recursive reference! Parent layout(Owner: " + Owner + ")'s HeightSpecification is WrapContent and child layout(Owner: " + childLayout.Owner + ")'s HeightSpecification is MatchParent!");
437                         }
438                         else
439                         {
440                             Tizen.Log.Error("NUI", "There is a recursive reference! Parent layout(Owner: " + Owner + ")'s HeightSpecification is WrapContent and child layout(Owner: " + childLayout.Owner + ")'s HeightSpecification is 0 with positive weight!");
441                         }
442                     }
443                 }
444
445                 if (remainingWidth > 0)
446                 {
447                     if ((childDesiredWidth == LayoutParamPolicies.MatchParent) || (useRemainingWidth))
448                     {
449                         if (isWidthExactly)
450                         {
451                             // In MeasureChildWithMargins(), it is assumed that widthMeasureSpec includes Padding.Start and Padding.End.
452                             // Therefore, Padding.Start and Padding.End are added to widthMeasureSpec.Size before it is passed to MeasureChildWithMargins().
453                             widthMeasureSpec.SetSize(new LayoutLength((int)(remainingWidth / childrenMatchParentCount) + Padding.Start + Padding.End));
454                             needToMeasure = true;
455                         }
456                         // RelativeLayout's MatchParent children should not fill to the RelativeLayout.
457                         // Because the children's sizes and positions are calculated by RelativeLayout's APIs.
458                         // Therefore, not to fill the RelativeLayout, the mode is changed from Exactly to AtMost.
459                         //
460                         // Not to print the recursive reference error message for this case, Specification is checked if it is WrapContent.
461                         else if (Owner.WidthSpecification == LayoutParamPolicies.WrapContent)
462                         {
463                             if (childDesiredWidth == LayoutParamPolicies.MatchParent)
464                             {
465                                 Tizen.Log.Error("NUI", "There is a recursive reference! Parent layout(Owner: " + Owner + ")'s WidthSpecification is WrapContent and child layout(Owner: " + childLayout.Owner + ")'s WidthSpecification is MatchParent!");
466                             }
467                             else
468                             {
469                                 Tizen.Log.Error("NUI", "There is a recursive reference! Parent layout(Owner: " + Owner + ")'s WidthSpecification is WrapContent and child layout(Owner: " + childLayout.Owner + ")'s WidthSpecification is 0 with positive weight!");
470                             }
471                         }
472                     }
473                 }
474
475                 if (needToMeasure == true)
476                 {
477                     MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0));
478                 }
479
480                 if ((childWeight > 0) && ((childDesiredWidth == LayoutParamPolicies.MatchParent) || (childDesiredWidth == 0)))
481                 {
482                     float childMeasuredWidth = childLayout.MeasuredWidth.Size.AsDecimal();
483
484                     if (childMeasuredWidth < 0)
485                     {
486                         totalWeightLength = Math.Max(totalWeightLength, totalWeightLength + childMargin.Start + childMargin.End);
487                     }
488                     else
489                     {
490                         totalWeightLength = Math.Max(totalWeightLength, totalWeightLength + childMeasuredWidth + childMargin.Start + childMargin.End);
491                     }
492                 }
493             } // 2ND PHASE foreach
494
495             // 3RD PHASE:
496             //
497             // We measure all weighted children whose owner has weight greater than 0.
498             // After 3rd phase, all weighted children has width which is proportional to their weights
499             // in remaining width of parent.
500             if (totalWeight > 0.0f)
501             {
502                 foreach (LayoutItem childLayout in LayoutChildren)
503                 {
504                     if (!childLayout.SetPositionByLayout)
505                     {
506                         continue;
507                     }
508                     int childDesiredWidth = childLayout.Owner.WidthSpecification;
509                     float childWeight = childLayout.Owner.Weight;
510
511                     if ((childWeight > 0) && ((childDesiredWidth == LayoutParamPolicies.MatchParent) || (childDesiredWidth == 0)))
512                     {
513                         if (isWidthExactly)
514                         {
515                             MeasureWeightedChild(childLayout, totalWeightLength, totalWeight, childWeight,
516                                                  widthMeasureSpec, heightMeasureSpec, childState,
517                                                  Orientation.Horizontal);
518                         }
519                         // RelativeLayout's MatchParent children should not fill to the RelativeLayout.
520                         // Because the children's sizes and positions are calculated by RelativeLayout's APIs.
521                         // Therefore, not to fill the RelativeLayout, the mode is changed from Exactly to AtMost.
522                         //
523                         // Not to print the recursive reference error message for this case, Specification is checked if it is WrapContent.
524                         else if (Owner.WidthSpecification == LayoutParamPolicies.WrapContent)
525                         {
526                             if (childDesiredWidth == LayoutParamPolicies.MatchParent)
527                             {
528                                 Tizen.Log.Error("NUI", "There is a recursive reference! Parent layout(Owner: " + Owner + ")'s WidthSpecification is WrapContent and child layout(Owner: " + childLayout.Owner + ")'s WidthSpecification is MatchParent!");
529                             }
530                             else
531                             {
532                                 Tizen.Log.Error("NUI", "There is a recursive reference! Parent layout(Owner: " + Owner + ")'s WidthSpecification is WrapContent and child layout(Owner: " + childLayout.Owner + ")'s WidthSpecification is 0 with positive weight!");
533                             }
534                         }
535                     }
536                 }
537             }
538             // 3RD PHASE foreach
539
540             // Decide the max height among children.
541             foreach (var childLayout in LayoutChildren)
542             {
543                 if (!childLayout.SetPositionByLayout)
544                 {
545                     continue;
546                 }
547
548                 Extents childMargin = childLayout.Margin;
549                 float childMarginHeight = childMargin.Top + childMargin.Bottom;
550                 float childMeasuredHeight = childLayout.MeasuredHeight.Size.AsDecimal();
551
552                 if (childMeasuredHeight < 0)
553                 {
554                     maxHeight = Math.Max(maxHeight, childMarginHeight);
555                 }
556                 else
557                 {
558                     maxHeight = Math.Max(maxHeight, childMeasuredHeight + childMarginHeight);
559                 }
560             }
561
562             // Decide the max height compared with paddings and its suggested height.
563             maxHeight = Math.Max(maxHeight, maxHeight + (Padding.Top + Padding.Bottom));
564             maxHeight = Math.Max(maxHeight, SuggestedMinimumHeight.AsRoundedValue());
565
566             widthSizeAndState.State = childState.widthState;
567
568             SetMeasuredDimensions(widthSizeAndState,
569                                   ResolveSizeAndState(new LayoutLength(maxHeight), heightMeasureSpec, childState.heightState));
570         } // MeasureHorizontal
571
572         private void MeasureVertical(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
573         {
574             var widthMode = widthMeasureSpec.Mode;
575             var heightMode = heightMeasureSpec.Mode;
576             bool isWidthExactly = (widthMode == MeasureSpecification.ModeType.Exactly);
577             bool isHeightExactly = (heightMode == MeasureSpecification.ModeType.Exactly);
578             float maxWidth = 0.0f;
579             float totalWeight = 0.0f;
580             int childrenCount = IterateLayoutChildren().Count();
581
582             // Child layout, which wants to match its height to its parent's remaining height, is either following 1 or 2.
583             // 1. Child layout whose Owner.HeightSpecification is LayoutParamPolicies.MatchParent.
584             // 2. Child layout whose Owner.HeightSpecification is 0 and Owner.Weight is greater than 0.
585             // The number of child layout which wants to match its height to its parent's remaining height.
586             int childrenMatchParentCount = 0;
587
588             // Reset measure variable
589             totalLength = 0.0f;
590
591             HeightAndWidthState childState = new HeightAndWidthState(MeasuredSize.StateType.MeasuredSizeOK,
592                                                                      MeasuredSize.StateType.MeasuredSizeOK);
593
594             // 1ST PHASE:
595             //
596             // We measure all children whose height specification policy is WrapContent without weight.
597             // After 1st phase, remaining height of parent is accumulated to calculate height of children
598             // whose height specification policy is MatchParent.
599             foreach (var childLayout in LayoutChildren)
600             {
601                 if (!childLayout.SetPositionByLayout)
602                 {
603                     continue;
604                 }
605                 int childDesiredWidth = childLayout.Owner.WidthSpecification;
606                 int childDesiredHeight = childLayout.Owner.HeightSpecification;
607                 float childWeight = childLayout.Owner.Weight;
608                 Extents childMargin = childLayout.Margin;
609                 float childMarginHeight = childMargin.Top + childMargin.Bottom;
610                 bool useRemainingHeight = (childDesiredHeight == 0) && (childWeight > 0);
611
612                 if ((childDesiredHeight == LayoutParamPolicies.MatchParent) || (useRemainingHeight))
613                 {
614                     totalWeight += childWeight;
615                     childrenMatchParentCount++;
616                 }
617
618                 // MatchParent child layout's margin is not added to totalLength.
619                 // Consequently, MatchParent child layout's margin is added to remaining size,
620                 // so the margin is not shared with other MatchParent child layouts.
621                 // e.g.
622                 // LinearLayout has size 100.
623                 // Child layout1 is MatchParent and its margin is 20. (This margin is not ad
624                 // Child layout2 is MatchParent and its margin is 0.
625                 // Then, child layout1's size is 30 and child layout2's size is 50.
626                 if ((childDesiredHeight == LayoutParamPolicies.WrapContent) || ((childDesiredHeight >= 0) && (!useRemainingHeight)))
627                 {
628                     MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0));
629
630                     float childMeasuredHeight = childLayout.MeasuredHeight.Size.AsDecimal();
631
632                     if (childMeasuredHeight < 0)
633                     {
634                         totalLength = Math.Max(totalLength, totalLength + childMarginHeight);
635                     }
636                     else
637                     {
638                         totalLength = Math.Max(totalLength, totalLength + childMeasuredHeight + childMarginHeight);
639                     }
640                 }
641
642                 if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
643                 {
644                     childState.widthState = MeasuredSize.StateType.MeasuredSizeTooSmall;
645                 }
646                 if (childLayout.MeasuredHeight.State == MeasuredSize.StateType.MeasuredSizeTooSmall)
647                 {
648                     childState.heightState = MeasuredSize.StateType.MeasuredSizeTooSmall;
649                 }
650             } // 1ST PHASE foreach
651
652             totalLength = Math.Max(totalLength, totalLength + CellPadding.Height * (childrenCount - 1) + Padding.Top + Padding.Bottom);
653             float heightSize = Math.Max(totalLength, SuggestedMinimumHeight.AsDecimal());
654             MeasuredSize heightSizeAndState = ResolveSizeAndState(new LayoutLength(heightSize), heightMeasureSpec, MeasuredSize.StateType.MeasuredSizeOK);
655             heightSize = heightSizeAndState.Size.AsDecimal();
656
657             float remainingHeight = heightSize - totalLength;
658             float totalWeightLength = 0.0f;
659
660             // Up to now, only WrapContent children's sizes are added to the totalLength.
661             // Since the totalLength is used in OnLayout as the sum of all children's sizes,
662             // the layout size is assigned to the totalLength if MatchParent child exists.
663             if (childrenMatchParentCount > 0)
664             {
665                 totalLength = heightSize;
666             }
667
668             // 2ND PHASE:
669             //
670             // We measure all children whose height specification policy is MatchParent without weight.
671             // After 2nd phase, all children's heights are calculated without considering weight.
672             // And the heights of all weighted children are accumulated to calculate weighted height.
673             foreach (var childLayout in LayoutChildren)
674             {
675                 if (!childLayout.SetPositionByLayout)
676                 {
677                     continue;
678                 }
679                 int childDesiredWidth = childLayout.Owner.WidthSpecification;
680                 int childDesiredHeight = childLayout.Owner.HeightSpecification;
681                 float childWeight = childLayout.Owner.Weight;
682                 Extents childMargin = childLayout.Margin;
683                 bool useRemainingHeight = (childDesiredHeight == 0) && (childWeight > 0);
684                 bool needToMeasure = false;
685
686                 if ((childDesiredWidth == LayoutParamPolicies.MatchParent) || (useRemainingHeight))
687                 {
688                     if (isWidthExactly)
689                     {
690                         needToMeasure = true;
691                     }
692                     // RelativeLayout's MatchParent children should not fill to the RelativeLayout.
693                     // Because the children's sizes and positions are calculated by RelativeLayout's APIs.
694                     // Therefore, not to fill the RelativeLayout, the mode is changed from Exactly to AtMost.
695                     //
696                     // Not to print the recursive reference error message for this case, Specification is checked if it is WrapContent.
697                     else if (Owner.WidthSpecification == LayoutParamPolicies.WrapContent)
698                     {
699                         if (childDesiredWidth == LayoutParamPolicies.MatchParent)
700                         {
701                             Tizen.Log.Error("NUI", "There is a recursive reference! Parent layout(Owner: " + Owner + ")'s WidthSpecification is WrapContent and child layout(Owner: " + childLayout.Owner + ")'s WidthSpecification is MatchParent!");
702                         }
703                         else
704                         {
705                             Tizen.Log.Error("NUI", "There is a recursive reference! Parent layout(Owner: " + Owner + ")'s WidthSpecification is WrapContent and child layout(Owner: " + childLayout.Owner + ")'s WidthSpecification is 0 with positive weight!");
706                         }
707                     }
708                 }
709
710                 if (remainingHeight > 0)
711                 {
712                     if ((childDesiredHeight == LayoutParamPolicies.MatchParent) || (useRemainingHeight))
713                     {
714                         if (isHeightExactly)
715                         {
716                             // In MeasureChildWithMargins(), it is assumed that heightMeasureSpec includes Padding.Top and Padding.Bottom.
717                             // Therefore, Padding.Top and Padding.Bottom are added to heightMeasureSpec.Size before it is passed to MeasureChildWithMargins().
718                             heightMeasureSpec.SetSize(new LayoutLength((int)(remainingHeight / childrenMatchParentCount) + Padding.Top + Padding.Bottom));
719                             needToMeasure = true;
720                         }
721                         // RelativeLayout's MatchParent children should not fill to the RelativeLayout.
722                         // Because the children's sizes and positions are calculated by RelativeLayout's APIs.
723                         // Therefore, not to fill the RelativeLayout, the mode is changed from Exactly to AtMost.
724                         //
725                         // Not to print the recursive reference error message for this case, Specification is checked if it is WrapContent.
726                         else if (Owner.HeightSpecification == LayoutParamPolicies.WrapContent)
727                         {
728                             if (childDesiredHeight == LayoutParamPolicies.MatchParent)
729                             {
730                                 Tizen.Log.Error("NUI", "There is a recursive reference! Parent layout(Owner: " + Owner + ")'s HeightSpecification is WrapContent and child layout(Owner: " + childLayout.Owner + ")'s HeightSpecification is MatchParent!");
731                             }
732                             else
733                             {
734                                 Tizen.Log.Error("NUI", "There is a recursive reference! Parent layout(Owner: " + Owner + ")'s HeightSpecification is WrapContent and child layout(Owner: " + childLayout.Owner + ")'s HeightSpecification is 0 with positive weight!");
735                             }
736                         }
737                     }
738                 }
739
740                 if (needToMeasure == true)
741                 {
742                     MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0));
743                 }
744
745                 if ((childWeight > 0) && ((childDesiredHeight == LayoutParamPolicies.MatchParent) || (childDesiredHeight == 0)))
746                 {
747                     float childMeasuredHeight = childLayout.MeasuredHeight.Size.AsDecimal();
748
749                     if (childMeasuredHeight < 0)
750                     {
751                         totalWeightLength = Math.Max(totalWeightLength, totalWeightLength + childMargin.Top + childMargin.Bottom);
752                     }
753                     else
754                     {
755                         totalWeightLength = Math.Max(totalWeightLength, totalWeightLength + childMeasuredHeight + childMargin.Top + childMargin.Bottom);
756                     }
757                 }
758             } // 2ND PHASE foreach
759
760             // 3RD PHASE:
761             //
762             // We measure all weighted children whose owner has weight greater than 0.
763             // After 3rd phase, all weighted children has height which is proportional to their weights
764             // in remaining height of parent.
765             if (totalWeight > 0)
766             {
767                 foreach (var childLayout in LayoutChildren)
768                 {
769                     if (!childLayout.SetPositionByLayout)
770                     {
771                         continue;
772                     }
773                     int childDesiredHeight = childLayout.Owner.HeightSpecification;
774                     float childWeight = childLayout.Owner.Weight;
775
776                     if ((childWeight > 0) && ((childDesiredHeight == LayoutParamPolicies.MatchParent) || (childDesiredHeight == 0)))
777                     {
778                         if (isHeightExactly)
779                         {
780                             MeasureWeightedChild(childLayout, totalWeightLength, totalWeight, childWeight,
781                                                  widthMeasureSpec, heightMeasureSpec, childState,
782                                                  Orientation.Vertical);
783                         }
784                         // RelativeLayout's MatchParent children should not fill to the RelativeLayout.
785                         // Because the children's sizes and positions are calculated by RelativeLayout's APIs.
786                         // Therefore, not to fill the RelativeLayout, the mode is changed from Exactly to AtMost.
787                         //
788                         // Not to print the recursive reference error message for this case, Specification is checked if it is WrapContent.
789                         else if (Owner.HeightSpecification == LayoutParamPolicies.WrapContent)
790                         {
791                             if (childDesiredHeight == LayoutParamPolicies.MatchParent)
792                             {
793                                 Tizen.Log.Error("NUI", "There is a recursive reference! Parent layout(Owner: " + Owner + ")'s HeightSpecification is WrapContent and child layout(Owner: " + childLayout.Owner + ")'s HeightSpecification is MatchParent!");
794                             }
795                             else
796                             {
797                                 Tizen.Log.Error("NUI", "There is a recursive reference! Parent layout(Owner: " + Owner + ")'s HeightSpecification is WrapContent and child layout(Owner: " + childLayout.Owner + ")'s HeightSpecification is 0 with positive weight!");
798                             }
799                         }
800                     }
801                 }
802             } // 3RD PHASE foreach
803
804             // Decide the max width among children.
805             foreach (var childLayout in LayoutChildren)
806             {
807                 if (!childLayout.SetPositionByLayout)
808                 {
809                     continue;
810                 }
811
812                 Extents childMargin = childLayout.Margin;
813                 float childMarginWidth = childMargin.Start + childMargin.End;
814                 float childMeasuredWidth = childLayout.MeasuredWidth.Size.AsDecimal();
815
816                 if (childMeasuredWidth < 0)
817                 {
818                     maxWidth = Math.Max(maxWidth, childMarginWidth);
819                 }
820                 else
821                 {
822                     maxWidth = Math.Max(maxWidth, childMeasuredWidth + childMarginWidth);
823                 }
824             }
825
826             // Decide the max width compared with paddings and its suggested width.
827             maxWidth = Math.Max(maxWidth, maxWidth + (Padding.Start + Padding.End));
828             maxWidth = Math.Max(maxWidth, SuggestedMinimumWidth.AsRoundedValue());
829
830             heightSizeAndState.State = childState.heightState;
831
832             SetMeasuredDimensions(ResolveSizeAndState(new LayoutLength(maxWidth), widthMeasureSpec, childState.widthState),
833                                   heightSizeAndState);
834         } // MeasureVertical
835
836         private void LayoutHorizontal(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
837         {
838             bool isLayoutRtl = Owner.LayoutDirection == ViewLayoutDirectionType.RTL;
839
840             LayoutLength childTop = new LayoutLength(Padding.Top);
841             LayoutLength childLeft = new LayoutLength(Padding.Start);
842
843             // Where bottom of child should go
844             LayoutLength height = new LayoutLength(bottom - top);
845
846             // Space available for child
847             LayoutLength childSpace = new LayoutLength(height - Padding.Top - Padding.Bottom);
848
849             var LinearChildren = IterateLayoutChildren();
850             int count = LinearChildren.Count<LayoutItem>();
851
852             switch (HorizontalAlignment)
853             {
854                 case HorizontalAlignment.End:
855                     // totalLength contains the padding already
856                     // In case of RTL map END alignment to the left edge
857                     if (isLayoutRtl)
858                     {
859                         childLeft = new LayoutLength(Padding.Start);
860                     }
861                     else
862                     {
863                         childLeft = new LayoutLength(Padding.Start + right.AsDecimal() - left.AsDecimal() - totalLength);
864                     }
865                     break;
866                 case HorizontalAlignment.Center:
867                     // totalLength contains the padding already
868                     childLeft = new LayoutLength(Padding.Start + (right.AsDecimal() - left.AsDecimal() - totalLength) / 2.0f);
869                     break;
870                 case HorizontalAlignment.Begin: // FALL THROUGH (default)
871                 default:
872                     // totalLength contains the padding already
873                     // In case of RTL map BEGIN alignment to the right edge
874                     if (isLayoutRtl)
875                     {
876                         childLeft = new LayoutLength(Padding.Start + right.AsDecimal() - left.AsDecimal() - totalLength);
877                     }
878                     else
879                     {
880                         childLeft = new LayoutLength(Padding.Start);
881                     }
882                     break;
883             }
884
885             int start = 0;
886             int dir = 1;
887
888             // In case of RTL, start drawing from the last child.
889             if (isLayoutRtl)
890             {
891                 start = count - 1;
892                 dir = -1;
893             }
894
895             for (int i = 0; i < count; i++)
896             {
897                 int childIndex = start + dir * i;
898                 // Get a reference to the childLayout at the given index
899                 LayoutItem childLayout = LinearChildren.ElementAt<LayoutItem>(i);
900
901                 LayoutLength childWidth = childLayout.MeasuredWidth.Size;
902                 LayoutLength childHeight = childLayout.MeasuredHeight.Size;
903                 Extents childMargin = childLayout.Margin;
904
905                 switch (VerticalAlignment)
906                 {
907                     case VerticalAlignment.Bottom:
908                         childTop = new LayoutLength(height - Padding.Bottom - childHeight - childMargin.Bottom);
909                         break;
910                     case VerticalAlignment.Center:
911                         childTop = new LayoutLength(Padding.Top + ((childSpace - childHeight).AsDecimal() / 2.0f) + childMargin.Top - childMargin.Bottom);
912                         break;
913                     case VerticalAlignment.Top: // FALLTHROUGH default
914                     default:
915                         childTop = new LayoutLength(Padding.Top + childMargin.Top);
916                         break;
917                 }
918                 childLeft += (isLayoutRtl ? childMargin.End : childMargin.Start);
919                 childLayout.Layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
920                 childLeft += childWidth + (isLayoutRtl ? childMargin.Start : childMargin.End) + ((i < count - 1) ? CellPadding.Width : 0);
921             }
922         } // LayoutHorizontally
923
924         private void LayoutVertical(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
925         {
926             LayoutLength childTop = new LayoutLength(Padding.Top);
927             LayoutLength childLeft = new LayoutLength(Padding.Start);
928
929             // Where end of child should go
930             LayoutLength width = new LayoutLength(right - left);
931
932             // Space available for child
933             LayoutLength childSpace = new LayoutLength(width - Padding.Start - Padding.End);
934
935             var LinearChildren = IterateLayoutChildren();
936             int count = LinearChildren.Count<LayoutItem>();
937
938             switch (VerticalAlignment)
939             {
940                 case VerticalAlignment.Bottom:
941                     // totalLength contains the padding already
942                     childTop = new LayoutLength(Padding.Top + bottom.AsDecimal() - top.AsDecimal() - totalLength);
943                     break;
944                 case VerticalAlignment.Center:
945                     // totalLength contains the padding already
946                     childTop = new LayoutLength(Padding.Top + (bottom.AsDecimal() - top.AsDecimal() - totalLength) / 2.0f);
947                     break;
948                 case VerticalAlignment.Top:  // FALL THROUGH (default)
949                 default:
950                     // totalLength contains the padding already
951                     childTop = new LayoutLength(Padding.Top);
952                     break;
953             }
954
955             for (int i = 0; i < count; i++)
956             {
957                 LayoutItem childLayout = LinearChildren.ElementAt<LayoutItem>(i);
958
959                 LayoutLength childWidth = childLayout.MeasuredWidth.Size;
960                 LayoutLength childHeight = childLayout.MeasuredHeight.Size;
961                 Extents childMargin = childLayout.Margin;
962
963                 childTop += childMargin.Top;
964                 switch (HorizontalAlignment)
965                 {
966                     case HorizontalAlignment.Begin:
967                     default:
968                         {
969                             childLeft = new LayoutLength(Padding.Start + childMargin.Start);
970                             break;
971                         }
972                     case HorizontalAlignment.End:
973                         {
974                             childLeft = new LayoutLength(width - Padding.End - childWidth - childMargin.End);
975                             break;
976                         }
977                     case HorizontalAlignment.Center:
978                         {
979                             childLeft = new LayoutLength(Padding.Start + ((childSpace - childWidth).AsDecimal() / 2.0f) + childMargin.Start - childMargin.End);
980                             break;
981                         }
982                 }
983                 childLayout.Layout(childLeft, childTop, childLeft + childWidth, childTop + childHeight);
984                 childTop += childHeight + childMargin.Bottom + ((i < count - 1) ? CellPadding.Height : 0);
985             }
986         } // LayoutVertical
987
988         private void ForceUniformHeight(MeasureSpecification widthMeasureSpec)
989         {
990             // Pretend that the linear layout has an exact size. This is the measured height of
991             // ourselves. The measured height should be the max height of the children, changed
992             // to accommodate the heightMeasureSpec from the parent
993             MeasureSpecification uniformMeasureSpec = new MeasureSpecification(MeasuredHeight.Size, MeasureSpecification.ModeType.Exactly);
994             foreach (var childLayout in LayoutChildren)
995             {
996                 if (!childLayout.SetPositionByLayout)
997                 {
998                     continue;
999                 }
1000                 int desiredChildHeight = childLayout.Owner.HeightSpecification;
1001                 int desiredChildWidth = childLayout.Owner.WidthSpecification;
1002
1003                 if (desiredChildHeight == LayoutParamPolicies.MatchParent)
1004                 {
1005                     // Temporarily force children to reuse their original measured width
1006                     int originalWidth = desiredChildWidth;
1007                     childLayout.Owner.WidthSpecification = (int)childLayout.MeasuredWidth.Size.AsRoundedValue();
1008                     // Remeasure with new dimensions
1009                     MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0),
1010                                              uniformMeasureSpec, new LayoutLength(0));
1011                     // Restore width specification
1012                     childLayout.Owner.WidthSpecification = originalWidth;
1013                 }
1014             }
1015         }
1016     } //LinearLayout
1017 } // namespace