f7e50172dc63cfe755d5941b0ebf521afa63803f
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / Pagination.cs
1 /*
2  * Copyright(c) 2021 Samsung Electronics Co., Ltd.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  *
16  */
17
18 using System;
19 using System.Collections.Generic;
20 using System.ComponentModel;
21 using System.Diagnostics;
22 using Tizen.NUI.BaseComponents;
23 using Tizen.NUI.Binding;
24
25 namespace Tizen.NUI.Components
26 {
27     /// <summary>
28     /// Pagination shows the number of pages available and the currently active page.
29     /// </summary>
30     /// <since_tizen> 8 </since_tizen>
31     public partial class Pagination : Control
32     {
33         /// <summary>The IndicatorSize bindable property.</summary>
34         [EditorBrowsable(EditorBrowsableState.Never)]
35         public static readonly BindableProperty IndicatorSizeProperty = BindableProperty.Create(nameof(IndicatorSize), typeof(Size), typeof(Pagination), null, propertyChanged: (bindable, oldValue, newValue) =>
36         {
37             if (newValue != null)
38             {
39                 var pagination = (Pagination)bindable;
40                 pagination.indicatorSize = new Size((Size)newValue);
41                 pagination.UpdateVisual();
42                 pagination.UpdateContainer();
43             }
44         },
45         defaultValueCreator: (bindable) =>
46         {
47             return ((Pagination)bindable).indicatorSize;
48         });
49
50         /// <summary>The IndicatorImageUrlSelector bindable property.</summary>
51         [EditorBrowsable(EditorBrowsableState.Never)]
52         public static readonly BindableProperty IndicatorImageUrlProperty = BindableProperty.Create("IndicatorImageUrl", typeof(Selector<string>), typeof(Pagination), null, propertyChanged: (bindable, oldValue, newValue) =>
53         {
54             var pagination = (Pagination)bindable;
55             pagination.indicatorImageUrl = ((Selector<string>)newValue)?.Clone();
56             pagination.UpdateVisual();
57         },
58         defaultValueCreator: (bindable) =>
59         {
60             return ((Pagination)bindable).indicatorImageUrl;
61         });
62
63         /// <summary>The IndicatorSpacing bindable property.</summary>
64         [EditorBrowsable(EditorBrowsableState.Never)]
65         public static readonly BindableProperty IndicatorSpacingProperty = BindableProperty.Create(nameof(IndicatorSpacing), typeof(int), typeof(Pagination), default(int), propertyChanged: (bindable, oldValue, newValue) =>
66         {
67             var pagination = (Pagination)bindable;
68             pagination.indicatorSpacing = (int)newValue;
69             pagination.UpdateVisual();
70         },
71         defaultValueCreator: (bindable) =>
72         {
73             return ((Pagination)bindable).indicatorSpacing;
74         });
75
76         private VisualView container;
77         private Size indicatorSize = new Size(10, 10);
78         private Selector<string> indicatorImageUrl;
79         private int indicatorSpacing;
80         private List<ImageVisual> indicatorList = new List<ImageVisual>();
81
82         private int indicatorCount = 0;
83         private int selectedIndex = 0;
84
85         private Color indicatorColor;
86         private Color selectedIndicatorColor;
87         private Selector<string> lastIndicatorImageUrl;
88
89         static Pagination() { }
90
91         /// <summary>
92         /// Creates a new instance of a Pagination.
93         /// </summary>
94         /// <since_tizen> 8 </since_tizen>
95         public Pagination() : base()
96         {
97         }
98
99         /// <summary>
100         /// Creates a new instance of a Pagination using style.
101         /// </summary>
102         /// <param name="style">The string to initialize the Pagination</param>
103         /// <since_tizen> 8 </since_tizen>
104         public Pagination(string style) : base(style)
105         {
106         }
107
108         /// <summary>
109         /// Creates a new instance of a Pagination using style.
110         /// </summary>
111         /// <param name="paginationStyle">The style object to initialize the Pagination</param>
112         /// <since_tizen> 8 </since_tizen>
113         public Pagination(PaginationStyle paginationStyle) : base(paginationStyle)
114         {
115         }
116
117         /// <summary>
118         /// Return currently applied style.
119         /// </summary>
120         /// <remarks>
121         /// Modifying contents in style may cause unexpected behaviour.
122         /// </remarks>
123         /// <since_tizen> 8 </since_tizen>
124         public PaginationStyle Style => (PaginationStyle)(ViewStyle as PaginationStyle)?.Clone();
125
126         /// <summary>
127         /// Gets or sets the size of the indicator.
128         /// </summary>
129         /// <since_tizen> 8 </since_tizen>
130         public Size IndicatorSize
131         {
132             get => (Size)GetValue(IndicatorSizeProperty);
133             set => SetValue(IndicatorSizeProperty, value);
134         }
135
136         /// <summary>
137         /// Gets or sets the background resource of indicator.
138         /// </summary>
139         /// <since_tizen> 8 </since_tizen>
140         public Selector<string> IndicatorImageUrl
141         {
142             get => (Selector<string>)GetValue(IndicatorImageUrlProperty);
143             set => SetValue(IndicatorImageUrlProperty, value);
144         }
145
146         /// <summary>
147         /// This is experimental API.
148         /// Make the last indicator has exceptional image, not common image in the Pagination.
149         /// </summary>
150         [EditorBrowsable(EditorBrowsableState.Never)]
151         public Selector<string> LastIndicatorImageUrl
152         {
153             get
154             {
155                 return GetValue(LastIndicatorImageUrlProperty) as Selector<string>;
156             }
157             set
158             {
159                 SetValue(LastIndicatorImageUrlProperty, value);
160                 NotifyPropertyChanged();
161             }
162         }
163         private Selector<string> InternalLastIndicatorImageUrl
164         {
165             get => lastIndicatorImageUrl;
166             set
167             {
168                 lastIndicatorImageUrl = value;
169                 if (value != null && indicatorCount > 0)
170                 {
171                     indicatorList[LastIndicatorIndex].URL = IsLastSelected ? value.Selected : value.Normal;
172                 }
173             }
174         }
175
176         /// <summary>
177         /// Gets or sets the space of the indicator.
178         /// </summary>
179         /// <since_tizen> 8 </since_tizen>
180         public int IndicatorSpacing
181         {
182             get => (int)GetValue(IndicatorSpacingProperty);
183             set => SetValue(IndicatorSpacingProperty, value);
184         }
185
186
187         /// <summary>
188         /// Gets or sets the count of the pages/indicators.
189         /// </summary>
190         /// <since_tizen> 8 </since_tizen>
191         /// <exception cref="ArgumentException">Thrown when the given value is negative.</exception>
192         public int IndicatorCount
193         {
194             get
195             {
196                 return (int)GetValue(IndicatorCountProperty);
197             }
198             set
199             {
200                 SetValue(IndicatorCountProperty, value);
201                 NotifyPropertyChanged();
202             }
203         }
204         private int InternalIndicatorCount
205         {
206             get
207             {
208                 return indicatorCount;
209             }
210             set
211             {
212                 if (value < 0)
213                 {
214                     throw new ArgumentException($"Setting {nameof(IndicatorCount)} to negative is not allowed.");
215                 }
216
217                 if (indicatorCount == value)
218                 {
219                     return;
220                 }
221
222                 int prevLastIndex = -1;
223
224                 if (indicatorCount < value)
225                 {
226                     prevLastIndex = LastIndicatorIndex;
227                     for (int i = indicatorCount; i < value; i++)
228                     {
229                         CreateIndicator(i);
230                     }
231                 }
232                 else
233                 {
234                     for (int i = value; i < indicatorCount; i++)
235                     {
236                         ImageVisual indicator = indicatorList[i];
237                         container.RemoveVisual("Indicator" + i);
238                     }
239                     indicatorList.RemoveRange(value, indicatorCount - value);
240
241                     if (selectedIndex >= value)
242                     {
243                         selectedIndex = Math.Max(0, value - 1);
244
245                         if (value > 0)
246                         {
247                             SelectIn(indicatorList[selectedIndex]);
248                         }
249                     }
250                 }
251                 indicatorCount = value;
252
253                 if (lastIndicatorImageUrl != null && indicatorImageUrl != null && indicatorCount > 0)
254                 {
255                     if (prevLastIndex >= 0)
256                     {
257                         indicatorList[prevLastIndex].URL = prevLastIndex == selectedIndex ? indicatorImageUrl.Selected : indicatorImageUrl.Normal;
258                     }
259                     indicatorList[LastIndicatorIndex].URL = IsLastSelected ? lastIndicatorImageUrl.Selected : lastIndicatorImageUrl.Normal;
260                 }
261
262                 UpdateContainer();
263             }
264         }
265
266         private void OnIndicatorColorChanged(float r, float g, float b, float a)
267         {
268             IndicatorColor = new Color(r, g, b, a);
269         }
270
271         /// <summary>
272         /// Color of the indicator.
273         /// </summary>
274         /// <since_tizen> 8 </since_tizen>
275         public Color IndicatorColor
276         {
277             get
278             {
279                 return GetValue(IndicatorColorProperty) as Color;
280             }
281             set
282             {
283                 SetValue(IndicatorColorProperty, value);
284                 NotifyPropertyChanged();
285             }
286         }
287         private Color InternalIndicatorColor
288         {
289             get
290             {
291                 return new Color(OnIndicatorColorChanged, indicatorColor);
292             }
293             set
294             {
295                 if (value == null)
296                 {
297                     return;
298                 }
299
300                 if (indicatorColor == null)
301                 {
302                     indicatorColor = new Color((Color)value);
303                 }
304                 else
305                 {
306                     if (indicatorColor == value)
307                     {
308                         return;
309                     }
310
311                     indicatorColor = value;
312                 }
313
314                 if (indicatorCount == 0)
315                 {
316                     return;
317                 }
318
319                 for (int i = 0; i < indicatorCount; i++)
320                 {
321                     if (i == selectedIndex)
322                     {
323                         continue;
324                     }
325
326                     ImageVisual indicator = indicatorList[i];
327                     indicator.MixColor = indicatorColor;
328                     indicator.Opacity = indicatorColor.A;
329                 }
330             }
331         }
332
333         private void OnSelectedIndicatorColorChanged(float r, float g, float b, float a)
334         {
335             SelectedIndicatorColor = new Color(r, g, b, a);
336         }
337
338         /// <summary>
339         /// Color of the selected indicator.
340         /// </summary>
341         /// <since_tizen> 8 </since_tizen>
342         public Color SelectedIndicatorColor
343         {
344             get
345             {
346                 return GetValue(SelectedIndicatorColorProperty) as Color;
347             }
348             set
349             {
350                 SetValue(SelectedIndicatorColorProperty, value);
351                 NotifyPropertyChanged();
352             }
353         }
354         private Color InternalSelectedIndicatorColor
355         {
356             get
357             {
358                 return new Color(OnSelectedIndicatorColorChanged, selectedIndicatorColor);
359             }
360             set
361             {
362                 if (value == null)
363                 {
364                     return;
365                 }
366
367                 if (selectedIndicatorColor == null)
368                 {
369                     selectedIndicatorColor = new Color((Color)value);
370                 }
371                 else
372                 {
373                     if (selectedIndicatorColor == value)
374                     {
375                         return;
376                     }
377
378                     selectedIndicatorColor = value;
379                 }
380
381                 if (indicatorList.Count > selectedIndex)
382                 {
383                     var indicator = indicatorList[selectedIndex];
384                     indicator.MixColor = selectedIndicatorColor;
385                     indicator.Opacity = selectedIndicatorColor.A;
386                 }
387             }
388         }
389
390         /// <summary>
391         /// Gets or sets the index of the select indicator.
392         /// </summary>
393         /// <since_tizen> 8 </since_tizen>
394         public int SelectedIndex
395         {
396             get
397             {
398                 return (int)GetValue(SelectedIndexProperty);
399             }
400             set
401             {
402                 SetValue(SelectedIndexProperty, value);
403                 NotifyPropertyChanged();
404             }
405         }
406         private int InternalSelectedIndex
407         {
408             get
409             {
410                 return selectedIndex;
411             }
412             set
413             {
414                 var refinedValue = Math.Max(0, Math.Min(value, indicatorCount - 1));
415
416                 if (selectedIndex == refinedValue)
417                 {
418                     return;
419                 }
420
421                 Debug.Assert(refinedValue >= 0 && refinedValue < indicatorCount);
422                 Debug.Assert(selectedIndex >= 0 && selectedIndex < indicatorCount);
423
424                 SelectOut(indicatorList[selectedIndex]);
425
426                 selectedIndex = refinedValue;
427
428                 SelectIn(indicatorList[selectedIndex]);
429
430                 if (Accessibility.Accessibility.Enabled && IsHighlighted)
431                 {
432                     EmitAccessibilityEvent(AccessibilityPropertyChangeEvent.Value);
433                 }
434             }
435         }
436
437         /// <summary>
438         /// Retrieves the position of a indicator by index.
439         /// </summary>
440         /// <param name="index">Indicator index</param>
441         /// <returns>The position of a indicator by index.</returns>
442         /// <since_tizen> 8 </since_tizen>
443         public Position GetIndicatorPosition(int index)
444         {
445             if (index < 0 || index >= indicatorList.Count)
446             {
447                 return null;
448             }
449             return new Position(indicatorList[index].Position.X + container.PositionX, indicatorList[index].Position.Y + container.PositionY);
450         }
451
452         /// <summary>
453         /// Minimum value.
454         /// </summary>
455         [EditorBrowsable(EditorBrowsableState.Never)]
456         protected override double AccessibilityGetMinimum()
457         {
458             return 0.0;
459         }
460
461         /// <summary>
462         /// Current value.
463         /// </summary>
464         [EditorBrowsable(EditorBrowsableState.Never)]
465         protected override double AccessibilityGetCurrent()
466         {
467             return (double)SelectedIndex;
468         }
469
470         /// <summary>
471         /// Maximum value.
472         /// </summary>
473         [EditorBrowsable(EditorBrowsableState.Never)]
474         protected override double AccessibilityGetMaximum()
475         {
476             return (double)IndicatorCount;
477         }
478
479         /// <summary>
480         /// Current value.
481         /// </summary>
482         [EditorBrowsable(EditorBrowsableState.Never)]
483         protected override bool AccessibilitySetCurrent(double value)
484         {
485             int integerValue = (int)value;
486
487             if (integerValue >= 0 && integerValue <= IndicatorCount)
488             {
489                 SelectedIndex = integerValue;
490                 return true;
491             }
492
493             return false;
494         }
495
496         /// <summary>
497         /// Minimum increment.
498         /// </summary>
499         [EditorBrowsable(EditorBrowsableState.Never)]
500         protected override double AccessibilityGetMinimumIncrement()
501         {
502             return 1.0;
503         }
504
505         /// <inheritdoc/>
506         [EditorBrowsable(EditorBrowsableState.Never)]
507         public override void OnInitialize()
508         {
509             base.OnInitialize();
510             SetAccessibilityConstructor(Role.ScrollBar, AccessibilityInterface.Value);
511             AccessibilityHighlightable = true;
512             AppendAccessibilityAttribute("style", "pagecontrolbyvalue");
513
514             container = new VisualView()
515             {
516                 Name = "Container",
517                 ParentOrigin = Tizen.NUI.ParentOrigin.CenterLeft,
518                 PivotPoint = Tizen.NUI.PivotPoint.CenterLeft,
519                 PositionUsesPivotPoint = true,
520             };
521             this.Add(container);
522
523             //TODO: Apply color properties from PaginationStyle class.
524             indicatorColor = new Color(1.0f, 1.0f, 1.0f, 0.5f);
525             selectedIndicatorColor = new Color(1.0f, 1.0f, 1.0f, 1.0f);
526         }
527
528         /// <summary>
529         /// You can override it to do your select out operation.
530         /// </summary>
531         /// <param name="selectOutIndicator">The indicator will be selected out</param>
532         /// <since_tizen> 8 </since_tizen>
533         protected virtual void SelectOut(VisualMap selectOutIndicator)
534         {
535             if (!(selectOutIndicator is ImageVisual visual)) return;
536             visual.URL = ((IsLastSelected && lastIndicatorImageUrl != null) ? lastIndicatorImageUrl : indicatorImageUrl)?.Normal;
537
538             if (indicatorColor == null)
539             {
540                 //TODO: Apply color properties from PaginationStyle class.
541                 visual.MixColor = new Color(1.0f, 1.0f, 1.0f, 0.5f);
542                 visual.Opacity = 0.5f;
543             }
544             else
545             {
546                 visual.MixColor = indicatorColor;
547                 visual.Opacity = indicatorColor.A;
548             }
549         }
550
551         /// <summary>
552         /// You can override it to do your select in operation.
553         /// </summary>
554         /// <param name="selectInIndicator">The indicator will be selected in</param>
555         /// <since_tizen> 8 </since_tizen>
556         protected virtual void SelectIn(VisualMap selectInIndicator)
557         {
558             if (!(selectInIndicator is ImageVisual visual)) return;
559             visual.URL = ((IsLastSelected && lastIndicatorImageUrl != null) ? lastIndicatorImageUrl : indicatorImageUrl)?.Selected;
560
561             if (selectedIndicatorColor == null)
562             {
563                 //TODO: Apply color properties from PaginationStyle class.
564                 visual.MixColor = new Color(1.0f, 1.0f, 1.0f, 1.0f);
565                 visual.Opacity = 1.0f;
566             }
567             else
568             {
569                 visual.MixColor = selectedIndicatorColor;
570                 visual.Opacity = selectedIndicatorColor.A;
571             }
572         }
573
574         /// <summary>
575         /// you can override it to create your own default style.
576         /// </summary>
577         /// <returns>The default pagination style.</returns>
578         /// <since_tizen> 8 </since_tizen>
579         protected override ViewStyle CreateViewStyle()
580         {
581             return new PaginationStyle();
582         }
583
584         /// <summary>
585         /// you can override it to clean-up your own resources.
586         /// </summary>
587         /// <param name="type">DisposeTypes</param>
588         /// <since_tizen> 8 </since_tizen>
589         protected override void Dispose(DisposeTypes type)
590         {
591             if (disposed)
592             {
593                 return;
594             }
595
596             if (type == DisposeTypes.Explicit)
597             {
598                 container.RemoveAll();
599                 indicatorList.Clear();
600
601                 this.Remove(container);
602                 container.Dispose();
603                 container = null;
604             }
605
606             base.Dispose(type);
607         }
608
609         private void CreateIndicator(int index)
610         {
611             Debug.Assert(indicatorSize != null);
612
613             ImageVisual indicator = new ImageVisual
614             {
615                 URL = indicatorImageUrl?.Normal,
616                 Size = indicatorSize,
617                 //TODO: Apply color properties from PaginationStyle class.
618                 MixColor = (indicatorColor == null) ? new Color(1.0f, 1.0f, 1.0f, 0.5f) : indicatorColor,
619                 Opacity = (indicatorColor == null) ? 0.5f : indicatorColor.A
620             };
621             indicator.Position = new Vector2((int)(indicatorSize.Width + indicatorSpacing) * indicatorList.Count, 0);
622             container.AddVisual("Indicator" + indicatorList.Count, indicator);
623             indicatorList.Add(indicator);
624
625             if (index == selectedIndex)
626             {
627                 SelectIn(indicatorList[selectedIndex]);
628             }
629         }
630
631         private void UpdateContainer()
632         {
633             Debug.Assert(indicatorSize != null);
634
635             if (indicatorList.Count > 0)
636             {
637                 container.SizeWidth = (indicatorSize.Width + indicatorSpacing) * indicatorList.Count - indicatorSpacing;
638             }
639             else
640             {
641                 container.SizeWidth = 0;
642             }
643             container.SizeHeight = indicatorSize.Height;
644             container.PositionX = (int)((this.SizeWidth - container.SizeWidth) / 2);
645         }
646
647         private void UpdateVisual()
648         {
649             Debug.Assert(indicatorSize != null);
650
651             if (indicatorImageUrl == null)
652             {
653                 return;
654             }
655
656             for (int i = 0; i < indicatorList.Count; i++)
657             {
658                 ImageVisual indicator = indicatorList[i];
659                 indicator.URL = indicatorImageUrl?.Normal;
660                 indicator.Size = indicatorSize;
661                 indicator.Position = new Vector2((int)(indicatorSize.Width + indicatorSpacing) * i, 0);
662             }
663
664             if (lastIndicatorImageUrl != null && indicatorCount > 0)
665             {
666                 indicatorList[LastIndicatorIndex].URL = lastIndicatorImageUrl.Normal;
667             }
668         }
669
670         private int LastIndicatorIndex => IndicatorCount - 1;
671         private bool IsLastSelected => LastIndicatorIndex == selectedIndex;
672     }
673 }