/*
* Copyright (c) 2021 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.Linq;
using Tizen.NUI.BaseComponents;
using Tizen.NUI.Binding.Internals;
using static Tizen.NUI.Binding.BindableObject;
namespace Tizen.NUI
{
///
/// [Draft] LayoutGroup class providing container functionality.
///
public class LayoutGroup : LayoutItem, ILayoutParent
{
///
/// [Draft] List of child layouts in this container.
///
/// 6
protected List LayoutChildren { get; } // Children of this LayoutGroup
///
/// [Draft] Constructor
///
/// 6
public LayoutGroup()
{
LayoutChildren = new List();
}
///
/// returns an enumerable collection of the child layouts that owner's is false.
///
/// An enumerable collection of the child layouts that affected by this layout.
[EditorBrowsable(EditorBrowsableState.Never)]
protected IEnumerable IterateLayoutChildren()
{
return LayoutChildren.Where(childLayout => childLayout.SetPositionByLayout);
}
///
/// From ILayoutParent.
///
/// Thrown when childLayout is null.
/// 6
/// LayoutItem to add to the layout group.
public virtual void Add(LayoutItem childLayout)
{
if (null == childLayout)
{
throw new ArgumentNullException(nameof(childLayout));
}
LayoutChildren.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.
///
/// 6
public void RemoveAll()
{
foreach (LayoutItem childLayout in LayoutChildren)
{
childLayout.ConditionForAnimation = ConditionForAnimation | TransitionCondition.Remove;
childLayout.Owner = null;
}
LayoutChildren.Clear();
// todo ensure child LayoutItems are still not parented to this group.
RequestLayout();
}
///
/// From ILayoutParent
///
/// LayoutItem to remove from the layout group.
/// 6
public virtual void Remove(LayoutItem layoutItem)
{
bool childRemoved = false;
foreach (LayoutItem childLayout in LayoutChildren.ToList())
{
if (childLayout == layoutItem)
{
childLayout.ClearReplaceFlag();
LayoutChildren.Remove(childLayout);
if (LayoutWithTransition)
{
var win = Window.Get(Owner);
if (!childLayout.IsReplaceFlag())
{
if (win == null)
{
NUIApplication.GetDefaultWindow().LayoutController.AddToRemovalStack(childLayout);
}
else
{
win.LayoutController.AddToRemovalStack(childLayout);
}
}
childLayout.ConditionForAnimation = childLayout.ConditionForAnimation | TransitionCondition.Remove;
// Add LayoutItem to the transition stack so can animate it out.
if (win == null)
{
NUIApplication.GetDefaultWindow().LayoutController.AddTransitionDataEntry(new LayoutData(layoutItem, ConditionForAnimation, 0, 0, 0, 0));
}
else
{
win.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;
break;
}
}
if (childRemoved)
{
// If child removed then set all siblings not being added to a ChangeOnRemove transition.
SetConditionsForAnimationOnLayoutGroup(TransitionCondition.ChangeOnRemove);
}
OnChildRemove(layoutItem);
RequestLayout();
}
///
/// Sets the order of the child layout in the layout group.
///
/// the child layout in the layout group
/// the order of the child layout in the layout group
[EditorBrowsable(EditorBrowsableState.Never)]
public void ChangeLayoutChildOrder(LayoutItem child, int order)
{
if ((child != null) && (LayoutChildren.Count > order))
{
LayoutChildren.Remove(child);
LayoutChildren.Insert(order, child);
RequestLayout();
}
}
// Attaches to View ChildAdded signal so called when a View is added to a view.
private void AddChildToLayoutGroup(View child)
{
if (View.LayoutingDisabled)
{
return;
}
// 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 (null == child.Layout)
{
// If child view does not have Layout, then default layout is automatically set to the child view.
// The Layout is automatically added to this LayoutGroup when child view sets Layout.
child.Layout = child.CreateDefaultLayout();
}
else
{
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)
{
if (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 LayoutChildren)
{
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;
// Child only can use parent's size without parent's padding and own margin.
LayoutLength resultSize = new LayoutLength(Math.Max(0.0f, (parentMeasureSpec.Size - padding).AsDecimal()));
switch (specMode)
{
// Parent has imposed an exact size on us
case MeasureSpecification.ModeType.Exactly:
{
if ((int)childDimension.AsRoundedValue() == LayoutParamPolicies.MatchParent)
{
resultMode = MeasureSpecification.ModeType.Exactly;
}
else if ((int)childDimension.AsRoundedValue() == LayoutParamPolicies.WrapContent)
{
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)
{
// Crashed. Cannot calculate.
// 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.
// Don't need parent's size. Size of this child will be determined by its children.
resultMode = MeasureSpecification.ModeType.AtMost;
}
else
{
// Child wants a specific size... so be it
resultSize = childDimension;
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
// There is no one who has exact size in parent hierarchy.
// Cannot calculate.
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;
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.
/// 6
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 LayoutChildren)
{
if (childLayout != null)
{
MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0));
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 == LayoutChildren.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));
}
internal override void OnMeasureIndependentChildren(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
{
foreach (var childLayout in LayoutChildren)
{
if (!childLayout.SetPositionByLayout)
{
MeasureChildWithoutPadding(childLayout, widthMeasureSpec, heightMeasureSpec);
}
}
}
///
/// 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.
/// 6
protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
{
foreach (LayoutItem childLayout in LayoutChildren)
{
if (childLayout != null)
{
// Use position if explicitly set to child otherwise will be top left.
var childLeft = new LayoutLength(childLayout.Owner.PositionX);
var childTop = new LayoutLength(childLayout.Owner.PositionY);
View owner = Owner;
if (owner != null)
{
// 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);
}
}
}
///
/// Layout independent children those Owners have true ExcludeLayouting.
/// These children are required not to be affected by this layout.
///
internal override void OnLayoutIndependentChildren(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
{
foreach (var childLayout in LayoutChildren)
{
if (!childLayout.SetPositionByLayout)
{
LayoutLength childWidth = childLayout.MeasuredWidth.Size;
LayoutLength childHeight = childLayout.MeasuredHeight.Size;
LayoutLength childPositionX = new LayoutLength(childLayout.Owner.PositionX);
LayoutLength childPositionY = new LayoutLength(childLayout.Owner.PositionY);
childLayout.Layout(childPositionX, childPositionY, childPositionX + childWidth, childPositionY + childHeight, true);
}
}
}
///
/// Overridden method called when the layout is attached to an owner.
///
/// 6
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 method to allow derived classes to remove any children before it is removed from
/// its parent.
///
[EditorBrowsable(EditorBrowsableState.Never)]
protected override void OnUnparent()
{
// Disconnect to owner ChildAdded signal.
Owner.ChildAdded -= OnChildAddedToOwner;
}
// 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.
/// 6
protected virtual void OnChildAdd(LayoutItem child)
{
}
///
/// Callback when child is removed from container.
///
/// The Layout child.
/// 6
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.
/// 6
protected virtual void MeasureChildren(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
{
foreach (LayoutItem childLayout in LayoutChildren)
{
MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0));
}
}
///
/// 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 GetChildMeasureSpecification.
///
/// The child to measure.
/// The width requirements for this view.
/// The height requirements for this view.
/// Thrown when child is null.
/// 6
protected virtual void MeasureChild(LayoutItem child, MeasureSpecification parentWidthMeasureSpec, MeasureSpecification parentHeightMeasureSpec)
{
if (null == child)
{
throw new ArgumentNullException(nameof(child));
}
View childOwner = child.Owner;
MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification(
new MeasureSpecification(new LayoutLength(parentWidthMeasureSpec.Size), parentWidthMeasureSpec.Mode),
new LayoutLength(Padding.Start + Padding.End),
new LayoutLength(childOwner.WidthSpecification));
MeasureSpecification childHeightMeasureSpec = GetChildMeasureSpecification(
new MeasureSpecification(new LayoutLength(parentHeightMeasureSpec.Size), parentHeightMeasureSpec.Mode),
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 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).
/// Thrown when child is null.
/// 6
protected virtual void MeasureChildWithMargins(LayoutItem child, MeasureSpecification parentWidthMeasureSpec, LayoutLength widthUsed, MeasureSpecification parentHeightMeasureSpec, LayoutLength heightUsed)
{
if (null == child)
{
throw new ArgumentNullException(nameof(child));
}
View childOwner = child.Owner;
Extents margin = childOwner.Margin;
MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification(
new MeasureSpecification(
new LayoutLength(parentWidthMeasureSpec.Size + widthUsed - (margin.Start + margin.End)),
parentWidthMeasureSpec.Mode),
new LayoutLength(Padding.Start + Padding.End),
new LayoutLength(childOwner.WidthSpecification));
MeasureSpecification childHeightMeasureSpec = GetChildMeasureSpecification(
new MeasureSpecification(
new LayoutLength(parentHeightMeasureSpec.Size + heightUsed - (margin.Top + margin.Bottom)),
parentHeightMeasureSpec.Mode),
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 without its padding.
/// and margins. The heavy lifting is done in GetChildMeasureSpecification.
///
/// The child to measure.
/// The width requirements for this view.
/// The height requirements for this view.
[EditorBrowsable(EditorBrowsableState.Never)]
protected void MeasureChildWithoutPadding(LayoutItem child, MeasureSpecification parentWidthMeasureSpec, MeasureSpecification parentHeightMeasureSpec)
{
View childOwner = child.Owner;
MeasureSpecification childWidthMeasureSpec = GetChildMeasureSpecification(
new MeasureSpecification(new LayoutLength(parentWidthMeasureSpec.Size), parentWidthMeasureSpec.Mode),
new LayoutLength(0),
new LayoutLength(childOwner.WidthSpecification));
MeasureSpecification childHeightMeasureSpec = GetChildMeasureSpecification(
new MeasureSpecification(new LayoutLength(parentHeightMeasureSpec.Size), parentHeightMeasureSpec.Mode),
new LayoutLength(0),
new LayoutLength(childOwner.HeightSpecification));
child.Measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
///
/// Gets the value that is contained in the attached BindableProperty.
///
/// The return type of property
/// The bindable object.
/// The BindableProperty for which to get the value.
/// The value that is contained in the attached BindableProperty.
[EditorBrowsable(EditorBrowsableState.Never)]
public static T GetAttachedValue(Binding.BindableObject bindable, Binding.BindableProperty property)
{
if (bindable == null)
throw new ArgumentNullException(nameof(bindable));
return (T)bindable.GetValue(property);
}
///
/// Sets the value of the attached property.
///
/// The bindable object.
/// The BindableProperty on which to assign a value.
/// The value to set.
[EditorBrowsable(EditorBrowsableState.Never)]
public static void SetAttachedValue(Binding.BindableObject bindable, Binding.BindableProperty property, object value)
{
if (bindable == null)
throw new ArgumentNullException(nameof(bindable));
bindable.SetValueCore(property, value, SetValueFlags.None, SetValuePrivateFlags.ManuallySet, false);
}
internal static void OnChildPropertyChanged(Binding.BindableObject bindable, object oldValue, object newValue)
{
// Unused parameters
_ = oldValue;
_ = newValue;
View view = bindable as View;
view?.Layout?.RequestLayout();
}
internal static Binding.BindableProperty.ValidateValueDelegate ValidateEnum(int enumMin, int enumMax)
{
return (Binding.BindableObject bindable, object value) =>
{
int @enum = (int)value;
return enumMin <= @enum && @enum <= enumMax;
};
}
}
}