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