/* 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 Tizen.NUI.BaseComponents; using Tizen.NUI.Binding; namespace Tizen.NUI { /// /// RelativeLayout calculates the size and position of all the children based on their relationship to each other. /// /// 9 public partial class RelativeLayout : LayoutGroup { /// /// LeftTargetProperty /// [EditorBrowsable(EditorBrowsableState.Never)] public static readonly BindableProperty LeftTargetProperty = BindableProperty.CreateAttached("LeftTarget", typeof(View), typeof(RelativeLayout), null, propertyChanged: OnChildPropertyChanged); /// /// RightTargetProperty /// [EditorBrowsable(EditorBrowsableState.Never)] public static readonly BindableProperty RightTargetProperty = BindableProperty.CreateAttached("RightTarget", typeof(View), typeof(RelativeLayout), null, propertyChanged: OnChildPropertyChanged); /// /// TopTargetProperty /// [EditorBrowsable(EditorBrowsableState.Never)] public static readonly BindableProperty TopTargetProperty = BindableProperty.CreateAttached("TopTarget", typeof(View), typeof(RelativeLayout), null, propertyChanged: OnChildPropertyChanged); /// /// BottomTargetProperty /// [EditorBrowsable(EditorBrowsableState.Never)] public static readonly BindableProperty BottomTargetProperty = BindableProperty.CreateAttached("BottomTarget", typeof(View), typeof(RelativeLayout), null, propertyChanged: OnChildPropertyChanged); /// /// LeftRelativeOffsetProperty /// [EditorBrowsable(EditorBrowsableState.Never)] public static readonly BindableProperty LeftRelativeOffsetProperty = BindableProperty.CreateAttached("LeftRelativeOffset", typeof(float), typeof(RelativeLayout), 0.0f, propertyChanged: OnChildPropertyChanged); /// /// RightRelativeOffsetProperty /// [EditorBrowsable(EditorBrowsableState.Never)] public static readonly BindableProperty RightRelativeOffsetProperty = BindableProperty.CreateAttached("RightRelativeOffset", typeof(float), typeof(RelativeLayout), 1.0f, propertyChanged: OnChildPropertyChanged); /// /// TopRelativeOffsetProperty /// [EditorBrowsable(EditorBrowsableState.Never)] public static readonly BindableProperty TopRelativeOffsetProperty = BindableProperty.CreateAttached("TopRelativeOffset", typeof(float), typeof(RelativeLayout), 0.0f, propertyChanged: OnChildPropertyChanged); /// /// BottomRelativeOffsetProperty /// [EditorBrowsable(EditorBrowsableState.Never)] public static readonly BindableProperty BottomRelativeOffsetProperty = BindableProperty.CreateAttached("BottomRelativeOffset", typeof(float), typeof(RelativeLayout), 1.0f, propertyChanged: OnChildPropertyChanged); /// /// HorizontalAlignmentProperty /// [EditorBrowsable(EditorBrowsableState.Never)] public static readonly BindableProperty HorizontalAlignmentProperty = BindableProperty.CreateAttached("HorizontalAlignment", typeof(Alignment), typeof(RelativeLayout), default(Alignment), propertyChanged: OnChildPropertyChanged); /// /// VerticalAlignmentProperty /// [EditorBrowsable(EditorBrowsableState.Never)] public static readonly BindableProperty VerticalAlignmentProperty = BindableProperty.CreateAttached("VerticalAlignment", typeof(Alignment), typeof(RelativeLayout), default(Alignment), propertyChanged: OnChildPropertyChanged); /// /// FillHorizontalProperty /// [EditorBrowsable(EditorBrowsableState.Never)] public static readonly BindableProperty FillHorizontalProperty = BindableProperty.CreateAttached("FillHorizontal", typeof(bool), typeof(RelativeLayout), false, propertyChanged: OnChildPropertyChanged); /// /// FillVerticalProperty /// [EditorBrowsable(EditorBrowsableState.Never)] public static readonly BindableProperty FillVerticalProperty = BindableProperty.CreateAttached("FillVertical", typeof(bool), typeof(RelativeLayout), false, propertyChanged: OnChildPropertyChanged); /// /// Constructor /// /// 9 public RelativeLayout() { } /// /// Gets left target object whose size and position is being used as reference. /// /// The child view whose size and position is being changed. /// The object whose size and position is being used as reference. /// The cannot be null. /// 9 [Binding.TypeConverter(typeof(RelativeTargetConverter))] public static View GetLeftTarget(BindableObject view) => GetAttachedValue(view, LeftTargetProperty); /// /// Gets right target object whose size and position is being used as reference. /// /// The child view whose size and position is being changed. /// The object whose size and position is being used as reference. /// The cannot be null. /// 9 [Binding.TypeConverter(typeof(RelativeTargetConverter))] public static View GetRightTarget(BindableObject view) => GetAttachedValue(view, RightTargetProperty); /// /// Gets top target object whose size and position is being used as reference. /// /// The child view whose size and position is being changed. /// The object whose size and position is being used as reference. /// The cannot be null. /// 9 [Binding.TypeConverter(typeof(RelativeTargetConverter))] public static View GetTopTarget(BindableObject view) => GetAttachedValue(view, TopTargetProperty); /// /// Gets bottom target object whose size and position is being used as reference. /// /// The child view whose size and position is being changed. /// The object whose size and position is being used as reference. /// The cannot be null. /// 9 [Binding.TypeConverter(typeof(RelativeTargetConverter))] public static View GetBottomTarget(BindableObject view) => GetAttachedValue(view, BottomTargetProperty); /// /// Gets left relative offset. /// /// The child view whose size and position is being changed. /// The ratio between left and right of the . /// The cannot be null. /// 9 public static float GetLeftRelativeOffset(View view) => GetAttachedValue(view, LeftRelativeOffsetProperty); /// /// Gets right relative offset. /// /// The child view whose size and position is being changed. /// The ratio between left and right of the . /// The cannot be null. /// 9 public static float GetRightRelativeOffset(View view) => GetAttachedValue(view, RightRelativeOffsetProperty); /// /// Gets top relative offset. /// /// The child view whose size and position is being changed. /// The ratio between top and bottom of the . /// The cannot be null. /// 9 public static float GetTopRelativeOffset(View view) => GetAttachedValue(view, TopRelativeOffsetProperty); /// /// Gets bottom relative offset. /// /// The child view whose size and position is being changed. /// The ratio between top and bottom of the . /// The cannot be null. /// 9 public static float GetBottomRelativeOffset(View view) => GetAttachedValue(view, BottomRelativeOffsetProperty); /// /// Gets the horizontal alignment /// /// The child view. /// The horizontal alignment of . /// The cannot be null. /// 9 public static Alignment GetHorizontalAlignment(View view) => GetAttachedValue(view, HorizontalAlignmentProperty); /// /// Gets the vertical alignment /// /// The child view. /// The vertical alignment of . /// The cannot be null. /// 9 public static Alignment GetVerticalAlignment(View view) => GetAttachedValue(view, VerticalAlignmentProperty); /// /// Gets the boolean value whether child fills its horizontal space. /// /// The child view. /// True if to fill the space, false otherwise. /// The cannot be null. /// 9 public static bool GetFillHorizontal(View view) => GetAttachedValue(view, FillHorizontalProperty); /// /// Gets the boolean value whether child fills its vertical space. /// /// The child view. /// True if to fill the space, false otherwise. /// The cannot be null. /// 9 public static bool GetFillVertical(View view) => GetAttachedValue(view, FillVerticalProperty); /// /// Specifies the left side edge of the child view relative to the target view.
/// null means parent relative layout. ///
/// The child view whose size and position is being changed. /// The object whose size and position is being used as reference. /// The cannot be null. /// 9 public static void SetLeftTarget(View view, View reference) => SetAttachedValue(view, LeftTargetProperty, reference); /// /// Specifies the right side edge of the child view relative to the target view.
/// null means parent relative layout. ///
/// The child view whose size and position is being changed. /// The object whose size and position is being used as reference. /// The cannot be null. /// 9 public static void SetRightTarget(View view, View reference) => SetAttachedValue(view, RightTargetProperty, reference); /// /// Specifies the top side edge of the child view relative to the target view.
/// null means parent relative layout. ///
/// The child view whose size and position is being changed. /// The object whose size and position is being used as reference. /// The cannot be null. /// 9 public static void SetTopTarget(View view, View reference) => SetAttachedValue(view, TopTargetProperty, reference); /// /// Specifies the bottom side edge of the child view relative to the target view.
/// null means parent relative layout. ///
/// The child view whose size and position is being changed. /// The object whose size and position is being used as reference. /// The cannot be null. /// 9 public static void SetBottomTarget(View view, View reference) => SetAttachedValue(view, BottomTargetProperty, reference); /// /// Sets the relative offset for left target. /// When is 0 the left edges of the left target and are aligned.
/// When is 1 the left edge of the is aligned to the right edge of the left target. ///
/// The child view whose size and position is being changed. /// The ratio between left and right of the . /// The cannot be null. /// 9 public static void SetLeftRelativeOffset(View view, float value) => SetAttachedValue(view, LeftRelativeOffsetProperty, value); /// /// Sets the relative offset for right target. /// When is 0 the right edge of the is aligned to the left edge of the right target.
/// When is 1 the right edges of the right target and are aligned. ///
/// The child view whose size and position is being changed. /// The ratio between left and right of the . /// The cannot be null. /// 9 public static void SetRightRelativeOffset(View view, float value) => SetAttachedValue(view, RightRelativeOffsetProperty, value); /// /// Sets the relative offset for top target. /// When is 0 the top edges of the top target and are aligned.
/// When is 1 the top edge of the is aligned to the bottom edge of the top target. ///
/// The child view whose size and position is being changed. /// The ratio between left and right of the . /// The cannot be null. /// 9 public static void SetTopRelativeOffset(View view, float value) => SetAttachedValue(view, TopRelativeOffsetProperty, value); /// /// Sets the relative offset for bottom target. /// When is 0 the bottom edge of the is aligned to the top edge of the bottom target.
/// When is 1 the bottom edges of the bottom target and are aligned. ///
/// The child view whose size and position is being changed. /// The ratio between left and right of the . /// The cannot be null. /// 9 public static void SetBottomRelativeOffset(View view, float value) => SetAttachedValue(view, BottomRelativeOffsetProperty, value); /// /// Sets the horizontal alignment of this child view. /// /// The child view. /// The horizontal alignment of . /// The cannot be null. /// 9 public static void SetHorizontalAlignment(View view, Alignment value) => SetAttachedValue(view, HorizontalAlignmentProperty, value); /// /// Sets the vertical alignment of this child view. /// /// The child view. /// The vertical alignment of . /// The cannot be null. /// 9 public static void SetVerticalAlignment(View view, Alignment value) => SetAttachedValue(view, VerticalAlignmentProperty, value); /// /// Sets the boolean value whether child fills its horizontal space. /// /// The child view. /// True if to fill the space, false otherwise. /// The cannot be null. /// 9 public static void SetFillHorizontal(View view, bool value) => SetAttachedValue(view, FillHorizontalProperty, value); /// /// Sets the boolean value whether child fills its vertical space. /// /// The child view. /// True if to fill the space, false otherwise. /// The cannot be null. /// 9 public static void SetFillVertical(View view, bool value) => SetAttachedValue(view, FillVerticalProperty, value); /// /// 9 protected override void OnMeasure(MeasureSpecification widthMeasureSpec, MeasureSpecification heightMeasureSpec) { MeasuredSize.StateType childWidthState = MeasuredSize.StateType.MeasuredSizeOK; MeasuredSize.StateType childHeightState = MeasuredSize.StateType.MeasuredSizeOK; for (int i = 0; i < LayoutChildren.Count; i++) { LayoutItem childLayout = LayoutChildren[i]; if (childLayout != null) { MeasureChildWithMargins(childLayout, widthMeasureSpec, new LayoutLength(0), heightMeasureSpec, new LayoutLength(0)); if (childLayout.MeasuredWidth.State == MeasuredSize.StateType.MeasuredSizeTooSmall) { childWidthState = MeasuredSize.StateType.MeasuredSizeTooSmall; } if (childLayout.MeasuredHeight.State == MeasuredSize.StateType.MeasuredSizeTooSmall) { childHeightState = MeasuredSize.StateType.MeasuredSizeTooSmall; } } } (float childrenWidth, float childrenHeight) = CalculateChildrenSize(widthMeasureSpec.Size.AsDecimal(), heightMeasureSpec.Size.AsDecimal()); SetMeasuredDimensions(ResolveSizeAndState(new LayoutLength(childrenWidth), widthMeasureSpec, childWidthState), ResolveSizeAndState(new LayoutLength(childrenHeight), heightMeasureSpec, childHeightState)); // There are 2 cases which require to calculate children's MeasuredWidth/Height as follows. // // 1. Text with Ellipsis true // TextLabel and TextField calculate MeasuredWidth/Height to cover their text string if they have WrapContent. // This causes children's Ellipsis cannot be displayed with RelativeLayout. // To resolve the above, RelativeLayout recalculates its children's MeasuredWidth/Height based on the children's space calculated by RelativeLayout APIs. // // 2. FillHorizontal/Vertical true // If children set FillHorizontal/Vertical true, then children's MeasuredWidth/Height are not correctly alculated. // Instead, children's size and position are correctly calculated in OnLayout(). // This causes that the grand children's MeasuredWidth/Height are calculated incorrectly. // To resolve the above, RelativeLayout calculates its children's MeasuredWidth/Height based on the children's geometry calculated by RelativeLayout APIs. // // e.g. // Let parent have RelativeLayout and parent's size be 1920x1080. // Let child have WrapContent with SetFillHorizontal/Vertical true. // Let grand child have MatchParent. // Then, child's size is 1920x1080 but child's MeasuredWidth/Height is 0x0. // Then, grand child's MeasuredWidth/Height is 0x0 and size is 0x0. // // TODO: Not to do duplicate operations in OnLayout() again. bool needClearCache = false; for (int i = 0; i < LayoutChildren.Count; i++) { LayoutItem childLayout = LayoutChildren[i]; if (childLayout != null) { bool ellipsisText = false; bool needMeasuredWidth = false; bool needMeasuredHeight = false; if (((childLayout.Owner is TextLabel textLabel) && textLabel.Ellipsis) || ((childLayout.Owner is TextField textField) && textField.Ellipsis)) { ellipsisText = true; needClearCache = true; } else { if (RelativeLayout.GetFillHorizontal(childLayout.Owner)) { needMeasuredWidth = true; needClearCache = true; } if (RelativeLayout.GetFillVertical(childLayout.Owner)) { needMeasuredHeight = true; needClearCache = true; } } if ((ellipsisText == false) && (needMeasuredWidth == false) && (needMeasuredHeight == false)) { continue; } float width = childLayout.MeasuredWidth.Size.AsDecimal(); float height = childLayout.MeasuredWidth.Size.AsDecimal(); if (ellipsisText) { Geometry horizontalSpace = GetHorizontalSpace(childLayout.Owner); if ((width > horizontalSpace.Size) || ((width < horizontalSpace.Size) && RelativeLayout.GetFillVertical(childLayout.Owner))) { width = horizontalSpace.Size; } Geometry verticalSpace = GetVerticalSpace(childLayout.Owner); if ((height > verticalSpace.Size) || ((height < verticalSpace.Size) && RelativeLayout.GetFillHorizontal(childLayout.Owner))) { height = verticalSpace.Size; } } else { if (needMeasuredWidth) { Geometry horizontalGeometry = GetHorizontalLayout(childLayout.Owner); width = horizontalGeometry.Size; } if (needMeasuredHeight) { Geometry verticalGeometry = GetVerticalLayout(childLayout.Owner); height = verticalGeometry.Size; } } // Padding sizes are added because Padding sizes will be subtracted in MeasureChild(). MeasureSpecification childWidthMeasureSpec = new MeasureSpecification(new LayoutLength(width + Padding.Start + Padding.End), MeasureSpecification.ModeType.Exactly); MeasureSpecification childHeightMeasureSpec = new MeasureSpecification(new LayoutLength(height + Padding.Top + Padding.Bottom), MeasureSpecification.ModeType.Exactly); // To calculate the grand children's Measure() with the mode type Exactly, // children's Measure() is called with MatchParent if the children have WrapContent. // // i.e. // If children have Wrapcontent and the grand children have MatchParent, // then grand children's MeasuredWidth/Height do not fill the children // because the grand children's Measure() is called with the mode type AtMost. int origWidthSpecification = childLayout.Owner.WidthSpecification; int origHeightSpecification = childLayout.Owner.HeightSpecification; if (ellipsisText || needMeasuredWidth) { origWidthSpecification = childLayout.Owner.WidthSpecification; childLayout.Owner.WidthSpecification = LayoutParamPolicies.MatchParent; } if (ellipsisText || needMeasuredHeight) { origHeightSpecification = childLayout.Owner.HeightSpecification; childLayout.Owner.HeightSpecification = LayoutParamPolicies.MatchParent; } MeasureChildWithMargins(childLayout, childWidthMeasureSpec, new LayoutLength(0), childHeightMeasureSpec, new LayoutLength(0)); if (ellipsisText || needMeasuredWidth) { childLayout.Owner.WidthSpecification = origWidthSpecification; } if (ellipsisText || needMeasuredHeight) { childLayout.Owner.HeightSpecification = origHeightSpecification; } } } if (needClearCache) { HorizontalRelativeCache.Clear(); VerticalRelativeCache.Clear(); } } /// /// 9 protected override void OnLayout(bool changed, LayoutLength left, LayoutLength top, LayoutLength right, LayoutLength bottom) { for (int i = 0; i < LayoutChildren.Count; i++) { LayoutItem childLayout = LayoutChildren[i]; if (childLayout != null) { Geometry horizontalGeometry = GetHorizontalLayout(childLayout.Owner); Geometry verticalGeometry = GetVerticalLayout(childLayout.Owner); LayoutLength childLeft = new LayoutLength(horizontalGeometry.Position + Padding.Start + childLayout.Margin.Start); LayoutLength childRight = new LayoutLength(horizontalGeometry.Position + horizontalGeometry.Size + Padding.Start - childLayout.Margin.End); LayoutLength childTop = new LayoutLength(verticalGeometry.Position + Padding.Top + childLayout.Margin.Top); LayoutLength childBottom = new LayoutLength(verticalGeometry.Position + verticalGeometry.Size + Padding.Top - childLayout.Margin.Bottom); childLayout.Layout(childLeft, childTop, childRight, childBottom); } } HorizontalRelativeCache.Clear(); VerticalRelativeCache.Clear(); } /// The alignment of the relative layout child. /// 9 public enum Alignment { /// At the start of the container. /// 9 Start = 0, /// At the center of the container. /// 9 Center = 1, /// At the end of the container. /// 9 End = 2, } } // Extension Method of RelativeLayout.Alignment. internal static partial class RelativeLayoutAlignmentExtension { public static float ToFloat(this RelativeLayout.Alignment align) { return 0.5f * (float)align; } } }