[NUI] Remove CA2000 false alarm in Navigator (#2729)
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / Navigation / Navigator.cs
1 /*
2  * Copyright(c) 2020 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.CodeAnalysis;
22 using System.Threading.Tasks;
23 using Tizen.NUI.BaseComponents;
24 using Tizen.NUI.Binding;
25
26 namespace Tizen.NUI.Components
27 {
28     /// <summary>
29     /// The Navigator is a class which navigates pages with stack methods such
30     /// as Push and Pop.
31     /// </summary>
32     [EditorBrowsable(EditorBrowsableState.Never)]
33     public class Navigator : Control
34     {
35         //This will be replaced with view transition class instance.
36         private Animation _curAnimation = null;
37
38         //This will be replaced with view transition class instance.
39         private Animation _newAnimation = null;
40
41         //TODO: Needs to consider how to remove disposed window from dictionary.
42         //Two dictionaries are required to remove disposed navigator from dictionary.
43         private static Dictionary<Window, Navigator> windowNavigator = new Dictionary<Window, Navigator>();
44         private static Dictionary<Navigator, Window> navigatorWindow = new Dictionary<Navigator, Window>();
45
46         /// <summary>
47         /// Creates a new instance of a Navigator.
48         /// </summary>
49         [EditorBrowsable(EditorBrowsableState.Never)]
50         public Navigator() : base()
51         {
52         }
53
54         /// <summary>
55         /// List of pages of Navigator.
56         /// </summary>
57         [EditorBrowsable(EditorBrowsableState.Never)]
58         public List<Page> NavigationPages { get; } = new List<Page>();
59
60         /// <summary>
61         /// Pushes a page to Navigator.
62         /// If the page is already in Navigator, then it is not pushed.
63         /// </summary>
64         /// <param name="page">The page to push to Navigator.</param>
65         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
66         [EditorBrowsable(EditorBrowsableState.Never)]
67         public void Push(Page page)
68         {
69             if (page == null)
70             {
71                 throw new ArgumentNullException(nameof(page), "page should not be null.");
72             }
73
74             //Duplicate page is not pushed.
75             if (NavigationPages.Contains(page)) return;
76
77             var curTop = Peek();
78
79             if (!curTop)
80             {
81                 Insert(0, page);
82                 return;
83             }
84
85             NavigationPages.Add(page);
86             Add(page);
87
88             //Invoke Page events
89             page.InvokeAppearing();
90             curTop.InvokeDisappearing();
91
92             //TODO: The following transition codes will be replaced with view transition.
93             if (_curAnimation)
94             {
95                 _curAnimation.Stop();
96                 _curAnimation.Clear();
97             }
98
99             if (_newAnimation)
100             {
101                 _newAnimation.Stop();
102                 _newAnimation.Clear();
103             }
104
105             _curAnimation = new Animation(1000);
106             using (var scaleVec = new Vector3(0.0f, 0.0f, 1.0f))
107             {
108                 _curAnimation.AnimateTo(curTop, "Scale", scaleVec, 0, 1000);
109             }
110             _curAnimation.AnimateTo(curTop, "Opacity", 0.0f, 0, 1000);
111             _curAnimation.EndAction = Animation.EndActions.Discard;
112             _curAnimation.Play();
113
114             using (var scaleVec = new Vector3(0.0f, 0.0f, 1.0f))
115             {
116                 using (var scaleProp = new Tizen.NUI.PropertyValue(scaleVec))
117                 {
118                     Tizen.NUI.Object.SetProperty(page.SwigCPtr, Page.Property.SCALE, scaleProp);
119                 }
120             }
121             using (var scaleProp = new Tizen.NUI.PropertyValue(0.0f))
122             {
123                 Tizen.NUI.Object.SetProperty(page.SwigCPtr, Page.Property.OPACITY, scaleProp);
124             }
125             _newAnimation = new Animation(1000);
126             using (var scaleVec = new Vector3(1.0f, 1.0f, 1.0f))
127             {
128                 _newAnimation.AnimateTo(page, "Scale", scaleVec, 0, 1000);
129             }
130             _newAnimation.AnimateTo(page, "Opacity", 1.0f, 0, 1000);
131             _newAnimation.Play();
132         }
133
134         /// <summary>
135         /// Pops the top page from Navigator.
136         /// </summary>
137         /// <returns>The popped page.</returns>
138         /// <exception cref="InvalidOperationException">Thrown when there is no page in Navigator.</exception>
139         [EditorBrowsable(EditorBrowsableState.Never)]
140         public Page Pop()
141         {
142             if (NavigationPages.Count == 0)
143             {
144                 throw new InvalidOperationException("There is no page in Navigator.");
145             }
146
147             var curTop = Peek();
148
149             if (NavigationPages.Count == 1)
150             {
151                 Remove(curTop);
152                 return curTop;
153             }
154
155             var newTop = NavigationPages[NavigationPages.Count - 2];
156
157             //Invoke Page events
158             newTop.InvokeAppearing();
159             curTop.InvokeDisappearing();
160
161             //TODO: The following transition codes will be replaced with view transition.
162             if (_curAnimation)
163             {
164                 _curAnimation.Stop();
165                 _curAnimation.Clear();
166             }
167
168             if (_newAnimation)
169             {
170                 _newAnimation.Stop();
171                 _newAnimation.Clear();
172             }
173
174             _curAnimation = new Animation(1000);
175             using (var scaleVec = new Vector3(0.0f, 0.0f, 1.0f))
176             {
177                 _curAnimation.AnimateTo(curTop, "Scale", scaleVec, 0, 1000);
178             }
179             _curAnimation.AnimateTo(curTop, "Opacity", 0.0f, 0, 1000);
180             _curAnimation.Play();
181             _curAnimation.Finished += (object sender, EventArgs e) =>
182             {
183                 //Removes the current top page after transition is finished.
184                 Remove(curTop);
185             };
186
187             using (var scaleVec = new Vector3(0.0f, 0.0f, 1.0f))
188             {
189                 using (var scaleProp = new Tizen.NUI.PropertyValue(scaleVec))
190                 {
191                     Tizen.NUI.Object.SetProperty(newTop.SwigCPtr, Page.Property.SCALE, scaleProp);
192                 }
193             }
194             using (var opacityProp = new Tizen.NUI.PropertyValue(0.0f))
195             {
196                 Tizen.NUI.Object.SetProperty(newTop.SwigCPtr, Page.Property.OPACITY, opacityProp);
197             }
198             _newAnimation = new Animation(1000);
199             using (var scaleVec = new Vector3(1.0f, 1.0f, 1.0f))
200             {
201                 _newAnimation.AnimateTo(newTop, "Scale", scaleVec, 0, 1000);
202             }
203             _newAnimation.AnimateTo(newTop, "Opacity", 1.0f, 0, 1000);
204             _newAnimation.Play();
205
206             return curTop;
207         }
208
209         /// <summary>
210         /// Inserts a page at the specified index of Navigator.
211         /// If the page is already in Navigator, then it is not inserted.
212         /// </summary>
213         /// <param name="index">The index of Navigator where a page will be inserted.</param>
214         /// <param name="page">The page to insert to Navigator.</param>
215         /// <exception cref="ArgumentOutOfRangeException">Thrown when the argument index is less than 0, or greater than the number of pages.</exception>
216         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
217         [EditorBrowsable(EditorBrowsableState.Never)]
218         public void Insert(int index, Page page)
219         {
220             if ((index < 0) || (index > NavigationPages.Count))
221             {
222                 throw new ArgumentOutOfRangeException(nameof(index), "index should be greater than or equal to 0, and less than or equal to the number of pages.");
223             }
224
225             if (page == null)
226             {
227                 throw new ArgumentNullException(nameof(page), "page should not be null.");
228             }
229
230             //Duplicate page is not pushed.
231             if (NavigationPages.Contains(page)) return;
232
233             NavigationPages.Insert(index, page);
234             Add(page);
235         }
236
237         /// <summary>
238         /// Inserts a page to Navigator before an existing page.
239         /// If the page is already in Navigator, then it is not inserted.
240         /// </summary>
241         /// <param name="before">The existing page, before which a page will be inserted.</param>
242         /// <param name="page">The page to insert to Navigator.</param>
243         /// <exception cref="ArgumentNullException">Thrown when the argument before is null.</exception>
244         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
245         /// <exception cref="ArgumentException">Thrown when the argument before does not exist in Navigator.</exception>
246         [EditorBrowsable(EditorBrowsableState.Never)]
247         public void InsertBefore(Page before, Page page)
248         {
249             if (before == null)
250             {
251                 throw new ArgumentNullException(nameof(before), "before should not be null.");
252             }
253
254             if (page == null)
255             {
256                 throw new ArgumentNullException(nameof(page), "page should not be null.");
257             }
258
259             //Find the index of before page.
260             int beforeIndex = NavigationPages.FindIndex(x => x == before);
261
262             //before does not exist in Navigator.
263             if (beforeIndex == -1)
264             {
265                 throw new ArgumentException("before does not exist in Navigator.", nameof(before));
266             }
267
268             Insert(beforeIndex, page);
269         }
270
271         /// <summary>
272         /// Removes a page from Navigator.
273         /// </summary>
274         /// <param name="page">The page to remove from Navigator.</param>
275         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
276         [EditorBrowsable(EditorBrowsableState.Never)]
277         public void Remove(Page page)
278         {
279             if (page == null)
280             {
281                 throw new ArgumentNullException(nameof(page), "page should not be null.");
282             }
283
284             NavigationPages.Remove(page);
285             base.Remove(page);
286         }
287
288         /// <summary>
289         /// Removes a page at the specified index of Navigator.
290         /// </summary>
291         /// <param name="index">The index of Navigator where a page will be removed.</param>
292         /// <exception cref="ArgumentOutOfRangeException">Thrown when the index is less than 0, or greater than or equal to the number of pages.</exception>
293         [EditorBrowsable(EditorBrowsableState.Never)]
294         public void RemoveAt(int index)
295         {
296             if ((index < 0) || (index >= NavigationPages.Count))
297             {
298                 throw new ArgumentOutOfRangeException(nameof(index), "index should be greater than or equal to 0, and less than the number of pages.");
299             }
300
301             Remove(NavigationPages[index]);
302         }
303
304         /// <summary>
305         /// Returns the page at the top of Navigator.
306         /// </summary>
307         /// <returns>The page at the top of Navigator.</returns>
308         [EditorBrowsable(EditorBrowsableState.Never)]
309         public Page Peek()
310         {
311             if (NavigationPages.Count == 0) return null;
312
313             return NavigationPages[NavigationPages.Count - 1];
314         }
315
316         /// <summary>
317         /// Disposes Navigator and all children on it.
318         /// </summary>
319         /// <param name="type">Dispose type.</param>
320         [EditorBrowsable(EditorBrowsableState.Never)]
321         protected override void Dispose(DisposeTypes type)
322         {
323             if (disposed)
324             {
325                 return;
326             }
327
328             if (type == DisposeTypes.Explicit)
329             {
330                 foreach (Page page in NavigationPages)
331                 {
332                     Utility.Dispose(page);
333                 }
334                 NavigationPages.Clear();
335
336                 Window window;
337
338                 if (navigatorWindow.TryGetValue(this, out window) == true)
339                 {
340                     navigatorWindow.Remove(this);
341                     windowNavigator.Remove(window);
342                 }
343             }
344
345             base.Dispose(type);
346         }
347
348         /// <summary>
349         /// Returns the default navigator of the given window.
350         /// </summary>
351         /// <returns>The default navigator of the given window.</returns>
352         /// <exception cref="ArgumentNullException">Thrown when the argument window is null.</exception>
353         [EditorBrowsable(EditorBrowsableState.Never)]
354         public static Navigator GetDefaultNavigator(Window window)
355         {
356             if (window == null)
357             {
358                 throw new ArgumentNullException(nameof(window), "window should not be null.");
359             }
360
361             if (windowNavigator.ContainsKey(window) == true)
362             {
363                 return windowNavigator[window];
364             }
365
366             var defaultNavigator = new Navigator();
367             defaultNavigator.WidthResizePolicy = ResizePolicyType.FillToParent;
368             defaultNavigator.HeightResizePolicy = ResizePolicyType.FillToParent;
369             window.Add(defaultNavigator);
370             windowNavigator.Add(window, defaultNavigator);
371             navigatorWindow.Add(defaultNavigator, window);
372
373             return defaultNavigator;
374         }
375
376         /// <summary>
377         /// Shows a dialog by pushing a page containing dialog to default navigator.
378         /// </summary>
379         /// <param name="content">The content of Dialog.</param>
380         [EditorBrowsable(EditorBrowsableState.Never)]
381         [SuppressMessage("Microsoft.Reliability",
382                          "CA2000:DisposeObjectsBeforeLosingScope",
383                          Justification = "The pushed views are added to NavigationPages and are disposed in Navigator.Dispose().")]
384         public static void ShowDialog(View content = null)
385         {
386             var window = NUIApplication.GetDefaultWindow();
387             var defaultNavigator = window.GetDefaultNavigator();
388
389             var dialog = new Dialog(content);
390             SetDialogScrim(dialog);
391
392             var dialogPage = new Page(dialog);
393             defaultNavigator.Push(dialogPage);
394         }
395
396         /// <summary>
397         /// Shows an alert dialog by pushing a page containing the alert dialog
398         /// to default navigator.
399         /// </summary>
400         /// <param name="titleContent">The title content of AlertDialog.</param>
401         /// <param name="content">The content of AlertDialog.</param>
402         /// <param name="actionContent">The action content of AlertDialog.</param>
403         [EditorBrowsable(EditorBrowsableState.Never)]
404         [SuppressMessage("Microsoft.Reliability",
405                          "CA2000:DisposeObjectsBeforeLosingScope",
406                          Justification = "The pushed views are added to NavigationPages and are disposed in Navigator.Dispose().")]
407         public static void ShowAlertDialog(View titleContent, View content, View actionContent)
408         {
409             var window = NUIApplication.GetDefaultWindow();
410             var defaultNavigator = window.GetDefaultNavigator();
411
412             var dialog = new AlertDialog(titleContent, content, actionContent);
413             SetDialogScrim(dialog);
414
415             var dialogPage = new Page(dialog);
416             defaultNavigator.Push(dialogPage);
417         }
418
419         /// <summary>
420         /// Shows an alert dialog by pushing a page containing the alert dialog
421         /// to default navigator.
422         /// </summary>
423         /// <param name="title">The title of AlertDialog.</param>
424         /// <param name="message">The message of AlertDialog.</param>
425         /// <param name="positiveButtonText">The positive button text in the action content of AlertDialog.</param>
426         /// <param name="positiveButtonClickedHandler">The clicked callback of the positive button in the action content of AlertDialog.</param>
427         /// <param name="negativeButtonText">The negative button text in the action content of AlertDialog.</param>
428         /// <param name="negativeButtonClickedHandler">The clicked callback of the negative button in the action content of AlertDialog.</param>
429         [EditorBrowsable(EditorBrowsableState.Never)]
430         [SuppressMessage("Microsoft.Reliability",
431                          "CA2000:DisposeObjectsBeforeLosingScope",
432                          Justification = "The pushed views are added to NavigationPages and are disposed in Navigator.Dispose().")]
433         public static void ShowAlertDialog(string title = null, string message = null, string positiveButtonText = null, EventHandler<ClickedEventArgs> positiveButtonClickedHandler = null, string negativeButtonText = null, EventHandler<ClickedEventArgs> negativeButtonClickedHandler = null)
434         {
435             var window = NUIApplication.GetDefaultWindow();
436             var defaultNavigator = window.GetDefaultNavigator();
437
438             var dialog = new AlertDialog(title, message, positiveButtonText, positiveButtonClickedHandler, negativeButtonText, negativeButtonClickedHandler);
439             SetDialogScrim(dialog);
440
441             var dialogPage = new Page(dialog);
442             defaultNavigator.Push(dialogPage);
443         }
444
445
446         private static void SetDialogScrim(Dialog dialog)
447         {
448             if (dialog == null)
449             {
450                 return;
451             }
452
453             var window = NUIApplication.GetDefaultWindow();
454             var defaultNavigator = window.GetDefaultNavigator();
455             var defaultScrim = dialog.Scrim;
456
457             //Copies default scrim's GUI properties.
458             var scrim = new VisualView();
459             scrim.BackgroundColor = defaultScrim.BackgroundColor;
460             scrim.Size = defaultScrim.Size;
461             scrim.TouchEvent += (object source, View.TouchEventArgs e) =>
462             {
463                 if (e.Touch.GetState(0) == PointStateType.Up)
464                 {
465                     defaultNavigator.Pop();
466                 }
467
468                 return true;
469             };
470
471             dialog.Scrim = scrim;
472         }
473     }
474 } //namespace Tizen.NUI