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