/* * Copyright (c) 2020 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 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; private bool parentReplacement = false; private bool setPositionByLayout = true; /// /// [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] Use transition for layouting child /// /// 6 /// This will be public opened in tizen_5.5 after ACR done. Before ACR, need to be hidden as inhouse API. [EditorBrowsable(EditorBrowsableState.Never)] public bool LayoutWithTransition { get; set; } /// /// [Draft] Set position by layouting result /// [EditorBrowsable(EditorBrowsableState.Never)] public bool SetPositionByLayout { get { return setPositionByLayout; } set { setPositionByLayout = value; if (Owner != null && Owner.ExcludeLayouting != value) { Owner.ExcludeLayouting = value; } } } /// /// [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() { LayoutWithTransition = false; _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) { Layout(left, top, right, bottom, false); } /// /// 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. /// true, if this layout is not affected by parent layout. [EditorBrowsable(EditorBrowsableState.Never)] public void Layout(LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom, bool independent) { bool changed = true; if (!independent) { changed = SetFrame(left.AsRoundedValue(), top.AsRoundedValue(), right.AsRoundedValue(), bottom.AsRoundedValue()); } else { // If height or width specification is not explicitly defined, // the size of the owner view must be reset even the ExcludeLayouting is false. if (Owner.HeightSpecification < 0 || Owner.WidthSpecification < 0) { Owner.SetSize(right.AsRoundedValue() - left.AsRoundedValue(), bottom.AsRoundedValue() - top.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; if (Parent != null) { LayoutGroup layoutGroup = Parent as LayoutGroup; if (layoutGroup != null && !layoutGroup.LayoutRequested) { layoutGroup.RequestLayout(); } } } /// /// Predicate to determine if this layout has been requested to re-layout.
///
internal bool LayoutRequested { get { return (Flags & LayoutFlags.ForceLayout) == LayoutFlags.ForceLayout; } } internal void SetReplaceFlag() { parentReplacement = true; } internal bool IsReplaceFlag() { return parentReplacement; } internal void ClearReplaceFlag() { parentReplacement = false; } /// /// 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 { float maximumWidth = Owner.MaximumSize.Width; float minimumWidth = Owner.MinimumSize.Width; float baseHeight = Owner.MaximumSize.Height > 0 ? Math.Min(Owner.MaximumSize.Height, Owner.NaturalSize.Height) : Owner.NaturalSize.Height; float baseWidth = Owner.GetWidthForHeight(baseHeight); float result = minimumWidth > 0 ? Math.Max(baseWidth, minimumWidth) : baseWidth; result = maximumWidth > 0 ? Math.Min(result, maximumWidth) : result; return new LayoutLength(result); } } /// /// 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 { float maximumHeight = Owner.MaximumSize.Height; float minimumHeight = Owner.MinimumSize.Height; float baseWidth = Owner.MaximumSize.Width > 0 ? Math.Min(Owner.MaximumSize.Width, Owner.NaturalSize.Width) : Owner.NaturalSize.Width; float baseHeight = Owner.GetHeightForWidth(baseWidth); float result = minimumHeight > 0 ? Math.Max(baseHeight, minimumHeight) : baseHeight; result = maximumHeight > 0 ? Math.Min(result, maximumHeight) : result; return new LayoutLength(result); } } /// /// 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); if (Owner.Parent != null && Owner.Parent.Layout != null && Owner.Parent.Layout.LayoutWithTransition) { NUIApplication.GetDefaultWindow().LayoutController.AddTransitionDataEntry(_layoutPositionData); } else { if (Owner.Position != null) { Owner.SetSize(right - left, bottom - top); Owner.SetPosition(left, top); } } // Reset condition for animation ready for next transition when required. ConditionForAnimation = TransitionCondition.Unspecified; } return changed; } } }