/* 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;
}
}
}