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