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