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