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