c17cfe884f9044b527f118541debb2d657bab131
[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 pops the peek page when back button or back key is pressed and released.
763         /// </summary>
764         [EditorBrowsable(EditorBrowsableState.Never)]
765         public bool EnableBackNavigation
766         {
767             get
768             {
769                 return (bool)GetValue(EnableBackNavigationProperty);
770             }
771             set
772             {
773                 SetValue(EnableBackNavigationProperty, value);
774                 NotifyPropertyChanged();
775             }
776         }
777
778         private bool InternalEnableBackNavigation
779         {
780             set
781             {
782                 enableBackNavigation = value;
783             }
784             get
785             {
786                 return enableBackNavigation;
787             }
788         }
789
790         /// <summary>
791         /// Disposes Navigator and all children on it.
792         /// </summary>
793         /// <param name="type">Dispose type.</param>
794         [EditorBrowsable(EditorBrowsableState.Never)]
795         protected override void Dispose(DisposeTypes type)
796         {
797             if (disposed)
798             {
799                 return;
800             }
801
802             if (type == DisposeTypes.Explicit)
803             {
804                 foreach (Page page in navigationPages)
805                 {
806                     Utility.Dispose(page);
807                 }
808                 navigationPages.Clear();
809
810                 Window window;
811
812                 if (navigatorWindow.TryGetValue(this, out window) == true)
813                 {
814                     navigatorWindow.Remove(this);
815                     windowNavigator.Remove(window);
816                 }
817
818                 AddedToWindow -= OnAddedToWindow;
819                 RemovedFromWindow -= OnRemovedFromWindow;
820             }
821
822             base.Dispose(type);
823         }
824
825         /// <summary>
826         /// Returns the default navigator of the given window.
827         /// </summary>
828         /// <returns>The default navigator of the given window.</returns>
829         /// <exception cref="ArgumentNullException">Thrown when the argument window is null.</exception>
830         /// <since_tizen> 9 </since_tizen>
831         public static Navigator GetDefaultNavigator(Window window)
832         {
833             if (window == null)
834             {
835                 throw new ArgumentNullException(nameof(window), "window should not be null.");
836             }
837
838             if (windowNavigator.ContainsKey(window) == true)
839             {
840                 return windowNavigator[window];
841             }
842
843             var defaultNavigator = new Navigator();
844             defaultNavigator.WidthResizePolicy = ResizePolicyType.FillToParent;
845             defaultNavigator.HeightResizePolicy = ResizePolicyType.FillToParent;
846             window.Add(defaultNavigator);
847             windowNavigator.Add(window, defaultNavigator);
848             navigatorWindow.Add(defaultNavigator, window);
849
850             return defaultNavigator;
851         }
852
853         /// <summary>
854         /// Called when the back navigation is started.
855         /// </summary>
856         /// <param name="eventArgs">The back navigation information.</param>
857         [EditorBrowsable(EditorBrowsableState.Never)]
858         protected virtual void OnBackNavigation(BackNavigationEventArgs eventArgs)
859         {
860             if (PageCount >= 1)
861             {
862                 Tizen.Log.Info("NUI", $"Navigator pops the peek page.\n");
863                 Pop();
864             }
865         }
866
867         /// <summary>
868         /// Called when the back navigation is required outside Navigator.
869         /// </summary>
870         internal void NavigateBack()
871         {
872             OnBackNavigation(new BackNavigationEventArgs());
873         }
874
875         /// <summary>
876         /// Create Transitions between currentTopPage and newTopPage
877         /// </summary>
878         /// <param name="currentTopPage">The top page of Navigator.</param>
879         /// <param name="newTopPage">The new top page after transition.</param>
880         /// <param name="pushTransition">True if this transition is for push new page</param>
881         private TransitionSet CreateTransitions(Page currentTopPage, Page newTopPage, bool pushTransition)
882         {
883             currentTopPage.SetVisible(true);
884             // Set Content visible because it was hidden by HideContentOfPage.
885             (currentTopPage as ContentPage)?.Content?.SetVisible(true);
886
887             newTopPage.SetVisible(true);
888             // Set Content visible because it was hidden by HideContentOfPage.
889             (newTopPage as ContentPage)?.Content?.SetVisible(true);
890
891             List<View> taggedViewsInNewTopPage = new List<View>();
892             RetrieveTaggedViews(taggedViewsInNewTopPage, newTopPage, true);
893             List<View> taggedViewsInCurrentTopPage = new List<View>();
894             RetrieveTaggedViews(taggedViewsInCurrentTopPage, currentTopPage, true);
895
896             List<KeyValuePair<View, View>> sameTaggedViewPair = new List<KeyValuePair<View, View>>();
897             foreach (View currentTopPageView in taggedViewsInCurrentTopPage)
898             {
899                 bool findPair = false;
900                 foreach (View newTopPageView in taggedViewsInNewTopPage)
901                 {
902                     if ((currentTopPageView.TransitionOptions != null) && (newTopPageView.TransitionOptions != null) &&
903                         currentTopPageView.TransitionOptions?.TransitionTag == newTopPageView.TransitionOptions?.TransitionTag)
904                     {
905                         sameTaggedViewPair.Add(new KeyValuePair<View, View>(currentTopPageView, newTopPageView));
906                         findPair = true;
907                         break;
908                     }
909                 }
910                 if (findPair)
911                 {
912                     taggedViewsInNewTopPage.Remove(sameTaggedViewPair[sameTaggedViewPair.Count - 1].Value);
913                 }
914             }
915             foreach (KeyValuePair<View, View> pair in sameTaggedViewPair)
916             {
917                 taggedViewsInCurrentTopPage.Remove(pair.Key);
918             }
919
920             TransitionSet newTransitionSet = new TransitionSet();
921             foreach (KeyValuePair<View, View> pair in sameTaggedViewPair)
922             {
923                 TransitionItem pairTransition = transition.CreateTransition(pair.Key, pair.Value, pushTransition);
924                 if (pair.Value.TransitionOptions?.TransitionWithChild ?? false)
925                 {
926                     pairTransition.TransitionWithChild = true;
927                 }
928                 newTransitionSet.AddTransition(pairTransition);
929             }
930
931             newTransitionSet.Finished += (object sender, EventArgs e) =>
932             {
933                 if (newTopPage.Layout != null)
934                 {
935                     newTopPage.Layout.RequestLayout();
936                 }
937                 if (currentTopPage.Layout != null)
938                 {
939                     currentTopPage.Layout.RequestLayout();
940                 }
941                 transitionFinished = true;
942                 InvokeTransitionFinished();
943                 transitionSet.Dispose();
944             };
945
946             if (!pushTransition || newTopPage is DialogPage == false)
947             {
948                 View transitionView = (currentTopPage is ContentPage) ? (currentTopPage as ContentPage).Content : (currentTopPage as DialogPage).Content;
949                 if (currentTopPage.DisappearingTransition != null && transitionView != null)
950                 {
951                     TransitionItemBase disappearingTransition = currentTopPage.DisappearingTransition.CreateTransition(transitionView, false);
952                     disappearingTransition.TransitionWithChild = true;
953                     newTransitionSet.AddTransition(disappearingTransition);
954                 }
955                 else
956                 {
957                     currentTopPage.SetVisible(false);
958                 }
959             }
960             if (pushTransition || currentTopPage is DialogPage == false)
961             {
962                 View transitionView = (newTopPage is ContentPage) ? (newTopPage as ContentPage).Content : (newTopPage as DialogPage).Content;
963                 if (newTopPage.AppearingTransition != null && transitionView != null)
964                 {
965                     TransitionItemBase appearingTransition = newTopPage.AppearingTransition.CreateTransition(transitionView, true);
966                     appearingTransition.TransitionWithChild = true;
967                     newTransitionSet.AddTransition(appearingTransition);
968                 }
969             }
970
971             newTransitionSet.Play();
972
973             return newTransitionSet;
974         }
975
976         /// <summary>
977         /// Retrieve Tagged Views in the view tree.
978         /// </summary>
979         /// <param name="taggedViews">Returned tagged view list..</param>
980         /// <param name="view">Root View to get tagged child View.</param>
981         /// <param name="isRoot">Flag to check current View is page or not</param>
982         private void RetrieveTaggedViews(List<View> taggedViews, View view, bool isRoot)
983         {
984             if (!isRoot && view.TransitionOptions != null)
985             {
986                 if (!string.IsNullOrEmpty(view.TransitionOptions?.TransitionTag))
987                 {
988                     taggedViews.Add((view as View));
989                     if (view.TransitionOptions.TransitionWithChild)
990                     {
991                         return;
992                     }
993                 }
994
995             }
996
997             foreach (View child in view.Children)
998             {
999                 RetrieveTaggedViews(taggedViews, child, false);
1000             }
1001         }
1002
1003         /// <summary>
1004         /// Notify accessibility states change of pages.
1005         /// </summary>
1006         /// <param name="disappearedPage">Disappeared page</param>
1007         /// <param name="appearedPage">Appeared page</param>
1008         private void NotifyAccessibilityStatesChangeOfPages(Page disappearedPage, Page appearedPage)
1009         {
1010             if (disappearedPage != null)
1011             {
1012                 disappearedPage.UnregisterDefaultLabel();
1013                 //We can call disappearedPage.NotifyAccessibilityStatesChange
1014                 //To reduce accessibility events, we are using currently highlighted view instead
1015                 View curHighlightedView = Accessibility.Accessibility.GetCurrentlyHighlightedView();
1016                 if (curHighlightedView != null)
1017                 {
1018                     curHighlightedView.NotifyAccessibilityStatesChange(new AccessibilityStates(AccessibilityState.Visible, AccessibilityState.Showing), AccessibilityStatesNotifyMode.Single);
1019                 }
1020             }
1021
1022             if (appearedPage != null)
1023             {
1024                 appearedPage.RegisterDefaultLabel();
1025                 appearedPage.NotifyAccessibilityStatesChange(new AccessibilityStates(AccessibilityState.Visible, AccessibilityState.Showing), AccessibilityStatesNotifyMode.Single);
1026             }
1027         }
1028
1029         internal void InvokeTransitionFinished()
1030         {
1031             TransitionFinished?.Invoke(this, new EventArgs());
1032         }
1033
1034         //TODO: The following transition codes will be replaced with view transition.
1035         private void InitializeAnimation()
1036         {
1037             if (curAnimation != null)
1038             {
1039                 curAnimation.Stop();
1040                 curAnimation.Clear();
1041                 curAnimation = null;
1042             }
1043
1044             if (newAnimation != null)
1045             {
1046                 newAnimation.Stop();
1047                 newAnimation.Clear();
1048                 newAnimation = null;
1049             }
1050         }
1051
1052         // Show and Register Content of Page to Accessibility bridge
1053         private void ShowContentOfPage(Page page)
1054         {
1055             View content = (page is DialogPage) ? (page as DialogPage)?.Content : (page as ContentPage)?.Content;
1056             if (content != null)
1057             {
1058                 content.Show(); // Calls RegisterDefaultLabel()
1059             }
1060         }
1061
1062         // Hide and Remove Content of Page from Accessibility bridge
1063         private void HideContentOfPage(Page page)
1064         {
1065             View content = (page is DialogPage) ? (page as DialogPage)?.Content : (page as ContentPage)?.Content;
1066             if (content != null)
1067             {
1068                 content.Hide(); // Calls UnregisterDefaultLabel()
1069             }
1070         }
1071     }
1072
1073     /// <summary>
1074     /// BackNavigationEventArgs is a class to record back navigation event arguments which will sent to user.
1075     /// </summary>
1076     [EditorBrowsable(EditorBrowsableState.Never)]
1077     public class BackNavigationEventArgs : EventArgs
1078     {
1079     }
1080 }