[NUI] Change Window.Instance to NUIApplication.GetDefaultWindow() (#1523)
[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] 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             Window.Instance.LayoutController.RequestLayout(this);
310         }
311
312         /// <summary>
313         /// Predicate to determine if this layout has been requested to re-layout.<br />
314         /// </summary>
315
316         internal bool LayoutRequested
317         {
318             get
319             {
320                 return ( Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout;
321             }
322         }
323
324         internal void SetReplaceFlag()
325         {
326             parentReplacement = true;
327         }
328
329         internal bool IsReplaceFlag()
330         {
331             return parentReplacement;
332         }
333
334         internal void ClearReplaceFlag()
335         {
336             parentReplacement = false;
337         }
338
339         /// <summary>
340         /// Get the measured width (without any measurement flags).<br />
341         /// This method should be used only during measurement and layout calculations.<br />
342         /// </summary>
343         /// <since_tizen> 6 </since_tizen>
344         public MeasuredSize MeasuredWidth{ get; set; } = new MeasuredSize( new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
345
346         /// <summary>
347         /// Get the measured height (without any measurement flags).<br />
348         /// This method should be used only during measurement and layout calculations.<br />
349         /// </summary>
350         /// <since_tizen> 6 </since_tizen>
351         public MeasuredSize MeasuredHeight{ get; set; } = new MeasuredSize( new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
352
353         /// <summary>
354         /// Returns the suggested minimum width that the layout should use.<br />
355         /// This returns the maximum of the layout's minimum width and the owner's natural width.<br />
356         /// </summary>
357         /// <since_tizen> 6 </since_tizen>
358         public LayoutLength SuggestedMinimumWidth
359         {
360             get
361             {
362                 int naturalWidth = Owner.NaturalSize2D.Width;
363                 return new LayoutLength(Math.Max( MinimumWidth.AsDecimal(), naturalWidth ));
364             }
365         }
366
367         /// <summary>
368         /// Returns the suggested minimum height that the layout should use.<br />
369         /// This returns the maximum of the layout's minimum height and the owner's natural height.<br />
370         /// </summary>
371         /// <since_tizen> 6 </since_tizen>
372         public LayoutLength SuggestedMinimumHeight
373         {
374             get
375             {
376                 int naturalHeight = Owner.NaturalSize2D.Height;
377                 return new LayoutLength(Math.Max( MinimumHeight.AsDecimal(), naturalHeight ));
378             }
379         }
380
381         /// <summary>
382         /// Sets the minimum width of the layout.<br />
383         /// It is not guaranteed the layout will be able to achieve this minimum width (for example, if its parent
384         /// layout constrains it with less available width).<br />
385         /// 1. if the owner's View.WidthSpecification has exact value, then that value overrides the minimum size.<br />
386         /// 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 />
387         /// 3. If the owner's View.WidthSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent width takes precedence over the minimum width.<br />
388         /// </summary>
389         internal LayoutLength MinimumWidth {get; set;}
390
391         /// <summary>
392         /// Sets the minimum height of the layout.<br />
393         /// It is not guaranteed the layout will be able to achieve this minimum height (for example, if its parent
394         /// layout constrains it with less available height).<br />
395         /// 1. if the owner's View.HeightSpecification has exact value, then that value overrides the minimum size.<br />
396         /// 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 />
397         /// 3. If the owner's View.HeightSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent height takes precedence over the minimum height.<br />
398         /// </summary>
399         internal LayoutLength MinimumHeight {get; set;}
400
401         ///<summary>
402         /// Utility to reconcile a desired size and state, with constraints imposed by a MeasureSpecification.
403         ///</summary>
404         /// <param name="size"> How big the layout wants to be.</param>
405         /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
406         /// <param name="childMeasuredState"> Size information bit mask for the layout's children.</param>
407         /// <returns> A measured size, which may indicate that it is too small. </returns>
408         /// <since_tizen> 6 </since_tizen>
409         protected MeasuredSize ResolveSizeAndState( LayoutLength size, MeasureSpecification measureSpecification, MeasuredSize.StateType childMeasuredState )
410         {
411             var specMode = measureSpecification.Mode;
412             LayoutLength specSize = measureSpecification.Size;
413             MeasuredSize result = new MeasuredSize( size, childMeasuredState );
414
415             switch( specMode )
416             {
417                 case MeasureSpecification.ModeType.AtMost:
418                 {
419                     if (specSize.AsRoundedValue() < size.AsRoundedValue())
420                     {
421                         result = new MeasuredSize( specSize, MeasuredSize.StateType.MeasuredSizeTooSmall);
422                     }
423                     break;
424                 }
425
426                 case MeasureSpecification.ModeType.Exactly:
427                 {
428                     result.Size = specSize;
429                     break;
430                 }
431
432                 case MeasureSpecification.ModeType.Unspecified:
433                 default:
434                 {
435                     break;
436                 }
437             }
438             return result;
439         }
440
441         /// <summary>
442         /// This method must be called by OnMeasure(MeasureSpec,MeasureSpec) to store the measured width and measured height.
443         /// </summary>
444         /// <param name="measuredWidth">The measured width of this layout.</param>
445         /// <param name="measuredHeight">The measured height of this layout.</param>
446         /// <since_tizen> 6 </since_tizen>
447         protected void SetMeasuredDimensions( MeasuredSize measuredWidth, MeasuredSize measuredHeight )
448         {
449             MeasuredWidth = measuredWidth;
450             MeasuredHeight = measuredHeight;
451             Flags = Flags | LayoutFlags.MeasuredDimensionSet;
452         }
453
454         /// <summary>
455         /// Measure the layout and its content to determine the measured width and the
456         /// measured height.<br />
457         /// The base class implementation of measure defaults to the background size,
458         /// unless a larger size is allowed by the MeasureSpec. Subclasses should
459         /// override to provide better measurements of their content.<br />
460         /// If this method is overridden, it is the subclass's responsibility to make sure the
461         /// measured height and width are at least the layout's minimum height and width.<br />
462         /// </summary>
463         /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
464         /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
465         /// <since_tizen> 6 </since_tizen>
466         protected virtual void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
467         {
468             // GetDefaultSize will limit the MeasureSpec to the suggested minimumWidth and minimumHeight
469             SetMeasuredDimensions( GetDefaultSize( SuggestedMinimumWidth, widthMeasureSpec ),
470                                    GetDefaultSize( SuggestedMinimumHeight, heightMeasureSpec ) );
471         }
472
473         /// <summary>
474         /// Called from Layout() when this layout should assign a size and position to each of its children. <br />
475         /// Derived classes with children should override this method and call Layout() on each of their children. <br />
476         /// </summary>
477         /// <param name="changed">This is a new size or position for this layout.</param>
478         /// <param name="left">Left position, relative to parent.</param>
479         /// <param name="top">Top position, relative to parent.</param>
480         /// <param name="right">Right position, relative to parent.</param>
481         /// <param name="bottom">Bottom position, relative to parent.</param>
482         /// <since_tizen> 6 </since_tizen>
483         protected virtual void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
484         {
485         }
486
487         /// <summary>
488         /// Virtual method to allow derived classes to remove any children before it is removed from
489         /// its parent.
490         /// </summary>
491         /// <since_tizen> 6 </since_tizen>
492         protected virtual void OnUnparent()
493         {
494         }
495
496         /// <summary>
497         /// Virtual method called when this Layout is attached to it's owner.
498         /// Allows derived layouts to take ownership of child Views and connect to any Owner signals required.
499         /// </summary>
500         /// <since_tizen> 6 </since_tizen>
501         protected virtual void OnAttachedToOwner()
502         {
503         }
504
505         private bool SetFrame(float left, float top, float right, float bottom)
506         {
507             bool changed = false;
508
509             if ( _layoutPositionData.Left != left ||
510                  _layoutPositionData.Right != right ||
511                  _layoutPositionData.Top != top ||
512                  _layoutPositionData.Bottom != bottom  )
513             {
514                 changed = true;
515
516                 float oldWidth = _layoutPositionData.Right - _layoutPositionData.Left;
517                 float oldHeight = _layoutPositionData.Bottom - _layoutPositionData.Top;
518                 float newWidth = right - left;
519                 float newHeight = bottom - top;
520                 bool sizeChanged = ( newWidth != oldWidth ) || ( newHeight != oldHeight );
521
522                 // Set condition to layout changed as currently unspecified. Add, Remove would have specified a condition.
523                 if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
524                 {
525                     ConditionForAnimation = TransitionCondition.LayoutChanged;
526                 }
527
528                 // Store new layout position data
529                 _layoutPositionData = new LayoutData(this, ConditionForAnimation, left, top, right, bottom);
530
531                 Debug.WriteLineIf( LayoutDebugFrameData, "LayoutItem FramePositionData View:" + _layoutPositionData.Item.Owner.Name +
532                                                          " left:" + _layoutPositionData.Left +
533                                                          " top:" + _layoutPositionData.Top +
534                                                          " right:" + _layoutPositionData.Right +
535                                                          " bottom:" + _layoutPositionData.Bottom );
536
537                 if (Owner.Parent != null && Owner.Parent.Layout != null && Owner.Parent.Layout.LayoutWithTransition)
538                 {
539                     NUIApplication.GetDefaultWindow().LayoutController.AddTransitionDataEntry(_layoutPositionData);
540                 }
541                 else
542                 {
543                     Owner.SetSize(right - left, bottom - top, Owner.Position.Z);
544                     if(SetPositionByLayout)
545                     {
546                         Owner.SetPosition(left, top, Owner.Position.Z);
547                     }
548                 }
549
550
551                 // Reset condition for animation ready for next transition when required.
552                 ConditionForAnimation = TransitionCondition.Unspecified;
553             }
554
555             return changed;
556         }
557     }
558 }