[NUI] Modify the default action of back navigation
[platform/core/csapi/tizenfx.git] / src / Tizen.NUI.Components / Controls / Navigation / Navigator.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 Tizen.NUI.BaseComponents;
22 using Tizen.NUI.Binding;
23
24 namespace Tizen.NUI.Components
25 {
26     /// <summary>
27     /// PoppedEventArgs is a class to record <see cref="Navigator.Popped"/> event arguments which will be sent to user.
28     /// </summary>
29     /// <since_tizen> 9 </since_tizen>
30     public class PoppedEventArgs : EventArgs
31     {
32         /// <summary>
33         /// Page popped by Navigator.
34         /// </summary>
35         /// <since_tizen> 9 </since_tizen>
36         public Page Page { get; internal set; }
37     }
38
39     /// <summary>
40     /// The Navigator is a class which navigates pages with stack methods such as Push and Pop.
41     /// </summary>
42     /// <remarks>
43     /// With Transition class, Navigator supports smooth transition of View pair between two Pages
44     /// by using <see cref="PushWithTransition(Page)"/> and <see cref="PopWithTransition()"/> methods.
45     /// If current top Page and next top Page have <see cref="View"/>s those have same TransitionTag,
46     /// Navigator creates smooth transition motion for them.
47     /// Navigator.Transition property can be used to set properties of the Transition such as TimePeriod and AlphaFunction.
48     /// When all transitions are finished, Navigator calls a callback methods those connected on the "TransitionFinished" event.
49     /// </remarks>
50     /// <example>
51     /// <code>
52     /// Navigator navigator = new Navigator()
53     /// {
54     ///     TimePeriod = new TimePeriod(500),
55     ///     AlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseInOutSine)
56     /// };
57     ///
58     /// View view = new View()
59     /// {
60     ///     TransitionOptions = new TransitionOptions()
61     ///     {
62     ///         /* Set properties for the transition of this View */
63     ///     }
64     /// };
65     ///
66     /// ContentPage newPage = new ContentPage()
67     /// {
68     ///     Content = view,
69     /// };
70     ///
71     /// Navigator.PushWithTransition(newPage);
72     /// </code>
73     /// </example>
74     /// <since_tizen> 9 </since_tizen>
75     public class Navigator : Control
76     {
77         /// <summary>
78         /// TransitionProperty
79         /// </summary>
80         [EditorBrowsable(EditorBrowsableState.Never)]
81         public static readonly BindableProperty TransitionProperty = BindableProperty.Create(nameof(Transition), typeof(Transition), typeof(Navigator), null, propertyChanged: (bindable, oldValue, newValue) =>
82         {
83             var instance = (Navigator)bindable;
84             if (newValue != null)
85             {
86                 instance.InternalTransition = newValue as Transition;
87             }
88         },
89         defaultValueCreator: (bindable) =>
90         {
91             var instance = (Navigator)bindable;
92             return instance.InternalTransition;
93         });
94
95         /// <summary>
96         /// EnableBackNavigationProperty
97         /// </summary>
98         [EditorBrowsable(EditorBrowsableState.Never)]
99         public static readonly BindableProperty EnableBackNavigationProperty = BindableProperty.Create(nameof(EnableBackNavigation), typeof(bool), typeof(Navigator), default(bool), propertyChanged: (bindable, oldValue, newValue) =>
100         {
101             var instance = (Navigator)bindable;
102             if (newValue != null)
103             {
104                 instance.InternalEnableBackNavigation = (bool)newValue;
105             }
106         },
107         defaultValueCreator: (bindable) =>
108         {
109             var instance = (Navigator)bindable;
110             return instance.InternalEnableBackNavigation;
111         });
112
113         private const int DefaultTransitionDuration = 300;
114
115         //This will be replaced with view transition class instance.
116         private Animation curAnimation = null;
117
118         //This will be replaced with view transition class instance.
119         private Animation newAnimation = null;
120
121         private TransitionSet transitionSet = null;
122
123         private Transition transition = new Transition()
124         {
125             TimePeriod = new TimePeriod(DefaultTransitionDuration),
126             AlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Default),
127         };
128
129         private bool transitionFinished = true;
130
131         //TODO: Needs to consider how to remove disposed window from dictionary.
132         //Two dictionaries are required to remove disposed navigator from dictionary.
133         private static Dictionary<Window, Navigator> windowNavigator = new Dictionary<Window, Navigator>();
134         private static Dictionary<Navigator, Window> navigatorWindow = new Dictionary<Navigator, Window>();
135
136         private List<Page> navigationPages = new List<Page>();
137
138         private bool enableBackNavigation = true;
139
140         private Window parentWindow;
141
142         private void OnWindowKeyEvent(object sender, Window.KeyEventArgs e)
143         {
144             if (!EnableBackNavigation)
145             {
146                 return;
147             }
148
149             if ((e.Key.State == Key.StateType.Up) && ((e.Key.KeyPressedName == "Escape") || (e.Key.KeyPressedName == "BackSpace") || (e.Key.KeyPressedName == "XF86Back")))
150             {
151                 OnBackNavigation(new BackNavigationEventArgs());
152             }
153         }
154
155         private void OnAddedToWindow(object sender, EventArgs e)
156         {
157             parentWindow = Window.Get(this);
158             parentWindow.KeyEvent += OnWindowKeyEvent;
159         }
160
161         private void OnRemovedFromWindow(object sender, EventArgs e)
162         {
163             parentWindow.KeyEvent -= OnWindowKeyEvent;
164             parentWindow = null;
165         }
166
167         private void Initialize()
168         {
169             Layout = new AbsoluteLayout();
170
171             AddedToWindow += OnAddedToWindow;
172             RemovedFromWindow += OnRemovedFromWindow;
173         }
174
175         /// <summary>
176         /// Creates a new instance of a Navigator.
177         /// </summary>
178         /// <since_tizen> 9 </since_tizen>
179         public Navigator() : base()
180         {
181             Initialize();
182         }
183
184         /// <summary>
185         /// Creates a new instance of Navigator with style.
186         /// </summary>
187         /// <param name="style">Creates Navigator by special style defined in UX.</param>
188         [EditorBrowsable(EditorBrowsableState.Never)]
189         public Navigator(string style) : base(style)
190         {
191             Initialize();
192         }
193
194         /// <summary>
195         /// Creates a new instance of a Navigator with style.
196         /// </summary>
197         /// <param name="style">A style applied to the newly created Navigator.</param>
198         [EditorBrowsable(EditorBrowsableState.Never)]
199         public Navigator(ControlStyle style) : base(style)
200         {
201             Initialize();
202         }
203
204         /// <inheritdoc/>
205         [EditorBrowsable(EditorBrowsableState.Never)]
206         public override void OnInitialize()
207         {
208             base.OnInitialize();
209
210             AccessibilityRole = Role.PageTabList;
211         }
212
213         /// <summary>
214         /// An event fired when Transition has been finished.
215         /// </summary>
216         /// <since_tizen> 9 </since_tizen>
217         public event EventHandler<EventArgs> TransitionFinished;
218
219         /// <summary>
220         /// An event fired when Pop of a page has been finished.
221         /// </summary>
222         /// <remarks>
223         /// When you free resources in the Popped event handler, please make sure if the popped page is the page you find.
224         /// </remarks>
225         /// <since_tizen> 9 </since_tizen>
226         public event EventHandler<PoppedEventArgs> Popped;
227
228         /// <summary>
229         /// Returns the count of pages in Navigator.
230         /// </summary>
231         /// <since_tizen> 9 </since_tizen>
232         public int PageCount => navigationPages.Count;
233
234         /// <summary>
235         /// Transition properties for the transition of View pair having same transition tag.
236         /// </summary>
237         /// <since_tizen> 9 </since_tizen>
238         public Transition Transition
239         {
240             get
241             {
242                 return GetValue(TransitionProperty) as Transition;
243             }
244             set
245             {
246                 SetValue(TransitionProperty, value);
247                 NotifyPropertyChanged();
248             }
249         }
250         private Transition InternalTransition
251         {
252             set
253             {
254                 transition = value;
255             }
256             get
257             {
258                 return transition;
259             }
260         }
261
262         /// <summary>
263         /// Pushes a page to Navigator.
264         /// If the page is already in Navigator, then it is not pushed.
265         /// </summary>
266         /// <param name="page">The page to push to Navigator.</param>
267         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
268         /// <since_tizen> 9 </since_tizen>
269         public void PushWithTransition(Page page)
270         {
271             if (!transitionFinished)
272             {
273                 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
274                 return;
275             }
276
277             if (page == null)
278             {
279                 throw new ArgumentNullException(nameof(page), "page should not be null.");
280             }
281
282             //Duplicate page is not pushed.
283             if (navigationPages.Contains(page)) return;
284
285             var topPage = Peek();
286
287             if (!topPage)
288             {
289                 Insert(0, page);
290                 return;
291             }
292
293             navigationPages.Add(page);
294             Add(page);
295             page.Navigator = this;
296
297             //Invoke Page events
298             page.InvokeAppearing();
299             topPage.InvokeDisappearing();
300             topPage.SaveKeyFocus();
301
302             transitionSet = CreateTransitions(topPage, page, true);
303             transitionSet.Finished += (object sender, EventArgs e) =>
304             {
305                 if (page is DialogPage == false)
306                 {
307                     topPage.SetVisible(false);
308                 }
309
310                 // Need to update Content of the new page
311                 ShowContentOfPage(page);
312
313                 //Invoke Page events
314                 page.InvokeAppeared();
315                 page.RestoreKeyFocus();
316                 
317                 topPage.InvokeDisappeared();
318                 NotifyAccessibilityStatesChangeOfPages(topPage, page);
319             };
320             transitionFinished = false;
321         }
322
323         /// <summary>
324         /// Pops the top page from Navigator.
325         /// </summary>
326         /// <returns>The popped page.</returns>
327         /// <exception cref="InvalidOperationException">Thrown when there is no page in Navigator.</exception>
328         /// <since_tizen> 9 </since_tizen>
329         public Page PopWithTransition()
330         {
331             if (!transitionFinished)
332             {
333                 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
334                 return null;
335             }
336
337             if (navigationPages.Count == 0)
338             {
339                 throw new InvalidOperationException("There is no page in Navigator.");
340             }
341
342             var topPage = Peek();
343
344             if (navigationPages.Count == 1)
345             {
346                 Remove(topPage);
347
348                 //Invoke Popped event
349                 Popped?.Invoke(this, new PoppedEventArgs() { Page = topPage });
350
351                 return topPage;
352             }
353             var newTopPage = navigationPages[navigationPages.Count - 2];
354
355             //Invoke Page events
356             newTopPage.InvokeAppearing();
357             topPage.InvokeDisappearing();
358             topPage.SaveKeyFocus();
359
360             transitionSet = CreateTransitions(topPage, newTopPage, false);
361             transitionSet.Finished += (object sender, EventArgs e) =>
362             {
363                 Remove(topPage);
364                 topPage.SetVisible(true);
365
366                 // Need to update Content of the new page
367                 ShowContentOfPage(newTopPage);
368
369                 //Invoke Page events
370                 newTopPage.InvokeAppeared();
371                 newTopPage.RestoreKeyFocus();
372
373                 topPage.InvokeDisappeared();
374
375                 //Invoke Popped event
376                 Popped?.Invoke(this, new PoppedEventArgs() { Page = topPage });
377             };
378             transitionFinished = false;
379
380             return topPage;
381         }
382
383         /// <summary>
384         /// Pushes a page to Navigator.
385         /// If the page is already in Navigator, then it is not pushed.
386         /// </summary>
387         /// <param name="page">The page to push to Navigator.</param>
388         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
389         /// <since_tizen> 9 </since_tizen>
390         public void Push(Page page)
391         {
392             if (!transitionFinished)
393             {
394                 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
395                 return;
396             }
397
398             if (page == null)
399             {
400                 throw new ArgumentNullException(nameof(page), "page should not be null.");
401             }
402
403             //Duplicate page is not pushed.
404             if (navigationPages.Contains(page)) return;
405
406             var curTop = Peek();
407
408             if (!curTop)
409             {
410                 Insert(0, page);
411                 return;
412             }
413
414             navigationPages.Add(page);
415             Add(page);
416             page.Navigator = this;
417
418             //Invoke Page events
419             page.InvokeAppearing();
420             curTop.InvokeDisappearing();
421
422             curTop.SaveKeyFocus();
423
424             //TODO: The following transition codes will be replaced with view transition.
425             InitializeAnimation();
426
427             if (page is DialogPage == false)
428             {
429                 curAnimation = new Animation(DefaultTransitionDuration);
430                 curAnimation.AnimateTo(curTop, "PositionX", 0.0f, 0, DefaultTransitionDuration);
431                 curAnimation.EndAction = Animation.EndActions.StopFinal;
432                 curAnimation.Finished += (object sender, EventArgs args) =>
433                 {
434                     curTop.SetVisible(false);
435
436                     //Invoke Page events
437                     curTop.InvokeDisappeared();
438                 };
439                 curAnimation.Play();
440
441                 page.PositionX = SizeWidth;
442                 page.SetVisible(true);
443                 // Set Content visible because it was hidden by HideContentOfPage.
444                 (page as ContentPage)?.Content?.SetVisible(true);
445
446                 newAnimation = new Animation(DefaultTransitionDuration);
447                 newAnimation.AnimateTo(page, "PositionX", 0.0f, 0, DefaultTransitionDuration);
448                 newAnimation.EndAction = Animation.EndActions.StopFinal;
449                 newAnimation.Finished += (object sender, EventArgs e) =>
450                 {
451                     // Need to update Content of the new page
452                     ShowContentOfPage(page);
453
454                     //Invoke Page events
455                     page.InvokeAppeared();
456                     NotifyAccessibilityStatesChangeOfPages(curTop, page);
457
458                     page.RestoreKeyFocus();
459                 };
460                 newAnimation.Play();
461             }
462             else
463             {
464                 ShowContentOfPage(page);
465                 page.RestoreKeyFocus();
466             }
467         }
468
469         /// <summary>
470         /// Pops the top page from Navigator.
471         /// </summary>
472         /// <returns>The popped page.</returns>
473         /// <exception cref="InvalidOperationException">Thrown when there is no page in Navigator.</exception>
474         /// <since_tizen> 9 </since_tizen>
475         public Page Pop()
476         {
477             if (!transitionFinished)
478             {
479                 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
480                 return null;
481             }
482
483             if (navigationPages.Count == 0)
484             {
485                 throw new InvalidOperationException("There is no page in Navigator.");
486             }
487
488             var curTop = Peek();
489
490             if (navigationPages.Count == 1)
491             {
492                 Remove(curTop);
493
494                 //Invoke Popped event
495                 Popped?.Invoke(this, new PoppedEventArgs() { Page = curTop });
496
497                 return curTop;
498             }
499
500             var newTop = navigationPages[navigationPages.Count - 2];
501
502             //Invoke Page events
503             newTop.InvokeAppearing();
504             curTop.InvokeDisappearing();
505             curTop.SaveKeyFocus();
506
507             //TODO: The following transition codes will be replaced with view transition.
508             InitializeAnimation();
509
510             if (curTop is DialogPage == false)
511             {
512                 curAnimation = new Animation(DefaultTransitionDuration);
513                 curAnimation.AnimateTo(curTop, "PositionX", SizeWidth, 0, DefaultTransitionDuration);
514                 curAnimation.EndAction = Animation.EndActions.StopFinal;
515                 curAnimation.Finished += (object sender, EventArgs e) =>
516                 {
517                     //Removes the current top page after transition is finished.
518                     Remove(curTop);
519
520                     curTop.PositionX = 0.0f;
521
522                     //Invoke Page events
523                     curTop.InvokeDisappeared();
524
525                     //Invoke Popped event
526                     Popped?.Invoke(this, new PoppedEventArgs() { Page = curTop });
527                 };
528                 curAnimation.Play();
529
530                 newTop.SetVisible(true);
531                 // Set Content visible because it was hidden by HideContentOfPage.
532                 (newTop as ContentPage)?.Content?.SetVisible(true);
533
534                 newAnimation = new Animation(DefaultTransitionDuration);
535                 newAnimation.AnimateTo(newTop, "PositionX", 0.0f, 0, DefaultTransitionDuration);
536                 newAnimation.EndAction = Animation.EndActions.StopFinal;
537                 newAnimation.Finished += (object sender, EventArgs e) =>
538                 {
539                     // Need to update Content of the new page
540                     ShowContentOfPage(newTop);
541
542                     //Invoke Page events
543                     newTop.InvokeAppeared();
544
545                     newTop.RestoreKeyFocus();
546                 };
547                 newAnimation.Play();
548             }
549             else
550             {
551                 Remove(curTop);
552                 newTop.RestoreKeyFocus();
553             }
554
555             return curTop;
556         }
557
558         /// <summary>
559         /// Returns the page of the given index in Navigator.
560         /// The indices of pages in Navigator are basically the order of pushing or inserting to Navigator.
561         /// So a page's index in Navigator can be changed whenever push/insert or pop/remove occurs.
562         /// </summary>
563         /// <param name="index">The index of a page in Navigator.</param>
564         /// <returns>The page of the given index in Navigator.</returns>
565         /// <exception cref="ArgumentOutOfRangeException">Thrown when the argument index is less than 0, or greater than the number of pages.</exception>
566         public Page GetPage(int index)
567         {
568             if ((index < 0) || (index > navigationPages.Count))
569             {
570                 throw new ArgumentOutOfRangeException(nameof(index), "index should be greater than or equal to 0, and less than or equal to the number of pages.");
571             }
572
573             return navigationPages[index];
574         }
575
576         /// <summary>
577         /// Returns the current index of the given page in Navigator.
578         /// The indices of pages in Navigator are basically the order of pushing or inserting to Navigator.
579         /// So a page's index in Navigator can be changed whenever push/insert or pop/remove occurs.
580         /// </summary>
581         /// <param name="page">The page in Navigator.</param>
582         /// <returns>The index of the given page in Navigator. If the given page is not in the Navigator, then -1 is returned.</returns>
583         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
584         /// <since_tizen> 9 </since_tizen>
585         public int IndexOf(Page page)
586         {
587             if (page == null)
588             {
589                 throw new ArgumentNullException(nameof(page), "page should not be null.");
590             }
591
592             for (int i = 0; i < navigationPages.Count; i++)
593             {
594                 if (navigationPages[i] == page)
595                 {
596                     return i;
597                 }
598             }
599
600             return -1;
601         }
602
603         /// <summary>
604         /// Inserts a page at the specified index of Navigator.
605         /// The indices of pages in Navigator are basically the order of pushing or inserting to Navigator.
606         /// So a page's index in Navigator can be changed whenever push/insert or pop/remove occurs.
607         /// To find the current index of a page in Navigator, please use IndexOf(page).
608         /// If the page is already in Navigator, then it is not inserted.
609         /// </summary>
610         /// <param name="index">The index of a page in Navigator where the page will be inserted.</param>
611         /// <param name="page">The page to insert to Navigator.</param>
612         /// <exception cref="ArgumentOutOfRangeException">Thrown when the argument index is less than 0, or greater than the number of pages.</exception>
613         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
614         /// <since_tizen> 9 </since_tizen>
615         public void Insert(int index, Page page)
616         {
617             if ((index < 0) || (index > navigationPages.Count))
618             {
619                 throw new ArgumentOutOfRangeException(nameof(index), "index should be greater than or equal to 0, and less than or equal to the number of pages.");
620             }
621
622             if (page == null)
623             {
624                 throw new ArgumentNullException(nameof(page), "page should not be null.");
625             }
626
627             //Duplicate page is not pushed.
628             if (navigationPages.Contains(page)) return;
629
630             //TODO: The following transition codes will be replaced with view transition.
631             InitializeAnimation();
632
633             ShowContentOfPage(page);
634
635             if (index == PageCount)
636             {
637                 page.SetVisible(true);
638             }
639             else
640             {
641                 page.SetVisible(false);
642             }
643
644             navigationPages.Insert(index, page);
645             Add(page);
646             page.SiblingOrder = index;
647             page.Navigator = this;
648             if (index == PageCount - 1)
649             {
650                 if (PageCount > 1)
651                 {
652                     NotifyAccessibilityStatesChangeOfPages(navigationPages[PageCount - 2], page);
653                 }
654                 else
655                 {
656                     NotifyAccessibilityStatesChangeOfPages(null, page);
657                 }
658             }
659         }
660
661         /// <summary>
662         /// Inserts a page to Navigator before an existing page.
663         /// If the page is already in Navigator, then it is not inserted.
664         /// </summary>
665         /// <param name="before">The existing page, before which a page will be inserted.</param>
666         /// <param name="page">The page to insert to Navigator.</param>
667         /// <exception cref="ArgumentNullException">Thrown when the argument before is null.</exception>
668         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
669         /// <exception cref="ArgumentException">Thrown when the argument before does not exist in Navigator.</exception>
670         /// <since_tizen> 9 </since_tizen>
671         public void InsertBefore(Page before, Page page)
672         {
673             if (before == null)
674             {
675                 throw new ArgumentNullException(nameof(before), "before should not be null.");
676             }
677
678             if (page == null)
679             {
680                 throw new ArgumentNullException(nameof(page), "page should not be null.");
681             }
682
683             //Find the index of before page.
684             int beforeIndex = navigationPages.FindIndex(x => x == before);
685
686             //before does not exist in Navigator.
687             if (beforeIndex == -1)
688             {
689                 throw new ArgumentException("before does not exist in Navigator.", nameof(before));
690             }
691
692             Insert(beforeIndex, page);
693         }
694
695         /// <summary>
696         /// Removes a page from Navigator.
697         /// </summary>
698         /// <param name="page">The page to remove from Navigator.</param>
699         /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
700         /// <since_tizen> 9 </since_tizen>
701         public void Remove(Page page)
702         {
703             if (page == null)
704             {
705                 throw new ArgumentNullException(nameof(page), "page should not be null.");
706             }
707
708             //TODO: The following transition codes will be replaced with view transition.
709             InitializeAnimation();
710
711             HideContentOfPage(page);
712
713             if (page == Peek())
714             {
715                 if (PageCount >= 2)
716                 {
717                     navigationPages[PageCount - 2].SetVisible(true);
718                     NotifyAccessibilityStatesChangeOfPages(page, navigationPages[PageCount - 2]);
719                 }
720                 else if (PageCount == 1)
721                 {
722                     NotifyAccessibilityStatesChangeOfPages(page, null);
723                 }
724             }
725             page.Navigator = null;
726             navigationPages.Remove(page);
727             base.Remove(page);
728         }
729
730         /// <summary>
731         /// Removes a page at the specified index of Navigator.
732         /// The indices of pages in Navigator are basically the order of pushing or inserting to Navigator.
733         /// So a page's index in Navigator can be changed whenever push/insert or pop/remove occurs.
734         /// To find the current index of a page in Navigator, please use IndexOf(page).
735         /// </summary>
736         /// <param name="index">The index of a page in Navigator where the page will be removed.</param>
737         /// <exception cref="ArgumentOutOfRangeException">Thrown when the index is less than 0, or greater than or equal to the number of pages.</exception>
738         /// <since_tizen> 9 </since_tizen>
739         public void RemoveAt(int index)
740         {
741             if ((index < 0) || (index >= navigationPages.Count))
742             {
743                 throw new ArgumentOutOfRangeException(nameof(index), "index should be greater than or equal to 0, and less than the number of pages.");
744             }
745
746             Remove(navigationPages[index]);
747         }
748
749         /// <summary>
750         /// Returns the page at the top of Navigator.
751         /// </summary>
752         /// <returns>The page at the top of Navigator.</returns>
753         /// <since_tizen> 9 </since_tizen>
754         public Page Peek()
755         {
756             if (navigationPages.Count == 0) return null;
757
758             return navigationPages[navigationPages.Count - 1];
759         }
760
761         /// <summary>
762         /// Gets or sets if Navigator proceeds back navigation when back button or back key is pressed and released.
763         /// Back navigation pops the peek page if Navigator has more than one page.
764         /// If Navigator has only one page, then the current program is exited.
765         /// </summary>
766         [EditorBrowsable(EditorBrowsableState.Never)]
767         public bool EnableBackNavigation
768         {
769             get
770             {
771                 return (bool)GetValue(EnableBackNavigationProperty);
772             }
773             set
774             {
775                 SetValue(EnableBackNavigationProperty, value);
776                 NotifyPropertyChanged();
777             }
778         }
779
780         private bool InternalEnableBackNavigation
781         {
782             set
783             {
784                 enableBackNavigation = value;
785             }
786             get
787             {
788                 return enableBackNavigation;
789             }
790         }
791
792         /// <summary>
793         /// Disposes Navigator and all children on it.
794         /// </summary>
795         /// <param name="type">Dispose type.</param>
796         [EditorBrowsable(EditorBrowsableState.Never)]
797         protected override void Dispose(DisposeTypes type)
798         {
799             if (disposed)
800             {
801                 return;
802             }
803
804             if (type == DisposeTypes.Explicit)
805             {
806                 foreach (Page page in navigationPages)
807                 {
808                     Utility.Dispose(page);
809                 }
810                 navigationPages.Clear();
811
812                 Window window;
813
814                 if (navigatorWindow.TryGetValue(this, out window) == true)
815                 {
816                     navigatorWindow.Remove(this);
817                     windowNavigator.Remove(window);
818                 }
819
820                 AddedToWindow -= OnAddedToWindow;
821                 RemovedFromWindow -= OnRemovedFromWindow;
822             }
823
824             base.Dispose(type);
825         }
826
827         /// <summary>
828         /// Returns the default navigator of the given window.
829         /// </summary>
830         /// <returns>The default navigator of the given window.</returns>
831         /// <exception cref="ArgumentNullException">Thrown when the argument window is null.</exception>
832         /// <since_tizen> 9 </since_tizen>
833         public static Navigator GetDefaultNavigator(Window window)
834         {
835             if (window == null)
836             {
837                 throw new ArgumentNullException(nameof(window), "window should not be null.");
838             }
839
840             if (windowNavigator.ContainsKey(window) == true)
841             {
842                 return windowNavigator[window];
843             }
844
845             var defaultNavigator = new Navigator();
846             defaultNavigator.WidthResizePolicy = ResizePolicyType.FillToParent;
847             defaultNavigator.HeightResizePolicy = ResizePolicyType.FillToParent;
848             window.Add(defaultNavigator);
849             windowNavigator.Add(window, defaultNavigator);
850             navigatorWindow.Add(defaultNavigator, window);
851
852             return defaultNavigator;
853         }
854
855         /// <summary>
856         /// Sets the default navigator of the given window.
857         /// SetDefaultNavigator does not remove the previous default navigator from the window.
858         /// Therefore, if a user does not want to show the previous default navigator, then it should be removed from the window manually.
859         /// </summary>
860         /// <exception cref="ArgumentNullException">Thrown when the argument window is null.</exception>
861         /// <exception cref="ArgumentNullException">Thrown when the argument newNavigator is null.</exception>
862         [EditorBrowsable(EditorBrowsableState.Never)]
863         public static void SetDefaultNavigator(Window window, Navigator newNavigator)
864         {
865             if (window == null)
866             {
867                 throw new ArgumentNullException(nameof(window), "window should not be null.");
868             }
869
870             if (newNavigator == null)
871             {
872                 throw new ArgumentNullException(nameof(newNavigator), "newNavigator should not be null.");
873             }
874
875             if (windowNavigator.ContainsKey(window) == true)
876             {
877                 var oldNavigator = windowNavigator[window];
878                 if (oldNavigator == newNavigator)
879                 {
880                     return;
881                 }
882
883                 navigatorWindow.Remove(oldNavigator);
884                 windowNavigator.Remove(window);
885             }
886
887             window.Add(newNavigator);
888             windowNavigator.Add(window, newNavigator);
889             navigatorWindow.Add(newNavigator, window);
890         }
891
892         /// <summary>
893         /// Called when the back navigation is started.
894         /// Back navigation pops the peek page if Navigator has more than one page.
895         /// If Navigator has only one page, then the current program is exited.
896         /// </summary>
897         /// <param name="eventArgs">The back navigation information.</param>
898         [EditorBrowsable(EditorBrowsableState.Never)]
899         protected virtual void OnBackNavigation(BackNavigationEventArgs eventArgs)
900         {
901             if (PageCount >= 1)
902             {
903                 if (Peek().EnableBackNavigation)
904                 {
905                     Peek().NavigateBack();
906                 }
907             }
908             else
909             {
910                 NUIApplication.Current?.Exit();
911             }
912         }
913
914         /// <summary>
915         /// Called when the back navigation is required outside Navigator.
916         /// </summary>
917         internal void NavigateBack()
918         {
919             OnBackNavigation(new BackNavigationEventArgs());
920         }
921
922         /// <summary>
923         /// Create Transitions between currentTopPage and newTopPage
924         /// </summary>
925         /// <param name="currentTopPage">The top page of Navigator.</param>
926         /// <param name="newTopPage">The new top page after transition.</param>
927         /// <param name="pushTransition">True if this transition is for push new page</param>
928         private TransitionSet CreateTransitions(Page currentTopPage, Page newTopPage, bool pushTransition)
929         {
930             currentTopPage.SetVisible(true);
931             // Set Content visible because it was hidden by HideContentOfPage.
932             (currentTopPage as ContentPage)?.Content?.SetVisible(true);
933
934             newTopPage.SetVisible(true);
935             // Set Content visible because it was hidden by HideContentOfPage.
936             (newTopPage as ContentPage)?.Content?.SetVisible(true);
937
938             List<View> taggedViewsInNewTopPage = new List<View>();
939             RetrieveTaggedViews(taggedViewsInNewTopPage, newTopPage, true);
940             List<View> taggedViewsInCurrentTopPage = new List<View>();
941             RetrieveTaggedViews(taggedViewsInCurrentTopPage, currentTopPage, true);
942
943             List<KeyValuePair<View, View>> sameTaggedViewPair = new List<KeyValuePair<View, View>>();
944             foreach (View currentTopPageView in taggedViewsInCurrentTopPage)
945             {
946                 bool findPair = false;
947                 foreach (View newTopPageView in taggedViewsInNewTopPage)
948                 {
949                     if ((currentTopPageView.TransitionOptions != null) && (newTopPageView.TransitionOptions != null) &&
950                         currentTopPageView.TransitionOptions?.TransitionTag == newTopPageView.TransitionOptions?.TransitionTag)
951                     {
952                         sameTaggedViewPair.Add(new KeyValuePair<View, View>(currentTopPageView, newTopPageView));
953                         findPair = true;
954                         break;
955                     }
956                 }
957                 if (findPair)
958                 {
959                     taggedViewsInNewTopPage.Remove(sameTaggedViewPair[sameTaggedViewPair.Count - 1].Value);
960                 }
961             }
962             foreach (KeyValuePair<View, View> pair in sameTaggedViewPair)
963             {
964                 taggedViewsInCurrentTopPage.Remove(pair.Key);
965             }
966
967             TransitionSet newTransitionSet = new TransitionSet();
968             foreach (KeyValuePair<View, View> pair in sameTaggedViewPair)
969             {
970                 TransitionItem pairTransition = transition.CreateTransition(pair.Key, pair.Value, pushTransition);
971                 if (pair.Value.TransitionOptions?.TransitionWithChild ?? false)
972                 {
973                     pairTransition.TransitionWithChild = true;
974                 }
975                 newTransitionSet.AddTransition(pairTransition);
976             }
977
978             newTransitionSet.Finished += (object sender, EventArgs e) =>
979             {
980                 if (newTopPage.Layout != null)
981                 {
982                     newTopPage.Layout.RequestLayout();
983                 }
984                 if (currentTopPage.Layout != null)
985                 {
986                     currentTopPage.Layout.RequestLayout();
987                 }
988                 transitionFinished = true;
989                 InvokeTransitionFinished();
990                 transitionSet.Dispose();
991             };
992
993             if (!pushTransition || newTopPage is DialogPage == false)
994             {
995                 View transitionView = (currentTopPage is ContentPage) ? (currentTopPage as ContentPage).Content : (currentTopPage as DialogPage).Content;
996                 if (currentTopPage.DisappearingTransition != null && transitionView != null)
997                 {
998                     TransitionItemBase disappearingTransition = currentTopPage.DisappearingTransition.CreateTransition(transitionView, false);
999                     disappearingTransition.TransitionWithChild = true;
1000                     newTransitionSet.AddTransition(disappearingTransition);
1001                 }
1002                 else
1003                 {
1004                     currentTopPage.SetVisible(false);
1005                 }
1006             }
1007             if (pushTransition || currentTopPage is DialogPage == false)
1008             {
1009                 View transitionView = (newTopPage is ContentPage) ? (newTopPage as ContentPage).Content : (newTopPage as DialogPage).Content;
1010                 if (newTopPage.AppearingTransition != null && transitionView != null)
1011                 {
1012                     TransitionItemBase appearingTransition = newTopPage.AppearingTransition.CreateTransition(transitionView, true);
1013                     appearingTransition.TransitionWithChild = true;
1014                     newTransitionSet.AddTransition(appearingTransition);
1015                 }
1016             }
1017
1018             newTransitionSet.Play();
1019
1020             return newTransitionSet;
1021         }
1022
1023         /// <summary>
1024         /// Retrieve Tagged Views in the view tree.
1025         /// </summary>
1026         /// <param name="taggedViews">Returned tagged view list..</param>
1027         /// <param name="view">Root View to get tagged child View.</param>
1028         /// <param name="isRoot">Flag to check current View is page or not</param>
1029         private void RetrieveTaggedViews(List<View> taggedViews, View view, bool isRoot)
1030         {
1031             if (!isRoot && view.TransitionOptions != null)
1032             {
1033                 if (!string.IsNullOrEmpty(view.TransitionOptions?.TransitionTag))
1034                 {
1035                     taggedViews.Add((view as View));
1036                     if (view.TransitionOptions.TransitionWithChild)
1037                     {
1038                         return;
1039                     }
1040                 }
1041
1042             }
1043
1044             foreach (View child in view.Children)
1045             {
1046                 RetrieveTaggedViews(taggedViews, child, false);
1047             }
1048         }
1049
1050         /// <summary>
1051         /// Notify accessibility states change of pages.
1052         /// </summary>
1053         /// <param name="disappearedPage">Disappeared page</param>
1054         /// <param name="appearedPage">Appeared page</param>
1055         private void NotifyAccessibilityStatesChangeOfPages(Page disappearedPage, Page appearedPage)
1056         {
1057             if (disappearedPage != null)
1058             {
1059                 disappearedPage.UnregisterDefaultLabel();
1060                 //We can call disappearedPage.NotifyAccessibilityStatesChange
1061                 //To reduce accessibility events, we are using currently highlighted view instead
1062                 View curHighlightedView = Accessibility.Accessibility.GetCurrentlyHighlightedView();
1063                 if (curHighlightedView != null)
1064                 {
1065                     curHighlightedView.NotifyAccessibilityStatesChange(new AccessibilityStates(AccessibilityState.Visible, AccessibilityState.Showing), AccessibilityStatesNotifyMode.Single);
1066                 }
1067             }
1068
1069             if (appearedPage != null)
1070             {
1071                 appearedPage.RegisterDefaultLabel();
1072                 appearedPage.NotifyAccessibilityStatesChange(new AccessibilityStates(AccessibilityState.Visible, AccessibilityState.Showing), AccessibilityStatesNotifyMode.Single);
1073             }
1074         }
1075
1076         internal void InvokeTransitionFinished()
1077         {
1078             TransitionFinished?.Invoke(this, new EventArgs());
1079         }
1080
1081         //TODO: The following transition codes will be replaced with view transition.
1082         private void InitializeAnimation()
1083         {
1084             if (curAnimation != null)
1085             {
1086                 curAnimation.Stop();
1087                 curAnimation.Clear();
1088                 curAnimation = null;
1089             }
1090
1091             if (newAnimation != null)
1092             {
1093                 newAnimation.Stop();
1094                 newAnimation.Clear();
1095                 newAnimation = null;
1096             }
1097         }
1098
1099         // Show and Register Content of Page to Accessibility bridge
1100         private void ShowContentOfPage(Page page)
1101         {
1102             View content = (page is DialogPage) ? (page as DialogPage)?.Content : (page as ContentPage)?.Content;
1103             if (content != null)
1104             {
1105                 content.Show(); // Calls RegisterDefaultLabel()
1106             }
1107         }
1108
1109         // Hide and Remove Content of Page from Accessibility bridge
1110         private void HideContentOfPage(Page page)
1111         {
1112             View content = (page is DialogPage) ? (page as DialogPage)?.Content : (page as ContentPage)?.Content;
1113             if (content != null)
1114             {
1115                 content.Hide(); // Calls UnregisterDefaultLabel()
1116             }
1117         }
1118     }
1119
1120     /// <summary>
1121     /// BackNavigationEventArgs is a class to record back navigation event arguments which will sent to user.
1122     /// </summary>
1123     [EditorBrowsable(EditorBrowsableState.Never)]
1124     public class BackNavigationEventArgs : EventArgs
1125     {
1126     }
1127 }