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