81581d52fa7dd8120b55dca2ef319af7015e24bd
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI / src / public / CustomView / Spin.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.ComponentModel;
20 using System.Globalization;
21 using Tizen.NUI.BaseComponents;
22 using Tizen.NUI.Binding;
23
24 // A spin control (for continuously changing values when users can easily predict a set of values)
25 namespace Tizen.NUI
26 {
27     ///<summary>
28     ///Spins the CustomView class.
29     /// </summary>
30     /// <since_tizen> 3 </since_tizen>
31     public class Spin : CustomView
32     {
33         /// <summary>
34         /// ValueProperty
35         /// </summary>
36         [EditorBrowsable(EditorBrowsableState.Never)]
37         public static readonly BindableProperty ValueProperty = BindableProperty.Create(nameof(Value), typeof(int), typeof(Tizen.NUI.Spin), 0, propertyChanged: (bindable, oldValue, newValue) =>
38         {
39             var instance = (Tizen.NUI.Spin)bindable;
40             if (newValue != null)
41             {
42                 instance.InternalValue = (int)newValue;
43             }
44         },
45         defaultValueCreator: (bindable) =>
46         {
47             var instance = (Tizen.NUI.Spin)bindable;
48             return instance.InternalValue;
49         });
50
51         /// <summary>
52         /// MinValueProperty
53         /// </summary>
54         [EditorBrowsable(EditorBrowsableState.Never)]
55         public static readonly BindableProperty MinValueProperty = BindableProperty.Create(nameof(MinValue), typeof(int), typeof(Tizen.NUI.Spin), 0, propertyChanged: (bindable, oldValue, newValue) =>
56         {
57             var instance = (Tizen.NUI.Spin)bindable;
58             if (newValue != null)
59             {
60                 instance.InternalMinValue = (int)newValue;
61             }
62         },
63         defaultValueCreator: (bindable) =>
64         {
65             var instance = (Tizen.NUI.Spin)bindable;
66             return instance.InternalMinValue;
67         });
68
69         /// <summary>
70         /// MaxValueProperty
71         /// </summary>
72         [EditorBrowsable(EditorBrowsableState.Never)]
73         public static readonly BindableProperty MaxValueProperty = BindableProperty.Create(nameof(MaxValue), typeof(int), typeof(Tizen.NUI.Spin), 0, propertyChanged: (bindable, oldValue, newValue) =>
74         {
75             var instance = (Tizen.NUI.Spin)bindable;
76             if (newValue != null)
77             {
78                 instance.InternalMaxValue = (int)newValue;
79             }
80         },
81         defaultValueCreator: (bindable) =>
82         {
83             var instance = (Tizen.NUI.Spin)bindable;
84             return instance.InternalMaxValue;
85         });
86
87         /// <summary>
88         /// StepProperty
89         /// </summary>
90         [EditorBrowsable(EditorBrowsableState.Never)]
91         public static readonly BindableProperty StepProperty = BindableProperty.Create(nameof(Step), typeof(int), typeof(Tizen.NUI.Spin), 0, propertyChanged: (bindable, oldValue, newValue) =>
92         {
93             var instance = (Tizen.NUI.Spin)bindable;
94             if (newValue != null)
95             {
96                 instance.InternalStep = (int)newValue;
97             }
98         },
99         defaultValueCreator: (bindable) =>
100         {
101             var instance = (Tizen.NUI.Spin)bindable;
102             return instance.InternalStep;
103         });
104
105         /// <summary>
106         /// WrappingEnabledProperty
107         /// </summary>
108         [EditorBrowsable(EditorBrowsableState.Never)]
109         public static readonly BindableProperty WrappingEnabledProperty = BindableProperty.Create(nameof(WrappingEnabled), typeof(bool), typeof(Tizen.NUI.Spin), false, propertyChanged: (bindable, oldValue, newValue) =>
110         {
111             var instance = (Tizen.NUI.Spin)bindable;
112             if (newValue != null)
113             {
114                 instance.InternalWrappingEnabled = (bool)newValue;
115             }
116         },
117         defaultValueCreator: (bindable) =>
118         {
119             var instance = (Tizen.NUI.Spin)bindable;
120             return instance.InternalWrappingEnabled;
121         });
122
123         /// <summary>
124         /// TextPointSizeProperty
125         /// </summary>
126         [EditorBrowsable(EditorBrowsableState.Never)]
127         public static readonly BindableProperty TextPointSizeProperty = BindableProperty.Create(nameof(TextPointSize), typeof(int), typeof(Tizen.NUI.Spin), 0, propertyChanged: (bindable, oldValue, newValue) =>
128         {
129             var instance = (Tizen.NUI.Spin)bindable;
130             if (newValue != null)
131             {
132                 instance.InternalTextPointSize = (int)newValue;
133             }
134         },
135         defaultValueCreator: (bindable) =>
136         {
137             var instance = (Tizen.NUI.Spin)bindable;
138             return instance.InternalTextPointSize;
139         });
140
141         /// <summary>
142         /// TextColorProperty
143         /// </summary>
144         [EditorBrowsable(EditorBrowsableState.Never)]
145         public static readonly BindableProperty TextColorProperty = BindableProperty.Create(nameof(TextColor), typeof(Tizen.NUI.Color), typeof(Tizen.NUI.Spin), null, propertyChanged: (bindable, oldValue, newValue) =>
146         {
147             var instance = (Tizen.NUI.Spin)bindable;
148             if (newValue != null)
149             {
150                 instance.InternalTextColor = (Tizen.NUI.Color)newValue;
151             }
152         },
153         defaultValueCreator: (bindable) =>
154         {
155             var instance = (Tizen.NUI.Spin)bindable;
156             return instance.InternalTextColor;
157         });
158
159         /// <summary>
160         /// MaxTextLengthProperty
161         /// </summary>
162         [EditorBrowsable(EditorBrowsableState.Never)]
163         public static readonly BindableProperty MaxTextLengthProperty = BindableProperty.Create(nameof(MaxTextLength), typeof(int), typeof(Tizen.NUI.Spin), 0, propertyChanged: (bindable, oldValue, newValue) =>
164         {
165             var instance = (Tizen.NUI.Spin)bindable;
166             if (newValue != null)
167             {
168                 instance.InternalMaxTextLength = (int)newValue;
169             }
170         },
171         defaultValueCreator: (bindable) =>
172         {
173             var instance = (Tizen.NUI.Spin)bindable;
174             return instance.InternalMaxTextLength;
175         });
176
177         /// <summary>
178         /// SpinTextProperty
179         /// </summary>
180         [EditorBrowsable(EditorBrowsableState.Never)]
181         public static readonly BindableProperty SpinTextProperty = BindableProperty.Create(nameof(SpinText), typeof(Tizen.NUI.BaseComponents.TextField), typeof(Tizen.NUI.Spin), null, propertyChanged: (bindable, oldValue, newValue) =>
182         {
183             var instance = (Tizen.NUI.Spin)bindable;
184             if (newValue != null)
185             {
186                 instance.InternalSpinText = (Tizen.NUI.BaseComponents.TextField)newValue;
187             }
188         },
189         defaultValueCreator: (bindable) =>
190         {
191             var instance = (Tizen.NUI.Spin)bindable;
192             return instance.InternalSpinText;
193         });
194
195         /// <summary>
196         /// IndicatorImageProperty
197         /// </summary>
198         [EditorBrowsable(EditorBrowsableState.Never)]
199         public static readonly BindableProperty IndicatorImageProperty = BindableProperty.Create(nameof(IndicatorImage), typeof(string), typeof(Tizen.NUI.Spin), string.Empty, propertyChanged: (bindable, oldValue, newValue) =>
200         {
201             var instance = (Tizen.NUI.Spin)bindable;
202             if (newValue != null)
203             {
204                 instance.InternalIndicatorImage = (string)newValue;
205             }
206         },
207         defaultValueCreator: (bindable) =>
208         {
209             var instance = (Tizen.NUI.Spin)bindable;
210             return instance.InternalIndicatorImage;
211         });
212
213         private VisualBase arrowVisual;
214         private TextField textField;
215         private int arrowVisualPropertyIndex;
216         private string arrowImage;
217         private int currentValue;
218         private int minValue;
219         private int maxValue;
220         private int singleStep;
221         private bool wrappingEnabled;
222         private int pointSize;
223         private Color textColor;
224         private Color textBackgroundColor;
225         private int maxTextLength;
226
227         // static constructor registers the control type (only runs once)
228         static Spin()
229         {
230             // ViewRegistry registers control type with DALi type registry
231             // also uses introspection to find any properties that need to be registered with type registry
232             CustomViewRegistry.Instance.Register(CreateInstance, typeof(Spin));
233         }
234
235         /// <summary>
236         /// Creates an initialized spin.
237         /// </summary>
238         /// <since_tizen> 3 </since_tizen>
239         public Spin() : base(typeof(Spin).FullName, CustomViewBehaviour.RequiresKeyboardNavigationSupport)
240         {
241         }
242
243         /// <summary>
244         /// Value to be set in the spin.
245         /// </summary>
246         /// <since_tizen> 3 </since_tizen>
247         [ScriptableProperty()]
248         // GetValue() is in BindableObject. It's different from this Value.
249         [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1721: Property names should not match get methods")]
250         public int Value
251         {
252             get
253             {
254                 return (int)GetValue(ValueProperty);
255             }
256             set
257             {
258                 SetValue(ValueProperty, value);
259                 NotifyPropertyChanged();
260             }
261         }
262         
263         private int InternalValue
264         {
265             get
266             {
267                 return currentValue;
268             }
269             set
270             {
271                 NUILog.Debug("Value set to " + value);
272                 currentValue = value;
273
274                 // Make sure no invalid value is accepted
275                 if (currentValue < minValue)
276                 {
277                     currentValue = minValue;
278                 }
279
280                 if (currentValue > maxValue)
281                 {
282                     currentValue = maxValue;
283                 }
284
285                 textField.Text = currentValue.ToString();
286             }
287         }
288
289         /// <summary>
290         /// Minimum value of the spin value.
291         /// </summary>
292         /// <since_tizen> 3 </since_tizen>
293         [ScriptableProperty()]
294         public int MinValue
295         {
296             get
297             {
298                 return (int)GetValue(MinValueProperty);
299             }
300             set
301             {
302                 SetValue(MinValueProperty, value);
303                 NotifyPropertyChanged();
304             }
305         }
306         
307         private int InternalMinValue
308         {
309             get
310             {
311                 return minValue;
312             }
313             set
314             {
315                 minValue = value;
316             }
317         }
318
319         /// <summary>
320         /// Maximum value of the spin value.
321         /// </summary>
322         /// <since_tizen> 3 </since_tizen>
323         [ScriptableProperty()]
324         public int MaxValue
325         {
326             get
327             {
328                 return (int)GetValue(MaxValueProperty);
329             }
330             set
331             {
332                 SetValue(MaxValueProperty, value);
333                 NotifyPropertyChanged();
334             }
335         }
336         
337         private int InternalMaxValue
338         {
339             get
340             {
341                 return maxValue;
342             }
343             set
344             {
345                 maxValue = value;
346             }
347         }
348
349         /// <summary>
350         /// Increasing, decreasing step of the spin value when up or down keys are pressed.
351         /// </summary>
352         /// <since_tizen> 3 </since_tizen>
353         [ScriptableProperty()]
354         public int Step
355         {
356             get
357             {
358                 return (int)GetValue(StepProperty);
359             }
360             set
361             {
362                 SetValue(StepProperty, value);
363                 NotifyPropertyChanged();
364             }
365         }
366         
367         private int InternalStep
368         {
369             get
370             {
371                 return singleStep;
372             }
373             set
374             {
375                 singleStep = value;
376             }
377         }
378
379         /// <summary>
380         /// Wrapping enabled status.
381         /// </summary>
382         /// <since_tizen> 3 </since_tizen>
383         [ScriptableProperty()]
384         public bool WrappingEnabled
385         {
386             get
387             {
388                 return (bool)GetValue(WrappingEnabledProperty);
389             }
390             set
391             {
392                 SetValue(WrappingEnabledProperty, value);
393                 NotifyPropertyChanged();
394             }
395         }
396         
397         private bool InternalWrappingEnabled
398         {
399             get
400             {
401                 return wrappingEnabled;
402             }
403             set
404             {
405                 wrappingEnabled = value;
406             }
407         }
408
409         /// <summary>
410         /// Text point size of the spin value.
411         /// </summary>
412         /// <since_tizen> 3 </since_tizen>
413         [ScriptableProperty()]
414         public int TextPointSize
415         {
416             get
417             {
418                 return (int)GetValue(TextPointSizeProperty);
419             }
420             set
421             {
422                 SetValue(TextPointSizeProperty, value);
423                 NotifyPropertyChanged();
424             }
425         }
426         
427         private int InternalTextPointSize
428         {
429             get
430             {
431                 return pointSize;
432             }
433             set
434             {
435                 pointSize = value;
436                 textField.PointSize = pointSize;
437             }
438         }
439
440         /// <summary>
441         /// The color of the spin value.
442         /// </summary>
443         /// <since_tizen> 3 </since_tizen>
444         [ScriptableProperty()]
445         public Color TextColor
446         {
447             get
448             {
449                 return GetValue(TextColorProperty) as Color;
450             }
451             set
452             {
453                 SetValue(TextColorProperty, value);
454                 NotifyPropertyChanged();
455             }
456         }
457         
458         private Color InternalTextColor
459         {
460             get
461             {
462                 return textColor;
463             }
464             set
465             {
466                 if (value != null)
467                 {
468                     NUILog.Debug("TextColor set to " + value.R + "," + value.G + "," + value.B);
469                 }
470
471                 textColor = value;
472                 textField.TextColor = textColor;
473             }
474         }
475
476         /// <summary>
477         /// Maximum text length of the spin value.
478         /// </summary>
479         /// <since_tizen> 3 </since_tizen>
480         [ScriptableProperty()]
481         public int MaxTextLength
482         {
483             get
484             {
485                 return (int)GetValue(MaxTextLengthProperty);
486             }
487             set
488             {
489                 SetValue(MaxTextLengthProperty, value);
490                 NotifyPropertyChanged();
491             }
492         }
493         
494         private int InternalMaxTextLength
495         {
496             get
497             {
498                 return maxTextLength;
499             }
500             set
501             {
502                 maxTextLength = value;
503                 textField.MaxLength = maxTextLength;
504             }
505         }
506
507         /// <summary>
508         /// Reference of TextField of the spin.
509         /// </summary>
510         /// <since_tizen> 3 </since_tizen>
511         public TextField SpinText
512         {
513             get
514             {
515                 return GetValue(SpinTextProperty) as TextField;
516             }
517             set
518             {
519                 SetValue(SpinTextProperty, value);
520                 NotifyPropertyChanged();
521             }
522         }
523         
524         private TextField InternalSpinText
525         {
526             get
527             {
528                 return textField;
529             }
530             set
531             {
532                 textField = value;
533             }
534         }
535
536         /// <summary>
537         /// Show indicator image, for example, up or down arrow image.
538         /// </summary>
539         /// <since_tizen> 3 </since_tizen>
540         public string IndicatorImage
541         {
542             get
543             {
544                 return GetValue(IndicatorImageProperty) as string;
545             }
546             set
547             {
548                 SetValue(IndicatorImageProperty, value);
549                 NotifyPropertyChanged();
550             }
551         }
552         
553         private string InternalIndicatorImage
554         {
555             get
556             {
557                 return arrowImage;
558             }
559             set
560             {
561                 arrowImage = value;
562                 var ptMap = new PropertyMap();
563                 var temp = new PropertyValue((int)Visual.Type.Image);
564                 ptMap.Add(Visual.Property.Type, temp);
565                 temp.Dispose();
566
567                 temp = new PropertyValue(arrowImage);
568                 ptMap.Add(ImageVisualProperty.URL, temp);
569                 temp.Dispose();
570
571                 temp = new PropertyValue(150);
572                 ptMap.Add(ImageVisualProperty.DesiredHeight, temp);
573                 temp.Dispose();
574
575                 temp = new PropertyValue(150);
576                 ptMap.Add(ImageVisualProperty.DesiredWidth, temp);
577                 temp.Dispose();
578
579                 arrowVisual = VisualFactory.Instance.CreateVisual(ptMap);
580                 ptMap.Dispose();
581
582                 RegisterVisual(arrowVisualPropertyIndex, arrowVisual);
583             }
584         }
585
586         // Called by DALi Builder if it finds a Spin control in a JSON file
587         static CustomView CreateInstance()
588         {
589             return new Spin();
590         }
591
592         /// <summary>
593         /// Overrides the method of OnInitialize() for the CustomView class.<br />
594         /// This method is called after the control has been initialized.<br />
595         /// Derived classes should do any second phase initialization by overriding this method.<br />
596         /// </summary>
597         /// <since_tizen> 3 </since_tizen>
598         public override void OnInitialize()
599         {
600             // Initialize the propertiesControl
601             arrowImage = Tizen.Applications.Application.Current.DirectoryInfo.Resource + "picture.png";
602             textBackgroundColor = new Color(0.6f, 0.6f, 0.6f, 1.0f);
603             currentValue = 0;
604             minValue = 0;
605             maxValue = 0;
606             singleStep = 1;
607             maxTextLength = 0;
608
609             // Create image visual for the arrow keys
610             var temp = new PropertyValue(arrowImage);
611             arrowVisualPropertyIndex = RegisterProperty("ArrowImage", temp, Tizen.NUI.PropertyAccessMode.ReadWrite);
612             temp.Dispose();
613
614             var ptMap = new PropertyMap();
615             temp = new PropertyValue((int)Visual.Type.Image);
616             ptMap.Add(Visual.Property.Type, temp);
617             temp.Dispose();
618
619             temp = new PropertyValue(arrowImage);
620             ptMap.Add(ImageVisualProperty.URL, temp);
621             temp.Dispose();
622
623             temp = new PropertyValue(150);
624             ptMap.Add(ImageVisualProperty.DesiredHeight, temp);
625             temp.Dispose();
626
627             temp = new PropertyValue(150);
628             ptMap.Add(ImageVisualProperty.DesiredWidth, temp);
629             temp.Dispose();
630
631             arrowVisual = VisualFactory.Instance.CreateVisual(ptMap);
632             ptMap.Dispose();
633             RegisterVisual(arrowVisualPropertyIndex, arrowVisual);
634
635             // Create a text field
636             textField = new TextField();
637             textField.PivotPoint = Tizen.NUI.PivotPoint.Center;
638             textField.WidthResizePolicy = ResizePolicyType.SizeRelativeToParent;
639             textField.HeightResizePolicy = ResizePolicyType.SizeRelativeToParent;
640             textField.SizeModeFactor = new Vector3(1.0f, 0.45f, 1.0f);
641             textField.PlaceholderText = "----";
642             textField.BackgroundColor = textBackgroundColor;
643             textField.HorizontalAlignment = HorizontalAlignment.Center;
644             textField.VerticalAlignment = VerticalAlignment.Center;
645             textField.Focusable = (true);
646             textField.Name = "_textField";
647             textField.Position2D = new Position2D(0, 40);
648
649             this.Add(textField);
650
651             textField.FocusGained += TextFieldKeyInputFocusGained;
652             textField.FocusLost += TextFieldKeyInputFocusLost;
653         }
654
655         /// <summary>
656         /// Overrides the method of GetNaturalSize() for the CustomView class.<br />
657         /// Returns the natural size of the actor.<br />
658         /// </summary>
659         /// <returns> Natural size of this spin itself.</returns>
660         /// <since_tizen> 3 </since_tizen>
661         public override Size2D GetNaturalSize()
662         {
663             return new Size2D(150, 150);
664         }
665
666         /// <summary>
667         /// An event handler is used when the TextField in the spin gets the key focus.<br />
668         /// Make sure when the current spin that takes input focus, also takes the keyboard focus.<br />
669         /// For example, when you tap the spin directly.<br />
670         /// </summary>
671         /// <param name="source">Sender of this event.</param>
672         /// <param name="e">Event arguments.</param>
673         /// <since_tizen> 3 </since_tizen>
674         public void TextFieldKeyInputFocusGained(object source, EventArgs e)
675         {
676             FocusManager.Instance.SetCurrentFocusView(textField);
677         }
678
679         /// <summary>
680         /// An event handler when the TextField in the spin looses it's key focus.
681         /// </summary>
682         /// <param name="source"></param>
683         /// <param name="e"></param>
684         /// <since_tizen> 3 </since_tizen>
685         public void TextFieldKeyInputFocusLost(object source, EventArgs e)
686         {
687             int previousValue = currentValue;
688
689             // If the input value is invalid, change it back to the previous valid value
690             if (int.TryParse(textField.Text, NumberStyles.None, CultureInfo.InvariantCulture, out currentValue))
691             {
692                 if (currentValue < minValue || currentValue > maxValue)
693                 {
694                     currentValue = previousValue;
695                 }
696             }
697             else
698             {
699                 currentValue = previousValue;
700             }
701
702             // Otherwise take the new value
703             this.Value = currentValue;
704         }
705
706         /// <summary>
707         /// Overrides the method of GetNextKeyboardFocusableView() for the CustomView class.<br />
708         /// Gets the next key focusable view in this view towards the given direction.<br />
709         /// A view needs to override this function in order to support two-dimensional key navigation.<br />
710         /// </summary>
711         /// <param name="currentFocusedView">The current focused view.</param>
712         /// <param name="direction">The direction to move the focus towards.</param>
713         /// <param name="loopEnabled">Whether the focus movement should be looped within the control.</param>
714         /// <returns>The next keyboard focusable view in this control or an empty handle if no view can be focused.</returns>
715         /// <since_tizen> 3 </since_tizen>
716         public override View GetNextFocusableView(View currentFocusedView, View.FocusDirection direction, bool loopEnabled)
717         {
718             // Respond to Up/Down keys to change the value while keeping the current spin focused
719             View nextFocusedView = currentFocusedView;
720             if (direction == View.FocusDirection.Up)
721             {
722                 this.Value += this.Step;
723                 nextFocusedView = textField;
724             }
725             else if (direction == View.FocusDirection.Down)
726             {
727                 this.Value -= this.Step;
728                 nextFocusedView = textField;
729             }
730             else
731             {
732                 // Return null
733                 return null;
734             }
735
736             return nextFocusedView;
737         }
738     }
739 }