From: Yeongjong Lee Date: Wed, 21 Oct 2020 06:45:02 +0000 (+0900) Subject: [NUI] introduce RelativeLayout X-Git-Tag: accepted/tizen/unified/20210219.040944~185 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=7538d6a1c2f79cf96fb08457c42e3027efddbc99;p=platform%2Fcore%2Fcsapi%2Ftizenfx.git [NUI] introduce RelativeLayout RelativeLayout calculates the size and position of all the children based on their relationship to each other. Please see confluence pages for more details. --- diff --git a/src/Tizen.NUI/src/internal/Layouting/RelativeLayout.cs b/src/Tizen.NUI/src/internal/Layouting/RelativeLayout.cs new file mode 100644 index 0000000..0efc594 --- /dev/null +++ b/src/Tizen.NUI/src/internal/Layouting/RelativeLayout.cs @@ -0,0 +1,280 @@ +/* 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.Collections.Generic; +using System.Globalization; +using System.Linq; +using Tizen.NUI.BaseComponents; +using Tizen.NUI.Binding; +using Tizen.NUI.Xaml; + +namespace Tizen.NUI +{ + // Methods and Properties for calculation. + public partial class RelativeLayout + { + readonly static Dictionary HorizontalRelativeCache = new Dictionary(); + readonly static Dictionary VerticalRelativeCache = new Dictionary(); + private static readonly RelativePropertyGetters RelativeHorizontalPropertyGetters = new RelativePropertyGetters + { + RelativeCache = HorizontalRelativeCache, + GetTargetFrom = GetLeftTarget, + GetTargetTo = GetRightTarget, + GetRelativeOffsetFrom = GetLeftRelativeOffset, + GetRelativeOffsetTo = GetRightRelativeOffset, + GetAlignment = GetHorizontalAlignment, + GetFill = GetFillHorizontal, + GetMeasuredSize = (item) => { return item.MeasuredWidth.Size.AsDecimal(); } + }; + private static readonly RelativePropertyGetters RelativeVerticalPropertyGetters = new RelativePropertyGetters + { + RelativeCache = VerticalRelativeCache, + GetTargetFrom = GetTopTarget, + GetTargetTo = GetBottomTarget, + GetRelativeOffsetFrom = GetTopRelativeOffset, + GetRelativeOffsetTo = GetBottomRelativeOffset, + GetAlignment = GetVerticalAlignment, + GetFill = GetFillVertical, + GetMeasuredSize = (item) => { return item.MeasuredHeight.Size.AsDecimal(); } + }; + + delegate Relative GetRelative(View view, in float layoutSize); + + private bool ValidateRelative(Relative relative, in float layoutSize, bool isFixedRelative) + { + float spaceSize = relative.spaceGeometry.Size; + float viewPosition = relative.viewGeometry.Position; + float viewSize = relative.viewGeometry.Size; + (float start, float end) space = (relative.spaceGeometry.Position, relative.spaceGeometry.Position + spaceSize); + + if (spaceSize > float.Epsilon) + { + if (!isFixedRelative && spaceSize < viewSize) + return false; + + if (space.start < 0 && space.end <= layoutSize) + { + if (viewPosition + viewSize > layoutSize) + return false; + } + else if (0 <= space.start && layoutSize < space.end) + { + if (viewPosition < 0) + return false; + } + else if (viewPosition < 0 || viewPosition + viewSize > layoutSize) + { + return false; + } + } + else if (space.start == 0.0) + { + if (viewPosition + viewSize > layoutSize) + return false; + } + else if (space.start == layoutSize) + { + if (viewPosition < 0) + return false; + } + else if (viewPosition < 0 || viewPosition + viewSize > layoutSize) + { + return false; + } + return true; + } + + private bool ValidateRelatives(in float layoutSize, HashSet fixedRelativeSet, GetRelative GetRelative) + { + foreach (LayoutItem childLayout in LayoutChildren.Where(item => item?.Owner != null)) + { + Relative relative = GetRelative(childLayout.Owner, layoutSize); + if (!ValidateRelative(relative, layoutSize, fixedRelativeSet.Contains(childLayout.Owner))) + return false; + } + + return true; + } + + private HashSet GetFixedRelativeSet(in float layoutSize, GetRelative GetRelative) + { + HashSet ignoreSet = new HashSet(); + + foreach (LayoutItem childLayout in LayoutChildren.Where(item => item?.Owner != null)) + { + Relative relative = GetRelative(childLayout.Owner, layoutSize); + if (relative.spaceGeometry.Size > float.Epsilon + && relative.spaceGeometry.Size < relative.viewGeometry.Size) + { + ignoreSet.Add(childLayout.Owner); + } + } + + return ignoreSet; + } + + private Relative GetHorizontalRelative(View view, in float layoutSize) => CalculateRelative(view, layoutSize, 0, RelativeHorizontalPropertyGetters); + + private Relative GetVerticalRelative(View view, in float layoutSize) => CalculateRelative(view, layoutSize, 0, RelativeVerticalPropertyGetters); + + private Relative CalculateRelative(View view, in float layoutSize, int depth, RelativePropertyGetters propertyGetters) + { + if (propertyGetters.RelativeCache.TryGetValue(view, out Relative cache)) + return cache; + + depth++; + if (depth > LayoutChildren.Count) + { + throw new InvalidOperationException("Circular dependency detected in RelativeLayout."); + } + Relative cacheFrom; + Relative cacheTo; + float RelativeOffsetFrom = propertyGetters.GetRelativeOffsetFrom(view); + float RelativeOffsetTo = propertyGetters.GetRelativeOffsetTo(view); + float viewSize = propertyGetters.GetMeasuredSize(view.Layout); + + if (propertyGetters.GetTargetFrom(view) is View targetFrom && targetFrom != Owner) + { + cacheFrom = CalculateRelative(targetFrom, layoutSize, depth, propertyGetters); + } + else + { + cacheFrom = new Relative() { viewGeometry = new Geometry(0, layoutSize) }; + } + + if (propertyGetters.GetTargetTo(view) is View targetTo && targetTo != Owner) + { + cacheTo = CalculateRelative(targetTo, layoutSize, depth, propertyGetters); + } + else + { + cacheTo = new Relative() { viewGeometry = new Geometry(0, layoutSize) }; + } + + float startPosition = cacheFrom.viewGeometry.Position + cacheFrom.viewGeometry.Size * RelativeOffsetFrom; + float endPosition = cacheTo.viewGeometry.Position + cacheTo.viewGeometry.Size * RelativeOffsetTo; + + float alignment = propertyGetters.GetAlignment(view).ToFloat(); + bool fill = propertyGetters.GetFill(view); + + Geometry viewGeometry = new Geometry(startPosition + ((endPosition - startPosition - viewSize) * alignment), viewSize); + Geometry spaceGeometry = new Geometry(startPosition, Math.Abs(endPosition - startPosition)); + if (fill && spaceGeometry.Size > viewGeometry.Size) + { + viewGeometry = spaceGeometry; + } + + Relative retCache = new Relative + { + viewGeometry = viewGeometry, + spaceGeometry = spaceGeometry + }; + propertyGetters.RelativeCache.Add(view, retCache); + + return retCache; + } + + private (float, float) CalculateChildrenSize(float parentWidth, float parentHeight) + { + int minWidth = (int)SuggestedMinimumWidth.AsDecimal(); + int maxWidth = (int)parentWidth; + int minHeight = (int)SuggestedMinimumHeight.AsDecimal(); + int maxHeight = (int)parentHeight; + + // Find minimum size that satisfy all constraints + float BinarySearch(int min, int max, Dictionary Cache, GetRelative GetRelative) + { + int current = (min != 0) ? min : (min + max) / 2; + + HashSet fixedRelativeSet = GetFixedRelativeSet(max, GetRelative); + Cache.Clear(); + do + { + if (ValidateRelatives(current, fixedRelativeSet, GetRelative)) + { + max = current; + } + else + { + min = current + 1; + } + current = (min + max) / 2; + Cache.Clear(); + } while (min < max); + + return current; + } + + float ChildrenWidth = BinarySearch(minWidth, maxWidth, HorizontalRelativeCache, GetHorizontalRelative); + float ChildrenHeight = BinarySearch(minHeight, maxHeight, VerticalRelativeCache, GetVerticalRelative); + return (ChildrenWidth, ChildrenHeight); + } + + private Geometry GetHorizontalLayout(View view) => GetHorizontalRelative(view, MeasuredWidth.Size.AsDecimal()).viewGeometry; + + private Geometry GetVerticalLayout(View view) => GetVerticalRelative(view, MeasuredHeight.Size.AsDecimal()).viewGeometry; + + private struct Geometry + { + public float Position { get; } + public float Size { get; } + + public Geometry(float position, float size) + { + Position = position; + Size = size; + } + } + + private struct Relative + { + public Geometry viewGeometry; + public Geometry spaceGeometry; + } + private struct RelativePropertyGetters + { + public Dictionary RelativeCache; + public Func GetTargetFrom; + public Func GetTargetTo; + public Func GetRelativeOffsetFrom; + public Func GetRelativeOffsetTo; + public Func GetAlignment; + public Func GetFill; + public Func GetMeasuredSize; + } + + class RelativeTargetConverter : TypeConverter, IExtendedTypeConverter + { + object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceProvider serviceProvider) + { + if (serviceProvider == null) + throw new ArgumentNullException(nameof(serviceProvider)); + + object target = null; + + if (serviceProvider.GetService(typeof(IReferenceProvider)) is IReferenceProvider referenceProvider) + target = referenceProvider.FindByName(value); + + return target ?? throw new ArgumentException($"Can't resolve name '{value}' on Element", nameof(value)); + } + + public override object ConvertFromInvariantString(string value) => throw new NotImplementedException(); + + public object ConvertFrom(CultureInfo culture, object value, IServiceProvider serviceProvider) => throw new NotImplementedException(); + } + } +} diff --git a/src/Tizen.NUI/src/public/Layouting/RelativeLayout.cs b/src/Tizen.NUI/src/public/Layouting/RelativeLayout.cs new file mode 100644 index 0000000..4b0c020 --- /dev/null +++ b/src/Tizen.NUI/src/public/Layouting/RelativeLayout.cs @@ -0,0 +1,414 @@ +/* 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. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + 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), 0.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), 0.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 + /// + [EditorBrowsable(EditorBrowsableState.Never)] + 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. + [EditorBrowsable(EditorBrowsableState.Never)] + [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. + [EditorBrowsable(EditorBrowsableState.Never)] + [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. + [EditorBrowsable(EditorBrowsableState.Never)] + [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. + [EditorBrowsable(EditorBrowsableState.Never)] + [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. + [EditorBrowsable(EditorBrowsableState.Never)] + 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. + [EditorBrowsable(EditorBrowsableState.Never)] + 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. + [EditorBrowsable(EditorBrowsableState.Never)] + 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. + [EditorBrowsable(EditorBrowsableState.Never)] + public static float GetBottomRelativeOffset(View view) => GetAttachedValue(view, BottomRelativeOffsetProperty); + + /// + /// Gets the horizontal alignment + /// + /// The child view. + /// The horizontal alignment of . + /// The cannot be null. + [EditorBrowsable(EditorBrowsableState.Never)] + public static Alignment GetHorizontalAlignment(View view) => GetAttachedValue(view, HorizontalAlignmentProperty); + + /// + /// Gets the vertical alignment + /// + /// The child view. + /// The vertical alignment of . + /// The cannot be null. + [EditorBrowsable(EditorBrowsableState.Never)] + 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. + [EditorBrowsable(EditorBrowsableState.Never)] + 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. + [EditorBrowsable(EditorBrowsableState.Never)] + 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. + [EditorBrowsable(EditorBrowsableState.Never)] + 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. + [EditorBrowsable(EditorBrowsableState.Never)] + 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. + [EditorBrowsable(EditorBrowsableState.Never)] + 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. + [EditorBrowsable(EditorBrowsableState.Never)] + 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. + [EditorBrowsable(EditorBrowsableState.Never)] + 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. + [EditorBrowsable(EditorBrowsableState.Never)] + 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. + [EditorBrowsable(EditorBrowsableState.Never)] + 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 right 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. + [EditorBrowsable(EditorBrowsableState.Never)] + 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. + [EditorBrowsable(EditorBrowsableState.Never)] + 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. + [EditorBrowsable(EditorBrowsableState.Never)] + 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. + [EditorBrowsable(EditorBrowsableState.Never)] + 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. + [EditorBrowsable(EditorBrowsableState.Never)] + public static void SetFillVertical(View view, bool value) => SetAttachedValue(view, FillVerticalProperty, value); + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + 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)); + } + + /// + [EditorBrowsable(EditorBrowsableState.Never)] + 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); + LayoutLength childRight = new LayoutLength(horizontalGeometry.Position + horizontalGeometry.Size); + LayoutLength childTop = new LayoutLength(verticalGeometry.Position); + LayoutLength childBottom = new LayoutLength(verticalGeometry.Position + verticalGeometry.Size); + + childLayout.Layout(childLeft, childTop, childRight, childBottom); + } + } + HorizontalRelativeCache.Clear(); + VerticalRelativeCache.Clear(); + } + + /// The alignment of the relative layout child. + [EditorBrowsable(EditorBrowsableState.Never)] + public enum Alignment + { + /// At the start of the container. + Start = 0, + /// At the center of the container. + Center = 1, + /// At the end of the container. + 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; + } + } +}