[NUI] Sync dalihub - Add C# layout (#816)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / internal / Layouting / LayoutItem.cs
1 /*
2  * Copyright (c) 2019 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 using System;
19 using System.ComponentModel;
20 using Tizen.NUI.BaseComponents;
21
22 namespace Tizen.NUI
23 {
24
25     [FlagsAttribute]
26     enum LayoutFlags : short
27     {
28       None = 0,
29       ForceLayout = 1,
30       LayoutRequired = 2,
31       MeasuredDimensionSet = 4
32     };
33
34     /// <summary>
35     /// [Draft] Base class for layouts. It is used to layout a View
36     /// It can be laid out by a LayoutGroup.
37     /// </summary>
38     internal class LayoutItem
39     {
40         private MeasureSpecification OldWidthMeasureSpec; // Store measure specification to compare against later
41         private MeasureSpecification OldHeightMeasureSpec;// Store measure specification to compare against later
42
43         private LayoutFlags Flags = LayoutFlags.None;
44
45         private ILayoutParent Parent;
46
47         private LayoutLength _left;
48         private LayoutLength _right;
49         private LayoutLength _top;
50         private LayoutLength _bottom;
51         private LayoutData _layoutData;
52
53         /// <summary>
54         /// The View that this Layout has been assigned to.
55         /// </summary>
56         public View Owner{get; set;}  // Should not keep a View alive.
57
58         /// <summary>
59         /// [Draft] Constructor
60         /// </summary>
61         public LayoutItem()
62         {
63         }
64
65         /// <summary>
66         /// [Draft] Constructor setting the owner of this LayoutItem.
67         /// </summary>
68         /// <param name="owner">Owning View of this layout, currently a View but may be extending for Windows/Layers.</param>
69         public LayoutItem(View owner)
70         {
71             Owner = owner;
72             _layoutData = new LayoutData();
73             _left = new LayoutLength(0);
74             _top = new LayoutLength(0);
75             _right = new LayoutLength(0);
76             _bottom = new LayoutLength(0);
77         }
78
79         /// <summary>
80         /// [Draft] Set parent to this layout.
81         /// </summary>
82         /// <param name="parent">Parent to set on this Layout.</param>
83         public void SetParent( ILayoutParent parent)
84         {
85             Parent = parent as LayoutGroup;
86             Log.Info("NUI", "Setting Parent Layout for:" +  Owner?.Name + " to (Parent):" + (parent == null ? "null":parent.ToString() ) + "\n");
87         }
88
89         /// <summary>
90         /// Unparent this layout from it's owner, and remove any layout children in derived types. <br />
91         /// </summary>
92         public void Unparent()
93         {
94             // Enable directly derived types to first remove children
95             OnUnparent();
96
97             // Remove myself from parent
98             Parent?.Remove( this );
99
100             // Remove parent reference
101             Parent = null;
102
103             // Lastly, clear layout from owning View.
104             Owner?.ResetLayout();
105         }
106
107         /// <summary>
108         /// Get the View owning this LayoutItem
109         /// </summary>
110         internal View GetOwner()
111         {
112             return Owner;
113         }
114
115         /// <summary>
116         /// Initialize the layout and allow derived classes to also perform any operations
117         /// </summary>
118         /// <param name="owner">Owner of this Layout.</param>
119         internal void AttachToOwner(View owner)
120         {
121             // Assign the layout owner.
122             Owner = owner;
123             OnAttachedToOwner();
124             // Add layout to parent layout if a layout container
125             View parent = Owner.GetParent() as View;
126             (parent?.Layout as LayoutGroup)?.Add( this );
127         }
128
129         /// <summary>
130         /// This is called to find out how big a layout should be. <br />
131         /// The parent supplies constraint information in the width and height parameters. <br />
132         /// The actual measurement work of a layout is performed in OnMeasure called by this
133         /// method. Therefore, only OnMeasure can and must be overridden by subclasses. <br />
134         /// </summary>
135         /// <param name="widthMeasureSpec"> Horizontal space requirements as imposed by the parent.</param>
136         /// <param name="heightMeasureSpec">Vertical space requirements as imposed by the parent.</param>
137         internal void Measure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
138         {
139             // Check if relayouting is required.
140             bool specChanged = (widthMeasureSpec.Size != OldWidthMeasureSpec.Size) ||
141                                (heightMeasureSpec.Size != OldHeightMeasureSpec.Size) ||
142                                (widthMeasureSpec.Mode != OldWidthMeasureSpec.Mode) ||
143                                (heightMeasureSpec.Mode != OldHeightMeasureSpec.Mode);
144
145             bool isSpecExactly = (widthMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly) &&
146                                  (heightMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly);
147
148             bool matchesSpecSize = (MeasuredWidth.Size == widthMeasureSpec.Size) &&
149                                    (MeasuredHeight.Size == heightMeasureSpec.Size);
150
151             bool needsLayout = specChanged && ( !isSpecExactly || !matchesSpecSize);
152             needsLayout = needsLayout || ((Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout);
153
154             Log.Info("NUI", "Measuring:" + Owner.Name + " needsLayout[" + needsLayout.ToString() + "]\n");
155
156             if (needsLayout)
157             {
158                 OnMeasure(widthMeasureSpec, heightMeasureSpec);
159                 Flags = Flags | LayoutFlags.LayoutRequired;
160                 Flags &= ~LayoutFlags.ForceLayout;
161             }
162             OldWidthMeasureSpec = widthMeasureSpec;
163             OldHeightMeasureSpec = heightMeasureSpec;
164
165             Log.Info("NUI", "LayoutItem Measure owner:" + Owner.Name + " width:" + widthMeasureSpec.Size.AsRoundedValue() + " height:" +  heightMeasureSpec.Size.AsRoundedValue() + "\n");
166         }
167
168         /// <summary>
169         /// Assign a size and position to a layout and all of its descendants. <br />
170         /// This is the second phase of the layout mechanism.  (The first is measuring). In this phase, each parent
171         /// calls layout on all of its children to position them.  This is typically done using the child<br />
172         /// measurements that were stored in the measure pass.<br />
173         /// </summary>
174         /// <param name="left">Left position, relative to parent.</param>
175         /// <param name="top">Top position, relative to parent.</param>
176         /// <param name="right">Right position, relative to parent.</param>
177         /// <param name="bottom">Bottom position, relative to parent.</param>
178         public void Layout(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
179         {
180             Log.Info("NUI", "LayoutItem Layout owner:" + Owner.Name + "\n");
181
182             bool changed = SetFrame(left, top, right, bottom);
183
184             // Check if Measure needed before Layouting
185             if (changed || ((Flags & LayoutFlags.LayoutRequired) == LayoutFlags.LayoutRequired))
186             {
187                 Log.Info("NUI", "LayoutItem Layout Frame changed or Layout forced\n");
188                 OnLayout(changed, left, top, right, bottom);
189                 // Clear flag
190                 Flags &= ~LayoutFlags.LayoutRequired;
191             }
192         }
193
194         /// <summary>
195         /// Utility to return a default size.<br />
196         /// Uses the supplied size if the MeasureSpecification imposed no constraints. Will get larger if allowed by the
197         /// MeasureSpecification.<br />
198         /// </summary>
199         /// <param name="size"> Default size for this layout.</param>
200         /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
201         /// <returns>The size this layout should be.</returns>
202         public static LayoutLength GetDefaultSize(LayoutLength size, MeasureSpecification measureSpecification)
203         {
204             LayoutLength result = size;
205             MeasureSpecification.ModeType specMode = measureSpecification.Mode;
206             LayoutLength specSize = measureSpecification.Size;
207
208             switch (specMode)
209             {
210                 case MeasureSpecification.ModeType.Unspecified:
211                 {
212                     result = size;
213                     break;
214                 }
215                 case MeasureSpecification.ModeType.AtMost:
216                 {
217                     // Ensure the default size does not exceed the spec size unless the default size is 0.
218                     // Another container could provide a default size of 0.
219
220                     // Do not set size to 0, use specSize in this case as could be a legacy container
221                     if( ( size.AsDecimal() < specSize.AsDecimal()) && ( size.AsDecimal() >  0) )
222                     {
223                         result = size;
224                     }
225                     else
226                     {
227                         result = specSize;
228                     }
229                     break;
230                 }
231                 case MeasureSpecification.ModeType.Exactly:
232                 {
233                     result = specSize;
234                     break;
235                 }
236             }
237
238             Log.Info("NUI", "DefaultSize :" + result.AsRoundedValue() +  "\n");
239
240             return result;
241         }
242
243         public ILayoutParent GetParent()
244         {
245             return Parent;
246         }
247
248         /// <summary>
249         /// Request that this layout is re-laid out.<br />
250         /// This will make this layout and all it's parent layouts dirty.<br />
251         /// </summary>
252         public void RequestLayout()
253         {
254             Log.Info("NUI", "RequestLayout on:" + Owner?.Name + "\n");
255             Flags = Flags | LayoutFlags.ForceLayout;
256             Window.Instance.LayoutController.RequestLayout(this);
257         }
258
259         /// <summary>
260         /// Predicate to determine if this layout has been requested to re-layout.<br />
261         /// </summary>
262         public bool LayoutRequested
263         {
264             get
265             {
266                 return ( Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout;
267             }
268         }
269
270         /// <summary>
271         /// Get the measured width (without any measurement flags).<br />
272         /// This method should be used only during measurement and layout calculations.<br />
273         /// </summary>
274         public MeasuredSize MeasuredWidth{ get; set; } = new MeasuredSize( new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
275
276         /// <summary>
277         /// Get the measured height (without any measurement flags).<br />
278         /// This method should be used only during measurement and layout calculations.<br />
279         /// </summary>
280         public MeasuredSize MeasuredHeight{ get; set; } = new MeasuredSize( new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
281
282         /// <summary>
283         /// Get the measured width and state.<br />
284         /// This method should be used only during measurement and layout calculations.<br />
285         /// </summary>
286         public MeasuredSize MeasuredWidthAndState
287         {
288             get
289             {
290                 return MeasuredWidth; // Not bitmasking State unless proven to be required.
291             }
292         }
293
294
295         /// <summary>
296         /// Get the measured height and state.<br />
297         /// This method should be used only during measurement and layout calculations.<br />
298         /// </summary>
299         public MeasuredSize MeasuredHeightAndState
300         {
301             get
302             {
303                 return MeasuredHeight;  // Not bitmasking State unless proven to be required.
304             }
305         }
306
307         /// <summary>
308         /// Returns the suggested minimum width that the layout should use.<br />
309         /// This returns the maximum of the layout's minimum width and the owner's natural width.<br />
310         /// </summary>
311         public LayoutLength SuggestedMinimumWidth
312         {
313             get
314             {
315                 int naturalWidth = Owner.NaturalSize2D.Width;
316                 Log.Info("NUI", "NaturalWidth for: " + Owner.Name + " :" + naturalWidth +"\n");
317                 return new LayoutLength(Math.Max( MinimumWidth.AsDecimal(), naturalWidth ));
318             }
319         }
320
321         /// <summary>
322         /// Returns the suggested minimum height that the layout should use.<br />
323         /// This returns the maximum of the layout's minimum height and the owner's natural height.<br />
324         /// </summary>
325         public LayoutLength SuggestedMinimumHeight
326         {
327             get
328             {
329                 int naturalHeight = Owner.NaturalSize2D.Height;
330                 Log.Info("NUI", "NaturalHeight for: " + Owner.Name + " :" + naturalHeight +"\n");
331                 return new LayoutLength(Math.Max( MinimumHeight.AsDecimal(), naturalHeight ));
332             }
333         }
334
335         /// <summary>
336         /// Sets the minimum width of the layout.<br />
337         /// It is not guaranteed the layout will be able to achieve this minimum width (for example, if its parent
338         /// layout constrains it with less available width).<br />
339         /// 1. if the owner's View.LayoutWidthSpecification has exact value, then that value overrides the minimum size.<br />
340         /// 2. If the owner's View.LayoutWidthSpecification is set to View.LayoutParamPolicies.WrapContent, then the view's width is set based on the suggested minimum width. (@see GetSuggestedMinimumWidth()).<br />
341         /// 3. If the owner's View.LayoutWidthSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent width takes precedence over the minimum width.<br />
342         /// </summary>
343         public LayoutLength MinimumWidth {get; set;}
344
345         /// <summary>
346         /// Sets the minimum height of the layout.<br />
347         /// It is not guaranteed the layout will be able to achieve this minimum height (for example, if its parent
348         /// layout constrains it with less available height).<br />
349         /// 1. if the owner's View.LayoutHeightSpecification has exact value, then that value overrides the minimum size.<br />
350         /// 2. If the owner's View.LayoutHeightSpecification is set to View.LayoutParamPolicies.WrapContent, then the view's height is set based on the suggested minimum height. (@see GetSuggestedMinimumHeight()).<br />
351         /// 3. If the owner's View.LayoutHeightSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent height takes precedence over the minimum height.<br />
352         /// </summary>
353         public LayoutLength MinimumHeight {get; set;}
354
355         ///<summary>
356         /// Utility to reconcile a desired size and state, with constraints imposed by a MeasureSpecification.
357         ///</summary>
358         /// <param name="size"> How big the layout wants to be.</param>
359         /// <param name="measureSpecification"> Constraints imposed by the parent.</param>
360         /// <param name="childMeasuredState"> Size information bit mask for the layout's children.</param>
361         /// <returns> A measured size, which may indicate that it is too small. </returns>
362         protected MeasuredSize ResolveSizeAndState( LayoutLength size, MeasureSpecification measureSpecification, MeasuredSize.StateType childMeasuredState )
363         {
364             var specMode = measureSpecification.Mode;
365             LayoutLength specSize = measureSpecification.Size;
366             MeasuredSize result = new MeasuredSize( size, childMeasuredState);
367
368             switch( specMode )
369             {
370                 case MeasureSpecification.ModeType.AtMost:
371                 {
372                     if (specSize.AsRoundedValue() < size.AsRoundedValue())
373                     {
374                         result = new MeasuredSize( specSize, MeasuredSize.StateType.MeasuredSizeTooSmall);
375                     }
376                     break;
377                 }
378
379                 case MeasureSpecification.ModeType.Exactly:
380                 {
381                     result.Size = specSize;
382                     break;
383                 }
384
385                 case MeasureSpecification.ModeType.Unspecified:
386                 default:
387                 {
388                     break;
389                 }
390             }
391             return result;
392         }
393
394         /// <summary>
395         /// This method must be called by OnMeasure(MeasureSpec,MeasureSpec) to store the measured width and measured height.
396         /// </summary>
397         /// <param name="measuredWidth">The measured width of this layout.</param>
398         /// <param name="measuredHeight">The measured height of this layout.</param>
399         protected void SetMeasuredDimensions( MeasuredSize measuredWidth, MeasuredSize measuredHeight )
400         {
401             Log.Info("NUI", "For " + Owner.Name + " MeasuredWidth:" + measuredWidth.Size.AsRoundedValue()
402                                    + " MeasureHeight:" + measuredHeight.Size.AsRoundedValue() + "\n");
403
404             MeasuredWidth = measuredWidth;
405             MeasuredHeight = measuredHeight;
406             Flags = Flags | LayoutFlags.MeasuredDimensionSet;
407         }
408
409         /// <summary>
410         /// Measure the layout and its content to determine the measured width and the
411         /// measured height.<br />
412         /// The base class implementation of measure defaults to the background size,
413         /// unless a larger size is allowed by the MeasureSpec. Subclasses should
414         /// override to provide better measurements of their content.<br />
415         /// If this method is overridden, it is the subclass's responsibility to make sure the
416         /// measured height and width are at least the layout's minimum height and width.<br />
417         /// </summary>
418         /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
419         /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
420         protected virtual void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
421         {
422             // GetDefaultSize will limit the MeasureSpec to the suggested minimumWidth and minimumHeight
423             SetMeasuredDimensions( GetDefaultSize( SuggestedMinimumWidth, widthMeasureSpec ),
424                                    GetDefaultSize( SuggestedMinimumHeight, heightMeasureSpec ) );
425         }
426
427         /// <summary>
428         /// Called from Layout() when this layout should assign a size and position to each of its children. <br />
429         /// Derived classes with children should override this method and call Layout() on each of their children. <br />
430         /// </summary>
431         /// <param name="changed">This is a new size or position for this layout.</param>
432         /// <param name="left">Left position, relative to parent.</param>
433         /// <param name="top">Top position, relative to parent.</param>
434         /// <param name="right">Right position, relative to parent.</param>
435         /// <param name="bottom">Bottom position, relative to parent.</param>
436         protected virtual void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
437         {
438         }
439
440         /// <summary>
441         /// Virtual method to inform derived classes when the layout size changed. <br />
442         /// </summary>
443         /// <param name="newSize">The new size of the layout.</param>
444         /// <param name="oldSize">The old size of the layout.</param>
445         protected virtual void OnSizeChanged(LayoutSize newSize, LayoutSize oldSize)
446         {
447         }
448
449         /// <summary>
450         /// Virtual method to allow derived classes to remove any children before it is removed from
451         /// its parent.
452         /// </summary>
453         public virtual void OnUnparent()
454         {
455         }
456
457         /// <summary>
458         /// Virtual method called when this Layout is attached to it's owner.
459         /// Allows derived layouts to take ownership of child Views and connect to any Owner signals required.
460         /// </summary>
461         protected virtual void OnAttachedToOwner()
462         {
463         }
464
465         private bool SetFrame(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
466         {
467             bool changed = false;
468
469             if( _left != left || _right != right || _top != top || _bottom != bottom  )
470             {
471                 changed = true;
472             }
473
474             LayoutLength oldWidth = _right - _left;
475             LayoutLength oldHeight = _bottom - _top;
476             LayoutLength newWidth = right - left;
477             LayoutLength newHeight = bottom - top;
478             bool sizeChanged = ( newWidth != oldWidth ) || ( newHeight != oldHeight );
479
480             _left = left;
481             _top = top;
482             _right = right;
483             _bottom = bottom;
484
485             // Set actual positions of View.
486             Owner.SetX(_left.AsRoundedValue());
487             Owner.SetY(_top.AsRoundedValue());
488             Owner.SetSize((int)newWidth.AsRoundedValue(), (int)newHeight.AsRoundedValue());
489
490             Log.Info("NUI", "Frame set for " + Owner.Name + " to left:" + _left.AsRoundedValue() + " top:"
491                             + _top.AsRoundedValue() + " width: " + newWidth.AsRoundedValue() + " height: "
492                             + newHeight.AsRoundedValue() + "\n");
493
494             return changed;
495         }
496
497     }
498 }