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