[NUI] Fix reparenting of Child bug (#1314)
[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                     if (! childLayout.IsReplaceFlag())
92                     {
93                         Window.Instance.LayoutController.AddToRemovalStack(childLayout);
94                     }
95                     childLayout.ClearReplaceFlag();
96                     LayoutChildren.Remove(childLayout);
97                     childLayout.ConditionForAnimation = childLayout.ConditionForAnimation | TransitionCondition.Remove;
98                     // Add LayoutItem to the transition stack so can animate it out.
99                     Window.Instance.LayoutController.AddTransitionDataEntry(new LayoutData(layoutItem, ConditionForAnimation, 0,0,0,0));
100                     // Reset condition for animation ready for next transition when required.
101                     // SetFrame usually would do this but this LayoutItem is being removed.
102                     childLayout.ConditionForAnimation = TransitionCondition.Unspecified;
103                     childRemoved = true;
104                 }
105             }
106             if (childRemoved)
107             {
108                 // If child removed then set all siblings not being added to a ChangeOnRemove transition.
109                 SetConditionsForAnimationOnLayoutGroup(TransitionCondition.ChangeOnRemove);
110             }
111             RequestLayout();
112         }
113
114         // Attaches to View ChildAdded signal so called when a View is added to a view.
115         private void AddChildToLayoutGroup(View child)
116         {
117             // Only give children a layout if their parent is an explicit container or a pure View.
118             // Pure View meaning not derived from a View, e.g a Legacy container.
119             // layoutSet flag is true when the View became a layout using the set Layout API opposed to automatically due to it's parent.
120             // First time the set Layout API is used by any View the Window no longer has layoutingDisabled.
121
122             // If child already has a Layout then don't change it.
123             if (! View.layoutingDisabled && (null == child.Layout))
124             {
125                 // Only wrap View with a Layout if a child a pure View or Layout explicitly set on this Layout
126                 if ((true == Owner.layoutSet || GetType() == typeof(View)))
127                 {
128                     // If child of this layout is a pure View then assign it a LayoutGroup
129                     // If the child is derived from a View then it may be a legacy or existing container hence will do layouting itself.
130                     if (child.GetType() == typeof(View))
131                     {
132                         child.Layout = new LayoutGroup();
133                     }
134                     else
135                     {
136                         // Adding child as a leaf, layouting will not propagate past this child.
137                         // Legacy containers will be a LayoutItems too and layout their children how they wish.
138                         child.Layout = new LayoutItem();
139                     }
140                 }
141             }
142             else
143             {
144                 // Add child layout to this LayoutGroup (Setting parent in the process)
145                 if(child.Layout != null)
146                 {
147                     Add(child.Layout);
148                 }
149             }
150             // Parent transitions are not attached to children.
151         }
152
153         /// <summary>
154         /// If the child has a layout then it is removed from the parent layout.
155         /// </summary>
156         /// <param name="child">Child View to remove.</param>
157         internal void RemoveChildFromLayoutGroup(View child)
158         {
159             Debug.Assert(child.Layout !=null);
160             Remove(child.Layout);
161         }
162
163         /// <summary>
164         /// Set all children in a LayoutGroup to the supplied condition.
165         /// Children with Add or Remove conditions should not be changed.
166         /// </summary>
167         private void SetConditionsForAnimationOnLayoutGroup( TransitionCondition conditionToSet)
168         {
169             foreach( LayoutItem childLayout in LayoutChildren )
170             {
171                 switch( conditionToSet )
172                 {
173                 case TransitionCondition.ChangeOnAdd :
174                 {
175                     // If other children also being added (TransitionCondition.Add) then do not change their
176                     // conditions, Continue to use their Add transitions.
177                     if (childLayout.ConditionForAnimation.HasFlag(TransitionCondition.Add))
178                     {
179                         break;  // Child being Added so don't update it's condition
180                     }
181                     else
182                     {
183                         // Set siblings for the child being added to use the ChangeOnAdd transition.
184                         childLayout.ConditionForAnimation = TransitionCondition.ChangeOnAdd;
185                     }
186                     break;
187                 }
188                 case TransitionCondition.ChangeOnRemove :
189                 {
190                     if (childLayout.ConditionForAnimation.HasFlag(TransitionCondition.Remove))
191                     {
192                         break; // Child being Removed so don't update it's condition
193                     }
194                     else
195                     {
196                         childLayout.ConditionForAnimation = TransitionCondition.ChangeOnRemove;
197                     }
198                     break;
199                 }
200                 case TransitionCondition.LayoutChanged :
201                 {
202                     childLayout.ConditionForAnimation = TransitionCondition.LayoutChanged;
203                     break;
204                 }
205                 }
206             }
207
208         }
209
210         /// <summary>
211         /// Callback for View.ChildAdded event
212         /// </summary>
213         /// <param name="sender">The object triggering the event.</param>
214         /// <param name="childAddedEvent">Arguments from the event.</param>
215         void OnChildAddedToOwner(object sender, View.ChildAddedEventArgs childAddedEvent)
216         {
217             AddChildToLayoutGroup(childAddedEvent.Added);
218         }
219
220         /// <summary>
221         /// Callback for View.ChildRemoved event
222         /// </summary>
223         /// <param name="sender">The object triggering the event.</param>
224         /// <param name="childRemovedEvent">Arguments from the event.</param>
225         void OnChildRemovedFromOwner(object sender, View.ChildRemovedEventArgs childRemovedEvent)
226         {
227             RemoveChildFromLayoutGroup(childRemovedEvent.Removed);
228         }
229
230         /// <summary>
231         /// Calculate the right measure spec for this child.
232         /// Does the hard part of MeasureChildren: figuring out the MeasureSpec to
233         /// pass to a particular child. This method figures out the right MeasureSpec
234         /// for one dimension (height or width) of one child view.<br />
235         /// </summary>
236         /// <param name="parentMeasureSpec">The requirements for this view. MeasureSpecification.</param>
237         /// <param name="padding">The padding of this view for the current dimension and margins, if applicable. LayoutLength.</param>
238         /// <param name="childDimension"> How big the child wants to be in the current dimension. LayoutLength.</param>
239         /// <returns>a MeasureSpec for the child.</returns>
240         public static MeasureSpecification GetChildMeasureSpecification(MeasureSpecification parentMeasureSpec, LayoutLength padding, LayoutLength childDimension)
241         {
242             MeasureSpecification.ModeType specMode = parentMeasureSpec.Mode;
243             MeasureSpecification.ModeType resultMode = MeasureSpecification.ModeType.Unspecified;
244             LayoutLength resultSize = new LayoutLength(Math.Max( 0.0f, (parentMeasureSpec.Size.AsDecimal() - padding.AsDecimal() ) )); // reduce available size by the owners padding
245
246             switch( specMode )
247             {
248                 // Parent has imposed an exact size on us
249                 case MeasureSpecification.ModeType.Exactly:
250                 {
251                 if ((int)childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent)
252                 {
253                     // Child wants to be our size. So be it.
254                     resultMode = MeasureSpecification.ModeType.Exactly;
255                 }
256                 else if ((int)childDimension.AsRoundedValue() == LayoutParamPolicies.WrapContent)
257                 {
258                     // Child wants to determine its own size. It can't be
259                     // bigger than us.
260                     resultMode = MeasureSpecification.ModeType.AtMost;
261                 }
262                 else
263                 {
264                     resultSize = childDimension;
265                     resultMode = MeasureSpecification.ModeType.Exactly;
266                 }
267
268                 break;
269                 }
270
271                 // Parent has imposed a maximum size on us
272                 case MeasureSpecification.ModeType.AtMost:
273                 {
274                 if (childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent)
275                 {
276                     // Child wants to be our size, but our size is not fixed.
277                     // Constrain child to not be bigger than us.
278                     resultMode = MeasureSpecification.ModeType.AtMost;
279                 }
280                 else if (childDimension.AsRoundedValue() == LayoutParamPolicies.WrapContent)
281                 {
282                     // Child wants to determine its own size. It can't be
283                     // bigger than us.
284                     resultMode = MeasureSpecification.ModeType.AtMost;
285                 }
286                 else
287                 {
288                     // Child wants a specific size... so be it
289                     resultSize = childDimension + padding;
290                     resultMode = MeasureSpecification.ModeType.Exactly;
291                 }
292
293                 break;
294                 }
295
296                 // Parent asked to see how big we want to be
297                 case MeasureSpecification.ModeType.Unspecified:
298                 {
299
300                 if ((childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent))
301                 {
302                     // Child wants to be our size... find out how big it should be
303                     resultMode = MeasureSpecification.ModeType.Unspecified;
304                 }
305                 else if (childDimension.AsRoundedValue() == (LayoutParamPolicies.WrapContent))
306                 {
307                     // Child wants to determine its own size.... find out how big
308                     // it should be
309                     resultMode = MeasureSpecification.ModeType.Unspecified;
310                 }
311                 else
312                 {
313                     // Child wants a specific size... let him have it
314                     resultSize = childDimension + padding;
315                     resultMode = MeasureSpecification.ModeType.Exactly;
316                 }
317                 break;
318                 }
319             } // switch
320
321             return new MeasureSpecification( resultSize, resultMode );
322         }
323
324         /// <summary>
325         /// Measure the layout and its content to determine the measured width and the measured height.<br />
326         /// If this method is overridden, it is the subclass's responsibility to make
327         /// sure the measured height and width are at least the layout's minimum height
328         /// and width. <br />
329         /// </summary>
330         /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
331         /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
332         /// <since_tizen> 6 </since_tizen>
333         protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
334         {
335             LayoutLength measuredWidth = new LayoutLength(0.0f);
336             LayoutLength measuredHeight = new LayoutLength(0.0f);
337
338             // Layout takes size of largest child width and largest child height dimensions
339             foreach( LayoutItem childLayout in LayoutChildren )
340             {
341                 if( childLayout != null )
342                 {
343                     MeasureChild( childLayout, widthMeasureSpec, heightMeasureSpec );
344                     LayoutLength childWidth = new LayoutLength(childLayout.MeasuredWidth.Size);
345                     LayoutLength childHeight = new LayoutLength( childLayout.MeasuredHeight.Size);
346
347                     Extents childMargin = childLayout.Margin;
348                     measuredWidth = new LayoutLength(Math.Max( measuredWidth.AsDecimal(), childWidth.AsDecimal() + childMargin.Start + childMargin.End));
349                     measuredHeight = new LayoutLength(Math.Max( measuredHeight.AsDecimal(), childHeight.AsDecimal() + childMargin.Top + childMargin.Bottom));
350                 }
351             }
352
353             if( 0 == LayoutChildren.Count )
354             {
355                 // Must be a leaf as has no children
356                 measuredWidth = GetDefaultSize( SuggestedMinimumWidth, widthMeasureSpec );
357                 measuredHeight = GetDefaultSize( SuggestedMinimumHeight, heightMeasureSpec );
358             }
359
360             SetMeasuredDimensions( new MeasuredSize( measuredWidth, MeasuredSize.StateType.MeasuredSizeOK ),
361                                    new MeasuredSize( measuredHeight, MeasuredSize.StateType.MeasuredSizeOK ) );
362         }
363
364         /// <summary>
365         /// Called from Layout() when this layout should assign a size and position to each of its children.<br />
366         /// Derived classes with children should override this method and call Layout() on each of their children.<br />
367         /// </summary>
368         /// <param name="changed">This is a new size or position for this layout.</param>
369         /// <param name="left">Left position, relative to parent.</param>
370         /// <param name="top"> Top position, relative to parent.</param>
371         /// <param name="right">Right position, relative to parent.</param>
372         /// <param name="bottom">Bottom position, relative to parent.</param>
373         /// <since_tizen> 6 </since_tizen>
374         protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
375         {
376             foreach( LayoutItem childLayout in LayoutChildren )
377             {
378                 if( childLayout !=null )
379                 {
380                     // Use position if explicitly set to child otherwise will be top left.
381                     var childLeft = new LayoutLength( childLayout.Owner.Position2D.X );
382                     var childTop = new LayoutLength( childLayout.Owner.Position2D.Y );
383
384                     View owner = Owner;
385
386                     if ( owner )
387                     {
388                         // Margin and Padding only supported when child anchor point is TOP_LEFT.
389                         if ( owner.PivotPoint == PivotPoint.TopLeft || ( owner.PositionUsesPivotPoint == false ) )
390                         {
391                             childLeft = childLeft + owner.Padding.Start + childLayout.Margin.Start;
392                             childTop = childTop + owner.Padding.Top + childLayout.Margin.Top;
393                         }
394                     }
395                     childLayout.Layout( childLeft, childTop, childLeft + childLayout.MeasuredWidth.Size, childTop + childLayout.MeasuredHeight.Size );
396                 }
397             }
398         }
399
400         /// <summary>
401         /// Overridden method called when the layout is attached to an owner.<br />
402         /// </summary>
403         /// <since_tizen> 6 </since_tizen>
404         protected override void OnAttachedToOwner()
405         {
406             // Layout takes ownership of it's owner's children.
407             foreach (View view in Owner.Children)
408             {
409                 AddChildToLayoutGroup(view);
410             }
411
412             // Connect to owner ChildAdded signal.
413             Owner.ChildAdded += OnChildAddedToOwner;
414
415             // Removing Child from the owners View will directly call the LayoutGroup removal API.
416         }
417
418         // Virtual Methods that can be overridden by derived classes.
419
420         /// <summary>
421         /// Callback when child is added to container.<br />
422         /// Derived classes can use this to set their own child properties on the child layout's owner.<br />
423         /// </summary>
424         /// <param name="child">The Layout child.</param>
425         /// <since_tizen> 6 </since_tizen>
426         protected virtual void OnChildAdd(LayoutItem child)
427         {
428         }
429
430         /// <summary>
431         /// Callback when child is removed from container.<br />
432         /// </summary>
433         /// <param name="child">The Layout child.</param>
434         /// <since_tizen> 6 </since_tizen>
435         protected virtual void OnChildRemove(LayoutItem child)
436         {
437         }
438
439         /// <summary>
440         /// Ask all of the children of this view to measure themselves, taking into
441         /// account both the MeasureSpec requirements for this view and its padding.<br />
442         /// The heavy lifting is done in GetChildMeasureSpec.<br />
443         /// </summary>
444         /// <param name="widthMeasureSpec">The width requirements for this view.</param>
445         /// <param name="heightMeasureSpec">The height requirements for this view.</param>
446         /// <since_tizen> 6 </since_tizen>
447         protected virtual void MeasureChildren(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
448         {
449             foreach( LayoutItem childLayout in LayoutChildren )
450             {
451                 MeasureChild( childLayout, widthMeasureSpec, heightMeasureSpec );
452             }
453         }
454
455         /// <summary>
456         /// Ask one of the children of this view to measure itself, taking into
457         /// account both the MeasureSpec requirements for this view and its padding.<br />
458         /// The heavy lifting is done in GetChildMeasureSpecification.<br />
459         /// </summary>
460         /// <param name="child">The child to measure.</param>
461         /// <param name="parentWidthMeasureSpec">The width requirements for this view.</param>
462         /// <param name="parentHeightMeasureSpec">The height requirements for this view.</param>
463         /// <since_tizen> 6 </since_tizen>
464         protected virtual void MeasureChild(LayoutItem child, MeasureSpecification parentWidthMeasureSpec, MeasureSpecification parentHeightMeasureSpec)
465         {
466             View childOwner = child.Owner;
467
468             Extents padding = childOwner.Padding; // Padding of this layout's owner, not of the child being measured.
469
470             MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification( parentWidthMeasureSpec,
471                                                                                        new LayoutLength(padding.Start + padding.End ),
472                                                                                        new LayoutLength(childOwner.WidthSpecification) );
473
474             MeasureSpecification childHeightMeasureSpec = GetChildMeasureSpecification( parentHeightMeasureSpec,
475                                                                                         new LayoutLength(padding.Top + padding.Bottom),
476                                                                                         new LayoutLength(childOwner.HeightSpecification) );
477
478             child.Measure( childWidthMeasureSpec, childHeightMeasureSpec );
479         }
480
481         /// <summary>
482         /// Ask one of the children of this view to measure itself, taking into
483         /// account both the MeasureSpec requirements for this view and its padding.<br />
484         /// and margins. The child must have MarginLayoutParams The heavy lifting is
485         /// done in GetChildMeasureSpecification.<br />
486         /// </summary>
487         /// <param name="child">The child to measure.</param>
488         /// <param name="parentWidthMeasureSpec">The width requirements for this view.</param>
489         /// <param name="widthUsed">Extra space that has been used up by the parent horizontally (possibly by other children of the parent).</param>
490         /// <param name="parentHeightMeasureSpec">The height requirements for this view.</param>
491         /// <param name="heightUsed">Extra space that has been used up by the parent vertically (possibly by other children of the parent).</param>
492         /// <since_tizen> 6 </since_tizen>
493         protected virtual void MeasureChildWithMargins(LayoutItem child, MeasureSpecification parentWidthMeasureSpec, LayoutLength widthUsed, MeasureSpecification parentHeightMeasureSpec, LayoutLength heightUsed)
494         {
495             View childOwner = child.Owner;
496             int desiredWidth = childOwner.WidthSpecification;
497             int desiredHeight = childOwner.HeightSpecification;
498
499             Extents padding = child.Padding; // Padding of this layout's owner, not of the child being measured.
500
501             MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification( parentWidthMeasureSpec,
502                                                                                        new LayoutLength( padding.Start + padding.End ) +
503                                                                                        widthUsed, new LayoutLength(desiredWidth) );
504
505
506             MeasureSpecification childHeightMeasureSpec = GetChildMeasureSpecification( parentHeightMeasureSpec,
507                                                                                         new LayoutLength( padding.Top + padding.Bottom )+
508                                                                                         heightUsed, new LayoutLength(desiredHeight) );
509
510             child.Measure( childWidthMeasureSpec, childHeightMeasureSpec );
511         }
512     }
513 }