--- /dev/null
+/* 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<View, Relative> HorizontalRelativeCache = new Dictionary<View, Relative>();
+ readonly static Dictionary<View, Relative> VerticalRelativeCache = new Dictionary<View, Relative>();
+ 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<View> 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<View> GetFixedRelativeSet(in float layoutSize, GetRelative GetRelative)
+ {
+ HashSet<View> ignoreSet = new HashSet<View>();
+
+ 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<View, Relative> Cache, GetRelative GetRelative)
+ {
+ int current = (min != 0) ? min : (min + max) / 2;
+
+ HashSet<View> 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<View, Relative> RelativeCache;
+ public Func<BindableObject, View> GetTargetFrom;
+ public Func<BindableObject, View> GetTargetTo;
+ public Func<View, float> GetRelativeOffsetFrom;
+ public Func<View, float> GetRelativeOffsetTo;
+ public Func<View, Alignment> GetAlignment;
+ public Func<View, bool> GetFill;
+ public Func<LayoutItem, float> 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();
+ }
+ }
+}
--- /dev/null
+/* 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
+{
+ /// <summary>
+ /// RelativeLayout calculates the size and position of all the children based on their relationship to each other.
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public partial class RelativeLayout : LayoutGroup
+ {
+ /// <summary>
+ /// LeftTargetProperty
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty LeftTargetProperty = BindableProperty.CreateAttached("LeftTarget", typeof(View), typeof(RelativeLayout), null, propertyChanged: OnChildPropertyChanged);
+
+ /// <summary>
+ /// RightTargetProperty
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty RightTargetProperty = BindableProperty.CreateAttached("RightTarget", typeof(View), typeof(RelativeLayout), null, propertyChanged: OnChildPropertyChanged);
+
+ /// <summary>
+ /// TopTargetProperty
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty TopTargetProperty = BindableProperty.CreateAttached("TopTarget", typeof(View), typeof(RelativeLayout), null, propertyChanged: OnChildPropertyChanged);
+
+ /// <summary>
+ /// BottomTargetProperty
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty BottomTargetProperty = BindableProperty.CreateAttached("BottomTarget", typeof(View), typeof(RelativeLayout), null, propertyChanged: OnChildPropertyChanged);
+
+ /// <summary>
+ /// LeftRelativeOffsetProperty
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty LeftRelativeOffsetProperty = BindableProperty.CreateAttached("LeftRelativeOffset", typeof(float), typeof(RelativeLayout), 0.0f, propertyChanged: OnChildPropertyChanged);
+
+ /// <summary>
+ /// RightRelativeOffsetProperty
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty RightRelativeOffsetProperty = BindableProperty.CreateAttached("RightRelativeOffset", typeof(float), typeof(RelativeLayout), 0.0f, propertyChanged: OnChildPropertyChanged);
+
+ /// <summary>
+ /// TopRelativeOffsetProperty
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty TopRelativeOffsetProperty = BindableProperty.CreateAttached("TopRelativeOffset", typeof(float), typeof(RelativeLayout), 0.0f, propertyChanged: OnChildPropertyChanged);
+
+ /// <summary>
+ /// BottomRelativeOffsetProperty
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty BottomRelativeOffsetProperty = BindableProperty.CreateAttached("BottomRelativeOffset", typeof(float), typeof(RelativeLayout), 0.0f, propertyChanged: OnChildPropertyChanged);
+
+ /// <summary>
+ /// HorizontalAlignmentProperty
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty HorizontalAlignmentProperty = BindableProperty.CreateAttached("HorizontalAlignment", typeof(Alignment), typeof(RelativeLayout), default(Alignment), propertyChanged: OnChildPropertyChanged);
+
+ /// <summary>
+ /// VerticalAlignmentProperty
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty VerticalAlignmentProperty = BindableProperty.CreateAttached("VerticalAlignment", typeof(Alignment), typeof(RelativeLayout), default(Alignment), propertyChanged: OnChildPropertyChanged);
+
+ /// <summary>
+ /// FillHorizontalProperty
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty FillHorizontalProperty = BindableProperty.CreateAttached("FillHorizontal", typeof(bool), typeof(RelativeLayout), false, propertyChanged: OnChildPropertyChanged);
+
+ /// <summary>
+ /// FillVerticalProperty
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static readonly BindableProperty FillVerticalProperty = BindableProperty.CreateAttached("FillVertical", typeof(bool), typeof(RelativeLayout), false, propertyChanged: OnChildPropertyChanged);
+
+ /// <summary>
+ /// Constructor
+ /// </summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public RelativeLayout() { }
+
+ /// <summary>
+ /// Gets left target object whose size and position is being used as reference.
+ /// </summary>
+ /// <param name="view">The child view whose size and position is being changed.</param>
+ /// <returns>The object whose size and position is being used as reference.</returns>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Binding.TypeConverter(typeof(RelativeTargetConverter))]
+ public static View GetLeftTarget(BindableObject view) => GetAttachedValue<View>(view, LeftTargetProperty);
+
+ /// <summary>
+ /// Gets right target object whose size and position is being used as reference.
+ /// </summary>
+ /// <param name="view">The child view whose size and position is being changed.</param>
+ /// <returns>The object whose size and position is being used as reference.</returns>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Binding.TypeConverter(typeof(RelativeTargetConverter))]
+ public static View GetRightTarget(BindableObject view) => GetAttachedValue<View>(view, RightTargetProperty);
+
+ /// <summary>
+ /// Gets top target object whose size and position is being used as reference.
+ /// </summary>
+ /// <param name="view">The child view whose size and position is being changed.</param>
+ /// <returns>The object whose size and position is being used as reference.</returns>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Binding.TypeConverter(typeof(RelativeTargetConverter))]
+ public static View GetTopTarget(BindableObject view) => GetAttachedValue<View>(view, TopTargetProperty);
+
+ /// <summary>
+ /// Gets bottom target object whose size and position is being used as reference.
+ /// </summary>
+ /// <param name="view">The child view whose size and position is being changed.</param>
+ /// <returns>The object whose size and position is being used as reference.</returns>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ [Binding.TypeConverter(typeof(RelativeTargetConverter))]
+ public static View GetBottomTarget(BindableObject view) => GetAttachedValue<View>(view, BottomTargetProperty);
+
+ /// <summary>
+ /// Gets left relative offset.
+ /// </summary>
+ /// <param name="view">The child view whose size and position is being changed.</param>
+ /// <returns>The ratio between left and right of the <seealso cref="LeftTargetProperty"/>.</returns>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static float GetLeftRelativeOffset(View view) => GetAttachedValue<float>(view, LeftRelativeOffsetProperty);
+
+ /// <summary>
+ /// Gets right relative offset.
+ /// </summary>
+ /// <param name="view">The child view whose size and position is being changed.</param>
+ /// <returns>The ratio between left and right of the <seealso cref="RightTargetProperty"/>.</returns>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static float GetRightRelativeOffset(View view) => GetAttachedValue<float>(view, RightRelativeOffsetProperty);
+
+ /// <summary>
+ /// Gets top relative offset.
+ /// </summary>
+ /// <param name="view">The child view whose size and position is being changed.</param>
+ /// <returns>The ratio between top and bottom of the <seealso cref="TopTargetProperty"/>.</returns>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static float GetTopRelativeOffset(View view) => GetAttachedValue<float>(view, TopRelativeOffsetProperty);
+
+ /// <summary>
+ /// Gets bottom relative offset.
+ /// </summary>
+ /// <param name="view">The child view whose size and position is being changed.</param>
+ /// <returns>The ratio between top and bottom of the <seealso cref="BottomTargetProperty"/>.</returns>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static float GetBottomRelativeOffset(View view) => GetAttachedValue<float>(view, BottomRelativeOffsetProperty);
+
+ /// <summary>
+ /// Gets the horizontal alignment
+ /// </summary>
+ /// <param name="view">The child view.</param>
+ /// <returns>The horizontal alignment of <paramref name="view"/>.</returns>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static Alignment GetHorizontalAlignment(View view) => GetAttachedValue<Alignment>(view, HorizontalAlignmentProperty);
+
+ /// <summary>
+ /// Gets the vertical alignment
+ /// </summary>
+ /// <param name="view">The child view.</param>
+ /// <returns>The vertical alignment of <paramref name="view"/>.</returns>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static Alignment GetVerticalAlignment(View view) => GetAttachedValue<Alignment>(view, VerticalAlignmentProperty);
+
+ /// <summary>
+ /// Gets the boolean value whether child fills its horizontal space.
+ /// </summary>
+ /// <param name="view">The child view.</param>
+ /// <returns>True if to fill the space, false otherwise.</returns>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static bool GetFillHorizontal(View view) => GetAttachedValue<bool>(view, FillHorizontalProperty);
+
+ /// <summary>
+ /// Gets the boolean value whether child fills its vertical space.
+ /// </summary>
+ /// <param name="view">The child view.</param>
+ /// <returns>True if to fill the space, false otherwise.</returns>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static bool GetFillVertical(View view) => GetAttachedValue<bool>(view, FillVerticalProperty);
+
+ /// <summary>
+ /// Specifies the left side edge of the child view relative to the target view. <br/>
+ /// null <paramref name="reference"/> means parent relative layout.
+ /// </summary>
+ /// <param name="view">The child view whose size and position is being changed.</param>
+ /// <param name="reference">The object whose size and position is being used as reference.</param>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void SetLeftTarget(View view, View reference) => SetAttachedValue(view, LeftTargetProperty, reference);
+
+ /// <summary>
+ /// Specifies the right side edge of the child view relative to the target view. <br/>
+ /// null <paramref name="reference"/> means parent relative layout.
+ /// </summary>
+ /// <param name="view">The child view whose size and position is being changed.</param>
+ /// <param name="reference">The object whose size and position is being used as reference.</param>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void SetRightTarget(View view, View reference) => SetAttachedValue(view, RightTargetProperty, reference);
+
+ /// <summary>
+ /// Specifies the top side edge of the child view relative to the target view. <br/>
+ /// null <paramref name="reference"/> means parent relative layout.
+ /// </summary>
+ /// <param name="view">The child view whose size and position is being changed.</param>
+ /// <param name="reference">The object whose size and position is being used as reference.</param>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void SetTopTarget(View view, View reference) => SetAttachedValue(view, TopTargetProperty, reference);
+
+ /// <summary>
+ /// Specifies the bottom side edge of the child view relative to the target view. <br/>
+ /// null <paramref name="reference"/> means parent relative layout.
+ /// </summary>
+ /// <param name="view">The child view whose size and position is being changed.</param>
+ /// <param name="reference">The object whose size and position is being used as reference.</param>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void SetBottomTarget(View view, View reference) => SetAttachedValue(view, BottomTargetProperty, reference);
+
+ /// <summary>
+ /// Sets the relative offset for left target.
+ /// When <paramref name="value"/> is 0 the left edges of the left target and <paramref name="view"/> are aligned.<br/>
+ /// When <paramref name="value"/> is 1 the left edge of the <paramref name="view"/> is aligned to the right edge of the left target.
+ /// </summary>
+ /// <param name="view">The child view whose size and position is being changed.</param>
+ /// <param name="value">The ratio between left and right of the <seealso cref="LeftTargetProperty"/>.</param>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void SetLeftRelativeOffset(View view, float value) => SetAttachedValue(view, LeftRelativeOffsetProperty, value);
+
+ /// <summary>
+ /// Sets the relative offset for right target.
+ /// When <paramref name="value"/> is 0 the right edge of the <paramref name="view"/> is aligned to the left edge of the right target.<br/>
+ /// When <paramref name="value"/> is 1 the right edges of the right target and <paramref name="view"/> are aligned.
+ /// </summary>
+ /// <param name="view">The child view whose size and position is being changed.</param>
+ /// <param name="value">The ratio between left and right of the <seealso cref="RightTargetProperty"/>.</param>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void SetRightRelativeOffset(View view, float value) => SetAttachedValue(view, RightRelativeOffsetProperty, value);
+
+ /// <summary>
+ /// Sets the relative offset for top target.
+ /// When <paramref name="value"/> is 0 the top edges of the top target and <paramref name="view"/> are aligned.<br/>
+ /// When <paramref name="value"/> is 1 the top edge of the <paramref name="view"/> is aligned to the bottom edge of the top target.
+ /// </summary>
+ /// <param name="view">The child view whose size and position is being changed.</param>
+ /// <param name="value">The ratio between left and right of the <seealso cref="TopTargetProperty"/>.</param>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void SetTopRelativeOffset(View view, float value) => SetAttachedValue(view, TopRelativeOffsetProperty, value);
+
+ /// <summary>
+ /// Sets the relative offset for bottom target.
+ /// When <paramref name="value"/> is 0 the bottom edge of the <paramref name="view"/> is aligned to the top edge of the right target.<br/>
+ /// When <paramref name="value"/> is 1 the bottom edges of the bottom target and <paramref name="view"/> are aligned.
+ /// </summary>
+ /// <param name="view">The child view whose size and position is being changed.</param>
+ /// <param name="value">The ratio between left and right of the <seealso cref="BottomTargetProperty"/>.</param>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void SetBottomRelativeOffset(View view, float value) => SetAttachedValue(view, BottomRelativeOffsetProperty, value);
+
+ /// <summary>
+ /// Sets the horizontal alignment of this child view.
+ /// </summary>
+ /// <param name="view">The child view.</param>
+ /// <param name="value">The horizontal alignment of <paramref name="view"/>.</param>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void SetHorizontalAlignment(View view, Alignment value) => SetAttachedValue(view, HorizontalAlignmentProperty, value);
+
+ /// <summary>
+ /// Sets the vertical alignment of this child view.
+ /// </summary>
+ /// <param name="view">The child view.</param>
+ /// <param name="value">The vertical alignment of <paramref name="view"/>.</param>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void SetVerticalAlignment(View view, Alignment value) => SetAttachedValue(view, VerticalAlignmentProperty, value);
+
+ /// <summary>
+ /// Sets the boolean value whether child fills its horizontal space.
+ /// </summary>
+ /// <param name="view">The child view.</param>
+ /// <param name="value">True if to fill the space, false otherwise.</param>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void SetFillHorizontal(View view, bool value) => SetAttachedValue(view, FillHorizontalProperty, value);
+
+ /// <summary>
+ /// Sets the boolean value whether child fills its vertical space.
+ /// </summary>
+ /// <param name="view">The child view.</param>
+ /// <param name="value">True if to fill the space, false otherwise.</param>
+ /// <exception cref="ArgumentNullException">The <paramref name="view"/> cannot be null.</exception>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static void SetFillVertical(View view, bool value) => SetAttachedValue(view, FillVerticalProperty, value);
+
+ /// <inheritdoc/>
+ [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));
+ }
+
+ /// <inheritdoc/>
+ [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();
+ }
+
+ /// <summary>The alignment of the relative layout child.</summary>
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public enum Alignment
+ {
+ /// <summary>At the start of the container.</summary>
+ Start = 0,
+ /// <summary>At the center of the container.</summary>
+ Center = 1,
+ /// <summary>At the end of the container.</summary>
+ 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;
+ }
+ }
+}