Fix margin bug in FlexLayout (#1418)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / Layouting / FlexLayout.cs
1 /* Copyright (c) 2019 Samsung Electronics Co., Ltd.
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  *
15  */
16
17 using System;
18 using System.ComponentModel;
19 using Tizen.NUI.BaseComponents;
20 using System.Runtime.InteropServices;
21 using System.Diagnostics;
22
23 namespace Tizen.NUI
24 {
25     /// <summary>
26     /// [Draft] This class implements a flex layout.
27     /// The flex layout implementation is based on open source Facebook Yoga layout engine.
28     /// For more information about the flex layout API and how to use it please refer to https://yogalayout.com/docs/
29     /// We implement the subset of the API in the class below.
30     /// </summary>
31     public class FlexLayout : LayoutGroup,  global::System.IDisposable
32     {
33         private static bool LayoutDebugFlex = false; // Debug flag
34         float Flex{ get; set;}
35         int AlignSelf{get; set;}
36
37         private global::System.Runtime.InteropServices.HandleRef swigCPtr;
38         private bool swigCMemOwn;
39         private bool disposed;
40         private bool isDisposeQueued = false;
41
42         private MeasureSpecification parentMeasureSpecificationWidth;
43         private MeasureSpecification parentMeasureSpecificationHeight;
44
45         private IntPtr _rootFlex;  // Pointer to the unmanged flex layout class.
46
47         internal const float FlexUndefined = 10E20F; // Auto setting which is equivalent to WrapContent.
48
49         internal struct MeasuredSize
50         {
51           public MeasuredSize(float x, float y)
52           {
53             width = x;
54             height = y;
55           }
56           float width;
57           float height;
58         };
59
60         [UnmanagedFunctionPointer(CallingConvention.StdCall)]
61         internal delegate MeasuredSize ChildMeasureCallback( global::System.IntPtr child, float width, int measureModeWidth, float height, int measureModeHeight );
62
63         event ChildMeasureCallback measureChildDelegate; // Stores a delegate to the child measure callback. Used for all children of this FlexLayout.
64
65         internal FlexLayout(global::System.IntPtr cPtr, bool cMemoryOwn)
66         {
67             swigCMemOwn = cMemoryOwn;
68             swigCPtr = new global::System.Runtime.InteropServices.HandleRef(this, cPtr);
69             _rootFlex = Interop.FlexLayout.FlexLayout_New();
70             measureChildDelegate = new ChildMeasureCallback(measureChild);
71         }
72
73         internal static global::System.Runtime.InteropServices.HandleRef getCPtr(FlexLayout obj)
74         {
75             return (obj == null) ? new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero) : obj.swigCPtr;
76         }
77
78         /// <summary>
79         /// Dispose.
80         /// </summary>
81         /// <since_tizen> 6 </since_tizen>
82         public void Dispose()
83         {
84             // Throw exception if Dispose() is called in separate thread.
85             if (!Window.IsInstalled())
86             {
87                 throw new System.InvalidOperationException("This API called from separate thread. This API must be called from MainThread.");
88             }
89
90             if (isDisposeQueued)
91             {
92                 Dispose(DisposeTypes.Implicit);
93             }
94             else
95             {
96                 Dispose(DisposeTypes.Explicit);
97                 System.GC.SuppressFinalize(this);
98             }
99         }
100
101         /// <summary>
102         /// Dispose.
103         /// </summary>
104         /// <since_tizen> 6 </since_tizen>
105         protected virtual void Dispose(DisposeTypes type)
106         {
107             if (disposed)
108             {
109                 return;
110             }
111
112             if (type == DisposeTypes.Explicit)
113             {
114                 // Called by User
115                 // Release your own managed resources here.
116                 // You should release all of your own disposable objects here.
117
118             }
119
120             // Release your own unmanaged resources here.
121             // You should not access any managed member here except static instance.
122             // because the execution order of Finalizes is non-deterministic.
123             if (swigCPtr.Handle != global::System.IntPtr.Zero)
124             {
125                 if (swigCMemOwn)
126                 {
127                     swigCMemOwn = false;
128                     Interop.FlexLayout.delete_FlexLayout(swigCPtr);
129                 }
130                 swigCPtr = new global::System.Runtime.InteropServices.HandleRef(null, global::System.IntPtr.Zero);
131             }
132             disposed = true;
133         }
134
135         /// <summary>
136         /// [Draft] Creates a FlexLayout object.
137         /// </summary>
138         /// <since_tizen> 6 </since_tizen>
139         public FlexLayout() : this(Interop.FlexLayout.FlexLayout_New(), true)
140         {
141             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
142         }
143
144         internal static FlexLayout DownCast(BaseHandle handle)
145         {
146             FlexLayout ret = new FlexLayout(Interop.FlexLayout.FlexLayout_DownCast(BaseHandle.getCPtr(handle)), true);
147             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
148             return ret;
149         }
150
151         internal FlexLayout(FlexLayout other) : this(Interop.FlexLayout.new_FlexLayout__SWIG_1(FlexLayout.getCPtr(other)), true)
152         {
153             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
154         }
155
156         internal FlexLayout Assign(FlexLayout other)
157         {
158             FlexLayout ret = new FlexLayout(Interop.FlexLayout.FlexLayout_Assign(swigCPtr, FlexLayout.getCPtr(other)), false);
159             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
160             return ret;
161         }
162
163         internal void SetFlexDirection(FlexLayout.FlexDirection flexDirection)
164         {
165             Interop.FlexLayout.FlexLayout_SetFlexDirection(swigCPtr, (int)flexDirection);
166             RequestLayout();
167         }
168
169         internal FlexLayout.FlexDirection GetFlexDirection()
170         {
171             FlexLayout.FlexDirection ret = (FlexLayout.FlexDirection)Interop.FlexLayout.FlexLayout_GetFlexDirection(swigCPtr);
172             return ret;
173
174         }
175
176         internal void SetFlexJustification(FlexLayout.FlexJustification flexJustification)
177         {
178             Interop.FlexLayout.FlexLayout_SetFlexJustification(swigCPtr, (int)flexJustification);
179             RequestLayout();
180         }
181
182         internal FlexLayout.FlexJustification GetFlexJustification()
183         {
184             FlexLayout.FlexJustification ret = (FlexLayout.FlexJustification)Interop.FlexLayout.FlexLayout_GetFlexJustification(swigCPtr);
185             return ret;
186         }
187
188         internal void SetFlexWrap(FlexLayout.FlexWrapType flexWrap)
189         {
190             Interop.FlexLayout.FlexLayout_SetFlexWrap(swigCPtr, (int)flexWrap);
191             RequestLayout();
192         }
193
194         internal FlexLayout.FlexWrapType GetFlexWrap()
195         {
196             FlexLayout.FlexWrapType ret = (FlexLayout.FlexWrapType)Interop.FlexLayout.FlexLayout_GetFlexWrap(swigCPtr);
197             return ret;
198         }
199
200         internal void SetFlexAlignment(FlexLayout.AlignmentType flexAlignment)
201         {
202             Interop.FlexLayout.FlexLayout_SetFlexAlignment(swigCPtr, (int)flexAlignment);
203             RequestLayout();
204         }
205
206         internal FlexLayout.AlignmentType GetFlexAlignment()
207         {
208             FlexLayout.AlignmentType ret = (FlexLayout.AlignmentType)Interop.FlexLayout.FlexLayout_GetFlexAlignment(swigCPtr);
209             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
210             return ret;
211         }
212
213         internal void SetFlexItemsAlignment(FlexLayout.AlignmentType flexAlignment)
214         {
215             Interop.FlexLayout.FlexLayout_SetFlexItemsAlignment(swigCPtr, (int)flexAlignment);
216             RequestLayout();
217         }
218
219         internal FlexLayout.AlignmentType GetFlexItemsAlignment()
220         {
221             FlexLayout.AlignmentType ret = (FlexLayout.AlignmentType)Interop.FlexLayout.FlexLayout_GetFlexItemsAlignment(swigCPtr);
222             if (NDalicPINVOKE.SWIGPendingException.Pending) throw NDalicPINVOKE.SWIGPendingException.Retrieve();
223             return ret;
224         }
225
226         /// <summary>
227         /// [Draft] Get/Set the flex direction in the layout.
228         /// The direction of the main-axis which determines the direction that flex items are laid out.
229         /// </summary>
230         /// <since_tizen> 6 </since_tizen>
231         public FlexDirection Direction
232         {
233             get
234             {
235                 return GetFlexDirection();
236             }
237             set
238             {
239                 SetFlexDirection(value);
240             }
241         }
242
243         /// <summary>
244         /// [Draft] Get/Set the justification in the layout.
245         /// </summary>
246         /// <since_tizen> 6 </since_tizen>
247         public FlexJustification Justification
248         {
249             get
250             {
251                 return GetFlexJustification();
252             }
253             set
254             {
255                 SetFlexJustification(value);
256             }
257         }
258
259         /// <summary>
260         /// [Draft] Get/Set the wrap in the layout.
261         /// </summary>
262         /// <since_tizen> 6 </since_tizen>
263         public FlexWrapType WrapType
264         {
265             get
266             {
267                 return GetFlexWrap();
268             }
269             set
270             {
271                 SetFlexWrap(value);
272             }
273         }
274
275         /// <summary>
276         /// [Draft] Get/Set the alignment of the layout content.
277         /// </summary>
278         /// <since_tizen> 6 </since_tizen>
279         public AlignmentType Alignment
280         {
281             get
282             {
283                 return GetFlexAlignment();
284             }
285             set
286             {
287                 SetFlexAlignment(value);
288             }
289         }
290
291         /// <summary>
292         /// [Draft] Get/Set the alignment of the layout items.
293         /// </summary>
294         /// <since_tizen> 6 </since_tizen>
295         public AlignmentType ItemsAlignment
296         {
297             get
298             {
299                 return GetFlexItemsAlignment();
300             }
301             set
302             {
303                 SetFlexItemsAlignment(value);
304             }
305         }
306
307         /// <summary>
308         /// [Draft] Enumeration for the direction of the main axis in the flex container.
309         /// This determines the direction that flex items are laid out in the flex container.
310         /// </summary>
311         /// <since_tizen> 6 </since_tizen>
312         public enum FlexDirection
313         {
314             /// <summary>
315             /// The flexible items are displayed vertically as a column
316             /// </summary>
317             Column,
318             /// <summary>
319             /// The flexible items are displayed vertically as a column, but in reverse order
320             /// </summary>
321             ColumnReverse,
322             /// <summary>
323             /// The flexible items are displayed horizontally as a row
324             /// </summary>
325             Row,
326             /// <summary>
327             /// The flexible items are displayed horizontally as a row, but in reverse order
328             /// </summary>
329             RowReverse
330         }
331
332         /// <summary>
333         /// [Draft] Enumeration for the alignment of the flex items when the items do not use all available space on the main-axis.
334         /// </summary>
335         /// <since_tizen> 6 </since_tizen>
336         public enum FlexJustification
337         {
338             /// <summary>
339             /// Items are positioned at the beginning of the container
340             /// </summary>
341             FlexStart,
342             /// <summary>
343             /// Items are positioned at the center of the container
344             /// </summary>
345             Center,
346             /// <summary>
347             /// Items are positioned at the end of the container
348             /// </summary>
349             FlexEnd,
350             /// <summary>
351             /// Items are positioned with equal space between the lines
352             /// </summary>
353             SpaceBetween,
354             /// <summary>
355             /// Items are positioned with equal space before, between, and after the lines
356             /// </summary>
357             SpaceAround
358         }
359
360         /// <summary>
361         /// [Draft] Enumeration for the wrap type of the flex container when there is no enough room for all the items on one flex line.
362         /// </summary>
363         /// <since_tizen> 6 </since_tizen>
364         public enum FlexWrapType
365         {
366             /// <summary>
367             /// Flex items laid out in single line (shrunk to fit the flex container along the main axis)
368             /// </summary>
369             NoWrap,
370             /// <summary>
371             /// Flex items laid out in multiple lines if needed
372             /// </summary>
373             Wrap
374         }
375
376         /// <summary>
377         /// [Draft] Enumeration for the alignment of the flex items or lines when the items or lines do not use all the available space on the cross-axis.
378         /// </summary>
379         /// <since_tizen> 6 </since_tizen>
380         public enum AlignmentType
381         {
382             /// <summary>
383             /// Inherits the same alignment from the parent
384             /// </summary>
385             Auto,
386             /// <summary>
387             /// At the beginning of the container
388             /// </summary>
389             FlexStart,
390             /// <summary>
391             /// At the center of the container
392             /// </summary>
393             Center,
394             /// <summary>
395             /// At the end of the container
396             /// </summary>
397             FlexEnd,
398             /// <summary>
399             /// Stretch to fit the container
400             /// </summary>
401             Stretch
402         }
403
404         private MeasuredSize measureChild(global::System.IntPtr childPtr, float width, int measureModeWidth, float height, int measureModeHeight)
405         {
406             // We need to measure child layout
407             View child = Registry.GetManagedBaseHandleFromNativePtr(childPtr) as View;
408
409             LayoutItem childLayout = child.Layout;
410
411             MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification(parentMeasureSpecificationWidth,
412                                     new LayoutLength(childLayout.Padding.Start + childLayout.Padding.End),
413                                     new LayoutLength(child.WidthSpecification));
414
415             MeasureSpecification childHeightMeasureSpec = GetChildMeasureSpecification(parentMeasureSpecificationHeight,
416                                     new LayoutLength(childLayout.Padding.Top + childLayout.Padding.Bottom),
417                                     new LayoutLength(child.HeightSpecification));
418
419             childLayout.Measure( childWidthMeasureSpec, childHeightMeasureSpec);
420
421             return new MeasuredSize(childLayout.MeasuredWidth.Size.AsRoundedValue(),childLayout.MeasuredHeight.Size.AsRoundedValue());
422         }
423
424         void InsertChild( LayoutItem child )
425         {
426             // Store created node for child
427             Interop.FlexLayout.FlexLayout_AddChildWithMargin(swigCPtr, View.getCPtr(child.Owner), Extents.getCPtr(child.Owner.Margin), measureChildDelegate, LayoutChildren.Count-1);
428         }
429
430         /// <summary>
431         /// Callback when child is added to container.<br />
432         /// Derived classes can use this to set their own child properties on the child layout's owner.<br />
433         /// </summary>
434         /// <param name="child">The Layout child.</param>
435         /// <since_tizen> 6 </since_tizen>
436         protected override void OnChildAdd(LayoutItem child)
437         {
438             InsertChild(child);
439         }
440
441         /// <summary>
442         /// Callback when child is removed from container.<br />
443         /// </summary>
444         /// <param name="child">The Layout child.</param>
445         /// <since_tizen> 6 </since_tizen>
446         protected override void OnChildRemove(LayoutItem child)
447         {
448             // When child View is removed from it's parent View (that is a Layout) then remove it from the layout too.
449             // FlexLayout refers to the child as a View not LayoutItem.
450             Interop.FlexLayout.FlexLayout_RemoveChild(swigCPtr, child);
451         }
452
453         /// <summary>
454         /// Measure the layout and its content to determine the measured width and the measured height.<br />
455         /// </summary>
456         /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
457         /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
458         /// <since_tizen> 6 </since_tizen>
459         protected override void OnMeasure( MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec )
460         {
461             bool isLayoutRtl = Owner.LayoutDirection == ViewLayoutDirectionType.RTL;
462             Extents padding = Owner.Padding;
463             Extents margin = Owner.Margin;
464
465             Interop.FlexLayout.FlexLayout_SetMargin(swigCPtr, Extents.getCPtr(margin));
466             Interop.FlexLayout.FlexLayout_SetPadding(swigCPtr, Extents.getCPtr(padding));
467
468             float width = FlexUndefined; // Behaves as WrapContent (Flex Auto)
469             float height = FlexUndefined; // Behaves as WrapContent (Flex Auto)
470
471             if( widthMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly ||  widthMeasureSpec.Mode == MeasureSpecification.ModeType.AtMost )
472             {
473                 width = widthMeasureSpec.Size.AsDecimal();
474             }
475
476             if ( heightMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly || heightMeasureSpec.Mode == MeasureSpecification.ModeType.AtMost )
477             {
478                 height = heightMeasureSpec.Size.AsDecimal();
479             }
480
481             // Save measureSpec to measure children.
482             // In other Layout, we can pass it as parameter to measuring child but in FlexLayout we can't
483             // because measurChild function is called by native callback.
484             parentMeasureSpecificationWidth = widthMeasureSpec;
485             parentMeasureSpecificationHeight = heightMeasureSpec;
486
487             Interop.FlexLayout.FlexLayout_CalculateLayout( swigCPtr, width, height, isLayoutRtl );
488
489             LayoutLength flexLayoutWidth =  new LayoutLength( Interop.FlexLayout.FlexLayout_GetWidth(swigCPtr) );
490             LayoutLength flexLayoutHeight = new LayoutLength( Interop.FlexLayout.FlexLayout_GetHeight(swigCPtr) );
491
492             Debug.WriteLineIf( LayoutDebugFlex, "FlexLayout OnMeasure width:" + flexLayoutWidth.AsRoundedValue()
493                                                 + " height:" + flexLayoutHeight.AsRoundedValue() );
494
495             SetMeasuredDimensions( GetDefaultSize(flexLayoutWidth, widthMeasureSpec ),
496                                    GetDefaultSize(flexLayoutHeight, heightMeasureSpec ) );
497         }
498
499         /// <summary>
500         /// Assign a size and position to each of its children.<br />
501         /// </summary>
502         /// <param name="changed">This is a new size or position for this layout.</param>
503         /// <param name="left">Left position, relative to parent.</param>
504         /// <param name="top"> Top position, relative to parent.</param>
505         /// <param name="right">Right position, relative to parent.</param>
506         /// <param name="bottom">Bottom position, relative to parent.</param>
507         /// <since_tizen> 6 </since_tizen>
508         protected override void OnLayout( bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom )
509         {
510
511             bool isLayoutRtl = Owner.LayoutDirection == ViewLayoutDirectionType.RTL;
512             LayoutLength width = right - left;
513             LayoutLength height = bottom - top;
514
515             // Call to FlexLayout implementation to calculate layout values for later retrieval.
516             Interop.FlexLayout.FlexLayout_CalculateLayout( swigCPtr, width.AsDecimal(), height.AsDecimal(), isLayoutRtl );
517
518             int count = LayoutChildren.Count;
519             for( int childIndex = 0; childIndex < count; childIndex++)
520             {
521                 LayoutItem childLayout = LayoutChildren[childIndex];
522                 if( childLayout != null )
523                 {
524                     // Get the frame for the child, start, top, end, bottom.
525                     Vector4 frame = new Vector4(Interop.FlexLayout.FlexLayout_GetNodeFrame(swigCPtr, childIndex ), true);
526                     childLayout.Layout( new LayoutLength(frame.X), new LayoutLength(frame.Y), new LayoutLength(frame.Z), new LayoutLength(frame.W) );
527                 }
528             }
529         }
530
531     } // FLexlayout
532 } // namesspace Tizen.NUI