[NUI][ATSPI] Add a function to check whether accessibility is enabled (#3463)
[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 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 => lastIndicatorImageUrl;
154             set
155             {
156                 lastIndicatorImageUrl = value;
157                 if (value != null && indicatorCount > 0)
158                 {
159                     indicatorList[LastIndicatorIndex].URL = IsLastSelected ? value.Selected : value.Normal;
160                 }
161             }
162         }
163
164         /// <summary>
165         /// Gets or sets the space of the indicator.
166         /// </summary>
167         /// <since_tizen> 8 </since_tizen>
168         public int IndicatorSpacing
169         {
170             get => (int)GetValue(IndicatorSpacingProperty);
171             set => SetValue(IndicatorSpacingProperty, value);
172         }
173
174
175         /// <summary>
176         /// Gets or sets the count of the pages/indicators.
177         /// </summary>
178         /// <since_tizen> 8 </since_tizen>
179         /// <exception cref="ArgumentException">Thrown when the given value is negative.</exception>
180         public int IndicatorCount
181         {
182             get
183             {
184                 return indicatorCount;
185             }
186             set
187             {
188                 if (value < 0)
189                 {
190                     throw new ArgumentException($"Setting {nameof(IndicatorCount)} to negative is not allowed.");
191                 }
192
193                 if (indicatorCount == value)
194                 {
195                     return;
196                 }
197
198                 int prevLastIndex = -1;
199
200                 if (indicatorCount < value)
201                 {
202                     prevLastIndex = LastIndicatorIndex;
203                     for (int i = indicatorCount; i < value; i++)
204                     {
205                         CreateIndicator(i);
206                     }
207                 }
208                 else
209                 {
210                     for (int i = value; i < indicatorCount; i++)
211                     {
212                         ImageVisual indicator = indicatorList[i];
213                         container.RemoveVisual("Indicator" + i);
214                     }
215                     indicatorList.RemoveRange(value, indicatorCount - value);
216
217                     if (selectedIndex >= value)
218                     {
219                         selectedIndex = Math.Max(0, value - 1);
220
221                         if (value > 0)
222                         {
223                             SelectIn(indicatorList[selectedIndex]);
224                         }
225                     }
226                 }
227                 indicatorCount = value;
228
229                 if (lastIndicatorImageUrl != null && indicatorImageUrl != null && indicatorCount > 0)
230                 {
231                     if (prevLastIndex >= 0)
232                     {
233                         indicatorList[prevLastIndex].URL = prevLastIndex == selectedIndex ? indicatorImageUrl.Selected : indicatorImageUrl.Normal;
234                     }
235                     indicatorList[LastIndicatorIndex].URL = IsLastSelected ? lastIndicatorImageUrl.Selected : lastIndicatorImageUrl.Normal;
236                 }
237
238                 UpdateContainer();
239             }
240         }
241
242         private void OnIndicatorColorChanged(float r, float g, float b, float a)
243         {
244             IndicatorColor = new Color(r, g, b, a);
245         }
246
247         /// <summary>
248         /// Color of the indicator.
249         /// </summary>
250         /// <since_tizen> 8 </since_tizen>
251         public Color IndicatorColor
252         {
253             get
254             {
255                 return new Color(OnIndicatorColorChanged, indicatorColor);
256             }
257             set
258             {
259                 if (value == null)
260                 {
261                     return;
262                 }
263
264                 if (indicatorColor == null)
265                 {
266                     indicatorColor = new Color((Color)value);
267                 }
268                 else
269                 {
270                     if (indicatorColor == value)
271                     {
272                         return;
273                     }
274
275                     indicatorColor = value;
276                 }
277
278                 if (indicatorCount == 0)
279                 {
280                     return;
281                 }
282
283                 for (int i = 0; i < indicatorCount; i++)
284                 {
285                     if (i == selectedIndex)
286                     {
287                         continue;
288                     }
289
290                     ImageVisual indicator = indicatorList[i];
291                     indicator.MixColor = indicatorColor;
292                     indicator.Opacity = indicatorColor.A;
293                 }
294             }
295         }
296
297         private void OnSelectedIndicatorColorChanged(float r, float g, float b, float a)
298         {
299             SelectedIndicatorColor = new Color(r, g, b, a);
300         }
301
302         /// <summary>
303         /// Color of the selected indicator.
304         /// </summary>
305         /// <since_tizen> 8 </since_tizen>
306         public Color SelectedIndicatorColor
307         {
308             get
309             {
310                 return new Color(OnSelectedIndicatorColorChanged, selectedIndicatorColor);
311             }
312             set
313             {
314                 if (value == null)
315                 {
316                     return;
317                 }
318
319                 if (selectedIndicatorColor == null)
320                 {
321                     selectedIndicatorColor = new Color((Color)value);
322                 }
323                 else
324                 {
325                     if (selectedIndicatorColor == value)
326                     {
327                         return;
328                     }
329
330                     selectedIndicatorColor = value;
331                 }
332
333                 if (indicatorList.Count > selectedIndex)
334                 {
335                     var indicator = indicatorList[selectedIndex];
336                     indicator.MixColor = selectedIndicatorColor;
337                     indicator.Opacity = selectedIndicatorColor.A;
338                 }
339             }
340         }
341
342         /// <summary>
343         /// Gets or sets the index of the select indicator.
344         /// </summary>
345         /// <since_tizen> 8 </since_tizen>
346         public int SelectedIndex
347         {
348             get
349             {
350                 return selectedIndex;
351             }
352             set
353             {
354                 var refinedValue = Math.Max(0, Math.Min(value, indicatorCount - 1));
355
356                 if (selectedIndex == refinedValue)
357                 {
358                     return;
359                 }
360
361                 Debug.Assert(refinedValue >= 0 && refinedValue < indicatorCount);
362                 Debug.Assert(selectedIndex >= 0 && selectedIndex < indicatorCount);
363
364                 SelectOut(indicatorList[selectedIndex]);
365
366                 selectedIndex = refinedValue;
367
368                 SelectIn(indicatorList[selectedIndex]);
369
370                 if (Accessibility.Accessibility.Enabled && IsHighlighted)
371                 {
372                     EmitAccessibilityEvent(AccessibilityPropertyChangeEvent.Value);
373                 }
374             }
375         }
376
377         /// <summary>
378         /// Retrieves the position of a indicator by index.
379         /// </summary>
380         /// <param name="index">Indicator index</param>
381         /// <returns>The position of a indicator by index.</returns>
382         /// <since_tizen> 8 </since_tizen>
383         public Position GetIndicatorPosition(int index)
384         {
385             if (index < 0 || index >= indicatorList.Count)
386             {
387                 return null;
388             }
389             return new Position(indicatorList[index].Position.X + container.PositionX, indicatorList[index].Position.Y + container.PositionY);
390         }
391
392         /// <summary>
393         /// Minimum value.
394         /// </summary>
395         [EditorBrowsable(EditorBrowsableState.Never)]
396         protected override double AccessibilityGetMinimum()
397         {
398             return 0.0;
399         }
400
401         /// <summary>
402         /// Current value.
403         /// </summary>
404         [EditorBrowsable(EditorBrowsableState.Never)]
405         protected override double AccessibilityGetCurrent()
406         {
407             return (double)SelectedIndex;
408         }
409
410         /// <summary>
411         /// Maximum value.
412         /// </summary>
413         [EditorBrowsable(EditorBrowsableState.Never)]
414         protected override double AccessibilityGetMaximum()
415         {
416             return (double)IndicatorCount;
417         }
418
419         /// <summary>
420         /// Current value.
421         /// </summary>
422         [EditorBrowsable(EditorBrowsableState.Never)]
423         protected override bool AccessibilitySetCurrent(double value)
424         {
425             int integerValue = (int)value;
426
427             if (integerValue >= 0 && integerValue <= IndicatorCount)
428             {
429                 SelectedIndex = integerValue;
430                 return true;
431             }
432
433             return false;
434         }
435
436         /// <summary>
437         /// Minimum increment.
438         /// </summary>
439         [EditorBrowsable(EditorBrowsableState.Never)]
440         protected override double AccessibilityGetMinimumIncrement()
441         {
442             return 1.0;
443         }
444
445         /// <inheritdoc/>
446         [EditorBrowsable(EditorBrowsableState.Never)]
447         public override void OnInitialize()
448         {
449             base.OnInitialize();
450             SetAccessibilityConstructor(Role.ScrollBar, AccessibilityInterface.Value);
451             AccessibilityHighlightable = true;
452             AppendAccessibilityAttribute("style", "pagecontrolbyvalue");
453
454             container = new VisualView()
455             {
456                 Name = "Container",
457                 ParentOrigin = Tizen.NUI.ParentOrigin.CenterLeft,
458                 PivotPoint = Tizen.NUI.PivotPoint.CenterLeft,
459                 PositionUsesPivotPoint = true,
460             };
461             this.Add(container);
462
463             //TODO: Apply color properties from PaginationStyle class.
464             indicatorColor = new Color(1.0f, 1.0f, 1.0f, 0.5f);
465             selectedIndicatorColor = new Color(1.0f, 1.0f, 1.0f, 1.0f);
466         }
467
468         /// <summary>
469         /// You can override it to do your select out operation.
470         /// </summary>
471         /// <param name="selectOutIndicator">The indicator will be selected out</param>
472         /// <since_tizen> 8 </since_tizen>
473         protected virtual void SelectOut(VisualMap selectOutIndicator)
474         {
475             if (!(selectOutIndicator is ImageVisual visual)) return;
476             visual.URL = ((IsLastSelected && lastIndicatorImageUrl != null) ? lastIndicatorImageUrl : indicatorImageUrl)?.Normal;
477
478             if (indicatorColor == null)
479             {
480                 //TODO: Apply color properties from PaginationStyle class.
481                 visual.MixColor = new Color(1.0f, 1.0f, 1.0f, 0.5f);
482                 visual.Opacity = 0.5f;
483             }
484             else
485             {
486                 visual.MixColor = indicatorColor;
487                 visual.Opacity = indicatorColor.A;
488             }
489         }
490
491         /// <summary>
492         /// You can override it to do your select in operation.
493         /// </summary>
494         /// <param name="selectInIndicator">The indicator will be selected in</param>
495         /// <since_tizen> 8 </since_tizen>
496         protected virtual void SelectIn(VisualMap selectInIndicator)
497         {
498             if (!(selectInIndicator is ImageVisual visual)) return;
499             visual.URL = ((IsLastSelected && lastIndicatorImageUrl != null) ? lastIndicatorImageUrl : indicatorImageUrl)?.Selected;
500
501             if (selectedIndicatorColor == null)
502             {
503                 //TODO: Apply color properties from PaginationStyle class.
504                 visual.MixColor = new Color(1.0f, 1.0f, 1.0f, 1.0f);
505                 visual.Opacity = 1.0f;
506             }
507             else
508             {
509                 visual.MixColor = selectedIndicatorColor;
510                 visual.Opacity = selectedIndicatorColor.A;
511             }
512         }
513
514         /// <summary>
515         /// you can override it to create your own default style.
516         /// </summary>
517         /// <returns>The default pagination style.</returns>
518         /// <since_tizen> 8 </since_tizen>
519         protected override ViewStyle CreateViewStyle()
520         {
521             return new PaginationStyle();
522         }
523
524         /// <summary>
525         /// you can override it to clean-up your own resources.
526         /// </summary>
527         /// <param name="type">DisposeTypes</param>
528         /// <since_tizen> 8 </since_tizen>
529         protected override void Dispose(DisposeTypes type)
530         {
531             if (disposed)
532             {
533                 return;
534             }
535
536             if (type == DisposeTypes.Explicit)
537             {
538                 container.RemoveAll();
539                 indicatorList.Clear();
540
541                 this.Remove(container);
542                 container.Dispose();
543                 container = null;
544             }
545
546             base.Dispose(type);
547         }
548
549         private void CreateIndicator(int index)
550         {
551             Debug.Assert(indicatorSize != null);
552
553             ImageVisual indicator = new ImageVisual
554             {
555                 URL = indicatorImageUrl?.Normal,
556                 Size = indicatorSize,
557                 //TODO: Apply color properties from PaginationStyle class.
558                 MixColor = (indicatorColor == null) ? new Color(1.0f, 1.0f, 1.0f, 0.5f) : indicatorColor,
559                 Opacity = (indicatorColor == null) ? 0.5f : indicatorColor.A
560             };
561             indicator.Position = new Vector2((int)(indicatorSize.Width + indicatorSpacing) * indicatorList.Count, 0);
562             container.AddVisual("Indicator" + indicatorList.Count, indicator);
563             indicatorList.Add(indicator);
564
565             if (index == selectedIndex)
566             {
567                 SelectIn(indicatorList[selectedIndex]);
568             }
569         }
570
571         private void UpdateContainer()
572         {
573             Debug.Assert(indicatorSize != null);
574
575             if (indicatorList.Count > 0)
576             {
577                 container.SizeWidth = (indicatorSize.Width + indicatorSpacing) * indicatorList.Count - indicatorSpacing;
578             }
579             else
580             {
581                 container.SizeWidth = 0;
582             }
583             container.SizeHeight = indicatorSize.Height;
584             container.PositionX = (int)((this.SizeWidth - container.SizeWidth) / 2);
585         }
586
587         private void UpdateVisual()
588         {
589             Debug.Assert(indicatorSize != null);
590
591             if (indicatorImageUrl == null)
592             {
593                 return;
594             }
595
596             for (int i = 0; i < indicatorList.Count; i++)
597             {
598                 ImageVisual indicator = indicatorList[i];
599                 indicator.URL = indicatorImageUrl?.Normal;
600                 indicator.Size = indicatorSize;
601                 indicator.Position = new Vector2((int)(indicatorSize.Width + indicatorSpacing) * i, 0);
602             }
603
604             if (lastIndicatorImageUrl != null && indicatorCount > 0)
605             {
606                 indicatorList[LastIndicatorIndex].URL = lastIndicatorImageUrl.Normal;
607             }
608         }
609
610         private int LastIndicatorIndex => IndicatorCount - 1;
611         private bool IsLastSelected => LastIndicatorIndex == selectedIndex;
612     }
613 }