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