14a91d13590af9891b978c8aae2c48cbe653f950
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / Layouting / LayoutGroup.cs
1 /*
2  * Copyright (c) 2021 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 using System;
18 using System.Collections.Generic;
19 using System.ComponentModel;
20 using System.Linq;
21
22 using Tizen.NUI.BaseComponents;
23 using Tizen.NUI.Binding.Internals;
24 using static Tizen.NUI.Binding.BindableObject;
25
26 namespace Tizen.NUI
27 {
28     /// <summary>
29     /// [Draft] LayoutGroup class providing container functionality.
30     /// </summary>
31     public class LayoutGroup : LayoutItem, ILayoutParent
32     {
33         /// <summary>
34         /// [Draft] List of child layouts in this container.
35         /// </summary>
36         /// <since_tizen> 6 </since_tizen>
37         protected List<LayoutItem> LayoutChildren { get; } // Children of this LayoutGroup
38
39         /// <summary>
40         /// [Draft] Constructor
41         /// </summary>
42         /// <since_tizen> 6 </since_tizen>
43         public LayoutGroup()
44         {
45             LayoutChildren = new List<LayoutItem>();
46         }
47
48         /// <summary>
49         /// returns an enumerable collection of the child layouts that owner's <see cref="View.ExcludeLayouting"/> is false.
50         /// </summary>
51         /// <returns>An enumerable collection of the child layouts that affected by this layout.</returns>
52         [EditorBrowsable(EditorBrowsableState.Never)]
53         protected IEnumerable<LayoutItem> IterateLayoutChildren()
54         {
55             return LayoutChildren.Where<LayoutItem>(childLayout => childLayout.SetPositionByLayout);
56         }
57
58         /// <summary>
59         /// From ILayoutParent.<br />
60         /// </summary>
61         /// <exception cref="ArgumentNullException"> Thrown when childLayout is null. </exception>
62         /// <since_tizen> 6 </since_tizen>
63         /// <param name="childLayout">LayoutItem to add to the layout group.</param>
64         public virtual void Add(LayoutItem childLayout)
65         {
66             if (null == childLayout)
67             {
68                 throw new ArgumentNullException(nameof(childLayout));
69             }
70             LayoutChildren.Add(childLayout);
71             childLayout.SetParent(this);
72             // Child added to use a Add transition.
73             childLayout.ConditionForAnimation = ConditionForAnimation | TransitionCondition.Add;
74             // Child's parent sets all other children not being added to a ChangeOnAdd transition.
75             SetConditionsForAnimationOnLayoutGroup(TransitionCondition.ChangeOnAdd);
76             OnChildAdd(childLayout);
77             RequestLayout();
78         }
79
80         /// <summary>
81         /// Remove all layout children.<br />
82         /// </summary>
83         /// <since_tizen> 6 </since_tizen>
84         public void RemoveAll()
85         {
86             foreach (LayoutItem childLayout in LayoutChildren)
87             {
88                 childLayout.ConditionForAnimation = ConditionForAnimation | TransitionCondition.Remove;
89                 childLayout.Owner = null;
90             }
91             LayoutChildren.Clear();
92             // todo ensure child LayoutItems are still not parented to this group.
93             RequestLayout();
94         }
95
96         /// <summary>
97         /// From ILayoutParent
98         /// </summary>
99         /// <param name="layoutItem">LayoutItem to remove from the layout group.</param>
100         /// <since_tizen> 6 </since_tizen>
101         public virtual void Remove(LayoutItem layoutItem)
102         {
103             bool childRemoved = false;
104             foreach (LayoutItem childLayout in LayoutChildren.ToList())
105             {
106                 if (childLayout == layoutItem)
107                 {
108                     childLayout.ClearReplaceFlag();
109                     LayoutChildren.Remove(childLayout);
110
111                     if (LayoutWithTransition)
112                     {
113                         if (!childLayout.IsReplaceFlag())
114                         {
115                             NUIApplication.GetDefaultWindow().LayoutController.AddToRemovalStack(childLayout);
116                         }
117
118                         childLayout.ConditionForAnimation = childLayout.ConditionForAnimation | TransitionCondition.Remove;
119                         // Add LayoutItem to the transition stack so can animate it out.
120                         NUIApplication.GetDefaultWindow().LayoutController.AddTransitionDataEntry(new LayoutData(layoutItem, ConditionForAnimation, 0, 0, 0, 0));
121                     }
122
123                     // Reset condition for animation ready for next transition when required.
124                     // SetFrame usually would do this but this LayoutItem is being removed.
125                     childLayout.ConditionForAnimation = TransitionCondition.Unspecified;
126                     childRemoved = true;
127
128                     break;
129                 }
130             }
131
132             if (childRemoved)
133             {
134                 // If child removed then set all siblings not being added to a ChangeOnRemove transition.
135                 SetConditionsForAnimationOnLayoutGroup(TransitionCondition.ChangeOnRemove);
136             }
137             OnChildRemove(layoutItem);
138             RequestLayout();
139         }
140
141
142         /// <summary>
143         /// Sets the sibling order of the layout item so the layout can be defined within the same parent.
144         /// </summary>
145         /// <param name="order">the sibling order of the layout item</param>
146         /// <since_tizen> 6 </since_tizen>
147         /// This will be public opened in tizen_next after ACR done. Before ACR, need to be hidden as inhouse API.
148         [EditorBrowsable(EditorBrowsableState.Never)]
149         public void ChangeLayoutSiblingOrder(int order)
150         {
151             if (Owner != null)
152             {
153                 var ownerParent = Owner.GetParent() as View;
154                 if (ownerParent != null)
155                 {
156                     var parent = ownerParent.Layout as LayoutGroup;
157
158                     if (parent != null && parent.LayoutChildren.Count > order)
159                     {
160                         parent.LayoutChildren.Remove(this);
161                         parent.LayoutChildren.Insert(order, this);
162                     }
163                 }
164             }
165             RequestLayout();
166         }
167
168         // Attaches to View ChildAdded signal so called when a View is added to a view.
169         private void AddChildToLayoutGroup(View child)
170         {
171             if (View.LayoutingDisabled)
172             {
173                 return;
174             }
175             // Only give children a layout if their parent is an explicit container or a pure View.
176             // Pure View meaning not derived from a View, e.g a Legacy container.
177             // layoutSet flag is true when the View became a layout using the set Layout API opposed to automatically due to it's parent.
178             // First time the set Layout API is used by any View the Window no longer has layoutingDisabled.
179
180             // If child already has a Layout then don't change it.
181             if (null == child.Layout)
182             {
183                 // Only wrap View with a Layout if a child a pure View or Layout explicitly set on this Layout
184                 if ((true == Owner.LayoutSet || GetType() == typeof(View)))
185                 {
186                     // If child of this layout is a pure View then assign it a LayoutGroup
187                     // If the child is derived from a View then it may be a legacy or existing container hence will do layouting itself.
188                     child.Layout = child.CreateDefaultLayout();
189                 }
190             }
191             else
192             {
193                 Add(child.Layout);
194             }
195             // Parent transitions are not attached to children.
196         }
197
198         /// <summary>
199         /// If the child has a layout then it is removed from the parent layout.
200         /// </summary>
201         /// <param name="child">Child View to remove.</param>
202         internal void RemoveChildFromLayoutGroup(View child)
203         {
204             if (child.Layout != null)
205             {
206                 Remove(child.Layout);
207             }
208         }
209
210         /// <summary>
211         /// Set all children in a LayoutGroup to the supplied condition.
212         /// Children with Add or Remove conditions should not be changed.
213         /// </summary>
214         private void SetConditionsForAnimationOnLayoutGroup(TransitionCondition conditionToSet)
215         {
216             foreach (LayoutItem childLayout in LayoutChildren)
217             {
218                 switch (conditionToSet)
219                 {
220                     case TransitionCondition.ChangeOnAdd:
221                         {
222                             // If other children also being added (TransitionCondition.Add) then do not change their
223                             // conditions, Continue to use their Add transitions.
224                             if (childLayout.ConditionForAnimation.HasFlag(TransitionCondition.Add))
225                             {
226                                 break;  // Child being Added so don't update it's condition
227                             }
228                             else
229                             {
230                                 // Set siblings for the child being added to use the ChangeOnAdd transition.
231                                 childLayout.ConditionForAnimation = TransitionCondition.ChangeOnAdd;
232                             }
233                             break;
234                         }
235                     case TransitionCondition.ChangeOnRemove:
236                         {
237                             if (childLayout.ConditionForAnimation.HasFlag(TransitionCondition.Remove))
238                             {
239                                 break; // Child being Removed so don't update it's condition
240                             }
241                             else
242                             {
243                                 childLayout.ConditionForAnimation = TransitionCondition.ChangeOnRemove;
244                             }
245                             break;
246                         }
247                     case TransitionCondition.LayoutChanged:
248                         {
249                             childLayout.ConditionForAnimation = TransitionCondition.LayoutChanged;
250                             break;
251                         }
252                 }
253             }
254
255         }
256
257         /// <summary>
258         /// Callback for View.ChildAdded event
259         /// </summary>
260         /// <param name="sender">The object triggering the event.</param>
261         /// <param name="childAddedEvent">Arguments from the event.</param>
262         void OnChildAddedToOwner(object sender, View.ChildAddedEventArgs childAddedEvent)
263         {
264             AddChildToLayoutGroup(childAddedEvent.Added);
265         }
266
267         /// <summary>
268         /// Callback for View.ChildRemoved event
269         /// </summary>
270         /// <param name="sender">The object triggering the event.</param>
271         /// <param name="childRemovedEvent">Arguments from the event.</param>
272         void OnChildRemovedFromOwner(object sender, View.ChildRemovedEventArgs childRemovedEvent)
273         {
274             RemoveChildFromLayoutGroup(childRemovedEvent.Removed);
275         }
276
277         /// <summary>
278         /// Calculate the right measure spec for this child.
279         /// Does the hard part of MeasureChildren: figuring out the MeasureSpec to
280         /// pass to a particular child. This method figures out the right MeasureSpec
281         /// for one dimension (height or width) of one child view.<br />
282         /// </summary>
283         /// <param name="parentMeasureSpec">The requirements for this view. MeasureSpecification.</param>
284         /// <param name="padding">The padding of this view for the current dimension and margins, if applicable. LayoutLength.</param>
285         /// <param name="childDimension"> How big the child wants to be in the current dimension. LayoutLength.</param>
286         /// <returns>a MeasureSpec for the child.</returns>
287         public static MeasureSpecification GetChildMeasureSpecification(MeasureSpecification parentMeasureSpec, LayoutLength padding, LayoutLength childDimension)
288         {
289             MeasureSpecification.ModeType specMode = parentMeasureSpec.Mode;
290             MeasureSpecification.ModeType resultMode = MeasureSpecification.ModeType.Unspecified;
291
292             // Child only can use parent's size without parent's padding and own margin.
293             LayoutLength resultSize = new LayoutLength(Math.Max(0.0f, (parentMeasureSpec.Size - padding).AsDecimal()));
294             switch (specMode)
295             {
296                 // Parent has imposed an exact size on us
297                 case MeasureSpecification.ModeType.Exactly:
298                     {
299                         if ((int)childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent)
300                         {
301                             resultMode = MeasureSpecification.ModeType.Exactly;
302                         }
303                         else if ((int)childDimension.AsRoundedValue() == LayoutParamPolicies.WrapContent)
304                         {
305                             resultMode = MeasureSpecification.ModeType.AtMost;
306                         }
307                         else
308                         {
309                             resultSize = childDimension;
310                             resultMode = MeasureSpecification.ModeType.Exactly;
311                         }
312
313                         break;
314                     }
315
316                 // Parent has imposed a maximum size on us
317                 case MeasureSpecification.ModeType.AtMost:
318                     {
319                         if (childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent)
320                         {
321                             // Crashed. Cannot calculate.
322
323                             // Child wants to be our size, but our size is not fixed.
324                             // Constrain child to not be bigger than us.
325                             resultMode = MeasureSpecification.ModeType.AtMost;
326                         }
327                         else if (childDimension.AsRoundedValue() == LayoutParamPolicies.WrapContent)
328                         {
329                             // Child wants to determine its own size. It can't be
330                             // bigger than us.
331
332                             // Don't need parent's size. Size of this child will be determined by its children.
333                             resultMode = MeasureSpecification.ModeType.AtMost;
334                         }
335                         else
336                         {
337                             // Child wants a specific size... so be it
338                             resultSize = childDimension;
339                             resultMode = MeasureSpecification.ModeType.Exactly;
340                         }
341
342                         break;
343                     }
344
345                 // Parent asked to see how big we want to be
346                 case MeasureSpecification.ModeType.Unspecified:
347                     {
348                         if ((childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent))
349                         {
350                             // Child wants to be our size... find out how big it should be
351
352                             // There is no one who has exact size in parent hierarchy.
353                             // Cannot calculate.
354                             resultMode = MeasureSpecification.ModeType.Unspecified;
355                         }
356                         else if (childDimension.AsRoundedValue() == (LayoutParamPolicies.WrapContent))
357                         {
358                             // Child wants to determine its own size.... find out how big
359                             // it should be
360                             resultMode = MeasureSpecification.ModeType.Unspecified;
361                         }
362                         else
363                         {
364                             // Child wants a specific size... let him have it
365                             resultSize = childDimension;
366                             resultMode = MeasureSpecification.ModeType.Exactly;
367                         }
368                         break;
369                     }
370             } // switch
371
372             return new MeasureSpecification(resultSize, resultMode);
373         }
374
375         /// <summary>
376         /// Measure the layout and its content to determine the measured width and the measured height.<br />
377         /// If this method is overridden, it is the subclass's responsibility to make
378         /// sure the measured height and width are at least the layout's minimum height
379         /// and width. <br />
380         /// </summary>
381         /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
382         /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
383         /// <since_tizen> 6 </since_tizen>
384         protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
385         {
386             LayoutLength measuredWidth = new LayoutLength(0.0f);
387             LayoutLength measuredHeight = new LayoutLength(0.0f);
388
389             // Layout takes size of largest child width and largest child height dimensions
390             foreach (LayoutItem childLayout in LayoutChildren)
391             {
392                 if (childLayout != null)
393                 {
394                     MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0));
395                     LayoutLength childWidth = new LayoutLength(childLayout.MeasuredWidth.Size);
396                     LayoutLength childHeight = new LayoutLength(childLayout.MeasuredHeight.Size);
397
398                     Extents childMargin = childLayout.Margin;
399                     measuredWidth = new LayoutLength(Math.Max(measuredWidth.AsDecimal(), childWidth.AsDecimal() + childMargin.Start + childMargin.End));
400                     measuredHeight = new LayoutLength(Math.Max(measuredHeight.AsDecimal(), childHeight.AsDecimal() + childMargin.Top + childMargin.Bottom));
401                 }
402             }
403
404             if (0 == LayoutChildren.Count)
405             {
406                 // Must be a leaf as has no children
407                 measuredWidth = GetDefaultSize(SuggestedMinimumWidth, widthMeasureSpec);
408                 measuredHeight = GetDefaultSize(SuggestedMinimumHeight, heightMeasureSpec);
409             }
410
411             SetMeasuredDimensions(new MeasuredSize(measuredWidth, MeasuredSize.StateType.MeasuredSizeOK),
412                                    new MeasuredSize(measuredHeight, MeasuredSize.StateType.MeasuredSizeOK));
413         }
414
415         internal override void OnMeasureIndependentChildren(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
416         {
417             foreach (var childLayout in LayoutChildren)
418             {
419                 if (!childLayout.SetPositionByLayout)
420                 {
421                     MeasureChildWithoutPadding(childLayout, widthMeasureSpec, heightMeasureSpec);
422                 }
423             }
424         }
425
426         /// <summary>
427         /// Called from Layout() when this layout should assign a size and position to each of its children.<br />
428         /// Derived classes with children should override this method and call Layout() on each of their children.<br />
429         /// </summary>
430         /// <param name="changed">This is a new size or position for this layout.</param>
431         /// <param name="left">Left position, relative to parent.</param>
432         /// <param name="top"> Top position, relative to parent.</param>
433         /// <param name="right">Right position, relative to parent.</param>
434         /// <param name="bottom">Bottom position, relative to parent.</param>
435         /// <since_tizen> 6 </since_tizen>
436         protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
437         {
438             foreach (LayoutItem childLayout in LayoutChildren)
439             {
440                 if (childLayout != null)
441                 {
442                     // Use position if explicitly set to child otherwise will be top left.
443                     var childLeft = new LayoutLength(childLayout.Owner.PositionX);
444                     var childTop = new LayoutLength(childLayout.Owner.PositionY);
445
446                     View owner = Owner;
447
448                     if (owner != null)
449                     {
450                         // Margin and Padding only supported when child anchor point is TOP_LEFT.
451                         if (owner.PivotPoint == PivotPoint.TopLeft || (owner.PositionUsesPivotPoint == false))
452                         {
453                             childLeft = childLeft + owner.Padding.Start + childLayout.Margin.Start;
454                             childTop = childTop + owner.Padding.Top + childLayout.Margin.Top;
455                         }
456                     }
457                     childLayout.Layout(childLeft, childTop, childLeft + childLayout.MeasuredWidth.Size, childTop + childLayout.MeasuredHeight.Size);
458                 }
459             }
460         }
461
462         /// <summary>
463         /// Layout independent children those Owners have true ExcludeLayouting. <br />
464         /// These children are required not to be affected by this layout. <br />
465         /// </summary>
466         internal override void OnLayoutIndependentChildren(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
467         {
468             foreach (var childLayout in LayoutChildren)
469             {
470                 if (!childLayout.SetPositionByLayout)
471                 {
472                     LayoutLength childWidth = childLayout.MeasuredWidth.Size;
473                     LayoutLength childHeight = childLayout.MeasuredHeight.Size;
474
475                     LayoutLength childPositionX = new LayoutLength(childLayout.Owner.PositionX);
476                     LayoutLength childPositionY = new LayoutLength(childLayout.Owner.PositionY);
477
478                     childLayout.Layout(childPositionX, childPositionY, childPositionX + childWidth, childPositionY + childHeight, true);
479                 }
480             }
481         }
482
483         /// <summary>
484         /// Overridden method called when the layout is attached to an owner.<br />
485         /// </summary>
486         /// <since_tizen> 6 </since_tizen>
487         protected override void OnAttachedToOwner()
488         {
489             // Layout takes ownership of it's owner's children.
490             foreach (View view in Owner.Children)
491             {
492                 AddChildToLayoutGroup(view);
493             }
494
495             // Connect to owner ChildAdded signal.
496             Owner.ChildAdded += OnChildAddedToOwner;
497
498             // Removing Child from the owners View will directly call the LayoutGroup removal API.
499         }
500
501         /// <summary>
502         /// Virtual method to allow derived classes to remove any children before it is removed from
503         /// its parent.
504         /// </summary>
505         [EditorBrowsable(EditorBrowsableState.Never)]
506         protected override void OnUnparent()
507         {
508             // Disconnect to owner ChildAdded signal.
509             Owner.ChildAdded -= OnChildAddedToOwner;
510         }
511
512         // Virtual Methods that can be overridden by derived classes.
513
514         /// <summary>
515         /// Callback when child is added to container.<br />
516         /// Derived classes can use this to set their own child properties on the child layout's owner.<br />
517         /// </summary>
518         /// <param name="child">The Layout child.</param>
519         /// <since_tizen> 6 </since_tizen>
520         protected virtual void OnChildAdd(LayoutItem child)
521         {
522         }
523
524         /// <summary>
525         /// Callback when child is removed from container.<br />
526         /// </summary>
527         /// <param name="child">The Layout child.</param>
528         /// <since_tizen> 6 </since_tizen>
529         protected virtual void OnChildRemove(LayoutItem child)
530         {
531         }
532
533         /// <summary>
534         /// Ask all of the children of this view to measure themselves, taking into
535         /// account both the MeasureSpec requirements for this view and its padding.<br />
536         /// The heavy lifting is done in GetChildMeasureSpec.<br />
537         /// </summary>
538         /// <param name="widthMeasureSpec">The width requirements for this view.</param>
539         /// <param name="heightMeasureSpec">The height requirements for this view.</param>
540         /// <since_tizen> 6 </since_tizen>
541         protected virtual void MeasureChildren(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
542         {
543             foreach (LayoutItem childLayout in LayoutChildren)
544             {
545                 MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0));
546             }
547         }
548
549         /// <summary>
550         /// Ask one of the children of this view to measure itself, taking into
551         /// account both the MeasureSpec requirements for this view and its padding.<br />
552         /// The heavy lifting is done in GetChildMeasureSpecification.<br />
553         /// </summary>
554         /// <param name="child">The child to measure.</param>
555         /// <param name="parentWidthMeasureSpec">The width requirements for this view.</param>
556         /// <param name="parentHeightMeasureSpec">The height requirements for this view.</param>
557         /// <exception cref="ArgumentNullException"> Thrown when child is null. </exception>
558         /// <since_tizen> 6 </since_tizen>
559         protected virtual void MeasureChild(LayoutItem child, MeasureSpecification parentWidthMeasureSpec, MeasureSpecification parentHeightMeasureSpec)
560         {
561             if (null == child)
562             {
563                 throw new ArgumentNullException(nameof(child));
564             }
565
566             View childOwner = child.Owner;
567
568             MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification(
569                         new MeasureSpecification(new LayoutLength(parentWidthMeasureSpec.Size), parentWidthMeasureSpec.Mode),
570                         new LayoutLength(Padding.Start + Padding.End),
571                         new LayoutLength(childOwner.WidthSpecification));
572
573             MeasureSpecification childHeightMeasureSpec = GetChildMeasureSpecification(
574                         new MeasureSpecification(new LayoutLength(parentHeightMeasureSpec.Size), parentHeightMeasureSpec.Mode),
575                         new LayoutLength(Padding.Top + Padding.Bottom),
576                         new LayoutLength(childOwner.HeightSpecification));
577
578             child.Measure(childWidthMeasureSpec, childHeightMeasureSpec);
579         }
580
581         /// <summary>
582         /// Ask one of the children of this view to measure itself, taking into
583         /// account both the MeasureSpec requirements for this view and its padding.<br />
584         /// and margins. The heavy lifting is done in GetChildMeasureSpecification.<br />
585         /// </summary>
586         /// <param name="child">The child to measure.</param>
587         /// <param name="parentWidthMeasureSpec">The width requirements for this view.</param>
588         /// <param name="widthUsed">Extra space that has been used up by the parent horizontally (possibly by other children of the parent).</param>
589         /// <param name="parentHeightMeasureSpec">The height requirements for this view.</param>
590         /// <param name="heightUsed">Extra space that has been used up by the parent vertically (possibly by other children of the parent).</param>
591         /// <exception cref="ArgumentNullException"> Thrown when child is null. </exception>
592         /// <since_tizen> 6 </since_tizen>
593         protected virtual void MeasureChildWithMargins(LayoutItem child, MeasureSpecification parentWidthMeasureSpec, LayoutLength widthUsed, MeasureSpecification parentHeightMeasureSpec, LayoutLength heightUsed)
594         {
595             if (null == child)
596             {
597                 throw new ArgumentNullException(nameof(child));
598             }
599
600             View childOwner = child.Owner;
601             Extents margin = childOwner.Margin;
602
603             MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification(
604                         new MeasureSpecification(
605                             new LayoutLength(parentWidthMeasureSpec.Size + widthUsed - (margin.Start + margin.End)),
606                             parentWidthMeasureSpec.Mode),
607                         new LayoutLength(Padding.Start + Padding.End),
608                         new LayoutLength(childOwner.WidthSpecification));
609
610             MeasureSpecification childHeightMeasureSpec = GetChildMeasureSpecification(
611                         new MeasureSpecification(
612                             new LayoutLength(parentHeightMeasureSpec.Size + heightUsed - (margin.Top + margin.Bottom)),
613                             parentHeightMeasureSpec.Mode),
614                         new LayoutLength(Padding.Top + Padding.Bottom),
615                         new LayoutLength(childOwner.HeightSpecification));
616             child.Measure(childWidthMeasureSpec, childHeightMeasureSpec);
617
618         }
619
620         /// <summary>
621         /// Ask one of the children of this view to measure itself, taking into
622         /// account both the MeasureSpec requirements for this view and without its padding.<br />
623         /// and margins. The heavy lifting is done in GetChildMeasureSpecification.<br />
624         /// </summary>
625         /// <param name="child">The child to measure.</param>
626         /// <param name="parentWidthMeasureSpec">The width requirements for this view.</param>
627         /// <param name="parentHeightMeasureSpec">The height requirements for this view.</param>
628         [EditorBrowsable(EditorBrowsableState.Never)]
629         protected void MeasureChildWithoutPadding(LayoutItem child, MeasureSpecification parentWidthMeasureSpec, MeasureSpecification parentHeightMeasureSpec)
630         {
631             View childOwner = child.Owner;
632
633             MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification(
634                         new MeasureSpecification(new LayoutLength(parentWidthMeasureSpec.Size), parentWidthMeasureSpec.Mode),
635                         new LayoutLength(0),
636                         new LayoutLength(childOwner.WidthSpecification));
637
638             MeasureSpecification childHeightMeasureSpec = GetChildMeasureSpecification(
639                         new MeasureSpecification(new LayoutLength(parentHeightMeasureSpec.Size), parentHeightMeasureSpec.Mode),
640                         new LayoutLength(0),
641                         new LayoutLength(childOwner.HeightSpecification));
642
643             child.Measure(childWidthMeasureSpec, childHeightMeasureSpec);
644         }
645
646         /// <summary>
647         /// Gets the value that is contained in the attached BindableProperty.
648         /// </summary>
649         /// <typeparam name="T">The return type of property</typeparam>
650         /// <param name="bindable">The bindable object.</param>
651         /// <param name="property">The BindableProperty for which to get the value.</param>
652         /// <returns>The value that is contained in the attached BindableProperty.</returns>
653         [EditorBrowsable(EditorBrowsableState.Never)]
654         public static T GetAttachedValue<T>(Binding.BindableObject bindable, Binding.BindableProperty property)
655         {
656             if (bindable == null)
657                 throw new ArgumentNullException(nameof(bindable));
658
659             return (T)bindable.GetValue(property);
660         }
661
662         /// <summary>
663         /// Sets the value of the attached property.
664         /// </summary>
665         /// <param name="bindable">The bindable object.</param>
666         /// <param name="property">The BindableProperty on which to assign a value.</param>
667         /// <param name="value">The value to set.</param>
668         [EditorBrowsable(EditorBrowsableState.Never)]
669         public static void SetAttachedValue(Binding.BindableObject bindable, Binding.BindableProperty property, object value)
670         {
671             if (bindable == null)
672                 throw new ArgumentNullException(nameof(bindable));
673
674             bindable.SetValueCore(property, value, SetValueFlags.None, SetValuePrivateFlags.ManuallySet, false);
675         }
676         internal static void OnChildPropertyChanged(Binding.BindableObject bindable, object oldValue, object newValue)
677         {
678             // Unused parameters
679             _ = oldValue;
680             _ = newValue;
681
682             View view = bindable as View;
683             view?.Layout?.RequestLayout();
684         }
685
686         internal static Binding.BindableProperty.ValidateValueDelegate ValidateEnum(int enumMin, int enumMax)
687         {
688             return (Binding.BindableObject bindable, object value) =>
689             {
690                 int @enum = (int)value;
691                 return enumMin <= @enum && @enum <= enumMax;
692             };
693         }
694     }
695 }