/* * 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. /// internal 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; public TransitionCondition ConditionForAnimation{get; set;} /// /// [Draft] The View that this Layout has been assigned to. /// public View Owner{get; set;} // Should not keep a View alive. /// /// [Draft] Is this Layout set to animate its content. /// public bool Animate{get; set;} /// /// [Draft] Margin for this LayoutItem /// public Extents Margin { get { return _margin; } set { _margin = value; RequestLayout(); } } /// /// [Draft] Padding for this LayoutItem /// public Extents Padding { get { return _padding; } set { _padding = value; RequestLayout(); } } /// /// [Draft] Constructor /// public LayoutItem() { Initialize(); } /// /// [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; Initialize(); } /// /// [Draft] Set parent to this layout. /// /// Parent to set on this Layout. public void SetParent( ILayoutParent parent) { Parent = parent as LayoutGroup; } /// /// 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(); } 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); } /// /// 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 ); // 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. 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); 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. 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. 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; } 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() { 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; 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; 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.
///
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.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.
///
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 ) { 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(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.Right ); Window.Instance.LayoutController.AddTransitionDataEntry(_layoutPositionData); // Reset condition for animation ready for next transition when required. ConditionForAnimation = TransitionCondition.Unspecified; } return changed; } } }