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