3d65eb6f9765a843c02d97460920a83825fd1ee1
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Wearable / src / internal / FishEyeLayoutManager.cs
1 /* Copyright (c) 2020 Samsung Electronics Co., Ltd.
2  *
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
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
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.
14  *
15  */
16 using System;
17 using Tizen.NUI.Components;
18 using System.Collections.Generic;
19
20 namespace Tizen.NUI.Wearable
21 {
22     /// <summary>
23     /// [Draft] This class implements a fish eye layout
24     /// </summary>
25     internal class FishEyeLayoutManager : RecycleLayoutManager
26     {
27         public int CurrentFocusedIndex { get; set; } = 0;
28         public int FocusedIndex { get; set; } = 0;
29         public FishEyeLayoutManager()
30         {
31         }
32
33         private float FindCandidatePosition(float startPosition, float scrollPosition, bool isBack)
34         {
35             // There are two ellipses which return how much scale needed.
36             // We can find candidate position by intersection of ellipse and line.
37             // Item size is always determined before calculation, so can get angle of it by height/width when orientation is vertical.
38             // Y intercept is on previous item.
39
40             double center = Math.Abs(scrollPosition);
41             double result = isBack ? center + 180.0f : center - 180.0f;
42
43             // double newFactor = Math.Pow(180,2) / CalculateXFactor(0);
44             double angle = ItemSize.Height / ItemSize.Width;
45             double intercept = (startPosition - center);
46             double constant = Math.Sqrt(Math.Pow((180 * 333), 2) / (Math.Pow(333, 2) - Math.Pow(153, 2)));
47
48             double semiMajorAxis = 333.0;
49             double centerOfEllipse = -153.0;
50             double centerOfEllipse2 = 153.0;
51
52             // Find intersection using qurdratic formula.
53             // We have two different equations because there are two different ellipse.
54             double a = Math.Pow(semiMajorAxis, 2) + Math.Pow(angle * constant, 2);
55             double b = -(intercept * Math.Pow(semiMajorAxis, 2) + centerOfEllipse * Math.Pow(angle * constant, 2));
56             double c = Math.Pow(intercept * semiMajorAxis, 2) + Math.Pow(angle * constant * centerOfEllipse, 2) - Math.Pow(angle * constant * semiMajorAxis, 2);
57             double b2 = -(intercept * Math.Pow(semiMajorAxis, 2) + centerOfEllipse2 * Math.Pow(angle * constant, 2));
58             double c2 = Math.Pow(intercept * semiMajorAxis, 2) + Math.Pow(angle * constant * centerOfEllipse2, 2) - Math.Pow(angle * constant * semiMajorAxis, 2);
59
60             double result1 = (-b + Math.Sqrt((Math.Pow(b, 2) - a * c))) / a;
61             double result2 = (-b - Math.Sqrt((Math.Pow(b, 2) - a * c))) / a;
62             double result3 = (-b2 + Math.Sqrt((Math.Pow(b2, 2) - a * c2))) / a;
63             double result4 = (-b2 - Math.Sqrt((Math.Pow(b2, 2) - a * c2))) / a;
64
65             result = isBack ? result1 : result4;
66             return (float)(center + result);
67         }
68
69
70         public override float CalculateLayoutOrientationSize()
71         {
72             return StepSize * (DataCount-1);
73         }
74
75         public override void Layout(float scrollPosition)
76         {
77             RecycleItem centerItem = Container.Children[FocusedIndex] as RecycleItem;
78             if (centerItem == null)
79             {
80                 return;
81             }
82
83             float centerItemPosition = LayoutOrientation == Orientation.Horizontal ? centerItem.Position.X : centerItem.Position.Y;
84
85             Vector2 stepRange = new Vector2(-scrollPosition - StepSize + 1.0f, -scrollPosition + StepSize - 1.0f);
86             Vector2 visibleRange = new Vector2(Math.Abs(scrollPosition) - 180, Math.Abs(scrollPosition) + 180);
87
88             if (StepSize != 0 && centerItemPosition <= stepRange.X)
89             {
90                 FocusedIndex = Math.Min(Container.Children.Count - 1, FocusedIndex + 1);
91                 centerItem = Container.Children[FocusedIndex] as RecycleItem;
92                 centerItem.Position = new Position(0.0f, Math.Abs(StepSize * (centerItem.DataIndex)));
93                 centerItem.Scale = new Vector3(1.0f, 1.0f, 1.0f);
94             }
95             else if (StepSize != 0 && centerItemPosition >= stepRange.Y)
96             {
97                 FocusedIndex = Math.Max(0, FocusedIndex - 1);
98                 centerItem = Container.Children[FocusedIndex] as RecycleItem;
99                 if (centerItem != null)
100                 {
101                     centerItem.Position = new Position(0.0f, Math.Abs(StepSize * (centerItem.DataIndex)));
102                     centerItem.Scale = new Vector3(1.0f, 1.0f, 1.0f);
103                 }
104             }
105             else
106             {
107                 float centerItemScale = CalculateScaleFactor(centerItemPosition, scrollPosition);
108                 centerItem.Scale = new Vector3(centerItemScale, centerItemScale, 1.0f);
109             }
110
111             RecycleItem prevItem = centerItem;
112
113             // Front of center item
114             for (int i = FocusedIndex - 1; i > -1; i--)
115             {
116                 RecycleItem target = Container.Children[i] as RecycleItem;
117                 if (target == null)
118                 {
119                     continue;
120                 }
121
122                 float prevItemPosition = LayoutOrientation == Orientation.Horizontal ? prevItem.Position.X : prevItem.Position.Y;
123                 float prevItemSize = (LayoutOrientation == Orientation.Horizontal ? prevItem.Size.Width : prevItem.Size.Height);
124                 float prevItemCurrentSize = (LayoutOrientation == Orientation.Horizontal ? prevItem.GetCurrentSizeFloat().Width : prevItem.GetCurrentSizeFloat().Height);
125                 prevItemSize = prevItemCurrentSize == 0 ? prevItemSize : prevItemCurrentSize;
126                 prevItemSize = prevItemSize * prevItem.Scale.X;
127
128                 float startPosition = prevItemPosition - prevItemSize / 2.0f;
129
130                 if (startPosition > visibleRange.X)
131                 {
132                     float candidatePosition = visibleRange.X;
133                     candidatePosition = FindCandidatePosition(startPosition, scrollPosition, false);
134                     target.Position = LayoutOrientation == Orientation.Horizontal ?
135                                 new Position(candidatePosition, target.Position.Y) :
136                                 new Position(target.Position.X, candidatePosition);
137
138                     float scaleFactor = CalculateScaleFactor(candidatePosition, scrollPosition);
139                     target.Scale = new Vector3(scaleFactor, scaleFactor, 1.0f);
140
141                     prevItem = target;
142                 }
143                 else
144                 {
145                     target.Scale = new Vector3(0.0f, 0.0f, 1.0f);
146                 }
147             }
148
149             prevItem = centerItem;
150
151             // Back of center item
152             for (int i = FocusedIndex + 1; i < Container.Children.Count; i++)
153             {
154                 RecycleItem target = Container.Children[i] as RecycleItem;
155                 if (target == null)
156                 {
157                     continue;
158                 }
159
160                 float prevItemPosition = LayoutOrientation == Orientation.Horizontal ? prevItem.Position.X : prevItem.Position.Y;
161                 float prevItemSize = (LayoutOrientation == Orientation.Horizontal ? prevItem.Size.Width : prevItem.Size.Height);
162                 float prevItemCurrentSize = (LayoutOrientation == Orientation.Horizontal ? prevItem.GetCurrentSizeFloat().Width : prevItem.GetCurrentSizeFloat().Height);
163                 prevItemSize = prevItemCurrentSize == 0 ? prevItemSize : prevItemCurrentSize;
164                 prevItemSize = prevItemSize * prevItem.Scale.X;
165
166                 float startPosition = prevItemPosition + prevItemSize / 2.0f;
167
168                 if (startPosition < visibleRange.Y)
169                 {
170                     float candidatePosition = visibleRange.Y;
171                     candidatePosition = FindCandidatePosition(startPosition, scrollPosition, true);
172                     target.Position = LayoutOrientation == Orientation.Horizontal ?
173                                 new Position(candidatePosition, target.Position.Y) :
174                                 new Position(target.Position.X, candidatePosition);
175
176                     float scaleFactor = CalculateScaleFactor(candidatePosition, scrollPosition);
177                     target.Scale = new Vector3(scaleFactor, scaleFactor, 1.0f);
178
179                     prevItem = target;
180                 }
181                 else
182                 {
183                     target.Scale = new Vector3(0.0f, 0.0f, 1.0f);
184                 }
185             }
186
187             if (StepSize == 0)
188             {
189                 if (LayoutOrientation == Orientation.Horizontal)
190                 {
191                     StepSize = Container.Children[0].Size.Width / 2.0f + Container.Children[1].Size.Width * Container.Children[1].Scale.X / 2.0f;
192                 }
193                 else
194                 {
195                     StepSize = Container.Children[0].Size.Height / 2.0f + Container.Children[1].Size.Height * Container.Children[1].Scale.X / 2.0f;
196                 }
197
198                 StepSize = float.IsNaN(StepSize)?0:StepSize;
199             }
200         }
201
202         public override List<RecycleItem> Recycle(float scrollPosition)
203         {
204             List<RecycleItem> result = new List<RecycleItem>();
205
206             bool isBack = scrollPosition - PrevScrollPosition < 0;
207
208             int previousFocusIndex = FocusedIndex;
209
210             if (!isBack && FocusedIndex < 6)
211             {
212                 RecycleItem target = Container.Children[Container.Children.Count - 1] as RecycleItem;
213                 if (target != null)
214                 {
215                     int previousSiblingOrder = target.SiblingOrder;
216                     target.SiblingOrder = 0;
217                     target.DataIndex = target.DataIndex - Container.Children.Count;
218                     target.Position = new Position(0, Math.Abs(scrollPosition) - 360);
219                     target.Scale = new Vector3(0, 0, 0);
220
221                     result.Add(target);
222
223                     FocusedIndex++;
224                 }
225             }
226             else if (isBack && FocusedIndex > 8)
227             {
228                 RecycleItem target = Container.Children[0] as RecycleItem;
229                 if (target != null)
230                 {
231                     int previousSiblingOrder = target.SiblingOrder;
232                     target.SiblingOrder = Container.Children.Count - 1;
233                     target.DataIndex = target.DataIndex + Container.Children.Count;
234                     target.Position = new Position(0, Math.Abs(scrollPosition) + 360);
235                     target.Scale = new Vector3(0, 0, 0);
236
237                     result.Add(target);
238
239                     FocusedIndex--;
240                 }
241             }
242
243             PrevScrollPosition = scrollPosition;
244
245             return result;
246         }
247
248         private double CalculateXFactor(double y)
249         {
250             double factor1 = Math.Pow(180, 2);
251             double factor2 = Math.Pow(333, 2);
252             double factor3 = Math.Pow((y + 153), 2);
253
254             return Math.Sqrt(factor1 - (factor1 / factor2 * factor3));
255         }
256
257         private float CalculateScaleFactor(float position, float scrollPosition)
258         {
259             float origin = Math.Abs(scrollPosition);
260             float diff = position - origin;
261
262             diff = Math.Max(diff, -180);
263             diff = Math.Min(diff, 180);
264             diff = Math.Abs(diff);
265
266             float result = (float)(CalculateXFactor(diff) / CalculateXFactor(0));
267             return result;
268         }
269
270         public override float CalculateCandidateScrollPosition(float scrollPosition)
271         {
272             int value = (int)(Math.Abs(scrollPosition) / StepSize);
273             float remain = Math.Abs(scrollPosition) % StepSize;
274
275             int newValue = remain > StepSize / 2.0f ? value + 1 : value;
276
277             CurrentFocusedIndex = newValue;
278             return -newValue * StepSize;
279         }
280     }
281 }