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