2 * Copyright(c) 2021 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 using System.Collections.Generic;
19 using System.ComponentModel;
20 using Tizen.NUI.BaseComponents;
21 using Tizen.NUI.Components;
23 namespace Tizen.NUI.Wearable
26 /// CircularPagination shows the number of pages available and the currently active page.
27 /// Especially, CircularPagination provides indicators specific to wearable device.
29 /// <since_tizen> 8 </since_tizen>
30 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
31 [EditorBrowsable(EditorBrowsableState.Never)]
32 public class CircularPagination : Control
34 private CircularPaginationStyle circularPaginationStyle;
36 private VisualView container;
38 private List<ImageVisual> indicatorList = new List<ImageVisual>();
40 private bool isSymmetrical = true;
41 private int middleIndex = 9;
42 private int indicatorCount = 0;
43 private int leftIndicatorCount = 0;
44 private int rightIndicatorCount = 0;
45 private int selectedIndex = -1;
46 private bool isCenterImageSet = false; // When CenterIndicatorImageURL is set, this variable becomes true.
47 private bool isCurrentIndicatorCentered = false; // When the current indicator is the center one, this variable becomes true.
48 private bool isOddNumber = true;
49 private bool uninitializedLeftIndicator = true; // Need it when the indicators are asymmetry and the right indicator count is set earlier than left one.
51 Position2D[] oddArray = new Position2D[] { new Position2D(36, 74), new Position2D(47, 60), new Position2D(60, 47), new Position2D(74, 36),
52 new Position2D(89, 26), new Position2D(105, 18), new Position2D(122, 11), new Position2D(139, 7),
53 new Position2D(157, 4), new Position2D(175, 3), new Position2D(193, 4), new Position2D(211, 7),
54 new Position2D(228, 11), new Position2D(245, 18), new Position2D(261, 26), new Position2D(276, 36),
55 new Position2D(290, 47), new Position2D(303, 60), new Position2D(314, 73) };
57 Position2D[] evenArray = new Position2D[] { new Position2D(41, 67), new Position2D(53, 53), new Position2D(67, 41), new Position2D(81, 31),
58 new Position2D(97, 22), new Position2D(113, 14), new Position2D(131, 9), new Position2D(148, 5),
59 new Position2D(166, 3), new Position2D(184, 3), new Position2D(202, 5), new Position2D(220, 9),
60 new Position2D(237, 14), new Position2D(253, 22), new Position2D(269, 31), new Position2D(283, 41),
61 new Position2D(297, 53), new Position2D(309, 67) };
63 static CircularPagination()
65 ThemeManager.AddPackageTheme(DefaultThemeCreator.Instance);
69 /// Creates a new instance of a CircularPagination.
71 /// <since_tizen> 8 </since_tizen>
72 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
73 [EditorBrowsable(EditorBrowsableState.Never)]
74 public CircularPagination() : base()
80 /// Creates a new instance of a CircularPagination using style.
82 /// <since_tizen> 8 </since_tizen>
83 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
84 [EditorBrowsable(EditorBrowsableState.Never)]
85 public CircularPagination(CircularPaginationStyle style) : base(style)
91 /// Gets or sets the size of the indicator.
93 /// <since_tizen> 8 </since_tizen>
94 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
95 [EditorBrowsable(EditorBrowsableState.Never)]
96 public Size IndicatorSize
100 return circularPaginationStyle?.IndicatorSize;
104 if (value == null || circularPaginationStyle == null)
108 circularPaginationStyle.IndicatorSize = value;
114 /// Gets or sets the background resource of indicator.
116 /// <since_tizen> 8 </since_tizen>
117 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
118 [EditorBrowsable(EditorBrowsableState.Never)]
119 public Selector<string> IndicatorImageURL
123 return circularPaginationStyle?.IndicatorImageURL;
127 if (value == null || circularPaginationStyle == null)
131 circularPaginationStyle.IndicatorImageURL = value;
137 /// Gets or sets the background resource of the center indicator.
139 /// <since_tizen> 8 </since_tizen>
140 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
141 [EditorBrowsable(EditorBrowsableState.Never)]
142 public Selector<string> CenterIndicatorImageURL
146 if (isCenterImageSet)
148 return circularPaginationStyle?.CenterIndicatorImageURL;
152 Log.Info("NUI", "CenterIndicatorImageURL is not set yet. \n");
159 if (value == null || circularPaginationStyle == null)
163 circularPaginationStyle.CenterIndicatorImageURL = value;
164 isCenterImageSet = true;
170 /// Checks whether the indicators are symmetrical or not.
172 /// The default value is true.
173 /// If the value is true, the user just can set IndicatorCount.
174 /// If false, the user should set both the number of Left Indicators and the number of Right Indicators.
175 /// Please refer to LeftIndicatorCount and RightIndicatorCount.
177 /// <since_tizen> 8 </since_tizen>
178 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
179 [EditorBrowsable(EditorBrowsableState.Never)]
180 public bool IsSymmetrical
184 return isSymmetrical;
188 if (isSymmetrical == value)
195 CreateIndicator(middleIndex);
198 isSymmetrical = value;
205 /// Gets or sets the number of the pages/indicators.
207 /// This value is for symmetrical indicators.
209 /// <since_tizen> 8 </since_tizen>
210 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
211 [EditorBrowsable(EditorBrowsableState.Never)]
212 public int IndicatorCount
216 return indicatorCount;
220 if (indicatorCount == value || indicatorCount < 0 || value <= 0)
224 if (isSymmetrical == false)
226 Log.Info("NUI", "This property is not for asymmetric pagination. Change to symmetrical pagination.\n");
227 isSymmetrical = true;
230 if (value % 2 == 1) // Odd number
239 if (indicatorCount < value)
244 arrayIndex = (19 - value) / 2;
248 arrayIndex = (18 - value) / 2;
250 if (arrayIndex < 0) return;
252 for (int i = (indicatorCount + 1); i <= value; i++)
254 CreateIndicator(arrayIndex);
258 // If selectedIndex is not set yet, the default value is middle index.
259 if (selectedIndex == -1)
261 selectedIndex = value / 2;
262 SelectIn(indicatorList[selectedIndex]);
267 for (int i = value; i < indicatorCount; i++)
269 ImageVisual indicator = indicatorList[i];
270 container.RemoveVisual("Indicator" + i);
272 indicatorList.RemoveRange(value, indicatorCount - value);
274 if (selectedIndex >= value)
276 selectedIndex = value - 1;
277 SelectIn(indicatorList[selectedIndex]);
280 indicatorCount = value;
288 /// Gets or sets the number of the left pages/indicators.
290 /// This value can be set when IsSymmetrical API is false.
292 /// <since_tizen> 8 </since_tizen>
293 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
294 [EditorBrowsable(EditorBrowsableState.Never)]
295 public int LeftIndicatorCount
299 return leftIndicatorCount;
303 if (isSymmetrical == true)
305 Log.Info("NUI", "This variable is not for symmetric pagination. \n");
306 isSymmetrical = false;
309 if (leftIndicatorCount == value || leftIndicatorCount < 0 || value > 9 || value < 0)
316 if (leftIndicatorCount < value)
318 for (int i = (middleIndex - value); i < (middleIndex - leftIndicatorCount); i++)
326 for (int i = 0; i < (leftIndicatorCount - value); i++)
328 ImageVisual indicator = indicatorList[i];
329 container.RemoveVisual("Indicator" + i);
331 indicatorList.RemoveRange(0, (leftIndicatorCount - value)); // LeftIndicator starts from index 0.
333 if (selectedIndex == 0)
336 SelectIn(indicatorList[selectedIndex]);
341 SelectIn(indicatorList[selectedIndex]);
344 leftIndicatorCount = value;
345 indicatorCount = leftIndicatorCount + rightIndicatorCount + 1;
347 // When RightIndicatorCount is set before, then selectedIndex should be updated using the current LeftIndicatorCount.
348 if (uninitializedLeftIndicator && selectedIndex == 0)
350 selectedIndex = leftIndicatorCount;
352 uninitializedLeftIndicator = false;
360 /// Gets or sets the number of the right pages/indicators.
362 /// This value can be set when IsSymmetrical API is false.
364 /// <since_tizen> 8 </since_tizen>
365 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
366 [EditorBrowsable(EditorBrowsableState.Never)]
367 public int RightIndicatorCount
371 return rightIndicatorCount;
375 if (isSymmetrical == true)
377 Log.Info("NUI", "This variable is not for symmetric pagination. \n");
378 isSymmetrical = false;
381 if (rightIndicatorCount == value || rightIndicatorCount < 0 || value > 9 || value < 0)
388 if (rightIndicatorCount < value)
390 for (int i = (middleIndex + rightIndicatorCount + 1); i <= (middleIndex + value); i++)
397 for (int i = (leftIndicatorCount + value + 1); i < (leftIndicatorCount + rightIndicatorCount); i++)
399 ImageVisual indicator = indicatorList[i];
400 container.RemoveVisual("Indicator" + i);
402 indicatorList.RemoveRange((leftIndicatorCount + value), (rightIndicatorCount - value));
404 if (selectedIndex >= (leftIndicatorCount + rightIndicatorCount))
407 SelectIn(indicatorList[selectedIndex]);
410 rightIndicatorCount = value;
411 indicatorCount = leftIndicatorCount + rightIndicatorCount + 1;
419 /// Gets or sets the index of the select indicator.
421 /// If no value is set, the default value is the center indicator.
423 /// <since_tizen> 8 </since_tizen>
424 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
425 [EditorBrowsable(EditorBrowsableState.Never)]
426 public int SelectedIndex
430 return selectedIndex;
434 if (selectedIndex == value || value < 0 || value >= indicatorCount)
439 // TODO : Here it needs to add virtual function for Animation.
441 if (selectedIndex >= 0)
443 if ((isSymmetrical && selectedIndex < indicatorCount) ||
444 (!isSymmetrical && selectedIndex <= (middleIndex + rightIndicatorCount)))
446 CheckCenterIndicator(selectedIndex);
447 SelectOut(indicatorList[selectedIndex]);
450 selectedIndex = value;
451 if (selectedIndex >= 0)
453 if ((isSymmetrical && selectedIndex < indicatorCount) ||
454 (!isSymmetrical && selectedIndex <= (middleIndex + rightIndicatorCount)))
456 CheckCenterIndicator(selectedIndex);
457 SelectIn(indicatorList[selectedIndex]);
464 /// Retrieves the position of a indicator by index.
466 /// <param name="index">Indicator index</param>
467 /// <returns>The position of a indicator by index</returns>
468 /// <since_tizen> 8 </since_tizen>
469 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
470 [EditorBrowsable(EditorBrowsableState.Never)]
471 public Position GetIndicatorPosition(int index)
473 if (index < 0 || index >= indicatorList.Count)
477 return new Position(indicatorList[index].Position.X, indicatorList[index].Position.Y);
481 /// Sets the position of a indicator by index.
483 /// <param name="index">Indicator index</param>
484 /// <param name="position">The position of a indicator by index</param>
485 /// <exception cref="ArgumentNullException">This exception can occur by the position is null.</exception>
486 /// <since_tizen> 8 </since_tizen>
487 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
488 [EditorBrowsable(EditorBrowsableState.Never)]
489 public virtual void SetIndicatorPosition(int index, Position position)
491 if (position == null)
493 throw new ArgumentNullException(nameof(position));
495 // Update odd / even Array and List by each converted index.
500 oddArray[(middleIndex - (indicatorCount / 2) + index)] = position;
502 else // IsSymmetrical is false and it means the number of left indicators is different from that of right ones.
504 oddArray[(middleIndex - leftIndicatorCount) + index] = position;
506 indicatorList[index].Position = new Vector2(position.X, position.Y);
508 else // Only symmetry circular pagination can be even number.
510 evenArray[(middleIndex - (indicatorCount / 2) + index)] = position;
511 indicatorList[index].Position = new Vector2(position.X, position.Y);
517 /// You can override it to do your select out operation.
519 /// <param name="selectOutIndicator">The indicator will be selected out</param>
520 /// <since_tizen> 8 </since_tizen>
521 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
522 [EditorBrowsable(EditorBrowsableState.Never)]
523 protected virtual void SelectOut(VisualMap selectOutIndicator)
525 if (!(selectOutIndicator is ImageVisual visual)) return;
526 if (isCurrentIndicatorCentered)
528 visual.URL = circularPaginationStyle?.CenterIndicatorImageURL?.Normal;
532 visual.URL = circularPaginationStyle?.IndicatorImageURL?.Normal;
534 visual.Opacity = 0.5f;
538 /// You can override it to do your select in operation.
540 /// <param name="selectInIndicator">The indicator will be selected in</param>
541 /// <since_tizen> 8 </since_tizen>
542 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
543 [EditorBrowsable(EditorBrowsableState.Never)]
544 protected virtual void SelectIn(VisualMap selectInIndicator)
546 if (!(selectInIndicator is ImageVisual visual)) return;
547 if (isCurrentIndicatorCentered)
549 visual.URL = circularPaginationStyle?.CenterIndicatorImageURL?.Selected;
553 visual.URL = circularPaginationStyle?.IndicatorImageURL?.Selected;
555 visual.Opacity = 1.0f;
559 /// you can override it to create your own default style.
561 /// <since_tizen> 8 </since_tizen>
562 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
563 [EditorBrowsable(EditorBrowsableState.Never)]
564 protected override ViewStyle CreateViewStyle()
566 return new CircularPaginationStyle();
570 /// you can override it to clean-up your own resources.
572 /// <param name="type">DisposeTypes</param>
573 /// <since_tizen> 8 </since_tizen>
574 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
575 [EditorBrowsable(EditorBrowsableState.Never)]
576 protected override void Dispose(DisposeTypes type)
583 if (type == DisposeTypes.Explicit)
585 container.RemoveAll();
586 indicatorList.Clear();
588 this.Remove(container);
596 private void Initialize()
598 circularPaginationStyle = ViewStyle as CircularPaginationStyle;
599 if (circularPaginationStyle == null)
601 throw new Exception("CircularPagination style is null.");
604 container = new VisualView()
607 ParentOrigin = Tizen.NUI.ParentOrigin.TopLeft,
608 PivotPoint = Tizen.NUI.PivotPoint.TopLeft,
609 PositionUsesPivotPoint = true,
614 // The parameter, index, is for the index of either oddArray or evenArray.
615 private void CreateIndicator(int index)
617 if (circularPaginationStyle == null || circularPaginationStyle.IndicatorSize == null)
622 ImageVisual indicator = new ImageVisual
624 URL = circularPaginationStyle.IndicatorImageURL?.Normal,
625 Size = new Size2D((int)circularPaginationStyle.IndicatorSize.Width, (int)circularPaginationStyle.IndicatorSize.Height),
631 indicator.Position = oddArray[index];
635 indicator.Position = evenArray[index];
638 container.AddVisual("Indicator" + indicatorList.Count, indicator);
639 indicatorList.Add(indicator);
642 private void CheckCenterIndicator(int index)
644 if (isCenterImageSet &&
645 (isSymmetrical && (index == indicatorCount / 2)) ||
646 (!isSymmetrical && (index == leftIndicatorCount)))
648 isCurrentIndicatorCentered = true;
652 isCurrentIndicatorCentered = false;
656 private void UpdateContainer()
658 if (circularPaginationStyle == null || circularPaginationStyle.IndicatorSize == null || container == null)
662 if (indicatorList.Count > 0)
664 container.SizeWidth = (circularPaginationStyle.IndicatorSize.Width) * indicatorList.Count;
668 container.SizeWidth = 0;
670 container.SizeHeight = circularPaginationStyle.IndicatorSize.Height;
673 private void UpdateVisual()
675 if (null == circularPaginationStyle.IndicatorSize) return;
676 var indicatorImageURL = circularPaginationStyle.IndicatorImageURL;
677 if (null == indicatorImageURL) return;
678 if (indicatorCount <= 0) return;
680 for (int i = 0; i < indicatorList.Count; i++)
682 ImageVisual indicator = indicatorList[i];
683 indicator.Size = new Size2D((int)circularPaginationStyle.IndicatorSize.Width, (int)circularPaginationStyle.IndicatorSize.Height);
685 CheckCenterIndicator(i);
687 if (i == selectedIndex)
689 // If the center image is set before, then should update the center visual separately.
690 if (isCurrentIndicatorCentered)
692 indicator.URL = circularPaginationStyle.CenterIndicatorImageURL?.Selected;
696 indicator.URL = indicatorImageURL.Selected;
698 indicator.Opacity = 1.0f;
702 // If the center image is set before, then should update the center visual separately.
703 if (isCurrentIndicatorCentered)
705 indicator.URL = circularPaginationStyle.CenterIndicatorImageURL?.Normal;
709 indicator.URL = indicatorImageURL.Normal;
711 indicator.Opacity = 0.5f;
718 indicator.Position = oddArray[middleIndex - (indicatorCount / 2) + i];
722 indicator.Position = oddArray[(middleIndex - leftIndicatorCount) + i];
728 indicator.Position = evenArray[middleIndex - (indicatorCount / 2) + i];
733 private void UpdateAsymmetry()
735 if (null == circularPaginationStyle.IndicatorSize) return;
736 var indicatorImageURL = circularPaginationStyle.IndicatorImageURL;
737 if (null == indicatorImageURL) return;
739 int listCount = indicatorList.Count;
741 for (int i = 0; i < listCount; i++)
743 container.RemoveVisual("Indicator" + i);
745 container.RemoveAll();
746 indicatorList.Clear();
748 for (int i = 0; i < listCount; i++)
750 ImageVisual newOne = new ImageVisual
752 Size = new Size2D((int)circularPaginationStyle.IndicatorSize.Width, (int)circularPaginationStyle.IndicatorSize.Height),
753 Position = oddArray[i + (middleIndex - leftIndicatorCount)]
756 if (isCenterImageSet && !isSymmetrical && (i == leftIndicatorCount))
758 newOne.URL = circularPaginationStyle.CenterIndicatorImageURL?.Normal;
762 newOne.URL = indicatorImageURL.Normal;
764 newOne.Opacity = 0.5f;
765 container.AddVisual("Indicator" + i, newOne);
766 indicatorList.Add(newOne);
769 // If selectedIndex is not set yet, the default value is middle index.
770 if (selectedIndex == -1)
772 selectedIndex = leftIndicatorCount;
775 if (isCenterImageSet && (selectedIndex == leftIndicatorCount))
777 indicatorList[selectedIndex].URL = circularPaginationStyle.CenterIndicatorImageURL?.Selected;
778 indicatorList[selectedIndex].Opacity = 1.0f;
782 indicatorList[selectedIndex].URL = indicatorImageURL.Selected;
783 indicatorList[selectedIndex].Opacity = 1.0f;