/* * Copyright (c) 2019 Samsung Electronics Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using Tizen.NUI.BaseComponents; using System.Linq; namespace Tizen.NUI { /// /// [Draft] LayoutGroup class providing container functionality. /// internal class LayoutGroup : LayoutItem, ILayoutParent { protected List _children{ get;} // Children of this LayoutGroup /// /// [Draft] Constructor /// public LayoutGroup() { _children = new List(); } /// /// [Draft] Constructor setting the owner of this LayoutGroup. /// /// Owning View of this layout, currently a View but may be extending for Windows/Layers. public LayoutGroup(View owner) : base(owner) { _children = new List(); } /// /// From ILayoutParent.
///
public virtual void Add(LayoutItem childLayout) { _children.Add(childLayout); childLayout.SetParent(this); // Child added to use a Add transition. childLayout.ConditionForAnimation = ConditionForAnimation | TransitionCondition.Add; // Child's parent sets all other children not being added to a ChangeOnAdd transition. SetConditionsForAnimationOnLayoutGroup(TransitionCondition.ChangeOnAdd); OnChildAdd(childLayout); RequestLayout(); } /// /// Remove all layout children.
///
public void RemoveAll() { foreach( LayoutItem childLayout in _children ) { childLayout.ConditionForAnimation = ConditionForAnimation | TransitionCondition.Remove; childLayout.Owner = null; } _children.Clear(); // todo ensure child LayoutItems are still not parented to this group. RequestLayout(); } /// /// From ILayoutParent /// public virtual void Remove(LayoutItem layoutItem) { bool childRemoved = false; foreach( LayoutItem childLayout in _children.ToList() ) { if( childLayout == layoutItem ) { Window.Instance.LayoutController.AddToRemovalStack(childLayout); _children.Remove(childLayout); childLayout.ConditionForAnimation = childLayout.ConditionForAnimation | TransitionCondition.Remove; // Add LayoutItem to the transition stack so can animate it out. Window.Instance.LayoutController.AddTransitionDataEntry(new LayoutData(layoutItem, ConditionForAnimation, 0,0,0,0)); // Reset condition for animation ready for next transition when required. // SetFrame usually would do this but this LayoutItem is being removed. childLayout.ConditionForAnimation = TransitionCondition.Unspecified; childRemoved = true; } } if (childRemoved) { // If child removed then set all siblings not being added to a ChangeOnRemove transition. SetConditionsForAnimationOnLayoutGroup(TransitionCondition.ChangeOnRemove); } RequestLayout(); } // Attaches to View ChildAdded signal so called when a View is added to a view. private void AddChildToLayoutGroup(View child) { // Only give children a layout if their parent is an explicit container or a pure View. // Pure View meaning not derived from a View, e.g a Legacy container. // layoutSet flag is true when the View became a layout using the set Layout API opposed to automatically due to it's parent. // First time the set Layout API is used by any View the Window no longer has layoutingDisabled. // If child already has a Layout then don't change it. if (! View.layoutingDisabled && (null == child.Layout)) { // Only wrap View with a Layout if a child a pure View or Layout explicitly set on this Layout if ((true == Owner.layoutSet || GetType() == typeof(View))) { // If child of this layout is a pure View then assign it a LayoutGroup // If the child is derived from a View then it may be a legacy or existing container hence will do layouting itself. if (child.GetType() == typeof(View)) { child.Layout = new LayoutGroup(); } else { // Adding child as a leaf, layouting will not propagate past this child. // Legacy containers will be a LayoutItems too and layout their children how they wish. child.Layout = new LayoutItem(); } } } else { // Add child layout to this LayoutGroup (Setting parent in the process) if(child.Layout != null) { Add(child.Layout); } } // Parent transitions are not attached to children. } /// /// If the child has a layout then it is removed from the parent layout. /// /// Child View to remove. internal void RemoveChildFromLayoutGroup(View child) { Debug.Assert(child.Layout !=null); Remove(child.Layout); } /// /// Set all children in a LayoutGroup to the supplied condition. /// Children with Add or Remove conditions should not be changed. /// private void SetConditionsForAnimationOnLayoutGroup( TransitionCondition conditionToSet) { foreach( LayoutItem childLayout in _children ) { switch( conditionToSet ) { case TransitionCondition.ChangeOnAdd : { // If other children also being added (TransitionCondition.Add) then do not change their // conditions, Continue to use their Add transitions. if (childLayout.ConditionForAnimation.HasFlag(TransitionCondition.Add)) { break; // Child being Added so don't update it's condition } else { // Set siblings for the child being added to use the ChangeOnAdd transition. childLayout.ConditionForAnimation = TransitionCondition.ChangeOnAdd; } break; } case TransitionCondition.ChangeOnRemove : { if (childLayout.ConditionForAnimation.HasFlag(TransitionCondition.Remove)) { break; // Child being Removed so don't update it's condition } else { childLayout.ConditionForAnimation = TransitionCondition.ChangeOnRemove; } break; } case TransitionCondition.LayoutChanged : { childLayout.ConditionForAnimation = TransitionCondition.LayoutChanged; break; } } } } /// /// Callback for View.ChildAdded event /// /// The object triggering the event. /// Arguments from the event. void OnChildAddedToOwner(object sender, View.ChildAddedEventArgs childAddedEvent) { AddChildToLayoutGroup(childAddedEvent.Added); } /// /// Callback for View.ChildRemoved event /// /// The object triggering the event. /// Arguments from the event. void OnChildRemovedFromOwner(object sender, View.ChildRemovedEventArgs childRemovedEvent) { RemoveChildFromLayoutGroup(childRemovedEvent.Removed); } /// /// Calculate the right measure spec for this child. /// Does the hard part of MeasureChildren: figuring out the MeasureSpec to /// pass to a particular child. This method figures out the right MeasureSpec /// for one dimension (height or width) of one child view.
///
/// The requirements for this view. MeasureSpecification. /// The padding of this view for the current dimension and margins, if applicable. LayoutLength. /// How big the child wants to be in the current dimension. LayoutLength. /// a MeasureSpec for the child. public static MeasureSpecification GetChildMeasureSpecification(MeasureSpecification parentMeasureSpec, LayoutLength padding, LayoutLength childDimension) { MeasureSpecification.ModeType specMode = parentMeasureSpec.Mode; MeasureSpecification.ModeType resultMode = MeasureSpecification.ModeType.Unspecified; LayoutLength resultSize = new LayoutLength(Math.Max( 0.0f, (parentMeasureSpec.Size.AsDecimal() - padding.AsDecimal() ) )); // reduce available size by the owners padding switch( specMode ) { // Parent has imposed an exact size on us case MeasureSpecification.ModeType.Exactly: { if ((int)childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent) { // Child wants to be our size. So be it. resultMode = MeasureSpecification.ModeType.Exactly; } else if ((int)childDimension.AsRoundedValue() == LayoutParamPolicies.WrapContent) { // Child wants to determine its own size. It can't be // bigger than us. resultMode = MeasureSpecification.ModeType.AtMost; } else { resultSize = childDimension; resultMode = MeasureSpecification.ModeType.Exactly; } break; } // Parent has imposed a maximum size on us case MeasureSpecification.ModeType.AtMost: { if (childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultMode = MeasureSpecification.ModeType.AtMost; } else if (childDimension.AsRoundedValue() == LayoutParamPolicies.WrapContent) { // Child wants to determine its own size. It can't be // bigger than us. resultMode = MeasureSpecification.ModeType.AtMost; } else { // Child wants a specific size... so be it resultSize = childDimension + padding; resultMode = MeasureSpecification.ModeType.Exactly; } break; } // Parent asked to see how big we want to be case MeasureSpecification.ModeType.Unspecified: { if ((childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent)) { // Child wants to be our size... find out how big it should be resultMode = MeasureSpecification.ModeType.Unspecified; } else if (childDimension.AsRoundedValue() == (LayoutParamPolicies.WrapContent)) { // Child wants to determine its own size.... find out how big // it should be resultMode = MeasureSpecification.ModeType.Unspecified; } else { // Child wants a specific size... let him have it resultSize = childDimension + padding; resultMode = MeasureSpecification.ModeType.Exactly; } break; } } // switch return new MeasureSpecification( resultSize, resultMode ); } /// /// Measure the layout and its content to determine the measured width and the measured height.
/// If this method is overridden, it is the subclass's responsibility to make /// sure the measured height and width are at least the layout's minimum height /// and width.
///
/// horizontal space requirements as imposed by the parent. /// vertical space requirements as imposed by the parent. protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec) { LayoutLength measuredWidth = new LayoutLength(0.0f); LayoutLength measuredHeight = new LayoutLength(0.0f); // Layout takes size of largest child width and largest child height dimensions foreach( LayoutItem childLayout in _children ) { if( childLayout != null ) { MeasureChild( childLayout, widthMeasureSpec, heightMeasureSpec ); LayoutLength childWidth = new LayoutLength(childLayout.MeasuredWidth.Size); LayoutLength childHeight = new LayoutLength( childLayout.MeasuredHeight.Size); Extents childMargin = childLayout.Margin; measuredWidth = new LayoutLength(Math.Max( measuredWidth.AsDecimal(), childWidth.AsDecimal() + childMargin.Start + childMargin.End)); measuredHeight = new LayoutLength(Math.Max( measuredHeight.AsDecimal(), childHeight.AsDecimal() + childMargin.Top + childMargin.Bottom)); } } if( 0 == _children.Count ) { // Must be a leaf as has no children measuredWidth = GetDefaultSize( SuggestedMinimumWidth, widthMeasureSpec ); measuredHeight = GetDefaultSize( SuggestedMinimumHeight, heightMeasureSpec ); } SetMeasuredDimensions( new MeasuredSize( measuredWidth, MeasuredSize.StateType.MeasuredSizeOK ), new MeasuredSize( measuredHeight, MeasuredSize.StateType.MeasuredSizeOK ) ); } /// /// Called from Layout() when this layout should assign a size and position to each of its children.
/// Derived classes with children should override this method and call Layout() on each of their children.
///
/// This is a new size or position for this layout. /// Left position, relative to parent. /// Top position, relative to parent. /// Right position, relative to parent. /// Bottom position, relative to parent. protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom) { foreach( LayoutItem childLayout in _children ) { if( childLayout !=null ) { // Use position if explicitly set to child otherwise will be top left. var childLeft = new LayoutLength( childLayout.Owner.Position2D.X ); var childTop = new LayoutLength( childLayout.Owner.Position2D.Y ); View owner = Owner; if ( owner ) { // Margin and Padding only supported when child anchor point is TOP_LEFT. if ( owner.PivotPoint == PivotPoint.TopLeft || ( owner.PositionUsesPivotPoint == false ) ) { childLeft = childLeft + owner.Padding.Start + childLayout.Margin.Start; childTop = childTop + owner.Padding.Top + childLayout.Margin.Top; } } childLayout.Layout( childLeft, childTop, childLeft + childLayout.MeasuredWidth.Size, childTop + childLayout.MeasuredHeight.Size ); } } } /// /// Overridden method called when the layout size changes.
///
/// The new size of the layout. /// The old size of the layout. protected override void OnSizeChanged(LayoutSize newSize, LayoutSize oldSize) { // Do nothing } /// /// Overridden method called when the layout is attached to an owner.
///
protected override void OnAttachedToOwner() { // Layout takes ownership of it's owner's children. foreach (View view in Owner.Children) { AddChildToLayoutGroup(view); } // Connect to owner ChildAdded signal. Owner.ChildAdded += OnChildAddedToOwner; // Removing Child from the owners View will directly call the LayoutGroup removal API. } // Virtual Methods that can be overridden by derived classes. /// /// Callback when child is added to container.
/// Derived classes can use this to set their own child properties on the child layout's owner.
///
/// The Layout child. protected virtual void OnChildAdd(LayoutItem child) { } /// /// Callback when child is removed from container.
///
/// The Layout child. protected virtual void OnChildRemove(LayoutItem child) { } /// /// Ask all of the children of this view to measure themselves, taking into /// account both the MeasureSpec requirements for this view and its padding.
/// The heavy lifting is done in GetChildMeasureSpec.
///
/// The width requirements for this view. /// The height requirements for this view. protected virtual void MeasureChildren(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec) { foreach( LayoutItem childLayout in _children ) { MeasureChild( childLayout, widthMeasureSpec, heightMeasureSpec ); } } /// /// Ask one of the children of this view to measure itself, taking into /// account both the MeasureSpec requirements for this view and its padding.
/// The heavy lifting is done in GetChildMeasureSpec.
///
/// The child to measure. /// The width requirements for this view. /// The height requirements for this view. protected virtual void MeasureChild(LayoutItem child, MeasureSpecification parentWidthMeasureSpec, MeasureSpecification parentHeightMeasureSpec) { View childOwner = child.Owner; Extents padding = child.Padding; // Padding of this layout's owner, not of the child being measured. MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification( parentWidthMeasureSpec, new LayoutLength(padding.Start + padding.End ), new LayoutLength(childOwner.WidthSpecification) ); MeasureSpecification childHeightMeasureSpec = GetChildMeasureSpecification( parentHeightMeasureSpec, new LayoutLength(padding.Top + padding.Bottom), new LayoutLength(childOwner.HeightSpecification) ); child.Measure( childWidthMeasureSpec, childHeightMeasureSpec ); } /// /// Ask one of the children of this view to measure itself, taking into /// account both the MeasureSpec requirements for this view and its padding.
/// and margins. The child must have MarginLayoutParams The heavy lifting is /// done in GetChildMeasureSpecification.
///
/// The child to measure. /// The width requirements for this view. /// Extra space that has been used up by the parent horizontally (possibly by other children of the parent). /// The height requirements for this view. /// Extra space that has been used up by the parent vertically (possibly by other children of the parent). protected virtual void MeasureChildWithMargins(LayoutItem child, MeasureSpecification parentWidthMeasureSpec, LayoutLength widthUsed, MeasureSpecification parentHeightMeasureSpec, LayoutLength heightUsed) { View childOwner = child.Owner; int desiredWidth = childOwner.WidthSpecification; int desiredHeight = childOwner.HeightSpecification; Extents padding = child.Padding; // Padding of this layout's owner, not of the child being measured. MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification( parentWidthMeasureSpec, new LayoutLength( padding.Start + padding.End ) + widthUsed, new LayoutLength(desiredWidth) ); MeasureSpecification childHeightMeasureSpec = GetChildMeasureSpecification( parentHeightMeasureSpec, new LayoutLength( padding.Top + padding.Bottom )+ heightUsed, new LayoutLength(desiredHeight) ); child.Measure( childWidthMeasureSpec, childHeightMeasureSpec ); } } }