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