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