2 * Copyright(c) 2021 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 using System.Collections.Generic;
20 using System.ComponentModel;
21 using Tizen.NUI.BaseComponents;
23 namespace Tizen.NUI.Components
26 /// The Navigator is a class which navigates pages with stack methods such as Push and Pop.
29 /// With Transition class, Navigator supports smooth transition of View pair between two Pages
30 /// by using <see cref="PushWithTransition(Page)"/> and <see cref="PopWithTransition()"/> methods.
31 /// If current top Page and next top Page have <see cref="View"/>s those have same TransitionTag,
32 /// Navigator creates smooth transition motion for them.
33 /// Navigator.Transition property can be used to set properties of the Transition such as TimePeriod and AlphaFunction.
34 /// When all transitions are finished, Navigator calls a callback methods those connected on the "TransitionFinished" event.
38 /// Navigator navigator = new Navigator()
40 /// TimePeriod = new TimePeriod(500),
41 /// AlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseInOutSine)
44 /// View view = new View()
46 /// TransitionOptions = new TransitionOptions()
48 /// /* Set properties for the transition of this View */
52 /// ContentPage newPage = new ContentPage()
57 /// Navigator.PushWithTransition(newPage);
60 /// <since_tizen> 9 </since_tizen>
61 public class Navigator : Control
63 private const int DefaultTransitionDuration = 500;
65 //This will be replaced with view transition class instance.
66 private Animation curAnimation = null;
68 //This will be replaced with view transition class instance.
69 private Animation newAnimation = null;
71 private TransitionSet transitionSet = null;
73 private Transition transition = new Transition()
75 TimePeriod = new TimePeriod(DefaultTransitionDuration),
76 AlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Default),
79 private bool transitionFinished = true;
81 //TODO: Needs to consider how to remove disposed window from dictionary.
82 //Two dictionaries are required to remove disposed navigator from dictionary.
83 private static Dictionary<Window, Navigator> windowNavigator = new Dictionary<Window, Navigator>();
84 private static Dictionary<Navigator, Window> navigatorWindow = new Dictionary<Navigator, Window>();
86 private List<Page> navigationPages = new List<Page>();
89 /// Creates a new instance of a Navigator.
91 /// <since_tizen> 9 </since_tizen>
92 public Navigator() : base()
94 Layout = new AbsoluteLayout();
98 /// An event fired when Transition has been finished.
100 /// <since_tizen> 9 </since_tizen>
101 public event EventHandler<EventArgs> TransitionFinished;
104 /// Returns the count of pages in Navigator.
106 /// <since_tizen> 9 </since_tizen>
107 public int PageCount => navigationPages.Count;
110 /// Transition properties for the transition of View pair having same transition tag.
112 /// <since_tizen> 9 </since_tizen>
113 public Transition Transition
126 /// Pushes a page to Navigator.
127 /// If the page is already in Navigator, then it is not pushed.
129 /// <param name="page">The page to push to Navigator.</param>
130 /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
131 /// <since_tizen> 9 </since_tizen>
132 public void PushWithTransition(Page page)
134 if (!transitionFinished)
136 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
142 throw new ArgumentNullException(nameof(page), "page should not be null.");
145 //Duplicate page is not pushed.
146 if (navigationPages.Contains(page)) return;
148 var topPage = Peek();
156 navigationPages.Add(page);
158 page.Navigator = this;
161 page.InvokeAppearing();
162 topPage.InvokeDisappearing();
164 transitionSet = CreateTransition(topPage, page, true);
165 transitionSet.Finished += (object sender, EventArgs e) =>
167 if (page is DialogPage == false)
169 topPage.SetVisible(false);
173 page.InvokeAppeared();
174 topPage.InvokeDisappeared();
176 transitionFinished = false;
180 /// Pops the top page from Navigator.
182 /// <returns>The popped page.</returns>
183 /// <exception cref="InvalidOperationException">Thrown when there is no page in Navigator.</exception>
184 /// <since_tizen> 9 </since_tizen>
185 public Page PopWithTransition()
187 if (!transitionFinished)
189 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
193 if (navigationPages.Count == 0)
195 throw new InvalidOperationException("There is no page in Navigator.");
198 var topPage = Peek();
200 if (navigationPages.Count == 1)
205 var newTopPage = navigationPages[navigationPages.Count - 2];
207 // newTopPage.RaiseAbove(topPage);
210 newTopPage.InvokeAppearing();
211 topPage.InvokeDisappearing();
213 transitionSet = CreateTransition(topPage, newTopPage, false);
214 transitionSet.Finished += (object sender, EventArgs e) =>
217 topPage.SetVisible(true);
220 newTopPage.InvokeAppeared();
221 topPage.InvokeDisappeared();
223 transitionFinished = false;
229 /// Pushes a page to Navigator.
230 /// If the page is already in Navigator, then it is not pushed.
232 /// <param name="page">The page to push to Navigator.</param>
233 /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
234 /// <since_tizen> 9 </since_tizen>
235 public void Push(Page page)
237 if (!transitionFinished)
239 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
245 throw new ArgumentNullException(nameof(page), "page should not be null.");
248 //Duplicate page is not pushed.
249 if (navigationPages.Contains(page)) return;
259 navigationPages.Add(page);
261 page.Navigator = this;
264 page.InvokeAppearing();
265 curTop.InvokeDisappearing();
267 //TODO: The following transition codes will be replaced with view transition.
268 InitializeAnimation();
270 if (page is DialogPage == false)
272 curAnimation = new Animation(1000);
273 curAnimation.AnimateTo(curTop, "Opacity", 1.0f, 0, 1000);
274 curAnimation.EndAction = Animation.EndActions.StopFinal;
275 curAnimation.Finished += (object sender, EventArgs args) =>
277 curTop.SetVisible(false);
280 curTop.InvokeDisappeared();
285 page.SetVisible(true);
286 newAnimation = new Animation(1000);
287 newAnimation.AnimateTo(page, "Opacity", 1.0f, 0, 1000);
288 newAnimation.EndAction = Animation.EndActions.StopFinal;
289 newAnimation.Finished += (object sender, EventArgs e) =>
292 page.InvokeAppeared();
299 /// Pops the top page from Navigator.
301 /// <returns>The popped page.</returns>
302 /// <exception cref="InvalidOperationException">Thrown when there is no page in Navigator.</exception>
303 /// <since_tizen> 9 </since_tizen>
306 if (!transitionFinished)
308 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
312 if (navigationPages.Count == 0)
314 throw new InvalidOperationException("There is no page in Navigator.");
319 if (navigationPages.Count == 1)
325 var newTop = navigationPages[navigationPages.Count - 2];
328 newTop.InvokeAppearing();
329 curTop.InvokeDisappearing();
331 //TODO: The following transition codes will be replaced with view transition.
332 InitializeAnimation();
334 if (curTop is DialogPage == false)
336 curAnimation = new Animation(1000);
337 curAnimation.AnimateTo(curTop, "Opacity", 0.0f, 0, 1000);
338 curAnimation.EndAction = Animation.EndActions.StopFinal;
339 curAnimation.Finished += (object sender, EventArgs e) =>
341 //Removes the current top page after transition is finished.
343 curTop.Opacity = 1.0f;
346 curTop.InvokeDisappeared();
350 newTop.Opacity = 1.0f;
351 newTop.SetVisible(true);
352 newAnimation = new Animation(1000);
353 newAnimation.AnimateTo(newTop, "Opacity", 1.0f, 0, 1000);
354 newAnimation.EndAction = Animation.EndActions.StopFinal;
355 newAnimation.Finished += (object sender, EventArgs e) =>
358 newTop.InvokeAppeared();
371 /// Returns the page of the given index in Navigator.
372 /// The indices of pages in Navigator are basically the order of pushing or inserting to Navigator.
373 /// So a page's index in Navigator can be changed whenever push/insert or pop/remove occurs.
375 /// <param name="index">The index of a page in Navigator.</param>
376 /// <returns>The page of the given index in Navigator.</returns>
377 /// <exception cref="ArgumentOutOfRangeException">Thrown when the argument index is less than 0, or greater than the number of pages.</exception>
378 public Page GetPage(int index)
380 if ((index < 0) || (index > navigationPages.Count))
382 throw new ArgumentOutOfRangeException(nameof(index), "index should be greater than or equal to 0, and less than or equal to the number of pages.");
385 return navigationPages[index];
389 /// Returns the current index of the given page in Navigator.
390 /// The indices of pages in Navigator are basically the order of pushing or inserting to Navigator.
391 /// So a page's index in Navigator can be changed whenever push/insert or pop/remove occurs.
393 /// <param name="page">The page in Navigator.</param>
394 /// <returns>The index of the given page in Navigator. If the given page is not in the Navigator, then -1 is returned.</returns>
395 /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
396 /// <since_tizen> 9 </since_tizen>
397 public int IndexOf(Page page)
401 throw new ArgumentNullException(nameof(page), "page should not be null.");
404 for (int i = 0; i < navigationPages.Count; i++)
406 if (navigationPages[i] == page)
416 /// Inserts a page at the specified index of Navigator.
417 /// The indices of pages in Navigator are basically the order of pushing or inserting to Navigator.
418 /// So a page's index in Navigator can be changed whenever push/insert or pop/remove occurs.
419 /// To find the current index of a page in Navigator, please use IndexOf(page).
420 /// If the page is already in Navigator, then it is not inserted.
422 /// <param name="index">The index of a page in Navigator where the page will be inserted.</param>
423 /// <param name="page">The page to insert to Navigator.</param>
424 /// <exception cref="ArgumentOutOfRangeException">Thrown when the argument index is less than 0, or greater than the number of pages.</exception>
425 /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
426 /// <since_tizen> 9 </since_tizen>
427 public void Insert(int index, Page page)
429 if ((index < 0) || (index > navigationPages.Count))
431 throw new ArgumentOutOfRangeException(nameof(index), "index should be greater than or equal to 0, and less than or equal to the number of pages.");
436 throw new ArgumentNullException(nameof(page), "page should not be null.");
439 //Duplicate page is not pushed.
440 if (navigationPages.Contains(page)) return;
442 //TODO: The following transition codes will be replaced with view transition.
443 InitializeAnimation();
445 if (index == PageCount)
448 page.SetVisible(true);
452 page.SetVisible(false);
456 navigationPages.Insert(index, page);
458 page.Navigator = this;
462 /// Inserts a page to Navigator before an existing page.
463 /// If the page is already in Navigator, then it is not inserted.
465 /// <param name="before">The existing page, before which a page will be inserted.</param>
466 /// <param name="page">The page to insert to Navigator.</param>
467 /// <exception cref="ArgumentNullException">Thrown when the argument before is null.</exception>
468 /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
469 /// <exception cref="ArgumentException">Thrown when the argument before does not exist in Navigator.</exception>
470 /// <since_tizen> 9 </since_tizen>
471 public void InsertBefore(Page before, Page page)
475 throw new ArgumentNullException(nameof(before), "before should not be null.");
480 throw new ArgumentNullException(nameof(page), "page should not be null.");
483 //Find the index of before page.
484 int beforeIndex = navigationPages.FindIndex(x => x == before);
486 //before does not exist in Navigator.
487 if (beforeIndex == -1)
489 throw new ArgumentException("before does not exist in Navigator.", nameof(before));
492 Insert(beforeIndex, page);
496 /// Removes a page from Navigator.
498 /// <param name="page">The page to remove from Navigator.</param>
499 /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
500 /// <since_tizen> 9 </since_tizen>
501 public void Remove(Page page)
505 throw new ArgumentNullException(nameof(page), "page should not be null.");
508 //TODO: The following transition codes will be replaced with view transition.
509 InitializeAnimation();
511 if ((page == Peek()) && (PageCount >= 2))
513 navigationPages[PageCount - 2].Opacity = 1.0f;
514 navigationPages[PageCount - 2].SetVisible(true);
517 page.Navigator = null;
518 navigationPages.Remove(page);
523 /// Removes a page at the specified index of Navigator.
524 /// The indices of pages in Navigator are basically the order of pushing or inserting to Navigator.
525 /// So a page's index in Navigator can be changed whenever push/insert or pop/remove occurs.
526 /// To find the current index of a page in Navigator, please use IndexOf(page).
528 /// <param name="index">The index of a page in Navigator where the page will be removed.</param>
529 /// <exception cref="ArgumentOutOfRangeException">Thrown when the index is less than 0, or greater than or equal to the number of pages.</exception>
530 /// <since_tizen> 9 </since_tizen>
531 public void RemoveAt(int index)
533 if ((index < 0) || (index >= navigationPages.Count))
535 throw new ArgumentOutOfRangeException(nameof(index), "index should be greater than or equal to 0, and less than the number of pages.");
538 Remove(navigationPages[index]);
542 /// Returns the page at the top of Navigator.
544 /// <returns>The page at the top of Navigator.</returns>
545 /// <since_tizen> 9 </since_tizen>
548 if (navigationPages.Count == 0) return null;
550 return navigationPages[navigationPages.Count - 1];
554 /// Disposes Navigator and all children on it.
556 /// <param name="type">Dispose type.</param>
557 [EditorBrowsable(EditorBrowsableState.Never)]
558 protected override void Dispose(DisposeTypes type)
565 if (type == DisposeTypes.Explicit)
567 foreach (Page page in navigationPages)
569 Utility.Dispose(page);
571 navigationPages.Clear();
575 if (navigatorWindow.TryGetValue(this, out window) == true)
577 navigatorWindow.Remove(this);
578 windowNavigator.Remove(window);
586 /// Returns the default navigator of the given window.
588 /// <returns>The default navigator of the given window.</returns>
589 /// <exception cref="ArgumentNullException">Thrown when the argument window is null.</exception>
590 /// <since_tizen> 9 </since_tizen>
591 public static Navigator GetDefaultNavigator(Window window)
595 throw new ArgumentNullException(nameof(window), "window should not be null.");
598 if (windowNavigator.ContainsKey(window) == true)
600 return windowNavigator[window];
603 var defaultNavigator = new Navigator();
604 defaultNavigator.WidthResizePolicy = ResizePolicyType.FillToParent;
605 defaultNavigator.HeightResizePolicy = ResizePolicyType.FillToParent;
606 window.Add(defaultNavigator);
607 windowNavigator.Add(window, defaultNavigator);
608 navigatorWindow.Add(defaultNavigator, window);
610 return defaultNavigator;
614 /// Create Transition between currentTopPage and newTopPage
616 /// <param name="currentTopPage">The top page of Navigator.</param>
617 /// <param name="newTopPage">The new top page after transition.</param>
618 /// <param name="pushTransition">True if this transition is for push new page</param>
619 private TransitionSet CreateTransition(Page currentTopPage, Page newTopPage, bool pushTransition)
621 currentTopPage.SetVisible(true);
622 newTopPage.SetVisible(true);
624 List<View> taggedViewsInNewTopPage = new List<View>();
625 RetrieveTaggedViews(taggedViewsInNewTopPage, newTopPage, true);
626 List<View> taggedViewsInCurrentTopPage = new List<View>();
627 RetrieveTaggedViews(taggedViewsInCurrentTopPage, currentTopPage, true);
629 List<KeyValuePair<View, View>> sameTaggedViewPair = new List<KeyValuePair<View, View>>();
630 foreach(View currentTopPageView in taggedViewsInCurrentTopPage)
632 bool findPair = false;
633 foreach(View newTopPageView in taggedViewsInNewTopPage)
635 if((currentTopPageView.TransitionOptions != null) && (newTopPageView.TransitionOptions != null) &&
636 currentTopPageView.TransitionOptions?.TransitionTag == newTopPageView.TransitionOptions?.TransitionTag)
638 sameTaggedViewPair.Add(new KeyValuePair<View, View>(currentTopPageView, newTopPageView));
645 taggedViewsInNewTopPage.Remove(sameTaggedViewPair[sameTaggedViewPair.Count - 1].Value);
648 foreach(KeyValuePair<View, View> pair in sameTaggedViewPair)
650 taggedViewsInCurrentTopPage.Remove(pair.Key);
653 TransitionSet newTransitionSet = new TransitionSet();
654 sameTaggedViewPair.Reverse();
655 foreach(KeyValuePair<View, View> pair in sameTaggedViewPair)
657 TransitionItem pairTransition = transition.CreateTransition(pair.Key, pair.Value);
658 if(pair.Value.TransitionOptions?.TransitionWithChild ?? false)
660 pairTransition.TransitionWithChild = true;
662 newTransitionSet.AddTransition(pairTransition);
665 newTransitionSet.Finished += (object sender, EventArgs e) =>
667 transitionFinished = true;
668 InvokeTransitionFinished();
669 transitionSet.Dispose();
670 currentTopPage.Opacity = 1.0f;
673 // default appearing/disappearing transition - fast fade (half duration compaired with that of view pair transition)
674 int duration = (transition.TimePeriod.DurationMilliseconds + transition.TimePeriod.DelayMilliseconds);
675 float durationSeconds = (float)duration / 1000.0f;
677 if (!pushTransition || newTopPage is DialogPage == false)
679 TransitionItemBase disappearingTransition = currentTopPage.DisappearingTransition.CreateTransition(currentTopPage, false);
680 newTransitionSet.AddTransition(disappearingTransition);
681 disappearingTransition.TransitionWithChild = true;
683 if (pushTransition || currentTopPage is DialogPage == false)
685 TransitionItemBase appearingTransition = newTopPage.AppearingTransition.CreateTransition(newTopPage, true);
686 appearingTransition.TransitionWithChild = true;
687 newTransitionSet.AddTransition(appearingTransition);
690 newTransitionSet.Play();
692 return newTransitionSet;
696 /// Retrieve Tagged Views in the view tree.
698 /// <param name="taggedViews">Returned tagged view list..</param>
699 /// <param name="view">Root View to get tagged child View.</param>
700 /// <param name="isPage">Flag to check current View is page or not</param>
701 private void RetrieveTaggedViews(List<View> taggedViews, View view, bool isPage)
705 if (!string.IsNullOrEmpty(view.TransitionOptions?.TransitionTag))
707 taggedViews.Add((view as View));
710 if (view.ChildCount == 0)
715 if (view.TransitionOptions?.TransitionWithChild ?? false)
720 foreach (View child in view.Children)
722 RetrieveTaggedViews(taggedViews, child, false);
726 internal void InvokeTransitionFinished()
728 TransitionFinished?.Invoke(this, new EventArgs());
731 //TODO: The following transition codes will be replaced with view transition.
732 private void InitializeAnimation()
734 if (curAnimation != null)
737 curAnimation.Clear();
741 if (newAnimation != null)
744 newAnimation.Clear();