Merge remote-tracking branch 'origin/master' into tizen
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / Layouting / LayoutGroup.cs
1 /*
2  * Copyright (c) 2020 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 using System;
18 using System.Collections.Generic;
19 using System.ComponentModel;
20 using System.Diagnostics;
21 using Tizen.NUI.BaseComponents;
22 using System.Linq;
23
24 namespace Tizen.NUI
25 {
26     /// <summary>
27     /// [Draft] LayoutGroup class providing container functionality.
28     /// </summary>
29     public class LayoutGroup : LayoutItem, ILayoutParent
30     {
31         /// <summary>
32         /// [Draft] List of child layouts in this container.
33         /// </summary>
34         /// <since_tizen> 6 </since_tizen>
35         protected List<LayoutItem> LayoutChildren{ get;} // Children of this LayoutGroup
36
37         /// <summary>
38         /// [Draft] Constructor
39         /// </summary>
40         /// <since_tizen> 6 </since_tizen>
41         public LayoutGroup()
42         {
43             LayoutChildren = new List<LayoutItem>();
44         }
45
46         /// <summary>
47         /// From ILayoutParent.<br />
48         /// </summary>
49         /// <since_tizen> 6 </since_tizen>
50         /// <param name="childLayout">LayoutItem to add to the layout group.</param>
51         public virtual void Add(LayoutItem childLayout)
52         {
53             LayoutChildren.Add(childLayout);
54             childLayout.SetParent(this);
55             // Child added to use a Add transition.
56             childLayout.ConditionForAnimation = ConditionForAnimation | TransitionCondition.Add;
57             // Child's parent sets all other children not being added to a ChangeOnAdd transition.
58             SetConditionsForAnimationOnLayoutGroup(TransitionCondition.ChangeOnAdd);
59             OnChildAdd(childLayout);
60             RequestLayout();
61         }
62
63         /// <summary>
64         /// Remove all layout children.<br />
65         /// </summary>
66         /// <since_tizen> 6 </since_tizen>
67         public void RemoveAll()
68         {
69             foreach( LayoutItem childLayout in LayoutChildren )
70             {
71                 childLayout.ConditionForAnimation = ConditionForAnimation | TransitionCondition.Remove;
72                 childLayout.Owner = null;
73             }
74             LayoutChildren.Clear();
75             // todo ensure child LayoutItems are still not parented to this group.
76             RequestLayout();
77         }
78
79         /// <summary>
80         /// From ILayoutParent
81         /// </summary>
82         /// <param name="layoutItem">LayoutItem to remove from the layout group.</param>
83         /// <since_tizen> 6 </since_tizen>
84         public virtual void Remove(LayoutItem layoutItem)
85         {
86             bool childRemoved = false;
87             foreach( LayoutItem childLayout in LayoutChildren.ToList() )
88             {
89                 if( childLayout == layoutItem )
90                 {
91                     childLayout.ClearReplaceFlag();
92                     LayoutChildren.Remove(childLayout);
93
94                     if (LayoutWithTransition)
95                     {
96                         if (!childLayout.IsReplaceFlag())
97                         {
98                             NUIApplication.GetDefaultWindow().LayoutController.AddToRemovalStack(childLayout);
99                         }
100
101                         childLayout.ConditionForAnimation = childLayout.ConditionForAnimation | TransitionCondition.Remove;
102                         // Add LayoutItem to the transition stack so can animate it out.
103                         NUIApplication.GetDefaultWindow().LayoutController.AddTransitionDataEntry(new LayoutData(layoutItem, ConditionForAnimation, 0, 0, 0, 0));
104                     }
105                     else
106                     {
107                         if(childLayout.Owner != null)
108                         {
109                             View parent = childLayout.Owner.GetParent() as View;
110
111                             if(parent != null)
112                             {
113                                 parent.RemoveChild(childLayout.Owner);
114                             }
115                             else
116                             {
117                                 NUIApplication.GetDefaultWindow().Remove(childLayout.Owner);
118                             }
119                         }
120                     }
121
122                     // Reset condition for animation ready for next transition when required.
123                     // SetFrame usually would do this but this LayoutItem is being removed.
124                     childLayout.ConditionForAnimation = TransitionCondition.Unspecified;
125                     childRemoved = true;
126                 }
127             }
128
129             if (childRemoved)
130             {
131                 // If child removed then set all siblings not being added to a ChangeOnRemove transition.
132                 SetConditionsForAnimationOnLayoutGroup(TransitionCondition.ChangeOnRemove);
133             }
134
135             RequestLayout();
136         }
137
138
139         /// <summary>
140         /// Sets the sibling order of the layout item so the layout can be defined within the same parent.
141         /// </summary>
142         /// <param name="order">the sibling order of the layout item</param>
143         /// <since_tizen> 6 </since_tizen>
144         /// This will be public opened in tizen_next after ACR done. Before ACR, need to be hidden as inhouse API.
145         [EditorBrowsable(EditorBrowsableState.Never)]
146         public void ChangeLayoutSiblingOrder(int order)
147         {
148             if(Owner != null && Owner.Parent != null)
149             {
150                 LayoutGroup parent = Owner.Parent.Layout as LayoutGroup;
151
152                 if(parent != null && parent.LayoutChildren.Count>order)
153                 {
154                     parent.LayoutChildren.Remove(this);
155                     parent.LayoutChildren.Insert(order,this);
156                 }
157             }
158
159             RequestLayout();
160         }
161
162         // Attaches to View ChildAdded signal so called when a View is added to a view.
163         private void AddChildToLayoutGroup(View child)
164         {
165             // Only give children a layout if their parent is an explicit container or a pure View.
166             // Pure View meaning not derived from a View, e.g a Legacy container.
167             // layoutSet flag is true when the View became a layout using the set Layout API opposed to automatically due to it's parent.
168             // First time the set Layout API is used by any View the Window no longer has layoutingDisabled.
169
170             // If child already has a Layout then don't change it.
171             if (! View.layoutingDisabled && (null == child.Layout))
172             {
173                 // Only wrap View with a Layout if a child a pure View or Layout explicitly set on this Layout
174                 if ((true == Owner.layoutSet || GetType() == typeof(View)))
175                 {
176                     // If child of this layout is a pure View then assign it a LayoutGroup
177                     // If the child is derived from a View then it may be a legacy or existing container hence will do layouting itself.
178                     if (child.GetType() == typeof(View))
179                     {
180                         child.Layout = new LayoutGroup();
181                     }
182                     else
183                     {
184                         // Adding child as a leaf, layouting will not propagate past this child.
185                         // Legacy containers will be a LayoutItems too and layout their children how they wish.
186                         child.Layout = new LayoutItem();
187                     }
188                 }
189             }
190             else
191             {
192                 // Add child layout to this LayoutGroup (Setting parent in the process)
193                 if(child.Layout != null)
194                 {
195                     Add(child.Layout);
196                 }
197             }
198             // Parent transitions are not attached to children.
199         }
200
201         /// <summary>
202         /// If the child has a layout then it is removed from the parent layout.
203         /// </summary>
204         /// <param name="child">Child View to remove.</param>
205         internal void RemoveChildFromLayoutGroup(View child)
206         {
207             Debug.Assert(child.Layout !=null);
208             Remove(child.Layout);
209         }
210
211         /// <summary>
212         /// Set all children in a LayoutGroup to the supplied condition.
213         /// Children with Add or Remove conditions should not be changed.
214         /// </summary>
215         private void SetConditionsForAnimationOnLayoutGroup( TransitionCondition conditionToSet)
216         {
217             foreach( LayoutItem childLayout in LayoutChildren )
218             {
219                 switch( conditionToSet )
220                 {
221                 case TransitionCondition.ChangeOnAdd :
222                 {
223                     // If other children also being added (TransitionCondition.Add) then do not change their
224                     // conditions, Continue to use their Add transitions.
225                     if (childLayout.ConditionForAnimation.HasFlag(TransitionCondition.Add))
226                     {
227                         break;  // Child being Added so don't update it's condition
228                     }
229                     else
230                     {
231                         // Set siblings for the child being added to use the ChangeOnAdd transition.
232                         childLayout.ConditionForAnimation = TransitionCondition.ChangeOnAdd;
233                     }
234                     break;
235                 }
236                 case TransitionCondition.ChangeOnRemove :
237                 {
238                     if (childLayout.ConditionForAnimation.HasFlag(TransitionCondition.Remove))
239                     {
240                         break; // Child being Removed so don't update it's condition
241                     }
242                     else
243                     {
244                         childLayout.ConditionForAnimation = TransitionCondition.ChangeOnRemove;
245                     }
246                     break;
247                 }
248                 case TransitionCondition.LayoutChanged :
249                 {
250                     childLayout.ConditionForAnimation = TransitionCondition.LayoutChanged;
251                     break;
252                 }
253                 }
254             }
255
256         }
257
258         /// <summary>
259         /// Callback for View.ChildAdded event
260         /// </summary>
261         /// <param name="sender">The object triggering the event.</param>
262         /// <param name="childAddedEvent">Arguments from the event.</param>
263         void OnChildAddedToOwner(object sender, View.ChildAddedEventArgs childAddedEvent)
264         {
265             AddChildToLayoutGroup(childAddedEvent.Added);
266         }
267
268         /// <summary>
269         /// Callback for View.ChildRemoved event
270         /// </summary>
271         /// <param name="sender">The object triggering the event.</param>
272         /// <param name="childRemovedEvent">Arguments from the event.</param>
273         void OnChildRemovedFromOwner(object sender, View.ChildRemovedEventArgs childRemovedEvent)
274         {
275             RemoveChildFromLayoutGroup(childRemovedEvent.Removed);
276         }
277
278         /// <summary>
279         /// Calculate the right measure spec for this child.
280         /// Does the hard part of MeasureChildren: figuring out the MeasureSpec to
281         /// pass to a particular child. This method figures out the right MeasureSpec
282         /// for one dimension (height or width) of one child view.<br />
283         /// </summary>
284         /// <param name="parentMeasureSpec">The requirements for this view. MeasureSpecification.</param>
285         /// <param name="padding">The padding of this view for the current dimension and margins, if applicable. LayoutLength.</param>
286         /// <param name="childDimension"> How big the child wants to be in the current dimension. LayoutLength.</param>
287         /// <returns>a MeasureSpec for the child.</returns>
288         public static MeasureSpecification GetChildMeasureSpecification(MeasureSpecification parentMeasureSpec, LayoutLength padding, LayoutLength childDimension)
289         {
290             MeasureSpecification.ModeType specMode = parentMeasureSpec.Mode;
291             MeasureSpecification.ModeType resultMode = MeasureSpecification.ModeType.Unspecified;
292
293             // Child only can use parent's size without parent's padding and own margin.
294             LayoutLength resultSize = new LayoutLength(Math.Max( 0.0f, parentMeasureSpec.Size.AsDecimal()));
295             switch( specMode )
296             {
297                 // Parent has imposed an exact size on us
298                 case MeasureSpecification.ModeType.Exactly:
299                 {
300                     if ((int)childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent)
301                     {
302                         // Child wants to be our size. So be it.
303                         resultMode = MeasureSpecification.ModeType.Exactly;
304                     }
305                     else if ((int)childDimension.AsRoundedValue() == LayoutParamPolicies.WrapContent)
306                     {
307                         // Child wants to determine its own size. It can't be
308                         // bigger than us.
309                         // Don't need parent's size. Size of this child will be determined by its children.
310                         resultMode = MeasureSpecification.ModeType.AtMost;
311                     }
312                     else
313                     {
314                         // Child has its own size.
315                         resultSize = childDimension;
316                         resultMode = MeasureSpecification.ModeType.Exactly;
317                     }
318
319                     break;
320                 }
321
322                 // Parent has imposed a maximum size on us
323                 case MeasureSpecification.ModeType.AtMost:
324                 {
325                     if (childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent)
326                     {
327                         // Crashed. Cannot calculate. 
328
329                         // Child wants to be our size, but our size is not fixed.
330                         // Constrain child to not be bigger than us.
331                         resultMode = MeasureSpecification.ModeType.AtMost;
332                     }
333                     else if (childDimension.AsRoundedValue() == LayoutParamPolicies.WrapContent)
334                     {
335                         // Child wants to determine its own size. It can't be
336                         // bigger than us.
337
338                         // Don't need parent's size. Size of this child will be determined by its children.
339                         resultMode = MeasureSpecification.ModeType.AtMost;
340                     }
341                     else
342                     {
343                         // Child wants a specific size... so be it
344                         resultSize = childDimension;
345                         resultMode = MeasureSpecification.ModeType.Exactly;
346                     }
347
348                     break;
349                 }
350
351                 // Parent asked to see how big we want to be
352                 case MeasureSpecification.ModeType.Unspecified:
353                 {
354                     if ((childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent))
355                     {
356                         // Child wants to be our size... find out how big it should be
357
358                         // There is no one who has exact size in parent hierarchy.
359                         // Cannot calculate.
360                         resultMode = MeasureSpecification.ModeType.Unspecified;
361                     }
362                     else if (childDimension.AsRoundedValue() == (LayoutParamPolicies.WrapContent))
363                     {
364                         // Child wants to determine its own size.... find out how big
365                         // it should be
366                         resultMode = MeasureSpecification.ModeType.Unspecified;
367                     }
368                     else
369                     {
370                         // Child wants a specific size... let him have it
371                         resultSize = childDimension;
372                         resultMode = MeasureSpecification.ModeType.Exactly;
373                     }
374                     break;
375                 }
376             } // switch
377
378             return new MeasureSpecification( resultSize, resultMode );
379         }
380
381         /// <summary>
382         /// Measure the layout and its content to determine the measured width and the measured height.<br />
383         /// If this method is overridden, it is the subclass's responsibility to make
384         /// sure the measured height and width are at least the layout's minimum height
385         /// and width. <br />
386         /// </summary>
387         /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
388         /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
389         /// <since_tizen> 6 </since_tizen>
390         protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
391         {
392             LayoutLength measuredWidth = new LayoutLength(0.0f);
393             LayoutLength measuredHeight = new LayoutLength(0.0f);
394
395             // Layout takes size of largest child width and largest child height dimensions
396             foreach( LayoutItem childLayout in LayoutChildren )
397             {
398                 if( childLayout != null )
399                 {
400                     MeasureChild( childLayout, widthMeasureSpec, heightMeasureSpec );
401                     LayoutLength childWidth = new LayoutLength(childLayout.MeasuredWidth.Size);
402                     LayoutLength childHeight = new LayoutLength( childLayout.MeasuredHeight.Size);
403
404                     Extents childMargin = childLayout.Margin;
405                     measuredWidth = new LayoutLength(Math.Max( measuredWidth.AsDecimal(), childWidth.AsDecimal() + childMargin.Start + childMargin.End));
406                     measuredHeight = new LayoutLength(Math.Max( measuredHeight.AsDecimal(), childHeight.AsDecimal() + childMargin.Top + childMargin.Bottom));
407                 }
408             }
409
410             if( 0 == LayoutChildren.Count )
411             {
412                 // Must be a leaf as has no children
413                 measuredWidth = GetDefaultSize( SuggestedMinimumWidth, widthMeasureSpec );
414                 measuredHeight = GetDefaultSize( SuggestedMinimumHeight, heightMeasureSpec );
415             }
416
417             SetMeasuredDimensions( new MeasuredSize( measuredWidth, MeasuredSize.StateType.MeasuredSizeOK ),
418                                    new MeasuredSize( measuredHeight, MeasuredSize.StateType.MeasuredSizeOK ) );
419         }
420
421         /// <summary>
422         /// Called from Layout() when this layout should assign a size and position to each of its children.<br />
423         /// Derived classes with children should override this method and call Layout() on each of their children.<br />
424         /// </summary>
425         /// <param name="changed">This is a new size or position for this layout.</param>
426         /// <param name="left">Left position, relative to parent.</param>
427         /// <param name="top"> Top position, relative to parent.</param>
428         /// <param name="right">Right position, relative to parent.</param>
429         /// <param name="bottom">Bottom position, relative to parent.</param>
430         /// <since_tizen> 6 </since_tizen>
431         protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
432         {
433             foreach( LayoutItem childLayout in LayoutChildren )
434             {
435                 if( childLayout !=null )
436                 {
437                     // Use position if explicitly set to child otherwise will be top left.
438                     var childLeft = new LayoutLength( childLayout.Owner.Position2D.X );
439                     var childTop = new LayoutLength( childLayout.Owner.Position2D.Y );
440
441                     View owner = Owner;
442
443                     if ( owner )
444                     {
445                         // Margin and Padding only supported when child anchor point is TOP_LEFT.
446                         if ( owner.PivotPoint == PivotPoint.TopLeft || ( owner.PositionUsesPivotPoint == false ) )
447                         {
448                             childLeft = childLeft + owner.Padding.Start + childLayout.Margin.Start;
449                             childTop = childTop + owner.Padding.Top + childLayout.Margin.Top;
450                         }
451                     }
452                     childLayout.Layout( childLeft, childTop, childLeft + childLayout.MeasuredWidth.Size, childTop + childLayout.MeasuredHeight.Size );
453                 }
454             }
455         }
456
457         /// <summary>
458         /// Overridden method called when the layout is attached to an owner.<br />
459         /// </summary>
460         /// <since_tizen> 6 </since_tizen>
461         protected override void OnAttachedToOwner()
462         {
463             // Layout takes ownership of it's owner's children.
464             foreach (View view in Owner.Children)
465             {
466                 AddChildToLayoutGroup(view);
467             }
468
469             // Connect to owner ChildAdded signal.
470             Owner.ChildAdded += OnChildAddedToOwner;
471
472             // Removing Child from the owners View will directly call the LayoutGroup removal API.
473         }
474
475         // Virtual Methods that can be overridden by derived classes.
476
477         /// <summary>
478         /// Callback when child is added to container.<br />
479         /// Derived classes can use this to set their own child properties on the child layout's owner.<br />
480         /// </summary>
481         /// <param name="child">The Layout child.</param>
482         /// <since_tizen> 6 </since_tizen>
483         protected virtual void OnChildAdd(LayoutItem child)
484         {
485         }
486
487         /// <summary>
488         /// Callback when child is removed from container.<br />
489         /// </summary>
490         /// <param name="child">The Layout child.</param>
491         /// <since_tizen> 6 </since_tizen>
492         protected virtual void OnChildRemove(LayoutItem child)
493         {
494         }
495
496         /// <summary>
497         /// Ask all of the children of this view to measure themselves, taking into
498         /// account both the MeasureSpec requirements for this view and its padding.<br />
499         /// The heavy lifting is done in GetChildMeasureSpec.<br />
500         /// </summary>
501         /// <param name="widthMeasureSpec">The width requirements for this view.</param>
502         /// <param name="heightMeasureSpec">The height requirements for this view.</param>
503         /// <since_tizen> 6 </since_tizen>
504         protected virtual void MeasureChildren(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
505         {
506             foreach( LayoutItem childLayout in LayoutChildren )
507             {
508                 MeasureChild( childLayout, widthMeasureSpec, heightMeasureSpec );
509             }
510         }
511
512         /// <summary>
513         /// Ask one of the children of this view to measure itself, taking into
514         /// account both the MeasureSpec requirements for this view and its padding.<br />
515         /// The heavy lifting is done in GetChildMeasureSpecification.<br />
516         /// </summary>
517         /// <param name="child">The child to measure.</param>
518         /// <param name="parentWidthMeasureSpec">The width requirements for this view.</param>
519         /// <param name="parentHeightMeasureSpec">The height requirements for this view.</param>
520         /// <since_tizen> 6 </since_tizen>
521         protected virtual void MeasureChild(LayoutItem child, MeasureSpecification parentWidthMeasureSpec, MeasureSpecification parentHeightMeasureSpec)
522         {
523             View childOwner = child.Owner;
524
525             MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification(
526                         new MeasureSpecification(
527                             new LayoutLength(parentWidthMeasureSpec.Size - (Padding.Start + Padding.End + childOwner.Margin.Start + childOwner.Margin.End)),
528                             parentWidthMeasureSpec.Mode),
529                         new LayoutLength(Padding.Start + Padding.End ),
530                         new LayoutLength(childOwner.WidthSpecification) );
531
532             MeasureSpecification childHeightMeasureSpec = GetChildMeasureSpecification(
533                         new MeasureSpecification(
534                             new LayoutLength(parentHeightMeasureSpec.Size - (Padding.Top + Padding.Bottom + childOwner.Margin.Top + childOwner.Margin.Bottom)),
535                             parentHeightMeasureSpec.Mode),
536                         new LayoutLength(Padding.Top + Padding.Bottom),
537                         new LayoutLength(childOwner.HeightSpecification));
538
539             child.Measure( childWidthMeasureSpec, childHeightMeasureSpec );
540         }
541
542         /// <summary>
543         /// Ask one of the children of this view to measure itself, taking into
544         /// account both the MeasureSpec requirements for this view and its padding.<br />
545         /// and margins. The child must have MarginLayoutParams The heavy lifting is
546         /// done in GetChildMeasureSpecification.<br />
547         /// </summary>
548         /// <param name="child">The child to measure.</param>
549         /// <param name="parentWidthMeasureSpec">The width requirements for this view.</param>
550         /// <param name="widthUsed">Extra space that has been used up by the parent horizontally (possibly by other children of the parent).</param>
551         /// <param name="parentHeightMeasureSpec">The height requirements for this view.</param>
552         /// <param name="heightUsed">Extra space that has been used up by the parent vertically (possibly by other children of the parent).</param>
553         /// <since_tizen> 6 </since_tizen>
554         protected virtual void MeasureChildWithMargins(LayoutItem child, MeasureSpecification parentWidthMeasureSpec, LayoutLength widthUsed, MeasureSpecification parentHeightMeasureSpec, LayoutLength heightUsed)
555         {
556             View childOwner = child.Owner;
557
558
559             MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification(
560                         new MeasureSpecification(
561                             new LayoutLength(parentWidthMeasureSpec.Size + widthUsed - (Padding.Start + Padding.End + childOwner.Margin.Start + childOwner.Margin.End)),
562                             parentWidthMeasureSpec.Mode),
563                         new LayoutLength(Padding.Start + Padding.End ),
564                         new LayoutLength(childOwner.WidthSpecification) );
565
566             MeasureSpecification childHeightMeasureSpec = GetChildMeasureSpecification(
567                         new MeasureSpecification(
568                             new LayoutLength(parentHeightMeasureSpec.Size + heightUsed - (Padding.Top + Padding.Bottom + childOwner.Margin.Top + childOwner.Margin.Bottom)),
569                             parentHeightMeasureSpec.Mode),
570                         new LayoutLength(Padding.Top + Padding.Bottom),
571                         new LayoutLength(childOwner.HeightSpecification));
572             child.Measure( childWidthMeasureSpec, childHeightMeasureSpec );
573
574         }
575     }
576 }