/*
* 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.ComponentModel;
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.
///
internal class LayoutItem
{
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;
private LayoutLength _left;
private LayoutLength _right;
private LayoutLength _top;
private LayoutLength _bottom;
private LayoutData _layoutData;
///
/// The View that this Layout has been assigned to.
///
public View Owner{get; set;} // Should not keep a View alive.
///
/// [Draft] Constructor
///
public LayoutItem()
{
}
///
/// [Draft] Constructor setting the owner of this LayoutItem.
///
/// Owning View of this layout, currently a View but may be extending for Windows/Layers.
public LayoutItem(View owner)
{
Owner = owner;
_layoutData = new LayoutData();
_left = new LayoutLength(0);
_top = new LayoutLength(0);
_right = new LayoutLength(0);
_bottom = new LayoutLength(0);
}
///
/// [Draft] Set parent to this layout.
///
/// Parent to set on this Layout.
public void SetParent( ILayoutParent parent)
{
Parent = parent as LayoutGroup;
Log.Info("NUI", "Setting Parent Layout for:" + Owner?.Name + " to (Parent):" + (parent == null ? "null":parent.ToString() ) + "\n");
}
///
/// Unparent this layout from it's owner, and remove any layout children in derived types.
///
public 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();
}
///
/// Get the View owning this LayoutItem
///
internal View GetOwner()
{
return Owner;
}
///
/// 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 );
}
///
/// 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.
internal 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);
Log.Info("NUI", "Measuring:" + Owner.Name + " needsLayout[" + needsLayout.ToString() + "]\n");
if (needsLayout)
{
OnMeasure(widthMeasureSpec, heightMeasureSpec);
Flags = Flags | LayoutFlags.LayoutRequired;
Flags &= ~LayoutFlags.ForceLayout;
}
OldWidthMeasureSpec = widthMeasureSpec;
OldHeightMeasureSpec = heightMeasureSpec;
Log.Info("NUI", "LayoutItem Measure owner:" + Owner.Name + " width:" + widthMeasureSpec.Size.AsRoundedValue() + " height:" + heightMeasureSpec.Size.AsRoundedValue() + "\n");
}
///
/// 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.
public void Layout(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
{
Log.Info("NUI", "LayoutItem Layout owner:" + Owner.Name + "\n");
bool changed = SetFrame(left, top, right, bottom);
// Check if Measure needed before Layouting
if (changed || ((Flags & LayoutFlags.LayoutRequired) == LayoutFlags.LayoutRequired))
{
Log.Info("NUI", "LayoutItem Layout Frame changed or Layout forced\n");
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.
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;
}
}
Log.Info("NUI", "DefaultSize :" + result.AsRoundedValue() + "\n");
return result;
}
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.
///
public void RequestLayout()
{
Log.Info("NUI", "RequestLayout on:" + Owner?.Name + "\n");
Flags = Flags | LayoutFlags.ForceLayout;
Window.Instance.LayoutController.RequestLayout(this);
}
///
/// Predicate to determine if this layout has been requested to re-layout.
///
public 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.
///
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.
///
public MeasuredSize MeasuredHeight{ get; set; } = new MeasuredSize( new LayoutLength(-3), MeasuredSize.StateType.MeasuredSizeOK);
///
/// Get the measured width and state.
/// This method should be used only during measurement and layout calculations.
///
public MeasuredSize MeasuredWidthAndState
{
get
{
return MeasuredWidth; // Not bitmasking State unless proven to be required.
}
}
///
/// Get the measured height and state.
/// This method should be used only during measurement and layout calculations.
///
public MeasuredSize MeasuredHeightAndState
{
get
{
return MeasuredHeight; // Not bitmasking State unless proven to be required.
}
}
///
/// 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.
///
public LayoutLength SuggestedMinimumWidth
{
get
{
int naturalWidth = Owner.NaturalSize2D.Width;
Log.Info("NUI", "NaturalWidth for: " + Owner.Name + " :" + naturalWidth +"\n");
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.
///
public LayoutLength SuggestedMinimumHeight
{
get
{
int naturalHeight = Owner.NaturalSize2D.Height;
Log.Info("NUI", "NaturalHeight for: " + Owner.Name + " :" + naturalHeight +"\n");
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.LayoutWidthSpecification has exact value, then that value overrides the minimum size.
/// 2. If the owner's View.LayoutWidthSpecification 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.LayoutWidthSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent width takes precedence over the minimum width.
///
public 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.LayoutHeightSpecification has exact value, then that value overrides the minimum size.
/// 2. If the owner's View.LayoutHeightSpecification 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.LayoutHeightSpecification is set to View.LayoutParamPolicies.MatchParent, then the parent height takes precedence over the minimum height.
///
public 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.
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.
protected void SetMeasuredDimensions( MeasuredSize measuredWidth, MeasuredSize measuredHeight )
{
Log.Info("NUI", "For " + Owner.Name + " MeasuredWidth:" + measuredWidth.Size.AsRoundedValue()
+ " MeasureHeight:" + measuredHeight.Size.AsRoundedValue() + "\n");
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.
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.
protected virtual void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
{
}
///
/// Virtual method to inform derived classes when the layout size changed.
///
/// The new size of the layout.
/// The old size of the layout.
protected virtual void OnSizeChanged(LayoutSize newSize, LayoutSize oldSize)
{
}
///
/// Virtual method to allow derived classes to remove any children before it is removed from
/// its parent.
///
public 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.
///
protected virtual void OnAttachedToOwner()
{
}
private bool SetFrame(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom)
{
bool changed = false;
if( _left != left || _right != right || _top != top || _bottom != bottom )
{
changed = true;
}
LayoutLength oldWidth = _right - _left;
LayoutLength oldHeight = _bottom - _top;
LayoutLength newWidth = right - left;
LayoutLength newHeight = bottom - top;
bool sizeChanged = ( newWidth != oldWidth ) || ( newHeight != oldHeight );
_left = left;
_top = top;
_right = right;
_bottom = bottom;
// Set actual positions of View.
Owner.SetX(_left.AsRoundedValue());
Owner.SetY(_top.AsRoundedValue());
Owner.SetSize((int)newWidth.AsRoundedValue(), (int)newHeight.AsRoundedValue());
Log.Info("NUI", "Frame set for " + Owner.Name + " to left:" + _left.AsRoundedValue() + " top:"
+ _top.AsRoundedValue() + " width: " + newWidth.AsRoundedValue() + " height: "
+ newHeight.AsRoundedValue() + "\n");
return changed;
}
}
}