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.
50 private Animation selectAnimation = null;
51 private bool isNeedAnimation = false; // TODO : Animation will support using override function later.
53 Position2D[] oddArray = new Position2D[] { new Position2D(36, 74), new Position2D(47, 60), new Position2D(60, 47), new Position2D(74, 36),
54 new Position2D(89, 26), new Position2D(105, 18), new Position2D(122, 11), new Position2D(139, 7),
55 new Position2D(157, 4), new Position2D(175, 3), new Position2D(193, 4), new Position2D(211, 7),
56 new Position2D(228, 11), new Position2D(245, 18), new Position2D(261, 26), new Position2D(276, 36),
57 new Position2D(290, 47), new Position2D(303, 60), new Position2D(314, 73) };
59 Position2D[] evenArray = new Position2D[] { new Position2D(41, 67), new Position2D(53, 53), new Position2D(67, 41), new Position2D(81, 31),
60 new Position2D(97, 22), new Position2D(113, 14), new Position2D(131, 9), new Position2D(148, 5),
61 new Position2D(166, 3), new Position2D(184, 3), new Position2D(202, 5), new Position2D(220, 9),
62 new Position2D(237, 14), new Position2D(253, 22), new Position2D(269, 31), new Position2D(283, 41),
63 new Position2D(297, 53), new Position2D(309, 67) };
65 static CircularPagination()
67 ThemeManager.AddPackageTheme(DefaultThemeCreator.Instance);
71 /// Creates a new instance of a CircularPagination.
73 /// <since_tizen> 8 </since_tizen>
74 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
75 [EditorBrowsable(EditorBrowsableState.Never)]
76 public CircularPagination() : base()
82 /// Creates a new instance of a CircularPagination using style.
84 /// <since_tizen> 8 </since_tizen>
85 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
86 [EditorBrowsable(EditorBrowsableState.Never)]
87 public CircularPagination(CircularPaginationStyle style) : base(style)
93 /// Gets or sets the size of the indicator.
95 /// <since_tizen> 8 </since_tizen>
96 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
97 [EditorBrowsable(EditorBrowsableState.Never)]
98 public Size IndicatorSize
102 return circularPaginationStyle?.IndicatorSize;
106 if (value == null || circularPaginationStyle == null)
110 circularPaginationStyle.IndicatorSize = value;
116 /// Gets or sets the background resource of indicator.
118 /// <since_tizen> 8 </since_tizen>
119 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
120 [EditorBrowsable(EditorBrowsableState.Never)]
121 public Selector<string> IndicatorImageURL
125 return circularPaginationStyle?.IndicatorImageURL;
129 if (value == null || circularPaginationStyle == null)
133 circularPaginationStyle.IndicatorImageURL = value;
139 /// Gets or sets the background resource of the center indicator.
141 /// <since_tizen> 8 </since_tizen>
142 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
143 [EditorBrowsable(EditorBrowsableState.Never)]
144 public Selector<string> CenterIndicatorImageURL
148 if (isCenterImageSet)
150 return circularPaginationStyle?.CenterIndicatorImageURL;
154 Log.Info("NUI", "CenterIndicatorImageURL is not set yet. \n");
161 if (value == null || circularPaginationStyle == null)
165 circularPaginationStyle.CenterIndicatorImageURL = value;
166 isCenterImageSet = true;
172 /// Checks whether the indicators are symmetrical or not.
174 /// The default value is true.
175 /// If the value is true, the user just can set IndicatorCount.
176 /// If false, the user should set both the number of Left Indicators and the number of Right Indicators.
177 /// Please refer to LeftIndicatorCount and RightIndicatorCount.
179 /// <since_tizen> 8 </since_tizen>
180 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
181 [EditorBrowsable(EditorBrowsableState.Never)]
182 public bool IsSymmetrical
186 return isSymmetrical;
190 if (isSymmetrical == value)
197 CreateIndicator(middleIndex);
200 isSymmetrical = value;
208 /// Gets or sets the number of the pages/indicators.
210 /// This value is for symmetrical indicators.
212 /// <since_tizen> 8 </since_tizen>
213 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
214 [EditorBrowsable(EditorBrowsableState.Never)]
215 public int IndicatorCount
219 return indicatorCount;
223 if (indicatorCount == value || indicatorCount < 0 || value <= 0)
227 if (isSymmetrical == false)
229 Log.Info("NUI", "This property is not for asymmetric pagination. Change to symmetrical pagination.\n");
230 isSymmetrical = true;
233 if (value % 2 == 1) // Odd number
242 if (indicatorCount < value)
247 arrayIndex = (19 - value) / 2;
251 arrayIndex = (18 - value) / 2;
253 if (arrayIndex < 0) return;
255 for (int i = (indicatorCount + 1); i <= value; i++)
257 CreateIndicator( arrayIndex );
261 // If selectedIndex is not set yet, the default value is middle index.
262 if (selectedIndex == -1)
264 selectedIndex = value / 2;
265 SelectIn(indicatorList[selectedIndex]);
270 for (int i = value; i < indicatorCount; i++)
272 ImageVisual indicator = indicatorList[i];
273 container.RemoveVisual("Indicator" + i);
275 indicatorList.RemoveRange(value, indicatorCount - value);
277 if (selectedIndex >= value)
279 selectedIndex = value - 1;
280 SelectIn(indicatorList[selectedIndex]);
283 indicatorCount = value;
291 /// Gets or sets the number of the left pages/indicators.
293 /// This value can be set when IsSymmetrical API is false.
295 /// <since_tizen> 8 </since_tizen>
296 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
297 [EditorBrowsable(EditorBrowsableState.Never)]
298 public int LeftIndicatorCount
302 return leftIndicatorCount;
306 if (isSymmetrical == true)
308 Log.Info("NUI", "This variable is not for symmetric pagination. \n");
309 isSymmetrical = false;
312 if (leftIndicatorCount == value || leftIndicatorCount < 0 || value > 9 || value < 0)
319 if (leftIndicatorCount < value)
321 for (int i = (middleIndex - value); i < (middleIndex - leftIndicatorCount); i++)
323 CreateIndicator( i );
329 for (int i = 0; i < (leftIndicatorCount - value); i++)
331 ImageVisual indicator = indicatorList[i];
332 container.RemoveVisual("Indicator" + i);
334 indicatorList.RemoveRange(0, (leftIndicatorCount - value)); // LeftIndicator starts from index 0.
336 if (selectedIndex == 0)
339 SelectIn(indicatorList[selectedIndex]);
344 SelectIn(indicatorList[selectedIndex]);
347 leftIndicatorCount = value;
348 indicatorCount = leftIndicatorCount + rightIndicatorCount + 1;
350 // When RightIndicatorCount is set before, then selectedIndex should be updated using the current LeftIndicatorCount.
351 if (uninitializedLeftIndicator && selectedIndex == 0)
353 selectedIndex = leftIndicatorCount;
355 uninitializedLeftIndicator = false;
363 /// Gets or sets the number of the right pages/indicators.
365 /// This value can be set when IsSymmetrical API is false.
367 /// <since_tizen> 8 </since_tizen>
368 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
369 [EditorBrowsable(EditorBrowsableState.Never)]
370 public int RightIndicatorCount
374 return rightIndicatorCount;
378 if (isSymmetrical == true)
380 Log.Info("NUI", "This variable is not for symmetric pagination. \n");
381 isSymmetrical = false;
384 if (rightIndicatorCount == value || rightIndicatorCount < 0 || value > 9 || value < 0)
391 if (rightIndicatorCount < value)
393 for (int i = (middleIndex + rightIndicatorCount + 1); i <= (middleIndex + value); i++)
395 CreateIndicator( i );
400 for (int i = (leftIndicatorCount + value + 1); i < (leftIndicatorCount + rightIndicatorCount); i++)
402 ImageVisual indicator = indicatorList[i];
403 container.RemoveVisual("Indicator" + i);
405 indicatorList.RemoveRange((leftIndicatorCount + value), (rightIndicatorCount - value));
407 if (selectedIndex >= (leftIndicatorCount + rightIndicatorCount))
410 SelectIn(indicatorList[selectedIndex]);
413 rightIndicatorCount = value;
414 indicatorCount = leftIndicatorCount + rightIndicatorCount + 1;
422 /// Gets or sets the index of the select indicator.
424 /// If no value is set, the default value is the center indicator.
426 /// <since_tizen> 8 </since_tizen>
427 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
428 [EditorBrowsable(EditorBrowsableState.Never)]
429 public int SelectedIndex
433 return selectedIndex;
437 if (selectedIndex == value || value < 0 || value >= indicatorCount)
442 // TODO : Here it needs to add virtual function for Animation.
444 if (selectedIndex >= 0)
446 if ( (isSymmetrical && selectedIndex < indicatorCount) ||
447 (!isSymmetrical && selectedIndex <= (middleIndex + rightIndicatorCount) ) )
449 CheckCenterIndicator(selectedIndex);
450 SelectOut(indicatorList[selectedIndex]);
453 selectedIndex = value;
454 if (selectedIndex >= 0)
456 if ( (isSymmetrical && selectedIndex < indicatorCount) ||
457 (!isSymmetrical && selectedIndex <= (middleIndex + rightIndicatorCount) ) )
459 CheckCenterIndicator(selectedIndex);
460 SelectIn(indicatorList[selectedIndex]);
467 /// Retrieves the position of a indicator by index.
469 /// <param name="index">Indicator index</param>
470 /// <returns>The position of a indicator by index</returns>
471 /// <since_tizen> 8 </since_tizen>
472 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
473 [EditorBrowsable(EditorBrowsableState.Never)]
474 public Position GetIndicatorPosition(int index)
476 if (index < 0 || index >= indicatorList.Count)
480 return new Position(indicatorList[index].Position.X, indicatorList[index].Position.Y);
484 /// Sets the position of a indicator by index.
486 /// <param name="index">Indicator index</param>
487 /// <param name="position">The position of a indicator by index</param>
488 /// <exception cref="ArgumentNullException">This exception can occur by the position is null.</exception>
489 /// <since_tizen> 8 </since_tizen>
490 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
491 [EditorBrowsable(EditorBrowsableState.Never)]
492 public virtual void SetIndicatorPosition(int index, Position position)
494 if (position == null)
496 throw new ArgumentNullException(nameof(position));
498 // Update odd / even Array and List by each converted index.
503 oddArray[(middleIndex - (indicatorCount / 2) + index)] = position;
505 else // IsSymmetrical is false and it means the number of left indicators is different from that of right ones.
507 oddArray[(middleIndex - leftIndicatorCount) + index] = position;
509 indicatorList[index].Position = new Vector2(position.X, position.Y);
511 else // Only symmetry circular pagination can be even number.
513 evenArray[(middleIndex - (indicatorCount / 2) + index)] = position;
514 indicatorList[index].Position = new Vector2(position.X, position.Y);
519 private void CreateSelectAnimation()
521 if (selectAnimation == null)
523 selectAnimation = new Animation(250);
528 /// You can override it to do your select out operation.
530 /// <param name="selectOutIndicator">The indicator will be selected out</param>
531 /// <since_tizen> 8 </since_tizen>
532 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
533 [EditorBrowsable(EditorBrowsableState.Never)]
534 protected virtual void SelectOut(VisualMap selectOutIndicator)
536 if (!(selectOutIndicator is ImageVisual visual)) return;
537 if (isCurrentIndicatorCentered)
539 visual.URL = circularPaginationStyle?.CenterIndicatorImageURL?.Normal;
543 visual.URL = circularPaginationStyle?.IndicatorImageURL?.Normal;
545 visual.Opacity = 0.5f;
549 /// You can override it to do your select in operation.
551 /// <param name="selectInIndicator">The indicator will be selected in</param>
552 /// <since_tizen> 8 </since_tizen>
553 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
554 [EditorBrowsable(EditorBrowsableState.Never)]
555 protected virtual void SelectIn(VisualMap selectInIndicator)
557 if (!(selectInIndicator is ImageVisual visual)) return;
558 if (isCurrentIndicatorCentered)
560 visual.URL = circularPaginationStyle?.CenterIndicatorImageURL?.Selected;
564 visual.URL = circularPaginationStyle?.IndicatorImageURL?.Selected;
566 visual.Opacity = 1.0f;
570 /// you can override it to create your own default style.
572 /// <since_tizen> 8 </since_tizen>
573 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
574 [EditorBrowsable(EditorBrowsableState.Never)]
575 protected override ViewStyle CreateViewStyle()
577 return new CircularPaginationStyle();
581 /// you can override it to clean-up your own resources.
583 /// <param name="type">DisposeTypes</param>
584 /// <since_tizen> 8 </since_tizen>
585 /// This will be public opened in tizen_6.0 after ACR done. Before ACR, need to be hidden as inhouse API.
586 [EditorBrowsable(EditorBrowsableState.Never)]
587 protected override void Dispose(DisposeTypes type)
594 if (type == DisposeTypes.Explicit)
596 if (selectAnimation != null)
598 if (selectAnimation.State == Animation.States.Playing)
600 selectAnimation.Stop();
602 selectAnimation.Dispose();
603 selectAnimation = null;
606 container.RemoveAll();
607 indicatorList.Clear();
609 this.Remove(container);
617 private void Initialize()
619 circularPaginationStyle = ViewStyle as CircularPaginationStyle;
620 if (circularPaginationStyle == null)
622 throw new Exception("CircularPagination style is null.");
625 container = new VisualView()
628 ParentOrigin = Tizen.NUI.ParentOrigin.TopLeft,
629 PivotPoint = Tizen.NUI.PivotPoint.TopLeft,
630 PositionUsesPivotPoint = true,
635 // The parameter, index, is for the index of either oddArray or evenArray.
636 private void CreateIndicator(int index)
638 if (circularPaginationStyle == null || circularPaginationStyle.IndicatorSize == null)
643 ImageVisual indicator = new ImageVisual
645 URL = circularPaginationStyle.IndicatorImageURL?.Normal,
646 Size = new Size2D((int)circularPaginationStyle.IndicatorSize.Width, (int)circularPaginationStyle.IndicatorSize.Height),
652 indicator.Position = oddArray[index];
656 indicator.Position = evenArray[index];
659 container.AddVisual("Indicator" + indicatorList.Count, indicator);
660 indicatorList.Add(indicator);
663 private void CheckCenterIndicator(int index)
665 if (isCenterImageSet &&
666 (isSymmetrical && (index == indicatorCount / 2)) ||
667 (!isSymmetrical && (index == leftIndicatorCount)) )
669 isCurrentIndicatorCentered = true;
673 isCurrentIndicatorCentered = false;
677 private void UpdateContainer()
679 if (circularPaginationStyle == null || circularPaginationStyle.IndicatorSize == null || container == null)
683 if (indicatorList.Count > 0)
685 container.SizeWidth = (circularPaginationStyle.IndicatorSize.Width) * indicatorList.Count;
689 container.SizeWidth = 0;
691 container.SizeHeight = circularPaginationStyle.IndicatorSize.Height;
694 private void UpdateVisual()
696 if (null == circularPaginationStyle.IndicatorSize) return;
697 if (null == circularPaginationStyle.IndicatorImageURL) return;
698 if (indicatorCount <= 0) return;
700 for (int i = 0; i < indicatorList.Count; i++)
702 ImageVisual indicator = indicatorList[i];
703 indicator.Size = new Size2D((int)circularPaginationStyle.IndicatorSize.Width, (int)circularPaginationStyle.IndicatorSize.Height);
705 CheckCenterIndicator(i);
707 if (i == selectedIndex)
709 // If the center image is set before, then should update the center visual separately.
710 if (isCurrentIndicatorCentered)
712 indicator.URL = circularPaginationStyle.CenterIndicatorImageURL.Selected;
716 indicator.URL = circularPaginationStyle.IndicatorImageURL.Selected;
718 indicator.Opacity = 1.0f;
722 // If the center image is set before, then should update the center visual separately.
723 if (isCurrentIndicatorCentered)
725 indicator.URL = circularPaginationStyle.CenterIndicatorImageURL.Normal;
729 indicator.URL = circularPaginationStyle.IndicatorImageURL.Normal;
731 indicator.Opacity = 0.5f;
738 indicator.Position = oddArray[middleIndex - (indicatorCount / 2) + i];
742 indicator.Position = oddArray[(middleIndex - leftIndicatorCount) + i];
748 indicator.Position = evenArray[middleIndex - (indicatorCount / 2) + i];
753 private void UpdateAsymmetry()
755 if (null == circularPaginationStyle.IndicatorSize) return;
756 if (null == circularPaginationStyle.IndicatorImageURL) return;
758 int listCount = indicatorList.Count;
760 for (int i = 0; i < listCount; i++)
762 container.RemoveVisual("Indicator" + i);
764 container.RemoveAll();
765 indicatorList.Clear();
767 for (int i = 0; i < listCount; i++)
769 ImageVisual newOne = new ImageVisual
771 Size = new Size2D((int)circularPaginationStyle.IndicatorSize.Width, (int)circularPaginationStyle.IndicatorSize.Height),
772 Position = oddArray[i + (middleIndex - leftIndicatorCount)]
775 if (isCenterImageSet && !isSymmetrical && (i == leftIndicatorCount))
777 newOne.URL = circularPaginationStyle.CenterIndicatorImageURL.Normal;
781 newOne.URL = circularPaginationStyle.IndicatorImageURL.Normal;
783 newOne.Opacity = 0.5f;
784 container.AddVisual("Indicator" + i, newOne);
785 indicatorList.Add(newOne);
788 // If selectedIndex is not set yet, the default value is middle index.
789 if (selectedIndex == -1)
791 selectedIndex = leftIndicatorCount;
794 if (isCenterImageSet && (selectedIndex == leftIndicatorCount))
796 indicatorList[selectedIndex].URL = circularPaginationStyle.CenterIndicatorImageURL.Selected;
797 indicatorList[selectedIndex].Opacity = 1.0f;
801 indicatorList[selectedIndex].URL = circularPaginationStyle.IndicatorImageURL.Selected;
802 indicatorList[selectedIndex].Opacity = 1.0f;