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);
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)
136 if(Owner != null && Owner.Parent != null)
138 LayoutGroup parent = Owner.Parent.Layout as LayoutGroup;
140 if(parent != null && parent.LayoutChildren.Count>order)
142 parent.LayoutChildren.Remove(this);
143 parent.LayoutChildren.Insert(order,this);
150 // Attaches to View ChildAdded signal so called when a View is added to a view.
151 private void AddChildToLayoutGroup(View child)
153 // Only give children a layout if their parent is an explicit container or a pure View.
154 // Pure View meaning not derived from a View, e.g a Legacy container.
155 // layoutSet flag is true when the View became a layout using the set Layout API opposed to automatically due to it's parent.
156 // First time the set Layout API is used by any View the Window no longer has layoutingDisabled.
158 // If child already has a Layout then don't change it.
159 if (! View.layoutingDisabled && (null == child.Layout))
161 // Only wrap View with a Layout if a child a pure View or Layout explicitly set on this Layout
162 if ((true == Owner.layoutSet || GetType() == typeof(View)))
164 // If child of this layout is a pure View then assign it a LayoutGroup
165 // If the child is derived from a View then it may be a legacy or existing container hence will do layouting itself.
166 child.Layout = new AbsoluteLayout();
171 // Add child layout to this LayoutGroup (Setting parent in the process)
172 if(child.Layout != null)
177 // Parent transitions are not attached to children.
181 /// If the child has a layout then it is removed from the parent layout.
183 /// <param name="child">Child View to remove.</param>
184 internal void RemoveChildFromLayoutGroup(View child)
186 Debug.Assert(child.Layout !=null);
187 Remove(child.Layout);
191 /// Set all children in a LayoutGroup to the supplied condition.
192 /// Children with Add or Remove conditions should not be changed.
194 private void SetConditionsForAnimationOnLayoutGroup( TransitionCondition conditionToSet)
196 foreach( LayoutItem childLayout in LayoutChildren )
198 switch( conditionToSet )
200 case TransitionCondition.ChangeOnAdd :
202 // If other children also being added (TransitionCondition.Add) then do not change their
203 // conditions, Continue to use their Add transitions.
204 if (childLayout.ConditionForAnimation.HasFlag(TransitionCondition.Add))
206 break; // Child being Added so don't update it's condition
210 // Set siblings for the child being added to use the ChangeOnAdd transition.
211 childLayout.ConditionForAnimation = TransitionCondition.ChangeOnAdd;
215 case TransitionCondition.ChangeOnRemove :
217 if (childLayout.ConditionForAnimation.HasFlag(TransitionCondition.Remove))
219 break; // Child being Removed so don't update it's condition
223 childLayout.ConditionForAnimation = TransitionCondition.ChangeOnRemove;
227 case TransitionCondition.LayoutChanged :
229 childLayout.ConditionForAnimation = TransitionCondition.LayoutChanged;
238 /// Callback for View.ChildAdded event
240 /// <param name="sender">The object triggering the event.</param>
241 /// <param name="childAddedEvent">Arguments from the event.</param>
242 void OnChildAddedToOwner(object sender, View.ChildAddedEventArgs childAddedEvent)
244 AddChildToLayoutGroup(childAddedEvent.Added);
248 /// Callback for View.ChildRemoved event
250 /// <param name="sender">The object triggering the event.</param>
251 /// <param name="childRemovedEvent">Arguments from the event.</param>
252 void OnChildRemovedFromOwner(object sender, View.ChildRemovedEventArgs childRemovedEvent)
254 RemoveChildFromLayoutGroup(childRemovedEvent.Removed);
258 /// Calculate the right measure spec for this child.
259 /// Does the hard part of MeasureChildren: figuring out the MeasureSpec to
260 /// pass to a particular child. This method figures out the right MeasureSpec
261 /// for one dimension (height or width) of one child view.<br />
263 /// <param name="parentMeasureSpec">The requirements for this view. MeasureSpecification.</param>
264 /// <param name="padding">The padding of this view for the current dimension and margins, if applicable. LayoutLength.</param>
265 /// <param name="childDimension"> How big the child wants to be in the current dimension. LayoutLength.</param>
266 /// <returns>a MeasureSpec for the child.</returns>
267 public static MeasureSpecification GetChildMeasureSpecification(MeasureSpecification parentMeasureSpec, LayoutLength padding, LayoutLength childDimension)
269 MeasureSpecification.ModeType specMode = parentMeasureSpec.Mode;
270 MeasureSpecification.ModeType resultMode = MeasureSpecification.ModeType.Unspecified;
272 // Child only can use parent's size without parent's padding and own margin.
273 LayoutLength resultSize = new LayoutLength(Math.Max( 0.0f, (parentMeasureSpec.Size - padding).AsDecimal()));
276 // Parent has imposed an exact size on us
277 case MeasureSpecification.ModeType.Exactly:
279 if ((int)childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent)
281 resultMode = MeasureSpecification.ModeType.Exactly;
283 else if ((int)childDimension.AsRoundedValue() == LayoutParamPolicies.WrapContent)
285 resultMode = MeasureSpecification.ModeType.AtMost;
289 resultSize = childDimension;
290 resultMode = MeasureSpecification.ModeType.Exactly;
296 // Parent has imposed a maximum size on us
297 case MeasureSpecification.ModeType.AtMost:
299 if (childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent)
301 // Crashed. Cannot calculate.
303 // Child wants to be our size, but our size is not fixed.
304 // Constrain child to not be bigger than us.
305 resultMode = MeasureSpecification.ModeType.AtMost;
307 else if (childDimension.AsRoundedValue() == LayoutParamPolicies.WrapContent)
309 // Child wants to determine its own size. It can't be
312 // Don't need parent's size. Size of this child will be determined by its children.
313 resultMode = MeasureSpecification.ModeType.AtMost;
317 // Child wants a specific size... so be it
318 resultSize = childDimension;
319 resultMode = MeasureSpecification.ModeType.Exactly;
325 // Parent asked to see how big we want to be
326 case MeasureSpecification.ModeType.Unspecified:
328 if ((childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent))
330 // Child wants to be our size... find out how big it should be
332 // There is no one who has exact size in parent hierarchy.
334 resultMode = MeasureSpecification.ModeType.Unspecified;
336 else if (childDimension.AsRoundedValue() == (LayoutParamPolicies.WrapContent))
338 // Child wants to determine its own size.... find out how big
340 resultMode = MeasureSpecification.ModeType.Unspecified;
344 // Child wants a specific size... let him have it
345 resultSize = childDimension;
346 resultMode = MeasureSpecification.ModeType.Exactly;
352 return new MeasureSpecification( resultSize, resultMode );
356 /// Measure the layout and its content to determine the measured width and the measured height.<br />
357 /// If this method is overridden, it is the subclass's responsibility to make
358 /// sure the measured height and width are at least the layout's minimum height
359 /// and width. <br />
361 /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
362 /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
363 /// <since_tizen> 6 </since_tizen>
364 protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
366 LayoutLength measuredWidth = new LayoutLength(0.0f);
367 LayoutLength measuredHeight = new LayoutLength(0.0f);
369 // Layout takes size of largest child width and largest child height dimensions
370 foreach( LayoutItem childLayout in LayoutChildren )
372 if( childLayout != null )
374 MeasureChildWithMargins( childLayout, widthMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0) );
375 LayoutLength childWidth = new LayoutLength(childLayout.MeasuredWidth.Size);
376 LayoutLength childHeight = new LayoutLength( childLayout.MeasuredHeight.Size);
378 Extents childMargin = childLayout.Margin;
379 measuredWidth = new LayoutLength(Math.Max( measuredWidth.AsDecimal(), childWidth.AsDecimal() + childMargin.Start + childMargin.End));
380 measuredHeight = new LayoutLength(Math.Max( measuredHeight.AsDecimal(), childHeight.AsDecimal() + childMargin.Top + childMargin.Bottom));
384 if( 0 == LayoutChildren.Count )
386 // Must be a leaf as has no children
387 measuredWidth = GetDefaultSize( SuggestedMinimumWidth, widthMeasureSpec );
388 measuredHeight = GetDefaultSize( SuggestedMinimumHeight, heightMeasureSpec );
391 SetMeasuredDimensions( new MeasuredSize( measuredWidth, MeasuredSize.StateType.MeasuredSizeOK ),
392 new MeasuredSize( measuredHeight, MeasuredSize.StateType.MeasuredSizeOK ) );
396 /// Called from Layout() when this layout should assign a size and position to each of its children.<br />
397 /// Derived classes with children should override this method and call Layout() on each of their children.<br />
399 /// <param name="changed">This is a new size or position for this layout.</param>
400 /// <param name="left">Left position, relative to parent.</param>
401 /// <param name="top"> Top position, relative to parent.</param>
402 /// <param name="right">Right position, relative to parent.</param>
403 /// <param name="bottom">Bottom position, relative to parent.</param>
404 /// <since_tizen> 6 </since_tizen>
405 protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
407 foreach( LayoutItem childLayout in LayoutChildren )
409 if( childLayout !=null )
411 // Use position if explicitly set to child otherwise will be top left.
412 var childLeft = new LayoutLength( childLayout.Owner.Position2D.X );
413 var childTop = new LayoutLength( childLayout.Owner.Position2D.Y );
419 // Margin and Padding only supported when child anchor point is TOP_LEFT.
420 if ( owner.PivotPoint == PivotPoint.TopLeft || ( owner.PositionUsesPivotPoint == false ) )
422 childLeft = childLeft + owner.Padding.Start + childLayout.Margin.Start;
423 childTop = childTop + owner.Padding.Top + childLayout.Margin.Top;
426 childLayout.Layout( childLeft, childTop, childLeft + childLayout.MeasuredWidth.Size, childTop + childLayout.MeasuredHeight.Size );
432 /// Overridden method called when the layout is attached to an owner.<br />
434 /// <since_tizen> 6 </since_tizen>
435 protected override void OnAttachedToOwner()
437 // Layout takes ownership of it's owner's children.
438 foreach (View view in Owner.Children)
440 AddChildToLayoutGroup(view);
443 // Connect to owner ChildAdded signal.
444 Owner.ChildAdded += OnChildAddedToOwner;
446 // Removing Child from the owners View will directly call the LayoutGroup removal API.
450 /// Virtual method to allow derived classes to remove any children before it is removed from
453 [EditorBrowsable(EditorBrowsableState.Never)]
454 protected override void OnUnparent()
456 // Disconnect to owner ChildAdded signal.
457 Owner.ChildAdded -= OnChildAddedToOwner;
460 // Virtual Methods that can be overridden by derived classes.
463 /// Callback when child is added to container.<br />
464 /// Derived classes can use this to set their own child properties on the child layout's owner.<br />
466 /// <param name="child">The Layout child.</param>
467 /// <since_tizen> 6 </since_tizen>
468 protected virtual void OnChildAdd(LayoutItem child)
473 /// Callback when child is removed from container.<br />
475 /// <param name="child">The Layout child.</param>
476 /// <since_tizen> 6 </since_tizen>
477 protected virtual void OnChildRemove(LayoutItem child)
482 /// Ask all of the children of this view to measure themselves, taking into
483 /// account both the MeasureSpec requirements for this view and its padding.<br />
484 /// The heavy lifting is done in GetChildMeasureSpec.<br />
486 /// <param name="widthMeasureSpec">The width requirements for this view.</param>
487 /// <param name="heightMeasureSpec">The height requirements for this view.</param>
488 /// <since_tizen> 6 </since_tizen>
489 protected virtual void MeasureChildren(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
491 foreach( LayoutItem childLayout in LayoutChildren )
493 MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0));
498 /// Ask one of the children of this view to measure itself, taking into
499 /// account both the MeasureSpec requirements for this view and its padding.<br />
500 /// The heavy lifting is done in GetChildMeasureSpecification.<br />
502 /// <param name="child">The child to measure.</param>
503 /// <param name="parentWidthMeasureSpec">The width requirements for this view.</param>
504 /// <param name="parentHeightMeasureSpec">The height requirements for this view.</param>
505 /// <since_tizen> 6 </since_tizen>
506 protected virtual void MeasureChild(LayoutItem child, MeasureSpecification parentWidthMeasureSpec, MeasureSpecification parentHeightMeasureSpec)
508 View childOwner = child.Owner;
510 MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification(
511 new MeasureSpecification(new LayoutLength(parentWidthMeasureSpec.Size), parentWidthMeasureSpec.Mode),
512 new LayoutLength(Padding.Start + Padding.End ),
513 new LayoutLength(childOwner.WidthSpecification) );
515 MeasureSpecification childHeightMeasureSpec = GetChildMeasureSpecification(
516 new MeasureSpecification(new LayoutLength(parentHeightMeasureSpec.Size), parentHeightMeasureSpec.Mode),
517 new LayoutLength(Padding.Top + Padding.Bottom),
518 new LayoutLength(childOwner.HeightSpecification));
520 child.Measure(childWidthMeasureSpec, childHeightMeasureSpec);
524 /// Ask one of the children of this view to measure itself, taking into
525 /// account both the MeasureSpec requirements for this view and its padding.<br />
526 /// and margins. The heavy lifting is done in GetChildMeasureSpecification.<br />
528 /// <param name="child">The child to measure.</param>
529 /// <param name="parentWidthMeasureSpec">The width requirements for this view.</param>
530 /// <param name="widthUsed">Extra space that has been used up by the parent horizontally (possibly by other children of the parent).</param>
531 /// <param name="parentHeightMeasureSpec">The height requirements for this view.</param>
532 /// <param name="heightUsed">Extra space that has been used up by the parent vertically (possibly by other children of the parent).</param>
533 /// <since_tizen> 6 </since_tizen>
534 protected virtual void MeasureChildWithMargins(LayoutItem child, MeasureSpecification parentWidthMeasureSpec, LayoutLength widthUsed, MeasureSpecification parentHeightMeasureSpec, LayoutLength heightUsed)
536 View childOwner = child.Owner;
539 MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification(
540 new MeasureSpecification(
541 new LayoutLength(parentWidthMeasureSpec.Size + widthUsed - (childOwner.Margin.Start + childOwner.Margin.End)),
542 parentWidthMeasureSpec.Mode),
543 new LayoutLength(Padding.Start + Padding.End ),
544 new LayoutLength(childOwner.WidthSpecification) );
546 MeasureSpecification childHeightMeasureSpec = GetChildMeasureSpecification(
547 new MeasureSpecification(
548 new LayoutLength(parentHeightMeasureSpec.Size + heightUsed - (childOwner.Margin.Top + childOwner.Margin.Bottom)),
549 parentHeightMeasureSpec.Mode),
550 new LayoutLength(Padding.Top + Padding.Bottom),
551 new LayoutLength(childOwner.HeightSpecification));
552 child.Measure( childWidthMeasureSpec, childHeightMeasureSpec );
557 /// Gets the value that is contained in the attached BindableProperty.
559 /// <typeparam name="T">The return type of property</typeparam>
560 /// <param name="bindable">The bindable object.</param>
561 /// <param name="property">The BindableProperty for which to get the value.</param>
562 /// <returns>The value that is contained in the attached BindableProperty.</returns>
563 [EditorBrowsable(EditorBrowsableState.Never)]
564 public static T GetAttachedValue<T>(Binding.BindableObject bindable, Binding.BindableProperty property)
566 if (bindable == null)
567 throw new ArgumentNullException(nameof(bindable));
569 return (T)bindable.GetValue(property);
573 /// Sets the value of the attached property.
575 /// <param name="bindable">The bindable object.</param>
576 /// <param name="property">The BindableProperty on which to assign a value.</param>
577 /// <param name="value">The value to set.</param>
578 [EditorBrowsable(EditorBrowsableState.Never)]
579 public static void SetAttachedValue(Binding.BindableObject bindable, Binding.BindableProperty property, object value)
581 if (bindable == null)
582 throw new ArgumentNullException(nameof(bindable));
584 bindable.SetValueCore(property, value, SetValueFlags.None, SetValuePrivateFlags.ManuallySet, false);
586 internal static void OnChildPropertyChanged(Binding.BindableObject bindable, object oldValue, object newValue)
588 View view = bindable as View;
589 view?.Layout?.RequestLayout();