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;
27 /// [Draft] LayoutGroup class providing container functionality.
29 public class LayoutGroup : LayoutItem, ILayoutParent
32 /// [Draft] List of child layouts in this container.
34 /// <since_tizen> 6 </since_tizen>
35 protected List<LayoutItem> LayoutChildren{ get;} // Children of this LayoutGroup
38 /// [Draft] Constructor
40 /// <since_tizen> 6 </since_tizen>
43 LayoutChildren = new List<LayoutItem>();
47 /// From ILayoutParent.<br />
49 /// <since_tizen> 6 </since_tizen>
50 /// <param name="childLayout">LayoutItem to add to the layout group.</param>
51 public virtual void Add(LayoutItem childLayout)
53 LayoutChildren.Add(childLayout);
54 childLayout.SetParent(this);
55 // Child added to use a Add transition.
56 childLayout.ConditionForAnimation = ConditionForAnimation | TransitionCondition.Add;
57 // Child's parent sets all other children not being added to a ChangeOnAdd transition.
58 SetConditionsForAnimationOnLayoutGroup(TransitionCondition.ChangeOnAdd);
59 OnChildAdd(childLayout);
64 /// Remove all layout children.<br />
66 /// <since_tizen> 6 </since_tizen>
67 public void RemoveAll()
69 foreach( LayoutItem childLayout in LayoutChildren )
71 childLayout.ConditionForAnimation = ConditionForAnimation | TransitionCondition.Remove;
72 childLayout.Owner = null;
74 LayoutChildren.Clear();
75 // todo ensure child LayoutItems are still not parented to this group.
80 /// From ILayoutParent
82 /// <param name="layoutItem">LayoutItem to remove from the layout group.</param>
83 /// <since_tizen> 6 </since_tizen>
84 public virtual void Remove(LayoutItem layoutItem)
86 bool childRemoved = false;
87 foreach( LayoutItem childLayout in LayoutChildren.ToList() )
89 if( childLayout == layoutItem )
91 childLayout.ClearReplaceFlag();
92 LayoutChildren.Remove(childLayout);
94 if (LayoutWithTransition)
96 if (!childLayout.IsReplaceFlag())
98 NUIApplication.GetDefaultWindow().LayoutController.AddToRemovalStack(childLayout);
101 childLayout.ConditionForAnimation = childLayout.ConditionForAnimation | TransitionCondition.Remove;
102 // Add LayoutItem to the transition stack so can animate it out.
103 NUIApplication.GetDefaultWindow().LayoutController.AddTransitionDataEntry(new LayoutData(layoutItem, ConditionForAnimation, 0, 0, 0, 0));
107 if(childLayout.Owner != null)
109 View parent = childLayout.Owner.GetParent() as View;
113 parent.RemoveChild(childLayout.Owner);
117 NUIApplication.GetDefaultWindow().Remove(childLayout.Owner);
122 // Reset condition for animation ready for next transition when required.
123 // SetFrame usually would do this but this LayoutItem is being removed.
124 childLayout.ConditionForAnimation = TransitionCondition.Unspecified;
131 // If child removed then set all siblings not being added to a ChangeOnRemove transition.
132 SetConditionsForAnimationOnLayoutGroup(TransitionCondition.ChangeOnRemove);
140 /// Sets the sibling order of the layout item so the layout can be defined within the same parent.
142 /// <param name="order">the sibling order of the layout item</param>
143 /// <since_tizen> 6 </since_tizen>
144 /// This will be public opened in tizen_next after ACR done. Before ACR, need to be hidden as inhouse API.
145 [EditorBrowsable(EditorBrowsableState.Never)]
146 public void ChangeLayoutSiblingOrder(int order)
148 if(Owner != null && Owner.Parent != null)
150 LayoutGroup parent = Owner.Parent.Layout as LayoutGroup;
152 if(parent != null && parent.LayoutChildren.Count>order)
154 parent.LayoutChildren.Remove(this);
155 parent.LayoutChildren.Insert(order,this);
162 // Attaches to View ChildAdded signal so called when a View is added to a view.
163 private void AddChildToLayoutGroup(View child)
165 // Only give children a layout if their parent is an explicit container or a pure View.
166 // Pure View meaning not derived from a View, e.g a Legacy container.
167 // layoutSet flag is true when the View became a layout using the set Layout API opposed to automatically due to it's parent.
168 // First time the set Layout API is used by any View the Window no longer has layoutingDisabled.
170 // If child already has a Layout then don't change it.
171 if (! View.layoutingDisabled && (null == child.Layout))
173 // Only wrap View with a Layout if a child a pure View or Layout explicitly set on this Layout
174 if ((true == Owner.layoutSet || GetType() == typeof(View)))
176 // If child of this layout is a pure View then assign it a LayoutGroup
177 // If the child is derived from a View then it may be a legacy or existing container hence will do layouting itself.
178 if (child.GetType() == typeof(View))
180 child.Layout = new LayoutGroup();
184 // Adding child as a leaf, layouting will not propagate past this child.
185 // Legacy containers will be a LayoutItems too and layout their children how they wish.
186 child.Layout = new LayoutItem();
192 // Add child layout to this LayoutGroup (Setting parent in the process)
193 if(child.Layout != null)
198 // Parent transitions are not attached to children.
202 /// If the child has a layout then it is removed from the parent layout.
204 /// <param name="child">Child View to remove.</param>
205 internal void RemoveChildFromLayoutGroup(View child)
207 Debug.Assert(child.Layout !=null);
208 Remove(child.Layout);
212 /// Set all children in a LayoutGroup to the supplied condition.
213 /// Children with Add or Remove conditions should not be changed.
215 private void SetConditionsForAnimationOnLayoutGroup( TransitionCondition conditionToSet)
217 foreach( LayoutItem childLayout in LayoutChildren )
219 switch( conditionToSet )
221 case TransitionCondition.ChangeOnAdd :
223 // If other children also being added (TransitionCondition.Add) then do not change their
224 // conditions, Continue to use their Add transitions.
225 if (childLayout.ConditionForAnimation.HasFlag(TransitionCondition.Add))
227 break; // Child being Added so don't update it's condition
231 // Set siblings for the child being added to use the ChangeOnAdd transition.
232 childLayout.ConditionForAnimation = TransitionCondition.ChangeOnAdd;
236 case TransitionCondition.ChangeOnRemove :
238 if (childLayout.ConditionForAnimation.HasFlag(TransitionCondition.Remove))
240 break; // Child being Removed so don't update it's condition
244 childLayout.ConditionForAnimation = TransitionCondition.ChangeOnRemove;
248 case TransitionCondition.LayoutChanged :
250 childLayout.ConditionForAnimation = TransitionCondition.LayoutChanged;
259 /// Callback for View.ChildAdded event
261 /// <param name="sender">The object triggering the event.</param>
262 /// <param name="childAddedEvent">Arguments from the event.</param>
263 void OnChildAddedToOwner(object sender, View.ChildAddedEventArgs childAddedEvent)
265 AddChildToLayoutGroup(childAddedEvent.Added);
269 /// Callback for View.ChildRemoved event
271 /// <param name="sender">The object triggering the event.</param>
272 /// <param name="childRemovedEvent">Arguments from the event.</param>
273 void OnChildRemovedFromOwner(object sender, View.ChildRemovedEventArgs childRemovedEvent)
275 RemoveChildFromLayoutGroup(childRemovedEvent.Removed);
279 /// Calculate the right measure spec for this child.
280 /// Does the hard part of MeasureChildren: figuring out the MeasureSpec to
281 /// pass to a particular child. This method figures out the right MeasureSpec
282 /// for one dimension (height or width) of one child view.<br />
284 /// <param name="parentMeasureSpec">The requirements for this view. MeasureSpecification.</param>
285 /// <param name="padding">The padding of this view for the current dimension and margins, if applicable. LayoutLength.</param>
286 /// <param name="childDimension"> How big the child wants to be in the current dimension. LayoutLength.</param>
287 /// <returns>a MeasureSpec for the child.</returns>
288 public static MeasureSpecification GetChildMeasureSpecification(MeasureSpecification parentMeasureSpec, LayoutLength padding, LayoutLength childDimension)
290 MeasureSpecification.ModeType specMode = parentMeasureSpec.Mode;
291 MeasureSpecification.ModeType resultMode = MeasureSpecification.ModeType.Unspecified;
293 // Child only can use parent's size without parent's padding and own margin.
294 LayoutLength resultSize = new LayoutLength(Math.Max( 0.0f, parentMeasureSpec.Size.AsDecimal()));
297 // Parent has imposed an exact size on us
298 case MeasureSpecification.ModeType.Exactly:
300 if ((int)childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent)
302 // Child wants to be our size. So be it.
303 resultMode = MeasureSpecification.ModeType.Exactly;
305 else if ((int)childDimension.AsRoundedValue() == LayoutParamPolicies.WrapContent)
307 // Child wants to determine its own size. It can't be
309 // Don't need parent's size. Size of this child will be determined by its children.
310 resultMode = MeasureSpecification.ModeType.AtMost;
314 // Child has its own size.
315 resultSize = childDimension;
316 resultMode = MeasureSpecification.ModeType.Exactly;
322 // Parent has imposed a maximum size on us
323 case MeasureSpecification.ModeType.AtMost:
325 if (childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent)
327 // Crashed. Cannot calculate.
329 // Child wants to be our size, but our size is not fixed.
330 // Constrain child to not be bigger than us.
331 resultMode = MeasureSpecification.ModeType.AtMost;
333 else if (childDimension.AsRoundedValue() == LayoutParamPolicies.WrapContent)
335 // Child wants to determine its own size. It can't be
338 // Don't need parent's size. Size of this child will be determined by its children.
339 resultMode = MeasureSpecification.ModeType.AtMost;
343 // Child wants a specific size... so be it
344 resultSize = childDimension;
345 resultMode = MeasureSpecification.ModeType.Exactly;
351 // Parent asked to see how big we want to be
352 case MeasureSpecification.ModeType.Unspecified:
354 if ((childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent))
356 // Child wants to be our size... find out how big it should be
358 // There is no one who has exact size in parent hierarchy.
360 resultMode = MeasureSpecification.ModeType.Unspecified;
362 else if (childDimension.AsRoundedValue() == (LayoutParamPolicies.WrapContent))
364 // Child wants to determine its own size.... find out how big
366 resultMode = MeasureSpecification.ModeType.Unspecified;
370 // Child wants a specific size... let him have it
371 resultSize = childDimension;
372 resultMode = MeasureSpecification.ModeType.Exactly;
378 return new MeasureSpecification( resultSize, resultMode );
382 /// Measure the layout and its content to determine the measured width and the measured height.<br />
383 /// If this method is overridden, it is the subclass's responsibility to make
384 /// sure the measured height and width are at least the layout's minimum height
385 /// and width. <br />
387 /// <param name="widthMeasureSpec">horizontal space requirements as imposed by the parent.</param>
388 /// <param name="heightMeasureSpec">vertical space requirements as imposed by the parent.</param>
389 /// <since_tizen> 6 </since_tizen>
390 protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
392 LayoutLength measuredWidth = new LayoutLength(0.0f);
393 LayoutLength measuredHeight = new LayoutLength(0.0f);
395 // Layout takes size of largest child width and largest child height dimensions
396 foreach( LayoutItem childLayout in LayoutChildren )
398 if( childLayout != null )
400 MeasureChild( childLayout, widthMeasureSpec, heightMeasureSpec );
401 LayoutLength childWidth = new LayoutLength(childLayout.MeasuredWidth.Size);
402 LayoutLength childHeight = new LayoutLength( childLayout.MeasuredHeight.Size);
404 Extents childMargin = childLayout.Margin;
405 measuredWidth = new LayoutLength(Math.Max( measuredWidth.AsDecimal(), childWidth.AsDecimal() + childMargin.Start + childMargin.End));
406 measuredHeight = new LayoutLength(Math.Max( measuredHeight.AsDecimal(), childHeight.AsDecimal() + childMargin.Top + childMargin.Bottom));
410 if( 0 == LayoutChildren.Count )
412 // Must be a leaf as has no children
413 measuredWidth = GetDefaultSize( SuggestedMinimumWidth, widthMeasureSpec );
414 measuredHeight = GetDefaultSize( SuggestedMinimumHeight, heightMeasureSpec );
417 SetMeasuredDimensions( new MeasuredSize( measuredWidth, MeasuredSize.StateType.MeasuredSizeOK ),
418 new MeasuredSize( measuredHeight, MeasuredSize.StateType.MeasuredSizeOK ) );
422 /// Called from Layout() when this layout should assign a size and position to each of its children.<br />
423 /// Derived classes with children should override this method and call Layout() on each of their children.<br />
425 /// <param name="changed">This is a new size or position for this layout.</param>
426 /// <param name="left">Left position, relative to parent.</param>
427 /// <param name="top"> Top position, relative to parent.</param>
428 /// <param name="right">Right position, relative to parent.</param>
429 /// <param name="bottom">Bottom position, relative to parent.</param>
430 /// <since_tizen> 6 </since_tizen>
431 protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
433 foreach( LayoutItem childLayout in LayoutChildren )
435 if( childLayout !=null )
437 // Use position if explicitly set to child otherwise will be top left.
438 var childLeft = new LayoutLength( childLayout.Owner.Position2D.X );
439 var childTop = new LayoutLength( childLayout.Owner.Position2D.Y );
445 // Margin and Padding only supported when child anchor point is TOP_LEFT.
446 if ( owner.PivotPoint == PivotPoint.TopLeft || ( owner.PositionUsesPivotPoint == false ) )
448 childLeft = childLeft + owner.Padding.Start + childLayout.Margin.Start;
449 childTop = childTop + owner.Padding.Top + childLayout.Margin.Top;
452 childLayout.Layout( childLeft, childTop, childLeft + childLayout.MeasuredWidth.Size, childTop + childLayout.MeasuredHeight.Size );
458 /// Overridden method called when the layout is attached to an owner.<br />
460 /// <since_tizen> 6 </since_tizen>
461 protected override void OnAttachedToOwner()
463 // Layout takes ownership of it's owner's children.
464 foreach (View view in Owner.Children)
466 AddChildToLayoutGroup(view);
469 // Connect to owner ChildAdded signal.
470 Owner.ChildAdded += OnChildAddedToOwner;
472 // Removing Child from the owners View will directly call the LayoutGroup removal API.
475 // Virtual Methods that can be overridden by derived classes.
478 /// Callback when child is added to container.<br />
479 /// Derived classes can use this to set their own child properties on the child layout's owner.<br />
481 /// <param name="child">The Layout child.</param>
482 /// <since_tizen> 6 </since_tizen>
483 protected virtual void OnChildAdd(LayoutItem child)
488 /// Callback when child is removed from container.<br />
490 /// <param name="child">The Layout child.</param>
491 /// <since_tizen> 6 </since_tizen>
492 protected virtual void OnChildRemove(LayoutItem child)
497 /// Ask all of the children of this view to measure themselves, taking into
498 /// account both the MeasureSpec requirements for this view and its padding.<br />
499 /// The heavy lifting is done in GetChildMeasureSpec.<br />
501 /// <param name="widthMeasureSpec">The width requirements for this view.</param>
502 /// <param name="heightMeasureSpec">The height requirements for this view.</param>
503 /// <since_tizen> 6 </since_tizen>
504 protected virtual void MeasureChildren(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
506 foreach( LayoutItem childLayout in LayoutChildren )
508 MeasureChild( childLayout, widthMeasureSpec, heightMeasureSpec );
513 /// Ask one of the children of this view to measure itself, taking into
514 /// account both the MeasureSpec requirements for this view and its padding.<br />
515 /// The heavy lifting is done in GetChildMeasureSpecification.<br />
517 /// <param name="child">The child to measure.</param>
518 /// <param name="parentWidthMeasureSpec">The width requirements for this view.</param>
519 /// <param name="parentHeightMeasureSpec">The height requirements for this view.</param>
520 /// <since_tizen> 6 </since_tizen>
521 protected virtual void MeasureChild(LayoutItem child, MeasureSpecification parentWidthMeasureSpec, MeasureSpecification parentHeightMeasureSpec)
523 View childOwner = child.Owner;
525 MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification(
526 new MeasureSpecification(
527 new LayoutLength(parentWidthMeasureSpec.Size - (Padding.Start + Padding.End + childOwner.Margin.Start + childOwner.Margin.End)),
528 parentWidthMeasureSpec.Mode),
529 new LayoutLength(Padding.Start + Padding.End ),
530 new LayoutLength(childOwner.WidthSpecification) );
532 MeasureSpecification childHeightMeasureSpec = GetChildMeasureSpecification(
533 new MeasureSpecification(
534 new LayoutLength(parentHeightMeasureSpec.Size - (Padding.Top + Padding.Bottom + childOwner.Margin.Top + childOwner.Margin.Bottom)),
535 parentHeightMeasureSpec.Mode),
536 new LayoutLength(Padding.Top + Padding.Bottom),
537 new LayoutLength(childOwner.HeightSpecification));
539 child.Measure( childWidthMeasureSpec, childHeightMeasureSpec );
543 /// Ask one of the children of this view to measure itself, taking into
544 /// account both the MeasureSpec requirements for this view and its padding.<br />
545 /// and margins. The child must have MarginLayoutParams The heavy lifting is
546 /// done in GetChildMeasureSpecification.<br />
548 /// <param name="child">The child to measure.</param>
549 /// <param name="parentWidthMeasureSpec">The width requirements for this view.</param>
550 /// <param name="widthUsed">Extra space that has been used up by the parent horizontally (possibly by other children of the parent).</param>
551 /// <param name="parentHeightMeasureSpec">The height requirements for this view.</param>
552 /// <param name="heightUsed">Extra space that has been used up by the parent vertically (possibly by other children of the parent).</param>
553 /// <since_tizen> 6 </since_tizen>
554 protected virtual void MeasureChildWithMargins(LayoutItem child, MeasureSpecification parentWidthMeasureSpec, LayoutLength widthUsed, MeasureSpecification parentHeightMeasureSpec, LayoutLength heightUsed)
556 View childOwner = child.Owner;
559 MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification(
560 new MeasureSpecification(
561 new LayoutLength(parentWidthMeasureSpec.Size + widthUsed - (Padding.Start + Padding.End + childOwner.Margin.Start + childOwner.Margin.End)),
562 parentWidthMeasureSpec.Mode),
563 new LayoutLength(Padding.Start + Padding.End ),
564 new LayoutLength(childOwner.WidthSpecification) );
566 MeasureSpecification childHeightMeasureSpec = GetChildMeasureSpecification(
567 new MeasureSpecification(
568 new LayoutLength(parentHeightMeasureSpec.Size + heightUsed - (Padding.Top + Padding.Bottom + childOwner.Margin.Top + childOwner.Margin.Bottom)),
569 parentHeightMeasureSpec.Mode),
570 new LayoutLength(Padding.Top + Padding.Bottom),
571 new LayoutLength(childOwner.HeightSpecification));
572 child.Measure( childWidthMeasureSpec, childHeightMeasureSpec );