1 /* Copyright (c) 2020 Samsung Electronics Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
7 * http://www.apache.org/licenses/LICENSE-2.0
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
18 using System.Collections.Generic;
19 using System.Globalization;
21 using Tizen.NUI.BaseComponents;
22 using Tizen.NUI.Binding;
27 // Methods and Properties for calculation.
28 public partial class RelativeLayout
30 readonly static Dictionary<View, Relative> HorizontalRelativeCache = new Dictionary<View, Relative>();
31 readonly static Dictionary<View, Relative> VerticalRelativeCache = new Dictionary<View, Relative>();
32 private static readonly RelativePropertyGetters RelativeHorizontalPropertyGetters = new RelativePropertyGetters
34 RelativeCache = HorizontalRelativeCache,
35 GetTargetFrom = GetLeftTarget,
36 GetTargetTo = GetRightTarget,
37 GetRelativeOffsetFrom = GetLeftRelativeOffset,
38 GetRelativeOffsetTo = GetRightRelativeOffset,
39 GetAlignment = GetHorizontalAlignment,
40 GetFill = GetFillHorizontal,
41 GetMeasuredSize = (item) => item.MeasuredWidth.Size.AsDecimal() + item.Margin.Start + item.Margin.End
43 private static readonly RelativePropertyGetters RelativeVerticalPropertyGetters = new RelativePropertyGetters
45 RelativeCache = VerticalRelativeCache,
46 GetTargetFrom = GetTopTarget,
47 GetTargetTo = GetBottomTarget,
48 GetRelativeOffsetFrom = GetTopRelativeOffset,
49 GetRelativeOffsetTo = GetBottomRelativeOffset,
50 GetAlignment = GetVerticalAlignment,
51 GetFill = GetFillVertical,
52 GetMeasuredSize = (item) => item.MeasuredHeight.Size.AsDecimal() + item.Margin.Top + item.Margin.Bottom
55 delegate Relative GetRelative(View view, in float layoutSize);
57 private bool ValidateRelative(Relative relative, in float layoutSize, bool isFixedRelative)
59 float spaceSize = relative.spaceGeometry.Size;
60 float viewPosition = relative.viewGeometry.Position;
61 float viewSize = relative.viewGeometry.Size;
62 (float start, float end) space = (relative.spaceGeometry.Position, relative.spaceGeometry.Position + spaceSize);
64 if (spaceSize > float.Epsilon)
66 if (!isFixedRelative && spaceSize < viewSize)
69 if (space.start < 0 && space.end <= layoutSize)
71 if (viewPosition + viewSize > layoutSize)
74 else if (0 <= space.start && layoutSize < space.end)
79 else if (viewPosition < 0 || viewPosition + viewSize > layoutSize)
84 else if (space.start == 0.0)
86 if (viewPosition + viewSize > layoutSize)
89 else if (space.start == layoutSize)
94 else if (viewPosition < 0 || viewPosition + viewSize > layoutSize)
101 private bool ValidateRelatives(in float layoutSize, HashSet<View> fixedRelativeSet, GetRelative GetRelative)
103 foreach (LayoutItem childLayout in LayoutChildren.Where(item => item?.Owner != null))
105 Relative relative = GetRelative(childLayout.Owner, layoutSize);
106 if (!ValidateRelative(relative, layoutSize, fixedRelativeSet.Contains(childLayout.Owner)))
113 private HashSet<View> GetFixedRelativeSet(in float layoutSize, GetRelative GetRelative)
115 HashSet<View> ignoreSet = new HashSet<View>();
117 foreach (LayoutItem childLayout in LayoutChildren.Where(item => item?.Owner != null))
119 Relative relative = GetRelative(childLayout.Owner, layoutSize);
120 if (relative.spaceGeometry.Size > float.Epsilon
121 && relative.spaceGeometry.Size < relative.viewGeometry.Size)
123 ignoreSet.Add(childLayout.Owner);
130 private Relative GetHorizontalRelative(View view, in float layoutSize) => CalculateRelative(view, layoutSize, 0, RelativeHorizontalPropertyGetters);
132 private Relative GetVerticalRelative(View view, in float layoutSize) => CalculateRelative(view, layoutSize, 0, RelativeVerticalPropertyGetters);
134 private Relative CalculateRelative(View view, in float layoutSize, int depth, RelativePropertyGetters propertyGetters)
136 if (propertyGetters.RelativeCache.TryGetValue(view, out Relative cache))
140 if (depth > LayoutChildren.Count)
142 throw new InvalidOperationException("Circular dependency detected in RelativeLayout.");
146 float RelativeOffsetFrom = propertyGetters.GetRelativeOffsetFrom(view);
147 float RelativeOffsetTo = propertyGetters.GetRelativeOffsetTo(view);
148 float viewSize = propertyGetters.GetMeasuredSize(view.Layout);
150 if (propertyGetters.GetTargetFrom(view) is View targetFrom && targetFrom != Owner)
152 cacheFrom = CalculateRelative(targetFrom, layoutSize, depth, propertyGetters);
156 cacheFrom = new Relative() { viewGeometry = new Geometry(0, layoutSize) };
159 if (propertyGetters.GetTargetTo(view) is View targetTo && targetTo != Owner)
161 cacheTo = CalculateRelative(targetTo, layoutSize, depth, propertyGetters);
165 cacheTo = new Relative() { viewGeometry = new Geometry(0, layoutSize) };
168 float startPosition = cacheFrom.viewGeometry.Position + cacheFrom.viewGeometry.Size * RelativeOffsetFrom;
169 float endPosition = cacheTo.viewGeometry.Position + cacheTo.viewGeometry.Size * RelativeOffsetTo;
170 if (startPosition > endPosition)
171 (startPosition, endPosition) = (endPosition, startPosition);
173 float alignment = propertyGetters.GetAlignment(view).ToFloat();
174 bool fill = propertyGetters.GetFill(view);
176 Geometry viewGeometry = new Geometry(startPosition + ((endPosition - startPosition - viewSize) * alignment), viewSize);
177 Geometry spaceGeometry = new Geometry(startPosition, Math.Abs(endPosition - startPosition));
178 if (fill && spaceGeometry.Size > viewGeometry.Size)
180 viewGeometry = spaceGeometry;
183 Relative retCache = new Relative
185 viewGeometry = viewGeometry,
186 spaceGeometry = spaceGeometry
188 propertyGetters.RelativeCache.Add(view, retCache);
193 private (float, float) CalculateChildrenSize(float parentWidth, float parentHeight)
195 int horizontalPadding = Padding.Start + Padding.End;
196 int verticalPadding = Padding.Top + Padding.Bottom;
197 int minWidth = (int)SuggestedMinimumWidth.AsDecimal() - horizontalPadding;
198 int maxWidth = (int)parentWidth - verticalPadding;
199 int minHeight = (int)SuggestedMinimumHeight.AsDecimal() - verticalPadding;
200 int maxHeight = (int)parentHeight - verticalPadding;
202 if (minWidth < 0) minWidth = 0;
203 if (maxWidth < 0) maxWidth = 0;
204 if (minHeight < 0) minHeight = 0;
205 if (maxHeight < 0) maxHeight = 0;
207 // Find minimum size that satisfy all constraints
208 float BinarySearch(int min, int max, Dictionary<View, Relative> Cache, GetRelative GetRelative)
210 int current = (min != 0) ? min : (min + max) / 2;
212 HashSet<View> fixedRelativeSet = GetFixedRelativeSet(max, GetRelative);
216 if (ValidateRelatives(current, fixedRelativeSet, GetRelative))
224 current = (min + max) / 2;
231 float ChildrenWidth = BinarySearch(minWidth, maxWidth, HorizontalRelativeCache, GetHorizontalRelative) + horizontalPadding;
232 float ChildrenHeight = BinarySearch(minHeight, maxHeight, VerticalRelativeCache, GetVerticalRelative) + verticalPadding;
233 return (ChildrenWidth, ChildrenHeight);
236 private Geometry GetHorizontalLayout(View view) => GetHorizontalRelative(view, MeasuredWidth.Size.AsDecimal() - (Padding.Start + Padding.End)).viewGeometry;
238 private Geometry GetVerticalLayout(View view) => GetVerticalRelative(view, MeasuredHeight.Size.AsDecimal() - (Padding.Top + Padding.Bottom)).viewGeometry;
240 private struct Geometry
242 public float Position { get; }
243 public float Size { get; }
245 public Geometry(float position, float size)
252 private struct Relative
254 public Geometry viewGeometry;
255 public Geometry spaceGeometry;
257 private struct RelativePropertyGetters
259 public Dictionary<View, Relative> RelativeCache;
260 public Func<BindableObject, View> GetTargetFrom;
261 public Func<BindableObject, View> GetTargetTo;
262 public Func<View, float> GetRelativeOffsetFrom;
263 public Func<View, float> GetRelativeOffsetTo;
264 public Func<View, Alignment> GetAlignment;
265 public Func<View, bool> GetFill;
266 public Func<LayoutItem, float> GetMeasuredSize;
269 class RelativeTargetConverter : TypeConverter, IExtendedTypeConverter
271 object IExtendedTypeConverter.ConvertFromInvariantString(string value, IServiceProvider serviceProvider)
273 if (serviceProvider == null)
274 throw new ArgumentNullException(nameof(serviceProvider));
276 object target = null;
278 if (serviceProvider.GetService(typeof(IReferenceProvider)) is IReferenceProvider referenceProvider)
279 target = referenceProvider.FindByName(value);
281 return target ?? throw new ArgumentException($"Can't resolve name '{value}' on Element", nameof(value));
284 public override object ConvertFromInvariantString(string value) => throw new NotImplementedException();
286 public object ConvertFrom(CultureInfo culture, object value, IServiceProvider serviceProvider) => throw new NotImplementedException();