/*
* 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.Diagnostics;
using Tizen.NUI.BaseComponents;
namespace Tizen.NUI
{
[FlagsAttribute]
enum LayoutFlags : short
{
None = 0,
ForceLayout = 1,
LayoutRequired = 2,
MeasuredDimensionSet = 4
};
///
/// [Draft] Base class for layouts. It is used to layout a View
/// It can be laid out by a LayoutGroup.
///
public class LayoutItem
{
static bool LayoutDebugFrameData = false; // Debug flag
private MeasureSpecification OldWidthMeasureSpec; // Store measure specification to compare against later
private MeasureSpecification OldHeightMeasureSpec;// Store measure specification to compare against later
private LayoutFlags Flags = LayoutFlags.None;
private ILayoutParent Parent;
LayoutData _layoutPositionData;
private Extents _padding;
private Extents _margin;
///
/// [Draft] Condition event that is causing this Layout to transition.
///
internal TransitionCondition ConditionForAnimation{get; set;}
///
/// [Draft] The View that this Layout has been assigned to.
///
/// 6
public View Owner{get; set;} // Should not keep a View alive.
///
/// [Draft] Margin for this LayoutItem
///
/// 6
public Extents Margin
{
get
{
return _margin;
}
set
{
_margin = value;
RequestLayout();
}
}
///
/// [Draft] Padding for this LayoutItem
///
/// 6
public Extents Padding
{
get
{
return _padding;
}
set
{
_padding = value;
RequestLayout();
}
}
///
/// [Draft] Constructor
///
/// 6
public LayoutItem()
{
Initialize();
}
///
/// [Draft] Set parent to this layout.
///
/// Parent to set on this Layout.
internal void SetParent( ILayoutParent parent)
{
Parent = parent as LayoutGroup;
}
///
/// Unparent this layout from it's owner, and remove any layout children in derived types.
///
internal void Unparent()
{
// Enable directly derived types to first remove children
OnUnparent();
// Remove myself from parent
Parent?.Remove( this );
// Remove parent reference
Parent = null;
// Lastly, clear layout from owning View.
Owner?.ResetLayout();
}
private void Initialize()
{
_layoutPositionData = new LayoutData(this,TransitionCondition.Unspecified,0,0,0,0);
_padding = new Extents(0,0,0,0);
_margin = new Extents(0,0,0,0);
}
///
/// Initialize the layout and allow derived classes to also perform any operations
///
/// Owner of this Layout.
internal void AttachToOwner(View owner)
{
// Assign the layout owner.
Owner = owner;
OnAttachedToOwner();
// Add layout to parent layout if a layout container
View parent = Owner.GetParent() as View;
(parent?.Layout as LayoutGroup)?.Add( this );
// If Add or ChangeOnAdd then do not update condition
if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
{
ConditionForAnimation = TransitionCondition.LayoutChanged;
}
}
///
/// This is called to find out how big a layout should be.
/// The parent supplies constraint information in the width and height parameters.
/// The actual measurement work of a layout is performed in OnMeasure called by this
/// method. Therefore, only OnMeasure can and must be overridden by subclasses.
///
/// Horizontal space requirements as imposed by the parent.
/// Vertical space requirements as imposed by the parent.
/// 6
public void Measure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
{
// Check if relayouting is required.
bool specChanged = (widthMeasureSpec.Size != OldWidthMeasureSpec.Size) ||
(heightMeasureSpec.Size != OldHeightMeasureSpec.Size) ||
(widthMeasureSpec.Mode != OldWidthMeasureSpec.Mode) ||
(heightMeasureSpec.Mode != OldHeightMeasureSpec.Mode);
bool isSpecExactly = (widthMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly) &&
(heightMeasureSpec.Mode == MeasureSpecification.ModeType.Exactly);
bool matchesSpecSize = (MeasuredWidth.Size == widthMeasureSpec.Size) &&
(MeasuredHeight.Size == heightMeasureSpec.Size);
bool needsLayout = specChanged && ( !isSpecExactly || !matchesSpecSize);
needsLayout = needsLayout || ((Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout);
if (needsLayout)
{
OnMeasure(widthMeasureSpec, heightMeasureSpec);
Flags = Flags | LayoutFlags.LayoutRequired;
Flags &= ~LayoutFlags.ForceLayout;
}
OldWidthMeasureSpec = widthMeasureSpec;
OldHeightMeasureSpec = heightMeasureSpec;
}
///
/// Assign a size and position to a layout and all of its descendants.
/// This is the second phase of the layout mechanism. (The first is measuring). In this phase, each parent
/// calls layout on all of its children to position them. This is typically done using the child
/// measurements that were stored in the measure pass.
///
/// Left position, relative to parent.
/// Top position, relative to parent.
/// Right position, relative to parent.
/// Bottom position, relative to parent.
/// 6
public void Layout(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
{
bool changed = SetFrame(left.AsRoundedValue(),
top.AsRoundedValue(),
right.AsRoundedValue(),
bottom.AsRoundedValue());
// Check if Measure needed before Layouting
if (changed || ((Flags & LayoutFlags.LayoutRequired) == LayoutFlags.LayoutRequired))
{
OnLayout(changed, left, top, right, bottom);
// Clear flag
Flags &= ~LayoutFlags.LayoutRequired;
}
}
///
/// Utility to return a default size.
/// Uses the supplied size if the MeasureSpecification imposed no constraints. Will get larger if allowed by the
/// MeasureSpecification.
///
/// Default size for this layout.
/// Constraints imposed by the parent.
/// The size this layout should be.
/// 6
public static LayoutLength GetDefaultSize(LayoutLength size, MeasureSpecification measureSpecification)
{
LayoutLength result = size;
MeasureSpecification.ModeType specMode = measureSpecification.Mode;
LayoutLength specSize = measureSpecification.Size;
switch (specMode)
{
case MeasureSpecification.ModeType.Unspecified:
{
result = size;
break;
}
case MeasureSpecification.ModeType.AtMost:
{
// Ensure the default size does not exceed the spec size unless the default size is 0.
// Another container could provide a default size of 0.
// Do not set size to 0, use specSize in this case as could be a legacy container
if( ( size.AsDecimal() < specSize.AsDecimal()) && ( size.AsDecimal() > 0) )
{
result = size;
}
else
{
result = specSize;
}
break;
}
case MeasureSpecification.ModeType.Exactly:
{
result = specSize;
break;
}
}
return result;
}
///
/// Get the Layouts parent
///
/// Layout parent with an LayoutParent interface
/// 6
public ILayoutParent GetParent()
{
return Parent;
}
///
/// Request that this layout is re-laid out.
/// This will make this layout and all it's parent layouts dirty.
///
/// 6
public void RequestLayout()
{
Flags = Flags | LayoutFlags.ForceLayout;
Window.Instance.LayoutController.RequestLayout(this);
}
///
/// Predicate to determine if this layout has been requested to re-layout.
///
internal bool LayoutRequested
{
get
{
return ( Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout;
}
}
///
/// Get the measured width (without any measurement flags).
/// This method should be used only during measurement and layout calculations.
///
/// 6
public MeasuredSize MeasuredWidth{ get; set; } = new MeasuredSize( new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
///
/// Get the measured height (without any measurement flags).
/// This method should be used only during measurement and layout calculations.
///
/// 6
public MeasuredSize MeasuredHeight{ get; set; } = new MeasuredSize( new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
///
/// Returns the suggested minimum width that the layout should use.
/// This returns the maximum of the layout's minimum width and the owner's natural width.
///
/// 6
public LayoutLength SuggestedMinimumWidth
{
get
{
int naturalWidth = Owner.NaturalSize2D.Width;
return new LayoutLength(Math.Max( MinimumWidth.AsDecimal(), naturalWidth ));
}
}
///
/// Returns the suggested minimum height that the layout should use.
/// This returns the maximum of the layout's minimum height and the owner's natural height.
///
/// 6
public LayoutLength SuggestedMinimumHeight
{
get
{
int naturalHeight = Owner.NaturalSize2D.Height;
return new LayoutLength(Math.Max( MinimumHeight.AsDecimal(), naturalHeight ));
}
}
///
/// Sets the minimum width of the layout.
/// It is not guaranteed the layout will be able to achieve this minimum width (for example, if its parent
/// layout constrains it with less available width).
/// 1. if the owner's View.WidthSpecification has exact value, then that value overrides the minimum size.
/// 2. If the owner's View.WidthSpecification is set to View.LayoutParamPolicies.WrapContent, then the view's width is set based on the suggested minimum width. (@see GetSuggestedMinimumWidth()).
/// 3. If the owner's View.WidthSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent width takes precedence over the minimum width.
///
internal LayoutLength MinimumWidth {get; set;}
///
/// Sets the minimum height of the layout.
/// It is not guaranteed the layout will be able to achieve this minimum height (for example, if its parent
/// layout constrains it with less available height).
/// 1. if the owner's View.HeightSpecification has exact value, then that value overrides the minimum size.
/// 2. If the owner's View.HeightSpecification is set to View.LayoutParamPolicies.WrapContent, then the view's height is set based on the suggested minimum height. (@see GetSuggestedMinimumHeight()).
/// 3. If the owner's View.HeightSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent height takes precedence over the minimum height.
///
internal LayoutLength MinimumHeight {get; set;}
///
/// Utility to reconcile a desired size and state, with constraints imposed by a MeasureSpecification.
///
/// How big the layout wants to be.
/// Constraints imposed by the parent.
/// Size information bit mask for the layout's children.
/// A measured size, which may indicate that it is too small.
/// 6
protected MeasuredSize ResolveSizeAndState( LayoutLength size, MeasureSpecification measureSpecification, MeasuredSize.StateType childMeasuredState )
{
var specMode = measureSpecification.Mode;
LayoutLength specSize = measureSpecification.Size;
MeasuredSize result = new MeasuredSize( size, childMeasuredState);
switch( specMode )
{
case MeasureSpecification.ModeType.AtMost:
{
if (specSize.AsRoundedValue() < size.AsRoundedValue())
{
result = new MeasuredSize( specSize, MeasuredSize.StateType.MeasuredSizeTooSmall);
}
break;
}
case MeasureSpecification.ModeType.Exactly:
{
result.Size = specSize;
break;
}
case MeasureSpecification.ModeType.Unspecified:
default:
{
break;
}
}
return result;
}
///
/// This method must be called by OnMeasure(MeasureSpec,MeasureSpec) to store the measured width and measured height.
///
/// The measured width of this layout.
/// The measured height of this layout.
/// 6
protected void SetMeasuredDimensions( MeasuredSize measuredWidth, MeasuredSize measuredHeight )
{
MeasuredWidth = measuredWidth;
MeasuredHeight = measuredHeight;
Flags = Flags | LayoutFlags.MeasuredDimensionSet;
}
///
/// Measure the layout and its content to determine the measured width and the
/// measured height.
/// The base class implementation of measure defaults to the background size,
/// unless a larger size is allowed by the MeasureSpec. Subclasses should
/// override to provide better measurements of their content.
/// 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 virtual void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec)
{
// GetDefaultSize will limit the MeasureSpec to the suggested minimumWidth and minimumHeight
SetMeasuredDimensions( GetDefaultSize( SuggestedMinimumWidth, widthMeasureSpec ),
GetDefaultSize( SuggestedMinimumHeight, 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 virtual void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
{
}
///
/// Virtual method to allow derived classes to remove any children before it is removed from
/// its parent.
///
/// 6
protected virtual void OnUnparent()
{
}
///
/// Virtual method called when this Layout is attached to it's owner.
/// Allows derived layouts to take ownership of child Views and connect to any Owner signals required.
///
/// 6
protected virtual void OnAttachedToOwner()
{
}
private bool SetFrame(float left, float top, float right, float bottom)
{
bool changed = false;
if ( _layoutPositionData.Left != left ||
_layoutPositionData.Right != right ||
_layoutPositionData.Top != top ||
_layoutPositionData.Bottom != bottom )
{
changed = true;
float oldWidth = _layoutPositionData.Right - _layoutPositionData.Left;
float oldHeight = _layoutPositionData.Bottom - _layoutPositionData.Top;
float newWidth = right - left;
float newHeight = bottom - top;
bool sizeChanged = ( newWidth != oldWidth ) || ( newHeight != oldHeight );
// Set condition to layout changed as currently unspecified. Add, Remove would have specified a condition.
if (ConditionForAnimation.Equals(TransitionCondition.Unspecified))
{
ConditionForAnimation = TransitionCondition.LayoutChanged;
}
// Store new layout position data
_layoutPositionData = new LayoutData(this, ConditionForAnimation, left, top, right, bottom);
Debug.WriteLineIf( LayoutDebugFrameData, "LayoutItem FramePositionData View:" + _layoutPositionData.Item.Owner.Name +
" left:" + _layoutPositionData.Left +
" top:" + _layoutPositionData.Top +
" right:" + _layoutPositionData.Right +
" bottom:" + _layoutPositionData.Bottom );
Window.Instance.LayoutController.AddTransitionDataEntry(_layoutPositionData);
// Reset condition for animation ready for next transition when required.
ConditionForAnimation = TransitionCondition.Unspecified;
}
return changed;
}
}
}