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 System.Diagnostics.CodeAnalysis;
22 using Tizen.NUI.BaseComponents;
24 namespace Tizen.NUI.Components
27 /// 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 PushWithTransition(Page) and PopWithTransition() methods.
31 /// If there is View pair of current top Page and next top Page those have same View.TransitionOptions.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.Transition = new Transition()
40 /// TimePeriod = new TimePeriod(0.5),
41 /// AlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.EaseInOutSine)
44 /// Navigator.PushWithTransition(newPage);
48 [EditorBrowsable(EditorBrowsableState.Never)]
49 public class Navigator : Control
51 private static readonly float DefaultTransitionDuration = 0.5f;
53 //This will be replaced with view transition class instance.
54 private Animation curAnimation = null;
56 //This will be replaced with view transition class instance.
57 private Animation newAnimation = null;
59 private TransitionSet transitionSet = null;
61 private Transition transition = new Transition()
63 TimePeriod = new TimePeriod(DefaultTransitionDuration),
64 AlphaFunction = new AlphaFunction(AlphaFunction.BuiltinFunctions.Default),
67 private bool transitionFinished = true;
69 //TODO: Needs to consider how to remove disposed window from dictionary.
70 //Two dictionaries are required to remove disposed navigator from dictionary.
71 private static Dictionary<Window, Navigator> windowNavigator = new Dictionary<Window, Navigator>();
72 private static Dictionary<Navigator, Window> navigatorWindow = new Dictionary<Navigator, Window>();
75 /// Creates a new instance of a Navigator.
77 [EditorBrowsable(EditorBrowsableState.Never)]
78 public Navigator() : base()
80 Layout = new AbsoluteLayout();
84 /// An event for the page disappearing signal which can be used to subscribe or unsubscribe the event handler provided by the user.
86 [EditorBrowsable(EditorBrowsableState.Never)]
87 public event EventHandler<EventArgs> TransitionFinished;
90 /// List of pages of Navigator.
92 [EditorBrowsable(EditorBrowsableState.Never)]
93 public List<Page> NavigationPages { get; } = new List<Page>();
96 /// Transition properties for the transition of View pair those have same transition tag.
98 [EditorBrowsable(EditorBrowsableState.Never)]
99 public Transition Transition
112 /// Pushes a page to Navigator.
113 /// If the page is already in Navigator, then it is not pushed.
115 /// <param name="page">The page to push to Navigator.</param>
116 /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
117 [EditorBrowsable(EditorBrowsableState.Never)]
118 public void PushWithTransition(Page page)
120 if (!transitionFinished)
122 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
128 throw new ArgumentNullException(nameof(page), "page should not be null.");
131 //Duplicate page is not pushed.
132 if (NavigationPages.Contains(page)) return;
134 var topPage = Peek();
142 NavigationPages.Add(page);
146 page.InvokeAppearing();
147 topPage.InvokeDisappearing();
149 transitionSet = CreateTransition(topPage, page, true);
150 transitionSet.Finished += (object sender, EventArgs e) =>
152 topPage.SetVisible(false);
154 transitionFinished = false;
158 /// Pops the top page from Navigator.
160 /// <returns>The popped page.</returns>
161 /// <exception cref="InvalidOperationException">Thrown when there is no page in Navigator.</exception>
162 [EditorBrowsable(EditorBrowsableState.Never)]
163 public Page PopWithTransition()
165 if (!transitionFinished)
167 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
171 if (NavigationPages.Count == 0)
173 throw new InvalidOperationException("There is no page in Navigator.");
176 var topPage = Peek();
178 if (NavigationPages.Count == 1)
183 var newTopPage = NavigationPages[NavigationPages.Count - 2];
185 // newTopPage.RaiseAbove(topPage);
188 newTopPage.InvokeAppearing();
189 topPage.InvokeDisappearing();
191 transitionSet = CreateTransition(topPage, newTopPage, false);
192 transitionSet.Finished += (object sender, EventArgs e) =>
195 topPage.SetVisible(true);
197 transitionFinished = false;
203 /// Pushes a page to Navigator.
204 /// If the page is already in Navigator, then it is not pushed.
206 /// <param name="page">The page to push to Navigator.</param>
207 /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
208 [EditorBrowsable(EditorBrowsableState.Never)]
209 public void Push(Page page)
211 if (!transitionFinished)
213 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
219 throw new ArgumentNullException(nameof(page), "page should not be null.");
222 //Duplicate page is not pushed.
223 if (NavigationPages.Contains(page)) return;
233 NavigationPages.Add(page);
237 page.InvokeAppearing();
238 curTop.InvokeDisappearing();
240 //TODO: The following transition codes will be replaced with view transition.
244 curAnimation.Clear();
250 newAnimation.Clear();
253 curAnimation = new Animation(1000);
254 using (var scaleVec = new Vector3(0.0f, 0.0f, 1.0f))
256 curAnimation.AnimateTo(curTop, "Scale", scaleVec, 0, 1000);
258 curAnimation.AnimateTo(curTop, "Opacity", 0.0f, 0, 1000);
259 curAnimation.EndAction = Animation.EndActions.Discard;
262 using (var scaleVec = new Vector3(0.0f, 0.0f, 1.0f))
264 using (var scaleProp = new Tizen.NUI.PropertyValue(scaleVec))
266 Tizen.NUI.Object.SetProperty(page.SwigCPtr, Page.Property.SCALE, scaleProp);
269 using (var scaleProp = new Tizen.NUI.PropertyValue(0.0f))
271 Tizen.NUI.Object.SetProperty(page.SwigCPtr, Page.Property.OPACITY, scaleProp);
273 newAnimation = new Animation(1000);
274 using (var scaleVec = new Vector3(1.0f, 1.0f, 1.0f))
276 newAnimation.AnimateTo(page, "Scale", scaleVec, 0, 1000);
278 newAnimation.AnimateTo(page, "Opacity", 1.0f, 0, 1000);
283 /// Pops the top page from Navigator.
285 /// <returns>The popped page.</returns>
286 /// <exception cref="InvalidOperationException">Thrown when there is no page in Navigator.</exception>
287 [EditorBrowsable(EditorBrowsableState.Never)]
290 if (!transitionFinished)
292 Tizen.Log.Error("NUI", "Transition is still not finished.\n");
296 if (NavigationPages.Count == 0)
298 throw new InvalidOperationException("There is no page in Navigator.");
303 if (NavigationPages.Count == 1)
309 var newTop = NavigationPages[NavigationPages.Count - 2];
312 newTop.InvokeAppearing();
313 curTop.InvokeDisappearing();
315 //TODO: The following transition codes will be replaced with view transition.
319 curAnimation.Clear();
325 newAnimation.Clear();
328 curAnimation = new Animation(1000);
329 using (var scaleVec = new Vector3(0.0f, 0.0f, 1.0f))
331 curAnimation.AnimateTo(curTop, "Scale", scaleVec, 0, 1000);
333 curAnimation.AnimateTo(curTop, "Opacity", 0.0f, 0, 1000);
335 curAnimation.Finished += (object sender, EventArgs e) =>
337 //Removes the current top page after transition is finished.
341 using (var scaleVec = new Vector3(0.0f, 0.0f, 1.0f))
343 using (var scaleProp = new Tizen.NUI.PropertyValue(scaleVec))
345 Tizen.NUI.Object.SetProperty(newTop.SwigCPtr, Page.Property.SCALE, scaleProp);
348 using (var opacityProp = new Tizen.NUI.PropertyValue(0.0f))
350 Tizen.NUI.Object.SetProperty(newTop.SwigCPtr, Page.Property.OPACITY, opacityProp);
352 newAnimation = new Animation(1000);
353 using (var scaleVec = new Vector3(1.0f, 1.0f, 1.0f))
355 newAnimation.AnimateTo(newTop, "Scale", scaleVec, 0, 1000);
357 newAnimation.AnimateTo(newTop, "Opacity", 1.0f, 0, 1000);
364 /// Inserts a page at the specified index of Navigator.
365 /// If the page is already in Navigator, then it is not inserted.
367 /// <param name="index">The index of Navigator where a page will be inserted.</param>
368 /// <param name="page">The page to insert to Navigator.</param>
369 /// <exception cref="ArgumentOutOfRangeException">Thrown when the argument index is less than 0, or greater than the number of pages.</exception>
370 /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
371 [EditorBrowsable(EditorBrowsableState.Never)]
372 public void Insert(int index, Page page)
374 if ((index < 0) || (index > NavigationPages.Count))
376 throw new ArgumentOutOfRangeException(nameof(index), "index should be greater than or equal to 0, and less than or equal to the number of pages.");
381 throw new ArgumentNullException(nameof(page), "page should not be null.");
384 //Duplicate page is not pushed.
385 if (NavigationPages.Contains(page)) return;
387 NavigationPages.Insert(index, page);
392 /// Inserts a page to Navigator before an existing page.
393 /// If the page is already in Navigator, then it is not inserted.
395 /// <param name="before">The existing page, before which a page will be inserted.</param>
396 /// <param name="page">The page to insert to Navigator.</param>
397 /// <exception cref="ArgumentNullException">Thrown when the argument before is null.</exception>
398 /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
399 /// <exception cref="ArgumentException">Thrown when the argument before does not exist in Navigator.</exception>
400 [EditorBrowsable(EditorBrowsableState.Never)]
401 public void InsertBefore(Page before, Page page)
405 throw new ArgumentNullException(nameof(before), "before should not be null.");
410 throw new ArgumentNullException(nameof(page), "page should not be null.");
413 //Find the index of before page.
414 int beforeIndex = NavigationPages.FindIndex(x => x == before);
416 //before does not exist in Navigator.
417 if (beforeIndex == -1)
419 throw new ArgumentException("before does not exist in Navigator.", nameof(before));
422 Insert(beforeIndex, page);
426 /// Removes a page from Navigator.
428 /// <param name="page">The page to remove from Navigator.</param>
429 /// <exception cref="ArgumentNullException">Thrown when the argument page is null.</exception>
430 [EditorBrowsable(EditorBrowsableState.Never)]
431 public void Remove(Page page)
435 throw new ArgumentNullException(nameof(page), "page should not be null.");
438 NavigationPages.Remove(page);
443 /// Removes a page at the specified index of Navigator.
445 /// <param name="index">The index of Navigator where a page will be removed.</param>
446 /// <exception cref="ArgumentOutOfRangeException">Thrown when the index is less than 0, or greater than or equal to the number of pages.</exception>
447 [EditorBrowsable(EditorBrowsableState.Never)]
448 public void RemoveAt(int index)
450 if ((index < 0) || (index >= NavigationPages.Count))
452 throw new ArgumentOutOfRangeException(nameof(index), "index should be greater than or equal to 0, and less than the number of pages.");
455 Remove(NavigationPages[index]);
459 /// Returns the page at the top of Navigator.
461 /// <returns>The page at the top of Navigator.</returns>
462 [EditorBrowsable(EditorBrowsableState.Never)]
465 if (NavigationPages.Count == 0) return null;
467 return NavigationPages[NavigationPages.Count - 1];
471 /// Disposes Navigator and all children on it.
473 /// <param name="type">Dispose type.</param>
474 [EditorBrowsable(EditorBrowsableState.Never)]
475 protected override void Dispose(DisposeTypes type)
482 if (type == DisposeTypes.Explicit)
484 foreach (Page page in NavigationPages)
486 Utility.Dispose(page);
488 NavigationPages.Clear();
492 if (navigatorWindow.TryGetValue(this, out window) == true)
494 navigatorWindow.Remove(this);
495 windowNavigator.Remove(window);
503 /// Returns the default navigator of the given window.
505 /// <returns>The default navigator of the given window.</returns>
506 /// <exception cref="ArgumentNullException">Thrown when the argument window is null.</exception>
507 [EditorBrowsable(EditorBrowsableState.Never)]
508 public static Navigator GetDefaultNavigator(Window window)
512 throw new ArgumentNullException(nameof(window), "window should not be null.");
515 if (windowNavigator.ContainsKey(window) == true)
517 return windowNavigator[window];
520 var defaultNavigator = new Navigator();
521 defaultNavigator.WidthResizePolicy = ResizePolicyType.FillToParent;
522 defaultNavigator.HeightResizePolicy = ResizePolicyType.FillToParent;
523 window.Add(defaultNavigator);
524 windowNavigator.Add(window, defaultNavigator);
525 navigatorWindow.Add(defaultNavigator, window);
527 return defaultNavigator;
531 /// Shows a dialog by pushing a page containing dialog to default navigator.
533 /// <param name="content">The content of Dialog.</param>
534 [EditorBrowsable(EditorBrowsableState.Never)]
535 [SuppressMessage("Microsoft.Reliability",
536 "CA2000:DisposeObjectsBeforeLosingScope",
537 Justification = "The pushed views are added to NavigationPages and are disposed in Navigator.Dispose().")]
538 public static void ShowDialog(View content = null)
540 var window = NUIApplication.GetDefaultWindow();
541 var defaultNavigator = window.GetDefaultNavigator();
543 var dialog = new Dialog(content);
544 SetDialogScrim(dialog);
546 var dialogPage = new Page(dialog);
547 defaultNavigator.Push(dialogPage);
551 /// Shows an alert dialog by pushing a page containing the alert dialog
552 /// to default navigator.
554 /// <param name="titleContent">The title content of AlertDialog.</param>
555 /// <param name="content">The content of AlertDialog.</param>
556 /// <param name="actionContent">The action content of AlertDialog.</param>
557 [EditorBrowsable(EditorBrowsableState.Never)]
558 [SuppressMessage("Microsoft.Reliability",
559 "CA2000:DisposeObjectsBeforeLosingScope",
560 Justification = "The pushed views are added to NavigationPages and are disposed in Navigator.Dispose().")]
561 public static void ShowAlertDialog(View titleContent, View content, View actionContent)
563 var window = NUIApplication.GetDefaultWindow();
564 var defaultNavigator = window.GetDefaultNavigator();
566 var dialog = new AlertDialog(titleContent, content, actionContent);
567 SetDialogScrim(dialog);
569 var dialogPage = new Page(dialog);
570 defaultNavigator.Push(dialogPage);
574 /// Shows an alert dialog by pushing a page containing the alert dialog
575 /// to default navigator.
577 /// <param name="title">The title of AlertDialog.</param>
578 /// <param name="message">The message of AlertDialog.</param>
579 /// <param name="positiveButtonText">The positive button text in the action content of AlertDialog.</param>
580 /// <param name="positiveButtonClickedHandler">The clicked callback of the positive button in the action content of AlertDialog.</param>
581 /// <param name="negativeButtonText">The negative button text in the action content of AlertDialog.</param>
582 /// <param name="negativeButtonClickedHandler">The clicked callback of the negative button in the action content of AlertDialog.</param>
583 [EditorBrowsable(EditorBrowsableState.Never)]
584 [SuppressMessage("Microsoft.Reliability",
585 "CA2000:DisposeObjectsBeforeLosingScope",
586 Justification = "The pushed views are added to NavigationPages and are disposed in Navigator.Dispose().")]
587 public static void ShowAlertDialog(string title = null, string message = null, string positiveButtonText = null, EventHandler<ClickedEventArgs> positiveButtonClickedHandler = null, string negativeButtonText = null, EventHandler<ClickedEventArgs> negativeButtonClickedHandler = null)
589 var window = NUIApplication.GetDefaultWindow();
590 var defaultNavigator = window.GetDefaultNavigator();
592 var dialog = new AlertDialog(title, message, positiveButtonText, positiveButtonClickedHandler, negativeButtonText, negativeButtonClickedHandler);
593 SetDialogScrim(dialog);
595 var dialogPage = new Page(dialog);
596 defaultNavigator.Push(dialogPage);
600 /// Create Transition between currentTopPage and newTopPage
602 /// <param name="currentTopPage">The top page of Navigator.</param>
603 /// <param name="newTopPage">The new top page after transition.</param>
604 /// <param name="pushTransition">True if this transition is for push new page</param>
605 private TransitionSet CreateTransition(Page currentTopPage, Page newTopPage, bool pushTransition)
607 currentTopPage.SetVisible(true);
608 newTopPage.SetVisible(true);
610 List<View> taggedViewsInNewTopPage = new List<View>();
611 RetrieveTaggedViews(taggedViewsInNewTopPage, newTopPage.Content);
612 List<View> taggedViewsInCurrentTopPage = new List<View>();
613 RetrieveTaggedViews(taggedViewsInCurrentTopPage, currentTopPage.Content);
615 List<KeyValuePair<View, View>> sameTaggedViewPair = new List<KeyValuePair<View, View>>();
616 foreach(View currentTopPageView in taggedViewsInCurrentTopPage)
618 bool findPair = false;
619 foreach(View newTopPageView in taggedViewsInNewTopPage)
621 if((currentTopPageView.TransitionOptions != null) && (newTopPageView.TransitionOptions != null) &&
622 currentTopPageView.TransitionOptions?.TransitionTag == newTopPageView.TransitionOptions?.TransitionTag)
624 sameTaggedViewPair.Add(new KeyValuePair<View, View>(currentTopPageView, newTopPageView));
631 taggedViewsInNewTopPage.Remove(sameTaggedViewPair[sameTaggedViewPair.Count - 1].Value);
634 foreach(KeyValuePair<View, View> pair in sameTaggedViewPair)
636 taggedViewsInCurrentTopPage.Remove(pair.Key);
639 TransitionSet newTransitionSet = new TransitionSet();
640 foreach(KeyValuePair<View, View> pair in sameTaggedViewPair)
642 TransitionItem pairTransition = transition.CreateTransition(pair.Key, pair.Value);
643 if(pair.Value.TransitionOptions?.TransitionWithChild ?? false)
645 pairTransition.TransitionWithChild = true;
647 newTransitionSet.AddTransition(pairTransition);
649 newTransitionSet.Play();
651 newTransitionSet.Finished += (object sender, EventArgs e) =>
653 transitionFinished = true;
654 InvokeTransitionFinished();
655 transitionSet.Dispose();
656 currentTopPage.Opacity = 1.0f;
659 // default entering/exit transition - fast fade (half duration compaired with that of view pair transition)
660 float duration = (transition.TimePeriod.DurationSeconds + transition.TimePeriod.DelaySeconds) * 0.8f;
661 Animation fade = new Animation(duration);
662 fade.AnimateTo(currentTopPage, "Opacity", 0.0f);
663 KeyFrames keyframes = new KeyFrames();
664 keyframes.Add(0.0f, 0.0f);
665 keyframes.Add(1.0f, 1.0f);
666 fade.AnimateBetween(newTopPage, "Opacity", keyframes);
669 return newTransitionSet;
672 private static void SetDialogScrim(Dialog dialog)
679 var window = NUIApplication.GetDefaultWindow();
680 var defaultNavigator = window.GetDefaultNavigator();
681 var defaultScrim = dialog.Scrim;
683 //Copies default scrim's GUI properties.
684 var scrim = new VisualView();
685 scrim.BackgroundColor = defaultScrim.BackgroundColor;
686 scrim.Size = defaultScrim.Size;
687 scrim.TouchEvent += (object source, View.TouchEventArgs e) =>
689 if (e.Touch.GetState(0) == PointStateType.Up)
691 defaultNavigator.Pop();
697 dialog.Scrim = scrim;
702 /// Retrieve Tagged Views in the view tree.
704 /// <param name="taggedViews">Returned tagged view list..</param>
705 /// <param name="view">Root View to get tagged child View.</param>
706 private void RetrieveTaggedViews(List<View> taggedViews, View view)
708 if (!string.IsNullOrEmpty(view.TransitionOptions?.TransitionTag))
710 taggedViews.Add((view as View));
713 if (view.ChildCount == 0)
718 if (view.TransitionOptions?.TransitionWithChild ?? false)
723 foreach (View child in view.Children)
725 RetrieveTaggedViews(taggedViews, child);
729 internal void InvokeTransitionFinished()
731 TransitionFinished?.Invoke(this, new EventArgs());
734 } //namespace Tizen.NUI