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