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