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