2 * Copyright (c) 2020 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 using System.Collections.Generic;
19 using System.ComponentModel;
20 using System.Diagnostics;
21 using Tizen.NUI.BaseComponents;
23 using Tizen.NUI.Binding.Internals;
24 using static Tizen.NUI.Binding.BindableObject;
29 /// [Draft] LayoutGroup class providing container functionality.
31 public class LayoutGroup : LayoutItem, ILayoutParent
34 /// [Draft] List of child layouts in this container.
36 /// <since_tizen> 6 </since_tizen>
37 protected List<LayoutItem> LayoutChildren{ get;} // Children of this LayoutGroup
40 /// [Draft] Constructor
42 /// <since_tizen> 6 </since_tizen>
45 LayoutChildren = new List<LayoutItem>();
49 /// From ILayoutParent.<br />
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)
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);
66 /// Remove all layout children.<br />
68 /// <since_tizen> 6 </since_tizen>
69 public void RemoveAll()
71 foreach( LayoutItem childLayout in LayoutChildren )
73 childLayout.ConditionForAnimation = ConditionForAnimation | TransitionCondition.Remove;
74 childLayout.Owner = null;
76 LayoutChildren.Clear();
77 // todo ensure child LayoutItems are still not parented to this group.
82 /// From ILayoutParent
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)
88 bool childRemoved = false;
89 foreach( LayoutItem childLayout in LayoutChildren.ToList() )
91 if( childLayout == layoutItem )
93 childLayout.ClearReplaceFlag();
94 LayoutChildren.Remove(childLayout);
96 if (LayoutWithTransition)
98 if (!childLayout.IsReplaceFlag())
100 NUIApplication.GetDefaultWindow().LayoutController.AddToRemovalStack(childLayout);
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));
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;
119 // If child removed then set all siblings not being added to a ChangeOnRemove transition.
120 SetConditionsForAnimationOnLayoutGroup(TransitionCondition.ChangeOnRemove);
122 OnChildRemove(layoutItem);
128 /// Sets the sibling order of the layout item so the layout can be defined within the same parent.
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)
138 var ownerParent = Owner.GetParent() as View;
139 if (ownerParent != null)
141 var parent = ownerParent.Layout as LayoutGroup;
143 if (parent != null && parent.LayoutChildren.Count > order)
145 parent.LayoutChildren.Remove(this);
146 parent.LayoutChildren.Insert(order, this);
153 // Attaches to View ChildAdded signal so called when a View is added to a view.
154 private void AddChildToLayoutGroup(View child)
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.
161 // If child already has a Layout then don't change it.
162 if (! View.layoutingDisabled && (null == child.Layout))
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)))
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();
174 // Add child layout to this LayoutGroup (Setting parent in the process)
175 if(child.Layout != null)
180 // Parent transitions are not attached to children.
184 /// If the child has a layout then it is removed from the parent layout.
186 /// <param name="child">Child View to remove.</param>
187 internal void RemoveChildFromLayoutGroup(View child)
189 if(child.Layout != null)
191 Remove(child.Layout);
196 /// Set all children in a LayoutGroup to the supplied condition.
197 /// Children with Add or Remove conditions should not be changed.
199 private void SetConditionsForAnimationOnLayoutGroup( TransitionCondition conditionToSet)
201 foreach( LayoutItem childLayout in LayoutChildren )
203 switch( conditionToSet )
205 case TransitionCondition.ChangeOnAdd :
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))
211 break; // Child being Added so don't update it's condition
215 // Set siblings for the child being added to use the ChangeOnAdd transition.
216 childLayout.ConditionForAnimation = TransitionCondition.ChangeOnAdd;
220 case TransitionCondition.ChangeOnRemove :
222 if (childLayout.ConditionForAnimation.HasFlag(TransitionCondition.Remove))
224 break; // Child being Removed so don't update it's condition
228 childLayout.ConditionForAnimation = TransitionCondition.ChangeOnRemove;
232 case TransitionCondition.LayoutChanged :
234 childLayout.ConditionForAnimation = TransitionCondition.LayoutChanged;
243 /// Callback for View.ChildAdded event
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)
249 AddChildToLayoutGroup(childAddedEvent.Added);
253 /// Callback for View.ChildRemoved event
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)
259 RemoveChildFromLayoutGroup(childRemovedEvent.Removed);
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 />
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)
274 MeasureSpecification.ModeType specMode = parentMeasureSpec.Mode;
275 MeasureSpecification.ModeType resultMode = MeasureSpecification.ModeType.Unspecified;
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()));
281 // Parent has imposed an exact size on us
282 case MeasureSpecification.ModeType.Exactly:
284 if ((int)childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent)
286 resultMode = MeasureSpecification.ModeType.Exactly;
288 else if ((int)childDimension.AsRoundedValue() == LayoutParamPolicies.WrapContent)
290 resultMode = MeasureSpecification.ModeType.AtMost;
294 resultSize = childDimension;
295 resultMode = MeasureSpecification.ModeType.Exactly;
301 // Parent has imposed a maximum size on us
302 case MeasureSpecification.ModeType.AtMost:
304 if (childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent)
306 // Crashed. Cannot calculate.
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;
312 else if (childDimension.AsRoundedValue() == LayoutParamPolicies.WrapContent)
314 // Child wants to determine its own size. It can't be
317 // Don't need parent's size. Size of this child will be determined by its children.
318 resultMode = MeasureSpecification.ModeType.AtMost;
322 // Child wants a specific size... so be it
323 resultSize = childDimension;
324 resultMode = MeasureSpecification.ModeType.Exactly;
330 // Parent asked to see how big we want to be
331 case MeasureSpecification.ModeType.Unspecified:
333 if ((childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent))
335 // Child wants to be our size... find out how big it should be
337 // There is no one who has exact size in parent hierarchy.
339 resultMode = MeasureSpecification.ModeType.Unspecified;
341 else if (childDimension.AsRoundedValue() == (LayoutParamPolicies.WrapContent))
343 // Child wants to determine its own size.... find out how big
345 resultMode = MeasureSpecification.ModeType.Unspecified;
349 // Child wants a specific size... let him have it
350 resultSize = childDimension;
351 resultMode = MeasureSpecification.ModeType.Exactly;
357 return new MeasureSpecification( resultSize, resultMode );
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 />
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)
371 LayoutLength measuredWidth = new LayoutLength(0.0f);
372 LayoutLength measuredHeight = new LayoutLength(0.0f);
374 // Layout takes size of largest child width and largest child height dimensions
375 foreach( LayoutItem childLayout in LayoutChildren )
377 if( childLayout != null )
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);
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));
389 if( 0 == LayoutChildren.Count )
391 // Must be a leaf as has no children
392 measuredWidth = GetDefaultSize( SuggestedMinimumWidth, widthMeasureSpec );
393 measuredHeight = GetDefaultSize( SuggestedMinimumHeight, heightMeasureSpec );
396 SetMeasuredDimensions( new MeasuredSize( measuredWidth, MeasuredSize.StateType.MeasuredSizeOK ),
397 new MeasuredSize( measuredHeight, MeasuredSize.StateType.MeasuredSizeOK ) );
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 />
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)
412 foreach( LayoutItem childLayout in LayoutChildren )
414 if( childLayout !=null )
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 );
424 // Margin and Padding only supported when child anchor point is TOP_LEFT.
425 if ( owner.PivotPoint == PivotPoint.TopLeft || ( owner.PositionUsesPivotPoint == false ) )
427 childLeft = childLeft + owner.Padding.Start + childLayout.Margin.Start;
428 childTop = childTop + owner.Padding.Top + childLayout.Margin.Top;
431 childLayout.Layout( childLeft, childTop, childLeft + childLayout.MeasuredWidth.Size, childTop + childLayout.MeasuredHeight.Size );
437 /// Overridden method called when the layout is attached to an owner.<br />
439 /// <since_tizen> 6 </since_tizen>
440 protected override void OnAttachedToOwner()
442 // Layout takes ownership of it's owner's children.
443 foreach (View view in Owner.Children)
445 AddChildToLayoutGroup(view);
448 // Connect to owner ChildAdded signal.
449 Owner.ChildAdded += OnChildAddedToOwner;
451 // Removing Child from the owners View will directly call the LayoutGroup removal API.
455 /// Virtual method to allow derived classes to remove any children before it is removed from
458 [EditorBrowsable(EditorBrowsableState.Never)]
459 protected override void OnUnparent()
461 // Disconnect to owner ChildAdded signal.
462 Owner.ChildAdded -= OnChildAddedToOwner;
465 // Virtual Methods that can be overridden by derived classes.
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 />
471 /// <param name="child">The Layout child.</param>
472 /// <since_tizen> 6 </since_tizen>
473 protected virtual void OnChildAdd(LayoutItem child)
478 /// Callback when child is removed from container.<br />
480 /// <param name="child">The Layout child.</param>
481 /// <since_tizen> 6 </since_tizen>
482 protected virtual void OnChildRemove(LayoutItem child)
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 />
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)
496 foreach( LayoutItem childLayout in LayoutChildren )
498 MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0));
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 />
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)
513 View childOwner = child.Owner;
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) );
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));
525 child.Measure(childWidthMeasureSpec, childHeightMeasureSpec);
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 />
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)
541 View childOwner = child.Owner;
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) );
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 );
562 /// Gets the value that is contained in the attached BindableProperty.
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)
571 if (bindable == null)
572 throw new ArgumentNullException(nameof(bindable));
574 return (T)bindable.GetValue(property);
578 /// Sets the value of the attached property.
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)
586 if (bindable == null)
587 throw new ArgumentNullException(nameof(bindable));
589 bindable.SetValueCore(property, value, SetValueFlags.None, SetValuePrivateFlags.ManuallySet, false);
591 internal static void OnChildPropertyChanged(Binding.BindableObject bindable, object oldValue, object newValue)
593 View view = bindable as View;
594 view?.Layout?.RequestLayout();
597 internal static Binding.BindableProperty.ValidateValueDelegate ValidateEnum(int enumMin, int enumMax)
600 return (Binding.BindableObject bindable, object value) =>
602 int @enum = (int)value;
603 return enumMin <= @enum && @enum <= enumMax;