[NUI] Layouting API made public (#1026)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / Layouting / LayoutItem.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
18
19 using System;
20 using System.Diagnostics;
21 using Tizen.NUI.BaseComponents;
22
23 namespace Tizen.NUI
24 {
25
26     [FlagsAttribute]
27     enum LayoutFlags : short
28     {
29       None = 0,
30       ForceLayout = 1,
31       LayoutRequired = 2,
32       MeasuredDimensionSet = 4
33     };
34
35     /// <summary>
36     /// [Draft] Base class for layouts. It is used to layout a View
37     /// It can be laid out by a LayoutGroup.
38     /// </summary>
39     public class LayoutItem
40     {
41         static bool LayoutDebugFrameData = false; // Debug flag
42         private MeasureSpecification OldWidthMeasureSpec; // Store measure specification to compare against later
43         private MeasureSpecification OldHeightMeasureSpec;// Store measure specification to compare against later
44
45         private LayoutFlags Flags = LayoutFlags.None;
46
47         private ILayoutParent Parent;
48
49         LayoutData _layoutPositionData;
50
51         private Extents _padding;
52         private Extents _margin;
53
54         /// <summary>
55         /// [Draft] Condition event that is causing this Layout to transition.
56         /// </summary>
57         internal TransitionCondition ConditionForAnimation{get; set;}
58
59         /// <summary>
60         /// [Draft] The View that this Layout has been assigned to.
61         /// </summary>
62         /// <since_tizen> 6 </since_tizen>
63         public View Owner{get; set;}  // Should not keep a View alive.
64
65         /// <summary>
66         /// [Draft] Margin for this LayoutItem
67         /// </summary>
68         /// <since_tizen> 6 </since_tizen>
69         public Extents Margin
70         {
71             get
72             {
73                 return _margin;
74             }
75             set
76             {
77                 _margin = value;
78                 RequestLayout();
79             }
80         }
81
82         /// <summary>
83         /// [Draft] Padding for this LayoutItem
84         /// </summary>
85         /// <since_tizen> 6 </since_tizen>
86         public Extents Padding
87         {
88             get
89             {
90                 return _padding;
91             }
92             set
93             {
94                 _padding = value;
95                 RequestLayout();
96             }
97         }
98
99         /// <summary>
100         /// [Draft] Constructor
101         /// </summary>
102         /// <since_tizen> 6 </since_tizen>
103         public LayoutItem()
104         {
105             Initialize();
106         }
107
108         /// <summary>
109         /// [Draft] Set parent to this layout.
110         /// </summary>
111         /// <param name="parent">Parent to set on this Layout.</param>
112         internal void SetParent( ILayoutParent parent)
113         {
114             Parent = parent as LayoutGroup;
115         }
116
117         /// <summary>
118         /// Unparent this layout from it's owner, and remove any layout children in derived types. <br />
119         /// </summary>
120         internal void Unparent()
121         {
122             // Enable directly derived types to first remove children
123             OnUnparent();
124
125             // Remove myself from parent
126             Parent?.Remove( this );
127
128             // Remove parent reference
129             Parent = null;
130
131             // Lastly, clear layout from owning View.
132             Owner?.ResetLayout();
133         }
134
135         private void Initialize()
136         {
137             _layoutPositionData = new LayoutData(this,TransitionCondition.Unspecified,0,0,0,0);
138             _padding = new Extents(0,0,0,0);
139             _margin = new Extents(0,0,0,0);
140         }
141
142         /// <summary>
143         /// Initialize the layout and allow derived classes to also perform any operations
144         /// </summary>
145         /// <param name="owner">Owner of this Layout.</param>
146         internal void AttachToOwner(View owner)
147         {
148             // Assign the layout owner.
149             Owner = owner;
150             OnAttachedToOwner();
151             // Add layout to parent layout if a layout container
152             View parent = Owner.GetParent() as View;
153             (parent?.Layout as LayoutGroup)?.Add( this );
154
155             // If Add or ChangeOnAdd then do not update condition
156             if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
157             {
158                 ConditionForAnimation = TransitionCondition.LayoutChanged;
159             }
160         }
161
162         /// <summary>
163         /// This is called to find out how big a layout should be. <br />
164         /// The parent supplies constraint information in the width and height parameters. <br />
165         /// The actual measurement work of a layout is performed in OnMeasure called by this
166         /// method. Therefore, only OnMeasure can and must be overridden by subclasses. <br />
167         /// </summary>
168         /// <param name="widthMeasureSpec"> Horizontal space requirements as imposed by the parent.</param>
169         /// <param name="heightMeasureSpec">Vertical space requirements as imposed by the parent.</param>
170         /// <since_tizen> 6 </since_tizen>
171         public void Measure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
172         {
173             // Check if relayouting is required.
174             bool specChanged = (widthMeasureSpec.Size != OldWidthMeasureSpec.Size) ||
175                                (heightMeasureSpec.Size != OldHeightMeasureSpec.Size) ||
176                                (widthMeasureSpec.Mode != OldWidthMeasureSpec.Mode) ||
177                                (heightMeasureSpec.Mode != OldHeightMeasureSpec.Mode);
178
179             bool isSpecExactly = (widthMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly) &&
180                                  (heightMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly);
181
182             bool matchesSpecSize = (MeasuredWidth.Size == widthMeasureSpec.Size) &&
183                                    (MeasuredHeight.Size == heightMeasureSpec.Size);
184
185             bool needsLayout = specChanged && ( !isSpecExactly || !matchesSpecSize);
186             needsLayout = needsLayout || ((Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout);
187
188             if (needsLayout)
189             {
190                 OnMeasure(widthMeasureSpec, heightMeasureSpec);
191                 Flags = Flags | LayoutFlags.LayoutRequired;
192                 Flags &= ~LayoutFlags.ForceLayout;
193             }
194             OldWidthMeasureSpec = widthMeasureSpec;
195             OldHeightMeasureSpec = heightMeasureSpec;
196         }
197
198         /// <summary>
199         /// Assign a size and position to a layout and all of its descendants. <br />
200         /// This is the second phase of the layout mechanism.  (The first is measuring). In this phase, each parent
201         /// calls layout on all of its children to position them.  This is typically done using the child<br />
202         /// measurements that were stored in the measure pass.<br />
203         /// </summary>
204         /// <param name="left">Left position, relative to parent.</param>
205         /// <param name="top">Top position, relative to parent.</param>
206         /// <param name="right">Right position, relative to parent.</param>
207         /// <param name="bottom">Bottom position, relative to parent.</param>
208         /// <since_tizen> 6 </since_tizen>
209         public void Layout(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
210         {
211             bool changed = SetFrame(left.AsRoundedValue(),
212                                     top.AsRoundedValue(),
213                                     right.AsRoundedValue(),
214                                     bottom.AsRoundedValue());
215
216             // Check if Measure needed before Layouting
217             if (changed || ((Flags & LayoutFlags.LayoutRequired) == LayoutFlags.LayoutRequired))
218             {
219                 OnLayout(changed, left, top, right, bottom);
220                 // Clear flag
221                 Flags &= ~LayoutFlags.LayoutRequired;
222             }
223         }
224
225         /// <summary>
226         /// Utility to return a default size.<br />
227         /// Uses the supplied size if the MeasureSpecification imposed no constraints. Will get larger if allowed by the
228         /// MeasureSpecification.<br />
229         /// </summary>
230         /// <param name="size"> Default size for this layout.</param>
231         /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
232         /// <returns>The size this layout should be.</returns>
233         /// <since_tizen> 6 </since_tizen>
234         public static LayoutLength GetDefaultSize(LayoutLength size, MeasureSpecification measureSpecification)
235         {
236             LayoutLength result = size;
237             MeasureSpecification.ModeType specMode = measureSpecification.Mode;
238             LayoutLength specSize = measureSpecification.Size;
239
240             switch (specMode)
241             {
242                 case MeasureSpecification.ModeType.Unspecified:
243                 {
244                     result = size;
245                     break;
246                 }
247                 case MeasureSpecification.ModeType.AtMost:
248                 {
249                     // Ensure the default size does not exceed the spec size unless the default size is 0.
250                     // Another container could provide a default size of 0.
251
252                     // Do not set size to 0, use specSize in this case as could be a legacy container
253                     if( ( size.AsDecimal() < specSize.AsDecimal()) && ( size.AsDecimal() >  0) )
254                     {
255                         result = size;
256                     }
257                     else
258                     {
259                         result = specSize;
260                     }
261                     break;
262                 }
263                 case MeasureSpecification.ModeType.Exactly:
264                 {
265                     result = specSize;
266                     break;
267                 }
268             }
269
270             return result;
271         }
272
273         /// <summary>
274         /// Get the Layouts parent
275         /// </summary>
276         /// <returns>Layout parent with an LayoutParent interface</returns>
277         /// <since_tizen> 6 </since_tizen>
278         public ILayoutParent GetParent()
279         {
280             return Parent;
281         }
282
283         /// <summary>
284         /// Request that this layout is re-laid out.<br />
285         /// This will make this layout and all it's parent layouts dirty.<br />
286         /// </summary>
287         /// <since_tizen> 6 </since_tizen>
288         public void RequestLayout()
289         {
290             Flags = Flags | LayoutFlags.ForceLayout;
291             Window.Instance.LayoutController.RequestLayout(this);
292         }
293
294         /// <summary>
295         /// Predicate to determine if this layout has been requested to re-layout.<br />
296         /// </summary>
297
298         internal bool LayoutRequested
299         {
300             get
301             {
302                 return ( Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout;
303             }
304         }
305
306         /// <summary>
307         /// Get the measured width (without any measurement flags).<br />
308         /// This method should be used only during measurement and layout calculations.<br />
309         /// </summary>
310         /// <since_tizen> 6 </since_tizen>
311         public MeasuredSize MeasuredWidth{ get; set; } = new MeasuredSize( new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
312
313         /// <summary>
314         /// Get the measured height (without any measurement flags).<br />
315         /// This method should be used only during measurement and layout calculations.<br />
316         /// </summary>
317         /// <since_tizen> 6 </since_tizen>
318         public MeasuredSize MeasuredHeight{ get; set; } = new MeasuredSize( new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
319
320         /// <summary>
321         /// Returns the suggested minimum width that the layout should use.<br />
322         /// This returns the maximum of the layout's minimum width and the owner's natural width.<br />
323         /// </summary>
324         /// <since_tizen> 6 </since_tizen>
325         public LayoutLength SuggestedMinimumWidth
326         {
327             get
328             {
329                 int naturalWidth = Owner.NaturalSize2D.Width;
330                 return new LayoutLength(Math.Max( MinimumWidth.AsDecimal(), naturalWidth ));
331             }
332         }
333
334         /// <summary>
335         /// Returns the suggested minimum height that the layout should use.<br />
336         /// This returns the maximum of the layout's minimum height and the owner's natural height.<br />
337         /// </summary>
338         /// <since_tizen> 6 </since_tizen>
339         public LayoutLength SuggestedMinimumHeight
340         {
341             get
342             {
343                 int naturalHeight = Owner.NaturalSize2D.Height;
344                 return new LayoutLength(Math.Max( MinimumHeight.AsDecimal(), naturalHeight ));
345             }
346         }
347
348         /// <summary>
349         /// Sets the minimum width of the layout.<br />
350         /// It is not guaranteed the layout will be able to achieve this minimum width (for example, if its parent
351         /// layout constrains it with less available width).<br />
352         /// 1. if the owner's View.WidthSpecification has exact value, then that value overrides the minimum size.<br />
353         /// 2. If the owner's View.WidthSpecification is set to View.LayoutParamPolicies.WrapContent, then the view's width is set based on the suggested minimum width. (@see GetSuggestedMinimumWidth()).<br />
354         /// 3. If the owner's View.WidthSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent width takes precedence over the minimum width.<br />
355         /// </summary>
356         internal LayoutLength MinimumWidth {get; set;}
357
358         /// <summary>
359         /// Sets the minimum height of the layout.<br />
360         /// It is not guaranteed the layout will be able to achieve this minimum height (for example, if its parent
361         /// layout constrains it with less available height).<br />
362         /// 1. if the owner's View.HeightSpecification has exact value, then that value overrides the minimum size.<br />
363         /// 2. If the owner's View.HeightSpecification is set to View.LayoutParamPolicies.WrapContent, then the view's height is set based on the suggested minimum height. (@see GetSuggestedMinimumHeight()).<br />
364         /// 3. If the owner's View.HeightSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent height takes precedence over the minimum height.<br />
365         /// </summary>
366         internal LayoutLength MinimumHeight {get; set;}
367
368         ///<summary>
369         /// Utility to reconcile a desired size and state, with constraints imposed by a MeasureSpecification.
370         ///</summary>
371         /// <param name="size"> How big the layout wants to be.</param>
372         /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
373         /// <param name="childMeasuredState"> Size information bit mask for the layout's children.</param>
374         /// <returns> A measured size, which may indicate that it is too small. </returns>
375         /// <since_tizen> 6 </since_tizen>
376         protected MeasuredSize ResolveSizeAndState( LayoutLength size, MeasureSpecification measureSpecification, MeasuredSize.StateType childMeasuredState )
377         {
378             var specMode = measureSpecification.Mode;
379             LayoutLength specSize = measureSpecification.Size;
380             MeasuredSize result = new MeasuredSize( size, childMeasuredState);
381
382             switch( specMode )
383             {
384                 case MeasureSpecification.ModeType.AtMost:
385                 {
386                     if (specSize.AsRoundedValue() < size.AsRoundedValue())
387                     {
388                         result = new MeasuredSize( specSize, MeasuredSize.StateType.MeasuredSizeTooSmall);
389                     }
390                     break;
391                 }
392
393                 case MeasureSpecification.ModeType.Exactly:
394                 {
395                     result.Size = specSize;
396                     break;
397                 }
398
399                 case MeasureSpecification.ModeType.Unspecified:
400                 default:
401                 {
402                     break;
403                 }
404             }
405             return result;
406         }
407
408         /// <summary>
409         /// This method must be called by OnMeasure(MeasureSpec,MeasureSpec) to store the measured width and measured height.
410         /// </summary>
411         /// <param name="measuredWidth">The measured width of this layout.</param>
412         /// <param name="measuredHeight">The measured height of this layout.</param>
413         /// <since_tizen> 6 </since_tizen>
414         protected void SetMeasuredDimensions( MeasuredSize measuredWidth, MeasuredSize measuredHeight )
415         {
416             MeasuredWidth = measuredWidth;
417             MeasuredHeight = measuredHeight;
418             Flags = Flags | LayoutFlags.MeasuredDimensionSet;
419         }
420
421         /// <summary>
422         /// Measure the layout and its content to determine the measured width and the
423         /// measured height.<br />
424         /// The base class implementation of measure defaults to the background size,
425         /// unless a larger size is allowed by the MeasureSpec. Subclasses should
426         /// override to provide better measurements of their content.<br />
427         /// If this method is overridden, it is the subclass's responsibility to make sure the
428         /// measured height and width are at least the layout's minimum height and width.<br />
429         /// </summary>
430         /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
431         /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
432         /// <since_tizen> 6 </since_tizen>
433         protected virtual void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
434         {
435             // GetDefaultSize will limit the MeasureSpec to the suggested minimumWidth and minimumHeight
436             SetMeasuredDimensions( GetDefaultSize( SuggestedMinimumWidth, widthMeasureSpec ),
437                                    GetDefaultSize( SuggestedMinimumHeight, heightMeasureSpec ) );
438         }
439
440         /// <summary>
441         /// Called from Layout() when this layout should assign a size and position to each of its children. <br />
442         /// Derived classes with children should override this method and call Layout() on each of their children. <br />
443         /// </summary>
444         /// <param name="changed">This is a new size or position for this layout.</param>
445         /// <param name="left">Left position, relative to parent.</param>
446         /// <param name="top">Top position, relative to parent.</param>
447         /// <param name="right">Right position, relative to parent.</param>
448         /// <param name="bottom">Bottom position, relative to parent.</param>
449         /// <since_tizen> 6 </since_tizen>
450         protected virtual void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
451         {
452         }
453
454         /// <summary>
455         /// Virtual method to allow derived classes to remove any children before it is removed from
456         /// its parent.
457         /// </summary>
458         /// <since_tizen> 6 </since_tizen>
459         protected virtual void OnUnparent()
460         {
461         }
462
463         /// <summary>
464         /// Virtual method called when this Layout is attached to it's owner.
465         /// Allows derived layouts to take ownership of child Views and connect to any Owner signals required.
466         /// </summary>
467         /// <since_tizen> 6 </since_tizen>
468         protected virtual void OnAttachedToOwner()
469         {
470         }
471
472         private bool SetFrame(float left, float top, float right, float bottom)
473         {
474             bool changed = false;
475
476             if ( _layoutPositionData.Left != left ||
477                  _layoutPositionData.Right != right ||
478                  _layoutPositionData.Top != top ||
479                  _layoutPositionData.Bottom != bottom  )
480             {
481                 changed = true;
482
483                 float oldWidth = _layoutPositionData.Right - _layoutPositionData.Left;
484                 float oldHeight = _layoutPositionData.Bottom - _layoutPositionData.Top;
485                 float newWidth = right - left;
486                 float newHeight = bottom - top;
487                 bool sizeChanged = ( newWidth != oldWidth ) || ( newHeight != oldHeight );
488
489                 // Set condition to layout changed as currently unspecified. Add, Remove would have specified a condition.
490                 if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
491                 {
492                     ConditionForAnimation = TransitionCondition.LayoutChanged;
493                 }
494
495                 // Store new layout position data
496                 _layoutPositionData = new LayoutData(this, ConditionForAnimation, left, top, right, bottom);
497
498                 Debug.WriteLineIf( LayoutDebugFrameData, "LayoutItem FramePositionData View:" + _layoutPositionData.Item.Owner.Name +
499                                                          " left:" + _layoutPositionData.Left +
500                                                          " top:" + _layoutPositionData.Top +
501                                                          " right:" + _layoutPositionData.Right +
502                                                          " bottom:" + _layoutPositionData.Bottom );
503
504                 Window.Instance.LayoutController.AddTransitionDataEntry(_layoutPositionData);
505
506                 // Reset condition for animation ready for next transition when required.
507                 ConditionForAnimation = TransitionCondition.Unspecified;
508             }
509
510             return changed;
511         }
512     }
513 }